From e7da38bd9c86b0f79c0592346feb468197d99ac6 Mon Sep 17 00:00:00 2001 From: l1nk4i <l1nk@foxmail.com> Date: Wed, 25 Sep 2024 23:32:34 +0800 Subject: [PATCH 01/14] feat: mysql handler --- l1nk4i/db/mysql.go | 59 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 l1nk4i/db/mysql.go diff --git a/l1nk4i/db/mysql.go b/l1nk4i/db/mysql.go new file mode 100644 index 0000000..0667f9a --- /dev/null +++ b/l1nk4i/db/mysql.go @@ -0,0 +1,59 @@ +package db + +import ( + "fmt" + "gorm.io/driver/mysql" + "gorm.io/gorm" + "l1nk4i/config" + "log" +) + +type User struct { + gorm.Model + Username string `gorm:"not null;column:username;unique"` + Password string `gorm:"not null;column:password"` + Role string `gorm:"not null;column:role"` +} + +var db *gorm.DB + +func init() { + username := config.Mysql.Username + password := config.Mysql.Password + host := config.Mysql.Host + port := config.Mysql.Port + dbname := config.Mysql.Dbname + + dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", username, password, host, port, dbname) + conn, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) + if err != nil { + panic("Cannot connect to mysql: " + err.Error()) + } + + if err = conn.AutoMigrate(&User{}); err != nil { + panic("AutoMigrate failed: " + err.Error()) + } + + db = conn +} + +func CreateUser(user *User) error { + err := db.Create(user).Error + if err != nil { + log.Printf("Create user failed: %s\n", err.Error()) + return err + } + + return nil +} + +func GetUser(username string) (*User, error) { + var user User + err := db.Where("username = ?", username).First(&user).Error + if err != nil { + log.Printf("Get user failed: %s\n", err.Error()) + return nil, err + } + + return &user, nil +} From f8cf8fbd1cf84a6ecd818422f145e8fa3793a8b6 Mon Sep 17 00:00:00 2001 From: l1nk4i <l1nk@foxmail.com> Date: Wed, 25 Sep 2024 23:33:09 +0800 Subject: [PATCH 02/14] feat: login and register handler --- l1nk4i/api/user/login.go | 29 +++++++++++++++++++++++++++++ l1nk4i/api/user/register.go | 30 ++++++++++++++++++++++++++++++ l1nk4i/utils/hash.go | 15 +++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 l1nk4i/api/user/login.go create mode 100644 l1nk4i/api/user/register.go create mode 100644 l1nk4i/utils/hash.go diff --git a/l1nk4i/api/user/login.go b/l1nk4i/api/user/login.go new file mode 100644 index 0000000..4cbb91b --- /dev/null +++ b/l1nk4i/api/user/login.go @@ -0,0 +1,29 @@ +package user + +import ( + "github.com/gin-gonic/gin" + "l1nk4i/db" + "l1nk4i/utils" + "net/http" +) + +func Login(c *gin.Context) { + var loginInfo struct { + Username string `json:"username"` + Password string `json:"password"` + } + + if err := c.ShouldBind(&loginInfo); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + } else { + if user, err := db.GetUser(loginInfo.Username); err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid username"}) + } else { + if utils.CheckPasswordHash(loginInfo.Password, user.Password) { + c.JSON(http.StatusOK, gin.H{"message": "login successful!"}) + } else { + c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid password"}) + } + } + } +} diff --git a/l1nk4i/api/user/register.go b/l1nk4i/api/user/register.go new file mode 100644 index 0000000..dbdb2f8 --- /dev/null +++ b/l1nk4i/api/user/register.go @@ -0,0 +1,30 @@ +package user + +import ( + "github.com/gin-gonic/gin" + "l1nk4i/db" + "l1nk4i/utils" + "net/http" +) + +func Register(c *gin.Context) { + var registerInfo struct { + Username string `json:"username"` + Password string `json:"password"` + Role string `json:"role"` + } + + if err := c.ShouldBindJSON(®isterInfo); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + } else { + user := db.User{ + Username: registerInfo.Username, + Password: utils.HashPassword(registerInfo.Password), + Role: registerInfo.Role, + } + if err := db.CreateUser(&user); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + } + c.JSON(http.StatusOK, gin.H{"message": "register successful!"}) + } +} diff --git a/l1nk4i/utils/hash.go b/l1nk4i/utils/hash.go new file mode 100644 index 0000000..b069491 --- /dev/null +++ b/l1nk4i/utils/hash.go @@ -0,0 +1,15 @@ +package utils + +import ( + "golang.org/x/crypto/bcrypt" +) + +func HashPassword(password string) string { + hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + return string(hashedPassword) +} + +func CheckPasswordHash(password, hashedPassword string) bool { + err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) + return err == nil +} From df0bd3c95b31f12228af94cd54707b89fd06a06e Mon Sep 17 00:00:00 2001 From: l1nk4i <l1nk@foxmail.com> Date: Thu, 26 Sep 2024 15:18:25 +0800 Subject: [PATCH 03/14] feat: sql api --- l1nk4i/db/answer.go | 40 ++++++++++++++++++++++++++++++++++++ l1nk4i/db/mysql.go | 47 ++++++++++++++++++++++--------------------- l1nk4i/db/question.go | 40 ++++++++++++++++++++++++++++++++++++ l1nk4i/db/user.go | 32 +++++++++++++++++++++++++++++ 4 files changed, 136 insertions(+), 23 deletions(-) create mode 100644 l1nk4i/db/answer.go create mode 100644 l1nk4i/db/question.go create mode 100644 l1nk4i/db/user.go diff --git a/l1nk4i/db/answer.go b/l1nk4i/db/answer.go new file mode 100644 index 0000000..731542d --- /dev/null +++ b/l1nk4i/db/answer.go @@ -0,0 +1,40 @@ +package db + +import "log" + +func CreateAnswer(answer *Answer) error { + err := db.Create(answer).Error + if err != nil { + log.Printf("[ERROR] Create answer error:%s\n", err.Error()) + return err + } + return nil +} + +func GetAnswer(uuid string) (*Answer, error) { + var answer Answer + err := db.Where("uuid = ?", uuid).First(&answer).Error + if err != nil { + log.Printf("[ERROR] Get answer error:%s\n", err.Error()) + return nil, err + } + return &answer, nil +} + +func DeleteAnswer(uuid string) error { + err := db.Unscoped().Where("uuid = ?", uuid).Delete(&Answer{}).Error + if err != nil { + log.Printf("[ERROR] Delete answer error:%s\n", err.Error()) + return err + } + return nil +} + +func UpdateAnswer(uuid, content string) error { + err := db.Model(&Answer{}).Where("uuid = ?", uuid).Update("content", content).Error + if err != nil { + log.Printf("[ERROR] Update answer error:%s\n", err.Error()) + return err + } + return nil +} diff --git a/l1nk4i/db/mysql.go b/l1nk4i/db/mysql.go index 0667f9a..dc05169 100644 --- a/l1nk4i/db/mysql.go +++ b/l1nk4i/db/mysql.go @@ -10,11 +10,31 @@ import ( type User struct { gorm.Model + UUID string `gorm:"not null;column:uuid;unique;type:varchar(36)"` + Username string `gorm:"not null;column:username;unique"` Password string `gorm:"not null;column:password"` Role string `gorm:"not null;column:role"` } +type Question struct { + gorm.Model + UUID string `gorm:"not null;column:uuid;unique;type:varchar(36)"` + UserId string `gorm:"not null;column:userid;unique"` + + Title string `gorm:"not null;column:title"` + Content string `gorm:"not null;column:content"` +} + +type Answer struct { + gorm.Model + UUID string `gorm:"not null;column:uuid;unique;type:varchar(36)"` + UserId string `gorm:"not null;column:userid;unique"` + QuestionId string `gorm:"not null;column:questionid;unique"` + + Content string `gorm:"not null;column:content"` +} + var db *gorm.DB func init() { @@ -23,37 +43,18 @@ func init() { host := config.Mysql.Host port := config.Mysql.Port dbname := config.Mysql.Dbname + params := "charset=utf8mb4&parseTime=True&loc=Local" - dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", username, password, host, port, dbname) + dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?%s", username, password, host, port, dbname, params) conn, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { panic("Cannot connect to mysql: " + err.Error()) } + log.Printf("[INFO] Connect to mysql successfully\n") - if err = conn.AutoMigrate(&User{}); err != nil { + if err = conn.AutoMigrate(&User{}, &Question{}, &Answer{}); err != nil { panic("AutoMigrate failed: " + err.Error()) } db = conn } - -func CreateUser(user *User) error { - err := db.Create(user).Error - if err != nil { - log.Printf("Create user failed: %s\n", err.Error()) - return err - } - - return nil -} - -func GetUser(username string) (*User, error) { - var user User - err := db.Where("username = ?", username).First(&user).Error - if err != nil { - log.Printf("Get user failed: %s\n", err.Error()) - return nil, err - } - - return &user, nil -} diff --git a/l1nk4i/db/question.go b/l1nk4i/db/question.go new file mode 100644 index 0000000..7029086 --- /dev/null +++ b/l1nk4i/db/question.go @@ -0,0 +1,40 @@ +package db + +import "log" + +func CreateQuestion(question *Question) error { + err := db.Create(question).Error + if err != nil { + log.Printf("[ERROR] Create Question failed %s\n", err.Error()) + return err + } + return nil +} + +func GetQuestion(uuid string) (*Question, error) { + var question Question + err := db.Where("uuid = ?", uuid).First(&question).Error + if err != nil { + log.Printf("[ERROR] Get Question failed %s\n", err.Error()) + return nil, err + } + return &question, nil +} + +func DeleteQuestion(uuid string) error { + err := db.Unscoped().Where("uuid = ?", uuid).Delete(&Question{}).Error + if err != nil { + log.Printf("[ERROR] Delete Question failed %s\n", err.Error()) + return err + } + return nil +} + +func UpdateQuestion(uuid, title, content string) error { + err := db.Model(&Question{}).Where("uuid = ?", uuid).Update("title", title).Update("content", content).Error + if err != nil { + log.Printf("[ERROR] Update Question failed %s\n", err.Error()) + return err + } + return nil +} diff --git a/l1nk4i/db/user.go b/l1nk4i/db/user.go new file mode 100644 index 0000000..e4bd38b --- /dev/null +++ b/l1nk4i/db/user.go @@ -0,0 +1,32 @@ +package db + +import "log" + +func CreateUser(user *User) error { + err := db.Create(user).Error + if err != nil { + log.Printf("[ERROR] Create user failed: %s\n", err.Error()) + return err + } + return nil +} + +func GetUserByUsername(username string) (*User, error) { + var user User + err := db.Where("username = ?", username).First(&user).Error + if err != nil { + log.Printf("[ERROR] Get user failed: %s\n", err.Error()) + return nil, err + } + return &user, nil +} + +func GetUserByUUID(uuid string) (*User, error) { + var user User + err := db.Where("uuid = ?", uuid).First(&user).Error + if err != nil { + log.Printf("[ERROR] Get user failed: %s\n", err.Error()) + return nil, err + } + return &user, nil +} From 8f1f7e9588e20c3a40fc0de79fb368b2f8049515 Mon Sep 17 00:00:00 2001 From: l1nk4i <l1nk@foxmail.com> Date: Thu, 26 Sep 2024 15:21:16 +0800 Subject: [PATCH 04/14] add: config --- l1nk4i/config/config.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 l1nk4i/config/config.go diff --git a/l1nk4i/config/config.go b/l1nk4i/config/config.go new file mode 100644 index 0000000..295c611 --- /dev/null +++ b/l1nk4i/config/config.go @@ -0,0 +1,24 @@ +package config + +import "github.com/pelletier/go-toml" + +var Mysql struct { + Username string `toml:"username"` + Password string `toml:"password"` + Host string `toml:"host"` + Port string `toml:"port"` + Dbname string `toml:"dbname"` +} + +var configFile = "config.toml" + +func init() { + conf, err := toml.LoadFile(configFile) + if err != nil { + panic("load config file failed: " + err.Error()) + } + + if err := conf.Get("mysql").(*toml.Tree).Unmarshal(&Mysql); err != nil { + panic("unmarshal config file failed: " + err.Error()) + } +} From 40cba041b945b9b4927ddec9d80b6abdb7bf64f6 Mon Sep 17 00:00:00 2001 From: l1nk4i <l1nk@foxmail.com> Date: Fri, 27 Sep 2024 23:36:06 +0800 Subject: [PATCH 05/14] feat: db handler --- l1nk4i/db/answer.go | 24 +++++++++++++++++------- l1nk4i/db/mysql.go | 15 +++++++++------ l1nk4i/db/question.go | 35 ++++++++++++++++++++++++++++------- l1nk4i/db/user.go | 2 +- 4 files changed, 55 insertions(+), 21 deletions(-) diff --git a/l1nk4i/db/answer.go b/l1nk4i/db/answer.go index 731542d..137f428 100644 --- a/l1nk4i/db/answer.go +++ b/l1nk4i/db/answer.go @@ -11,18 +11,28 @@ func CreateAnswer(answer *Answer) error { return nil } -func GetAnswer(uuid string) (*Answer, error) { +func GetAnswerByAnswerID(answerID string) (*Answer, error) { var answer Answer - err := db.Where("uuid = ?", uuid).First(&answer).Error + err := db.Where("answer_id = ?", answerID).First(&answer).Error if err != nil { - log.Printf("[ERROR] Get answer error:%s\n", err.Error()) + log.Printf("[ERROR] Get answer by answerID error:%s\n", err.Error()) return nil, err } return &answer, nil } -func DeleteAnswer(uuid string) error { - err := db.Unscoped().Where("uuid = ?", uuid).Delete(&Answer{}).Error +func GetAnswersByQuestionID(questionID string) (*[]Answer, error) { + var answers []Answer + err := db.Where("question_id = ?", questionID).Limit(100).Find(&answers).Error + if err != nil { + log.Printf("[ERROR] Get answer by questionID error:%s\n", err.Error()) + return nil, err + } + return &answers, nil +} + +func DeleteAnswer(answerID string) error { + err := db.Unscoped().Where("answer_id = ?", answerID).Delete(&Answer{}).Error if err != nil { log.Printf("[ERROR] Delete answer error:%s\n", err.Error()) return err @@ -30,8 +40,8 @@ func DeleteAnswer(uuid string) error { return nil } -func UpdateAnswer(uuid, content string) error { - err := db.Model(&Answer{}).Where("uuid = ?", uuid).Update("content", content).Error +func UpdateAnswer(answerID, content string) error { + err := db.Model(&Answer{}).Where("answer_id = ?", answerID).Update("content", content).Error if err != nil { log.Printf("[ERROR] Update answer error:%s\n", err.Error()) return err diff --git a/l1nk4i/db/mysql.go b/l1nk4i/db/mysql.go index dc05169..e449827 100644 --- a/l1nk4i/db/mysql.go +++ b/l1nk4i/db/mysql.go @@ -10,7 +10,7 @@ import ( type User struct { gorm.Model - UUID string `gorm:"not null;column:uuid;unique;type:varchar(36)"` + UserID string `gorm:"not null;column:user_id;unique;type:varchar(36)"` Username string `gorm:"not null;column:username;unique"` Password string `gorm:"not null;column:password"` @@ -19,8 +19,11 @@ type User struct { type Question struct { gorm.Model - UUID string `gorm:"not null;column:uuid;unique;type:varchar(36)"` - UserId string `gorm:"not null;column:userid;unique"` + QuestionID string `gorm:"not null;column:question_id;unique;type:varchar(36)"` + UserID string `gorm:"not null;column:user_id;type:varchar(36)"` + + BestAnswerID string `gorm:"column:best_answer_id;type:varchar(36)"` + //IsAccessible bool `gorm:"not null;column:is_accessible;type:bool;default:false"` Title string `gorm:"not null;column:title"` Content string `gorm:"not null;column:content"` @@ -28,9 +31,9 @@ type Question struct { type Answer struct { gorm.Model - UUID string `gorm:"not null;column:uuid;unique;type:varchar(36)"` - UserId string `gorm:"not null;column:userid;unique"` - QuestionId string `gorm:"not null;column:questionid;unique"` + AnswerID string `gorm:"not null;column:answer_id;unique;type:varchar(36)"` + UserID string `gorm:"not null;column:user_id;type:varchar(36)"` + QuestionID string `gorm:"not null;column:question_id;"` Content string `gorm:"not null;column:content"` } diff --git a/l1nk4i/db/question.go b/l1nk4i/db/question.go index 7029086..e261e93 100644 --- a/l1nk4i/db/question.go +++ b/l1nk4i/db/question.go @@ -11,18 +11,28 @@ func CreateQuestion(question *Question) error { return nil } -func GetQuestion(uuid string) (*Question, error) { +func GetQuestionByQuestionID(questionID string) (*Question, error) { var question Question - err := db.Where("uuid = ?", uuid).First(&question).Error + err := db.Where("question_id = ?", questionID).First(&question).Error if err != nil { - log.Printf("[ERROR] Get Question failed %s\n", err.Error()) + log.Printf("[ERROR] Get Question by question_id failed %s\n", err.Error()) return nil, err } return &question, nil } -func DeleteQuestion(uuid string) error { - err := db.Unscoped().Where("uuid = ?", uuid).Delete(&Question{}).Error +func GetQuestionByUserID(userID string) (*[]Question, error) { + var questions []Question + err := db.Where("user_id = ?", userID).Limit(100).Find(&questions).Error + if err != nil { + log.Printf("[ERROR] Get Questions by user_id failed %s\n", err.Error()) + return nil, err + } + return &questions, nil +} + +func DeleteQuestion(questionID string) error { + err := db.Unscoped().Where("question_id = ?", questionID).Delete(&Question{}).Error if err != nil { log.Printf("[ERROR] Delete Question failed %s\n", err.Error()) return err @@ -30,11 +40,22 @@ func DeleteQuestion(uuid string) error { return nil } -func UpdateQuestion(uuid, title, content string) error { - err := db.Model(&Question{}).Where("uuid = ?", uuid).Update("title", title).Update("content", content).Error +func UpdateQuestion(questionID, title, content string) error { + err := db.Model(&Question{}).Where("question_id = ?", questionID).Updates(Question{Title: title, Content: content}).Error if err != nil { log.Printf("[ERROR] Update Question failed %s\n", err.Error()) return err } return nil } + +func SearchQuestions(content string) (*[]Question, error) { + var questions []Question + searchPattern := "%" + content + "%" + err := db.Where("title LIKE ? OR content LIKE ?", searchPattern, searchPattern).Limit(20).Find(&questions).Error + if err != nil { + log.Printf("[ERROR] Search Questions failed %s\n", err.Error()) + return nil, err + } + return &questions, nil +} diff --git a/l1nk4i/db/user.go b/l1nk4i/db/user.go index e4bd38b..ddc4ed5 100644 --- a/l1nk4i/db/user.go +++ b/l1nk4i/db/user.go @@ -23,7 +23,7 @@ func GetUserByUsername(username string) (*User, error) { func GetUserByUUID(uuid string) (*User, error) { var user User - err := db.Where("uuid = ?", uuid).First(&user).Error + err := db.Where("user_id = ?", uuid).First(&user).Error if err != nil { log.Printf("[ERROR] Get user failed: %s\n", err.Error()) return nil, err From 298c7adc00b6aa917c885d1102ddaa1f02a2cc43 Mon Sep 17 00:00:00 2001 From: l1nk4i <l1nk@foxmail.com> Date: Fri, 27 Sep 2024 23:37:08 +0800 Subject: [PATCH 06/14] add: user api --- l1nk4i/api/user/login.go | 38 +++++++++++++++++++++++++------- l1nk4i/api/user/logout.go | 16 ++++++++++++++ l1nk4i/api/user/register.go | 43 ++++++++++++++++++++++++++----------- l1nk4i/api/user/userinfo.go | 36 +++++++++++++++++++++++++++++++ l1nk4i/api/user/utils.go | 25 +++++++++++++++++++++ 5 files changed, 137 insertions(+), 21 deletions(-) create mode 100644 l1nk4i/api/user/logout.go create mode 100644 l1nk4i/api/user/userinfo.go create mode 100644 l1nk4i/api/user/utils.go diff --git a/l1nk4i/api/user/login.go b/l1nk4i/api/user/login.go index 4cbb91b..cbe3bff 100644 --- a/l1nk4i/api/user/login.go +++ b/l1nk4i/api/user/login.go @@ -1,6 +1,7 @@ package user import ( + "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" "l1nk4i/db" "l1nk4i/utils" @@ -14,16 +15,37 @@ func Login(c *gin.Context) { } if err := c.ShouldBind(&loginInfo); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON"}) + return + } + + if !validateUsername(loginInfo.Username) { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid Username"}) + return + } + + if !validatePassword(loginInfo.Password) { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid Password"}) + return + } + + if user, err := db.GetUserByUsername(loginInfo.Username); err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid Username"}) + return } else { - if user, err := db.GetUser(loginInfo.Username); err != nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid username"}) + if utils.CheckPasswordHash(loginInfo.Password, user.Password) { + session := sessions.Default(c) + session.Clear() + session.Set("user_id", user.UserID) + session.Set("role", user.Role) + session.Save() + + c.JSON(http.StatusOK, gin.H{"msg": "login successful!"}) + return } else { - if utils.CheckPasswordHash(loginInfo.Password, user.Password) { - c.JSON(http.StatusOK, gin.H{"message": "login successful!"}) - } else { - c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid password"}) - } + c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid Password"}) + return } } + } diff --git a/l1nk4i/api/user/logout.go b/l1nk4i/api/user/logout.go new file mode 100644 index 0000000..058eabf --- /dev/null +++ b/l1nk4i/api/user/logout.go @@ -0,0 +1,16 @@ +package user + +import ( + "github.com/gin-contrib/sessions" + "github.com/gin-gonic/gin" + "net/http" +) + +func Logout(c *gin.Context) { + session := sessions.Default(c) + session.Clear() + session.Set("role", "guest") + session.Save() + c.JSON(http.StatusOK, gin.H{"msg": "Logout successful!"}) + return +} diff --git a/l1nk4i/api/user/register.go b/l1nk4i/api/user/register.go index dbdb2f8..261dc59 100644 --- a/l1nk4i/api/user/register.go +++ b/l1nk4i/api/user/register.go @@ -1,7 +1,9 @@ package user import ( + "fmt" "github.com/gin-gonic/gin" + "github.com/google/uuid" "l1nk4i/db" "l1nk4i/utils" "net/http" @@ -11,20 +13,35 @@ func Register(c *gin.Context) { var registerInfo struct { Username string `json:"username"` Password string `json:"password"` - Role string `json:"role"` } - + fmt.Println(registerInfo) if err := c.ShouldBindJSON(®isterInfo); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - } else { - user := db.User{ - Username: registerInfo.Username, - Password: utils.HashPassword(registerInfo.Password), - Role: registerInfo.Role, - } - if err := db.CreateUser(&user); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - } - c.JSON(http.StatusOK, gin.H{"message": "register successful!"}) + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON"}) + return + } + + fmt.Println(registerInfo) + + if !validateUsername(registerInfo.Username) { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid Username"}) + return + } + + if !validatePassword(registerInfo.Password) { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid Password"}) + return + } + + user := db.User{ + UserID: uuid.New().String(), + Username: registerInfo.Username, + Password: utils.HashPassword(registerInfo.Password), + Role: "user", } + if err := db.CreateUser(&user); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Create user error"}) + return + } + + c.JSON(http.StatusOK, gin.H{"msg": "register successful!"}) } diff --git a/l1nk4i/api/user/userinfo.go b/l1nk4i/api/user/userinfo.go new file mode 100644 index 0000000..a277afa --- /dev/null +++ b/l1nk4i/api/user/userinfo.go @@ -0,0 +1,36 @@ +package user + +import ( + "github.com/gin-contrib/sessions" + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "l1nk4i/db" + "net/http" +) + +// UserInfo get Username by session +func UserInfo(c *gin.Context) { + session := sessions.Default(c) + userid, exists := session.Get("user_id").(string) + if !exists { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid session"}) + return + } + + if _, err := uuid.Parse(userid); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid session"}) + return + } + + user, err := db.GetUserByUUID(userid) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "user not found"}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "user_id": user.UserID, + "username": user.Username, + "role": user.Role, + }) +} diff --git a/l1nk4i/api/user/utils.go b/l1nk4i/api/user/utils.go new file mode 100644 index 0000000..5c7fb0a --- /dev/null +++ b/l1nk4i/api/user/utils.go @@ -0,0 +1,25 @@ +package user + +import "regexp" + +// 2 <= length <= 20 +// ^[a-zA-Z0-9]+$ +func validateUsername(username string) bool { + if len(username) < 2 || len(username) > 20 { + return false + } + + re := regexp.MustCompile(`^[a-zA-Z0-9]+$`) + return re.MatchString(username) +} + +// 8 <= length <= 30 +// ^[a-zA-Z0-9\W_]+$ +func validatePassword(password string) bool { + if len(password) < 8 || len(password) > 30 { + return false + } + + re := regexp.MustCompile(`^[a-zA-Z0-9\W_]+$`) + return re.MatchString(password) +} From 7df0c6265ddd497c95a22275ff846d2570da2381 Mon Sep 17 00:00:00 2001 From: l1nk4i <l1nk@foxmail.com> Date: Sat, 28 Sep 2024 00:09:44 +0800 Subject: [PATCH 07/14] add: question api --- l1nk4i/api/question/create.go | 42 +++++++++++++++++++++ l1nk4i/api/question/delete.go | 70 +++++++++++++++++++++++++++++++++++ l1nk4i/api/question/get.go | 27 ++++++++++++++ l1nk4i/api/question/list.go | 31 ++++++++++++++++ l1nk4i/api/question/search.go | 24 ++++++++++++ l1nk4i/api/question/update.go | 49 ++++++++++++++++++++++++ 6 files changed, 243 insertions(+) create mode 100644 l1nk4i/api/question/create.go create mode 100644 l1nk4i/api/question/delete.go create mode 100644 l1nk4i/api/question/get.go create mode 100644 l1nk4i/api/question/list.go create mode 100644 l1nk4i/api/question/search.go create mode 100644 l1nk4i/api/question/update.go diff --git a/l1nk4i/api/question/create.go b/l1nk4i/api/question/create.go new file mode 100644 index 0000000..6cbbbbe --- /dev/null +++ b/l1nk4i/api/question/create.go @@ -0,0 +1,42 @@ +package question + +import ( + "github.com/gin-contrib/sessions" + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "l1nk4i/db" + "net/http" +) + +func Create(c *gin.Context) { + var questionInfo struct { + Title string `json:"title"` + Content string `json:"content"` + } + + if err := c.ShouldBind(&questionInfo); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON"}) + return + } + + session := sessions.Default(c) + userID, exists := session.Get("user_id").(string) + if !exists { + c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid session"}) + return + } + + question := db.Question{ + QuestionID: uuid.New().String(), + UserID: userID, + Title: questionInfo.Title, + Content: questionInfo.Content, + } + if err := db.CreateQuestion(&question); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "create question error"}) + return + } + + c.JSON(http.StatusOK, gin.H{"question_id": question.QuestionID}) + return +} diff --git a/l1nk4i/api/question/delete.go b/l1nk4i/api/question/delete.go new file mode 100644 index 0000000..d68382a --- /dev/null +++ b/l1nk4i/api/question/delete.go @@ -0,0 +1,70 @@ +package question + +import ( + "github.com/gin-contrib/sessions" + "github.com/gin-gonic/gin" + "l1nk4i/db" + "net/http" +) + +func Delete(c *gin.Context) { + var questionInfo struct { + QuestionID string `json:"question_id"` + } + + if err := c.ShouldBind(&questionInfo); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON"}) + return + } + + // Verify user identity + session := sessions.Default(c) + userid, exists := session.Get("user_id").(string) + if !exists { + c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid session"}) + return + } + + question, err := db.GetQuestionByQuestionID(questionInfo.QuestionID) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid question_id"}) + return + } + + if question.UserID != userid { + c.JSON(http.StatusForbidden, gin.H{"error": "permission denied"}) + return + } + + // Delete question + err = db.DeleteQuestion(questionInfo.QuestionID) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "delete question error"}) + return + } + + // Delete answers to the question + err = deleteAnswers(questionInfo.QuestionID) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "delete answers error"}) + return + } + + c.JSON(http.StatusOK, gin.H{"msg": "delete question successful!"}) +} + +func deleteAnswers(questionID string) error { + answers, err := db.GetAnswersByQuestionID(questionID) + if err != nil { + return err + } + + for _, answer := range *answers { + err = db.DeleteAnswer(answer.AnswerID) + if err != nil { + return err + } + } + + return nil +} diff --git a/l1nk4i/api/question/get.go b/l1nk4i/api/question/get.go new file mode 100644 index 0000000..1b6c453 --- /dev/null +++ b/l1nk4i/api/question/get.go @@ -0,0 +1,27 @@ +package question + +import ( + "github.com/gin-gonic/gin" + "l1nk4i/db" + "net/http" +) + +// Get gets question by question_id +func Get(c *gin.Context) { + var questionInfo struct { + QuestionID string `json:"question_id"` + } + + if err := c.ShouldBind(&questionInfo); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON"}) + return + } + + question, err := db.GetQuestionByQuestionID(questionInfo.QuestionID) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid question_id"}) + return + } + + c.JSON(http.StatusOK, gin.H{"data": question}) +} diff --git a/l1nk4i/api/question/list.go b/l1nk4i/api/question/list.go new file mode 100644 index 0000000..43d8468 --- /dev/null +++ b/l1nk4i/api/question/list.go @@ -0,0 +1,31 @@ +package question + +import ( + "github.com/gin-contrib/sessions" + "github.com/gin-gonic/gin" + "l1nk4i/db" + "net/http" +) + +// List lists user's all question_id +func List(c *gin.Context) { + session := sessions.Default(c) + userID, exists := session.Get("user_id").(string) + if !exists { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid session"}) + return + } + + questions, err := db.GetQuestionByUserID(userID) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid session"}) + return + } + + var questionIDs []string + for _, question := range *questions { + questionIDs = append(questionIDs, question.QuestionID) + } + + c.JSON(http.StatusOK, gin.H{"question_id": questionIDs}) +} diff --git a/l1nk4i/api/question/search.go b/l1nk4i/api/question/search.go new file mode 100644 index 0000000..d93b4ed --- /dev/null +++ b/l1nk4i/api/question/search.go @@ -0,0 +1,24 @@ +package question + +import ( + "github.com/gin-gonic/gin" + "l1nk4i/db" + "net/http" +) + +func Search(c *gin.Context) { + var searchInfo struct { + Content string `json:"content"` + } + + if err := c.ShouldBind(&searchInfo); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON"}) + } + + questions, err := db.SearchQuestions(searchInfo.Content) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid content"}) + } + + c.JSON(http.StatusOK, gin.H{"data": questions}) +} diff --git a/l1nk4i/api/question/update.go b/l1nk4i/api/question/update.go new file mode 100644 index 0000000..ba21433 --- /dev/null +++ b/l1nk4i/api/question/update.go @@ -0,0 +1,49 @@ +package question + +import ( + "github.com/gin-contrib/sessions" + "github.com/gin-gonic/gin" + "l1nk4i/db" + "net/http" +) + +func Update(c *gin.Context) { + var questionInfo struct { + QuestionID string `json:"question_id"` + Title string `json:"title"` + Content string `json:"content"` + } + + if err := c.ShouldBind(&questionInfo); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON"}) + return + } + + // Verify user identity + session := sessions.Default(c) + userid, exists := session.Get("user_id").(string) + if !exists { + c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid session"}) + return + } + + question, err := db.GetQuestionByQuestionID(questionInfo.QuestionID) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid question_id"}) + return + } + + if question.UserID != userid { + c.JSON(http.StatusForbidden, gin.H{"error": "permission denied"}) + return + } + + // Update question + err = db.UpdateQuestion(questionInfo.QuestionID, questionInfo.Title, questionInfo.Content) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "update question error"}) + return + } + + c.JSON(http.StatusOK, gin.H{"msg": "update question successful!"}) +} From 6f478b92b8a955c2b6375f5cb824bf972c5de414 Mon Sep 17 00:00:00 2001 From: l1nk4i <l1nk@foxmail.com> Date: Sat, 28 Sep 2024 00:28:33 +0800 Subject: [PATCH 08/14] add: answer api --- l1nk4i/api/answer/create.go | 49 +++++++++++++++++++++++++++++++++++++ l1nk4i/api/answer/delete.go | 47 +++++++++++++++++++++++++++++++++++ l1nk4i/api/answer/get.go | 27 ++++++++++++++++++++ l1nk4i/api/answer/update.go | 48 ++++++++++++++++++++++++++++++++++++ 4 files changed, 171 insertions(+) create mode 100644 l1nk4i/api/answer/create.go create mode 100644 l1nk4i/api/answer/delete.go create mode 100644 l1nk4i/api/answer/get.go create mode 100644 l1nk4i/api/answer/update.go diff --git a/l1nk4i/api/answer/create.go b/l1nk4i/api/answer/create.go new file mode 100644 index 0000000..59cd827 --- /dev/null +++ b/l1nk4i/api/answer/create.go @@ -0,0 +1,49 @@ +package answer + +import ( + "github.com/gin-contrib/sessions" + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "l1nk4i/db" + "net/http" +) + +func Create(c *gin.Context) { + var answerInfo struct { + QuestionID string `json:"question_id"` + Content string `json:"content"` + } + + if err := c.ShouldBind(&answerInfo); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON"}) + return + } + + session := sessions.Default(c) + userID, exists := session.Get("user_id").(string) + if !exists { + c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid session"}) + return + } + + // Verify that the question exists + _, err := db.GetQuestionByQuestionID(answerInfo.QuestionID) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid question_id"}) + return + } + + answer := db.Answer{ + AnswerID: uuid.New().String(), + UserID: userID, + QuestionID: answerInfo.QuestionID, + Content: answerInfo.Content, + } + if err := db.CreateAnswer(&answer); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "create answer error"}) + return + } + + c.JSON(http.StatusOK, gin.H{"answer_id": answer.AnswerID}) + return +} diff --git a/l1nk4i/api/answer/delete.go b/l1nk4i/api/answer/delete.go new file mode 100644 index 0000000..0b1c803 --- /dev/null +++ b/l1nk4i/api/answer/delete.go @@ -0,0 +1,47 @@ +package answer + +import ( + "github.com/gin-contrib/sessions" + "github.com/gin-gonic/gin" + "l1nk4i/db" + "net/http" +) + +func Delete(c *gin.Context) { + var answerInfo struct { + AnswerID string `json:"answer_id"` + } + + if err := c.ShouldBindJSON(&answerInfo); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON"}) + return + } + + // Verify user identity + session := sessions.Default(c) + userid, exists := session.Get("user_id").(string) + if !exists { + c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid session"}) + return + } + + answer, err := db.GetAnswerByAnswerID(answerInfo.AnswerID) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid answer_id"}) + return + } + + if answer.UserID != userid { + c.JSON(http.StatusForbidden, gin.H{"error": "permission denied"}) + return + } + + // Delete answer + err = db.DeleteAnswer(answerInfo.AnswerID) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "delete answer error"}) + return + } + + c.JSON(http.StatusOK, gin.H{"msg": "delete answer successful!"}) +} diff --git a/l1nk4i/api/answer/get.go b/l1nk4i/api/answer/get.go new file mode 100644 index 0000000..fa49e77 --- /dev/null +++ b/l1nk4i/api/answer/get.go @@ -0,0 +1,27 @@ +package answer + +import ( + "github.com/gin-gonic/gin" + "l1nk4i/db" + "net/http" +) + +// Get gets answers to the question by question_id +func Get(c *gin.Context) { + var QuestionInfo struct { + QuestionId string `json:"question_id"` + } + + if err := c.ShouldBind(&QuestionInfo); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON"}) + return + } + + answers, err := db.GetAnswersByQuestionID(QuestionInfo.QuestionId) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid question_id"}) + return + } + + c.JSON(http.StatusOK, gin.H{"data": answers}) +} diff --git a/l1nk4i/api/answer/update.go b/l1nk4i/api/answer/update.go new file mode 100644 index 0000000..11b59f5 --- /dev/null +++ b/l1nk4i/api/answer/update.go @@ -0,0 +1,48 @@ +package answer + +import ( + "github.com/gin-contrib/sessions" + "github.com/gin-gonic/gin" + "l1nk4i/db" + "net/http" +) + +func Update(c *gin.Context) { + var answerInfo struct { + AnswerID string `json:"answer_id"` + Content string `json:"content"` + } + + if err := c.ShouldBindJSON(&answerInfo); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON"}) + return + } + + // Verify user identity + session := sessions.Default(c) + userid, exists := session.Get("user_id").(string) + if !exists { + c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid session"}) + return + } + + answer, err := db.GetAnswerByAnswerID(answerInfo.AnswerID) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid answer_id"}) + return + } + + if answer.UserID != userid { + c.JSON(http.StatusForbidden, gin.H{"error": "permission denied"}) + return + } + + // Update answer + err = db.UpdateAnswer(answerInfo.AnswerID, answerInfo.Content) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid answer"}) + return + } + + c.JSON(http.StatusOK, gin.H{"msg": "update answer successful!"}) +} From 9a75317ad848689030aa1fb269f567c5031545d6 Mon Sep 17 00:00:00 2001 From: l1nk4i <l1nk@foxmail.com> Date: Sat, 28 Sep 2024 00:30:42 +0800 Subject: [PATCH 09/14] add: backend router --- l1nk4i/main.go | 7 ++++++ l1nk4i/router/router.go | 50 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 l1nk4i/main.go create mode 100644 l1nk4i/router/router.go diff --git a/l1nk4i/main.go b/l1nk4i/main.go new file mode 100644 index 0000000..e03e837 --- /dev/null +++ b/l1nk4i/main.go @@ -0,0 +1,7 @@ +package main + +import "l1nk4i/router" + +func main() { + router.Run() +} diff --git a/l1nk4i/router/router.go b/l1nk4i/router/router.go new file mode 100644 index 0000000..849135c --- /dev/null +++ b/l1nk4i/router/router.go @@ -0,0 +1,50 @@ +package router + +import ( + "crypto/rand" + "github.com/gin-contrib/sessions" + "github.com/gin-contrib/sessions/cookie" + "github.com/gin-gonic/gin" + "l1nk4i/api/answer" + "l1nk4i/api/question" + "l1nk4i/api/user" +) + +func Run() { + r := gin.Default() + + secret := make([]byte, 32) + _, _ = rand.Read(secret) + store := cookie.NewStore(secret) + r.Use(sessions.Sessions("session", store)) + + apiGroup := r.Group("/api") + { + userApiGroup := apiGroup.Group("/user") + { + userApiGroup.POST("/login", user.Login) + userApiGroup.POST("/register", user.Register) + userApiGroup.GET("/logout", user.Logout) + userApiGroup.GET("/userinfo", user.UserInfo) + } + questionApiGroup := apiGroup.Group("/question") + { + questionApiGroup.POST("/create", question.Create) + questionApiGroup.POST("/delete", question.Delete) + questionApiGroup.POST("/update", question.Update) + questionApiGroup.POST("/get", question.Get) + + questionApiGroup.POST("/list", question.List) + questionApiGroup.POST("/search", question.Search) + } + answerApiGroup := apiGroup.Group("/answer") + { + answerApiGroup.POST("/create", answer.Create) + answerApiGroup.POST("/delete", answer.Delete) + answerApiGroup.POST("/update", answer.Update) + answerApiGroup.POST("/get", answer.Get) + } + } + + r.Run(":8080") +} From 2fc97d85b201ca198740ba93287b6791f7e0a3ee Mon Sep 17 00:00:00 2001 From: l1nk4i <l1nk@foxmail.com> Date: Wed, 2 Oct 2024 14:43:47 +0800 Subject: [PATCH 10/14] refactor: fit RESTful --- .gitignore | 4 +- l1nk4i/api/answer/create.go | 9 +-- l1nk4i/api/answer/delete.go | 13 +--- l1nk4i/api/answer/get.go | 11 +--- l1nk4i/api/answer/update.go | 9 +-- l1nk4i/api/question/delete.go | 15 ++--- l1nk4i/api/question/get.go | 11 +--- l1nk4i/api/question/search.go | 10 +-- l1nk4i/api/question/update.go | 11 ++-- l1nk4i/go.mod | 48 ++++++++++++++ l1nk4i/go.sum | 114 ++++++++++++++++++++++++++++++++++ l1nk4i/router/router.go | 29 +++++---- 12 files changed, 208 insertions(+), 76 deletions(-) create mode 100644 l1nk4i/go.mod create mode 100644 l1nk4i/go.sum diff --git a/.gitignore b/.gitignore index 723ef36..ae234af 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -.idea \ No newline at end of file +.idea +l1nk4i/config.toml +.gitignore \ No newline at end of file diff --git a/l1nk4i/api/answer/create.go b/l1nk4i/api/answer/create.go index 59cd827..a8590fe 100644 --- a/l1nk4i/api/answer/create.go +++ b/l1nk4i/api/answer/create.go @@ -9,9 +9,10 @@ import ( ) func Create(c *gin.Context) { + questionID := c.Param("question-id") + var answerInfo struct { - QuestionID string `json:"question_id"` - Content string `json:"content"` + Content string `json:"content"` } if err := c.ShouldBind(&answerInfo); err != nil { @@ -27,7 +28,7 @@ func Create(c *gin.Context) { } // Verify that the question exists - _, err := db.GetQuestionByQuestionID(answerInfo.QuestionID) + _, err := db.GetQuestionByQuestionID(questionID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid question_id"}) return @@ -36,7 +37,7 @@ func Create(c *gin.Context) { answer := db.Answer{ AnswerID: uuid.New().String(), UserID: userID, - QuestionID: answerInfo.QuestionID, + QuestionID: questionID, Content: answerInfo.Content, } if err := db.CreateAnswer(&answer); err != nil { diff --git a/l1nk4i/api/answer/delete.go b/l1nk4i/api/answer/delete.go index 0b1c803..77bb250 100644 --- a/l1nk4i/api/answer/delete.go +++ b/l1nk4i/api/answer/delete.go @@ -8,14 +8,7 @@ import ( ) func Delete(c *gin.Context) { - var answerInfo struct { - AnswerID string `json:"answer_id"` - } - - if err := c.ShouldBindJSON(&answerInfo); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON"}) - return - } + answerID := c.Param("answer-id") // Verify user identity session := sessions.Default(c) @@ -25,7 +18,7 @@ func Delete(c *gin.Context) { return } - answer, err := db.GetAnswerByAnswerID(answerInfo.AnswerID) + answer, err := db.GetAnswerByAnswerID(answerID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid answer_id"}) return @@ -37,7 +30,7 @@ func Delete(c *gin.Context) { } // Delete answer - err = db.DeleteAnswer(answerInfo.AnswerID) + err = db.DeleteAnswer(answerID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "delete answer error"}) return diff --git a/l1nk4i/api/answer/get.go b/l1nk4i/api/answer/get.go index fa49e77..ede658c 100644 --- a/l1nk4i/api/answer/get.go +++ b/l1nk4i/api/answer/get.go @@ -8,16 +8,9 @@ import ( // Get gets answers to the question by question_id func Get(c *gin.Context) { - var QuestionInfo struct { - QuestionId string `json:"question_id"` - } - - if err := c.ShouldBind(&QuestionInfo); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON"}) - return - } + questionID := c.Param("question-id") - answers, err := db.GetAnswersByQuestionID(QuestionInfo.QuestionId) + answers, err := db.GetAnswersByQuestionID(questionID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid question_id"}) return diff --git a/l1nk4i/api/answer/update.go b/l1nk4i/api/answer/update.go index 11b59f5..48b4fd6 100644 --- a/l1nk4i/api/answer/update.go +++ b/l1nk4i/api/answer/update.go @@ -8,9 +8,10 @@ import ( ) func Update(c *gin.Context) { + answerID := c.Param("answer-id") + var answerInfo struct { - AnswerID string `json:"answer_id"` - Content string `json:"content"` + Content string `json:"content"` } if err := c.ShouldBindJSON(&answerInfo); err != nil { @@ -26,7 +27,7 @@ func Update(c *gin.Context) { return } - answer, err := db.GetAnswerByAnswerID(answerInfo.AnswerID) + answer, err := db.GetAnswerByAnswerID(answerID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid answer_id"}) return @@ -38,7 +39,7 @@ func Update(c *gin.Context) { } // Update answer - err = db.UpdateAnswer(answerInfo.AnswerID, answerInfo.Content) + err = db.UpdateAnswer(answerID, answerInfo.Content) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid answer"}) return diff --git a/l1nk4i/api/question/delete.go b/l1nk4i/api/question/delete.go index d68382a..8f84903 100644 --- a/l1nk4i/api/question/delete.go +++ b/l1nk4i/api/question/delete.go @@ -8,14 +8,7 @@ import ( ) func Delete(c *gin.Context) { - var questionInfo struct { - QuestionID string `json:"question_id"` - } - - if err := c.ShouldBind(&questionInfo); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON"}) - return - } + questionID := c.Param("question-id") // Verify user identity session := sessions.Default(c) @@ -25,7 +18,7 @@ func Delete(c *gin.Context) { return } - question, err := db.GetQuestionByQuestionID(questionInfo.QuestionID) + question, err := db.GetQuestionByQuestionID(questionID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid question_id"}) return @@ -37,14 +30,14 @@ func Delete(c *gin.Context) { } // Delete question - err = db.DeleteQuestion(questionInfo.QuestionID) + err = db.DeleteQuestion(questionID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "delete question error"}) return } // Delete answers to the question - err = deleteAnswers(questionInfo.QuestionID) + err = deleteAnswers(questionID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "delete answers error"}) return diff --git a/l1nk4i/api/question/get.go b/l1nk4i/api/question/get.go index 1b6c453..f372bd4 100644 --- a/l1nk4i/api/question/get.go +++ b/l1nk4i/api/question/get.go @@ -8,16 +8,9 @@ import ( // Get gets question by question_id func Get(c *gin.Context) { - var questionInfo struct { - QuestionID string `json:"question_id"` - } - - if err := c.ShouldBind(&questionInfo); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON"}) - return - } + questionID := c.Param("question-id") - question, err := db.GetQuestionByQuestionID(questionInfo.QuestionID) + question, err := db.GetQuestionByQuestionID(questionID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid question_id"}) return diff --git a/l1nk4i/api/question/search.go b/l1nk4i/api/question/search.go index d93b4ed..0af64bf 100644 --- a/l1nk4i/api/question/search.go +++ b/l1nk4i/api/question/search.go @@ -7,15 +7,9 @@ import ( ) func Search(c *gin.Context) { - var searchInfo struct { - Content string `json:"content"` - } - - if err := c.ShouldBind(&searchInfo); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON"}) - } + searchContent := c.Query("content") - questions, err := db.SearchQuestions(searchInfo.Content) + questions, err := db.SearchQuestions(searchContent) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid content"}) } diff --git a/l1nk4i/api/question/update.go b/l1nk4i/api/question/update.go index ba21433..5bc6d71 100644 --- a/l1nk4i/api/question/update.go +++ b/l1nk4i/api/question/update.go @@ -8,10 +8,11 @@ import ( ) func Update(c *gin.Context) { + questionID := c.Param("question-id") + var questionInfo struct { - QuestionID string `json:"question_id"` - Title string `json:"title"` - Content string `json:"content"` + Title string `json:"title"` + Content string `json:"content"` } if err := c.ShouldBind(&questionInfo); err != nil { @@ -27,7 +28,7 @@ func Update(c *gin.Context) { return } - question, err := db.GetQuestionByQuestionID(questionInfo.QuestionID) + question, err := db.GetQuestionByQuestionID(questionID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid question_id"}) return @@ -39,7 +40,7 @@ func Update(c *gin.Context) { } // Update question - err = db.UpdateQuestion(questionInfo.QuestionID, questionInfo.Title, questionInfo.Content) + err = db.UpdateQuestion(questionID, questionInfo.Title, questionInfo.Content) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "update question error"}) return diff --git a/l1nk4i/go.mod b/l1nk4i/go.mod new file mode 100644 index 0000000..ceedce0 --- /dev/null +++ b/l1nk4i/go.mod @@ -0,0 +1,48 @@ +module l1nk4i + +go 1.23.1 + +require ( + github.com/gin-contrib/sessions v1.0.1 + github.com/gin-gonic/gin v1.10.0 + golang.org/x/crypto v0.27.0 + gorm.io/driver/mysql v1.5.7 + gorm.io/gorm v1.25.12 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/bytedance/sonic v1.12.3 // indirect + github.com/bytedance/sonic/loader v0.2.0 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.5 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.22.1 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/goccy/go-json v0.10.3 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/context v1.1.2 // indirect + github.com/gorilla/securecookie v1.1.2 // indirect + github.com/gorilla/sessions v1.2.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.8 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + golang.org/x/arch v0.10.0 // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/text v0.18.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/l1nk4i/go.sum b/l1nk4i/go.sum new file mode 100644 index 0000000..66a1deb --- /dev/null +++ b/l1nk4i/go.sum @@ -0,0 +1,114 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/bytedance/sonic v1.12.3 h1:W2MGa7RCU1QTeYRTPE3+88mVC0yXmsRQRChiyVocVjU= +github.com/bytedance/sonic v1.12.3/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM= +github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4= +github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4= +github.com/gin-contrib/sessions v1.0.1 h1:3hsJyNs7v7N8OtelFmYXFrulAf6zSR7nW/putcPEHxI= +github.com/gin-contrib/sessions v1.0.1/go.mod h1:ouxSFM24/OgIud5MJYQJLpy6AwxQ5EYO9yLhbtObGkM= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= +github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o= +github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= +github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +golang.org/x/arch v0.10.0 h1:S3huipmSclq3PJMNe76NGwkBR504WFkQ5dhzWzP8ZW8= +golang.org/x/arch v0.10.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= +gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= diff --git a/l1nk4i/router/router.go b/l1nk4i/router/router.go index 849135c..100ac7f 100644 --- a/l1nk4i/router/router.go +++ b/l1nk4i/router/router.go @@ -20,29 +20,28 @@ func Run() { apiGroup := r.Group("/api") { - userApiGroup := apiGroup.Group("/user") + userApiGroup := apiGroup.Group("/users") { userApiGroup.POST("/login", user.Login) userApiGroup.POST("/register", user.Register) - userApiGroup.GET("/logout", user.Logout) + userApiGroup.POST("/logout", user.Logout) userApiGroup.GET("/userinfo", user.UserInfo) } - questionApiGroup := apiGroup.Group("/question") + questionApiGroup := apiGroup.Group("/questions") { - questionApiGroup.POST("/create", question.Create) - questionApiGroup.POST("/delete", question.Delete) - questionApiGroup.POST("/update", question.Update) - questionApiGroup.POST("/get", question.Get) - - questionApiGroup.POST("/list", question.List) - questionApiGroup.POST("/search", question.Search) + questionApiGroup.POST("/", question.Create) + questionApiGroup.DELETE("/:question-id", question.Delete) + questionApiGroup.PUT("/:question-id", question.Update) + questionApiGroup.GET("/:question-id", question.Get) + questionApiGroup.GET("/", question.List) + questionApiGroup.GET("/search", question.Search) + questionApiGroup.POST("/:question-id/answers", answer.Create) + questionApiGroup.GET("/:question-id/answers", answer.Get) } - answerApiGroup := apiGroup.Group("/answer") + answerApiGroup := apiGroup.Group("/answers") { - answerApiGroup.POST("/create", answer.Create) - answerApiGroup.POST("/delete", answer.Delete) - answerApiGroup.POST("/update", answer.Update) - answerApiGroup.POST("/get", answer.Get) + answerApiGroup.DELETE("/:answer-id", answer.Delete) + answerApiGroup.PUT("/:answer-id", answer.Update) } } From 5c258767564ddf830634c3a78fa0a05908f8cef3 Mon Sep 17 00:00:00 2001 From: l1nk4i <l1nk@foxmail.com> Date: Thu, 3 Oct 2024 00:18:08 +0800 Subject: [PATCH 11/14] fit: admin api and select best answer function --- .gitignore | 3 ++- l1nk4i/api/admin/deleteAnswer.go | 32 +++++++++++++++++++++++ l1nk4i/api/admin/deleteQuestion.go | 32 +++++++++++++++++++++++ l1nk4i/api/question/best.go | 41 ++++++++++++++++++++++++++++++ l1nk4i/db/question.go | 9 +++++++ l1nk4i/router/router.go | 7 +++++ 6 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 l1nk4i/api/admin/deleteAnswer.go create mode 100644 l1nk4i/api/admin/deleteQuestion.go create mode 100644 l1nk4i/api/question/best.go diff --git a/.gitignore b/.gitignore index ae234af..c48e205 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .idea l1nk4i/config.toml -.gitignore \ No newline at end of file +.gitignore +l1nk4i/test \ No newline at end of file diff --git a/l1nk4i/api/admin/deleteAnswer.go b/l1nk4i/api/admin/deleteAnswer.go new file mode 100644 index 0000000..470b4e9 --- /dev/null +++ b/l1nk4i/api/admin/deleteAnswer.go @@ -0,0 +1,32 @@ +package admin + +import ( + "github.com/gin-contrib/sessions" + "github.com/gin-gonic/gin" + "l1nk4i/db" + "net/http" +) + +func DeleteAnswer(c *gin.Context) { + answerID := c.Param("answer-id") + + session := sessions.Default(c) + role, exists := session.Get("role").(string) + if !exists { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid session"}) + return + } + + if role != "admin" { + c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + return + } + + err := db.DeleteAnswer(answerID) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid answer-id"}) + return + } + + c.JSON(http.StatusOK, gin.H{"msg": "delete answer successful!"}) +} diff --git a/l1nk4i/api/admin/deleteQuestion.go b/l1nk4i/api/admin/deleteQuestion.go new file mode 100644 index 0000000..511a339 --- /dev/null +++ b/l1nk4i/api/admin/deleteQuestion.go @@ -0,0 +1,32 @@ +package admin + +import ( + "github.com/gin-contrib/sessions" + "github.com/gin-gonic/gin" + "l1nk4i/db" + "net/http" +) + +func DeleteQuestion(c *gin.Context) { + questionID := c.Param("question-id") + + session := sessions.Default(c) + role, exists := session.Get("role").(string) + if !exists { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid session"}) + return + } + + if role != "admin" { + c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + return + } + + err := db.DeleteQuestion(questionID) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid question-id"}) + return + } + + c.JSON(http.StatusOK, gin.H{"msg": "delete question successful!"}) +} diff --git a/l1nk4i/api/question/best.go b/l1nk4i/api/question/best.go new file mode 100644 index 0000000..7c2c516 --- /dev/null +++ b/l1nk4i/api/question/best.go @@ -0,0 +1,41 @@ +package question + +import ( + "github.com/gin-contrib/sessions" + "github.com/gin-gonic/gin" + "l1nk4i/db" + "net/http" +) + +func Best(c *gin.Context) { + answerID := c.Param("answer-id") + questionID := c.Param("question-id") + + // Verify user identity + session := sessions.Default(c) + userid, exists := session.Get("user_id").(string) + if !exists { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid session"}) + return + } + + question, err := db.GetQuestionByQuestionID(questionID) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid question_id"}) + return + } + + if question.UserID != userid { + c.JSON(http.StatusForbidden, gin.H{"error": "permission denied"}) + return + } + + // Set the best answer + err = db.UpdateBestAnswer(questionID, answerID) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "update best answer failed"}) + return + } + + c.JSON(http.StatusOK, gin.H{"msg": "update best answer successful!"}) +} diff --git a/l1nk4i/db/question.go b/l1nk4i/db/question.go index e261e93..1451ff8 100644 --- a/l1nk4i/db/question.go +++ b/l1nk4i/db/question.go @@ -59,3 +59,12 @@ func SearchQuestions(content string) (*[]Question, error) { } return &questions, nil } + +func UpdateBestAnswer(questionID, answerID string) error { + err := db.Model(&Question{}).Where("question_id = ?", questionID).Updates(Question{BestAnswerID: answerID}).Error + if err != nil { + log.Printf("[ERROR] Update Question benst answer failed %s\n", err.Error()) + return err + } + return nil +} diff --git a/l1nk4i/router/router.go b/l1nk4i/router/router.go index 100ac7f..7fcb1ff 100644 --- a/l1nk4i/router/router.go +++ b/l1nk4i/router/router.go @@ -5,6 +5,7 @@ import ( "github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions/cookie" "github.com/gin-gonic/gin" + "l1nk4i/api/admin" "l1nk4i/api/answer" "l1nk4i/api/question" "l1nk4i/api/user" @@ -37,12 +38,18 @@ func Run() { questionApiGroup.GET("/search", question.Search) questionApiGroup.POST("/:question-id/answers", answer.Create) questionApiGroup.GET("/:question-id/answers", answer.Get) + questionApiGroup.PUT("/:question-id/best/:answer-id", question.Best) } answerApiGroup := apiGroup.Group("/answers") { answerApiGroup.DELETE("/:answer-id", answer.Delete) answerApiGroup.PUT("/:answer-id", answer.Update) } + adminApiGroup := apiGroup.Group("/admin") + { + adminApiGroup.DELETE("/questions/:question-id", admin.DeleteQuestion) + adminApiGroup.DELETE("/answers/:answer-id", admin.DeleteAnswer) + } } r.Run(":8080") From fead4d76dc5ac144c8ae60b3fa8238e7429d3f7d Mon Sep 17 00:00:00 2001 From: l1nk4i <l1nk@foxmail.com> Date: Thu, 3 Oct 2024 20:13:44 +0800 Subject: [PATCH 12/14] fix: validate input --- l1nk4i/api/question/best.go | 14 +++++++++++++- l1nk4i/api/question/delete.go | 23 ----------------------- l1nk4i/api/user/register.go | 8 ++++++-- l1nk4i/db/question.go | 19 +++++++++++++------ 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/l1nk4i/api/question/best.go b/l1nk4i/api/question/best.go index 7c2c516..231a1c5 100644 --- a/l1nk4i/api/question/best.go +++ b/l1nk4i/api/question/best.go @@ -21,7 +21,7 @@ func Best(c *gin.Context) { question, err := db.GetQuestionByQuestionID(questionID) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid question_id"}) + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid question-id"}) return } @@ -30,6 +30,18 @@ func Best(c *gin.Context) { return } + // Validate answer-id + answer, err := db.GetAnswerByAnswerID(answerID) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid answer-id"}) + return + } + + if answer.QuestionID != questionID { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid answer-id"}) + return + } + // Set the best answer err = db.UpdateBestAnswer(questionID, answerID) if err != nil { diff --git a/l1nk4i/api/question/delete.go b/l1nk4i/api/question/delete.go index 8f84903..6394260 100644 --- a/l1nk4i/api/question/delete.go +++ b/l1nk4i/api/question/delete.go @@ -36,28 +36,5 @@ func Delete(c *gin.Context) { return } - // Delete answers to the question - err = deleteAnswers(questionID) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "delete answers error"}) - return - } - c.JSON(http.StatusOK, gin.H{"msg": "delete question successful!"}) } - -func deleteAnswers(questionID string) error { - answers, err := db.GetAnswersByQuestionID(questionID) - if err != nil { - return err - } - - for _, answer := range *answers { - err = db.DeleteAnswer(answer.AnswerID) - if err != nil { - return err - } - } - - return nil -} diff --git a/l1nk4i/api/user/register.go b/l1nk4i/api/user/register.go index 261dc59..305ef41 100644 --- a/l1nk4i/api/user/register.go +++ b/l1nk4i/api/user/register.go @@ -20,8 +20,6 @@ func Register(c *gin.Context) { return } - fmt.Println(registerInfo) - if !validateUsername(registerInfo.Username) { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid Username"}) return @@ -32,6 +30,12 @@ func Register(c *gin.Context) { return } + exists, _ := db.GetUserByUsername(registerInfo.Username) + if exists != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "username exists"}) + return + } + user := db.User{ UserID: uuid.New().String(), Username: registerInfo.Username, diff --git a/l1nk4i/db/question.go b/l1nk4i/db/question.go index 1451ff8..5ab2dfc 100644 --- a/l1nk4i/db/question.go +++ b/l1nk4i/db/question.go @@ -5,7 +5,7 @@ import "log" func CreateQuestion(question *Question) error { err := db.Create(question).Error if err != nil { - log.Printf("[ERROR] Create Question failed %s\n", err.Error()) + log.Printf("[ERROR] Create question failed %s\n", err.Error()) return err } return nil @@ -15,7 +15,7 @@ func GetQuestionByQuestionID(questionID string) (*Question, error) { var question Question err := db.Where("question_id = ?", questionID).First(&question).Error if err != nil { - log.Printf("[ERROR] Get Question by question_id failed %s\n", err.Error()) + log.Printf("[ERROR] Get question by question_id failed %s\n", err.Error()) return nil, err } return &question, nil @@ -34,7 +34,14 @@ func GetQuestionByUserID(userID string) (*[]Question, error) { func DeleteQuestion(questionID string) error { err := db.Unscoped().Where("question_id = ?", questionID).Delete(&Question{}).Error if err != nil { - log.Printf("[ERROR] Delete Question failed %s\n", err.Error()) + log.Printf("[ERROR] Delete question failed %s\n", err.Error()) + return err + } + + // Delete answers + err = db.Unscoped().Where("question_id = ?", questionID).Delete(&Answer{}).Error + if err != nil { + log.Printf("[ERROR] Delete answers error:%s\n", err.Error()) return err } return nil @@ -43,7 +50,7 @@ func DeleteQuestion(questionID string) error { func UpdateQuestion(questionID, title, content string) error { err := db.Model(&Question{}).Where("question_id = ?", questionID).Updates(Question{Title: title, Content: content}).Error if err != nil { - log.Printf("[ERROR] Update Question failed %s\n", err.Error()) + log.Printf("[ERROR] Update question failed %s\n", err.Error()) return err } return nil @@ -54,7 +61,7 @@ func SearchQuestions(content string) (*[]Question, error) { searchPattern := "%" + content + "%" err := db.Where("title LIKE ? OR content LIKE ?", searchPattern, searchPattern).Limit(20).Find(&questions).Error if err != nil { - log.Printf("[ERROR] Search Questions failed %s\n", err.Error()) + log.Printf("[ERROR] Search questions failed %s\n", err.Error()) return nil, err } return &questions, nil @@ -63,7 +70,7 @@ func SearchQuestions(content string) (*[]Question, error) { func UpdateBestAnswer(questionID, answerID string) error { err := db.Model(&Question{}).Where("question_id = ?", questionID).Updates(Question{BestAnswerID: answerID}).Error if err != nil { - log.Printf("[ERROR] Update Question benst answer failed %s\n", err.Error()) + log.Printf("[ERROR] Update question benst answer failed %s\n", err.Error()) return err } return nil From dfa39c8e79b497a6773a99636bffb07f77a78d63 Mon Sep 17 00:00:00 2001 From: l1nk4i <l1nk@foxmail.com> Date: Fri, 4 Oct 2024 21:11:21 +0800 Subject: [PATCH 13/14] docs: api doc --- l1nk4i/docs/apiDoc.md | 890 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 890 insertions(+) create mode 100644 l1nk4i/docs/apiDoc.md diff --git a/l1nk4i/docs/apiDoc.md b/l1nk4i/docs/apiDoc.md new file mode 100644 index 0000000..6a849ca --- /dev/null +++ b/l1nk4i/docs/apiDoc.md @@ -0,0 +1,890 @@ +--- +title: hduhelp_backend_task +language_tabs: + - shell: Shell + - http: HTTP + - javascript: JavaScript + - ruby: Ruby + - python: Python + - php: PHP + - java: Java + - go: Go +toc_footers: [] +includes: [] +search: true +code_clipboard: true +highlight_theme: darkula +headingLevel: 2 +generator: "@tarslib/widdershins v4.0.23" + +--- + +# hduhelp_backend_task + +Base URLs: + +# Authentication + +# users + +## POST 用户注册 + +POST /api/users/register + +注册 + +> Body 请求参数 + +```json +{ + "username": "l1nkQAQ", + "password": "vidarteam" +} +``` + +### 请求参数 + +|名称|位置|类型|必选|说明| +|---|---|---|---|---| +|body|body|object| 否 |none| +|» username|body|string| 是 |none| +|» password|body|string| 是 |none| + +> 返回示例 + +> 200 Response + +```json +{ + "msg": "string" +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|none|Inline| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|none|Inline| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|none|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» msg|string|true|none||none| + +状态码 **400** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» error|string|true|none||none| + +状态码 **500** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» error|string|true|none||none| + +## GET 获取当前用户信息 + +GET /api/users/userinfo + +查询当前用户信息 + +> 返回示例 + +> 200 Response + +```json +{ + "role": "string", + "user_id": "string", + "username": "string" +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|none|Inline| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|none|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» role|string|true|none||none| +|» user_id|string|true|none||none| +|» username|string|true|none||none| + +状态码 **400** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» error|string|true|none||none| + +## POST 用户登录 + +POST /api/users/login + +登录 + +> Body 请求参数 + +```json +{ + "username": "l1nkQAQ", + "password": "vidarteam" +} +``` + +### 请求参数 + +|名称|位置|类型|必选|说明| +|---|---|---|---|---| +|body|body|object| 否 |none| +|» username|body|string| 是 |none| +|» password|body|string| 是 |none| + +> 返回示例 + +> 200 Response + +```json +{ + "msg": "string" +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|none|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» msg|string|true|none||none| + +## POST 用户登出 + +POST /api/users/logout + +登出 + +> 返回示例 + +> 200 Response + +```json +{ + "msg": "string" +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|none|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» msg|string|true|none||none| + +# questions + +## POST 创建问题 + +POST /api/questions + +创建问题 + +> Body 请求参数 + +```json +{ + "title": "man", + "content": "what can i say" +} +``` + +### 请求参数 + +|名称|位置|类型|必选|说明| +|---|---|---|---|---| +|body|body|object| 否 |none| +|» title|body|string| 是 |none| +|» content|body|string| 是 |none| + +> 返回示例 + +> 200 Response + +```json +{ + "msg": "string" +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|none|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» msg|string|true|none||none| + +## GET 列出用户发表的所有问题 + +GET /api/questions + +列出用户发表的所有问题 + +> 返回示例 + +> 200 Response + +```json +{ + "question_id": [ + "string" + ] +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|none|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» question_id|[string]|true|none||none| + +## POST 创建指定问题的回答 + +POST /api/questions/{question-id}/answers + +发送回答 + +> Body 请求参数 + +```json +{ + "content": "manman" +} +``` + +### 请求参数 + +|名称|位置|类型|必选|说明| +|---|---|---|---|---| +|question-id|path|string| 是 |none| +|body|body|object| 否 |none| +|» question_id|body|string| 是 |none| +|» content|body|string| 是 |none| + +> 返回示例 + +> 200 Response + +```json +{ + "answer_id": "string" +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|none|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» answer_id|string|true|none||none| + +## GET 获取指定问题的所有回答 + +GET /api/questions/{question-id}/answers + +获得问题对应的所有回答 + +### 请求参数 + +|名称|位置|类型|必选|说明| +|---|---|---|---|---| +|question-id|path|string| 是 |none| + +> 返回示例 + +> 200 Response + +```json +{ + "data": [ + { + "ID": 0, + "CreatedAt": "string", + "UpdatedAt": "string", + "DeletedAt": null, + "AnswerID": "string", + "UserID": "string", + "QuestionID": "string", + "Content": "string" + } + ] +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|none|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» data|[object]|true|none||none| +|»» ID|integer|true|none||none| +|»» CreatedAt|string|true|none||none| +|»» UpdatedAt|string|true|none||none| +|»» DeletedAt|null|true|none||none| +|»» AnswerID|string|true|none||none| +|»» UserID|string|true|none||none| +|»» QuestionID|string|true|none||none| +|»» Content|string|true|none||none| + +## DELETE 删除指定问题 + +DELETE /api/questions/{question-id} + +删除问题 + +### 请求参数 + +|名称|位置|类型|必选|说明| +|---|---|---|---|---| +|question-id|path|string| 是 |none| + +> 返回示例 + +> 200 Response + +```json +{ + "msg": "string" +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|none|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» msg|string|true|none||none| + +## PUT 更新指定问题 + +PUT /api/questions/{question-id} + +更改问题的标题或者内容 + +> Body 请求参数 + +```json +{ + "title": "manba", + "content": "out" +} +``` + +### 请求参数 + +|名称|位置|类型|必选|说明| +|---|---|---|---|---| +|question-id|path|string| 是 |none| +|body|body|object| 否 |none| +|» question_id|body|string| 是 |none| +|» title|body|string| 是 |none| +|» content|body|string| 是 |none| + +> 返回示例 + +> 200 Response + +```json +{ + "msg": "string" +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|none|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» msg|string|true|none||none| + +## GET 获取指定问题 + +GET /api/questions/{question-id} + +通过question_id查找对应问题对象 + +### 请求参数 + +|名称|位置|类型|必选|说明| +|---|---|---|---|---| +|question-id|path|string| 是 |none| + +> 返回示例 + +> 200 Response + +```json +{ + "data": { + "ID": 0, + "CreatedAt": "string", + "UpdatedAt": "string", + "DeletedAt": null, + "QuestionID": "string", + "UserID": "string", + "BestAnswerID": "string", + "Title": "string", + "Content": "string" + } +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|none|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» data|object|true|none||none| +|»» ID|integer|true|none||none| +|»» CreatedAt|string|true|none||none| +|»» UpdatedAt|string|true|none||none| +|»» DeletedAt|null|true|none||none| +|»» QuestionID|string|true|none||none| +|»» UserID|string|true|none||none| +|»» BestAnswerID|string|true|none||none| +|»» Title|string|true|none||none| +|»» Content|string|true|none||none| + +## GET 搜索问题 + +GET /api/questions/search + +通过输入匹配问题标题和内容,搜索问题(无鉴权) + +### 请求参数 + +|名称|位置|类型|必选|说明| +|---|---|---|---|---| +|content|query|string| 否 |none| + +> 返回示例 + +> 200 Response + +```json +{ + "data": [ + { + "ID": 0, + "CreatedAt": "string", + "UpdatedAt": "string", + "DeletedAt": null, + "QuestionID": "string", + "UserID": "string", + "BestAnswerID": "string", + "Title": "string", + "Content": "string" + } + ] +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|none|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» data|[object]|true|none||none| +|»» ID|integer|true|none||none| +|»» CreatedAt|string|true|none||none| +|»» UpdatedAt|string|true|none||none| +|»» DeletedAt|null|true|none||none| +|»» QuestionID|string|true|none||none| +|»» UserID|string|true|none||none| +|»» BestAnswerID|string|true|none||none| +|»» Title|string|true|none||none| +|»» Content|string|true|none||none| + +## PUT 更新最佳答案 + +PUT /api/questions/{question-id}/best/{answer-id} + +更新最佳答案 + +### 请求参数 + +|名称|位置|类型|必选|说明| +|---|---|---|---|---| +|question-id|path|string| 是 |none| +|answer-id|path|string| 是 |none| + +> 返回示例 + +> 200 Response + +```json +{ + "msg": "string" +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|none|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» msg|string|true|none||none| + +# answers + +## DELETE 删除指定回答 + +DELETE /api/answers/{answer-id} + +删除回答 + +### 请求参数 + +|名称|位置|类型|必选|说明| +|---|---|---|---|---| +|answer-id|path|string| 是 |none| + +> 返回示例 + +> 200 Response + +```json +{ + "msg": "string" +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|none|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» msg|string|true|none||none| + +## PUT 更新指定回答 + +PUT /api/answers/{answer-id} + +更改回答内容 + +> Body 请求参数 + +```json +{ + "content": "manba out" +} +``` + +### 请求参数 + +|名称|位置|类型|必选|说明| +|---|---|---|---|---| +|answer-id|path|string| 是 |none| +|body|body|object| 否 |none| +|» answer_id|body|string| 是 |none| +|» content|body|string| 是 |none| + +> 返回示例 + +> 200 Response + +```json +{ + "msg": "string" +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|none|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» msg|string|true|none||none| + +# admin + +## DELETE 删除问题 + +DELETE /api/admin/questions/{question-id} + +删除问题 + +### 请求参数 + +|名称|位置|类型|必选|说明| +|---|---|---|---|---| +|question-id|path|string| 是 |none| + +> 返回示例 + +> 200 Response + +```json +{ + "msg": "string" +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|none|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» msg|string|true|none||none| + +## DELETE 删除答案 + +DELETE /api/admin/answers/{answer-id} + +删除答案 + +### 请求参数 + +|名称|位置|类型|必选|说明| +|---|---|---|---|---| +|answer-id|path|string| 是 |none| + +> 返回示例 + +> 200 Response + +```json +{ + "msg": "string" +} +``` + +### 返回结果 + +|状态码|状态码含义|说明|数据模型| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|none|Inline| + +### 返回数据结构 + +状态码 **200** + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|» msg|string|true|none||none| + +# 数据模型 + +<h2 id="tocS_User">User</h2> + +<a id="schemauser"></a> +<a id="schema_User"></a> +<a id="tocSuser"></a> +<a id="tocsuser"></a> + +```json +{ + "username": "string", + "password": "string", + "role": "string", + "created_at": "2019-08-24T14:15:22Z", + "deleted_at": "2019-08-24T14:15:22Z", + "id": 1, + "updated_at": "2019-08-24T14:15:22Z", + "user_id": "string" +} + +``` + +### 属性 + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|username|string|true|none||none| +|password|string|true|none||none| +|role|string|true|none||none| +|created_at|string(date-time)|false|none||none| +|deleted_at|string(date-time)|false|none||none| +|id|integer|true|none||none| +|updated_at|string(date-time)|false|none||none| +|user_id|string|true|none||none| + +<h2 id="tocS_Question">Question</h2> + +<a id="schemaquestion"></a> +<a id="schema_Question"></a> +<a id="tocSquestion"></a> +<a id="tocsquestion"></a> + +```json +{ + "title": "string", + "content": "string", + "best_answer_id": "string", + "created_at": "2019-08-24T14:15:22Z", + "deleted_at": "2019-08-24T14:15:22Z", + "id": 1, + "question_id": "string", + "updated_at": "2019-08-24T14:15:22Z", + "user_id": "string" +} + +``` + +### 属性 + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|title|string|true|none||none| +|content|string|true|none||none| +|best_answer_id|string|false|none||none| +|created_at|string(date-time)|false|none||none| +|deleted_at|string(date-time)|false|none||none| +|id|integer|true|none||none| +|question_id|string|true|none||none| +|updated_at|string(date-time)|false|none||none| +|user_id|string|true|none||none| + +<h2 id="tocS_Answer">Answer</h2> + +<a id="schemaanswer"></a> +<a id="schema_Answer"></a> +<a id="tocSanswer"></a> +<a id="tocsanswer"></a> + +```json +{ + "content": "string", + "answer_id": "string", + "created_at": "2019-08-24T14:15:22Z", + "deleted_at": "2019-08-24T14:15:22Z", + "id": 1, + "question_id": "string", + "updated_at": "2019-08-24T14:15:22Z", + "user_id": "string" +} + +``` + +### 属性 + +|名称|类型|必选|约束|中文名|说明| +|---|---|---|---|---|---| +|content|string|true|none||none| +|answer_id|string|true|none||none| +|created_at|string(date-time)|false|none||none| +|deleted_at|string(date-time)|false|none||none| +|id|integer|true|none||none| +|question_id|string|true|none||none| +|updated_at|string(date-time)|false|none||none| +|user_id|string|true|none||none| + From c8baf54eb24b06579a6543e31c4b607e9f3f74d0 Mon Sep 17 00:00:00 2001 From: l1nk4i <l1nk@foxmail.com> Date: Sat, 5 Oct 2024 21:56:47 +0800 Subject: [PATCH 14/14] feat: support docker now --- l1nk4i/Dockerfile | 20 ++++++++++++++++++++ l1nk4i/api/user/register.go | 5 ++--- l1nk4i/docker-compose.yml | 23 +++++++++++++++++++++++ l1nk4i/entrypoint.sh | 15 +++++++++++++++ l1nk4i/init.sql | 4 ++++ 5 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 l1nk4i/Dockerfile create mode 100644 l1nk4i/docker-compose.yml create mode 100644 l1nk4i/entrypoint.sh create mode 100644 l1nk4i/init.sql diff --git a/l1nk4i/Dockerfile b/l1nk4i/Dockerfile new file mode 100644 index 0000000..0ce0926 --- /dev/null +++ b/l1nk4i/Dockerfile @@ -0,0 +1,20 @@ +FROM golang:1.23.1 AS builder + +WORKDIR /app + +COPY . . + +RUN go build -o myapp + +FROM ubuntu:latest + +WORKDIR /app + +COPY --from=builder /app/myapp /app/ +COPY config.toml . +COPY entrypoint.sh . +RUN chmod +x entrypoint.sh + +ENTRYPOINT ["./entrypoint.sh"] + +EXPOSE 8080 \ No newline at end of file diff --git a/l1nk4i/api/user/register.go b/l1nk4i/api/user/register.go index 305ef41..8b184ef 100644 --- a/l1nk4i/api/user/register.go +++ b/l1nk4i/api/user/register.go @@ -1,7 +1,6 @@ package user import ( - "fmt" "github.com/gin-gonic/gin" "github.com/google/uuid" "l1nk4i/db" @@ -14,7 +13,7 @@ func Register(c *gin.Context) { Username string `json:"username"` Password string `json:"password"` } - fmt.Println(registerInfo) + if err := c.ShouldBindJSON(®isterInfo); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON"}) return @@ -35,7 +34,7 @@ func Register(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": "username exists"}) return } - + user := db.User{ UserID: uuid.New().String(), Username: registerInfo.Username, diff --git a/l1nk4i/docker-compose.yml b/l1nk4i/docker-compose.yml new file mode 100644 index 0000000..f6acf33 --- /dev/null +++ b/l1nk4i/docker-compose.yml @@ -0,0 +1,23 @@ +version: '3.8' + +services: + db: + image: mysql:latest + environment: + MYSQL_RANDOM_ROOT_PASSWORD: true + volumes: + - /home/l1nk/mysql_data:/var/lib/mysql + - ./init.sql:/docker-entrypoint-initdb.d/init.sql + expose: + - "3306" + - "33060" + ports: + - "3306:3306" # debug + + + app: + build: . + ports: + - "8080:8080" + depends_on: + - db diff --git a/l1nk4i/entrypoint.sh b/l1nk4i/entrypoint.sh new file mode 100644 index 0000000..17a3f78 --- /dev/null +++ b/l1nk4i/entrypoint.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# 循环执行 myapp +while true; do + ./myapp # 运行应用 + + # 检查上一个命令的退出状态 + if [ $? -eq 0 ]; then + echo "myapp executed successfully." + break # 如果成功,退出循环 + else + echo "myapp failed. Retrying in 2 seconds..." + sleep 2 # 等待 2 秒后重试 + fi +done diff --git a/l1nk4i/init.sql b/l1nk4i/init.sql new file mode 100644 index 0000000..5b6ce06 --- /dev/null +++ b/l1nk4i/init.sql @@ -0,0 +1,4 @@ +CREATE DATABASE appdb; +CREATE USER 'app'@'%' IDENTIFIED BY 'safe_password'; +GRANT ALL PRIVILEGES ON appdb.* TO 'app'@'%'; +FLUSH PRIVILEGES; \ No newline at end of file