Skip to content

Commit e4d7e25

Browse files
committed
feat: Add latest APIs for announcements
1 parent 9571ee9 commit e4d7e25

7 files changed

Lines changed: 455 additions & 30 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
CREATE INDEX `announcement_tags_tag_id_idx` ON `announcement_tags` (`tag_id`);
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
{
2+
"version": "6",
3+
"dialect": "sqlite",
4+
"id": "c2075a4c-fcbd-4300-a6da-c708350bdf9f",
5+
"prevId": "7d6e4138-b731-4824-828d-3a9def6e38cb",
6+
"tables": {
7+
"announcement_tags": {
8+
"name": "announcement_tags",
9+
"columns": {
10+
"announcement_id": {
11+
"name": "announcement_id",
12+
"type": "integer",
13+
"primaryKey": false,
14+
"notNull": true,
15+
"autoincrement": false
16+
},
17+
"tag_id": {
18+
"name": "tag_id",
19+
"type": "integer",
20+
"primaryKey": false,
21+
"notNull": true,
22+
"autoincrement": false
23+
}
24+
},
25+
"indexes": {
26+
"announcement_tags_tag_id_idx": {
27+
"name": "announcement_tags_tag_id_idx",
28+
"columns": [
29+
"tag_id"
30+
],
31+
"isUnique": false
32+
}
33+
},
34+
"foreignKeys": {
35+
"announcement_tags_announcement_id_announcements_id_fk": {
36+
"name": "announcement_tags_announcement_id_announcements_id_fk",
37+
"tableFrom": "announcement_tags",
38+
"tableTo": "announcements",
39+
"columnsFrom": [
40+
"announcement_id"
41+
],
42+
"columnsTo": [
43+
"id"
44+
],
45+
"onDelete": "cascade",
46+
"onUpdate": "no action"
47+
},
48+
"announcement_tags_tag_id_tags_id_fk": {
49+
"name": "announcement_tags_tag_id_tags_id_fk",
50+
"tableFrom": "announcement_tags",
51+
"tableTo": "tags",
52+
"columnsFrom": [
53+
"tag_id"
54+
],
55+
"columnsTo": [
56+
"id"
57+
],
58+
"onDelete": "cascade",
59+
"onUpdate": "no action"
60+
}
61+
},
62+
"compositePrimaryKeys": {
63+
"announcement_tags_announcement_id_tag_id_pk": {
64+
"columns": [
65+
"announcement_id",
66+
"tag_id"
67+
],
68+
"name": "announcement_tags_announcement_id_tag_id_pk"
69+
}
70+
},
71+
"uniqueConstraints": {},
72+
"checkConstraints": {}
73+
},
74+
"announcements": {
75+
"name": "announcements",
76+
"columns": {
77+
"id": {
78+
"name": "id",
79+
"type": "integer",
80+
"primaryKey": true,
81+
"notNull": true,
82+
"autoincrement": true
83+
},
84+
"author": {
85+
"name": "author",
86+
"type": "text",
87+
"primaryKey": false,
88+
"notNull": false,
89+
"autoincrement": false
90+
},
91+
"title": {
92+
"name": "title",
93+
"type": "text",
94+
"primaryKey": false,
95+
"notNull": true,
96+
"autoincrement": false
97+
},
98+
"content": {
99+
"name": "content",
100+
"type": "text",
101+
"primaryKey": false,
102+
"notNull": false,
103+
"autoincrement": false
104+
},
105+
"created_at": {
106+
"name": "created_at",
107+
"type": "text",
108+
"primaryKey": false,
109+
"notNull": true,
110+
"autoincrement": false
111+
},
112+
"archived_at": {
113+
"name": "archived_at",
114+
"type": "text",
115+
"primaryKey": false,
116+
"notNull": false,
117+
"autoincrement": false
118+
},
119+
"level": {
120+
"name": "level",
121+
"type": "integer",
122+
"primaryKey": false,
123+
"notNull": true,
124+
"autoincrement": false,
125+
"default": 0
126+
}
127+
},
128+
"indexes": {},
129+
"foreignKeys": {},
130+
"compositePrimaryKeys": {},
131+
"uniqueConstraints": {},
132+
"checkConstraints": {}
133+
},
134+
"tags": {
135+
"name": "tags",
136+
"columns": {
137+
"id": {
138+
"name": "id",
139+
"type": "integer",
140+
"primaryKey": true,
141+
"notNull": true,
142+
"autoincrement": true
143+
},
144+
"name": {
145+
"name": "name",
146+
"type": "text",
147+
"primaryKey": false,
148+
"notNull": true,
149+
"autoincrement": false
150+
}
151+
},
152+
"indexes": {
153+
"tags_name_unique": {
154+
"name": "tags_name_unique",
155+
"columns": [
156+
"name"
157+
],
158+
"isUnique": true
159+
}
160+
},
161+
"foreignKeys": {},
162+
"compositePrimaryKeys": {},
163+
"uniqueConstraints": {},
164+
"checkConstraints": {}
165+
}
166+
},
167+
"views": {},
168+
"enums": {},
169+
"_meta": {
170+
"schemas": {},
171+
"tables": {},
172+
"columns": {}
173+
},
174+
"internal": {
175+
"indexes": {}
176+
}
177+
}

drizzle/migrations/meta/_journal.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@
1515
"when": 1773076587551,
1616
"tag": "0001_announcement_tags",
1717
"breakpoints": true
18+
},
19+
{
20+
"idx": 2,
21+
"version": "6",
22+
"when": 1773666521974,
23+
"tag": "0002_latest_announcements",
24+
"breakpoints": true
1825
}
1926
]
2027
}

src/db/schema.ts

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { sqliteTable, integer, text, primaryKey } from "drizzle-orm/sqlite-core";
1+
import {
2+
index,
3+
integer,
4+
primaryKey,
5+
sqliteTable,
6+
text,
7+
} from "drizzle-orm/sqlite-core";
28

39
export const announcements = sqliteTable("announcements", {
410
id: integer("id").primaryKey({ autoIncrement: true }),
@@ -13,21 +19,22 @@ export const announcements = sqliteTable("announcements", {
1319
});
1420

1521
export const tags = sqliteTable("tags", {
16-
id: integer("id").primaryKey({ autoIncrement: true }),
17-
name: text("name").notNull().unique(),
22+
id: integer("id").primaryKey({ autoIncrement: true }),
23+
name: text("name").notNull().unique(),
1824
});
1925

2026
export const announcementTags = sqliteTable(
21-
"announcement_tags",
22-
{
23-
announcementId: integer("announcement_id")
24-
.notNull()
25-
.references(() => announcements.id, { onDelete: "cascade" }),
26-
tagId: integer("tag_id")
27-
.notNull()
28-
.references(() => tags.id, { onDelete: "cascade" }),
29-
},
30-
(table) => ({
31-
pk: primaryKey({ columns: [table.announcementId, table.tagId] }),
32-
}),
27+
"announcement_tags",
28+
{
29+
announcementId: integer("announcement_id")
30+
.notNull()
31+
.references(() => announcements.id, { onDelete: "cascade" }),
32+
tagId: integer("tag_id")
33+
.notNull()
34+
.references(() => tags.id, { onDelete: "cascade" }),
35+
},
36+
(table) => ({
37+
pk: primaryKey({ columns: [table.announcementId, table.tagId] }),
38+
tagIdIdx: index("announcement_tags_tag_id_idx").on(table.tagId),
39+
}),
3340
);

src/routes/announcements.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
AnnouncementResponseSchema,
88
AnnouncementsResponseSchema,
99
CreateAnnouncementBodySchema,
10+
LatestAnnouncementIdsResponseSchema,
11+
LatestAnnouncementsResponseSchema,
1012
UpdateAnnouncementBodySchema,
1113
} from "../schemas/announcements";
1214
import * as announcementsService from "../services/announcements";
@@ -34,6 +36,56 @@ app.openapi(
3436
},
3537
);
3638

39+
app.openapi(
40+
createRoute({
41+
method: "get",
42+
path: "/latest",
43+
tags: ["Announcements"],
44+
summary: "Get the latest announcement for each tag",
45+
description:
46+
"Get the newest announcement for every available announcement tag.",
47+
responses: {
48+
200: {
49+
content: {
50+
"application/json": { schema: LatestAnnouncementsResponseSchema },
51+
},
52+
description: "The newest announcement for each tag.",
53+
},
54+
},
55+
}),
56+
async (c) => {
57+
return c.json(
58+
await announcementsService.getLatestAnnouncementsByTag(c.env),
59+
200,
60+
);
61+
},
62+
);
63+
64+
app.openapi(
65+
createRoute({
66+
method: "get",
67+
path: "/latest/id",
68+
tags: ["Announcements"],
69+
summary: "Get the latest announcement ID for each tag",
70+
description:
71+
"Get the ID of the newest announcement for every available announcement tag.",
72+
responses: {
73+
200: {
74+
content: {
75+
"application/json": { schema: LatestAnnouncementIdsResponseSchema },
76+
},
77+
description: "The newest announcement ID for each tag.",
78+
},
79+
},
80+
}),
81+
async (c) => {
82+
return c.json(
83+
await announcementsService.getLatestAnnouncementIdsByTag(c.env),
84+
200,
85+
);
86+
},
87+
);
88+
3789
app.openapi(
3890
createRoute({
3991
method: "post",

src/schemas/announcements.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,28 @@ export const AnnouncementResponseSchema = z
2828

2929
export const AnnouncementsResponseSchema = z.array(AnnouncementResponseSchema);
3030

31+
export const LatestAnnouncementEntrySchema = z
32+
.object({
33+
tag: z.string().nullable().openapi({ example: "Important" }),
34+
announcement: AnnouncementResponseSchema,
35+
})
36+
.openapi("LatestAnnouncementEntry");
37+
38+
export const LatestAnnouncementsResponseSchema = z
39+
.array(LatestAnnouncementEntrySchema)
40+
.openapi("LatestAnnouncementsByTag");
41+
42+
export const LatestAnnouncementIdEntrySchema = z
43+
.object({
44+
tag: z.string().nullable().openapi({ example: "Important" }),
45+
id: z.number().int().openapi({ example: 1 }),
46+
})
47+
.openapi("LatestAnnouncementIdEntry");
48+
49+
export const LatestAnnouncementIdsResponseSchema = z
50+
.array(LatestAnnouncementIdEntrySchema)
51+
.openapi("LatestAnnouncementIdsByTag");
52+
3153
export const CreateAnnouncementBodySchema = z
3254
.object({
3355
author: z.string().optional().openapi({ example: "ReVanced" }),

0 commit comments

Comments
 (0)