Skip to content

Commit 5e53f7e

Browse files
Refactor post classification from Category to Tag
1 parent 1adaa50 commit 5e53f7e

10 files changed

Lines changed: 292 additions & 259 deletions

File tree

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
Warnings:
3+
4+
- You are about to drop the `categories` table. If the table is not empty, all the data it contains will be lost.
5+
- You are about to drop the `categories_on_posts` table. If the table is not empty, all the data it contains will be lost.
6+
- You are about to drop the `votes_on_posts` table. If the table is not empty, all the data it contains will be lost.
7+
8+
*/
9+
-- DropForeignKey
10+
ALTER TABLE "public"."categories_on_posts" DROP CONSTRAINT "categories_on_posts_name_fkey";
11+
12+
-- DropForeignKey
13+
ALTER TABLE "public"."categories_on_posts" DROP CONSTRAINT "categories_on_posts_post_id_fkey";
14+
15+
-- DropForeignKey
16+
ALTER TABLE "public"."votes_on_posts" DROP CONSTRAINT "votes_on_posts_post_id_fkey";
17+
18+
-- DropForeignKey
19+
ALTER TABLE "public"."votes_on_posts" DROP CONSTRAINT "votes_on_posts_user_id_fkey";
20+
21+
-- DropTable
22+
DROP TABLE "public"."categories";
23+
24+
-- DropTable
25+
DROP TABLE "public"."categories_on_posts";
26+
27+
-- DropTable
28+
DROP TABLE "public"."votes_on_posts";
29+
30+
-- CreateTable
31+
CREATE TABLE "public"."tags" (
32+
"order" SERIAL NOT NULL,
33+
"name" TEXT NOT NULL
34+
);
35+
36+
-- CreateTable
37+
CREATE TABLE "public"."posts_tags" (
38+
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
39+
"post_id" UUID NOT NULL,
40+
"name" TEXT NOT NULL,
41+
42+
CONSTRAINT "posts_tags_pkey" PRIMARY KEY ("id")
43+
);
44+
45+
-- CreateTable
46+
CREATE TABLE "public"."posts_votes" (
47+
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
48+
"order" SERIAL NOT NULL,
49+
"user_id" UUID NOT NULL,
50+
"post_id" UUID NOT NULL,
51+
"is_upvote" BOOLEAN NOT NULL DEFAULT true,
52+
53+
CONSTRAINT "posts_votes_pkey" PRIMARY KEY ("id")
54+
);
55+
56+
-- CreateIndex
57+
CREATE UNIQUE INDEX "tags_order_key" ON "public"."tags"("order");
58+
59+
-- CreateIndex
60+
CREATE UNIQUE INDEX "tags_name_key" ON "public"."tags"("name");
61+
62+
-- CreateIndex
63+
CREATE UNIQUE INDEX "posts_tags_post_id_name_key" ON "public"."posts_tags"("post_id", "name");
64+
65+
-- CreateIndex
66+
CREATE UNIQUE INDEX "posts_votes_order_key" ON "public"."posts_votes"("order");
67+
68+
-- CreateIndex
69+
CREATE UNIQUE INDEX "posts_votes_user_id_post_id_key" ON "public"."posts_votes"("user_id", "post_id");
70+
71+
-- AddForeignKey
72+
ALTER TABLE "public"."posts_tags" ADD CONSTRAINT "posts_tags_post_id_fkey" FOREIGN KEY ("post_id") REFERENCES "public"."posts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
73+
74+
-- AddForeignKey
75+
ALTER TABLE "public"."posts_tags" ADD CONSTRAINT "posts_tags_name_fkey" FOREIGN KEY ("name") REFERENCES "public"."tags"("name") ON DELETE CASCADE ON UPDATE CASCADE;
76+
77+
-- AddForeignKey
78+
ALTER TABLE "public"."posts_votes" ADD CONSTRAINT "posts_votes_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
79+
80+
-- AddForeignKey
81+
ALTER TABLE "public"."posts_votes" ADD CONSTRAINT "posts_votes_post_id_fkey" FOREIGN KEY ("post_id") REFERENCES "public"."posts"("id") ON DELETE CASCADE ON UPDATE CASCADE;

prisma/schema.prisma

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -41,41 +41,41 @@ model Comment {
4141
}
4242

4343
model Post {
44-
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
45-
order Int @unique @default(autoincrement())
46-
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
47-
authorId String @map("author_id") @db.Uuid
48-
published Boolean @default(false)
49-
createdAt DateTime @default(now()) @map("created_at")
50-
updatedAt DateTime @updatedAt @map("updated_at")
51-
categories CategoryOnPost[]
52-
votes VoteOnPost[]
53-
comments Comment[]
54-
content String
55-
title String
56-
image Image? @relation(fields: [imageId], references: [id])
57-
imageId String? @map("image_id") @db.Uuid
44+
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
45+
order Int @unique @default(autoincrement())
46+
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
47+
authorId String @map("author_id") @db.Uuid
48+
published Boolean @default(false)
49+
createdAt DateTime @default(now()) @map("created_at")
50+
updatedAt DateTime @updatedAt @map("updated_at")
51+
tags TagsOnPosts[]
52+
votes VoteOnPost[]
53+
comments Comment[]
54+
content String
55+
title String
56+
image Image? @relation(fields: [imageId], references: [id])
57+
imageId String? @map("image_id") @db.Uuid
5858
5959
@@map("posts")
6060
}
6161

62-
model Category {
63-
order Int @unique @default(autoincrement())
64-
name String @unique
65-
posts CategoryOnPost[]
62+
model Tag {
63+
order Int @unique @default(autoincrement())
64+
name String @unique
65+
posts TagsOnPosts[]
6666
67-
@@map("categories")
67+
@@map("tags")
6868
}
6969

70-
model CategoryOnPost {
71-
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
72-
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
73-
postId String @map("post_id") @db.Uuid
74-
category Category @relation(fields: [name], references: [name], onDelete: Cascade)
75-
name String
70+
model TagsOnPosts {
71+
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
72+
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
73+
postId String @map("post_id") @db.Uuid
74+
tag Tag @relation(fields: [name], references: [name], onDelete: Cascade)
75+
name String
7676
7777
@@unique([postId, name])
78-
@@map("categories_on_posts")
78+
@@map("posts_tags")
7979
}
8080

8181
model VoteOnPost {
@@ -88,7 +88,7 @@ model VoteOnPost {
8888
isUpvote Boolean @default(true) @map("is_upvote")
8989
9090
@@unique([userId, postId])
91-
@@map("votes_on_posts")
91+
@@map("posts_votes")
9292
}
9393

9494
model Image {

prisma/seed/seed.ts

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,27 +25,27 @@ const titles = [
2525
const postCount = titles.length;
2626

2727
const categories = [
28-
'Open Source',
29-
'Full Stack',
30-
'JavaScript',
31-
'TypeScript',
32-
'Security',
33-
'Frontend',
34-
'Software',
35-
'Testing',
36-
'Backend',
28+
'open_source',
29+
'full_stack',
30+
'javaScript',
31+
'typeScript',
32+
'security',
33+
'frontend',
34+
'software',
35+
'testing',
36+
'backend',
3737
];
3838

3939
export async function seed() {
4040
console.log('Resetting the database...');
4141
await db.$transaction([
4242
db.comment.deleteMany({}),
4343
db.voteOnPost.deleteMany({}),
44-
db.categoryOnPost.deleteMany({}),
45-
db.category.deleteMany({}),
44+
db.tagsOnPosts.deleteMany({}),
4645
db.post.deleteMany({}),
4746
db.image.deleteMany({}),
4847
db.user.deleteMany({}),
48+
db.tag.deleteMany({}),
4949
]);
5050

5151
console.log('Creating the author account...');
@@ -115,11 +115,9 @@ export async function seed() {
115115
imageId: dbImages[i].id,
116116
authorId: dbPostAuthor.id,
117117
content: faker.lorem.paragraphs(faker.number.int({ min: 10, max: 15 })),
118-
categories: {
118+
tags: {
119119
create: randomCategories.map((name) => ({
120-
category: {
121-
connectOrCreate: { where: { name }, create: { name } },
122-
},
120+
tag: { connectOrCreate: { where: { name }, create: { name } } },
123121
})),
124122
},
125123
},

src/api/v1/posts/post.schema.ts

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,17 @@ export const publishedSchema = z.boolean(
3333
getRequiredAndTypeErrors('Published flag', 'boolean', true)
3434
);
3535

36-
export const categorySchema = z
37-
.string(getRequiredAndTypeErrors('Category'))
38-
.trim();
36+
export const tagSchema = z
37+
.string(getRequiredAndTypeErrors('Tag'))
38+
.trim()
39+
.regex(/^[^\s]*$/, { message: 'A tag cannot have spaces' });
3940

40-
export const categoriesSchema = z
41-
.array(categorySchema)
42-
.max(7)
43-
.transform((categories) => {
41+
export const tagsSchema = z
42+
.array(tagSchema)
43+
.max(7, { message: 'Expect maximum of 7 tags' })
44+
.transform((tags) => {
4445
return Array.from(
45-
new Set(
46-
categories
47-
.filter((c) => !!c)
48-
.map((c) => `${c[0].toUpperCase()}${c.slice(1).toLowerCase()}`)
49-
)
46+
new Set(tags.filter((c) => !!c).map((c) => c.toLowerCase()))
5047
);
5148
});
5249

@@ -57,14 +54,10 @@ export const commentSchema = z.object({
5754
.nonempty('A comment must have content'),
5855
});
5956

60-
export const postSchema = z
61-
.object({
62-
title: titleSchema,
63-
image: imageSchema,
64-
content: contentSchema,
65-
published: publishedSchema.optional(),
66-
categories: categoriesSchema.optional(),
67-
})
68-
.transform((data) => {
69-
return { ...data, categories: data.categories ?? [] };
70-
});
57+
export const postSchema = z.object({
58+
title: titleSchema,
59+
image: imageSchema,
60+
content: contentSchema,
61+
tags: tagsSchema.default([]),
62+
published: publishedSchema.optional(),
63+
});

src/api/v1/posts/posts.router.ts

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ postsRouter.get(
5454
}
5555
);
5656

57-
postsRouter.get('/categories', async (req, res) => {
58-
const categoriesFilter = Utils.getCategoriesFilterFromReqQuery(req);
59-
res.json(await Service.getCategories(categoriesFilter));
57+
postsRouter.get('/tags', async (req, res) => {
58+
const tagsFilter = Utils.getTagsFilterFromReqQuery(req);
59+
res.json(await Service.getTags(tagsFilter));
6060
});
6161

6262
postsRouter.get(
@@ -77,17 +77,11 @@ postsRouter.get(
7777
}
7878
);
7979

80-
postsRouter.get(
81-
'/categories/count',
82-
Validators.authValidator,
83-
async (req, res) => {
84-
const user = req.user as Types.PublicUser;
85-
const categoriesCount = await Service.countPostsCategoriesByPostsAuthorId(
86-
user.id
87-
);
88-
res.json(categoriesCount);
89-
}
90-
);
80+
postsRouter.get('/tags/count', Validators.authValidator, async (req, res) => {
81+
const user = req.user as Types.PublicUser;
82+
const tagsCount = await Service.countPostsTagsByPostsAuthorId(user.id);
83+
res.json(tagsCount);
84+
});
9185

9286
postsRouter.get(
9387
'/comments/count',
@@ -118,8 +112,8 @@ postsRouter.get(
118112
);
119113

120114
postsRouter.get(
121-
'/:id/categories',
122-
createHandlersForGettingPrivatePostData(Service.findPostCategories)
115+
'/:id/tags',
116+
createHandlersForGettingPrivatePostData(Service.findPostTags)
123117
);
124118

125119
postsRouter.get(
@@ -145,8 +139,8 @@ postsRouter.get(
145139
);
146140

147141
postsRouter.get(
148-
'/:id/categories/count',
149-
createHandlersForGettingPrivatePostData(Service.countPostCategories)
142+
'/:id/tags/count',
143+
createHandlersForGettingPrivatePostData(Service.countPostTags)
150144
);
151145

152146
postsRouter.get(

0 commit comments

Comments
 (0)