init project

This commit is contained in:
LeeShuang 2021-12-08 23:43:14 +08:00
commit 2f72bfa946
19 changed files with 677 additions and 0 deletions

28
.gitignore vendored Normal file
View File

@ -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

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# ✍️ Memos
🏗 In heavily development.

82
api/auth.go Normal file
View File

@ -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")
}

82
api/memo.go Normal file
View File

@ -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")
}

19
api/middlewares.go Normal file
View File

@ -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)
})
}

60
api/user.go Normal file
View File

@ -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")
}

11
api/utils.go Normal file
View File

@ -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
}

9
common/error/codes.go Normal file
View File

@ -0,0 +1,9 @@
package error
var Codes = map[string]int{
"NOT_AUTH": 20001,
"REQUEST_BODY_ERROR": 40001,
"DATABASE_ERROR": 50001,
}

48
common/error/error.go Normal file
View File

@ -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)
}

15
common/utils.go Normal file
View File

@ -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")
}

9
go.mod Normal file
View File

@ -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

6
go.sum Normal file
View File

@ -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=

20
main.go Normal file
View File

@ -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)
}

BIN
resources/memos.db Normal file

Binary file not shown.

30
resources/sqlite.sql Normal file
View File

@ -0,0 +1,30 @@
CREATE TABLE `users` (
`id` TEXT NOT NULL PRIMARY KEY,
`username` TEXT NOT NULL,
`password` TEXT NOT NULL,
`github_name` TEXT NULL DEFAULT '',
`wx_open_id` TEXT NULL DEFAULT '',
`created_at` TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE `memos` (
`id` TEXT NOT NULL PRIMARY KEY,
`content` TEXT NOT NULL,
`user_id` TEXT NOT NULL,
`created_at` TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
`deleted_at` TEXT,
FOREIGN KEY(`user_id`) REFERENCES `users`(`id`)
);
CREATE TABLE `queries` (
`id` TEXT NOT NULL PRIMARY KEY,
`user_id` TEXT NOT NULL,
`title` TEXT NOT NULL,
`querystring` TEXT NOT NULL,
`created_at` TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
`pinned_at` TEXT NULL,
FOREIGN KEY(`user_id`) REFERENCES `users`(`id`)
);

13
scripts/.air.toml Normal file
View File

@ -0,0 +1,13 @@
root = "."
tmp_dir = "tmp"
[build]
bin = "./tmp/main"
cmd = "go build -o ./tmp/main ."
delay = 1000
exclude_dir = ["assets", "tmp", "vendor"]
exclude_file = []
exclude_regex = []
exclude_unchanged = false
follow_symlink = false
full_bin = ""

72
store/memo.go Normal file
View File

@ -0,0 +1,72 @@
package store
import "memos/common"
type Memo struct {
Id string `json:"id"`
Content string `json:"content"`
UserId string `json:"userId"`
DeletedAt string `json:"deletedAt"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
func CreateNewMemo(content string, userId string) (Memo, error) {
nowDateTimeStr := common.GetNowDateTimeStr()
newMemo := Memo{
Id: common.GenUUID(),
Content: content,
UserId: userId,
DeletedAt: "",
CreatedAt: nowDateTimeStr,
UpdatedAt: nowDateTimeStr,
}
query := `INSERT INTO memos (id, content, user_id, deleted_at, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)`
_, err := DB.Exec(query, newMemo.Id, newMemo.Content, newMemo.UserId, newMemo.DeletedAt, newMemo.CreatedAt, newMemo.UpdatedAt)
return newMemo, err
}
func UpdateMemo(id string, content string, deletedAt string) (Memo, error) {
nowDateTimeStr := common.GetNowDateTimeStr()
memo, _ := GetMemoById(id)
if content != "" {
memo.Content = content
}
if deletedAt != "" {
memo.DeletedAt = deletedAt
}
memo.UpdatedAt = nowDateTimeStr
query := `UPDATE memos SET (content, deleted_at, updated_at) VALUES (?, ?, ?)`
_, err := DB.Exec(query, memo.Content, memo.DeletedAt, memo.UpdatedAt)
return memo, err
}
func GetMemoById(id string) (Memo, error) {
query := `SELECT id, content, user_id, deleted_at, created_at, updated_at FROM memos WHERE id=?`
var memo Memo
err := DB.QueryRow(query, id).Scan(&memo.Id, &memo.Content, &memo.UserId, &memo.DeletedAt, &memo.CreatedAt, &memo.UpdatedAt)
return memo, err
}
func GetMemosByUserId(userId string) ([]Memo, error) {
query := `SELECT id, content, user_id, deleted_at, created_at, updated_at FROM memos WHERE user_id=?`
rows, err := DB.Query(query, userId)
var memos []Memo
for rows.Next() {
var memo Memo
err = rows.Scan(&memo.Id, &memo.Content, &memo.UserId, &memo.DeletedAt, &memo.CreatedAt, &memo.UpdatedAt)
memos = append(memos, memo)
}
return memos, err
}

31
store/sqlite.go Normal file
View File

@ -0,0 +1,31 @@
package store
import (
"database/sql"
"fmt"
_ "github.com/mattn/go-sqlite3"
)
var DB *sql.DB
func InitDBConn() {
db, err := sql.Open("sqlite3", "./resources/memos.db")
if err != nil {
fmt.Println("connect failed")
} else {
DB = db
fmt.Println("connect to sqlite succeed")
}
}
func FormatDBError(err error) error {
if err == nil {
return nil
}
switch err.Error() {
default:
return err
}
}

139
store/user.go Normal file
View File

@ -0,0 +1,139 @@
package store
import (
"database/sql"
"fmt"
"memos/common"
)
type User struct {
Id string `json:"id"`
Username string `json:"username"`
Password string `json:"password"`
WxOpenId string `json:"wxOpenId"`
GithubName string `json:"githubName"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
func CreateNewUser(username string, password string, githubName string, wxOpenId string) (User, error) {
nowDateTimeStr := common.GetNowDateTimeStr()
newUser := User{
Id: common.GenUUID(),
Username: username,
Password: password,
WxOpenId: wxOpenId,
GithubName: githubName,
CreatedAt: nowDateTimeStr,
UpdatedAt: nowDateTimeStr,
}
query := `INSERT INTO users (id, username, password, wx_open_id, github_name, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)`
_, err := DB.Exec(query, newUser.Id, newUser.Username, newUser.Password, newUser.WxOpenId, newUser.GithubName, newUser.CreatedAt, newUser.UpdatedAt)
return newUser, err
}
func UpdateUser(id string, username string, password string, githubName string, wxOpenId string) (User, error) {
nowDateTimeStr := common.GetNowDateTimeStr()
user, _ := GetUserById(id)
if username != "" {
user.Username = username
}
if password != "" {
user.Password = password
}
if githubName != "" {
user.GithubName = githubName
}
if wxOpenId != "" {
user.WxOpenId = wxOpenId
}
user.UpdatedAt = nowDateTimeStr
query := `UPDATE users SET (username, password, wx_open_id, github_name, updated_at) VALUES (?, ?, ?, ?, ?)`
_, err := DB.Exec(query, user.Username, user.Password, user.WxOpenId, user.GithubName, user.UpdatedAt)
return user, err
}
func GetUserById(id string) (User, error) {
query := `SELECT id, username, password, wx_open_id, github_name, created_at, updated_at FROM users WHERE id=?`
var user User
err := DB.QueryRow(query, id).Scan(&user.Id, &user.Username, &user.Password, &user.WxOpenId, &user.GithubName, &user.CreatedAt, &user.UpdatedAt)
return user, err
}
func GetUserByUsernameAndPassword(username string, password string) (User, error) {
query := `SELECT id, username, password, wx_open_id, github_name, created_at, updated_at FROM users WHERE username=? AND password=?`
var user User
err := DB.QueryRow(query, username, password).Scan(&user.Id, &user.Username, &user.Password, &user.WxOpenId, &user.GithubName, &user.CreatedAt, &user.UpdatedAt)
return user, err
}
func GetUserByGithubName(githubName string) (User, error) {
query := `SELECT id, username, password, wx_open_id, github_name, created_at, updated_at FROM users WHERE github_name=?`
var user User
err := DB.QueryRow(query, githubName).Scan(&user.Id, &user.Username, &user.Password, &user.WxOpenId, &user.GithubName, &user.CreatedAt, &user.UpdatedAt)
return user, err
}
func GetUserByWxOpenId(wxOpenId string) (User, error) {
query := `SELECT id, username, password, wx_open_id, github_name, created_at, updated_at FROM users WHERE id=?`
var user User
err := DB.QueryRow(query, wxOpenId).Scan(&user.Id, &user.Username, &user.Password, &user.WxOpenId, &user.GithubName, &user.CreatedAt, &user.UpdatedAt)
return user, err
}
func CheckUsernameUsable(username string) (bool, error) {
query := `SELECT * FROM users WHERE username=?`
query = fmt.Sprintf("SELECT COUNT(*) FROM (%s)", query)
var count uint
err := DB.QueryRow(query, username).Scan(&count)
if err != nil && err != sql.ErrNoRows {
return false, FormatDBError(err)
}
if count > 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
}
}