Compare commits
91 commits
40868bbd61
...
a8cc166f41
Author | SHA1 | Date | |
---|---|---|---|
a8cc166f41 | |||
a529b939c4 | |||
10080b8513 | |||
44a75ff12e | |||
de71e725e4 | |||
d2c10f6a0a | |||
8f21ab1d68 | |||
73004dc2d8 | |||
83b4b4476a | |||
382bece410 | |||
b464e6b511 | |||
911ec29234 | |||
9ef984eccb | |||
49111aaa3f | |||
33a07f073b | |||
6a4bbae300 | |||
b135a61130 | |||
c0ff8e87bb | |||
a469948da5 | |||
d97d2c84d2 | |||
7f419afe47 | |||
9a187788c4 | |||
71315cbb5b | |||
49386afc07 | |||
0caddb59d8 | |||
70a819f525 | |||
05649e3c11 | |||
4476ee6eb9 | |||
05f56a2186 | |||
d3dfc3dc0d | |||
68d36c62ce | |||
53973c114e | |||
7ee8df3732 | |||
8369a92520 | |||
f5450d9b4f | |||
a6694c4f7e | |||
25ebb4bb3b | |||
aebbfb9f5d | |||
4772425abd | |||
38401d4eba | |||
af18024baf | |||
93a75b9214 | |||
6eae281fd0 | |||
6c2c09f9e2 | |||
43e24222cc | |||
f326bd8e1a | |||
29b3490132 | |||
3de329b157 | |||
d7da7f85cd | |||
2484546be3 | |||
47f08a9bf7 | |||
d42294fb0c | |||
fe2d1ff0f3 | |||
378991e582 | |||
b8ad6891e3 | |||
8207230886 | |||
b5dfe9a4e3 | |||
4b0f51f444 | |||
e426e0f861 | |||
2d3a6f7d6c | |||
2e2b74b282 | |||
f245b4156f | |||
d62e954348 | |||
2889424bfb | |||
3ee0dcf950 | |||
c98a72f2f1 | |||
29f2a01ac6 | |||
4cf5ce26ff | |||
16b878a2e9 | |||
280fa57e45 | |||
be1502a4ac | |||
f240bf525d | |||
525b8597f2 | |||
0da8e35b9b | |||
cd1ad5dbc4 | |||
5fbd7ac091 | |||
5e6feb39eb | |||
b652499788 | |||
96ff00f120 | |||
58cf178968 | |||
360f0788dd | |||
445a15ccc7 | |||
40d13101a3 | |||
68cafccec7 | |||
f5a5704be3 | |||
50d915854f | |||
b10b374b84 | |||
c71de4a14c | |||
eb04c276ab | |||
3e890d4363 | |||
87dc6162f4 |
13 changed files with 4668 additions and 342 deletions
16
package.json
16
package.json
|
@ -45,7 +45,7 @@
|
|||
"cmdk": "^1.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"lucide-react": "^0.511.0",
|
||||
"next": "15.4.0-canary.85",
|
||||
"next": "15.4.0-canary.94",
|
||||
"next-auth": "^5.0.0-beta.25",
|
||||
"next-swagger-doc": "^0.4.1",
|
||||
"next-themes": "^0.4.6",
|
||||
|
@ -61,22 +61,22 @@
|
|||
"devDependencies": {
|
||||
"@eslint/eslintrc": "3.3.1",
|
||||
"@tailwindcss/postcss": "4.1.10",
|
||||
"@types/node": "22.15.32",
|
||||
"@types/node": "22.15.33",
|
||||
"@types/react": "19.1.8",
|
||||
"@types/react-dom": "19.1.6",
|
||||
"@types/swagger-ui-react": "^5",
|
||||
"@types/webpack-env": "^1.18.8",
|
||||
"@types/swagger-ui-react": "5",
|
||||
"@types/webpack-env": "1.18.8",
|
||||
"dotenv-cli": "8.0.0",
|
||||
"eslint": "9.29.0",
|
||||
"eslint-config-next": "15.3.4",
|
||||
"eslint-config-prettier": "10.1.5",
|
||||
"orval": "^7.10.0",
|
||||
"orval": "7.10.0",
|
||||
"postcss": "8.5.6",
|
||||
"prettier": "3.5.3",
|
||||
"prisma": "6.9.0",
|
||||
"prisma": "6.10.1",
|
||||
"tailwindcss": "4.1.10",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"ts-node": "10.9.2",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"tw-animate-css": "1.3.4",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
|
|
212
src/app/api/user/[user]/calendar/route.ts
Normal file
212
src/app/api/user/[user]/calendar/route.ts
Normal file
|
@ -0,0 +1,212 @@
|
|||
import { auth } from '@/auth';
|
||||
import { prisma } from '@/prisma';
|
||||
import {
|
||||
returnZodTypeCheckedResponse,
|
||||
userAuthenticated,
|
||||
} from '@/lib/apiHelpers';
|
||||
import {
|
||||
userCalendarQuerySchema,
|
||||
UserCalendarResponseSchema,
|
||||
UserCalendarSchema,
|
||||
} from './validation';
|
||||
import {
|
||||
ErrorResponseSchema,
|
||||
ZodErrorResponseSchema,
|
||||
} from '@/app/api/validation';
|
||||
import { z } from 'zod/v4';
|
||||
|
||||
export const GET = auth(async function GET(req, { params }) {
|
||||
const authCheck = userAuthenticated(req);
|
||||
if (!authCheck.continue)
|
||||
return returnZodTypeCheckedResponse(
|
||||
ErrorResponseSchema,
|
||||
authCheck.response,
|
||||
authCheck.metadata,
|
||||
);
|
||||
|
||||
const dataRaw = Object.fromEntries(new URL(req.url).searchParams);
|
||||
const data = await userCalendarQuerySchema.safeParseAsync(dataRaw);
|
||||
if (!data.success)
|
||||
return returnZodTypeCheckedResponse(
|
||||
ZodErrorResponseSchema,
|
||||
{
|
||||
success: false,
|
||||
message: 'Invalid request data',
|
||||
errors: data.error.issues,
|
||||
},
|
||||
{ status: 400 },
|
||||
);
|
||||
const { end, start } = data.data;
|
||||
|
||||
const requestUserId = authCheck.user.id;
|
||||
|
||||
const requestedUserId = (await params).user;
|
||||
|
||||
const requestedUser = await prisma.user.findFirst({
|
||||
where: {
|
||||
id: requestedUserId,
|
||||
},
|
||||
select: {
|
||||
meetingParts: {
|
||||
where: {
|
||||
meeting: {
|
||||
start_time: {
|
||||
lte: end,
|
||||
},
|
||||
end_time: {
|
||||
gte: start,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
meeting: {
|
||||
start_time: 'asc',
|
||||
},
|
||||
},
|
||||
select: {
|
||||
meeting: {
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
description: true,
|
||||
start_time: true,
|
||||
end_time: true,
|
||||
status: true,
|
||||
location: true,
|
||||
created_at: true,
|
||||
updated_at: true,
|
||||
organizer_id: true,
|
||||
participants: {
|
||||
select: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
meetingsOrg: {
|
||||
where: {
|
||||
start_time: {
|
||||
lte: end,
|
||||
},
|
||||
end_time: {
|
||||
gte: start,
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
start_time: 'asc',
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
description: true,
|
||||
start_time: true,
|
||||
end_time: true,
|
||||
status: true,
|
||||
location: true,
|
||||
created_at: true,
|
||||
updated_at: true,
|
||||
organizer_id: true,
|
||||
participants: {
|
||||
select: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
blockedSlots: {
|
||||
where: {
|
||||
start_time: {
|
||||
lte: end,
|
||||
},
|
||||
end_time: {
|
||||
gte: start,
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
start_time: 'asc',
|
||||
},
|
||||
select: {
|
||||
id: requestUserId === requestedUserId ? true : false,
|
||||
reason: requestUserId === requestedUserId ? true : false,
|
||||
start_time: true,
|
||||
end_time: true,
|
||||
is_recurring: requestUserId === requestedUserId ? true : false,
|
||||
recurrence_end_date: requestUserId === requestedUserId ? true : false,
|
||||
rrule: requestUserId === requestedUserId ? true : false,
|
||||
created_at: requestUserId === requestedUserId ? true : false,
|
||||
updated_at: requestUserId === requestedUserId ? true : false,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!requestedUser)
|
||||
return returnZodTypeCheckedResponse(
|
||||
ErrorResponseSchema,
|
||||
{ success: false, message: 'User not found' },
|
||||
{ status: 404 },
|
||||
);
|
||||
|
||||
const calendar: z.input<typeof UserCalendarSchema> = [];
|
||||
|
||||
for (const event of requestedUser.meetingParts) {
|
||||
if (
|
||||
event.meeting.participants.some((p) => p.user.id === requestUserId) ||
|
||||
event.meeting.organizer_id === requestUserId
|
||||
) {
|
||||
calendar.push({ ...event.meeting, type: 'event' });
|
||||
} else {
|
||||
calendar.push({
|
||||
start_time: event.meeting.start_time,
|
||||
end_time: event.meeting.end_time,
|
||||
type: 'blocked_private',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const event of requestedUser.meetingsOrg) {
|
||||
if (
|
||||
event.participants.some((p) => p.user.id === requestUserId) ||
|
||||
event.organizer_id === requestUserId
|
||||
) {
|
||||
calendar.push({ ...event, type: 'event' });
|
||||
} else {
|
||||
calendar.push({
|
||||
start_time: event.start_time,
|
||||
end_time: event.end_time,
|
||||
type: 'blocked_private',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const slot of requestedUser.blockedSlots) {
|
||||
calendar.push({
|
||||
start_time: slot.start_time,
|
||||
end_time: slot.end_time,
|
||||
id: slot.id,
|
||||
reason: slot.reason,
|
||||
is_recurring: slot.is_recurring,
|
||||
recurrence_end_date: slot.recurrence_end_date,
|
||||
rrule: slot.rrule,
|
||||
created_at: slot.created_at,
|
||||
updated_at: slot.updated_at,
|
||||
type:
|
||||
requestUserId === requestedUserId ? 'blocked_owned' : 'blocked_private',
|
||||
});
|
||||
}
|
||||
|
||||
return returnZodTypeCheckedResponse(UserCalendarResponseSchema, {
|
||||
success: true,
|
||||
calendar,
|
||||
});
|
||||
});
|
37
src/app/api/user/[user]/calendar/swagger.ts
Normal file
37
src/app/api/user/[user]/calendar/swagger.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import {
|
||||
userCalendarQuerySchema,
|
||||
UserCalendarResponseSchema,
|
||||
} from './validation';
|
||||
import {
|
||||
notAuthenticatedResponse,
|
||||
userNotFoundResponse,
|
||||
} from '@/lib/defaultApiResponses';
|
||||
import { OpenAPIRegistry } from '@asteasolutions/zod-to-openapi';
|
||||
import zod from 'zod/v4';
|
||||
import { UserIdParamSchema } from '@/app/api/validation';
|
||||
|
||||
export default function registerSwaggerPaths(registry: OpenAPIRegistry) {
|
||||
registry.registerPath({
|
||||
method: 'get',
|
||||
path: '/api/user/{user}/calendar',
|
||||
request: {
|
||||
params: zod.object({
|
||||
user: UserIdParamSchema,
|
||||
}),
|
||||
query: userCalendarQuerySchema,
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'User calendar retrieved successfully.',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: UserCalendarResponseSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
...notAuthenticatedResponse,
|
||||
...userNotFoundResponse,
|
||||
},
|
||||
tags: ['User'],
|
||||
});
|
||||
}
|
99
src/app/api/user/[user]/calendar/validation.ts
Normal file
99
src/app/api/user/[user]/calendar/validation.ts
Normal file
|
@ -0,0 +1,99 @@
|
|||
import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi';
|
||||
import zod from 'zod/v4';
|
||||
import {
|
||||
eventEndTimeSchema,
|
||||
EventSchema,
|
||||
eventStartTimeSchema,
|
||||
} from '@/app/api/event/validation';
|
||||
|
||||
extendZodWithOpenApi(zod);
|
||||
|
||||
export const BlockedSlotSchema = zod
|
||||
.object({
|
||||
start_time: eventStartTimeSchema,
|
||||
end_time: eventEndTimeSchema,
|
||||
type: zod.literal('blocked_private'),
|
||||
})
|
||||
.openapi('BlockedSlotSchema', {
|
||||
description: 'Blocked time slot in the user calendar',
|
||||
});
|
||||
|
||||
export const OwnedBlockedSlotSchema = BlockedSlotSchema.extend({
|
||||
id: zod.string(),
|
||||
reason: zod.string().nullish(),
|
||||
is_recurring: zod.boolean().default(false),
|
||||
recurrence_end_date: zod.date().nullish(),
|
||||
rrule: zod.string().nullish(),
|
||||
created_at: zod.date().nullish(),
|
||||
updated_at: zod.date().nullish(),
|
||||
type: zod.literal('blocked_owned'),
|
||||
}).openapi('OwnedBlockedSlotSchema', {
|
||||
description: 'Blocked slot owned by the user',
|
||||
});
|
||||
|
||||
export const VisibleSlotSchema = EventSchema.omit({
|
||||
organizer: true,
|
||||
participants: true,
|
||||
})
|
||||
.extend({
|
||||
type: zod.literal('event'),
|
||||
})
|
||||
.openapi('VisibleSlotSchema', {
|
||||
description: 'Visible time slot in the user calendar',
|
||||
});
|
||||
|
||||
export const UserCalendarSchema = zod
|
||||
.array(VisibleSlotSchema.or(BlockedSlotSchema).or(OwnedBlockedSlotSchema))
|
||||
.openapi('UserCalendarSchema', {
|
||||
description: 'Array of events in the user calendar',
|
||||
});
|
||||
|
||||
export const UserCalendarResponseSchema = zod.object({
|
||||
success: zod.boolean().default(true),
|
||||
calendar: UserCalendarSchema,
|
||||
});
|
||||
|
||||
export const userCalendarQuerySchema = zod
|
||||
.object({
|
||||
start: zod.iso
|
||||
.datetime()
|
||||
.optional()
|
||||
.transform((val) => {
|
||||
if (val) return new Date(val);
|
||||
const now = new Date();
|
||||
const startOfWeek = new Date(
|
||||
now.getFullYear(),
|
||||
now.getMonth(),
|
||||
now.getDate() - now.getDay(),
|
||||
);
|
||||
return startOfWeek;
|
||||
}),
|
||||
end: zod.iso
|
||||
.datetime()
|
||||
.optional()
|
||||
.transform((val) => {
|
||||
if (val) return new Date(val);
|
||||
const now = new Date();
|
||||
const endOfWeek = new Date(
|
||||
now.getFullYear(),
|
||||
now.getMonth(),
|
||||
now.getDate() + (6 - now.getDay()),
|
||||
);
|
||||
return endOfWeek;
|
||||
}),
|
||||
})
|
||||
.openapi('UserCalendarQuerySchema', {
|
||||
description: 'Query parameters for filtering the user calendar',
|
||||
properties: {
|
||||
start: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: 'Start date for filtering the calendar events',
|
||||
},
|
||||
end: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: 'End date for filtering the calendar events',
|
||||
},
|
||||
},
|
||||
});
|
122
src/app/api/user/me/password/route.ts
Normal file
122
src/app/api/user/me/password/route.ts
Normal file
|
@ -0,0 +1,122 @@
|
|||
import { auth } from '@/auth';
|
||||
import { prisma } from '@/prisma';
|
||||
import { updateUserPasswordServerSchema } from '../validation';
|
||||
import {
|
||||
returnZodTypeCheckedResponse,
|
||||
userAuthenticated,
|
||||
} from '@/lib/apiHelpers';
|
||||
import { FullUserResponseSchema } from '../../validation';
|
||||
import {
|
||||
ErrorResponseSchema,
|
||||
ZodErrorResponseSchema,
|
||||
} from '@/app/api/validation';
|
||||
import bcrypt from 'bcryptjs';
|
||||
|
||||
export const PATCH = auth(async function PATCH(req) {
|
||||
const authCheck = userAuthenticated(req);
|
||||
if (!authCheck.continue)
|
||||
return returnZodTypeCheckedResponse(
|
||||
ErrorResponseSchema,
|
||||
authCheck.response,
|
||||
authCheck.metadata,
|
||||
);
|
||||
|
||||
const body = await req.json();
|
||||
const parsedBody = updateUserPasswordServerSchema.safeParse(body);
|
||||
if (!parsedBody.success)
|
||||
return returnZodTypeCheckedResponse(
|
||||
ZodErrorResponseSchema,
|
||||
{
|
||||
success: false,
|
||||
message: 'Invalid request data',
|
||||
errors: parsedBody.error.issues,
|
||||
},
|
||||
{ status: 400 },
|
||||
);
|
||||
|
||||
const { current_password, new_password } = parsedBody.data;
|
||||
|
||||
const dbUser = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: authCheck.user.id,
|
||||
},
|
||||
include: {
|
||||
accounts: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!dbUser)
|
||||
return returnZodTypeCheckedResponse(
|
||||
ErrorResponseSchema,
|
||||
{
|
||||
success: false,
|
||||
message: 'User not found',
|
||||
},
|
||||
{ status: 404 },
|
||||
);
|
||||
|
||||
if (!dbUser.password_hash)
|
||||
return returnZodTypeCheckedResponse(
|
||||
ErrorResponseSchema,
|
||||
{
|
||||
success: false,
|
||||
message: 'User does not have a password set',
|
||||
},
|
||||
{ status: 400 },
|
||||
);
|
||||
|
||||
if (
|
||||
dbUser.accounts.length === 0 ||
|
||||
dbUser.accounts[0].provider !== 'credentials'
|
||||
)
|
||||
return returnZodTypeCheckedResponse(
|
||||
ErrorResponseSchema,
|
||||
{
|
||||
success: false,
|
||||
message: 'Credentials login is not enabled for this user',
|
||||
},
|
||||
{ status: 400 },
|
||||
);
|
||||
|
||||
const isCurrentPasswordValid = await bcrypt.compare(
|
||||
current_password,
|
||||
dbUser.password_hash || '',
|
||||
);
|
||||
|
||||
if (!isCurrentPasswordValid)
|
||||
return returnZodTypeCheckedResponse(
|
||||
ErrorResponseSchema,
|
||||
{
|
||||
success: false,
|
||||
message: 'Current password is incorrect',
|
||||
},
|
||||
{ status: 401 },
|
||||
);
|
||||
|
||||
const hashedNewPassword = await bcrypt.hash(new_password, 10);
|
||||
|
||||
const updatedUser = await prisma.user.update({
|
||||
where: {
|
||||
id: dbUser.id,
|
||||
},
|
||||
data: {
|
||||
password_hash: hashedNewPassword,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
first_name: true,
|
||||
last_name: true,
|
||||
email: true,
|
||||
image: true,
|
||||
timezone: true,
|
||||
created_at: true,
|
||||
updated_at: true,
|
||||
},
|
||||
});
|
||||
|
||||
return returnZodTypeCheckedResponse(FullUserResponseSchema, {
|
||||
success: true,
|
||||
user: updatedUser,
|
||||
});
|
||||
});
|
43
src/app/api/user/me/password/swagger.ts
Normal file
43
src/app/api/user/me/password/swagger.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import { OpenAPIRegistry } from '@asteasolutions/zod-to-openapi';
|
||||
import { FullUserResponseSchema } from '../../validation';
|
||||
import { updateUserPasswordServerSchema } from '../validation';
|
||||
import {
|
||||
invalidRequestDataResponse,
|
||||
notAuthenticatedResponse,
|
||||
serverReturnedDataValidationErrorResponse,
|
||||
userNotFoundResponse,
|
||||
} from '@/lib/defaultApiResponses';
|
||||
|
||||
export default function registerSwaggerPaths(registry: OpenAPIRegistry) {
|
||||
registry.registerPath({
|
||||
method: 'patch',
|
||||
path: '/api/user/me/password',
|
||||
description: 'Update the password of the currently authenticated user',
|
||||
request: {
|
||||
body: {
|
||||
description: 'User password update request body',
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: updateUserPasswordServerSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'User information updated successfully',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: FullUserResponseSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
...invalidRequestDataResponse,
|
||||
...notAuthenticatedResponse,
|
||||
...userNotFoundResponse,
|
||||
...serverReturnedDataValidationErrorResponse,
|
||||
},
|
||||
tags: ['User'],
|
||||
});
|
||||
}
|
|
@ -8,6 +8,7 @@ import {
|
|||
import { FullUserResponseSchema } from '../validation';
|
||||
import {
|
||||
ErrorResponseSchema,
|
||||
SuccessResponseSchema,
|
||||
ZodErrorResponseSchema,
|
||||
} from '@/app/api/validation';
|
||||
|
||||
|
@ -117,3 +118,43 @@ export const PATCH = auth(async function PATCH(req) {
|
|||
{ status: 200 },
|
||||
);
|
||||
});
|
||||
|
||||
export const DELETE = auth(async function DELETE(req) {
|
||||
const authCheck = userAuthenticated(req);
|
||||
if (!authCheck.continue)
|
||||
return returnZodTypeCheckedResponse(
|
||||
ErrorResponseSchema,
|
||||
authCheck.response,
|
||||
authCheck.metadata,
|
||||
);
|
||||
|
||||
const dbUser = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: authCheck.user.id,
|
||||
},
|
||||
});
|
||||
if (!dbUser)
|
||||
return returnZodTypeCheckedResponse(
|
||||
ErrorResponseSchema,
|
||||
{
|
||||
success: false,
|
||||
message: 'User not found',
|
||||
},
|
||||
{ status: 404 },
|
||||
);
|
||||
|
||||
await prisma.user.delete({
|
||||
where: {
|
||||
id: authCheck.user.id,
|
||||
},
|
||||
});
|
||||
|
||||
return returnZodTypeCheckedResponse(
|
||||
SuccessResponseSchema,
|
||||
{
|
||||
success: true,
|
||||
message: 'User deleted successfully',
|
||||
},
|
||||
{ status: 200 },
|
||||
);
|
||||
});
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
serverReturnedDataValidationErrorResponse,
|
||||
userNotFoundResponse,
|
||||
} from '@/lib/defaultApiResponses';
|
||||
import { SuccessResponseSchema } from '../../validation';
|
||||
|
||||
export default function registerSwaggerPaths(registry: OpenAPIRegistry) {
|
||||
registry.registerPath({
|
||||
|
@ -60,4 +61,24 @@ export default function registerSwaggerPaths(registry: OpenAPIRegistry) {
|
|||
},
|
||||
tags: ['User'],
|
||||
});
|
||||
|
||||
registry.registerPath({
|
||||
method: 'delete',
|
||||
path: '/api/user/me',
|
||||
description: 'Delete the currently authenticated user',
|
||||
responses: {
|
||||
200: {
|
||||
description: 'User deleted successfully',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: SuccessResponseSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
...notAuthenticatedResponse,
|
||||
...userNotFoundResponse,
|
||||
...serverReturnedDataValidationErrorResponse,
|
||||
},
|
||||
tags: ['User'],
|
||||
});
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import {
|
|||
lastNameSchema,
|
||||
newUserEmailServerSchema,
|
||||
newUserNameServerSchema,
|
||||
passwordSchema,
|
||||
timezoneSchema,
|
||||
} from '@/app/api/user/validation';
|
||||
|
||||
// ----------------------------------------
|
||||
|
@ -16,6 +18,16 @@ export const updateUserServerSchema = zod.object({
|
|||
first_name: firstNameSchema.optional(),
|
||||
last_name: lastNameSchema.optional(),
|
||||
email: newUserEmailServerSchema.optional(),
|
||||
image: zod.string().optional(),
|
||||
timezone: zod.string().optional(),
|
||||
image: zod.url().optional(),
|
||||
timezone: timezoneSchema.optional(),
|
||||
});
|
||||
|
||||
export const updateUserPasswordServerSchema = zod
|
||||
.object({
|
||||
current_password: zod.string().min(1, 'Current password is required'),
|
||||
new_password: passwordSchema,
|
||||
confirm_new_password: passwordSchema,
|
||||
})
|
||||
.refine((data) => data.new_password === data.confirm_new_password, {
|
||||
message: 'New password and confirm new password must match',
|
||||
});
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi';
|
||||
import { prisma } from '@/prisma';
|
||||
import zod from 'zod/v4';
|
||||
import { allTimeZones } from '@/lib/timezones';
|
||||
|
||||
extendZodWithOpenApi(zod);
|
||||
|
||||
|
@ -107,6 +108,15 @@ export const passwordSchema = zod
|
|||
'Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character',
|
||||
);
|
||||
|
||||
// ----------------------------------------
|
||||
//
|
||||
// Timezone Validation
|
||||
//
|
||||
// ----------------------------------------
|
||||
export const timezoneSchema = zod.enum(allTimeZones).openapi('Timezone', {
|
||||
description: 'Valid timezone from the list of supported timezones',
|
||||
});
|
||||
|
||||
// ----------------------------------------
|
||||
//
|
||||
// User Schema Validation (for API responses)
|
||||
|
@ -119,8 +129,11 @@ export const FullUserSchema = zod
|
|||
first_name: zod.string().nullish(),
|
||||
last_name: zod.string().nullish(),
|
||||
email: zod.email(),
|
||||
image: zod.string().nullish(),
|
||||
timezone: zod.string(),
|
||||
image: zod.url().nullish(),
|
||||
timezone: zod
|
||||
.string()
|
||||
.refine((i) => (allTimeZones as string[]).includes(i))
|
||||
.nullish(),
|
||||
created_at: zod.date(),
|
||||
updated_at: zod.date(),
|
||||
})
|
||||
|
|
3717
src/lib/timezones.ts
Normal file
3717
src/lib/timezones.ts
Normal file
File diff suppressed because it is too large
Load diff
|
@ -2,6 +2,6 @@ export { auth as middleware } from '@/auth';
|
|||
|
||||
export const config = {
|
||||
matcher: [
|
||||
'/((?!api|_next/static|api-doc|_next/image|site\.webmanifest|web-app-manifest-(?:192x192|512x512)\.png|apple-touch-icon.png|favicon(?:-(?:dark|light))?\.(?:png|svg|ico)|fonts).*)',
|
||||
'/((?!api|_next/static|api-doc|api-doc|_next/image|site\.webmanifest|web-app-manifest-(?:192x192|512x512)\.png|apple-touch-icon.png|favicon(?:-(?:dark|light))?\.(?:png|svg|ico)|fonts).*)',
|
||||
],
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue