From 2f5681387407999d7821d5e1ba75adda09cf7fae Mon Sep 17 00:00:00 2001 From: cialloo Date: Fri, 24 Oct 2025 21:06:39 +0800 Subject: [PATCH] update --- api/Blog.api | 235 +++++------------------------------------- ddl/files.sql | 27 ++--- ddl/hashtags.sql | 26 ++--- ddl/post_hashtags.sql | 25 ++--- ddl/posts.sql | 35 +++---- ddl/tmpfiles.sql | 21 ++++ 6 files changed, 84 insertions(+), 285 deletions(-) create mode 100644 ddl/tmpfiles.sql diff --git a/api/Blog.api b/api/Blog.api index d3abe7d..0d1fd6e 100644 --- a/api/Blog.api +++ b/api/Blog.api @@ -1,228 +1,39 @@ syntax = "v1" info ( - title: "Blog API" - desc: "Blog service API with file upload/download and CRUD operations" - author: "cialloo" - date: "2025-10-22" - version: "v1" + title: "Blog API" + desc: "API for blog application with file operations" + author: "user" + date: "2025-10-24" + version: "v1" ) -// ============= File Upload/Download Types ============= -type ( - // Request for generating presigned upload URL - GenerateUploadUrlReq { - FileName string `json:"file_name"` // Original file name - FileSize int64 `json:"file_size"` // File size in bytes - } +type UploadReq { + FileName string `json:"fileName"` +} - GenerateUploadUrlResp { - FileId int64 `json:"file_id"` // Generated file ID - PresignedUrl string `json:"presigned_url"` // S3 presigned upload URL - FileKey string `json:"file_key"` // S3 file key - ExpiresIn int `json:"expires_in"` // URL expiration time in seconds - } +type UploadResp { + PresignedUrl string `json:"presignedUrl"` +} - // Request for generating presigned download URL - GenerateDownloadUrlReq { - FileId int64 `path:"file_id"` // File ID to download - } +type DownloadReq { + FileKey string `json:"fileKey"` +} - GenerateDownloadUrlResp { - FileName string `json:"file_name"` // Original file name - PresignedUrl string `json:"presigned_url"` // S3 presigned download URL - FileSize int64 `json:"file_size"` // File size in bytes - ExpiresIn int `json:"expires_in"` // URL expiration time in seconds - } - - // Confirm upload completion - ConfirmUploadReq { - FileId int64 `json:"file_id"` // File ID to confirm - } - - ConfirmUploadResp { - Success bool `json:"success"` // Upload confirmation status - Message string `json:"message"` // Status message - } -) - -// ============= Post Types ============= -type ( - // Create post request - CreatePostReq { - Title string `json:"title"` // Post title - Content string `json:"content"` // Post content - Hashtags []string `json:"hashtags,optional"` // Associated hashtags - } - - CreatePostResp { - PostId int64 `json:"post_id"` // Created post ID - } - - // Update post request - UpdatePostReq { - PostId int64 `path:"post_id"` // Post ID to update - Title string `json:"title,optional"` // New title - Content string `json:"content,optional"` // New content - Hashtags []string `json:"hashtags,optional"` // New hashtags (replaces all) - } - - UpdatePostResp { - Success bool `json:"success"` // Update status - } - - // Delete post request - DeletePostReq { - PostId int64 `path:"post_id"` // Post ID to delete - } - - DeletePostResp { - Success bool `json:"success"` // Delete status - } - - // Get post detail request - GetPostReq { - PostId int64 `path:"post_id"` // Post ID to retrieve - } - - PostDetail { - PostId int64 `json:"post_id"` // Post ID - Title string `json:"title"` // Post title - Content string `json:"content"` // Post content - ViewCount int64 `json:"view_count"` // View count - Hashtags []Hashtag `json:"hashtags"` // Associated hashtags - CreatedAt string `json:"created_at"` // Creation timestamp - UpdatedAt string `json:"updated_at"` // Last update timestamp - } - - GetPostResp { - Post PostDetail `json:"post"` // Post detail - } - - // List posts request - ListPostsReq { - Page int `form:"page,default=1"` // Page number - PageSize int `form:"page_size,default=10"` // Items per page - Hashtag string `form:"hashtag,optional"` // Filter by hashtag name - SortBy string `form:"sort_by,default=created_at,options=created_at|view_count|updated_at"` // Sort field - } - - PostItem { - PostId int64 `json:"post_id"` // Post ID - Title string `json:"title"` // Post title - ViewCount int64 `json:"view_count"` // View count - Hashtags []Hashtag `json:"hashtags"` // Associated hashtags - CreatedAt string `json:"created_at"` // Creation timestamp - UpdatedAt string `json:"updated_at"` // Last update timestamp - } - - ListPostsResp { - Posts []PostItem `json:"posts"` // List of posts - Total int64 `json:"total"` // Total count - Page int `json:"page"` // Current page - PageSize int `json:"page_size"` // Items per page - TotalPages int `json:"total_pages"` // Total pages - } -) - -// ============= Hashtag Types ============= -type ( - Hashtag { - HashtagId int64 `json:"hashtag_id"` // Hashtag ID - Name string `json:"name"` // Hashtag name - UsageCount int64 `json:"usage_count"` // Usage count - } - - // List hashtags request - ListHashtagsReq { - Page int `form:"page,default=1"` // Page number - PageSize int `form:"page_size,default=20"` // Items per page - SortBy string `form:"sort_by,default=usage_count,options=usage_count|created_at|name"` // Sort field - } - - ListHashtagsResp { - Hashtags []Hashtag `json:"hashtags"` // List of hashtags - Total int64 `json:"total"` // Total count - Page int `json:"page"` // Current page - PageSize int `json:"page_size"` // Items per page - TotalPages int `json:"total_pages"` // Total pages - } - - // Get popular hashtags - GetPopularHashtagsReq { - Limit int `form:"limit,default=10"` // Number of hashtags to return - } - - GetPopularHashtagsResp { - Hashtags []Hashtag `json:"hashtags"` // Popular hashtags - } -) - -// ============= Service Definition ============= -@server ( - prefix: /api/v1/blog - group: file -) -service Blog { - @doc "Generate presigned URL for file upload" - @handler generateUploadUrl - post /files/upload/url (GenerateUploadUrlReq) returns (GenerateUploadUrlResp) - - @doc "Generate presigned URL for file download" - @handler generateDownloadUrl - get /files/download/:file_id (GenerateDownloadUrlReq) returns (GenerateDownloadUrlResp) - - @doc "Confirm file upload completion" - @handler confirmUpload - post /files/upload/confirm (ConfirmUploadReq) returns (ConfirmUploadResp) +type DownloadResp { + PresignedUrl string `json:"presignedUrl"` } @server ( - prefix: /api/v1/blog - group: post + prefix: /api/v1 ) service Blog { - @doc "Create a new blog post" - @handler createPost - post /posts (CreatePostReq) returns (CreatePostResp) + @handler uploadFile + post /files/upload (UploadReq) returns (UploadResp) - @doc "Update an existing blog post" - @handler updatePost - put /posts/:post_id (UpdatePostReq) returns (UpdatePostResp) + @handler downloadFile + get /files/download (DownloadReq) returns (DownloadResp) - @doc "Delete a blog post" - @handler deletePost - delete /posts/:post_id (DeletePostReq) returns (DeletePostResp) - - @doc "Get blog post detail" - @handler getPost - get /posts/:post_id (GetPostReq) returns (GetPostResp) - - @doc "List blog posts with pagination and filtering" - @handler listPosts - get /posts (ListPostsReq) returns (ListPostsResp) + @handler previewFile + get /files/preview (DownloadReq) returns (DownloadResp) } - -@server ( - prefix: /api/v1/blog - group: hashtag -) -service Blog { - @doc "List all hashtags with pagination" - @handler listHashtags - get /hashtags (ListHashtagsReq) returns (ListHashtagsResp) - - @doc "Get popular hashtags" - @handler getPopularHashtags - get /hashtags/popular (GetPopularHashtagsReq) returns (GetPopularHashtagsResp) -} - -@server ( - prefix: /api/v1/blog - group: common -) -service Blog { - @doc "Health check endpoint" - @handler ping - get /ping -} \ No newline at end of file diff --git a/ddl/files.sql b/ddl/files.sql index fc482fc..d97e770 100644 --- a/ddl/files.sql +++ b/ddl/files.sql @@ -1,25 +1,16 @@ --- Files table: stores uploaded file metadata CREATE TABLE IF NOT EXISTS files ( - id BIGSERIAL PRIMARY KEY, + id BIGSERIAL, file_name VARCHAR(255) NOT NULL, file_key VARCHAR(500) NOT NULL UNIQUE, - byte_size BIGINT NOT NULL, - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id) ); --- Index for looking up files by key (most common query) -CREATE INDEX IF NOT EXISTS idx_files_file_key ON files(file_key); +COMMENT ON TABLE files IS 'Files table for storing uploaded files'; --- Index for sorting files by creation date -CREATE INDEX IF NOT EXISTS idx_files_created_at ON files(created_at DESC); +COMMENT ON COLUMN files.id IS 'Unique identifier for the file'; +COMMENT ON COLUMN files.file_name IS 'Name of the uploaded file'; +COMMENT ON COLUMN files.file_key IS 'Unique key for the file storage'; +COMMENT ON COLUMN files.created_at IS 'Timestamp when the file was uploaded'; --- Index for file size queries -CREATE INDEX IF NOT EXISTS idx_files_byte_size ON files(byte_size); - --- Comments on columns -COMMENT ON TABLE files IS 'File metadata table for uploaded files'; -COMMENT ON COLUMN files.id IS 'Primary key, auto-incrementing file ID'; -COMMENT ON COLUMN files.file_name IS 'Original name of the uploaded file'; -COMMENT ON COLUMN files.file_key IS 'Unique storage key/path for the file'; -COMMENT ON COLUMN files.byte_size IS 'File size in bytes'; -COMMENT ON COLUMN files.created_at IS 'Timestamp when the file was uploaded'; \ No newline at end of file +CREATE INDEX idx_files_created_at ON files(created_at); \ No newline at end of file diff --git a/ddl/hashtags.sql b/ddl/hashtags.sql index 2a9ce98..8829c01 100644 --- a/ddl/hashtags.sql +++ b/ddl/hashtags.sql @@ -1,23 +1,15 @@ --- Hashtags table: stores unique hashtag information CREATE TABLE IF NOT EXISTS hashtags ( - id BIGSERIAL PRIMARY KEY, + id BIGSERIAL, name VARCHAR(50) NOT NULL UNIQUE, - usage_count BIGINT DEFAULT 0, - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id) ); --- Index for looking up hashtags by name (most common query) -CREATE INDEX IF NOT EXISTS idx_hashtags_name ON hashtags(name); +COMMENT ON TABLE hashtags IS 'Hashtags table for storing unique hashtag names'; --- Index for sorting hashtags by popularity -CREATE INDEX IF NOT EXISTS idx_hashtags_usage_count ON hashtags(usage_count DESC); +COMMENT ON COLUMN hashtags.id IS 'Unique identifier for the hashtag'; +COMMENT ON COLUMN hashtags.name IS 'Name of the hashtag'; +COMMENT ON COLUMN hashtags.created_at IS 'Timestamp when the hashtag was created'; --- Index for trending hashtags (recent + popular) -CREATE INDEX IF NOT EXISTS idx_hashtags_created_usage ON hashtags(created_at DESC, usage_count DESC); - --- Comments on columns -COMMENT ON TABLE hashtags IS 'Hashtags table for categorizing posts'; -COMMENT ON COLUMN hashtags.id IS 'Primary key, auto-incrementing hashtag ID'; -COMMENT ON COLUMN hashtags.name IS 'Unique hashtag name'; -COMMENT ON COLUMN hashtags.usage_count IS 'Number of times the hashtag has been used'; -COMMENT ON COLUMN hashtags.created_at IS 'Timestamp when the hashtag was first created'; \ No newline at end of file +CREATE INDEX idx_hashtags_name ON hashtags(name); +CREATE INDEX idx_hashtags_created_at ON hashtags(created_at); \ No newline at end of file diff --git a/ddl/post_hashtags.sql b/ddl/post_hashtags.sql index cdad062..0120823 100644 --- a/ddl/post_hashtags.sql +++ b/ddl/post_hashtags.sql @@ -1,23 +1,16 @@ --- Post_hashtags junction table: many-to-many relationship between posts and hashtags CREATE TABLE IF NOT EXISTS post_hashtags ( - post_id BIGINT NOT NULL REFERENCES posts(id) ON DELETE CASCADE, - hashtag_id BIGINT NOT NULL REFERENCES hashtags(id) ON DELETE CASCADE, + post_id BIGINT NOT NULL, + hashtag_id BIGINT NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (post_id, hashtag_id) ); --- Index for finding all posts with a specific hashtag -CREATE INDEX IF NOT EXISTS idx_post_hashtags_hashtag_id ON post_hashtags(hashtag_id, post_id); +COMMENT ON TABLE post_hashtags IS 'Junction table linking posts and hashtags for many-to-many relationship'; --- Index for finding all hashtags for a specific post -CREATE INDEX IF NOT EXISTS idx_post_hashtags_post_id ON post_hashtags(post_id, hashtag_id); +COMMENT ON COLUMN post_hashtags.post_id IS 'Reference to the post id'; +COMMENT ON COLUMN post_hashtags.hashtag_id IS 'Reference to the hashtag id'; +COMMENT ON COLUMN post_hashtags.created_at IS 'Timestamp when the association was created'; --- Index for sorting by creation date -CREATE INDEX IF NOT EXISTS idx_post_hashtags_created_at ON post_hashtags(created_at DESC); - --- Comments on columns -COMMENT ON TABLE post_hashtags IS 'Junction table linking posts and hashtags'; -COMMENT ON COLUMN post_hashtags.post_id IS 'Foreign key referencing posts table'; -COMMENT ON COLUMN post_hashtags.hashtag_id IS 'Foreign key referencing hashtags table'; -COMMENT ON COLUMN post_hashtags.created_at IS 'Timestamp when the hashtag was associated with the post'; \ No newline at end of file +CREATE INDEX idx_post_hashtags_post_id ON post_hashtags(post_id); +CREATE INDEX idx_post_hashtags_hashtag_id ON post_hashtags(hashtag_id); +CREATE INDEX idx_post_hashtags_created_at ON post_hashtags(created_at); \ No newline at end of file diff --git a/ddl/posts.sql b/ddl/posts.sql index c7f7f26..ab37897 100644 --- a/ddl/posts.sql +++ b/ddl/posts.sql @@ -1,30 +1,21 @@ --- Posts table: stores blog post information CREATE TABLE IF NOT EXISTS posts ( - id BIGSERIAL PRIMARY KEY, + id BIGSERIAL, title VARCHAR(255) NOT NULL, content TEXT NOT NULL, - view_count BIGINT DEFAULT 0, + cover_id BIGINT, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id) ); --- Index for sorting posts by creation date -CREATE INDEX IF NOT EXISTS idx_posts_created_at ON posts(created_at DESC); +COMMENT ON TABLE posts IS 'Blog posts table'; --- Index for sorting posts by view count (popular posts) -CREATE INDEX IF NOT EXISTS idx_posts_view_count ON posts(view_count DESC); - --- Index for sorting posts by update date -CREATE INDEX IF NOT EXISTS idx_posts_updated_at ON posts(updated_at DESC); - --- Combined index for pagination queries -CREATE INDEX IF NOT EXISTS idx_posts_created_view ON posts(created_at DESC, view_count DESC); - --- Comments on columns -COMMENT ON TABLE posts IS 'Blog posts table containing article information'; -COMMENT ON COLUMN posts.id IS 'Primary key, auto-incrementing post ID'; -COMMENT ON COLUMN posts.title IS 'Post title'; -COMMENT ON COLUMN posts.content IS 'Post content in text format'; -COMMENT ON COLUMN posts.view_count IS 'Number of times the post has been viewed'; +COMMENT ON COLUMN posts.id IS 'Unique identifier for the post'; +COMMENT ON COLUMN posts.title IS 'Title of the blog post'; +COMMENT ON COLUMN posts.content IS 'Content of the blog post'; +COMMENT ON COLUMN posts.cover_id IS 'Reference to the cover image file id'; COMMENT ON COLUMN posts.created_at IS 'Timestamp when the post was created'; -COMMENT ON COLUMN posts.updated_at IS 'Timestamp when the post was last updated'; \ No newline at end of file +COMMENT ON COLUMN posts.updated_at IS 'Timestamp when the post was last updated'; + +CREATE INDEX idx_posts_created_at ON posts(created_at); +CREATE INDEX idx_posts_cover_id ON posts(cover_id); \ No newline at end of file diff --git a/ddl/tmpfiles.sql b/ddl/tmpfiles.sql new file mode 100644 index 0000000..767f03b --- /dev/null +++ b/ddl/tmpfiles.sql @@ -0,0 +1,21 @@ +CREATE TABLE IF NOT EXISTS tmpfiles ( + id BIGSERIAL, + file_name VARCHAR(255) NOT NULL, + key VARCHAR(500) NOT NULL UNIQUE, + presigned_url TEXT NOT NULL, + expiration TIMESTAMP WITH TIME ZONE NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id) +); + +COMMENT ON TABLE tmpfiles IS 'Temporary files table for storing files with presigned URLs and expiration times'; + +COMMENT ON COLUMN tmpfiles.id IS 'Unique identifier for the temporary file'; +COMMENT ON COLUMN tmpfiles.file_name IS 'Name of the temporary file'; +COMMENT ON COLUMN tmpfiles.key IS 'Unique key for the temporary file'; +COMMENT ON COLUMN tmpfiles.presigned_url IS 'Presigned URL for accessing the temporary file'; +COMMENT ON COLUMN tmpfiles.expiration IS 'Expiration timestamp for the temporary file'; +COMMENT ON COLUMN tmpfiles.created_at IS 'Timestamp when the temporary file was created'; + +CREATE INDEX idx_tmpfiles_expiration ON tmpfiles(expiration); +CREATE INDEX idx_tmpfiles_created_at ON tmpfiles(created_at); \ No newline at end of file