Ma Suhyeon

Implement two more APIs

# Created by https://www.toptal.com/developers/gitignore/api/go,vscode
# Edit at https://www.toptal.com/developers/gitignore?templates=go,vscode
### Go ###
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
### Go Patch ###
/vendor/
/Godeps/
### vscode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# End of https://www.toptal.com/developers/gitignore/api/go,vscode
__debug_bin
config.json
# Created by https://www.toptal.com/developers/gitignore/api/go,vscode
# Edit at https://www.toptal.com/developers/gitignore?templates=go,vscode
### Go ###
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
### Go Patch ###
/vendor/
/Godeps/
### vscode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# End of https://www.toptal.com/developers/gitignore/api/go,vscode
__debug_bin
config.json
data
\ No newline at end of file
......
package main
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
type Prop int
const (
PropUserNo Prop = iota
)
type App struct {
Config Config
db *sqlx.DB
router *mux.Router
}
func NewApp(config Config) *App {
app := new(App)
app.Config = config
dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s", config.Database.User, config.Database.Password, config.Database.Host, config.Database.Name)
app.db = sqlx.MustOpen("mysql", dsn)
app.router = mux.NewRouter()
app.router.HandleFunc("/users", app.PostUsers).Methods("POST")
app.router.HandleFunc("/users/tokens", app.PostTokens).Methods("POST")
app.router.Handle("/extractions", app.WithAuth(app.PostExtractions)).Methods("Post")
return app
}
func (app *App) Serve() {
http.ListenAndServe(fmt.Sprintf(":%d", app.Config.Port), app.router)
}
package main
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
type Prop int
const (
PropUserNo Prop = iota
)
type App struct {
Config Config
db *sqlx.DB
router *mux.Router
}
func NewApp(config Config) *App {
app := new(App)
app.Config = config
dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s", config.Database.User, config.Database.Password, config.Database.Host, config.Database.Name)
app.db = sqlx.MustOpen("mysql", dsn)
app.router = mux.NewRouter()
app.router.HandleFunc("/users", app.PostUsers).Methods("POST")
app.router.HandleFunc("/users/tokens", app.PostTokens).Methods("POST")
app.router.Handle("/extractions", app.WithAuth(app.PostExtractions)).Methods("Post")
app.router.HandleFunc("/extractions/{file}/calls", app.GetCalls).Methods("GET")
app.router.HandleFunc("/extractions/{file}/messages", app.GetMessages).Methods("GET")
app.router.HandleFunc("/extractions/{file}/calls/analyses", app.GetCallsAnalyses).Methods("GET")
app.router.HandleFunc("/extractions/{file}/apps/analyses", app.GetAppsAnalyses).Methods("GET")
app.router.HandleFunc("/extractions/{file}/messages/analyses", app.GetMessagesAnalyses).Methods("GET")
return app
}
func (app *App) Serve() {
http.ListenAndServe(fmt.Sprintf(":%d", app.Config.Port), app.router)
}
......
package main
import (
"encoding/json"
"io/ioutil"
)
type Config struct {
Port int `json:"port"`
Database struct {
Host string `json:"host"`
Name string `json:"name"`
User string `json:"user"`
Password string `json:"password"`
} `json:"database"`
TokenSecret string `json:"token_secret"`
}
func LoadConfig(path string) (Config, error) {
config := Config{}
data, err := ioutil.ReadFile(path)
if err == nil {
err = json.Unmarshal(data, &config)
}
return config, err
}
package main
import (
"encoding/json"
"io/ioutil"
)
type Config struct {
Port int `json:"port"`
Database struct {
Host string `json:"host"`
Name string `json:"name"`
User string `json:"user"`
Password string `json:"password"`
} `json:"database"`
TokenSecret string `json:"token_secret"`
}
func LoadConfig(path string) (Config, error) {
config := Config{}
data, err := ioutil.ReadFile(path)
if err == nil {
err = json.Unmarshal(data, &config)
}
return config, err
}
......
package main
import (
"fmt"
"net/http"
"time"
"github.com/gorilla/mux"
"github.com/jmoiron/sqlx"
_ "github.com/mattn/go-sqlite3"
)
type Call struct {
ID int `json:"id" db:"id"`
Type int `json:"type" db:"type"`
Name *string `json:"name" db:"name"`
Number int `json:"number" db:"number"`
Duration int `json:"duration" db:"duration"`
Date Time `json:"date" db:"date"`
}
func (app *App) GetCalls(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
calls := []Call{}
db, err := sqlx.Connect("sqlite3", fmt.Sprintf("data/1/%s", vars["file"]))
if err != nil {
WriteError(w, http.StatusInternalServerError, "Could not open db file")
return
}
defer db.Close()
query := `SELECT * FROM calllog`
fmt.Println(db.Select(&calls, query))
WriteJson(w, calls)
}
type CallStats struct {
Number string `json:"number" db:"number"`
Name *string `json:"name" db:"name"`
Incoming int `json:"incoming" db:"incoming"`
Outgoing int `json:"outgoing" db:"outgoing"`
Duration int `json:"duration" db:"duration"`
}
func (app *App) GetCallsAnalyses(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
calls := []CallStats{}
db, err := sqlx.Connect("sqlite3", fmt.Sprintf("data/1/%s", vars["file"]))
if err != nil {
WriteError(w, http.StatusInternalServerError, "Could not open db file")
return
}
defer db.Close()
query := `SELECT number, name,
(SELECT COUNT(1) FROM calllog s WHERE s.number=c.number AND s.type=1) incoming,
(SELECT COUNT(1) FROM calllog s WHERE s.number=c.number AND s.type=2) outgoing,
SUM(duration) duration
FROM calllog c GROUP BY number ORDER BY duration DESC`
db.Select(&calls, query)
WriteJson(w, calls)
}
type AppInfo struct {
PackageName string `json:"package_name" db:"packagename"`
Name string `json:"name" db:"name"`
Version string `json:"version" db:"version"`
WifiUsage int `json:"wifi_usage" db:"wifiusage"`
CellularUsage int `json:"cellular_usage" db:"cellularusage"`
LastUsed time.Time `json:"last_used" db:"lasttimeused"`
ForegroundTime int `json:"foreground_time" db:"totaltimeforeground"`
}
func (app *App) GetAppsAnalyses(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
apps := []AppInfo{}
db, err := sqlx.Connect("sqlite3", fmt.Sprintf("data/1/%s", vars["file"]))
if err != nil {
WriteError(w, http.StatusInternalServerError, "Could not open db file")
return
}
defer db.Close()
query := `SELECT
a.packagename, a.name, a.version, a.wifiusage, a.cellularusage,
u.lasttimeused, u.totaltimeforeground
FROM AppInfo a JOIN AppUsageYear u
ORDER BY totaltimeforeground DESC`
db.Select(&apps, query)
WriteJson(w, apps)
}
type Message struct {
ID int `json:"id" db:"mid"`
Type int `json:"type" db:"type"`
Address string `json:"address"`
Body string `json:"body"`
Date Time `json:"date" db:"date"`
}
func (app *App) GetMessages(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
messages := []Message{}
db, err := sqlx.Connect("sqlite3", fmt.Sprintf("data/1/%s", vars["file"]))
if err != nil {
WriteError(w, http.StatusInternalServerError, "Could not open db file")
return
}
defer db.Close()
query := `SELECT mid, type, address, body, date FROM sms`
db.Select(&messages, query)
WriteJson(w, messages)
}
type MessageStats struct {
Address string `json:"number" db:"number"`
Receive int `json:"incoming" db:"incoming"`
Send int `json:"outgoing" db:"outgoing"`
}
func (app *App) GetMessagesAnalyses(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
messages := []MessageStats{}
db, err := sqlx.Connect("sqlite3", fmt.Sprintf("data/1/%s", vars["file"]))
if err != nil {
WriteError(w, http.StatusInternalServerError, "Could not open db file")
return
}
defer db.Close()
query := `SELECT address,
(SELECT COUNT(1) FROM sms m WHERE m.address=s.address AND m.type=1) receive,
(SELECT COUNT(1) FROM sms m WHERE m.address=s.address AND m.type=2) send
FROM sms s GROUP BY address ORDER BY receive + send DESC`
db.Select(&messages, query)
WriteJson(w, messages)
}
package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
"github.com/google/uuid"
)
func (app *App) PostExtractions(w http.ResponseWriter, r *http.Request) {
userNo := r.Context().Value(PropUserNo).(uint64)
r.ParseMultipartForm(32 << 20)
form, _, err := r.FormFile("file")
if err != nil {
WriteError(w, http.StatusInternalServerError, "Unknown error")
return
}
defer form.Close()
dir := fmt.Sprintf("data/%d", userNo)
os.MkdirAll(dir, 0644)
name := strings.Replace(uuid.New().String(), "-", "", -1)
file, err := os.Create(fmt.Sprintf("%s/%s", dir, name))
if err != nil {
WriteError(w, http.StatusInternalServerError, "Unknown error")
return
}
defer file.Close()
_, err = io.Copy(file, form)
if err != nil {
WriteError(w, http.StatusInternalServerError, "Unknown error")
return
}
w.Write([]byte("success"))
}
package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
"github.com/google/uuid"
)
func (app *App) PostExtractions(w http.ResponseWriter, r *http.Request) {
userNo := r.Context().Value(PropUserNo).(uint64)
r.ParseMultipartForm(32 << 20)
form, _, err := r.FormFile("file")
if err != nil {
WriteError(w, http.StatusInternalServerError, "Unknown error")
return
}
defer form.Close()
dir := fmt.Sprintf("data/%d", userNo)
os.MkdirAll(dir, 0644)
name := strings.Replace(uuid.New().String(), "-", "", -1)
file, err := os.Create(fmt.Sprintf("%s/%s", dir, name))
if err != nil {
WriteError(w, http.StatusInternalServerError, "Unknown error")
return
}
defer file.Close()
_, err = io.Copy(file, form)
if err != nil {
WriteError(w, http.StatusInternalServerError, "Unknown error")
return
}
w.Write([]byte("success"))
}
......
......@@ -8,5 +8,6 @@ require (
github.com/google/uuid v1.1.2
github.com/gorilla/mux v1.8.0
github.com/jmoiron/sqlx v1.2.0
github.com/mattn/go-sqlite3 v1.9.0
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
)
......
......@@ -11,6 +11,7 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7
github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
......
package main
import (
"log"
)
func main() {
config, err := LoadConfig("config.json")
if err != nil {
log.Fatal(err)
}
app := NewApp(config)
app.Serve()
}
package main
import (
"log"
)
func main() {
config, err := LoadConfig("config.json")
if err != nil {
log.Fatal(err)
}
app := NewApp(config)
app.Serve()
}
......
package main
import (
"time"
)
type Time time.Time
func (t *Time) Scan(v interface{}) error {
p, err := time.Parse("2006-01-02 15:04:05", string(v.([]byte)))
*t = Time(p)
return err
}
func (t *Time) MarshalJSON() ([]byte, error) {
return time.Time(*t).MarshalJSON()
}
package main
import (
"context"
"encoding/json"
"net/http"
"strings"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/go-sql-driver/mysql"
"golang.org/x/crypto/sha3"
)
type User struct {
No uint64 `json:"no"`
ID string `json:"id"`
Name string `json:"name"`
CreatedAt time.Time `json:"created_at"`
ExpiredAt time.Time `json:"expired_at"`
}
func (app *App) PostUsers(w http.ResponseWriter, r *http.Request) {
body := make(map[string]interface{})
err := json.NewDecoder(r.Body).Decode(&body)
if err != nil {
WriteError(w, http.StatusBadRequest, "Failed to parse request json")
return
}
hash := sha3.Sum256([]byte(body["password"].(string)))
res, err := app.db.Exec("INSERT INTO users (`id`, `password`, `name`) VALUES (?, ?, ?)", body["id"], hash[:], body["name"])
if err != nil {
if merr, ok := err.(*mysql.MySQLError); ok {
if merr.Number == 1062 {
WriteError(w, http.StatusConflict, "Already registered")
return
}
}
WriteError(w, http.StatusInternalServerError, "Failed to register")
return
}
no, _ := res.LastInsertId()
WriteJson(w, map[string]interface{}{"user_no": no})
}
type AuthClaims struct {
UserNo uint64 `json:"user_no"`
jwt.StandardClaims
}
func (app *App) PostTokens(w http.ResponseWriter, r *http.Request) {
body := make(map[string]interface{})
err := json.NewDecoder(r.Body).Decode(&body)
if err != nil {
WriteError(w, http.StatusBadRequest, "Failed to parse request json")
return
}
hash := sha3.Sum256([]byte(body["password"].(string)))
rows, err := app.db.Query("SELECT `no` FROM users WHERE `id`=? AND `password`=?", body["id"], hash[:])
if err != nil {
WriteError(w, http.StatusInternalServerError, "Failed to register")
return
}
if !rows.Next() {
WriteError(w, http.StatusUnauthorized, "Login failed")
return
}
no := uint64(0)
rows.Scan(&no)
token := jwt.NewWithClaims(jwt.SigningMethodHS256, AuthClaims{UserNo: no})
auth, err := token.SignedString([]byte(app.Config.TokenSecret))
if err != nil {
WriteError(w, http.StatusInternalServerError, "Login failed")
return
}
WriteJson(w, map[string]interface{}{"token": auth})
}
func (app *App) WithAuth(next func(http.ResponseWriter, *http.Request)) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
auth := r.Header.Get("Authorization")
if len(auth) > 6 && strings.Index(auth, "Bearer ") == 0 {
token, err := jwt.ParseWithClaims(auth[7:], &AuthClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(app.Config.TokenSecret), nil
})
if err == nil {
claims := token.Claims.(*AuthClaims)
ctx := context.WithValue(r.Context(), PropUserNo, claims.UserNo)
next(w, r.WithContext(ctx))
return
}
}
WriteError(w, http.StatusUnauthorized, "Authorization failed")
})
}
package main
import (
"context"
"encoding/json"
"net/http"
"strings"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/go-sql-driver/mysql"
"golang.org/x/crypto/sha3"
)
type User struct {
No uint64 `json:"no"`
ID string `json:"id"`
Name string `json:"name"`
CreatedAt time.Time `json:"created_at"`
ExpiredAt time.Time `json:"expired_at"`
}
func (app *App) PostUsers(w http.ResponseWriter, r *http.Request) {
body := make(map[string]interface{})
err := json.NewDecoder(r.Body).Decode(&body)
if err != nil {
WriteError(w, http.StatusBadRequest, "Failed to parse request json")
return
}
hash := sha3.Sum256([]byte(body["password"].(string)))
res, err := app.db.Exec("INSERT INTO users (`id`, `password`, `name`) VALUES (?, ?, ?)", body["id"], hash[:], body["name"])
if err != nil {
if merr, ok := err.(*mysql.MySQLError); ok {
if merr.Number == 1062 {
WriteError(w, http.StatusConflict, "Already registered")
return
}
}
WriteError(w, http.StatusInternalServerError, "Failed to register")
return
}
no, _ := res.LastInsertId()
WriteJson(w, map[string]interface{}{"user_no": no})
}
type AuthClaims struct {
UserNo uint64 `json:"user_no"`
jwt.StandardClaims
}
func (app *App) PostTokens(w http.ResponseWriter, r *http.Request) {
body := make(map[string]interface{})
err := json.NewDecoder(r.Body).Decode(&body)
if err != nil {
WriteError(w, http.StatusBadRequest, "Failed to parse request json")
return
}
hash := sha3.Sum256([]byte(body["password"].(string)))
rows, err := app.db.Query("SELECT `no` FROM users WHERE `id`=? AND `password`=?", body["id"], hash[:])
if err != nil {
WriteError(w, http.StatusInternalServerError, "Failed to register")
return
}
if !rows.Next() {
WriteError(w, http.StatusUnauthorized, "Login failed")
return
}
no := uint64(0)
rows.Scan(&no)
token := jwt.NewWithClaims(jwt.SigningMethodHS256, AuthClaims{UserNo: no})
auth, err := token.SignedString([]byte(app.Config.TokenSecret))
if err != nil {
WriteError(w, http.StatusInternalServerError, "Login failed")
return
}
WriteJson(w, map[string]interface{}{"token": auth})
}
func (app *App) WithAuth(next func(http.ResponseWriter, *http.Request)) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
auth := r.Header.Get("Authorization")
if len(auth) > 6 && strings.Index(auth, "Bearer ") == 0 {
token, err := jwt.ParseWithClaims(auth[7:], &AuthClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(app.Config.TokenSecret), nil
})
if err == nil {
claims := token.Claims.(*AuthClaims)
ctx := context.WithValue(r.Context(), PropUserNo, claims.UserNo)
next(w, r.WithContext(ctx))
return
}
}
WriteError(w, http.StatusUnauthorized, "Authorization failed")
})
}
......
package main
import (
"encoding/json"
"net/http"
)
func WriteJson(w http.ResponseWriter, data interface{}) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(data)
}
func WriteError(w http.ResponseWriter, status int, message string) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(map[string]interface{}{"msg": message})
}
package main
import (
"encoding/json"
"net/http"
)
func WriteJson(w http.ResponseWriter, data interface{}) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(data)
}
func WriteError(w http.ResponseWriter, status int, message string) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(map[string]interface{}{"msg": message})
}
......