Showing
11 changed files
with
720 additions
and
0 deletions
src/server/.gitignore
0 → 100644
1 | + | ||
2 | +# Created by https://www.toptal.com/developers/gitignore/api/go,vscode | ||
3 | +# Edit at https://www.toptal.com/developers/gitignore?templates=go,vscode | ||
4 | + | ||
5 | +### Go ### | ||
6 | +# Binaries for programs and plugins | ||
7 | +*.exe | ||
8 | +*.exe~ | ||
9 | +*.dll | ||
10 | +*.so | ||
11 | +*.dylib | ||
12 | + | ||
13 | +# Test binary, built with `go test -c` | ||
14 | +*.test | ||
15 | + | ||
16 | +# Output of the go coverage tool, specifically when used with LiteIDE | ||
17 | +*.out | ||
18 | + | ||
19 | +# Dependency directories (remove the comment below to include it) | ||
20 | +# vendor/ | ||
21 | + | ||
22 | +### Go Patch ### | ||
23 | +/vendor/ | ||
24 | +/Godeps/ | ||
25 | + | ||
26 | +### vscode ### | ||
27 | +.vscode/* | ||
28 | +!.vscode/settings.json | ||
29 | +!.vscode/tasks.json | ||
30 | +!.vscode/launch.json | ||
31 | +!.vscode/extensions.json | ||
32 | +*.code-workspace | ||
33 | + | ||
34 | +# End of https://www.toptal.com/developers/gitignore/api/go,vscode | ||
35 | + | ||
36 | +__debug_bin | ||
37 | +config.json | ||
38 | +data | ||
39 | +mf-server | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
src/server/app.go
0 → 100644
1 | +package main | ||
2 | + | ||
3 | +import ( | ||
4 | + "fmt" | ||
5 | + | ||
6 | + "github.com/labstack/echo/v4" | ||
7 | + "github.com/labstack/echo/v4/middleware" | ||
8 | + | ||
9 | + _ "github.com/go-sql-driver/mysql" | ||
10 | + "github.com/jmoiron/sqlx" | ||
11 | +) | ||
12 | + | ||
13 | +type Prop int | ||
14 | + | ||
15 | +const ( | ||
16 | + PropUserNo Prop = iota | ||
17 | +) | ||
18 | + | ||
19 | +type App struct { | ||
20 | + Config Config | ||
21 | + db *sqlx.DB | ||
22 | + echo *echo.Echo | ||
23 | +} | ||
24 | + | ||
25 | +func NewApp(config Config) *App { | ||
26 | + app := new(App) | ||
27 | + app.Config = config | ||
28 | + | ||
29 | + dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?parseTime=true", config.Database.User, config.Database.Password, config.Database.Host, config.Database.Name) | ||
30 | + app.db = sqlx.MustOpen("mysql", dsn) | ||
31 | + | ||
32 | + auth := middleware.JWTWithConfig(middleware.JWTConfig{ | ||
33 | + SigningKey: []byte(config.TokenSecret), | ||
34 | + Claims: &AuthClaims{}, | ||
35 | + }) | ||
36 | + | ||
37 | + app.echo = echo.New() | ||
38 | + app.echo.POST("/users", app.PostUsers) | ||
39 | + app.echo.POST("/users/tokens", app.PostTokens) | ||
40 | + app.echo.POST("/extractions", app.PostExtractions, auth) | ||
41 | + | ||
42 | + extraction := app.echo.Group("/extractions/:no") | ||
43 | + extraction.GET("/calls", app.GetCalls) | ||
44 | + extraction.GET("/messages", app.GetMessages) | ||
45 | + extraction.GET("/calls/analyses", app.GetCallsAnalyses) | ||
46 | + extraction.GET("/apps/analyses", app.GetAppsAnalyses) | ||
47 | + extraction.GET("/messages/analyses", app.GetMessagesAnalyses) | ||
48 | + extraction.GET("/processes", app.GetProcesses) | ||
49 | + extraction.GET("/alarms", app.GetAlarms) | ||
50 | + | ||
51 | + return app | ||
52 | +} | ||
53 | + | ||
54 | +func (app *App) Serve() { | ||
55 | + app.echo.Logger.Fatal(app.echo.Start(fmt.Sprintf(":%d", app.Config.Port))) | ||
56 | +} |
src/server/config.go
0 → 100644
1 | +package main | ||
2 | + | ||
3 | +import ( | ||
4 | + "encoding/json" | ||
5 | + "io/ioutil" | ||
6 | +) | ||
7 | + | ||
8 | +type Config struct { | ||
9 | + Port int `json:"port"` | ||
10 | + Database struct { | ||
11 | + Host string `json:"host"` | ||
12 | + Name string `json:"name"` | ||
13 | + User string `json:"user"` | ||
14 | + Password string `json:"password"` | ||
15 | + } `json:"database"` | ||
16 | + TokenSecret string `json:"token_secret"` | ||
17 | +} | ||
18 | + | ||
19 | +func LoadConfig(path string) (Config, error) { | ||
20 | + config := Config{} | ||
21 | + | ||
22 | + data, err := ioutil.ReadFile(path) | ||
23 | + if err == nil { | ||
24 | + err = json.Unmarshal(data, &config) | ||
25 | + } | ||
26 | + | ||
27 | + return config, err | ||
28 | +} |
src/server/data.go
0 → 100644
1 | +package main | ||
2 | + | ||
3 | +import ( | ||
4 | + "fmt" | ||
5 | + "net/http" | ||
6 | + "strconv" | ||
7 | + "strings" | ||
8 | + "time" | ||
9 | + | ||
10 | + "github.com/jmoiron/sqlx" | ||
11 | + "github.com/labstack/echo/v4" | ||
12 | + | ||
13 | + _ "github.com/mattn/go-sqlite3" | ||
14 | +) | ||
15 | + | ||
16 | +type Call struct { | ||
17 | + ID int `json:"id" db:"id"` | ||
18 | + Type int `json:"type" db:"type"` | ||
19 | + Name *string `json:"name" db:"name"` | ||
20 | + Number int `json:"number" db:"number"` | ||
21 | + Duration int `json:"duration" db:"duration"` | ||
22 | + Date time.Time `json:"date" db:"date"` | ||
23 | +} | ||
24 | + | ||
25 | +func (app *App) GetCalls(c echo.Context) error { | ||
26 | + calls := []Call{} | ||
27 | + | ||
28 | + query := `SELECT * FROM calls WHERE extraction_no=?` | ||
29 | + app.db.Unsafe().Select(&calls, query, c.Param("no")) | ||
30 | + | ||
31 | + return c.JSON(http.StatusOK, calls) | ||
32 | +} | ||
33 | + | ||
34 | +type CallStats struct { | ||
35 | + Number string `json:"number" db:"number"` | ||
36 | + Name *string `json:"name" db:"name"` | ||
37 | + Incoming int `json:"incoming" db:"incoming"` | ||
38 | + Outgoing int `json:"outgoing" db:"outgoing"` | ||
39 | + Duration int `json:"duration" db:"duration"` | ||
40 | +} | ||
41 | + | ||
42 | +func (app *App) GetCallsAnalyses(c echo.Context) error { | ||
43 | + calls := []CallStats{} | ||
44 | + | ||
45 | + query := `SELECT number, name, | ||
46 | + (SELECT COUNT(1) FROM calls s WHERE s.extraction_no=c.extraction_no AND s.number=c.number AND s.type=1) incoming, | ||
47 | + (SELECT COUNT(1) FROM calls s WHERE s.extraction_no=c.extraction_no AND s.number=c.number AND s.type=2) outgoing, | ||
48 | + SUM(duration) duration | ||
49 | + FROM calls c WHERE extraction_no=? GROUP BY number ORDER BY duration DESC` | ||
50 | + app.db.Select(&calls, query, c.Param("no")) | ||
51 | + | ||
52 | + return c.JSON(http.StatusOK, calls) | ||
53 | +} | ||
54 | + | ||
55 | +type AppInfo struct { | ||
56 | + PackageName string `json:"package_name" db:"package_name"` | ||
57 | + Name string `json:"name" db:"name"` | ||
58 | + Version string `json:"version" db:"version"` | ||
59 | + WifiUsage int `json:"wifi_usage" db:"wifi_usage"` | ||
60 | + CellularUsage int `json:"cellular_usage" db:"cellular_usage"` | ||
61 | + LastUsed time.Time `json:"last_used" db:"last_used"` | ||
62 | + ForegroundTime int64 `json:"foreground_time" db:"foreground_time"` | ||
63 | +} | ||
64 | + | ||
65 | +func (app *App) GetAppsAnalyses(c echo.Context) error { | ||
66 | + apps := []AppInfo{} | ||
67 | + | ||
68 | + query := `SELECT * FROM apps WHERE extraction_no=? ORDER BY foreground_time DESC LIMIT 0, 100` | ||
69 | + app.db.Unsafe().Select(&apps, query, c.Param("no")) | ||
70 | + | ||
71 | + return c.JSON(http.StatusOK, apps) | ||
72 | +} | ||
73 | + | ||
74 | +type Message struct { | ||
75 | + ID int `json:"id" db:"id"` | ||
76 | + Type int `json:"type" db:"type"` | ||
77 | + Address string `json:"address" db:"address"` | ||
78 | + Body string `json:"body" db:"body"` | ||
79 | + Date time.Time `json:"date" db:"date"` | ||
80 | +} | ||
81 | + | ||
82 | +func (app *App) GetMessages(c echo.Context) error { | ||
83 | + messages := []Message{} | ||
84 | + | ||
85 | + query := `SELECT * FROM messages WHERE extraction_no=?` | ||
86 | + app.db.Unsafe().Select(&messages, query, c.Param("no")) | ||
87 | + | ||
88 | + return c.JSON(http.StatusOK, messages) | ||
89 | +} | ||
90 | + | ||
91 | +type MessageStats struct { | ||
92 | + Address string `json:"address" db:"address"` | ||
93 | + Receive int `json:"receive" db:"receive"` | ||
94 | + Send int `json:"send" db:"send"` | ||
95 | +} | ||
96 | + | ||
97 | +func (app *App) GetMessagesAnalyses(c echo.Context) error { | ||
98 | + messages := []MessageStats{} | ||
99 | + | ||
100 | + query := `SELECT address, | ||
101 | + (SELECT COUNT(1) FROM messages m WHERE m.extraction_no=s.extraction_no AND m.address=s.address AND m.type=1) receive, | ||
102 | + (SELECT COUNT(1) FROM messages m WHERE m.extraction_no=s.extraction_no AND m.address=s.address AND m.type=2) send | ||
103 | + FROM messages s WHERE extraction_no=? GROUP BY address ORDER BY receive + send DESC` | ||
104 | + app.db.Select(&messages, query, c.Param("no")) | ||
105 | + | ||
106 | + return c.JSON(http.StatusOK, messages) | ||
107 | +} | ||
108 | + | ||
109 | +type Process struct { | ||
110 | + UID string `json:"uid" db:"UID"` | ||
111 | + PID int `json:"pid" db:"PID"` | ||
112 | + PPID int `json:"ppid" db:"PPID"` | ||
113 | + STIME string `json:"stime" db:"STIME"` | ||
114 | + TIME string `json:"time" db:"TIME"` | ||
115 | + CMD string `json:"cmd" db:"CMD"` | ||
116 | +} | ||
117 | + | ||
118 | +func (app *App) GetProcesses(c echo.Context) error { | ||
119 | + processes := []Process{} | ||
120 | + db, err := sqlx.Connect("sqlite3", fmt.Sprintf("data/1/%s", c.Param("file"))) | ||
121 | + if err != nil { | ||
122 | + return echo.NewHTTPError(http.StatusInternalServerError, "Could not open db file") | ||
123 | + } | ||
124 | + defer db.Close() | ||
125 | + | ||
126 | + query := `SELECT UID, CAST(PID AS INTEGER) PID, CAST(PPID AS INTEGER) PPID, STIME, TIME, CMD FROM process WHERE UID LIKE 'u%' ORDER BY TIME DESC` | ||
127 | + db.Select(&processes, query) | ||
128 | + | ||
129 | + return c.JSON(http.StatusOK, processes) | ||
130 | +} | ||
131 | + | ||
132 | +type Alarm struct { | ||
133 | + ID string `json:"id"` | ||
134 | + When time.Time `json:"when"` | ||
135 | + History []AlarmHistory `json:"history"` | ||
136 | +} | ||
137 | + | ||
138 | +type AlarmHistory struct { | ||
139 | + Type string `json:"type"` | ||
140 | + When time.Time `json:"when"` | ||
141 | +} | ||
142 | + | ||
143 | +func (app *App) GetAlarms(c echo.Context) error { | ||
144 | + db, err := sqlx.Connect("sqlite3", fmt.Sprintf("data/1/%s", c.Param("file"))) | ||
145 | + if err != nil { | ||
146 | + return echo.NewHTTPError(http.StatusInternalServerError, "Could not open db file") | ||
147 | + } | ||
148 | + defer db.Close() | ||
149 | + | ||
150 | + alarms := map[string]Alarm{} | ||
151 | + rows, _ := db.Queryx("SELECT * FROM alarm ORDER BY TIME") | ||
152 | + | ||
153 | + for rows.Next() { | ||
154 | + var tm string | ||
155 | + var typ string | ||
156 | + var detail string | ||
157 | + | ||
158 | + rows.Scan(&tm, &typ, &detail) | ||
159 | + | ||
160 | + detail = detail[strings.Index(detail, "{")+1 : strings.Index(detail, "}")] | ||
161 | + s := strings.Split(detail, " ") | ||
162 | + timestamp, _ := strconv.ParseInt(s[4], 10, 64) | ||
163 | + timestamp /= 1000 | ||
164 | + | ||
165 | + if _, ok := alarms[s[0]]; !ok { | ||
166 | + alarms[s[0]] = Alarm{ID: s[0], When: time.Unix(timestamp, 0)} | ||
167 | + } | ||
168 | + | ||
169 | + when, _ := time.Parse("2006-01-02 15:04:05", tm) | ||
170 | + alarm := alarms[s[0]] | ||
171 | + alarm.History = append(alarms[s[0]].History, AlarmHistory{ | ||
172 | + Type: typ, | ||
173 | + When: when, | ||
174 | + }) | ||
175 | + alarms[s[0]] = alarm | ||
176 | + } | ||
177 | + | ||
178 | + results := []Alarm{} | ||
179 | + for _, v := range alarms { | ||
180 | + results = append(results, v) | ||
181 | + } | ||
182 | + | ||
183 | + return c.JSON(http.StatusOK, results) | ||
184 | +} |
src/server/extraction.go
0 → 100644
1 | +package main | ||
2 | + | ||
3 | +import ( | ||
4 | + "fmt" | ||
5 | + "io" | ||
6 | + "io/ioutil" | ||
7 | + "net/http" | ||
8 | + "os" | ||
9 | + | ||
10 | + "github.com/dgrijalva/jwt-go" | ||
11 | + "github.com/jmoiron/sqlx" | ||
12 | + "github.com/labstack/echo/v4" | ||
13 | +) | ||
14 | + | ||
15 | +func (app *App) PostExtractions(c echo.Context) error { | ||
16 | + userNo := c.Get("user").(*jwt.Token).Claims.(*AuthClaims).UserNo | ||
17 | + | ||
18 | + form, err := c.FormFile("file") | ||
19 | + if err != nil { | ||
20 | + return echo.NewHTTPError(http.StatusInternalServerError, "Unknown error") | ||
21 | + } | ||
22 | + | ||
23 | + src, err := form.Open() | ||
24 | + if err != nil { | ||
25 | + return echo.NewHTTPError(http.StatusInternalServerError, "Unknown error") | ||
26 | + } | ||
27 | + defer src.Close() | ||
28 | + | ||
29 | + file, err := ioutil.TempFile("", "extraction") | ||
30 | + if err != nil { | ||
31 | + return echo.NewHTTPError(http.StatusInternalServerError, "Unknown error") | ||
32 | + } | ||
33 | + defer os.Remove(file.Name()) | ||
34 | + | ||
35 | + if _, err := io.Copy(file, src); err != nil { | ||
36 | + return echo.NewHTTPError(http.StatusInternalServerError, "Unknown error") | ||
37 | + } | ||
38 | + file.Close() | ||
39 | + | ||
40 | + db, err := sqlx.Connect("sqlite3", file.Name()) | ||
41 | + if err != nil { | ||
42 | + return echo.NewHTTPError(http.StatusInternalServerError, "Could not open db file") | ||
43 | + } | ||
44 | + defer db.Close() | ||
45 | + | ||
46 | + tx, err := app.db.Beginx() | ||
47 | + if err != nil { | ||
48 | + return echo.NewHTTPError(http.StatusInternalServerError, "Unknown error") | ||
49 | + } | ||
50 | + | ||
51 | + res, _ := tx.Exec("INSERT INTO extractions (`owner`) VALUES (?)", userNo) | ||
52 | + extNo, _ := res.LastInsertId() | ||
53 | + | ||
54 | + rows, err := db.Queryx("SELECT * FROM calllog") | ||
55 | + fmt.Println(err) | ||
56 | + if err == nil { | ||
57 | + for rows.Next() { | ||
58 | + vals, _ := rows.SliceScan() | ||
59 | + fmt.Println(vals) | ||
60 | + _, err = tx.Exec("INSERT INTO calls VALUES (?, ?, ?, ?, ?, ?, ?)", append([]interface{}{extNo}, vals...)...) | ||
61 | + fmt.Println(err) | ||
62 | + } | ||
63 | + } | ||
64 | + | ||
65 | + sql := `SELECT | ||
66 | + a.packagename, a.name, a.version, a.wifiusage, a.cellularusage, | ||
67 | + u.lasttimeused, u.totaltimeforeground | ||
68 | + FROM AppInfo a JOIN AppUsageYear u ON a.packagename=u.packagename` | ||
69 | + rows, err = db.Queryx(sql) | ||
70 | + if err == nil { | ||
71 | + for rows.Next() { | ||
72 | + vals, _ := rows.SliceScan() | ||
73 | + tx.Exec("INSERT INTO apps VALUES (?, ?, ?, ?, ?, ?, ?, ?)", append([]interface{}{extNo}, vals...)...) | ||
74 | + } | ||
75 | + } | ||
76 | + | ||
77 | + rows, err = db.Queryx("SELECT mid, type, address, body, date FROM sms") | ||
78 | + if err == nil { | ||
79 | + for rows.Next() { | ||
80 | + vals, _ := rows.SliceScan() | ||
81 | + tx.Exec("INSERT INTO messages VALUES (?, ?, ?, ?, ?, ?)", append([]interface{}{extNo}, vals...)...) | ||
82 | + } | ||
83 | + } | ||
84 | + | ||
85 | + rows, err = db.Queryx("SELECT PID, UID, PPID, STIME, TIME, CMD FROM process") | ||
86 | + if err == nil { | ||
87 | + for rows.Next() { | ||
88 | + vals, _ := rows.SliceScan() | ||
89 | + tx.Exec("INSERT INTO processes VALUES (?, ?, ?, ?, ?, ?, ?)", append([]interface{}{extNo}, vals...)...) | ||
90 | + } | ||
91 | + } | ||
92 | + | ||
93 | + /*alarms := map[string]Alarm{} | ||
94 | + rows, _ = db.Queryx("SELECT * FROM alarm ORDER BY TIME") | ||
95 | + | ||
96 | + for rows.Next() { | ||
97 | + var tm string | ||
98 | + var typ string | ||
99 | + var detail string | ||
100 | + | ||
101 | + rows.Scan(&tm, &typ, &detail) | ||
102 | + | ||
103 | + detail = detail[strings.Index(detail, "{")+1 : strings.Index(detail, "}")] | ||
104 | + s := strings.Split(detail, " ") | ||
105 | + timestamp, _ := strconv.ParseInt(s[4], 10, 64) | ||
106 | + timestamp /= 1000 | ||
107 | + | ||
108 | + if _, ok := alarms[s[0]]; !ok { | ||
109 | + alarms[s[0]] = Alarm{ID: s[0], When: time.Unix(timestamp, 0)} | ||
110 | + } | ||
111 | + | ||
112 | + when, _ := time.Parse("2006-01-02 15:04:05", tm) | ||
113 | + alarm := alarms[s[0]] | ||
114 | + alarm.History = append(alarms[s[0]].History, AlarmHistory{ | ||
115 | + Type: typ, | ||
116 | + When: when, | ||
117 | + }) | ||
118 | + alarms[s[0]] = alarm | ||
119 | + } | ||
120 | + | ||
121 | + for _, v := range alarms { | ||
122 | + tx.Exec("INSERT INTO alarms VALUES (?, ?, ?)", extNo, v.ID, v.When) | ||
123 | + | ||
124 | + for _, h := range v.History { | ||
125 | + tx.Exec("INSERT INTO alarm_histories VALUES (?, ?, ?, ?)", extNo, v.ID, h.Type, h.When) | ||
126 | + } | ||
127 | + }*/ | ||
128 | + | ||
129 | + tx.Commit() | ||
130 | + | ||
131 | + return c.NoContent(http.StatusNoContent) | ||
132 | +} |
src/server/go.mod
0 → 100644
1 | +module mf-server | ||
2 | + | ||
3 | +go 1.15 | ||
4 | + | ||
5 | +require ( | ||
6 | + github.com/dgrijalva/jwt-go v3.2.0+incompatible | ||
7 | + github.com/go-sql-driver/mysql v1.5.0 | ||
8 | + github.com/google/uuid v1.1.2 | ||
9 | + github.com/gorilla/handlers v1.5.1 | ||
10 | + github.com/gorilla/mux v1.8.0 | ||
11 | + github.com/jmoiron/sqlx v1.2.0 | ||
12 | + github.com/labstack/echo v3.3.10+incompatible | ||
13 | + github.com/labstack/echo/v4 v4.2.2 | ||
14 | + github.com/mattn/go-sqlite3 v1.9.0 | ||
15 | + golang.org/x/crypto v0.0.0-20201217014255-9d1352758620 | ||
16 | +) |
src/server/go.sum
0 → 100644
1 | +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
2 | +github.com/decred/dcrd/chaincfg/chainhash v1.0.2/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60= | ||
3 | +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= | ||
4 | +github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0/go.mod h1:J70FGZSbzsjecRTiTzER+3f1KZLNaXkuv+yeFTKoxM8= | ||
5 | +github.com/dgrijalva/jwt-go v1.0.2 h1:KPldsxuKGsS2FPWsNeg9ZO18aCrGKujPoWXn2yo+KQM= | ||
6 | +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= | ||
7 | +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= | ||
8 | +github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= | ||
9 | +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= | ||
10 | +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= | ||
11 | +github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= | ||
12 | +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= | ||
13 | +github.com/goccy/go-json v0.4.8 h1:TfwOxfSp8hXH+ivoOk36RyDNmXATUETRdaNWDaZglf8= | ||
14 | +github.com/goccy/go-json v0.4.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= | ||
15 | +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= | ||
16 | +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||
17 | +github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= | ||
18 | +github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= | ||
19 | +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= | ||
20 | +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= | ||
21 | +github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= | ||
22 | +github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= | ||
23 | +github.com/labstack/echo v1.4.4 h1:1bEiBNeGSUKxcPDGfZ/7IgdhJJZx8wV/pICJh4W2NJI= | ||
24 | +github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= | ||
25 | +github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= | ||
26 | +github.com/labstack/echo/v4 v4.2.2 h1:bq2fdZCionY1jck8rzUpQEu2YSmI8QbX6LHrCa60IVs= | ||
27 | +github.com/labstack/echo/v4 v4.2.2/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= | ||
28 | +github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= | ||
29 | +github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= | ||
30 | +github.com/lestrrat-go/backoff/v2 v2.0.7/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= | ||
31 | +github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ= | ||
32 | +github.com/lestrrat-go/codegen v1.0.0/go.mod h1:JhJw6OQAuPEfVKUCLItpaVLumDGWQznd1VaXrBk9TdM= | ||
33 | +github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE= | ||
34 | +github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc= | ||
35 | +github.com/lestrrat-go/jwx v1.1.7 h1:+PNt2U7FfrK4xn+ZCG+9jPRq5eqyG30gwpVwcekrCjA= | ||
36 | +github.com/lestrrat-go/jwx v1.1.7/go.mod h1:Tg2uP7bpxEHUDtuWjap/PxroJ4okxGzkQznXiG+a5Dc= | ||
37 | +github.com/lestrrat-go/option v0.0.0-20210103042652-6f1ecfceda35/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= | ||
38 | +github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4= | ||
39 | +github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= | ||
40 | +github.com/lestrrat-go/pdebug/v3 v3.0.1/go.mod h1:za+m+Ve24yCxTEhR59N7UlnJomWwCiIqbJRmKeiADU4= | ||
41 | +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= | ||
42 | +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= | ||
43 | +github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= | ||
44 | +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= | ||
45 | +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= | ||
46 | +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= | ||
47 | +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= | ||
48 | +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= | ||
49 | +github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= | ||
50 | +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= | ||
51 | +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= | ||
52 | +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||
53 | +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
54 | +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
55 | +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | ||
56 | +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= | ||
57 | +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||
58 | +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||
59 | +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= | ||
60 | +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= | ||
61 | +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= | ||
62 | +github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= | ||
63 | +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= | ||
64 | +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||
65 | +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||
66 | +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||
67 | +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||
68 | +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||
69 | +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= | ||
70 | +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||
71 | +golang.org/x/crypto v0.0.0-20201217014255-9d1352758620 h1:3wPMTskHO3+O6jqTEXyFcsnuxMQOqYSaHsDxcbUXpqA= | ||
72 | +golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= | ||
73 | +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||
74 | +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||
75 | +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||
76 | +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||
77 | +golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= | ||
78 | +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= | ||
79 | +golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= | ||
80 | +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= | ||
81 | +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
82 | +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
83 | +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
84 | +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||
85 | +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||
86 | +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= | ||
87 | +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
88 | +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
89 | +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
90 | +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
91 | +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
92 | +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
93 | +golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6 h1:DvY3Zkh7KabQE/kfzMvYvKirSiguP9Q/veMtkYyf0o8= | ||
94 | +golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
95 | +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= | ||
96 | +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
97 | +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= | ||
98 | +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||
99 | +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= | ||
100 | +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||
101 | +golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE= | ||
102 | +golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||
103 | +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||
104 | +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||
105 | +golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= | ||
106 | +golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= | ||
107 | +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||
108 | +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||
109 | +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||
110 | +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
111 | +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||
112 | +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
src/server/main.go
0 → 100644
src/server/types.go
0 → 100644
1 | +package main | ||
2 | + | ||
3 | +import ( | ||
4 | + "time" | ||
5 | +) | ||
6 | + | ||
7 | +type Time time.Time | ||
8 | + | ||
9 | +func (t *Time) Scan(v interface{}) error { | ||
10 | + p, err := time.Parse("2006-01-02 15:04:05", string(v.([]byte))) | ||
11 | + *t = Time(p) | ||
12 | + return err | ||
13 | +} | ||
14 | + | ||
15 | +func (t *Time) MarshalJSON() ([]byte, error) { | ||
16 | + return time.Time(*t).MarshalJSON() | ||
17 | +} |
src/server/user.go
0 → 100644
1 | +package main | ||
2 | + | ||
3 | +import ( | ||
4 | + "context" | ||
5 | + "net/http" | ||
6 | + "strings" | ||
7 | + "time" | ||
8 | + | ||
9 | + "github.com/dgrijalva/jwt-go" | ||
10 | + "github.com/go-sql-driver/mysql" | ||
11 | + "github.com/labstack/echo/v4" | ||
12 | + "golang.org/x/crypto/sha3" | ||
13 | +) | ||
14 | + | ||
15 | +type User struct { | ||
16 | + No uint64 `json:"no"` | ||
17 | + ID string `json:"id"` | ||
18 | + Name string `json:"name"` | ||
19 | + CreatedAt time.Time `json:"created_at"` | ||
20 | + ExpiredAt time.Time `json:"expired_at"` | ||
21 | +} | ||
22 | + | ||
23 | +func (app *App) PostUsers(c echo.Context) error { | ||
24 | + body := struct { | ||
25 | + ID string `json:"id"` | ||
26 | + Password string `json:"password"` | ||
27 | + Name string `json:"name"` | ||
28 | + }{} | ||
29 | + if c.Bind(&body) != nil { | ||
30 | + return echo.NewHTTPError(http.StatusBadRequest, "Failed to parse request json") | ||
31 | + } | ||
32 | + | ||
33 | + hash := sha3.Sum256([]byte(body.Password)) | ||
34 | + | ||
35 | + res, err := app.db.Exec("INSERT INTO users (`id`, `password`, `name`) VALUES (?, ?, ?)", body.ID, hash[:], body.Name) | ||
36 | + if err != nil { | ||
37 | + if merr, ok := err.(*mysql.MySQLError); ok { | ||
38 | + if merr.Number == 1062 { | ||
39 | + return echo.NewHTTPError(http.StatusConflict, "Already registered") | ||
40 | + } | ||
41 | + } | ||
42 | + | ||
43 | + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to register") | ||
44 | + } | ||
45 | + | ||
46 | + no, _ := res.LastInsertId() | ||
47 | + return c.JSON(http.StatusOK, echo.Map{"user_no": no}) | ||
48 | +} | ||
49 | + | ||
50 | +type AuthClaims struct { | ||
51 | + UserNo uint64 `json:"user_no"` | ||
52 | + jwt.StandardClaims | ||
53 | +} | ||
54 | + | ||
55 | +func (app *App) PostTokens(c echo.Context) error { | ||
56 | + body := struct { | ||
57 | + ID string `json:"id"` | ||
58 | + Password string `json:"password"` | ||
59 | + }{} | ||
60 | + if c.Bind(&body) != nil { | ||
61 | + return echo.NewHTTPError(http.StatusBadRequest, "Failed to parse request json") | ||
62 | + } | ||
63 | + | ||
64 | + hash := sha3.Sum256([]byte(body.Password)) | ||
65 | + rows, err := app.db.Query("SELECT `no` FROM users WHERE `id`=? AND `password`=?", body.ID, hash[:]) | ||
66 | + if err != nil { | ||
67 | + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to register") | ||
68 | + } | ||
69 | + | ||
70 | + if !rows.Next() { | ||
71 | + return echo.NewHTTPError(http.StatusUnauthorized, "Login failed") | ||
72 | + } | ||
73 | + | ||
74 | + no := uint64(0) | ||
75 | + rows.Scan(&no) | ||
76 | + | ||
77 | + token := jwt.NewWithClaims(jwt.SigningMethodHS256, AuthClaims{UserNo: no}) | ||
78 | + auth, err := token.SignedString([]byte(app.Config.TokenSecret)) | ||
79 | + if err != nil { | ||
80 | + return echo.NewHTTPError(http.StatusInternalServerError, "Login failed") | ||
81 | + } | ||
82 | + | ||
83 | + return c.JSON(http.StatusOK, map[string]interface{}{"token": auth}) | ||
84 | +} | ||
85 | + | ||
86 | +func (app *App) Authenticate(next func(http.ResponseWriter, *http.Request)) http.Handler { | ||
87 | + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
88 | + auth := r.Header.Get("Authorization") | ||
89 | + if len(auth) > 6 && strings.Index(auth, "Bearer ") == 0 { | ||
90 | + token, err := jwt.ParseWithClaims(auth[7:], &AuthClaims{}, func(token *jwt.Token) (interface{}, error) { | ||
91 | + return []byte(app.Config.TokenSecret), nil | ||
92 | + }) | ||
93 | + | ||
94 | + if err == nil { | ||
95 | + claims := token.Claims.(*AuthClaims) | ||
96 | + ctx := context.WithValue(r.Context(), PropUserNo, claims.UserNo) | ||
97 | + next(w, r.WithContext(ctx)) | ||
98 | + return | ||
99 | + } | ||
100 | + } | ||
101 | + | ||
102 | + WriteError(w, http.StatusUnauthorized, "Authorization failed") | ||
103 | + }) | ||
104 | +} |
src/server/util.go
0 → 100644
1 | +package main | ||
2 | + | ||
3 | +import ( | ||
4 | + "encoding/json" | ||
5 | + "net/http" | ||
6 | +) | ||
7 | + | ||
8 | +func WriteJson(w http.ResponseWriter, data interface{}) { | ||
9 | + w.Header().Set("Content-Type", "application/json") | ||
10 | + json.NewEncoder(w).Encode(data) | ||
11 | +} | ||
12 | + | ||
13 | +func WriteError(w http.ResponseWriter, status int, message string) { | ||
14 | + w.Header().Set("Content-Type", "application/json") | ||
15 | + w.WriteHeader(status) | ||
16 | + json.NewEncoder(w).Encode(map[string]interface{}{"msg": message}) | ||
17 | +} |
-
Please register or login to post a comment