diff --git a/README.md b/README.md index d63a30f..1d08a58 100644 --- a/README.md +++ b/README.md @@ -94,16 +94,16 @@ log: path: logs/go-gin.log # Log file path db_path: db/go-gin.sqlite # Database path rate_limit: - max: 100 # requests per minute + max: 100 # requests per minute, 0 means no limit enable_cors: false # Enable CORS enable_user_registration: true # Enable user registration upload: - virtual_path: /upload # Virtual path - dir: upload # Upload directory + virtual_path: /upload # Virtual path, used for generating absolute URLs, must start with / + dir: upload # Upload directory, relative to the project root directory. Or you can use an absolute path, such as /var/www/upload max_size: 10485760 # 10MB, unit: byte - keep_original_name: true # Keep original file name - create_date_dir: true # Create date directory - allow_types: # Allowed file types + keep_original_name: false # Keep original file name, if false, the server will generate a random file name + create_date_dir: true # Create date directory, such as /upload/2021/01/01 + allow_types: # Allowed file types, if empty, all types are allowed - image/jpeg - image/jpg - image/png diff --git a/cmd/srv/controller/api_v1.go b/cmd/srv/controller/api_v1.go index a1671d1..f29a09c 100644 --- a/cmd/srv/controller/api_v1.go +++ b/cmd/srv/controller/api_v1.go @@ -28,16 +28,16 @@ func (v *apiV1) serve() { Redirect: fmt.Sprintf("%s/login", singleton.Conf.Site.BaseURL), })) - r.POST("/attchment/upload", v.upload) // upload file + r.PUT("/attachment", v.upload) // upload file - r.POST("/post", v.postPost) // create post + r.POST("/post", v.editPost) // create post r.GET("/post/:id", v.getPost) // get post r.DELETE("/post/:id", v.deletePost) // delete post r.GET("/posts", v.getPosts) // get posts - user := v.r.Group("user") + user := r.Group("user") { - user.GET("/info", v.getUserInfo) + user.GET("/info", v.userInfo) user.GET("/logout", v.logout) user.GET("/refresh", v.refresh) } @@ -46,12 +46,12 @@ func (v *apiV1) serve() { var authModel = model.Auth{} func (v *apiV1) upload(c *gin.Context) { - result, err := singleton.AttchmentUpload.Upload(c) + attachment, err := gogin.AttachmentUpload(c) if err != nil { mygin.ResponseJSON(c, 400, gin.H{}, err.Error()) return } - mygin.ResponseJSON(c, 200, result, "upload success") + mygin.ResponseJSON(c, 200, attachment, "upload success") } func (v *apiV1) logout(c *gin.Context) { @@ -78,7 +78,7 @@ func (v *apiV1) refresh(c *gin.Context) { mygin.ResponseJSON(c, 200, tk, "refresh success") } -func (v *apiV1) postPost(c *gin.Context) { +func (v *apiV1) editPost(c *gin.Context) { var postForm mappers.PostForm isForm := parse.ParseBool(c.Query("form"), false) if err := mygin.BindForm(c, isForm, &postForm); err != nil { @@ -109,33 +109,57 @@ func (v *apiV1) postPost(c *gin.Context) { gogin.ShowMessagePage(c, "Post success", fmt.Sprintf("/post/%d", post.ID), "View post") } else { mygin.ResponseJSON(c, 200, - gogin.CommonEnvironment(c, gin.H{ - "post": post, - }), "post success") + gin.H{ + "info": post, + }, "post success") } } func (v *apiV1) getPost(c *gin.Context) { - c.JSON(200, gin.H{ - "message": "post", - }) + var post model.Post + err := post.Get(parse.ParseInt(c.Param("id"), 0), singleton.DB) + if err != nil { + mygin.ResponseJSON(c, 400, gin.H{}, err.Error()) + return + } + mygin.ResponseJSON(c, 200, + gin.H{ + "info": post, + }) } func (v *apiV1) deletePost(c *gin.Context) { - c.JSON(200, gin.H{ - "message": "post", - }) + var post model.Post + err := post.Get(parse.ParseInt(c.Param("id"), 0), singleton.DB) + if err != nil { + mygin.ResponseJSON(c, 400, gin.H{}, err.Error()) + return + } + if post.CreatedUser != gogin.GetCurrentUserId(c) { + mygin.ResponseJSON(c, 400, gin.H{}, "no permission") + return + } + err = post.Delete(parse.ParseInt(c.Param("id"), 0), singleton.DB) + if err != nil { + mygin.ResponseJSON(c, 400, gin.H{}, err.Error()) + return + } + mygin.ResponseJSON(c, 200, gin.H{}, "delete success") } func (v *apiV1) getPosts(c *gin.Context) { - posts, _ := postModel.List(singleton.DB, "") - mygin.ResponseJSON(c, 200, gogin.CommonEnvironment(c, gin.H{ - "Posts": posts, - })) + posts, _ := model.NewPost().List(singleton.DB, "id > ?", 0) + mygin.ResponseJSON(c, 200, gin.H{ + "list": posts, + }) } -func (v *apiV1) getUserInfo(c *gin.Context) { - c.JSON(200, gin.H{ - "message": "user info", - }) +func (v *apiV1) userInfo(c *gin.Context) { + var user model.User + err := user.GetByID(gogin.GetCurrentUserId(c), singleton.DB) + if err != nil { + mygin.ResponseJSON(c, 400, gin.H{}, err.Error()) + return + } + mygin.ResponseJSON(c, 200, user, "get user info success") } diff --git a/cmd/srv/controller/show_page.go b/cmd/srv/controller/show_page.go index 595fbcb..f9007d7 100644 --- a/cmd/srv/controller/show_page.go +++ b/cmd/srv/controller/show_page.go @@ -15,8 +15,6 @@ type showPage struct { r *gin.Engine } -var postModel = model.Post{} - func (sp *showPage) serve() { gr := sp.r.Group("") gr.GET("/post/:id", sp.postDetail) @@ -31,7 +29,8 @@ func (sp *showPage) postDetail(c *gin.Context) { Code: http.StatusNotFound}, true) return } - post, err := postModel.Get(postId, singleton.DB) + post := model.Post{} + err := post.Get(postId, singleton.DB) if err != nil { gogin.ShowErrorPage(c, mygin.ErrInfo{ Msg: "Post not found", @@ -45,7 +44,7 @@ func (sp *showPage) postDetail(c *gin.Context) { } func (sp *showPage) postList(c *gin.Context) { - posts, _ := postModel.List(singleton.DB, "id > ?", 0) + posts, _ := model.NewPost().List(singleton.DB, "id > ?", 0) c.HTML(http.StatusOK, "post/list", gogin.CommonEnvironment(c, gin.H{ "Posts": posts, })) diff --git a/cmd/srv/controller/user_api.go b/cmd/srv/controller/user_api.go index 60d2749..504094c 100644 --- a/cmd/srv/controller/user_api.go +++ b/cmd/srv/controller/user_api.go @@ -19,8 +19,8 @@ type userAPI struct { func (ua *userAPI) serve() { ur := ua.r.Group("") - ur.POST("/login", ua.login) - ur.POST("/register", ua.register) + ur.POST("/user/login", ua.login) + ur.POST("/user/register", ua.register) v1 := ua.r.Group("v1") { diff --git a/cmd/srv/controller/user_page.go b/cmd/srv/controller/user_page.go index b705acc..e38beb9 100644 --- a/cmd/srv/controller/user_page.go +++ b/cmd/srv/controller/user_page.go @@ -2,6 +2,7 @@ package controller import ( "go-gin/internal/gogin" + "go-gin/model" "go-gin/service/singleton" "net/http" @@ -29,7 +30,7 @@ func (up *userPage) serve() { func (sp *userPage) mine(c *gin.Context) { user, _ := gogin.GetCurrentUser(c) - posts, _ := postModel.List(singleton.DB, "created_user = ?", user.ID) + posts, _ := model.NewPost().List(singleton.DB, "created_user = ?", user.ID) c.HTML(http.StatusOK, "user/mine", gogin.CommonEnvironment(c, gin.H{ "Title": "Mine", "Posts": posts, diff --git a/config.example.yaml b/config.example.yaml index 310fac8..e1dc6e1 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -10,13 +10,21 @@ log: path: logs/go-gin.log # Log file path db_path: db/go-gin.sqlite # Database path rate_limit: - max: 100 # requests per minute + max: 100 # requests per minute, 0 means no limit enable_cors: false # Enable CORS enable_user_registration: true # Enable user registration upload: - virtual_path: /upload # Virtual path - dir: upload # Upload directory - max_size: 10485760 # 10MB + virtual_path: /upload # Virtual path, used for generating absolute URLs, must start with / + dir: upload # Upload directory, relative to the project root directory. Or you can use an absolute path, such as /var/www/upload + max_size: 10485760 # 10MB, unit: byte + keep_original_name: false # Keep original file name, if false, the server will generate a random file name + create_date_dir: true # Create date directory, such as /upload/2021/01/01 + allow_types: # Allowed file types, if empty, all types are allowed + - image/jpeg + - image/jpg + - image/png + - image/gif + - image/bmp jwt: # JWT settings access_secret: qhkxjrRmYcVYKSEobqsvhxhtPVeTWquu # Access token secret refresh_secret: qhkxjrRmYcVYKSEobqsvhxhtPV3TWquu # Refresh token secret diff --git a/internal/gogin/common.go b/internal/gogin/common.go index 8c72bd7..7ec742f 100644 --- a/internal/gogin/common.go +++ b/internal/gogin/common.go @@ -79,3 +79,13 @@ func UserLogout(c *gin.Context) { c.SetCookie(singleton.Conf.JWT.AccessTokenCookieName, "", -1, "/", "", false, true) c.SetCookie(singleton.Conf.JWT.RefreshTokenCookieName, "", -1, "/", "", false, true) } + +func AttachmentUpload(c *gin.Context) (*model.Attachment, error) { + result, err := singleton.AttachmentUpload.Upload(c) + if err != nil { + return nil, err + } + var attachmentModel = model.Attachment{} + attachmentModel.CreateByAttachment(singleton.DB, *result) + return &attachmentModel, nil +} diff --git a/model/attachment.go b/model/attachment.go new file mode 100644 index 0000000..382b153 --- /dev/null +++ b/model/attachment.go @@ -0,0 +1,58 @@ +package model + +import ( + "go-gin/pkg/mygin" + "go-gin/pkg/utils" + + "gorm.io/gorm" +) + +type Attachment struct { + Common + Num string `json:"num,omitempty" gorm:"column:num"` + Name string `json:"name,omitempty" gorm:"column:name"` + SavePath string `json:"-" gorm:"column:save_path"` + MD5 string `json:"md5,omitempty" gorm:"column:md5"` + Description string `json:"description,omitempty" gorm:"column:description"` + Url string `json:"url,omitempty" gorm:"-"` + Size int64 `json:"size,omitempty" gorm:"column:size"` + MiMe string `json:"mime,omitempty" gorm:"column:mime"` + CreatedUser uint64 `json:"created_user,omitempty" gorm:"column:created_user"` + CoverUrl string `json:"cover_url,omitempty" gorm:"column:cover_url"` + Width int `json:"width,omitempty" gorm:"column:width"` + Height int `json:"height,omitempty" gorm:"column:height"` + ExtConfig string `json:"ext_config,omitempty" gorm:"column:ext_config"` +} + +func NewAttachment() *Attachment { + return &Attachment{} +} + +func (a *Attachment) Create(db *gorm.DB) (err error) { + err = db.Model(&Attachment{}).Create(&a).Error + return err +} + +func (a *Attachment) CreateByAttachment(db *gorm.DB, uploadedFile mygin.AttachmentUploadedFile) (err error) { + a.Num = utils.GenHexStr(32) + a.Name = uploadedFile.OriginalName + a.SavePath = uploadedFile.SavePath + a.MD5 = uploadedFile.MD5 + a.Url = uploadedFile.Url + a.Size = uploadedFile.Size + a.MiMe = uploadedFile.MiMe + a.Width = uploadedFile.Width + a.Height = uploadedFile.Height + err = db.Model(&Attachment{}).Create(&a).Error + return err +} + +func (a *Attachment) GetByNum(db *gorm.DB, num string) (attachment Attachment, err error) { + err = db.Model(&Attachment{}).Where("num = ?", num).First(&attachment).Error + return attachment, err +} + +func (a *Attachment) GetByMD5(db *gorm.DB, md5 string) (attachment Attachment, err error) { + err = db.Model(&Attachment{}).Where("md5 = ?", md5).First(&attachment).Error + return attachment, err +} diff --git a/model/post.go b/model/post.go index 31b56f9..83552fa 100644 --- a/model/post.go +++ b/model/post.go @@ -13,6 +13,10 @@ type Post struct { CreatedUser uint64 `json:"created_user,omitempty" gorm:"column:created_user"` } +func NewPost() *Post { + return &Post{} +} + func (p *Post) Create(form mappers.PostForm, db *gorm.DB) (err error) { p.Title = form.Title p.Content = form.Content @@ -37,9 +41,9 @@ func (p *Post) Delete(id int, db *gorm.DB) (err error) { return err } -func (p *Post) Get(id int, db *gorm.DB) (post Post, err error) { - err = db.Model(&Post{}).Where("id = ?", id).First(&post).Error - return post, err +func (p *Post) Get(id int, db *gorm.DB) (err error) { + err = db.Model(&Post{}).Where("id = ?", id).First(&p).Error + return err } func (p *Post) List(db *gorm.DB, query interface{}, args ...interface{}) (posts []Post, err error) { diff --git a/model/user.go b/model/user.go index 0554086..6e99fb3 100644 --- a/model/user.go +++ b/model/user.go @@ -13,7 +13,7 @@ import ( type User struct { Common UserName string `json:"username,omitempty" gorm:"unique;column:username"` - Password string `json:"password,omitempty" gorm:"column:password"` + Password string `json:"-" gorm:"column:password"` ForgotPasswordCode string `json:"forgot_password_code,omitempty" gorm:"column:forgot_password_code"` VerificationCode string `json:"verification_code,omitempty" gorm:"column:verification_code"` @@ -75,11 +75,20 @@ func (u *User) Register(form mappers.RegisterForm, db *gorm.DB, conf *gconfig.Co return err } +func NewUser() *User { + return &User{} +} + func (u *User) GetByUsername(username string, db *gorm.DB) (err error) { err = db.Model(&User{}).Where("username = ?", username).First(&u).Error return err } +func (u *User) GetByID(id uint64, db *gorm.DB) (err error) { + err = db.Model(&User{}).Where("id = ?", id).First(&u).Error + return err +} + func (u *User) GetByEmail(email string, db *gorm.DB) (err error) { err = db.Model(&User{}).Where("email = ?", email).First(&u).Error return err diff --git a/pkg/mygin/attchment_upload.go b/pkg/mygin/attachment_upload.go similarity index 79% rename from pkg/mygin/attchment_upload.go rename to pkg/mygin/attachment_upload.go index ff86561..ac60ca1 100644 --- a/pkg/mygin/attchment_upload.go +++ b/pkg/mygin/attachment_upload.go @@ -14,7 +14,7 @@ import ( "github.com/gin-gonic/gin" ) -type AttchmentUpload struct { +type AttachmentUpload struct { BaseURL string // BaseURL is the base url for the uploaded file MaxSize int64 // MaxSize is the max file size, default is 2MB AllowTypes []string // AllowTypes is the allowed file types @@ -24,21 +24,21 @@ type AttchmentUpload struct { KeepOriginalName bool // KeepOriginalName is the flag to keep the original file name } -type AttchmentUploadResult struct { +type AttachmentUploadedFile struct { Url string `json:"url"` Name string `json:"name"` OriginalName string `json:"original_name"` Size int64 `json:"size"` MiMe string `json:"mime"` - With int `json:"width"` - Hei int `json:"height"` - Ext string `json:"ext"` - MD5 string `json:"md5"` - SavePath string `json:"save_path"` + Width int `json:"width"` // Width is the width of the image + Height int `json:"height"` // Height is the height of the image + Ext string `json:"ext"` // Ext is the file extension, eg: .jpg + MD5 string `json:"md5"` // MD5 is the md5 of the file + SavePath string `json:"save_path"` // SavePath is the path to save the file } -func (a *AttchmentUpload) Upload(c *gin.Context) (*AttchmentUploadResult, error) { - result := &AttchmentUploadResult{} +func (a *AttachmentUpload) Upload(c *gin.Context) (*AttachmentUploadedFile, error) { + result := &AttachmentUploadedFile{} form_file, err := c.FormFile(a.FormName) if err != nil { return result, err @@ -89,14 +89,14 @@ func (a *AttchmentUpload) Upload(c *gin.Context) (*AttchmentUploadResult, error) result.MiMe = form_file_mime result.Ext = form_file_ext result.MD5 = md5 - result.With = w - result.Hei = h + result.Width = w + result.Height = h result.SavePath = savePath return result, nil } -func NewAttchmentUpload() *AttchmentUpload { - return &AttchmentUpload{ +func NewAttachmentUpload() *AttachmentUpload { + return &AttachmentUpload{ BaseURL: "/upload", MaxSize: 1024 * 1024 * 2, AllowTypes: []string{"image/jpeg", "image/png", "image/gif", "image/jpg"}, diff --git a/pkg/mygin/response.go b/pkg/mygin/response.go index 68d81a7..8cf43dc 100644 --- a/pkg/mygin/response.go +++ b/pkg/mygin/response.go @@ -4,8 +4,8 @@ import "github.com/gin-gonic/gin" type Response struct { Code int `json:"code,omitempty"` - Message string `json:"message,omitempty"` Data interface{} `json:"data,omitempty"` + Message string `json:"message,omitempty"` } type ErrInfo struct { @@ -18,14 +18,14 @@ type ErrInfo struct { func ResponseJSON(c *gin.Context, code int, data interface{}, messages ...string) { rlt := &Response{} - if len(messages) > 0 { - rlt.Message = messages[0] - } if code > 0 { rlt.Code = code } if data != nil { rlt.Data = data } + if len(messages) > 0 { + rlt.Message = messages[0] + } c.AbortWithStatusJSON(code, rlt) } diff --git a/resource/template/login.html b/resource/template/login.html index b347dbf..66cc430 100644 --- a/resource/template/login.html +++ b/resource/template/login.html @@ -1,6 +1,6 @@ {{ define "login" }} {{template "base/header" .}} -