feat: add book admin display fields
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
| 入参包含分页、筛选、排序、动作语义、组合参数 | 必须使用 `request` |
|
||||
| 返回实体原样或实体列表 | 可直接返回实体 |
|
||||
| 返回包含展示、聚合、联表、树、嵌套结构 | 必须使用 `response` |
|
||||
| 关联表列表/详情需要给前端展示关联对象名称、标题、label | 必须使用 `response` 联表展示字段,实体只保留外键 ID |
|
||||
| 多个接口 contract 完全一致 | 复用同一个 `request/response` |
|
||||
| `admin/app` 字段、校验、分页、展示口径不同 | 按端拆分 `request/response` |
|
||||
|
||||
@@ -38,6 +39,12 @@
|
||||
- 同一业务模块存在 `admin/app` 两套接口时,实体只保留一份;仅 `request/response` 按端拆分。
|
||||
- 改实体字段时,必须同步检查 `doc-sql`、`Service` 查询/写入、`API` 绑定/返回。
|
||||
- 改 `request/response` 或接口 contract 时,必须同步检查并更新 `doc-api`、`API`、`Service`。
|
||||
- 关联表实体只能承载自身字段和外键 ID,禁止把关联对象的 `name/title/label` 等展示字段写入实体或 `doc-sql`。
|
||||
- 关联表的 admin 列表响应默认必须补充主要关联对象展示字段,字段名使用 `<关联对象语义><Name/Title/Label>`,例如 `authorName`、`bookTitle`、`typeLabel`。
|
||||
- 关联表详情响应如果用于前端详情页、编辑弹窗或审阅页面,也必须返回与列表一致的关联展示字段;纯内部读取接口可只返回实体。
|
||||
- 展示字段来源必须由 `Service` 通过 `JOIN`、预加载或批量查询组装到 `response`,禁止要求前端拿到 ID 后再逐条反查名称。
|
||||
- 关联展示字段只读;`Create/Update` 请求仍只接收外键 ID 和可编辑业务字段,禁止接收或信任前端传入的展示名称。
|
||||
- 若列表支持按关联展示字段搜索或排序,`total` 统计查询必须同步使用等价关联条件,避免分页总数和列表数据不一致。
|
||||
|
||||
## 推荐做法
|
||||
|
||||
@@ -47,6 +54,7 @@
|
||||
| 动作入参 | `<Action><StructName>Req` | `ChangePasswordReq` |
|
||||
| 详情出参 | `<StructName>Response` | `BookResponse` |
|
||||
| 列表项出参 | `<StructName>ListItem` | `BookListItem` |
|
||||
| 关联展示字段 | `<Related> + Name/Title/Label` | `authorName`、`bookTitle` |
|
||||
| 选项出参 | `<StructName>Option` | `AuthorOption` |
|
||||
| 文件 | `model/<module>/<resource>.go`、`model/<module>/request/<resource>.go`、`model/<module>/response/<resource>.go` | `model/book/book.go` |
|
||||
|
||||
|
||||
@@ -131,7 +131,9 @@
|
||||
| bookAuthorRelation | object | 关系实体 |
|
||||
| bookAuthorRelation.id | uint | 关系 ID |
|
||||
| bookAuthorRelation.bookId | uint | 书籍 ID |
|
||||
| bookAuthorRelation.bookTitle | string | 书籍标题展示字段,来源于 `book.title AS book_title` |
|
||||
| bookAuthorRelation.authorId | uint | 作者 ID |
|
||||
| bookAuthorRelation.authorName | string | 作者名称展示字段,来源于 `book_author.name AS author_name` |
|
||||
| bookAuthorRelation.authorSort | int | 作者展示顺序 |
|
||||
| bookAuthorRelation.createdAt | string | 创建时间 |
|
||||
| bookAuthorRelation.updatedAt | string | 更新时间 |
|
||||
@@ -160,9 +162,10 @@
|
||||
| list | array | `bookRes.BookAuthorRelationListItem` 列表 |
|
||||
| list[].id | uint | 关系 ID |
|
||||
| list[].bookId | uint | 书籍 ID |
|
||||
| list[].bookTitle | string | 书籍标题展示字段,来源于 `book.title AS book_title` |
|
||||
| list[].authorId | uint | 作者 ID |
|
||||
| list[].authorSort | int | 作者展示顺序 |
|
||||
| list[].authorName | string | 作者名称展示字段 |
|
||||
| list[].authorName | string | 作者名称展示字段,来源于 `book_author.name AS author_name` |
|
||||
| total | int64 | 总数 |
|
||||
| page | int | 当前页 |
|
||||
| pageSize | int | 每页数量 |
|
||||
@@ -170,6 +173,7 @@
|
||||
#### 规则
|
||||
|
||||
- 列表默认排序:`bookId asc, authorSort asc, id desc`。
|
||||
- 列表通过 `book_author_relation` 联表 `book`、`book_author` 补充 `bookTitle/authorName` 展示字段;写接口仍只接收 `bookId/authorId/authorSort`。
|
||||
|
||||
## 规则
|
||||
|
||||
@@ -177,4 +181,5 @@
|
||||
- `bookId + authorId` 唯一,对应数据库唯一索引 `uk_book_author_relation_book_id_author_id`。
|
||||
- `authorSort` 必须大于 `0`。
|
||||
- 创建时 `authorSort` 未传或为 `0` 时默认 `1`。
|
||||
- `bookTitle/authorName` 为只读展示字段,禁止写入实体表结构,禁止作为创建或更新入参。
|
||||
- 删除策略为硬删。
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
- 实体模型:`book.BookChapter`
|
||||
- 搜索入参:`bookReq.BookChapterSearch`
|
||||
- 详情响应:`bookRes.BookChapterResponse`
|
||||
- 列表项:`book.BookChapter`
|
||||
- 列表项:`bookRes.BookChapterListItem`
|
||||
- 返回:写操作 `response.Response{msg}`;详情 `response.Response{data}`;列表 `response.PageResult`
|
||||
- 删除策略:硬删
|
||||
|
||||
@@ -143,6 +143,7 @@
|
||||
| bookChapter.createdAt | string | 创建时间 |
|
||||
| bookChapter.updatedAt | string | 更新时间 |
|
||||
| bookChapter.bookId | uint | 所属书籍 ID |
|
||||
| bookChapter.bookTitle | string | 书籍标题展示字段,来源于 `book.title AS book_title` |
|
||||
| bookChapter.title | string | 章节标题 |
|
||||
| bookChapter.chapterNo | int | 同书内章节顺序编号 |
|
||||
| bookChapter.isReadable | bool | 是否对 app 端开放阅读 |
|
||||
@@ -173,11 +174,12 @@
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|:---|:---|:---|
|
||||
| list | array | `book.BookChapter` 列表 |
|
||||
| list | array | `bookRes.BookChapterListItem` 列表 |
|
||||
| list[].id | uint | 章节 ID |
|
||||
| list[].createdAt | string | 创建时间 |
|
||||
| list[].updatedAt | string | 更新时间 |
|
||||
| list[].bookId | uint | 所属书籍 ID |
|
||||
| list[].bookTitle | string | 书籍标题展示字段,来源于 `book.title AS book_title` |
|
||||
| list[].title | string | 章节标题 |
|
||||
| list[].chapterNo | int | 同书内章节顺序编号 |
|
||||
| list[].isReadable | bool | 是否对 app 端开放阅读 |
|
||||
@@ -191,6 +193,7 @@
|
||||
#### 规则
|
||||
|
||||
- 列表默认排序:`book_id asc, chapter_no asc`。
|
||||
- 列表通过 `book_chapter` 联表 `book` 补充 `bookTitle` 展示字段;写接口仍只接收 `bookId`。
|
||||
|
||||
## 规则
|
||||
|
||||
@@ -199,3 +202,4 @@
|
||||
- `chapterNo` 必须大于 `0`。
|
||||
- `totalLines` 不能小于 `0`;`isReadable=true` 时 `totalLines` 必须大于 `0`。
|
||||
- 创建、更新复用实体 `book.BookChapter`;列表查询复用 `bookReq.BookChapterSearch`;详情返回 `bookRes.BookChapterResponse`。
|
||||
- `bookTitle` 为只读展示字段,禁止写入实体表结构,禁止作为创建或更新入参。
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
- 实体模型:`book.BookComment`
|
||||
- 搜索入参:`bookReq.BookCommentSearch`
|
||||
- 详情响应:`bookRes.BookCommentResponse`
|
||||
- 列表项:`bookRes.BookCommentListItem`
|
||||
- 返回:写操作 `response.Response{msg}`;详情 `response.Response{data}`;列表 `response.PageResult`
|
||||
- 删除策略:硬删
|
||||
|
||||
@@ -143,7 +144,9 @@
|
||||
| bookComment.updatedAt | time | 更新时间 |
|
||||
| bookComment.memberUserId | uint | 评论用户的会员 ID |
|
||||
| bookComment.bookId | uint | 所属书籍 ID |
|
||||
| bookComment.bookTitle | string | 书籍标题展示字段,来源于 `book.title AS book_title` |
|
||||
| bookComment.chapterId | uint | 评论目标章节 ID,`0` 表示整本书 |
|
||||
| bookComment.chapterTitle | string | 章节标题展示字段,来源于 `book_chapter.title AS chapter_title`;整书评论可为空 |
|
||||
| bookComment.lineIndex | int | 评论目标文本行下标,`0` 表示整章或整本书 |
|
||||
| bookComment.content | string | 评论正文内容 |
|
||||
| bookComment.likeCount | int64 | 评论点赞聚合值 |
|
||||
@@ -172,13 +175,15 @@
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|:---|:---|:---|
|
||||
| list | array | `book.BookComment` 列表 |
|
||||
| list | array | `bookRes.BookCommentListItem` 列表 |
|
||||
| list[].id | uint | 评论 ID |
|
||||
| list[].createdAt | time | 创建时间 |
|
||||
| list[].updatedAt | time | 更新时间 |
|
||||
| list[].memberUserId | uint | 评论用户的会员 ID |
|
||||
| list[].bookId | uint | 所属书籍 ID |
|
||||
| list[].bookTitle | string | 书籍标题展示字段,来源于 `book.title AS book_title` |
|
||||
| list[].chapterId | uint | 评论目标章节 ID,`0` 表示整本书 |
|
||||
| list[].chapterTitle | string | 章节标题展示字段,来源于 `book_chapter.title AS chapter_title`;整书评论可为空 |
|
||||
| list[].lineIndex | int | 评论目标文本行下标,`0` 表示整章或整本书 |
|
||||
| list[].content | string | 评论正文内容 |
|
||||
| list[].likeCount | int64 | 评论点赞聚合值 |
|
||||
@@ -190,6 +195,7 @@
|
||||
#### 规则
|
||||
|
||||
- 列表默认排序:`id desc`。
|
||||
- 列表通过 `book_comment` 联表 `book`、`book_chapter` 补充 `bookTitle/chapterTitle` 展示字段;写接口仍只接收 `bookId/chapterId`。
|
||||
|
||||
## 规则
|
||||
|
||||
@@ -197,3 +203,4 @@
|
||||
- `lineIndex/likeCount` 不能小于 `0`。
|
||||
- 整书评论 `chapterId=0` 时 `lineIndex` 必须为 `0`。
|
||||
- 删除策略为硬删。
|
||||
- `bookTitle/chapterTitle` 为只读展示字段,禁止写入实体表结构,禁止作为创建或更新入参。
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
- 实体模型:`book.BookCommentLikeRecord`
|
||||
- 搜索入参:`bookReq.BookCommentLikeRecordSearch`
|
||||
- 详情响应:`bookRes.BookCommentLikeRecordResponse`
|
||||
- 列表项:`book.BookCommentLikeRecord`
|
||||
- 列表项:`bookRes.BookCommentLikeRecordListItem`
|
||||
- 返回:写操作 `response.Response{msg}`;详情 `response.Response{data}`;列表 `response.PageResult`
|
||||
- 删除策略:硬删
|
||||
|
||||
@@ -133,6 +133,8 @@
|
||||
| bookCommentLikeRecord.createdAt | string | 创建时间 |
|
||||
| bookCommentLikeRecord.updatedAt | string | 更新时间 |
|
||||
| bookCommentLikeRecord.commentId | uint | 被点赞评论 ID |
|
||||
| bookCommentLikeRecord.commentContent | string | 被点赞评论内容展示字段,来源于 `book_comment.content AS comment_content` |
|
||||
| bookCommentLikeRecord.bookTitle | string | 评论所属书籍标题展示字段,来源于 `book.title AS book_title` |
|
||||
| bookCommentLikeRecord.memberUserId | uint | 点赞用户的会员 ID |
|
||||
| bookCommentLikeRecord.likedAt | string | 点赞发生时间 |
|
||||
|
||||
@@ -157,11 +159,13 @@
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|:---|:---|:---|
|
||||
| list | array | `book.BookCommentLikeRecord` 列表 |
|
||||
| list | array | `bookRes.BookCommentLikeRecordListItem` 列表 |
|
||||
| list[].id | uint | 点赞记录 ID |
|
||||
| list[].createdAt | string | 创建时间 |
|
||||
| list[].updatedAt | string | 更新时间 |
|
||||
| list[].commentId | uint | 被点赞评论 ID |
|
||||
| list[].commentContent | string | 被点赞评论内容展示字段,来源于 `book_comment.content AS comment_content` |
|
||||
| list[].bookTitle | string | 评论所属书籍标题展示字段,来源于 `book.title AS book_title` |
|
||||
| list[].memberUserId | uint | 点赞用户的会员 ID |
|
||||
| list[].likedAt | string | 点赞发生时间 |
|
||||
| total | int64 | 总数 |
|
||||
@@ -171,6 +175,7 @@
|
||||
#### 规则
|
||||
|
||||
- 列表默认排序:`id desc`。
|
||||
- 列表通过 `book_comment_like_record` 联表 `book_comment`、`book` 补充 `commentContent/bookTitle` 展示字段;写接口仍只接收 `commentId/memberUserId`。
|
||||
|
||||
## 规则
|
||||
|
||||
@@ -178,3 +183,4 @@
|
||||
- `commentId + memberUserId` 是幂等判断边界,必须保持唯一。
|
||||
- 删除策略为硬删;删除后该点赞关系不再存在。
|
||||
- 时间字段以接口实际 JSON 序列化格式为准,业务语义为 `liked_at`。
|
||||
- `commentContent/bookTitle` 为只读展示字段,禁止写入实体表结构,禁止作为创建或更新入参。
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
- 实体模型:`book.BookFavoriteRecord`
|
||||
- 搜索入参:`bookReq.BookFavoriteRecordSearch`
|
||||
- 详情响应:`bookRes.BookFavoriteRecordResponse`
|
||||
- 列表项:`book.BookFavoriteRecord`
|
||||
- 列表项:`bookRes.BookFavoriteRecordListItem`
|
||||
- 返回:写操作 `response.Response{msg}`;详情 `response.Response{data}`;列表 `response.PageResult`
|
||||
- 删除策略:硬删
|
||||
|
||||
@@ -134,6 +134,7 @@
|
||||
| bookFavoriteRecord.updatedAt | string | 更新时间 |
|
||||
| bookFavoriteRecord.memberUserId | uint | 收藏用户的会员 ID |
|
||||
| bookFavoriteRecord.bookId | uint | 收藏书籍 ID |
|
||||
| bookFavoriteRecord.bookTitle | string | 书籍标题展示字段,来源于 `book.title AS book_title` |
|
||||
| bookFavoriteRecord.favoritedAt | string | 收藏发生时间 |
|
||||
|
||||
### 分页查询书籍收藏记录列表
|
||||
@@ -157,12 +158,13 @@
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|:---|:---|:---|
|
||||
| list | array | `book.BookFavoriteRecord` 列表 |
|
||||
| list | array | `bookRes.BookFavoriteRecordListItem` 列表 |
|
||||
| list[].id | uint | 收藏记录 ID |
|
||||
| list[].createdAt | string | 创建时间 |
|
||||
| list[].updatedAt | string | 更新时间 |
|
||||
| list[].memberUserId | uint | 收藏用户的会员 ID |
|
||||
| list[].bookId | uint | 收藏书籍 ID |
|
||||
| list[].bookTitle | string | 书籍标题展示字段,来源于 `book.title AS book_title` |
|
||||
| list[].favoritedAt | string | 收藏发生时间 |
|
||||
| total | int64 | 总数 |
|
||||
| page | int | 当前页 |
|
||||
@@ -171,6 +173,7 @@
|
||||
#### 规则
|
||||
|
||||
- 列表默认排序:`id desc`。
|
||||
- 列表通过 `book_favorite_record` 联表 `book` 补充 `bookTitle` 展示字段;写接口仍只接收 `memberUserId/bookId`。
|
||||
|
||||
## 规则
|
||||
|
||||
@@ -178,3 +181,4 @@
|
||||
- 写操作挂载 `middleware.OperationRecord()`;详情和分页列表不挂操作审计。
|
||||
- `memberUserId + bookId` 唯一约束由数据库索引 `uk_book_favorite_record_member_user_id_book_id` 保证。
|
||||
- 删除策略为硬删,实体使用 `HardDeleteModel`,不维护 `deletedAt`。
|
||||
- `bookTitle` 为只读展示字段,禁止写入实体表结构,禁止作为创建或更新入参。
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
- 实体模型:`book.BookReadRecord`
|
||||
- 搜索入参:`bookReq.BookReadRecordSearch`
|
||||
- 详情响应:`bookRes.BookReadRecordResponse`
|
||||
- 列表项:`book.BookReadRecord`
|
||||
- 列表项:`bookRes.BookReadRecordListItem`
|
||||
- 返回:写操作 `response.Response{msg}`;详情 `response.Response{data}`;列表 `response.PageResult`
|
||||
- 删除策略:硬删
|
||||
|
||||
@@ -147,6 +147,7 @@
|
||||
| bookReadRecord.bookTitleSnapshot | string | 阅读书籍标题快照 |
|
||||
| bookReadRecord.readProgress | float | 阅读进度百分比 |
|
||||
| bookReadRecord.chapterId | uint | 当前续读章节 ID |
|
||||
| bookReadRecord.chapterTitle | string | 章节标题展示字段,来源于 `book_chapter.title AS chapter_title` |
|
||||
| bookReadRecord.lineIndex | int | 当前续读文本行下标,正文首行为 `1` |
|
||||
| bookReadRecord.lastReadAt | string | 最近一次阅读时间 |
|
||||
|
||||
@@ -171,7 +172,7 @@
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|:---|:---|:---|
|
||||
| list | array | `book.BookReadRecord` 列表 |
|
||||
| list | array | `bookRes.BookReadRecordListItem` 列表 |
|
||||
| list[].id | uint | 阅读记录 ID |
|
||||
| list[].createdAt | string | 创建时间 |
|
||||
| list[].updatedAt | string | 更新时间 |
|
||||
@@ -180,6 +181,7 @@
|
||||
| list[].bookTitleSnapshot | string | 阅读书籍标题快照 |
|
||||
| list[].readProgress | float | 阅读进度百分比 |
|
||||
| list[].chapterId | uint | 当前续读章节 ID |
|
||||
| list[].chapterTitle | string | 章节标题展示字段,来源于 `book_chapter.title AS chapter_title` |
|
||||
| list[].lineIndex | int | 当前续读文本行下标,正文首行为 `1` |
|
||||
| list[].lastReadAt | string | 最近一次阅读时间 |
|
||||
| total | int64 | 总数 |
|
||||
@@ -189,6 +191,7 @@
|
||||
#### 规则
|
||||
|
||||
- 列表默认排序:`last_read_at desc, id desc`。
|
||||
- 列表通过 `book_read_record` 联表 `book_chapter` 补充 `chapterTitle` 展示字段;书籍标题使用记录内 `bookTitleSnapshot`。
|
||||
|
||||
## 规则
|
||||
|
||||
@@ -197,3 +200,4 @@
|
||||
- 删除策略为硬删,删除后不保留软删标记。
|
||||
- `memberUserId + bookId` 是业务唯一键。
|
||||
- `readProgress` 范围为 `0-100`;`chapterId` 必须大于 `0`;`lineIndex` 必须大于 `0`。
|
||||
- `chapterTitle` 为只读展示字段,禁止写入实体表结构,禁止作为创建或更新入参。
|
||||
|
||||
@@ -222,6 +222,8 @@
|
||||
- Query 字段:取 `request/Search`、`common.GetById`、`common.IdsReq`。
|
||||
- Detail 响应:取 `response` 包装结构和实体 JSON 字段。
|
||||
- List 响应:取 `ListItem`、`Select/Scan` 字段、额外展示字段。
|
||||
- 关联表响应:列表默认写明关联对象展示字段;详情用于前端展示或编辑时同步写明同口径展示字段。
|
||||
- 关联展示字段:来源于关联表 `JOIN`、预加载或批量查询组装;字段名使用 `<关联对象语义><Name/Title/Label>`,例如 `authorName`、`bookTitle`。
|
||||
- 字段规则:取 `doc-sql` 非空/默认值/唯一索引/check,叠加 `doc-dict` 值域和 `Service` 校验。
|
||||
- 字段名:使用实际 `json/form` 名称,禁止使用 Go 字段名替代。
|
||||
- 端点鉴权:默认继承 `## 基本信息`;只有端点鉴权不同才在端点内单独写。
|
||||
|
||||
@@ -12,28 +12,57 @@ type BookAuthorListItem struct {
|
||||
book.BookAuthor `gorm:"embedded"`
|
||||
AuthorName string `json:"authorName" gorm:"column:author_name"`
|
||||
}
|
||||
type BookAuthorRelationResponse struct {
|
||||
BookAuthorRelation book.BookAuthorRelation `json:"bookAuthorRelation"`
|
||||
}
|
||||
type BookAuthorRelationListItem struct {
|
||||
type BookAuthorRelationDisplay struct {
|
||||
book.BookAuthorRelation `gorm:"embedded"`
|
||||
BookTitle string `json:"bookTitle" gorm:"column:book_title"`
|
||||
AuthorName string `json:"authorName" gorm:"column:author_name"`
|
||||
}
|
||||
type BookAuthorRelationResponse struct {
|
||||
BookAuthorRelation BookAuthorRelationDisplay `json:"bookAuthorRelation"`
|
||||
}
|
||||
type BookAuthorRelationListItem = BookAuthorRelationDisplay
|
||||
type BookChapterDisplay struct {
|
||||
book.BookChapter `gorm:"embedded"`
|
||||
BookTitle string `json:"bookTitle" gorm:"column:book_title"`
|
||||
}
|
||||
type BookChapterResponse struct {
|
||||
BookChapter book.BookChapter `json:"bookChapter"`
|
||||
BookChapter BookChapterDisplay `json:"bookChapter"`
|
||||
}
|
||||
type BookChapterListItem = BookChapterDisplay
|
||||
type BookCommentDisplay struct {
|
||||
book.BookComment `gorm:"embedded"`
|
||||
BookTitle string `json:"bookTitle" gorm:"column:book_title"`
|
||||
ChapterTitle string `json:"chapterTitle" gorm:"column:chapter_title"`
|
||||
}
|
||||
type BookCommentResponse struct {
|
||||
BookComment book.BookComment `json:"bookComment"`
|
||||
BookComment BookCommentDisplay `json:"bookComment"`
|
||||
}
|
||||
type BookCommentListItem = BookCommentDisplay
|
||||
type BookCommentLikeRecordDisplay struct {
|
||||
book.BookCommentLikeRecord `gorm:"embedded"`
|
||||
BookTitle string `json:"bookTitle" gorm:"column:book_title"`
|
||||
CommentContent string `json:"commentContent" gorm:"column:comment_content"`
|
||||
}
|
||||
type BookCommentLikeRecordResponse struct {
|
||||
BookCommentLikeRecord book.BookCommentLikeRecord `json:"bookCommentLikeRecord"`
|
||||
BookCommentLikeRecord BookCommentLikeRecordDisplay `json:"bookCommentLikeRecord"`
|
||||
}
|
||||
type BookCommentLikeRecordListItem = BookCommentLikeRecordDisplay
|
||||
type BookFavoriteRecordDisplay struct {
|
||||
book.BookFavoriteRecord `gorm:"embedded"`
|
||||
BookTitle string `json:"bookTitle" gorm:"column:book_title"`
|
||||
}
|
||||
type BookFavoriteRecordResponse struct {
|
||||
BookFavoriteRecord book.BookFavoriteRecord `json:"bookFavoriteRecord"`
|
||||
BookFavoriteRecord BookFavoriteRecordDisplay `json:"bookFavoriteRecord"`
|
||||
}
|
||||
type BookFavoriteRecordListItem = BookFavoriteRecordDisplay
|
||||
type BookReadRecordDisplay struct {
|
||||
book.BookReadRecord `gorm:"embedded"`
|
||||
ChapterTitle string `json:"chapterTitle" gorm:"column:chapter_title"`
|
||||
}
|
||||
type BookReadRecordResponse struct {
|
||||
BookReadRecord book.BookReadRecord `json:"bookReadRecord"`
|
||||
BookReadRecord BookReadRecordDisplay `json:"bookReadRecord"`
|
||||
}
|
||||
type BookReadRecordListItem = BookReadRecordDisplay
|
||||
type BookSeriesResponse struct {
|
||||
BookSeries book.BookSeries `json:"bookSeries"`
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ func (s *BookAuthorService) CreateBookAuthor(item book.BookAuthor) error {
|
||||
if err := validateBookAuthor(item); err != nil {
|
||||
return err
|
||||
}
|
||||
return global.GVA_DB.Save(&item).Error
|
||||
return createWithExplicitIsEnabled(&item, item.IsEnabled)
|
||||
}
|
||||
|
||||
func (s *BookAuthorService) DeleteBookAuthor(item book.BookAuthor) error {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package book
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
@@ -17,7 +18,7 @@ func setupBookAuthorListTestDB(t *testing.T) {
|
||||
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, db.AutoMigrate(&book.BookAuthor{}, &book.BookAuthorRelation{}))
|
||||
require.NoError(t, db.AutoMigrate(&book.Book{}, &book.BookAuthor{}, &book.BookAuthorRelation{}))
|
||||
|
||||
global.GVA_DB = db
|
||||
t.Cleanup(func() {
|
||||
@@ -43,26 +44,74 @@ func TestBookAuthorService_GetBookAuthorInfoListReturnsAuthorName(t *testing.T)
|
||||
require.Equal(t, "鲁迅", list[0].AuthorName)
|
||||
}
|
||||
|
||||
func TestBookAuthorRelationService_GetBookAuthorRelationInfoListReturnsAuthorName(t *testing.T) {
|
||||
func TestBookAuthorRelationService_GetBookAuthorRelationInfoListReturnsDisplayFields(t *testing.T) {
|
||||
setupBookAuthorListTestDB(t)
|
||||
|
||||
bookItem := book.Book{
|
||||
Title: "边城",
|
||||
BookType: "novel",
|
||||
EraTag: book.BookEraTagModern,
|
||||
CompletionStatus: book.BookCompletionStatusCompleted,
|
||||
PublishStatus: book.BookPublishStatusOnShelf,
|
||||
}
|
||||
require.NoError(t, global.GVA_DB.Create(&bookItem).Error)
|
||||
|
||||
author := book.BookAuthor{Name: "沈从文", IsEnabled: true}
|
||||
require.NoError(t, global.GVA_DB.Create(&author).Error)
|
||||
require.NoError(t, global.GVA_DB.Create(&book.BookAuthorRelation{
|
||||
BookID: 11,
|
||||
BookID: bookItem.ID,
|
||||
AuthorID: author.ID,
|
||||
AuthorSort: 1,
|
||||
}).Error)
|
||||
|
||||
list, total, err := (&BookAuthorRelationService{}).GetBookAuthorRelationInfoList(bookReq.BookAuthorRelationSearch{
|
||||
PageInfo: commonReq.PageInfo{Page: 1, PageSize: 10},
|
||||
BookID: uintPtr(11),
|
||||
BookID: uintPtr(bookItem.ID),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, total)
|
||||
require.Len(t, list, 1)
|
||||
require.Equal(t, bookItem.ID, list[0].BookID)
|
||||
require.Equal(t, author.ID, list[0].AuthorID)
|
||||
require.Equal(t, "沈从文", list[0].AuthorName)
|
||||
|
||||
var got map[string]any
|
||||
payload, err := json.Marshal(list[0])
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, json.Unmarshal(payload, &got))
|
||||
require.Equal(t, "边城", got["bookTitle"])
|
||||
}
|
||||
|
||||
func TestBookAuthorRelationService_GetBookAuthorRelationReturnsDisplayFields(t *testing.T) {
|
||||
setupBookAuthorListTestDB(t)
|
||||
|
||||
bookItem := book.Book{
|
||||
Title: "边城",
|
||||
BookType: "novel",
|
||||
EraTag: book.BookEraTagModern,
|
||||
CompletionStatus: book.BookCompletionStatusCompleted,
|
||||
PublishStatus: book.BookPublishStatusOnShelf,
|
||||
}
|
||||
require.NoError(t, global.GVA_DB.Create(&bookItem).Error)
|
||||
|
||||
author := book.BookAuthor{Name: "沈从文", IsEnabled: true}
|
||||
require.NoError(t, global.GVA_DB.Create(&author).Error)
|
||||
relation := book.BookAuthorRelation{
|
||||
BookID: bookItem.ID,
|
||||
AuthorID: author.ID,
|
||||
AuthorSort: 1,
|
||||
}
|
||||
require.NoError(t, global.GVA_DB.Create(&relation).Error)
|
||||
|
||||
item, err := (&BookAuthorRelationService{}).GetBookAuthorRelation(relation.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
var got map[string]any
|
||||
payload, err := json.Marshal(item)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, json.Unmarshal(payload, &got))
|
||||
require.Equal(t, "边城", got["bookTitle"])
|
||||
require.Equal(t, "沈从文", got["authorName"])
|
||||
}
|
||||
|
||||
func uintPtr(v uint) *uint {
|
||||
|
||||
@@ -6,10 +6,13 @@ import (
|
||||
bookReq "github.com/flipped-aurora/gin-vue-admin/server/model/book/request"
|
||||
bookRes "github.com/flipped-aurora/gin-vue-admin/server/model/book/response"
|
||||
commonReq "github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type BookAuthorRelationService struct{}
|
||||
|
||||
const bookAuthorRelationDisplaySelect = "bar.*, b.title AS book_title, ba.name AS author_name"
|
||||
|
||||
func (s *BookAuthorRelationService) CreateBookAuthorRelation(item book.BookAuthorRelation) error {
|
||||
item = applyBookAuthorRelationDefaults(item)
|
||||
if err := validateBookAuthorRelation(item); err != nil {
|
||||
@@ -33,13 +36,16 @@ func (s *BookAuthorRelationService) UpdateBookAuthorRelation(item book.BookAutho
|
||||
return global.GVA_DB.Save(&item).Error
|
||||
}
|
||||
|
||||
func (s *BookAuthorRelationService) GetBookAuthorRelation(id uint) (item book.BookAuthorRelation, err error) {
|
||||
err = global.GVA_DB.Where("id = ?", id).First(&item).Error
|
||||
func (s *BookAuthorRelationService) GetBookAuthorRelation(id uint) (item bookRes.BookAuthorRelationDisplay, err error) {
|
||||
err = bookAuthorRelationDisplayDB().
|
||||
Select(bookAuthorRelationDisplaySelect).
|
||||
Where("bar.id = ?", id).
|
||||
Take(&item).Error
|
||||
return
|
||||
}
|
||||
|
||||
func (s *BookAuthorRelationService) GetBookAuthorRelationInfoList(info bookReq.BookAuthorRelationSearch) (list []bookRes.BookAuthorRelationListItem, total int64, err error) {
|
||||
db := global.GVA_DB.Table("book_author_relation AS bar")
|
||||
db := bookAuthorRelationDisplayDB()
|
||||
if info.BookID != nil {
|
||||
db = db.Where("bar.book_id = ?", *info.BookID)
|
||||
}
|
||||
@@ -50,10 +56,15 @@ func (s *BookAuthorRelationService) GetBookAuthorRelationInfoList(info bookReq.B
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = db.Select("bar.*, ba.name AS author_name").
|
||||
Joins("LEFT JOIN book_author AS ba ON ba.id = bar.author_id").
|
||||
err = db.Select(bookAuthorRelationDisplaySelect).
|
||||
Scopes(paginate(info.PageInfo)).
|
||||
Order("bar.book_id asc, bar.author_sort asc, bar.id desc").
|
||||
Scan(&list).Error
|
||||
return
|
||||
}
|
||||
|
||||
func bookAuthorRelationDisplayDB() *gorm.DB {
|
||||
return global.GVA_DB.Table("book_author_relation AS bar").
|
||||
Joins("LEFT JOIN book AS b ON b.id = bar.book_id").
|
||||
Joins("LEFT JOIN book_author AS ba ON ba.id = bar.author_id")
|
||||
}
|
||||
|
||||
@@ -4,16 +4,20 @@ import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/book"
|
||||
bookReq "github.com/flipped-aurora/gin-vue-admin/server/model/book/request"
|
||||
bookRes "github.com/flipped-aurora/gin-vue-admin/server/model/book/response"
|
||||
commonReq "github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type BookChapterService struct{}
|
||||
|
||||
const bookChapterDisplaySelect = "bc.*, b.title AS book_title"
|
||||
|
||||
func (s *BookChapterService) CreateBookChapter(item book.BookChapter) error {
|
||||
if err := validateBookChapter(item); err != nil {
|
||||
return err
|
||||
}
|
||||
return global.GVA_DB.Save(&item).Error
|
||||
return createWithExplicitIsEnabled(&item, item.IsEnabled)
|
||||
}
|
||||
|
||||
func (s *BookChapterService) DeleteBookChapter(item book.BookChapter) error {
|
||||
@@ -31,29 +35,37 @@ func (s *BookChapterService) UpdateBookChapter(item book.BookChapter) error {
|
||||
return global.GVA_DB.Save(&item).Error
|
||||
}
|
||||
|
||||
func (s *BookChapterService) GetBookChapter(id uint) (item book.BookChapter, err error) {
|
||||
err = global.GVA_DB.Where("id = ?", id).First(&item).Error
|
||||
func (s *BookChapterService) GetBookChapter(id uint) (item bookRes.BookChapterDisplay, err error) {
|
||||
err = bookChapterDisplayDB().
|
||||
Select(bookChapterDisplaySelect).
|
||||
Where("bc.id = ?", id).
|
||||
Take(&item).Error
|
||||
return
|
||||
}
|
||||
|
||||
func (s *BookChapterService) GetBookChapterInfoList(info bookReq.BookChapterSearch) (list []book.BookChapter, total int64, err error) {
|
||||
db := global.GVA_DB.Model(&book.BookChapter{})
|
||||
func (s *BookChapterService) GetBookChapterInfoList(info bookReq.BookChapterSearch) (list []bookRes.BookChapterListItem, total int64, err error) {
|
||||
db := bookChapterDisplayDB()
|
||||
if info.BookID != nil {
|
||||
db = db.Where("book_id = ?", *info.BookID)
|
||||
db = db.Where("bc.book_id = ?", *info.BookID)
|
||||
}
|
||||
if info.IsReadable != nil {
|
||||
db = db.Where("is_readable = ?", *info.IsReadable)
|
||||
db = db.Where("bc.is_readable = ?", *info.IsReadable)
|
||||
}
|
||||
if info.IsEnabled != nil {
|
||||
db = db.Where("is_enabled = ?", *info.IsEnabled)
|
||||
db = db.Where("bc.is_enabled = ?", *info.IsEnabled)
|
||||
}
|
||||
if info.Keyword != "" {
|
||||
db = db.Where("title LIKE ?", "%"+info.Keyword+"%")
|
||||
db = db.Where("bc.title LIKE ?", "%"+info.Keyword+"%")
|
||||
}
|
||||
err = db.Count(&total).Error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = db.Scopes(paginate(info.PageInfo)).Order("book_id asc, chapter_no asc").Find(&list).Error
|
||||
err = db.Select(bookChapterDisplaySelect).Scopes(paginate(info.PageInfo)).Order("bc.book_id asc, bc.chapter_no asc").Scan(&list).Error
|
||||
return
|
||||
}
|
||||
|
||||
func bookChapterDisplayDB() *gorm.DB {
|
||||
return global.GVA_DB.Table("book_chapter AS bc").
|
||||
Joins("LEFT JOIN book AS b ON b.id = bc.book_id")
|
||||
}
|
||||
|
||||
@@ -4,11 +4,15 @@ import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/book"
|
||||
bookReq "github.com/flipped-aurora/gin-vue-admin/server/model/book/request"
|
||||
bookRes "github.com/flipped-aurora/gin-vue-admin/server/model/book/response"
|
||||
commonReq "github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type BookCommentService struct{}
|
||||
|
||||
const bookCommentDisplaySelect = "bc.*, b.title AS book_title, ch.title AS chapter_title"
|
||||
|
||||
func (s *BookCommentService) CreateBookComment(item book.BookComment) error {
|
||||
if err := validateBookComment(item); err != nil {
|
||||
return err
|
||||
@@ -31,29 +35,38 @@ func (s *BookCommentService) UpdateBookComment(item book.BookComment) error {
|
||||
return global.GVA_DB.Save(&item).Error
|
||||
}
|
||||
|
||||
func (s *BookCommentService) GetBookComment(id uint) (item book.BookComment, err error) {
|
||||
err = global.GVA_DB.Where("id = ?", id).First(&item).Error
|
||||
func (s *BookCommentService) GetBookComment(id uint) (item bookRes.BookCommentDisplay, err error) {
|
||||
err = bookCommentDisplayDB().
|
||||
Select(bookCommentDisplaySelect).
|
||||
Where("bc.id = ?", id).
|
||||
Take(&item).Error
|
||||
return
|
||||
}
|
||||
|
||||
func (s *BookCommentService) GetBookCommentInfoList(info bookReq.BookCommentSearch) (list []book.BookComment, total int64, err error) {
|
||||
db := global.GVA_DB.Model(&book.BookComment{})
|
||||
func (s *BookCommentService) GetBookCommentInfoList(info bookReq.BookCommentSearch) (list []bookRes.BookCommentListItem, total int64, err error) {
|
||||
db := bookCommentDisplayDB()
|
||||
if info.MemberUserID != nil {
|
||||
db = db.Where("member_user_id = ?", *info.MemberUserID)
|
||||
db = db.Where("bc.member_user_id = ?", *info.MemberUserID)
|
||||
}
|
||||
if info.BookID != nil {
|
||||
db = db.Where("book_id = ?", *info.BookID)
|
||||
db = db.Where("bc.book_id = ?", *info.BookID)
|
||||
}
|
||||
if info.ChapterID != nil {
|
||||
db = db.Where("chapter_id = ?", *info.ChapterID)
|
||||
db = db.Where("bc.chapter_id = ?", *info.ChapterID)
|
||||
}
|
||||
if info.CommentStatus != "" {
|
||||
db = db.Where("comment_status = ?", info.CommentStatus)
|
||||
db = db.Where("bc.comment_status = ?", info.CommentStatus)
|
||||
}
|
||||
err = db.Count(&total).Error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = db.Scopes(paginate(info.PageInfo)).Order("id desc").Find(&list).Error
|
||||
err = db.Select(bookCommentDisplaySelect).Scopes(paginate(info.PageInfo)).Order("bc.id desc").Scan(&list).Error
|
||||
return
|
||||
}
|
||||
|
||||
func bookCommentDisplayDB() *gorm.DB {
|
||||
return global.GVA_DB.Table("book_comment AS bc").
|
||||
Joins("LEFT JOIN book AS b ON b.id = bc.book_id").
|
||||
Joins("LEFT JOIN book_chapter AS ch ON ch.id = bc.chapter_id")
|
||||
}
|
||||
|
||||
@@ -4,11 +4,15 @@ import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/book"
|
||||
bookReq "github.com/flipped-aurora/gin-vue-admin/server/model/book/request"
|
||||
bookRes "github.com/flipped-aurora/gin-vue-admin/server/model/book/response"
|
||||
commonReq "github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type BookCommentLikeRecordService struct{}
|
||||
|
||||
const bookCommentLikeRecordDisplaySelect = "bclr.*, b.title AS book_title, bc.content AS comment_content"
|
||||
|
||||
func (s *BookCommentLikeRecordService) CreateBookCommentLikeRecord(item book.BookCommentLikeRecord) error {
|
||||
return global.GVA_DB.Create(&item).Error
|
||||
}
|
||||
@@ -25,23 +29,32 @@ func (s *BookCommentLikeRecordService) UpdateBookCommentLikeRecord(item book.Boo
|
||||
return global.GVA_DB.Save(&item).Error
|
||||
}
|
||||
|
||||
func (s *BookCommentLikeRecordService) GetBookCommentLikeRecord(id uint) (item book.BookCommentLikeRecord, err error) {
|
||||
err = global.GVA_DB.Where("id = ?", id).First(&item).Error
|
||||
func (s *BookCommentLikeRecordService) GetBookCommentLikeRecord(id uint) (item bookRes.BookCommentLikeRecordDisplay, err error) {
|
||||
err = bookCommentLikeRecordDisplayDB().
|
||||
Select(bookCommentLikeRecordDisplaySelect).
|
||||
Where("bclr.id = ?", id).
|
||||
Take(&item).Error
|
||||
return
|
||||
}
|
||||
|
||||
func (s *BookCommentLikeRecordService) GetBookCommentLikeRecordInfoList(info bookReq.BookCommentLikeRecordSearch) (list []book.BookCommentLikeRecord, total int64, err error) {
|
||||
db := global.GVA_DB.Model(&book.BookCommentLikeRecord{})
|
||||
func (s *BookCommentLikeRecordService) GetBookCommentLikeRecordInfoList(info bookReq.BookCommentLikeRecordSearch) (list []bookRes.BookCommentLikeRecordListItem, total int64, err error) {
|
||||
db := bookCommentLikeRecordDisplayDB()
|
||||
if info.CommentID != nil {
|
||||
db = db.Where("comment_id = ?", *info.CommentID)
|
||||
db = db.Where("bclr.comment_id = ?", *info.CommentID)
|
||||
}
|
||||
if info.MemberUserID != nil {
|
||||
db = db.Where("member_user_id = ?", *info.MemberUserID)
|
||||
db = db.Where("bclr.member_user_id = ?", *info.MemberUserID)
|
||||
}
|
||||
err = db.Count(&total).Error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = db.Scopes(paginate(info.PageInfo)).Order("id desc").Find(&list).Error
|
||||
err = db.Select(bookCommentLikeRecordDisplaySelect).Scopes(paginate(info.PageInfo)).Order("bclr.id desc").Scan(&list).Error
|
||||
return
|
||||
}
|
||||
|
||||
func bookCommentLikeRecordDisplayDB() *gorm.DB {
|
||||
return global.GVA_DB.Table("book_comment_like_record AS bclr").
|
||||
Joins("LEFT JOIN book_comment AS bc ON bc.id = bclr.comment_id").
|
||||
Joins("LEFT JOIN book AS b ON b.id = bc.book_id")
|
||||
}
|
||||
|
||||
199
server/service/book/book_display_fields_test.go
Normal file
199
server/service/book/book_display_fields_test.go
Normal file
@@ -0,0 +1,199 @@
|
||||
package book
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/book"
|
||||
bookReq "github.com/flipped-aurora/gin-vue-admin/server/model/book/request"
|
||||
commonReq "github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func setupBookDisplayTestDB(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, db.AutoMigrate(
|
||||
&book.Book{},
|
||||
&book.BookChapter{},
|
||||
&book.BookComment{},
|
||||
&book.BookCommentLikeRecord{},
|
||||
&book.BookFavoriteRecord{},
|
||||
&book.BookReadRecord{},
|
||||
))
|
||||
|
||||
global.GVA_DB = db
|
||||
t.Cleanup(func() {
|
||||
global.GVA_DB = nil
|
||||
})
|
||||
}
|
||||
|
||||
func createBookDisplayFixture(t *testing.T) (book.Book, book.BookChapter, book.BookComment) {
|
||||
t.Helper()
|
||||
|
||||
bookItem := book.Book{
|
||||
Title: "边城",
|
||||
BookType: "novel",
|
||||
EraTag: book.BookEraTagModern,
|
||||
CompletionStatus: book.BookCompletionStatusCompleted,
|
||||
PublishStatus: book.BookPublishStatusOnShelf,
|
||||
}
|
||||
require.NoError(t, global.GVA_DB.Create(&bookItem).Error)
|
||||
|
||||
chapter := book.BookChapter{
|
||||
BookID: bookItem.ID,
|
||||
Title: "第一章 茶峒",
|
||||
ChapterNo: 1,
|
||||
ContentFileUrl: "chapters/biancheng-1.txt",
|
||||
IsEnabled: true,
|
||||
}
|
||||
require.NoError(t, global.GVA_DB.Create(&chapter).Error)
|
||||
|
||||
comment := book.BookComment{
|
||||
MemberUserID: 7,
|
||||
BookID: bookItem.ID,
|
||||
ChapterID: chapter.ID,
|
||||
Content: "这段很有画面感",
|
||||
CommentStatus: book.BookCommentStatusNormal,
|
||||
}
|
||||
require.NoError(t, global.GVA_DB.Create(&comment).Error)
|
||||
|
||||
return bookItem, chapter, comment
|
||||
}
|
||||
|
||||
func requireJSONField(t *testing.T, value any, key string, expected any) {
|
||||
t.Helper()
|
||||
|
||||
payload, err := json.Marshal(value)
|
||||
require.NoError(t, err)
|
||||
var got map[string]any
|
||||
require.NoError(t, json.Unmarshal(payload, &got))
|
||||
require.Equal(t, expected, got[key])
|
||||
}
|
||||
|
||||
func TestBookChapterServiceReturnsBookTitle(t *testing.T) {
|
||||
setupBookDisplayTestDB(t)
|
||||
bookItem, chapter, _ := createBookDisplayFixture(t)
|
||||
|
||||
list, total, err := (&BookChapterService{}).GetBookChapterInfoList(bookReq.BookChapterSearch{
|
||||
PageInfo: commonReq.PageInfo{Page: 1, PageSize: 10},
|
||||
BookID: uintPtr(bookItem.ID),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, total)
|
||||
require.Len(t, list, 1)
|
||||
require.Equal(t, chapter.ID, list[0].ID)
|
||||
requireJSONField(t, list[0], "bookTitle", "边城")
|
||||
|
||||
detail, err := (&BookChapterService{}).GetBookChapter(chapter.ID)
|
||||
require.NoError(t, err)
|
||||
requireJSONField(t, detail, "bookTitle", "边城")
|
||||
}
|
||||
|
||||
func TestBookCommentServiceReturnsBookAndChapterTitles(t *testing.T) {
|
||||
setupBookDisplayTestDB(t)
|
||||
bookItem, _, comment := createBookDisplayFixture(t)
|
||||
|
||||
list, total, err := (&BookCommentService{}).GetBookCommentInfoList(bookReq.BookCommentSearch{
|
||||
PageInfo: commonReq.PageInfo{Page: 1, PageSize: 10},
|
||||
BookID: uintPtr(bookItem.ID),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, total)
|
||||
require.Len(t, list, 1)
|
||||
require.Equal(t, comment.ID, list[0].ID)
|
||||
requireJSONField(t, list[0], "bookTitle", "边城")
|
||||
requireJSONField(t, list[0], "chapterTitle", "第一章 茶峒")
|
||||
|
||||
detail, err := (&BookCommentService{}).GetBookComment(comment.ID)
|
||||
require.NoError(t, err)
|
||||
requireJSONField(t, detail, "bookTitle", "边城")
|
||||
requireJSONField(t, detail, "chapterTitle", "第一章 茶峒")
|
||||
}
|
||||
|
||||
func TestBookCommentLikeRecordServiceReturnsCommentDisplayFields(t *testing.T) {
|
||||
setupBookDisplayTestDB(t)
|
||||
_, _, comment := createBookDisplayFixture(t)
|
||||
record := book.BookCommentLikeRecord{
|
||||
CommentID: comment.ID,
|
||||
MemberUserID: 8,
|
||||
LikedAt: time.Now(),
|
||||
}
|
||||
require.NoError(t, global.GVA_DB.Create(&record).Error)
|
||||
|
||||
list, total, err := (&BookCommentLikeRecordService{}).GetBookCommentLikeRecordInfoList(bookReq.BookCommentLikeRecordSearch{
|
||||
PageInfo: commonReq.PageInfo{Page: 1, PageSize: 10},
|
||||
CommentID: uintPtr(comment.ID),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, total)
|
||||
require.Len(t, list, 1)
|
||||
require.Equal(t, record.ID, list[0].ID)
|
||||
requireJSONField(t, list[0], "bookTitle", "边城")
|
||||
requireJSONField(t, list[0], "commentContent", "这段很有画面感")
|
||||
|
||||
detail, err := (&BookCommentLikeRecordService{}).GetBookCommentLikeRecord(record.ID)
|
||||
require.NoError(t, err)
|
||||
requireJSONField(t, detail, "bookTitle", "边城")
|
||||
requireJSONField(t, detail, "commentContent", "这段很有画面感")
|
||||
}
|
||||
|
||||
func TestBookFavoriteRecordServiceReturnsBookTitle(t *testing.T) {
|
||||
setupBookDisplayTestDB(t)
|
||||
bookItem, _, _ := createBookDisplayFixture(t)
|
||||
record := book.BookFavoriteRecord{
|
||||
MemberUserID: 9,
|
||||
BookID: bookItem.ID,
|
||||
FavoritedAt: time.Now(),
|
||||
}
|
||||
require.NoError(t, global.GVA_DB.Create(&record).Error)
|
||||
|
||||
list, total, err := (&BookFavoriteRecordService{}).GetBookFavoriteRecordInfoList(bookReq.BookFavoriteRecordSearch{
|
||||
PageInfo: commonReq.PageInfo{Page: 1, PageSize: 10},
|
||||
BookID: uintPtr(bookItem.ID),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, total)
|
||||
require.Len(t, list, 1)
|
||||
require.Equal(t, record.ID, list[0].ID)
|
||||
requireJSONField(t, list[0], "bookTitle", "边城")
|
||||
|
||||
detail, err := (&BookFavoriteRecordService{}).GetBookFavoriteRecord(record.ID)
|
||||
require.NoError(t, err)
|
||||
requireJSONField(t, detail, "bookTitle", "边城")
|
||||
}
|
||||
|
||||
func TestBookReadRecordServiceReturnsChapterTitle(t *testing.T) {
|
||||
setupBookDisplayTestDB(t)
|
||||
bookItem, chapter, _ := createBookDisplayFixture(t)
|
||||
record := book.BookReadRecord{
|
||||
MemberUserID: 10,
|
||||
BookID: bookItem.ID,
|
||||
BookTitleSnapshot: bookItem.Title,
|
||||
ReadProgress: 12.5,
|
||||
ChapterID: chapter.ID,
|
||||
LineIndex: 3,
|
||||
LastReadAt: time.Now(),
|
||||
}
|
||||
require.NoError(t, global.GVA_DB.Create(&record).Error)
|
||||
|
||||
list, total, err := (&BookReadRecordService{}).GetBookReadRecordInfoList(bookReq.BookReadRecordSearch{
|
||||
PageInfo: commonReq.PageInfo{Page: 1, PageSize: 10},
|
||||
BookID: uintPtr(bookItem.ID),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, total)
|
||||
require.Len(t, list, 1)
|
||||
require.Equal(t, record.ID, list[0].ID)
|
||||
requireJSONField(t, list[0], "chapterTitle", "第一章 茶峒")
|
||||
|
||||
detail, err := (&BookReadRecordService{}).GetBookReadRecord(record.ID)
|
||||
require.NoError(t, err)
|
||||
requireJSONField(t, detail, "chapterTitle", "第一章 茶峒")
|
||||
}
|
||||
@@ -4,11 +4,15 @@ import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/book"
|
||||
bookReq "github.com/flipped-aurora/gin-vue-admin/server/model/book/request"
|
||||
bookRes "github.com/flipped-aurora/gin-vue-admin/server/model/book/response"
|
||||
commonReq "github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type BookFavoriteRecordService struct{}
|
||||
|
||||
const bookFavoriteRecordDisplaySelect = "bfr.*, b.title AS book_title"
|
||||
|
||||
func (s *BookFavoriteRecordService) CreateBookFavoriteRecord(item book.BookFavoriteRecord) error {
|
||||
return global.GVA_DB.Create(&item).Error
|
||||
}
|
||||
@@ -25,23 +29,31 @@ func (s *BookFavoriteRecordService) UpdateBookFavoriteRecord(item book.BookFavor
|
||||
return global.GVA_DB.Save(&item).Error
|
||||
}
|
||||
|
||||
func (s *BookFavoriteRecordService) GetBookFavoriteRecord(id uint) (item book.BookFavoriteRecord, err error) {
|
||||
err = global.GVA_DB.Where("id = ?", id).First(&item).Error
|
||||
func (s *BookFavoriteRecordService) GetBookFavoriteRecord(id uint) (item bookRes.BookFavoriteRecordDisplay, err error) {
|
||||
err = bookFavoriteRecordDisplayDB().
|
||||
Select(bookFavoriteRecordDisplaySelect).
|
||||
Where("bfr.id = ?", id).
|
||||
Take(&item).Error
|
||||
return
|
||||
}
|
||||
|
||||
func (s *BookFavoriteRecordService) GetBookFavoriteRecordInfoList(info bookReq.BookFavoriteRecordSearch) (list []book.BookFavoriteRecord, total int64, err error) {
|
||||
db := global.GVA_DB.Model(&book.BookFavoriteRecord{})
|
||||
func (s *BookFavoriteRecordService) GetBookFavoriteRecordInfoList(info bookReq.BookFavoriteRecordSearch) (list []bookRes.BookFavoriteRecordListItem, total int64, err error) {
|
||||
db := bookFavoriteRecordDisplayDB()
|
||||
if info.MemberUserID != nil {
|
||||
db = db.Where("member_user_id = ?", *info.MemberUserID)
|
||||
db = db.Where("bfr.member_user_id = ?", *info.MemberUserID)
|
||||
}
|
||||
if info.BookID != nil {
|
||||
db = db.Where("book_id = ?", *info.BookID)
|
||||
db = db.Where("bfr.book_id = ?", *info.BookID)
|
||||
}
|
||||
err = db.Count(&total).Error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = db.Scopes(paginate(info.PageInfo)).Order("id desc").Find(&list).Error
|
||||
err = db.Select(bookFavoriteRecordDisplaySelect).Scopes(paginate(info.PageInfo)).Order("bfr.id desc").Scan(&list).Error
|
||||
return
|
||||
}
|
||||
|
||||
func bookFavoriteRecordDisplayDB() *gorm.DB {
|
||||
return global.GVA_DB.Table("book_favorite_record AS bfr").
|
||||
Joins("LEFT JOIN book AS b ON b.id = bfr.book_id")
|
||||
}
|
||||
|
||||
@@ -4,11 +4,15 @@ import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/book"
|
||||
bookReq "github.com/flipped-aurora/gin-vue-admin/server/model/book/request"
|
||||
bookRes "github.com/flipped-aurora/gin-vue-admin/server/model/book/response"
|
||||
commonReq "github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type BookReadRecordService struct{}
|
||||
|
||||
const bookReadRecordDisplaySelect = "brr.*, ch.title AS chapter_title"
|
||||
|
||||
func (s *BookReadRecordService) CreateBookReadRecord(item book.BookReadRecord) error {
|
||||
if err := validateBookReadRecord(item); err != nil {
|
||||
return err
|
||||
@@ -31,23 +35,31 @@ func (s *BookReadRecordService) UpdateBookReadRecord(item book.BookReadRecord) e
|
||||
return global.GVA_DB.Save(&item).Error
|
||||
}
|
||||
|
||||
func (s *BookReadRecordService) GetBookReadRecord(id uint) (item book.BookReadRecord, err error) {
|
||||
err = global.GVA_DB.Where("id = ?", id).First(&item).Error
|
||||
func (s *BookReadRecordService) GetBookReadRecord(id uint) (item bookRes.BookReadRecordDisplay, err error) {
|
||||
err = bookReadRecordDisplayDB().
|
||||
Select(bookReadRecordDisplaySelect).
|
||||
Where("brr.id = ?", id).
|
||||
Take(&item).Error
|
||||
return
|
||||
}
|
||||
|
||||
func (s *BookReadRecordService) GetBookReadRecordInfoList(info bookReq.BookReadRecordSearch) (list []book.BookReadRecord, total int64, err error) {
|
||||
db := global.GVA_DB.Model(&book.BookReadRecord{})
|
||||
func (s *BookReadRecordService) GetBookReadRecordInfoList(info bookReq.BookReadRecordSearch) (list []bookRes.BookReadRecordListItem, total int64, err error) {
|
||||
db := bookReadRecordDisplayDB()
|
||||
if info.MemberUserID != nil {
|
||||
db = db.Where("member_user_id = ?", *info.MemberUserID)
|
||||
db = db.Where("brr.member_user_id = ?", *info.MemberUserID)
|
||||
}
|
||||
if info.BookID != nil {
|
||||
db = db.Where("book_id = ?", *info.BookID)
|
||||
db = db.Where("brr.book_id = ?", *info.BookID)
|
||||
}
|
||||
err = db.Count(&total).Error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = db.Scopes(paginate(info.PageInfo)).Order("last_read_at desc, id desc").Find(&list).Error
|
||||
err = db.Select(bookReadRecordDisplaySelect).Scopes(paginate(info.PageInfo)).Order("brr.last_read_at desc, brr.id desc").Scan(&list).Error
|
||||
return
|
||||
}
|
||||
|
||||
func bookReadRecordDisplayDB() *gorm.DB {
|
||||
return global.GVA_DB.Table("book_read_record AS brr").
|
||||
Joins("LEFT JOIN book_chapter AS ch ON ch.id = brr.chapter_id")
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
type BookSeriesService struct{}
|
||||
|
||||
func (s *BookSeriesService) CreateBookSeries(item book.BookSeries) error {
|
||||
return global.GVA_DB.Save(&item).Error
|
||||
return createWithExplicitIsEnabled(&item, item.IsEnabled)
|
||||
}
|
||||
|
||||
func (s *BookSeriesService) DeleteBookSeries(item book.BookSeries) error {
|
||||
|
||||
@@ -11,3 +11,15 @@ func paginate(info commonReq.PageInfo) func(db *gorm.DB) *gorm.DB { return (&inf
|
||||
func deleteByIDs[T any](ids []int) error {
|
||||
return global.GVA_DB.Where("id in ?", ids).Delete(new(T)).Error
|
||||
}
|
||||
|
||||
func createWithExplicitIsEnabled[T any](item *T, isEnabled bool) error {
|
||||
return global.GVA_DB.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Create(item).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if isEnabled {
|
||||
return nil
|
||||
}
|
||||
return tx.Model(item).Update("is_enabled", false).Error
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user