|
1 | 1 | import type { PresignedPost } from "@aws-sdk/s3-presigned-post" |
2 | 2 | import { ArticleFilterQuerySchema, ArticleSchema, ArticleTagSchema, ArticleWriteSchema } from "@dotkomonline/types" |
| 3 | +import type { inferProcedureInput, inferProcedureOutput } from "@trpc/server" |
3 | 4 | import { z } from "zod" |
4 | 5 | import { isEditor } from "../../authorization" |
5 | 6 | import { withAuditLogEntry, withAuthentication, withAuthorization, withDatabaseTransaction } from "../../middlewares" |
6 | 7 | import { BasePaginateInputSchema, PaginateInputSchema } from "../../query" |
7 | | -import { procedure, staffProcedure, t } from "../../trpc" |
| 8 | +import { procedure, t } from "../../trpc" |
8 | 9 |
|
9 | | -export const articleRouter = t.router({ |
10 | | - create: procedure |
11 | | - .input( |
12 | | - z.object({ |
13 | | - article: ArticleWriteSchema, |
14 | | - tags: z.array(ArticleTagSchema.shape.name), |
15 | | - }) |
16 | | - ) |
17 | | - .use(withAuthentication()) |
18 | | - .use(withAuthorization(isEditor())) |
19 | | - .use(withDatabaseTransaction()) |
20 | | - .use(withAuditLogEntry()) |
21 | | - .mutation(async ({ input, ctx }) => { |
22 | | - const article = await ctx.articleService.create(ctx.handle, input.article) |
23 | | - const tags = await ctx.articleService.setTags(ctx.handle, article.id, input.tags) |
24 | | - return { |
25 | | - ...article, |
26 | | - tags, |
27 | | - } |
28 | | - }), |
| 10 | +export type CreateArticleInput = inferProcedureInput<typeof createArticleProcedure> |
| 11 | +export type CreateArticleOutput = inferProcedureOutput<typeof createArticleProcedure> |
| 12 | +const createArticleProcedure = procedure |
| 13 | + .input( |
| 14 | + z.object({ |
| 15 | + article: ArticleWriteSchema, |
| 16 | + tags: z.array(ArticleTagSchema.shape.name), |
| 17 | + }) |
| 18 | + ) |
| 19 | + .use(withAuthentication()) |
| 20 | + .use(withAuthorization(isEditor())) |
| 21 | + .use(withDatabaseTransaction()) |
| 22 | + .use(withAuditLogEntry()) |
| 23 | + .mutation(async ({ input, ctx }) => { |
| 24 | + const article = await ctx.articleService.create(ctx.handle, input.article) |
| 25 | + const tags = await ctx.articleService.setTags(ctx.handle, article.id, input.tags) |
| 26 | + return { |
| 27 | + ...article, |
| 28 | + tags, |
| 29 | + } |
| 30 | + }) |
29 | 31 |
|
30 | | - edit: staffProcedure |
31 | | - .input( |
32 | | - z.object({ |
33 | | - id: ArticleSchema.shape.id, |
34 | | - input: ArticleWriteSchema.partial(), |
35 | | - tags: z.array(ArticleTagSchema.shape.name), |
36 | | - }) |
37 | | - ) |
38 | | - .mutation(async ({ input, ctx }) => { |
39 | | - return ctx.executeAuditedTransaction(async (handle) => { |
40 | | - const article = await ctx.articleService.update(handle, input.id, input.input) |
41 | | - const tags = await ctx.articleService.setTags(handle, input.id, input.tags) |
42 | | - return { ...article, tags } |
43 | | - }) |
44 | | - }), |
| 32 | +export type EditArticleInput = inferProcedureInput<typeof editArticleProcedure> |
| 33 | +export type EditArticleOutput = inferProcedureOutput<typeof editArticleProcedure> |
| 34 | +const editArticleProcedure = procedure |
| 35 | + .input( |
| 36 | + z.object({ |
| 37 | + id: ArticleSchema.shape.id, |
| 38 | + input: ArticleWriteSchema.partial(), |
| 39 | + tags: z.array(ArticleTagSchema.shape.name), |
| 40 | + }) |
| 41 | + ) |
| 42 | + .use(withAuthentication()) |
| 43 | + .use(withAuthorization(isEditor())) |
| 44 | + .use(withDatabaseTransaction()) |
| 45 | + .use(withAuditLogEntry()) |
| 46 | + .mutation(async ({ input, ctx }) => { |
| 47 | + const article = await ctx.articleService.update(ctx.handle, input.id, input.input) |
| 48 | + const tags = await ctx.articleService.setTags(ctx.handle, input.id, input.tags) |
| 49 | + return { ...article, tags } |
| 50 | + }) |
45 | 51 |
|
46 | | - all: procedure |
47 | | - .input(PaginateInputSchema) |
48 | | - .query(async ({ input, ctx }) => |
49 | | - ctx.executeTransaction(async (handle) => ctx.articleService.findMany(handle, {}, input)) |
50 | | - ), |
| 52 | +export type AllArticlesInput = inferProcedureInput<typeof allArticlesProcedure> |
| 53 | +export type AllArticlesOutput = inferProcedureOutput<typeof allArticlesProcedure> |
| 54 | +const allArticlesProcedure = procedure |
| 55 | + .input(PaginateInputSchema) |
| 56 | + .use(withDatabaseTransaction()) |
| 57 | + .query(async ({ input, ctx }) => ctx.articleService.findMany(ctx.handle, {}, input)) |
51 | 58 |
|
52 | | - findArticles: procedure |
53 | | - .input(BasePaginateInputSchema.extend({ filters: ArticleFilterQuerySchema })) |
54 | | - .query(async ({ input, ctx }) => { |
55 | | - const items = await ctx.executeTransaction(async (handle) => |
56 | | - ctx.articleService.findMany(handle, input.filters, input) |
57 | | - ) |
| 59 | +export type FindArticlesInput = inferProcedureInput<typeof findArticlesProcedure> |
| 60 | +export type FindArticlesOutput = inferProcedureOutput<typeof findArticlesProcedure> |
| 61 | +const findArticlesProcedure = procedure |
| 62 | + .input(BasePaginateInputSchema.extend({ filters: ArticleFilterQuerySchema })) |
| 63 | + .use(withDatabaseTransaction()) |
| 64 | + .query(async ({ input, ctx }) => { |
| 65 | + const items = await ctx.articleService.findMany(ctx.handle, input.filters, input) |
58 | 66 |
|
59 | | - return { |
60 | | - items, |
61 | | - nextCursor: items.at(-1)?.id, |
62 | | - } |
63 | | - }), |
| 67 | + return { |
| 68 | + items, |
| 69 | + nextCursor: items.at(-1)?.id, |
| 70 | + } |
| 71 | + }) |
64 | 72 |
|
65 | | - find: procedure |
66 | | - .input(ArticleSchema.shape.id) |
67 | | - .query(async ({ input, ctx }) => |
68 | | - ctx.executeTransaction(async (handle) => ctx.articleService.findById(handle, input)) |
69 | | - ), |
| 73 | +export type FindArticleInput = inferProcedureInput<typeof findArticleProcedure> |
| 74 | +export type FindArticleOutput = inferProcedureOutput<typeof findArticleProcedure> |
| 75 | +const findArticleProcedure = procedure |
| 76 | + .input(ArticleSchema.shape.id) |
| 77 | + .use(withDatabaseTransaction()) |
| 78 | + .query(async ({ input, ctx }) => ctx.articleService.findById(ctx.handle, input)) |
70 | 79 |
|
71 | | - get: procedure |
72 | | - .input(ArticleSchema.shape.id) |
73 | | - .query(async ({ input, ctx }) => |
74 | | - ctx.executeTransaction(async (handle) => ctx.articleService.getById(handle, input)) |
75 | | - ), |
| 80 | +export type GetArticleInput = inferProcedureInput<typeof getArticleProcedure> |
| 81 | +export type GetArticleOutput = inferProcedureOutput<typeof getArticleProcedure> |
| 82 | +const getArticleProcedure = procedure |
| 83 | + .input(ArticleSchema.shape.id) |
| 84 | + .use(withDatabaseTransaction()) |
| 85 | + .query(async ({ input, ctx }) => ctx.articleService.getById(ctx.handle, input)) |
76 | 86 |
|
77 | | - related: procedure |
78 | | - .input(ArticleSchema) |
79 | | - .query(async ({ input, ctx }) => |
80 | | - ctx.executeTransaction(async (handle) => ctx.articleService.findRelated(handle, input)) |
81 | | - ), |
| 87 | +export type FindRelatedArticlesInput = inferProcedureInput<typeof findRelatedArticlesProcedure> |
| 88 | +export type FindRelatedArticlesOutput = inferProcedureOutput<typeof findRelatedArticlesProcedure> |
| 89 | +const findRelatedArticlesProcedure = procedure |
| 90 | + .input(ArticleSchema) |
| 91 | + .use(withDatabaseTransaction()) |
| 92 | + .query(async ({ input, ctx }) => ctx.articleService.findRelated(ctx.handle, input)) |
82 | 93 |
|
83 | | - featured: procedure.query(async ({ ctx }) => |
84 | | - ctx.executeTransaction(async (handle) => ctx.articleService.findFeatured(handle)) |
85 | | - ), |
| 94 | +export type FindFeaturedArticlesInput = inferProcedureInput<typeof findFeaturedArticlesProcedure> |
| 95 | +export type FindFeaturedArticlesOutput = inferProcedureOutput<typeof findFeaturedArticlesProcedure> |
| 96 | +const findFeaturedArticlesProcedure = procedure |
| 97 | + .use(withDatabaseTransaction()) |
| 98 | + .query(async ({ ctx }) => ctx.articleService.findFeatured(ctx.handle)) |
86 | 99 |
|
87 | | - getTags: procedure.query(async ({ ctx }) => |
88 | | - ctx.executeTransaction(async (handle) => ctx.articleService.getTags(handle)) |
89 | | - ), |
| 100 | +export type GetArticleTagsInput = inferProcedureInput<typeof getArticleTagsProcedure> |
| 101 | +export type GetArticleTagsOutput = inferProcedureOutput<typeof getArticleTagsProcedure> |
| 102 | +const getArticleTagsProcedure = procedure |
| 103 | + .use(withDatabaseTransaction()) |
| 104 | + .query(async ({ ctx }) => ctx.articleService.getTags(ctx.handle)) |
90 | 105 |
|
91 | | - findTagsOrderedByPopularity: procedure.query(async ({ ctx }) => |
92 | | - ctx.executeTransaction(async (handle) => ctx.articleService.findTagsOrderedByPopularity(handle)) |
93 | | - ), |
| 106 | +export type FindArticleTagsOrderedByPopularityInput = inferProcedureInput< |
| 107 | + typeof findArticleTagsOrderedByPopularityProcedure |
| 108 | +> |
| 109 | +export type FindArticleTagsOrderedByPopularityOutput = inferProcedureOutput< |
| 110 | + typeof findArticleTagsOrderedByPopularityProcedure |
| 111 | +> |
| 112 | +const findArticleTagsOrderedByPopularityProcedure = procedure |
| 113 | + .use(withDatabaseTransaction()) |
| 114 | + .query(async ({ ctx }) => ctx.articleService.findTagsOrderedByPopularity(ctx.handle)) |
94 | 115 |
|
95 | | - addTag: staffProcedure |
96 | | - .input( |
97 | | - z.object({ |
98 | | - id: ArticleSchema.shape.id, |
99 | | - tag: ArticleTagSchema.shape.name, |
100 | | - }) |
101 | | - ) |
102 | | - .mutation(async ({ input, ctx }) => { |
103 | | - return ctx.executeAuditedTransaction(async (handle) => ctx.articleService.addTag(handle, input.id, input.tag)) |
104 | | - }), |
| 116 | +export type AddArticleTagInput = inferProcedureInput<typeof addArticleTagProcedure> |
| 117 | +export type AddArticleTagOutput = inferProcedureOutput<typeof addArticleTagProcedure> |
| 118 | +const addArticleTagProcedure = procedure |
| 119 | + .input( |
| 120 | + z.object({ |
| 121 | + id: ArticleSchema.shape.id, |
| 122 | + tag: ArticleTagSchema.shape.name, |
| 123 | + }) |
| 124 | + ) |
| 125 | + .use(withAuthentication()) |
| 126 | + .use(withAuthorization(isEditor())) |
| 127 | + .use(withDatabaseTransaction()) |
| 128 | + .use(withAuditLogEntry()) |
| 129 | + .mutation(async ({ input, ctx }) => { |
| 130 | + return ctx.articleService.addTag(ctx.handle, input.id, input.tag) |
| 131 | + }) |
105 | 132 |
|
106 | | - removeTag: staffProcedure |
107 | | - .input( |
108 | | - z.object({ |
109 | | - id: ArticleSchema.shape.id, |
110 | | - tag: ArticleTagSchema.shape.name, |
111 | | - }) |
112 | | - ) |
113 | | - .mutation(async ({ input, ctx }) => { |
114 | | - return ctx.executeAuditedTransaction(async (handle) => ctx.articleService.removeTag(handle, input.id, input.tag)) |
115 | | - }), |
| 133 | +export type RemoveArticleTagInput = inferProcedureInput<typeof removeArticleTagProcedure> |
| 134 | +export type RemoveArticleTagOutput = inferProcedureOutput<typeof removeArticleTagProcedure> |
| 135 | +const removeArticleTagProcedure = procedure |
| 136 | + .input( |
| 137 | + z.object({ |
| 138 | + id: ArticleSchema.shape.id, |
| 139 | + tag: ArticleTagSchema.shape.name, |
| 140 | + }) |
| 141 | + ) |
| 142 | + .use(withAuthentication()) |
| 143 | + .use(withAuthorization(isEditor())) |
| 144 | + .use(withDatabaseTransaction()) |
| 145 | + .use(withAuditLogEntry()) |
| 146 | + .mutation(async ({ input, ctx }) => { |
| 147 | + return ctx.articleService.removeTag(ctx.handle, input.id, input.tag) |
| 148 | + }) |
116 | 149 |
|
117 | | - createFileUpload: staffProcedure |
118 | | - .input( |
119 | | - z.object({ |
120 | | - filename: z.string(), |
121 | | - contentType: z.string(), |
122 | | - }) |
| 150 | +export type CreateArticleFileUploadInput = inferProcedureInput<typeof createArticleFileUploadProcedure> |
| 151 | +export type CreateArticleFileUploadOutput = inferProcedureOutput<typeof createArticleFileUploadProcedure> |
| 152 | +const createArticleFileUploadProcedure = procedure |
| 153 | + .input( |
| 154 | + z.object({ |
| 155 | + filename: z.string(), |
| 156 | + contentType: z.string(), |
| 157 | + }) |
| 158 | + ) |
| 159 | + .output(z.custom<PresignedPost>()) |
| 160 | + .use(withAuthentication()) |
| 161 | + .use(withAuthorization(isEditor())) |
| 162 | + .use(withDatabaseTransaction()) |
| 163 | + .use(withAuditLogEntry()) |
| 164 | + .mutation(async ({ input, ctx }) => { |
| 165 | + return await ctx.articleService.createFileUpload( |
| 166 | + ctx.handle, |
| 167 | + input.filename, |
| 168 | + input.contentType, |
| 169 | + ctx.principal.subject |
123 | 170 | ) |
124 | | - .output(z.custom<PresignedPost>()) |
125 | | - .mutation(async ({ input, ctx }) => { |
126 | | - return ctx.executeTransaction(async (handle) => { |
127 | | - return await ctx.articleService.createFileUpload( |
128 | | - handle, |
129 | | - input.filename, |
130 | | - input.contentType, |
131 | | - ctx.principal.subject |
132 | | - ) |
133 | | - }) |
134 | | - }), |
| 171 | + }) |
| 172 | + |
| 173 | +export const articleRouter = t.router({ |
| 174 | + create: createArticleProcedure, |
| 175 | + edit: editArticleProcedure, |
| 176 | + all: allArticlesProcedure, |
| 177 | + findArticles: findArticlesProcedure, |
| 178 | + find: findArticleProcedure, |
| 179 | + get: getArticleProcedure, |
| 180 | + related: findRelatedArticlesProcedure, |
| 181 | + featured: findFeaturedArticlesProcedure, |
| 182 | + getTags: getArticleTagsProcedure, |
| 183 | + findTagsOrderedByPopularity: findArticleTagsOrderedByPopularityProcedure, |
| 184 | + addTag: addArticleTagProcedure, |
| 185 | + removeTag: removeArticleTagProcedure, |
| 186 | + createFileUpload: createArticleFileUploadProcedure, |
135 | 187 | }) |
0 commit comments