diff --git a/.env.example b/.env.example index 15c64fa..5847191 100644 --- a/.env.example +++ b/.env.example @@ -14,4 +14,5 @@ R2_PUBLIC_URL=https://your_r2_public_url.com R2_BUCKET_NAME=your_r2_bucket_name JWT_SECRET=your_jwt_secret TOKEN_EXPIRATION=2592000 -LOGIN_BYPASS_KEY=your_login_bypass_key \ No newline at end of file +LOGIN_BYPASS_KEY=your_login_bypass_key +SENTRY_DSN= \ No newline at end of file diff --git a/package.json b/package.json index f2ddd54..76098ae 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@aws-sdk/client-s3": "^3.556.0", "@aws-sdk/s3-request-presigner": "^3.563.0", "@hono/node-server": "^1.8.2", + "@hono/sentry": "^1.1.0", "@hono/swagger-ui": "^0.2.1", "@hono/zod-openapi": "^0.9.9", "@paralleldrive/cuid2": "^2.2.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f8686ee..6545dde 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: '@hono/node-server': specifier: ^1.8.2 version: 1.8.2 + '@hono/sentry': + specifier: ^1.1.0 + version: 1.1.0(hono@4.2.9) '@hono/swagger-ui': specifier: ^0.2.1 version: 0.2.1(hono@4.2.9) @@ -601,6 +604,11 @@ packages: resolution: {integrity: sha512-h8l2TBLCPHZBUrrkosZ6L5CpBLj6zdESyF4B+zngiCDF7aZFQJ0alVbLx7jn8PCVi9EyoFf8a4hOZFi1tD95EA==} engines: {node: '>=18.14.1'} + '@hono/sentry@1.1.0': + resolution: {integrity: sha512-sg0hyn3VhQi2IQtHQgHxdyUWgvy+LMdjne0iVkFf0gW2KM3/uZyvMxdTZBlzOPeMq50q6CIEZrxtZJ8TZt8Vcw==} + peerDependencies: + hono: '>=3.*' + '@hono/swagger-ui@0.2.1': resolution: {integrity: sha512-wBxVMRe3/v8xH4o6icmwztiIq0DG0s7+jHVMHVUAoFFCWEQNL2iskMmQtrhSDtsFmBZUeUFQUaaJ6Ir6DOmHLA==} peerDependencies: @@ -653,6 +661,22 @@ packages: resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@sentry/core@7.112.2': + resolution: {integrity: sha512-gHPCcJobbMkk0VR18J65WYQTt3ED4qC6X9lHKp27Ddt63E+MDGkG6lvYBU1LS8cV7CdyBGC1XXDCfor61GvLsA==} + engines: {node: '>=8'} + + '@sentry/integrations@7.112.2': + resolution: {integrity: sha512-ioC2yyU6DqtLkdmWnm87oNvdn2+9oKctJeA4t+jkS6JaJ10DcezjCwiLscX4rhB9aWJV3IWF7Op0O6K3w0t2Hg==} + engines: {node: '>=8'} + + '@sentry/types@7.112.2': + resolution: {integrity: sha512-kCMLt7yhY5OkWE9MeowlTNmox9pqDxcpvqguMo4BDNZM5+v9SEb1AauAdR78E1a1V8TyCzjBD7JDfXWhvpYBcQ==} + engines: {node: '>=8'} + + '@sentry/utils@7.112.2': + resolution: {integrity: sha512-OjLh0hx0t1EcL4ZIjf+4svlmmP+tHUDGcr5qpFWH78tjmkPW4+cqPuZCZfHSuWcDdeiaXi8TnYoVRqDcJKK/eQ==} + engines: {node: '>=8'} + '@smithy/abort-controller@2.2.0': resolution: {integrity: sha512-wRlta7GuLWpTqtFfGo+nZyOO1vEvewdNR1R4rTxpC8XU6vG/NDyrFBhwLZsqg1NUoR1noVaXJPC/7ZK47QCySw==} engines: {node: '>=14.0.0'} @@ -1678,6 +1702,9 @@ packages: resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} engines: {node: '>= 4'} + immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} @@ -1842,6 +1869,12 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + lie@3.1.1: + resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==} + + localforage@1.10.0: + resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -2217,6 +2250,9 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + toucan-js@3.4.0: + resolution: {integrity: sha512-ifqPB5QIBC07gDGhWyMpSFp6Z6cjRLsjxhQ3wZmE6YGDntJZNCage77AIyrVihQLQM6/6T8TQumEJDuWlBw56w==} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -3063,6 +3099,11 @@ snapshots: '@hono/node-server@1.8.2': {} + '@hono/sentry@1.1.0(hono@4.2.9)': + dependencies: + hono: 4.2.9 + toucan-js: 3.4.0 + '@hono/swagger-ui@0.2.1(hono@4.2.9)': dependencies: hono: 4.2.9 @@ -3111,6 +3152,24 @@ snapshots: '@pkgr/core@0.1.1': {} + '@sentry/core@7.112.2': + dependencies: + '@sentry/types': 7.112.2 + '@sentry/utils': 7.112.2 + + '@sentry/integrations@7.112.2': + dependencies: + '@sentry/core': 7.112.2 + '@sentry/types': 7.112.2 + '@sentry/utils': 7.112.2 + localforage: 1.10.0 + + '@sentry/types@7.112.2': {} + + '@sentry/utils@7.112.2': + dependencies: + '@sentry/types': 7.112.2 + '@smithy/abort-controller@2.2.0': dependencies: '@smithy/types': 2.12.0 @@ -4475,6 +4534,8 @@ snapshots: ignore@5.3.1: {} + immediate@3.0.6: {} + import-fresh@3.3.0: dependencies: parent-module: 1.0.1 @@ -4635,6 +4696,14 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + lie@3.1.1: + dependencies: + immediate: 3.0.6 + + localforage@1.10.0: + dependencies: + lie: 3.1.1 + locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -5003,6 +5072,13 @@ snapshots: dependencies: is-number: 7.0.0 + toucan-js@3.4.0: + dependencies: + '@sentry/core': 7.112.2 + '@sentry/integrations': 7.112.2 + '@sentry/types': 7.112.2 + '@sentry/utils': 7.112.2 + tr46@0.0.3: {} ts-api-utils@1.3.0(typescript@5.4.5): diff --git a/src/configs/env.config.ts b/src/configs/env.config.ts index c65d4f3..bb4cc32 100644 --- a/src/configs/env.config.ts +++ b/src/configs/env.config.ts @@ -25,6 +25,7 @@ const EnvSchema = z.object({ LOGIN_BYPASS_KEY: z.string().default('default-bypass-key'), GOOGLE_CALENDAR_SECRET_PATH: z.string().default('google_credentials.json'), GOOGLE_CALENDAR_ID: z.string().default('primary'), + SENTRY_DSN: z.string().url().optional(), }); const result = EnvSchema.safeParse(process.env); diff --git a/src/controllers/calendar.controller.ts b/src/controllers/calendar.controller.ts index 0467542..623d1e6 100644 --- a/src/controllers/calendar.controller.ts +++ b/src/controllers/calendar.controller.ts @@ -1,3 +1,7 @@ +import { GaxiosError } from 'gaxios'; +import { calendar_v3, google } from 'googleapis'; +import { env } from '~/configs/env.config'; +import { googleAuth } from '~/lib/googleapi'; import { deleteCalendarEventRoute, getCalendarEventByIdRoute, @@ -6,10 +10,6 @@ import { updateCalendarEventRoute, } from '~/routes/calendar.route'; import { createAuthRouter } from './router-factory'; -import { google, calendar_v3 } from 'googleapis'; -import { GaxiosError } from 'gaxios'; -import { googleAuth } from '~/lib/googleapi'; -import { env } from '~/configs/env.config'; export const calendarRouter = createAuthRouter(); @@ -44,7 +44,7 @@ calendarRouter.openapi(postCalendarEventRoute, async (c) => { if (error instanceof GaxiosError) { return c.json({ error: error.message }, 400); } - return c.json({ error: 'Something went wrong' }, 500); + throw error; } }); @@ -69,7 +69,7 @@ calendarRouter.openapi(getCalendarEventRoute, async (c) => { if (error instanceof GaxiosError) { return c.json({ error: error.message }, 400); } - return c.json({ error: 'Something went wrong' }, 500); + throw error; } }); @@ -88,7 +88,7 @@ calendarRouter.openapi(getCalendarEventByIdRoute, async (c) => { if (error instanceof GaxiosError) { return c.json({ error: error.message }, 400); } - return c.json({ error: 'Something went wrong' }, 500); + throw error; } }); @@ -149,6 +149,6 @@ calendarRouter.openapi(deleteCalendarEventRoute, async (c) => { if (error instanceof GaxiosError) { return c.json({ error: error.message }, 400); } - return c.json({ error: 'Something went wrong' }, 500); + throw error; } }); diff --git a/src/controllers/category.controller.ts b/src/controllers/category.controller.ts index 61e4e75..3dcd334 100644 --- a/src/controllers/category.controller.ts +++ b/src/controllers/category.controller.ts @@ -1,10 +1,10 @@ +import { db } from '~/db/drizzle'; +import { getCategoryById, getCategoryList } from '~/repositories/category.repo'; import { getCategoryByIdRoute, getListCategoryRoute, } from '~/routes/category.route'; import { createAuthRouter } from './router-factory'; -import { db } from '~/db/drizzle'; -import { getCategoryById, getCategoryList } from '~/repositories/category.repo'; export const categoryRouter = createAuthRouter(); @@ -14,17 +14,8 @@ categoryRouter.openapi(getListCategoryRoute, async (c) => { }); categoryRouter.openapi(getCategoryByIdRoute, async (c) => { - try { - const { categoryId } = c.req.valid('param'); - const category = await getCategoryById(db, categoryId); - if (!category) return c.json({ error: 'Category not found' }, 404); - return c.json(category, 200); - } catch (err) { - return c.json( - { - error: 'Something went wrong', - }, - 400, - ); - } + const { categoryId } = c.req.valid('param'); + const category = await getCategoryById(db, categoryId); + if (!category) return c.json({ error: 'Category not found' }, 404); + return c.json(category, 200); }); diff --git a/src/controllers/comment.controller.ts b/src/controllers/comment.controller.ts index f9ea3a5..078b920 100644 --- a/src/controllers/comment.controller.ts +++ b/src/controllers/comment.controller.ts @@ -42,7 +42,7 @@ commentRouter.openapi(postCommentRoute, async (c) => { } catch (err) { if (err instanceof PostgresError) return c.json({ error: err.message }, 400); - return c.json(err, 400); + throw err; } }); diff --git a/src/controllers/course.controller.ts b/src/controllers/course.controller.ts index f847786..fcc2900 100644 --- a/src/controllers/course.controller.ts +++ b/src/controllers/course.controller.ts @@ -1,28 +1,28 @@ -import { - createCourseRoute, - deleteCourseRoute, - listCourseRoute, - getCourseByIdRoute, - updateCourseRoute, - createUserCourseRoute, - getUserCourseRoute, - getCurrentUserCourseRoute, - deleteUserCourseRoute, -} from '~/routes/course.route'; -import { createAuthRouter } from './router-factory'; +import { PostgresError } from 'postgres'; +import { db } from '~/db/drizzle'; import { createCourse, + createUserCourse, deleteCourse, - getListCourses, + deleteUserCourse, getCourseById, - updateCourse, - createUserCourse, + getListCourses, getUserCourse, - deleteUserCourse, + updateCourse, } from '~/repositories/course.repo'; -import { db } from '~/db/drizzle'; -import { PostgresError } from 'postgres'; +import { + createCourseRoute, + createUserCourseRoute, + deleteCourseRoute, + deleteUserCourseRoute, + getCourseByIdRoute, + getCurrentUserCourseRoute, + getUserCourseRoute, + listCourseRoute, + updateCourseRoute, +} from '~/routes/course.route'; import { CourseSchema } from '~/types/course.types'; +import { createAuthRouter } from './router-factory'; export const courseRouter = createAuthRouter(); @@ -40,34 +40,20 @@ courseRouter.openapi(createUserCourseRoute, async (c) => { return c.json({ error: 'Course not found' }, 404); return c.json({ error: 'Course has already been taken' }, 400); } - return c.json(err, 400); + throw err; } }); courseRouter.openapi(getUserCourseRoute, async (c) => { - try { - const userId = c.var.user.id; - const userCourses = await getUserCourse(db, userId); - return c.json(userCourses, 200); - } catch (err) { - if (err instanceof Error) { - return c.json({ error: err.message }, 400); - } - return c.json({ error: 'Something went wrong' }, 500); - } + const userId = c.var.user.id; + const userCourses = await getUserCourse(db, userId); + return c.json(userCourses, 200); }); courseRouter.openapi(getCurrentUserCourseRoute, async (c) => { - try { - const userId = c.var.user.id; - const userCourses = await getUserCourse(db, userId, true); - return c.json(userCourses, 200); - } catch (err) { - if (err instanceof Error) { - return c.json({ error: err.message }, 400); - } - return c.json({ error: 'Something went wrong' }, 500); - } + const userId = c.var.user.id; + const userCourses = await getUserCourse(db, userId, true); + return c.json(userCourses, 200); }); courseRouter.openapi(deleteUserCourseRoute, async (c) => { @@ -89,48 +75,21 @@ courseRouter.openapi(deleteUserCourseRoute, async (c) => { // Course Controller courseRouter.openapi(listCourseRoute, async (c) => { - try { - const courses = await getListCourses(db, c.req.valid('query')); - return c.json( - { - courses, - }, - 200, - ); - } catch (err) { - return c.json( - { - error: 'Something went wrong', - }, - 400, - ); - } + const courses = await getListCourses(db, c.req.valid('query')); + return c.json( + { + courses, + }, + 200, + ); }); courseRouter.openapi(getCourseByIdRoute, async (c) => { - try { - const { courseId } = c.req.valid('param'); - const course = await getCourseById(db, courseId); + const { courseId } = c.req.valid('param'); + const course = await getCourseById(db, courseId); - if (!course) return c.json({ error: 'Course not found' }, 404); - return c.json(course, 200); - } catch (err) { - if (err instanceof Error) { - return c.json( - { - error: err.message, - }, - 400, - ); - } else { - return c.json( - { - error: 'Something went wrong', - }, - 400, - ); - } - } + if (!course) return c.json({ error: 'Course not found' }, 404); + return c.json(course, 200); }); courseRouter.openapi(createCourseRoute, async (c) => { @@ -141,59 +100,23 @@ courseRouter.openapi(createCourseRoute, async (c) => { } catch (err) { if (err instanceof PostgresError) return c.json({ error: 'Course has already been created' }, 400); - return c.json(err, 400); + throw err; } }); courseRouter.openapi(updateCourseRoute, async (c) => { - try { - const data = c.req.valid('json'); - const { courseId } = c.req.valid('param'); - const course = await updateCourse(db, data, courseId); + const data = c.req.valid('json'); + const { courseId } = c.req.valid('param'); + const course = await updateCourse(db, data, courseId); - if (!course) return c.json({ error: 'Course not found' }, 404); - return c.json(course, 200); - } catch (err) { - if (err instanceof Error) { - return c.json( - { - error: err.message, - }, - 400, - ); - } else { - return c.json( - { - error: 'Something went wrong', - }, - 400, - ); - } - } + if (!course) return c.json({ error: 'Course not found' }, 404); + return c.json(course, 200); }); courseRouter.openapi(deleteCourseRoute, async (c) => { - try { - const { courseId } = c.req.valid('param'); - const course = await deleteCourse(db, courseId); + const { courseId } = c.req.valid('param'); + const course = await deleteCourse(db, courseId); - if (!course) return c.json({ error: 'Course not found' }, 404); - return c.json(course, 200); - } catch (err) { - if (err instanceof Error) { - return c.json( - { - error: err.message, - }, - 400, - ); - } else { - return c.json( - { - error: 'Something went wrong', - }, - 400, - ); - } - } + if (!course) return c.json({ error: 'Course not found' }, 404); + return c.json(course, 200); }); diff --git a/src/controllers/info.controller.ts b/src/controllers/info.controller.ts index 6410d8f..5dacf26 100644 --- a/src/controllers/info.controller.ts +++ b/src/controllers/info.controller.ts @@ -33,7 +33,7 @@ infoRouter.openapi(postReadInfoRoute, async (c) => { } catch (err) { if (err instanceof PostgresError) return c.json({ error: 'User have already read this info' }, 400); - return c.json(err, 400); + throw err; } }); @@ -56,22 +56,17 @@ infoRouter.openapi(createInfoRoute, async (c) => { } catch (err) { if (err instanceof PostgresError) return c.json({ error: err.message }, 400); - console.log(err); - return c.json({ error: 'Something went wrong' }, 400); + throw err; } }); infoRouter.openapi(deleteInfoRoute, async (c) => { - try { - const { infoId } = c.req.valid('param'); - const info = await deleteInfo(db, infoId); - if (!info) { - return c.json({ error: 'Info not found' }, 404); - } - return c.json({}, 200); - } catch (err) { - return c.json(err, 400); + const { infoId } = c.req.valid('param'); + const info = await deleteInfo(db, infoId); + if (!info) { + return c.json({ error: 'Info not found' }, 404); } + return c.json({}, 200); }); infoRouter.openapi(getListInfoRoute, async (c) => { @@ -85,14 +80,10 @@ infoRouter.openapi(getListInfoRoute, async (c) => { }); infoRouter.openapi(getInfoByIdRoute, async (c) => { - try { - const { infoId } = c.req.valid('param'); - const info = await getInfoById(db, infoId, c.var.user.id); - if (!info) { - return c.json({ error: 'Info not found' }, 404); - } - return c.json(info, 200); - } catch (err) { - return c.json(err, 500); + const { infoId } = c.req.valid('param'); + const info = await getInfoById(db, infoId, c.var.user.id); + if (!info) { + return c.json({ error: 'Info not found' }, 404); } + return c.json(info, 200); }); diff --git a/src/controllers/reaction.controller.ts b/src/controllers/reaction.controller.ts index 885af4c..cff8159 100644 --- a/src/controllers/reaction.controller.ts +++ b/src/controllers/reaction.controller.ts @@ -15,85 +15,36 @@ import { createAuthRouter } from './router-factory'; export const reactionRouter = createAuthRouter(); reactionRouter.openapi(getReactionsRoute, async (c) => { - try { - const reactions = await getReactions( - db, - c.req.valid('query'), - c.var.user.id, - ); - if (!reactions) return c.json({ error: 'No reactions found' }, 404); - return c.json(reactions, 200); - } catch (err) { - return c.json( - { - error: 'Something went wrong', - }, - 400, - ); - } + const reactions = await getReactions(db, c.req.valid('query'), c.var.user.id); + if (!reactions) return c.json({ error: 'No reactions found' }, 404); + return c.json(reactions, 200); }); reactionRouter.openapi(CreateOrUpdateReactionRoute, async (c) => { - try { - const data = c.req.valid('json'); - const reaction = await createOrUpdateReaction(db, data, c.var.user.id); + const data = c.req.valid('json'); + const reaction = await createOrUpdateReaction(db, data, c.var.user.id); - if (!reaction) return c.json({ error: 'Reaction not found' }, 404); - return c.json(reaction, 200); - } catch (err) { - if (err instanceof Error) { - return c.json( - { - error: err.message, - }, - 400, - ); - } else { - return c.json( - { - error: 'Something went wrong', - }, - 400, - ); - } - } + if (!reaction) return c.json({ error: 'Reaction not found' }, 404); + return c.json(reaction, 200); }); reactionRouter.openapi(deleteCommentReactionRoute, async (c) => { - try { - const { commentId } = c.req.valid('param'); - const reaction = await deleteReaction( - db, - commentId, - c.var.user.id, - 'comment', - ); - - if (!reaction) return c.json({ error: 'Reaction not found' }, 404); - return c.json(reaction, 200); - } catch (err) { - return c.json( - { - error: 'Something went wrong', - }, - 400, - ); - } + const { commentId } = c.req.valid('param'); + const reaction = await deleteReaction( + db, + commentId, + c.var.user.id, + 'comment', + ); + + if (!reaction) return c.json({ error: 'Reaction not found' }, 404); + return c.json(reaction, 200); }); reactionRouter.openapi(deleteInfoReactionRoute, async (c) => { - try { - const { infoId } = c.req.valid('param'); - const reaction = await deleteReaction(db, infoId, c.var.user.id, 'info'); + const { infoId } = c.req.valid('param'); + const reaction = await deleteReaction(db, infoId, c.var.user.id, 'info'); - if (!reaction) return c.json({ error: 'Reaction not found' }, 404); - return c.json(reaction, 200); - } catch (err) { - return c.json( - { - error: 'Something went wrong', - }, - 400, - ); - } + if (!reaction) return c.json({ error: 'Reaction not found' }, 404); + return c.json(reaction, 200); }); diff --git a/src/controllers/user-unsubscribe.controller.ts b/src/controllers/user-unsubscribe.controller.ts index f090f60..ae9be23 100644 --- a/src/controllers/user-unsubscribe.controller.ts +++ b/src/controllers/user-unsubscribe.controller.ts @@ -1,3 +1,6 @@ +import { z } from 'zod'; +import { db } from '~/db/drizzle'; +import { checkRequired, isCategoryExists } from '~/repositories/category.repo'; import { deleteUserUnsubscribeCategory, getListUserUnsubscribeCategory, @@ -5,25 +8,22 @@ import { postListUserUnsubcribeCategory, postUserUnsubcribeCategory, } from '~/repositories/user-unsubscribe.repo'; -import { createAuthRouter } from './router-factory'; -import { db } from '~/db/drizzle'; import { - getUserUnsubscribeCategoryRoute, - getListUserUnsubscribeCategoryRoute, - postUserUnsubscribeCategoryRoute, - postListUserUnsubscribeCategoryRoute, deleteListUserUnsubscribeRoute, deleteUserUnsubscribeCategoryRoute, + getListUserUnsubscribeCategoryRoute, + getUserUnsubscribeCategoryRoute, + postListUserUnsubscribeCategoryRoute, + postUserUnsubscribeCategoryRoute, } from '~/routes/user-unsubscribe.route'; +import { CategoryNotFoundSchema, CategorySchema } from '~/types/category.types'; import { DeleteListUserUnsubscribeCategoryResponseSchema, DeleteUserUnsubscribeCategorySchema, GetUserUnsubscribeCategoryResponseSchema, PostUserUnsubscribeCategorySchema, } from '~/types/user-unsubscribe.types'; -import { z } from 'zod'; -import { checkRequired, isCategoryExists } from '~/repositories/category.repo'; -import { CategoryNotFoundSchema, CategorySchema } from '~/types/category.types'; +import { createAuthRouter } from './router-factory'; export const userUnsubscribeRouter = createAuthRouter(); @@ -31,37 +31,33 @@ userUnsubscribeRouter.openapi(getUserUnsubscribeCategoryRoute, async (c) => { const { id } = c.var.user; const { categoryId } = c.req.valid('param'); - try { - const categoryExist = await isCategoryExists(db, categoryId); - if (!categoryExist) { - return c.json( - { error: `Category with id ${categoryId} does not exist!` }, - 400, - ); - } - - const category = await getUserUnsubscribeCategory(db, { - userId: id, - categoryId, - }); + const categoryExist = await isCategoryExists(db, categoryId); + if (!categoryExist) { + return c.json( + { error: `Category with id ${categoryId} does not exist!` }, + 400, + ); + } - let response: z.infer = { - userId: id, - categoryId, - unsubscribed: false, - }; + const category = await getUserUnsubscribeCategory(db, { + userId: id, + categoryId, + }); - if (category) { - response = { - ...category, - unsubscribed: true, - }; - } + let response: z.infer = { + userId: id, + categoryId, + unsubscribed: false, + }; - return c.json(response, 200); - } catch (e) { - return c.json({ error: 'Something went wrong!' }, 500); + if (category) { + response = { + ...category, + unsubscribed: true, + }; } + + return c.json(response, 200); }); userUnsubscribeRouter.openapi( @@ -69,18 +65,14 @@ userUnsubscribeRouter.openapi( async (c) => { const { id } = c.var.user; - try { - const categories = await getListUserUnsubscribeCategory(db, id); - const categoriesArray = categories.map((category) => category.categoryId); + const categories = await getListUserUnsubscribeCategory(db, id); + const categoriesArray = categories.map((category) => category.categoryId); - const data = { - userId: id, - categoryId: categoriesArray, - }; - return c.json(data, 200); - } catch (e) { - return c.json({ error: 'Something went wrong!' }, 500); - } + const data = { + userId: id, + categoryId: categoriesArray, + }; + return c.json(data, 200); }, ); @@ -92,36 +84,32 @@ userUnsubscribeRouter.openapi(postUserUnsubscribeCategoryRoute, async (c) => { categoryId, }; - try { - const { requiredPush } = await checkRequired(db, categoryId); - if (requiredPush === null) { - return c.json( - { error: `Category with id ${categoryId} does not exist!` }, - 400, - ); - } + const { requiredPush } = await checkRequired(db, categoryId); + if (requiredPush === null) { + return c.json( + { error: `Category with id ${categoryId} does not exist!` }, + 400, + ); + } - if (requiredPush) { - return c.json( - { - error: `Subscription to category with id '${categoryId}' is required!`, - }, - 400, - ); - } + if (requiredPush) { + return c.json( + { + error: `Subscription to category with id '${categoryId}' is required!`, + }, + 400, + ); + } - let res = await postUserUnsubcribeCategory(db, data); - if (!res) { - // If unsubscription is already in DB - res = { - userId: id, - categoryId, - }; - } - return c.json(res, 201); - } catch (e) { - return c.json({ error: 'Something went wrong!' }, 500); + let res = await postUserUnsubcribeCategory(db, data); + if (!res) { + // If unsubscription is already in DB + res = { + userId: id, + categoryId, + }; } + return c.json(res, 201); }); userUnsubscribeRouter.openapi( @@ -171,32 +159,30 @@ userUnsubscribeRouter.openapi( }); }); - try { - const res = await postListUserUnsubcribeCategory( - db, - categoriesToUnsubscribe, - ); + const categoriesUnsubs = await postListUserUnsubcribeCategory( + db, + categoriesToUnsubscribe, + ); - const categoriesAlreadyUnsubscribed: string[] = []; - const insertedCategoriesSet = new Set(res.map((data) => data.categoryId)); + const categoriesAlreadyUnsubscribed: string[] = []; + const insertedCategoriesSet = new Set( + categoriesUnsubs.map((data) => data.categoryId), + ); - categoriesToUnsubscribe.forEach((data) => { - if (!insertedCategoriesSet.has(data.categoryId)) { - categoriesAlreadyUnsubscribed.push(data.categoryId); - } - }); + categoriesToUnsubscribe.forEach((data) => { + if (!insertedCategoriesSet.has(data.categoryId)) { + categoriesAlreadyUnsubscribed.push(data.categoryId); + } + }); - const returnObj = { - userId: id, - categoryId: res.map((data) => data.categoryId), - requiredSubscriptions: subsRequired, - categoriesNotFound, - categoriesAlreadyUnsubscribed, - }; - return c.json(returnObj, 201); - } catch (e) { - return c.json({ error: 'Something went wrong!' }, 500); - } + const returnObj = { + userId: id, + categoryId: categoriesUnsubs.map((data) => data.categoryId), + requiredSubscriptions: subsRequired, + categoriesNotFound, + categoriesAlreadyUnsubscribed, + }; + return c.json(returnObj, 201); }, ); @@ -204,114 +190,106 @@ userUnsubscribeRouter.openapi(deleteUserUnsubscribeCategoryRoute, async (c) => { const { id } = c.var.user; const { categoryId } = c.req.valid('json'); - try { - const categoryExist = await isCategoryExists(db, categoryId); - if (!categoryExist) { - return c.json( - { error: `Category with id ${categoryId} does not exist!` }, - 400, - ); - } - - const res = await deleteUserUnsubscribeCategory( - db, - { - userId: id, - categoryId, - }, - id, + const categoryExist = await isCategoryExists(db, categoryId); + if (!categoryExist) { + return c.json( + { error: `Category with id ${categoryId} does not exist!` }, + 400, ); + } - if (!res) { - return c.json( - { error: 'User is already subscribed to that category!' }, - 400, - ); - } - - return c.json(res, 201); - } catch (e) { - return c.json({ error: 'Something went wrong!' }, 500); + const res = await deleteUserUnsubscribeCategory( + db, + { + userId: id, + categoryId, + }, + id, + ); + + if (!res) { + return c.json( + { error: 'User is already subscribed to that category!' }, + 400, + ); } + + return c.json(res, 201); }); userUnsubscribeRouter.openapi(deleteListUserUnsubscribeRoute, async (c) => { const { id } = c.var.user; const categoryIds = c.req.valid('json').categoryId; - try { - const existingCategories: string[] = []; - const checkExistingCategoryPromises: Array< - Promise> - > = []; + const existingCategories: string[] = []; + const checkExistingCategoryPromises: Array< + Promise> + > = []; - categoryIds.forEach((categoryId) => { - checkExistingCategoryPromises.push(isCategoryExists(db, categoryId)); - }); + categoryIds.forEach((categoryId) => { + checkExistingCategoryPromises.push(isCategoryExists(db, categoryId)); + }); - const res = await Promise.allSettled(checkExistingCategoryPromises); - res.forEach((result) => { - if (result.status === 'fulfilled') { - if (result.value) { - existingCategories.push(result.value.id); - } + const res = await Promise.allSettled(checkExistingCategoryPromises); + res.forEach((result) => { + if (result.status === 'fulfilled') { + if (result.value) { + existingCategories.push(result.value.id); } - }); - - if (existingCategories.length === 0) { - return c.json({ error: 'All categories does not exist!' }, 400); } + }); - const notFoundCategories = categoryIds.filter( - (categoryId) => !existingCategories.includes(categoryId), - ); + if (existingCategories.length === 0) { + return c.json({ error: 'All categories does not exist!' }, 400); + } - const deletePromises: Array< - Promise> - > = []; + const notFoundCategories = categoryIds.filter( + (categoryId) => !existingCategories.includes(categoryId), + ); - existingCategories.forEach((categoryId) => { - deletePromises.push( - deleteUserUnsubscribeCategory( - db, - { - userId: id, - categoryId, - }, - id, - ), - ); - }); + const deletePromises: Array< + Promise> + > = []; + + existingCategories.forEach((categoryId) => { + deletePromises.push( + deleteUserUnsubscribeCategory( + db, + { + userId: id, + categoryId, + }, + id, + ), + ); + }); - const resDelete = await Promise.allSettled(deletePromises); - const deletedCategories: string[] = []; - const alreadySubscribedCategories: string[] = []; + const resDelete = await Promise.allSettled(deletePromises); + const deletedCategories: string[] = []; + const alreadySubscribedCategories: string[] = []; - resDelete.forEach((result) => { - if (result.status === 'fulfilled') { - if (result.value) { - deletedCategories.push(result.value.categoryId); - } + resDelete.forEach((result) => { + if (result.status === 'fulfilled') { + if (result.value) { + deletedCategories.push(result.value.categoryId); } - }); + } + }); - existingCategories.forEach((categoryId) => { - if (!deletedCategories.includes(categoryId)) { - alreadySubscribedCategories.push(categoryId); - } - }); + existingCategories.forEach((categoryId) => { + if (!deletedCategories.includes(categoryId)) { + alreadySubscribedCategories.push(categoryId); + } + }); - const returnObj: z.infer< - typeof DeleteListUserUnsubscribeCategoryResponseSchema - > = { - userId: id, - categoryId: deletedCategories, - categoriesNotFound: notFoundCategories, - categoriesAlreadySubscribed: alreadySubscribedCategories, - }; + const returnObj: z.infer< + typeof DeleteListUserUnsubscribeCategoryResponseSchema + > = { + userId: id, + categoryId: deletedCategories, + categoriesNotFound: notFoundCategories, + categoriesAlreadySubscribed: alreadySubscribedCategories, + }; - return c.json(returnObj, 201); - } catch (e) { - return c.json({ error: 'Something went wrong!' }, 500); - } + return c.json(returnObj, 201); }); diff --git a/src/index.ts b/src/index.ts index a872076..2b8daca 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ import { serve } from '@hono/node-server'; +import { sentry } from '@hono/sentry'; import { swaggerUI } from '@hono/swagger-ui'; import { OpenAPIHono } from '@hono/zod-openapi'; import fs from 'fs'; @@ -21,6 +22,10 @@ const app = new OpenAPIHono({ }, }); +if (env.SENTRY_DSN) { + app.use('*', sentry({ dsn: env.SENTRY_DSN })); +} + app.use(logger()); app.use( '/api/*',