From 2f72bfa946a24d8a0654955afdd70e1e877d91b2 Mon Sep 17 00:00:00 2001 From: LeeShuang Date: Wed, 8 Dec 2021 23:43:14 +0800 Subject: [PATCH] init project --- .gitignore | 28 +++++++++ README.md | 3 + api/auth.go | 82 +++++++++++++++++++++++++ api/memo.go | 82 +++++++++++++++++++++++++ api/middlewares.go | 19 ++++++ api/user.go | 60 ++++++++++++++++++ api/utils.go | 11 ++++ common/error/codes.go | 9 +++ common/error/error.go | 48 +++++++++++++++ common/utils.go | 15 +++++ go.mod | 9 +++ go.sum | 6 ++ main.go | 20 ++++++ resources/memos.db | Bin 0 -> 45056 bytes resources/sqlite.sql | 30 +++++++++ scripts/.air.toml | 13 ++++ store/memo.go | 72 ++++++++++++++++++++++ store/sqlite.go | 31 ++++++++++ store/user.go | 139 ++++++++++++++++++++++++++++++++++++++++++ 19 files changed, 677 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 api/auth.go create mode 100644 api/memo.go create mode 100644 api/middlewares.go create mode 100644 api/user.go create mode 100644 api/utils.go create mode 100644 common/error/codes.go create mode 100644 common/error/error.go create mode 100644 common/utils.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 resources/memos.db create mode 100644 resources/sqlite.sql create mode 100644 scripts/.air.toml create mode 100644 store/memo.go create mode 100644 store/sqlite.go create mode 100644 store/user.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..78660904 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Temp output +*.out +*.log +tmp + +# Air (hot reload) generated +.air + +# Frontend asset +dist + +# Dev database +data + +# build folder +build + +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..15ed47e3 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# ✍️ Memos + +🏗 In heavily development. diff --git a/api/auth.go b/api/auth.go new file mode 100644 index 00000000..1d408b12 --- /dev/null +++ b/api/auth.go @@ -0,0 +1,82 @@ +package api + +import ( + "encoding/json" + "memos/common/error" + "memos/store" + "net/http" + + "github.com/gorilla/mux" +) + +type UserSignUp struct { + Username string `json:"username"` + Password string `json:"password"` +} + +func handleUserSignUp(w http.ResponseWriter, r *http.Request) { + var userSignup UserSignUp + err := json.NewDecoder(r.Body).Decode(&userSignup) + + if err != nil { + error.ErrorHandler(w, "REQUEST_BODY_ERROR") + return + } + + user, err := store.CreateNewUser(userSignup.Username, userSignup.Password, "", "") + + if err != nil { + error.ErrorHandler(w, "") + return + } + + json.NewEncoder(w).Encode(user) +} + +type UserSignin struct { + Username string `json:"username"` + Password string `json:"password"` +} + +func handleUserSignIn(w http.ResponseWriter, r *http.Request) { + var userSignin UserSignin + err := json.NewDecoder(r.Body).Decode(&userSignin) + + if err != nil { + error.ErrorHandler(w, "") + return + } + + user, err := store.GetUserByUsernameAndPassword(userSignin.Username, userSignin.Password) + + if err != nil { + error.ErrorHandler(w, "") + return + } + + userIdCookie := &http.Cookie{ + Name: "user_id", + Value: user.Id, + MaxAge: 3600 * 24 * 30, + } + http.SetCookie(w, userIdCookie) + + json.NewEncoder(w).Encode(user) +} + +func handleUserSignOut(w http.ResponseWriter, r *http.Request) { + userIdCookie := &http.Cookie{ + Name: "user_id", + Value: "", + MaxAge: 0, + } + http.SetCookie(w, userIdCookie) +} + +func RegisterAuthRoutes(r *mux.Router) { + authRouter := r.PathPrefix("/api/auth").Subrouter() + + authRouter.HandleFunc("/signup", handleUserSignUp).Methods("POST") + authRouter.HandleFunc("/signin", handleUserSignIn).Methods("POST") + authRouter.HandleFunc("/signout", handleUserSignOut).Methods("POST") +} diff --git a/api/memo.go b/api/memo.go new file mode 100644 index 00000000..1b8a1c01 --- /dev/null +++ b/api/memo.go @@ -0,0 +1,82 @@ +package api + +import ( + "encoding/json" + "memos/common/error" + "memos/store" + "net/http" + + "github.com/gorilla/mux" +) + +func handleGetMyMemos(w http.ResponseWriter, r *http.Request) { + userId, _ := GetUserIdInCookie(r) + + memos, err := store.GetMemosByUserId(userId) + + if err != nil { + error.ErrorHandler(w, "DATABASE_ERROR") + return + } + + json.NewEncoder(w).Encode(memos) +} + +type CreateMemo struct { + Content string `json:"content"` +} + +func handleCreateMemo(w http.ResponseWriter, r *http.Request) { + userId, _ := GetUserIdInCookie(r) + + var createMemo CreateMemo + err := json.NewDecoder(r.Body).Decode(&createMemo) + + if err != nil { + error.ErrorHandler(w, "") + return + } + + memo, err := store.CreateNewMemo(createMemo.Content, userId) + + if err != nil { + error.ErrorHandler(w, "") + return + } + + json.NewEncoder(w).Encode(memo) +} + +func handleUpdateMemo(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + memoId := vars["id"] + + userId, _ := GetUserIdInCookie(r) + + var createMemo CreateMemo + err := json.NewDecoder(r.Body).Decode(&createMemo) + + if err != nil { + error.ErrorHandler(w, "") + return + } + + memo, err := store.UpdateMemo(memoId, createMemo.Content, userId) + + if err != nil { + error.ErrorHandler(w, "") + return + } + + json.NewEncoder(w).Encode(memo) +} + +func RegisterMemoRoutes(r *mux.Router) { + memoRouter := r.PathPrefix("/api/memo").Subrouter() + + memoRouter.Use(AuthCheckerMiddleWare) + + memoRouter.HandleFunc("/all", handleGetMyMemos).Methods("GET") + memoRouter.HandleFunc("/", handleCreateMemo).Methods("PUT") + memoRouter.HandleFunc("/{id}", handleUpdateMemo).Methods("PATCH") +} diff --git a/api/middlewares.go b/api/middlewares.go new file mode 100644 index 00000000..095abaf8 --- /dev/null +++ b/api/middlewares.go @@ -0,0 +1,19 @@ +package api + +import ( + "memos/common/error" + "net/http" +) + +func AuthCheckerMiddleWare(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + userId, err := GetUserIdInCookie(r) + + if err != nil || userId == "" { + error.ErrorHandler(w, "NOT_AUTH") + return + } + + next.ServeHTTP(w, r) + }) +} diff --git a/api/user.go b/api/user.go new file mode 100644 index 00000000..ffa58836 --- /dev/null +++ b/api/user.go @@ -0,0 +1,60 @@ +package api + +import ( + "encoding/json" + "memos/common/error" + "memos/store" + "net/http" + + "github.com/gorilla/mux" +) + +func handleGetMyUserInfo(w http.ResponseWriter, r *http.Request) { + userId, _ := GetUserIdInCookie(r) + + user, err := store.GetUserById(userId) + + if err != nil { + error.ErrorHandler(w, "DATABASE_ERROR") + return + } + + json.NewEncoder(w).Encode(user) +} + +type UpdateUser struct { + Username string `json:"username"` + Password string `json:"password"` + GithubName string `json:"githubName"` + WxOpenId string `json:"wxOpenId"` +} + +func handleUpdateMyUserInfo(w http.ResponseWriter, r *http.Request) { + userId, _ := GetUserIdInCookie(r) + + user, err := store.GetUserById(userId) + + if err != nil { + error.ErrorHandler(w, "DATABASE_ERROR") + return + } + + var updateUser UpdateUser + err = json.NewDecoder(r.Body).Decode(&updateUser) + + if err != nil { + error.ErrorHandler(w, "REQUEST_BODY_ERROR") + return + } + + json.NewEncoder(w).Encode(user) +} + +func RegisterUserRoutes(r *mux.Router) { + userRouter := r.PathPrefix("/api/user").Subrouter() + + userRouter.Use(AuthCheckerMiddleWare) + + userRouter.HandleFunc("/me", handleGetMyUserInfo).Methods("GET") + userRouter.HandleFunc("/me", handleUpdateMyUserInfo).Methods("PATCH") +} diff --git a/api/utils.go b/api/utils.go new file mode 100644 index 00000000..c4eec6cb --- /dev/null +++ b/api/utils.go @@ -0,0 +1,11 @@ +package api + +import ( + "net/http" +) + +func GetUserIdInCookie(r *http.Request) (string, error) { + userIdCookie, err := r.Cookie("user_id") + + return userIdCookie.Value, err +} diff --git a/common/error/codes.go b/common/error/codes.go new file mode 100644 index 00000000..35d9d6b2 --- /dev/null +++ b/common/error/codes.go @@ -0,0 +1,9 @@ +package error + +var Codes = map[string]int{ + "NOT_AUTH": 20001, + + "REQUEST_BODY_ERROR": 40001, + + "DATABASE_ERROR": 50001, +} diff --git a/common/error/error.go b/common/error/error.go new file mode 100644 index 00000000..f52d866f --- /dev/null +++ b/common/error/error.go @@ -0,0 +1,48 @@ +package error + +import ( + "encoding/json" + "net/http" +) + +type ServerError struct { + Code int + Message string +} + +type ErrorResponse struct { + StatusCode int `json:"statusCode"` + StatusMessage string `json:"statusMessage"` + Succeed bool `json:"succeed"` + Data interface{} `json:"data"` +} + +func getServerError(err string) ServerError { + code, exists := Codes[err] + + if !exists { + err = "Bad Request" + code = 40000 + } + + return ServerError{ + Code: code, + Message: err, + } +} + +func ErrorHandler(w http.ResponseWriter, err string) { + serverError := getServerError(err) + + res := ErrorResponse{ + StatusCode: serverError.Code, + StatusMessage: serverError.Message, + Succeed: false, + Data: nil, + } + + statusCode := int(serverError.Code / 100) + + w.WriteHeader(statusCode) + json.NewEncoder(w).Encode(res) +} diff --git a/common/utils.go b/common/utils.go new file mode 100644 index 00000000..ac89f3a8 --- /dev/null +++ b/common/utils.go @@ -0,0 +1,15 @@ +package common + +import ( + "time" + + "github.com/google/uuid" +) + +func GenUUID() string { + return uuid.New().String() +} + +func GetNowDateTimeStr() string { + return time.Now().Format("RFC3339") +} diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..973cdcf3 --- /dev/null +++ b/go.mod @@ -0,0 +1,9 @@ +module memos + +go 1.17 + +require github.com/gorilla/mux v1.8.0 + +require github.com/mattn/go-sqlite3 v1.14.9 + +require github.com/google/uuid v1.3.0 diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..fda9a009 --- /dev/null +++ b/go.sum @@ -0,0 +1,6 @@ +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA= +github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= diff --git a/main.go b/main.go new file mode 100644 index 00000000..181b5951 --- /dev/null +++ b/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "memos/api" + "memos/store" + "net/http" + + "github.com/gorilla/mux" +) + +func main() { + store.InitDBConn() + + r := mux.NewRouter().StrictSlash(true) + + api.RegisterUserRoutes(r) + api.RegisterAuthRoutes(r) + + http.ListenAndServe("localhost:8080", r) +} diff --git a/resources/memos.db b/resources/memos.db new file mode 100644 index 0000000000000000000000000000000000000000..71a6abcdbe13d11d60a3797cba36cc4187822cf8 GIT binary patch literal 45056 zcmeI)O>Y`U7zglOu+2*XoA!cIUq<8*klK2emttrd6_a&p1p@)xG`TDo#&{)IFuN%6 z)oKn^Pd!yVwEYr2^wL{z{}fB*y_uw;RgVZaxOMC7Lhr@7yyJ=*KrVi7D< z^?Y3?_54;*Cs7fLl30+CsM(H^y8cC-l*+8^6^rllXsd5I)Nwx*B2yB7H*B1@*#rcdmnE_)JdG)GQf+%|-_p^3^L-jIKKTf2eB z2#^v3ZCTHc7~?aaa-M*4o_#+E*_p5r^D05+6_$05~Jay*?4rF8H{4ne$I_WB5F5c z+$kQ6e{gjzl3zdXJQ~v)+W5+9cwh4KJr)lxI{m~XU}&P^uOM6QJNcDGPd48Xlhc=F zakPljUM$w;j+E&z%%>suX@JW|~f>rJFbTffarH z#OM#GHJ#l=@bsuTlW*}+6Fog@F4XS7^H(tb%Ju>72m%m*00bZa0SG_<0uX=z1Rwwb z2tWV=5P$##AOHafKmY;|fB*y_0D+|mgm~s_OG}T$5P$##AOHafKmY;|fB*y_009WR z0f8XTf!F_UKpH6^009U<00Izz00bZa0SG_<0?QNNfB*L=UrEZZY=H{|AOHafKmY;| zfB*y_009U<00OU9AnWlfkw_?%OsdUx&$PrMp(eBqHM^16Bx)w7W^<{uODHF^rq_8> ztuvywqa~85^hdKBrgN!OPMr;D>~<=rI 0 { + return false, nil + } else { + return true, nil + } +} + +func CheckGithubNameUsable(githubName string) (bool, error) { + query := `SELECT * FROM users WHERE github_name=?` + query = fmt.Sprintf("SELECT COUNT(*) FROM (%s)", query) + + var count uint + err := DB.QueryRow(query, githubName).Scan(&count) + if err != nil && err != sql.ErrNoRows { + return false, FormatDBError(err) + } + + if count > 0 { + return false, nil + } else { + return true, nil + } +} + +func CheckPasswordValid(id string, password string) (bool, error) { + query := `SELECT * FROM users WHERE id=? AND password=?` + query = fmt.Sprintf("SELECT COUNT(*) FROM (%s)", query) + + var count uint + err := DB.QueryRow(query, id, password).Scan(&count) + if err != nil && err != sql.ErrNoRows { + return false, FormatDBError(err) + } + + if count > 0 { + return true, nil + } else { + return false, nil + } +}