Skip to content

Commit 746e2da

Browse files
committed
feat: add posts router with CRUD operations and media upload functionality
1 parent 08e2b4e commit 746e2da

File tree

14 files changed

+2466
-317
lines changed

14 files changed

+2466
-317
lines changed

.github/prompts/main.md.prompt.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
agent: agent
3+
---
4+
项目在部署时只包含一个数据库环境变量,所有配置均从src\services\config\zcconfig.js中读取,配置项在src\services\config\configTypes.js中定义。
5+
项目使用prisma,postgresql,不使用任何外键,关系由prisma管理。
6+
我不需要你编写各种文档文件,如果有必要的说明直接输出即可,如果创建了新的接口,你需要给出其位置(注意接口没用/api前缀),调用方法,传入值和返回值示例,直接使用代码块包裹在输出中,不需要变成curl。
7+
你需要严格遵守javascript的最佳实践,代码风格参考现有代码,并且要考虑安全性和性能,不要创建太多文件,复用现有代码。
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
-- CreateEnum
2+
CREATE TYPE "ow_post_type" AS ENUM ('normal', 'reply', 'retweet', 'quote');
3+
4+
-- CreateTable
5+
CREATE TABLE "ow_posts" (
6+
"id" SERIAL NOT NULL,
7+
"author_id" INTEGER NOT NULL,
8+
"post_type" "ow_post_type" NOT NULL DEFAULT 'normal',
9+
"content" TEXT,
10+
"character_count" INTEGER NOT NULL DEFAULT 0,
11+
"in_reply_to_id" INTEGER,
12+
"thread_root_id" INTEGER,
13+
"quoted_post_id" INTEGER,
14+
"retweet_post_id" INTEGER,
15+
"reply_count" INTEGER NOT NULL DEFAULT 0,
16+
"retweet_count" INTEGER NOT NULL DEFAULT 0,
17+
"like_count" INTEGER NOT NULL DEFAULT 0,
18+
"bookmark_count" INTEGER NOT NULL DEFAULT 0,
19+
"is_deleted" BOOLEAN NOT NULL DEFAULT false,
20+
"created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
21+
"updated_at" TIMESTAMP(6) NOT NULL,
22+
"metadata" JSONB,
23+
24+
CONSTRAINT "ow_posts_pkey" PRIMARY KEY ("id")
25+
);
26+
27+
-- CreateTable
28+
CREATE TABLE "ow_posts_mention" (
29+
"id" SERIAL NOT NULL,
30+
"post_id" INTEGER NOT NULL,
31+
"user_id" INTEGER NOT NULL,
32+
"created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
33+
"notified" BOOLEAN NOT NULL DEFAULT false,
34+
35+
CONSTRAINT "ow_posts_mention_pkey" PRIMARY KEY ("id")
36+
);
37+
38+
-- CreateTable
39+
CREATE TABLE "ow_posts_like" (
40+
"id" SERIAL NOT NULL,
41+
"user_id" INTEGER NOT NULL,
42+
"post_id" INTEGER NOT NULL,
43+
"created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
44+
45+
CONSTRAINT "ow_posts_like_pkey" PRIMARY KEY ("id")
46+
);
47+
48+
-- CreateTable
49+
CREATE TABLE "ow_posts_bookmark" (
50+
"id" SERIAL NOT NULL,
51+
"user_id" INTEGER NOT NULL,
52+
"post_id" INTEGER NOT NULL,
53+
"created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
54+
55+
CONSTRAINT "ow_posts_bookmark_pkey" PRIMARY KEY ("id")
56+
);
57+
58+
-- CreateTable
59+
CREATE TABLE "ow_posts_media" (
60+
"id" SERIAL NOT NULL,
61+
"post_id" INTEGER NOT NULL,
62+
"asset_id" INTEGER,
63+
"order" INTEGER NOT NULL DEFAULT 0,
64+
"created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
65+
66+
CONSTRAINT "ow_posts_media_pkey" PRIMARY KEY ("id")
67+
);
68+
69+
-- CreateIndex
70+
CREATE INDEX "ow_posts_author_id_created_at_idx" ON "ow_posts"("author_id", "created_at" DESC);
71+
72+
-- CreateIndex
73+
CREATE INDEX "ow_posts_created_at_idx" ON "ow_posts"("created_at" DESC);
74+
75+
-- CreateIndex
76+
CREATE INDEX "ow_posts_in_reply_to_id_idx" ON "ow_posts"("in_reply_to_id");
77+
78+
-- CreateIndex
79+
CREATE INDEX "ow_posts_thread_root_id_idx" ON "ow_posts"("thread_root_id");
80+
81+
-- CreateIndex
82+
CREATE INDEX "ow_posts_quoted_post_id_idx" ON "ow_posts"("quoted_post_id");
83+
84+
-- CreateIndex
85+
CREATE INDEX "ow_posts_retweet_post_id_idx" ON "ow_posts"("retweet_post_id");
86+
87+
-- CreateIndex
88+
CREATE INDEX "ow_posts_post_type_idx" ON "ow_posts"("post_type");
89+
90+
-- CreateIndex
91+
CREATE INDEX "ow_posts_is_deleted_idx" ON "ow_posts"("is_deleted");
92+
93+
-- CreateIndex
94+
CREATE INDEX "ow_posts_is_deleted_created_at_idx" ON "ow_posts"("is_deleted", "created_at" DESC);
95+
96+
-- CreateIndex
97+
CREATE INDEX "ow_posts_thread_root_id_created_at_idx" ON "ow_posts"("thread_root_id", "created_at" ASC);
98+
99+
-- CreateIndex
100+
CREATE INDEX "ow_posts_mention_user_id_created_at_idx" ON "ow_posts_mention"("user_id", "created_at" DESC);
101+
102+
-- CreateIndex
103+
CREATE INDEX "ow_posts_mention_post_id_idx" ON "ow_posts_mention"("post_id");
104+
105+
-- CreateIndex
106+
CREATE UNIQUE INDEX "ow_posts_mention_post_id_user_id_key" ON "ow_posts_mention"("post_id", "user_id");
107+
108+
-- CreateIndex
109+
CREATE INDEX "ow_posts_like_user_id_created_at_idx" ON "ow_posts_like"("user_id", "created_at" DESC);
110+
111+
-- CreateIndex
112+
CREATE INDEX "ow_posts_like_post_id_idx" ON "ow_posts_like"("post_id");
113+
114+
-- CreateIndex
115+
CREATE UNIQUE INDEX "ow_posts_like_user_id_post_id_key" ON "ow_posts_like"("user_id", "post_id");
116+
117+
-- CreateIndex
118+
CREATE INDEX "ow_posts_bookmark_user_id_created_at_idx" ON "ow_posts_bookmark"("user_id", "created_at" DESC);
119+
120+
-- CreateIndex
121+
CREATE INDEX "ow_posts_bookmark_post_id_idx" ON "ow_posts_bookmark"("post_id");
122+
123+
-- CreateIndex
124+
CREATE UNIQUE INDEX "ow_posts_bookmark_user_id_post_id_key" ON "ow_posts_bookmark"("user_id", "post_id");
125+
126+
-- CreateIndex
127+
CREATE INDEX "ow_posts_media_post_id_idx" ON "ow_posts_media"("post_id");
128+
129+
-- CreateIndex
130+
CREATE INDEX "ow_posts_media_asset_id_idx" ON "ow_posts_media"("asset_id");
131+
132+
-- CreateIndex
133+
CREATE UNIQUE INDEX "ow_posts_media_post_id_asset_id_key" ON "ow_posts_media"("post_id", "asset_id");
134+
135+
-- CreateIndex
136+
CREATE UNIQUE INDEX "ow_posts_media_post_id_order_key" ON "ow_posts_media"("post_id", "order");
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- AlterTable
2+
ALTER TABLE "ow_posts" ADD COLUMN "embed" JSONB;

prisma/schema.prisma

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ model ow_users {
128128
uploaded_assets ow_assets[] @relation("AssetUploader")
129129
banned_assets ow_assets[] @relation("AssetBannedBy")
130130
push_subscriptions ow_push_subscriptions[] @relation("UserPushSubscriptions")
131+
posts ow_posts[] @relation("PostAuthor")
132+
likes ow_posts_like[] @relation("LikeAuthor")
133+
bookmarks ow_posts_bookmark[] @relation("BookmarkAuthor")
134+
mentioned_in_posts ow_posts_mention[] @relation("UserMentions")
131135
132136
@@index([email])
133137
@@index([status])
@@ -775,6 +779,7 @@ model ow_assets {
775779
uploader ow_users? @relation("AssetUploader", fields: [uploader_id], references: [id], onDelete: SetNull, onUpdate: SetNull)
776780
banned_by_user ow_users? @relation("AssetBannedBy", fields: [banned_by], references: [id], onDelete: SetNull, onUpdate: SetNull)
777781
projects ow_projects_assets[] @relation("AssetProjects")
782+
post_media ow_posts_media[]
778783
779784
@@index([md5])
780785
@@index([uploader_id])
@@ -809,3 +814,132 @@ model ow_push_subscriptions {
809814
@@index([last_used_at])
810815
@@map("ow_push_subscriptions")
811816
}
817+
818+
// 推文类型枚举
819+
enum ow_post_type {
820+
normal
821+
reply
822+
retweet
823+
quote
824+
}
825+
826+
// 推文表 - Twitter-like posts (包含转推/引用/回复)
827+
model ow_posts {
828+
id Int @id @default(autoincrement())
829+
author_id Int
830+
post_type ow_post_type @default(normal)
831+
content String? @db.Text
832+
character_count Int @default(0) // 字符数(用于280字符限制检查)
833+
in_reply_to_id Int? // 回复指向
834+
thread_root_id Int? // 线程根推文
835+
quoted_post_id Int? // 引用推文指向
836+
retweet_post_id Int? // 转推指向
837+
reply_count Int @default(0)
838+
retweet_count Int @default(0)
839+
like_count Int @default(0)
840+
bookmark_count Int @default(0)
841+
is_deleted Boolean @default(false)
842+
embed Json? // 嵌入资源 { type: 'project'|'list'|'user', id, ...其他参数 }
843+
created_at DateTime @default(now()) @db.Timestamp(6)
844+
updated_at DateTime @updatedAt @db.Timestamp(6)
845+
metadata Json? // 存储额外的元数据
846+
847+
// 关联
848+
author ow_users? @relation("PostAuthor", fields: [author_id], references: [id], onDelete: Cascade, onUpdate: Cascade)
849+
in_reply_to ow_posts? @relation("PostReplies", fields: [in_reply_to_id], references: [id], onDelete: Restrict, onUpdate: Restrict)
850+
replies ow_posts[] @relation("PostReplies")
851+
thread_root ow_posts? @relation("PostThreads", fields: [thread_root_id], references: [id], onDelete: Restrict, onUpdate: Restrict)
852+
thread_posts ow_posts[] @relation("PostThreads")
853+
quoted_post ow_posts? @relation("PostQuotes", fields: [quoted_post_id], references: [id], onDelete: Restrict, onUpdate: Restrict)
854+
quoted_by ow_posts[] @relation("PostQuotes")
855+
retweet_post ow_posts? @relation("PostRetweets", fields: [retweet_post_id], references: [id], onDelete: Restrict, onUpdate: Restrict)
856+
retweeted_by ow_posts[] @relation("PostRetweets")
857+
likes ow_posts_like[]
858+
bookmarks ow_posts_bookmark[]
859+
post_media ow_posts_media[]
860+
mentions ow_posts_mention[]
861+
862+
@@index([author_id, created_at(sort: Desc)])
863+
@@index([created_at(sort: Desc)])
864+
@@index([in_reply_to_id])
865+
@@index([thread_root_id])
866+
@@index([quoted_post_id])
867+
@@index([retweet_post_id])
868+
@@index([post_type])
869+
@@index([is_deleted])
870+
@@index([is_deleted, created_at(sort: Desc)])
871+
@@index([thread_root_id, created_at(sort: Asc)])
872+
@@map("ow_posts")
873+
}
874+
875+
// 提及用户关联表 - Mentions
876+
model ow_posts_mention {
877+
id Int @id @default(autoincrement())
878+
post_id Int
879+
user_id Int
880+
created_at DateTime @default(now()) @db.Timestamp(6)
881+
notified Boolean @default(false)
882+
883+
// 关联
884+
post ow_posts @relation(fields: [post_id], references: [id], onDelete: Cascade, onUpdate: Cascade)
885+
user ow_users @relation("UserMentions", fields: [user_id], references: [id], onDelete: Cascade, onUpdate: Cascade)
886+
887+
@@unique([post_id, user_id])
888+
@@index([user_id, created_at(sort: Desc)])
889+
@@index([post_id])
890+
@@map("ow_posts_mention")
891+
}
892+
893+
// 点赞表 - Likes
894+
model ow_posts_like {
895+
id Int @id @default(autoincrement())
896+
user_id Int
897+
post_id Int
898+
created_at DateTime @default(now()) @db.Timestamp(6)
899+
900+
// 关联
901+
user ow_users? @relation("LikeAuthor", fields: [user_id], references: [id], onDelete: Cascade, onUpdate: Cascade)
902+
post ow_posts @relation(fields: [post_id], references: [id], onDelete: Cascade, onUpdate: Cascade)
903+
904+
@@unique([user_id, post_id])
905+
@@index([user_id, created_at(sort: Desc)])
906+
@@index([post_id])
907+
@@map("ow_posts_like")
908+
}
909+
910+
// 收藏表 - Bookmarks
911+
model ow_posts_bookmark {
912+
id Int @id @default(autoincrement())
913+
user_id Int
914+
post_id Int
915+
created_at DateTime @default(now()) @db.Timestamp(6)
916+
917+
// 关联
918+
user ow_users? @relation("BookmarkAuthor", fields: [user_id], references: [id], onDelete: Cascade, onUpdate: Cascade)
919+
post ow_posts @relation(fields: [post_id], references: [id], onDelete: Cascade, onUpdate: Cascade)
920+
921+
@@unique([user_id, post_id])
922+
@@index([user_id, created_at(sort: Desc)])
923+
@@index([post_id])
924+
@@map("ow_posts_bookmark")
925+
}
926+
927+
// 推文媒体关联表 - Post media
928+
model ow_posts_media {
929+
id Int @id @default(autoincrement())
930+
post_id Int
931+
asset_id Int?
932+
order Int @default(0) // 媒体顺序
933+
created_at DateTime @default(now()) @db.Timestamp(6)
934+
935+
// 关联
936+
post ow_posts @relation(fields: [post_id], references: [id], onDelete: Cascade, onUpdate: Cascade)
937+
asset ow_assets? @relation(fields: [asset_id], references: [id], onDelete: SetNull, onUpdate: SetNull)
938+
939+
@@unique([post_id, asset_id])
940+
@@unique([post_id, order])
941+
@@index([post_id])
942+
@@index([asset_id])
943+
@@map("ow_posts_media")
944+
}
945+

src/config/notificationTemplates.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,5 +105,40 @@
105105
"custom_title",
106106
"custom_content"
107107
]
108+
},
109+
"post_mention": {
110+
"title": "推文提及",
111+
"template": "{{actor_name}} 在推文中提到了你",
112+
"icon": "mention",
113+
"requiresActor": true,
114+
"requiresData": []
115+
},
116+
"post_reply": {
117+
"title": "推文回复",
118+
"template": "{{actor_name}} 回复了你的推文",
119+
"icon": "reply",
120+
"requiresActor": true,
121+
"requiresData": []
122+
},
123+
"post_retweet": {
124+
"title": "推文转推",
125+
"template": "{{actor_name}} 转推了你的推文",
126+
"icon": "retweet",
127+
"requiresActor": true,
128+
"requiresData": []
129+
},
130+
"post_quote": {
131+
"title": "推文引用",
132+
"template": "{{actor_name}} 引用了你的推文",
133+
"icon": "quote",
134+
"requiresActor": true,
135+
"requiresData": []
136+
},
137+
"post_like": {
138+
"title": "推文点赞",
139+
"template": "{{actor_name}} 点赞了你的推文",
140+
"icon": "like",
141+
"requiresActor": true,
142+
"requiresData": []
108143
}
109144
}

src/controllers/auth/loginController.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -676,7 +676,7 @@ export const validateMagicLinkAndLogin = async (req, res) => {
676676
export const logout = async (req, res) => {
677677
try {
678678
// 使用中间件验证后的用户信息
679-
const result = await authUtils.revokeToken(req.user.token_id);
679+
const result = await authUtils.revokeToken(res.locals.tokeninfo.token_id);
680680

681681
if (result.success) {
682682
return res.status(200).json({

0 commit comments

Comments
 (0)