diff --git a/api/Blog.api b/api/Blog.api index b49b9c0..89dba1d 100644 --- a/api/Blog.api +++ b/api/Blog.api @@ -36,6 +36,34 @@ type ( } ) +type ( + CreatePostReq { + title string `json:"title"` + content string `json:"content"` + cover_image_key string `json:"cover_image_key"` + } + CreatePostResp { + post_id string `json:"post_id"` + } +) + +type ( + EditPostReq { + post_id string `json:"post_id"` + title string `json:"title"` + content string `json:"content"` + cover_image_key string `json:"cover_image_key"` + } + EditPostResp {} +) + +type ( + DeletePostReq { + post_id string `json:"post_id"` + } + DeletePostResp {} +) + @server ( prefix: /api/blog ) @@ -48,6 +76,33 @@ service Blog { get /ping (PingReq) returns (PingResp) } +@server ( + middleware: SuperAdminAuthMiddleware + prefix: /api/blog/post +) +service Blog { + @doc ( + summary: "Create a new blog post" + description: "Create a new blog post" + ) + @handler CreatePostHandler + post /create (CreatePostReq) returns (CreatePostResp) + + @doc ( + summary: "Edit an existing blog post" + description: "Edit an existing blog post" + ) + @handler EditPostHandler + post /edit (EditPostReq) returns (EditPostResp) + + @doc ( + summary: "Delete a blog post" + description: "Delete a blog post" + ) + @handler DeletePostHandler + post /delete (DeletePostReq) returns (DeletePostResp) +} + @server ( middleware: SuperAdminAuthMiddleware prefix: /api/blog diff --git a/app/internal/handler/createposthandler.go b/app/internal/handler/createposthandler.go new file mode 100644 index 0000000..4f8a877 --- /dev/null +++ b/app/internal/handler/createposthandler.go @@ -0,0 +1,29 @@ +package handler + +import ( + "net/http" + + "git.cialloo.com/CiallooWeb/Blog/app/internal/logic" + "git.cialloo.com/CiallooWeb/Blog/app/internal/svc" + "git.cialloo.com/CiallooWeb/Blog/app/internal/types" + "github.com/zeromicro/go-zero/rest/httpx" +) + +// Create a new blog post +func CreatePostHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.CreatePostReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := logic.NewCreatePostLogic(r.Context(), svcCtx) + resp, err := l.CreatePost(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/app/internal/handler/deleteposthandler.go b/app/internal/handler/deleteposthandler.go new file mode 100644 index 0000000..fc5f82a --- /dev/null +++ b/app/internal/handler/deleteposthandler.go @@ -0,0 +1,29 @@ +package handler + +import ( + "net/http" + + "git.cialloo.com/CiallooWeb/Blog/app/internal/logic" + "git.cialloo.com/CiallooWeb/Blog/app/internal/svc" + "git.cialloo.com/CiallooWeb/Blog/app/internal/types" + "github.com/zeromicro/go-zero/rest/httpx" +) + +// Delete a blog post +func DeletePostHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.DeletePostReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := logic.NewDeletePostLogic(r.Context(), svcCtx) + resp, err := l.DeletePost(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/app/internal/handler/editposthandler.go b/app/internal/handler/editposthandler.go new file mode 100644 index 0000000..e212e27 --- /dev/null +++ b/app/internal/handler/editposthandler.go @@ -0,0 +1,29 @@ +package handler + +import ( + "net/http" + + "git.cialloo.com/CiallooWeb/Blog/app/internal/logic" + "git.cialloo.com/CiallooWeb/Blog/app/internal/svc" + "git.cialloo.com/CiallooWeb/Blog/app/internal/types" + "github.com/zeromicro/go-zero/rest/httpx" +) + +// Edit an existing blog post +func EditPostHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.EditPostReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := logic.NewEditPostLogic(r.Context(), svcCtx) + resp, err := l.EditPost(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/app/internal/handler/routes.go b/app/internal/handler/routes.go index 3c5c531..8fa1bf7 100644 --- a/app/internal/handler/routes.go +++ b/app/internal/handler/routes.go @@ -24,6 +24,33 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { rest.WithPrefix("/api/blog"), ) + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.SuperAdminAuthMiddleware}, + []rest.Route{ + { + // Create a new blog post + Method: http.MethodPost, + Path: "/create", + Handler: CreatePostHandler(serverCtx), + }, + { + // Delete a blog post + Method: http.MethodPost, + Path: "/delete", + Handler: DeletePostHandler(serverCtx), + }, + { + // Edit an existing blog post + Method: http.MethodPost, + Path: "/edit", + Handler: EditPostHandler(serverCtx), + }, + }..., + ), + rest.WithPrefix("/api/blog/post"), + ) + server.AddRoutes( rest.WithMiddlewares( []rest.Middleware{serverCtx.SuperAdminAuthMiddleware}, diff --git a/app/internal/logic/createpostlogic.go b/app/internal/logic/createpostlogic.go new file mode 100644 index 0000000..9befe4d --- /dev/null +++ b/app/internal/logic/createpostlogic.go @@ -0,0 +1,59 @@ +package logic + +import ( + "context" + "database/sql" + "fmt" + + "git.cialloo.com/CiallooWeb/Blog/app/internal/svc" + "git.cialloo.com/CiallooWeb/Blog/app/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type CreatePostLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// Create a new blog post +func NewCreatePostLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreatePostLogic { + return &CreatePostLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *CreatePostLogic) CreatePost(req *types.CreatePostReq) (resp *types.CreatePostResp, err error) { + var coverID sql.NullInt64 + + // If cover image key is provided, get the file ID from files table + if req.Cover_image_key != "" { + query := `SELECT id FROM files WHERE file_key = $1` + err := l.svcCtx.DB.QueryRowContext(l.ctx, query, req.Cover_image_key).Scan(&coverID.Int64) + if err != nil { + if err == sql.ErrNoRows { + l.Errorf("Cover image file not found with key: %s", req.Cover_image_key) + return nil, fmt.Errorf("cover image not found") + } + l.Errorf("Failed to get cover image file: %v", err) + return nil, err + } + coverID.Valid = true + } + + // Insert new post + var postID int64 + query := `INSERT INTO posts (title, content, cover_id) VALUES ($1, $2, $3) RETURNING id` + err = l.svcCtx.DB.QueryRowContext(l.ctx, query, req.Title, req.Content, coverID).Scan(&postID) + if err != nil { + l.Errorf("Failed to create post: %v", err) + return nil, err + } + + return &types.CreatePostResp{ + Post_id: fmt.Sprintf("%d", postID), + }, nil +} diff --git a/app/internal/logic/deletepostlogic.go b/app/internal/logic/deletepostlogic.go new file mode 100644 index 0000000..949aee7 --- /dev/null +++ b/app/internal/logic/deletepostlogic.go @@ -0,0 +1,49 @@ +package logic + +import ( + "context" + "fmt" + + "git.cialloo.com/CiallooWeb/Blog/app/internal/svc" + "git.cialloo.com/CiallooWeb/Blog/app/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type DeletePostLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// Delete a blog post +func NewDeletePostLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeletePostLogic { + return &DeletePostLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *DeletePostLogic) DeletePost(req *types.DeletePostReq) (resp *types.DeletePostResp, err error) { + // Delete post from database + query := `DELETE FROM posts WHERE id = $1` + result, err := l.svcCtx.DB.ExecContext(l.ctx, query, req.Post_id) + if err != nil { + l.Errorf("Failed to delete post: %v", err) + return nil, err + } + + // Check if post exists + rowsAffected, err := result.RowsAffected() + if err != nil { + l.Errorf("Failed to get rows affected: %v", err) + return nil, err + } + if rowsAffected == 0 { + l.Errorf("Post not found with id: %s", req.Post_id) + return nil, fmt.Errorf("post not found") + } + + return &types.DeletePostResp{}, nil +} diff --git a/app/internal/logic/editpostlogic.go b/app/internal/logic/editpostlogic.go new file mode 100644 index 0000000..70b11b2 --- /dev/null +++ b/app/internal/logic/editpostlogic.go @@ -0,0 +1,67 @@ +package logic + +import ( + "context" + "database/sql" + "fmt" + + "git.cialloo.com/CiallooWeb/Blog/app/internal/svc" + "git.cialloo.com/CiallooWeb/Blog/app/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type EditPostLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// Edit an existing blog post +func NewEditPostLogic(ctx context.Context, svcCtx *svc.ServiceContext) *EditPostLogic { + return &EditPostLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *EditPostLogic) EditPost(req *types.EditPostReq) (resp *types.EditPostResp, err error) { + var coverID sql.NullInt64 + + // If cover image key is provided, get the file ID from files table + if req.Cover_image_key != "" { + query := `SELECT id FROM files WHERE file_key = $1` + err := l.svcCtx.DB.QueryRowContext(l.ctx, query, req.Cover_image_key).Scan(&coverID.Int64) + if err != nil { + if err == sql.ErrNoRows { + l.Errorf("Cover image file not found with key: %s", req.Cover_image_key) + return nil, fmt.Errorf("cover image not found") + } + l.Errorf("Failed to get cover image file: %v", err) + return nil, err + } + coverID.Valid = true + } + + // Update post with updated_at timestamp + query := `UPDATE posts SET title = $1, content = $2, cover_id = $3, updated_at = CURRENT_TIMESTAMP WHERE id = $4` + result, err := l.svcCtx.DB.ExecContext(l.ctx, query, req.Title, req.Content, coverID, req.Post_id) + if err != nil { + l.Errorf("Failed to update post: %v", err) + return nil, err + } + + // Check if post exists + rowsAffected, err := result.RowsAffected() + if err != nil { + l.Errorf("Failed to get rows affected: %v", err) + return nil, err + } + if rowsAffected == 0 { + l.Errorf("Post not found with id: %s", req.Post_id) + return nil, fmt.Errorf("post not found") + } + + return &types.EditPostResp{}, nil +} diff --git a/app/internal/types/types.go b/app/internal/types/types.go index 4fde4a5..470963c 100644 --- a/app/internal/types/types.go +++ b/app/internal/types/types.go @@ -3,6 +3,23 @@ package types +type CreatePostReq struct { + Title string `json:"title"` + Content string `json:"content"` + Cover_image_key string `json:"cover_image_key"` +} + +type CreatePostResp struct { + Post_id string `json:"post_id"` +} + +type DeletePostReq struct { + Post_id string `json:"post_id"` +} + +type DeletePostResp struct { +} + type DownloadPresignedURLReq struct { File_key string `json:"file_key"` // Key to identify the file to download } @@ -12,6 +29,16 @@ type DownloadPresignedURLResp struct { Expire_at int64 `json:"expire_at"` // Expiration timestamp } +type EditPostReq struct { + Post_id string `json:"post_id"` + Title string `json:"title"` + Content string `json:"content"` + Cover_image_key string `json:"cover_image_key"` +} + +type EditPostResp struct { +} + type PingReq struct { }