From e48c9b3e0948064060e3732922d5b94aba1e85d9 Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Thu, 10 Oct 2024 16:40:49 -0700 Subject: [PATCH] Using a different OpenAPI dependency for hono for now, adding docs for IAM. --- src/lib/server/api/common/exceptions.ts | 30 +++- src/lib/server/api/common/types/controller.ts | 12 +- src/lib/server/api/common/types/hono.ts | 4 + src/lib/server/api/configure-open-api.ts | 91 ++++++----- .../server/api/controllers/iam.controller.ts | 31 ++-- src/lib/server/api/controllers/iam.routes.ts | 48 ++++++ .../api/databases/tables/users.table.ts | 19 ++- src/lib/server/api/index.ts | 10 +- .../api/repositories/users.repository.ts | 32 ++-- .../settings/profile/+page.server.ts | 86 +++++------ .../(protected)/settings/profile/+page.svelte | 143 ++++++++++-------- .../(protected)/settings/profile/schemas.ts | 14 +- 12 files changed, 317 insertions(+), 203 deletions(-) create mode 100644 src/lib/server/api/controllers/iam.routes.ts diff --git a/src/lib/server/api/common/exceptions.ts b/src/lib/server/api/common/exceptions.ts index cbda928..36989b4 100644 --- a/src/lib/server/api/common/exceptions.ts +++ b/src/lib/server/api/common/exceptions.ts @@ -1,26 +1,40 @@ -import { StatusCodes } from '$lib/constants/status-codes' -import { HTTPException } from 'hono/http-exception' +import { StatusCodes } from '$lib/constants/status-codes'; +import { HTTPException } from 'hono/http-exception'; +import * as HttpStatusPhrases from 'stoker/http-status-phrases'; +import { createMessageObjectSchema } from 'stoker/openapi/schemas'; export function TooManyRequests(message = 'Too many requests') { - return new HTTPException(StatusCodes.TOO_MANY_REQUESTS, { message }) + return new HTTPException(StatusCodes.TOO_MANY_REQUESTS, { message }); } +export const tooManyRequestsSchema = createMessageObjectSchema(HttpStatusPhrases.TOO_MANY_REQUESTS); + export function Forbidden(message = 'Forbidden') { - return new HTTPException(StatusCodes.FORBIDDEN, { message }) + return new HTTPException(StatusCodes.FORBIDDEN, { message }); } +export const forbiddenSchema = createMessageObjectSchema(HttpStatusPhrases.FORBIDDEN); + export function Unauthorized(message = 'Unauthorized') { - return new HTTPException(StatusCodes.UNAUTHORIZED, { message }) + return new HTTPException(StatusCodes.UNAUTHORIZED, { message }); } +export const unauthorizedSchema = createMessageObjectSchema(HttpStatusPhrases.UNAUTHORIZED); + export function NotFound(message = 'Not Found') { - return new HTTPException(StatusCodes.NOT_FOUND, { message }) + return new HTTPException(StatusCodes.NOT_FOUND, { message }); } +export const notFoundSchema = createMessageObjectSchema(HttpStatusPhrases.NOT_FOUND); + export function BadRequest(message = 'Bad Request') { - return new HTTPException(StatusCodes.BAD_REQUEST, { message }) + return new HTTPException(StatusCodes.BAD_REQUEST, { message }); } +export const badRequestSchema = createMessageObjectSchema(HttpStatusPhrases.BAD_REQUEST); + export function InternalError(message = 'Internal Error') { - return new HTTPException(StatusCodes.INTERNAL_SERVER_ERROR, { message }) + return new HTTPException(StatusCodes.INTERNAL_SERVER_ERROR, { message }); } + +export const internalErrorSchema = createMessageObjectSchema(HttpStatusPhrases.INTERNAL_SERVER_ERROR); diff --git a/src/lib/server/api/common/types/controller.ts b/src/lib/server/api/common/types/controller.ts index fd77884..517d509 100644 --- a/src/lib/server/api/common/types/controller.ts +++ b/src/lib/server/api/common/types/controller.ts @@ -1,11 +1,11 @@ -import { Hono } from 'hono' -import type { BlankSchema } from 'hono/types' -import type { HonoTypes } from './hono' +import { Hono } from 'hono'; +import type { BlankSchema } from 'hono/types'; +import type { AppBindings } from './hono'; export abstract class Controller { - protected readonly controller: Hono + protected readonly controller: Hono; constructor() { - this.controller = new Hono() + this.controller = new Hono(); } - abstract routes(): Hono + abstract routes(): Hono; } diff --git a/src/lib/server/api/common/types/hono.ts b/src/lib/server/api/common/types/hono.ts index 0974d5b..d2c2837 100644 --- a/src/lib/server/api/common/types/hono.ts +++ b/src/lib/server/api/common/types/hono.ts @@ -1,7 +1,11 @@ +import type { Hono } from 'hono'; import type { PinoLogger } from 'hono-pino'; import type { Promisify, RateLimitInfo } from 'hono-rate-limiter'; import type { Session, User } from 'lucia'; +// export type AppOpenAPI = OpenAPIHono; +export type AppOpenAPI = Hono; + export type AppBindings = { Variables: { logger: PinoLogger; diff --git a/src/lib/server/api/configure-open-api.ts b/src/lib/server/api/configure-open-api.ts index ae692f3..413d516 100644 --- a/src/lib/server/api/configure-open-api.ts +++ b/src/lib/server/api/configure-open-api.ts @@ -1,42 +1,59 @@ -// // import type { AppOpenAPI } from '$lib/server/api/common/types/hono'; -// import { apiReference } from '@scalar/hono-api-reference'; -// import { Hono } from 'hono'; -// -// import type { AppBindings } from '$lib/server/api/common/types/hono'; +// import type { AppOpenAPI } from '$lib/server/api/common/types/hono'; +import { apiReference } from '@scalar/hono-api-reference'; +import { Hono } from 'hono'; + +import type { AppOpenAPI } from '$lib/server/api/common/types/hono'; // import { createOpenApiDocument } from 'hono-zod-openapi'; -// import packageJSON from '../../../../package.json'; -// -// // export default function configureOpenAPI(app: AppOpenAPI) { -// // app.doc('/doc', { -// // openapi: '3.0.0', -// // info: { -// // title: 'Bored Game API', -// // description: 'Bored Game API', -// // version: packageJSON.version, -// // }, -// // }); -// // -// // app.get( -// // '/reference', -// // apiReference({ -// // theme: 'kepler', -// // layout: 'classic', -// // defaultHttpClient: { -// // targetKey: 'javascript', -// // clientKey: 'fetch', -// // }, -// // spec: { -// // url: '/api/doc', -// // }, -// // }), -// // ); -// // } -// -// export default function configureOpenAPI(app: Hono) { -// createOpenApiDocument(app, { +import { createOpenApiDocument } from 'hono-zod-openapi'; +import packageJSON from '../../../../package.json'; + +// export default function configureOpenAPI(app: AppOpenAPI) { +// app.doc('/doc', { +// openapi: '3.0.0', // info: { -// title: 'Example API', -// version: '1.0.0', +// title: 'Bored Game API', +// description: 'Bored Game API', +// version: packageJSON.version, // }, // }); +// +// app.get( +// '/reference', +// apiReference({ +// theme: 'kepler', +// layout: 'classic', +// defaultHttpClient: { +// targetKey: 'javascript', +// clientKey: 'fetch', +// }, +// spec: { +// url: '/api/doc', +// }, +// }), +// ); // } + +export default function configureOpenAPI(app: AppOpenAPI) { + createOpenApiDocument(app, { + info: { + title: 'Bored Game API', + description: 'Bored Game API', + version: packageJSON.version, + }, + }); + + app.get( + '/reference', + apiReference({ + theme: 'kepler', + layout: 'classic', + defaultHttpClient: { + targetKey: 'javascript', + clientKey: 'fetch', + }, + spec: { + url: '/api/doc', + }, + }), + ); +} diff --git a/src/lib/server/api/controllers/iam.controller.ts b/src/lib/server/api/controllers/iam.controller.ts index 32f650a..4554385 100644 --- a/src/lib/server/api/controllers/iam.controller.ts +++ b/src/lib/server/api/controllers/iam.controller.ts @@ -1,5 +1,6 @@ import { StatusCodes } from '$lib/constants/status-codes'; import { Controller } from '$lib/server/api/common/types/controller'; +import { iam, updateProfile } from '$lib/server/api/controllers/iam.routes'; import { changePasswordDto } from '$lib/server/api/dtos/change-password.dto'; import { updateEmailDto } from '$lib/server/api/dtos/update-email.dto'; import { updateProfileDto } from '$lib/server/api/dtos/update-profile.dto'; @@ -9,6 +10,7 @@ import { IamService } from '$lib/server/api/services/iam.service'; import { LoginRequestsService } from '$lib/server/api/services/loginrequest.service'; import { LuciaService } from '$lib/server/api/services/lucia.service'; import { zValidator } from '@hono/zod-validator'; +import { openApi } from 'hono-zod-openapi'; import { setCookie } from 'hono/cookie'; import { inject, injectable } from 'tsyringe'; import { requireAuth } from '../middleware/require-auth.middleware'; @@ -24,22 +26,27 @@ export class IamController extends Controller { } routes() { - const tags = ['IAM']; - return this.controller - .get('/', requireAuth, async (c) => { + .get('/', requireAuth, openApi(iam), async (c) => { const user = c.var.user; return c.json({ user }); }) - .put('/update/profile', requireAuth, zValidator('json', updateProfileDto), limiter({ limit: 30, minutes: 60 }), async (c) => { - const user = c.var.user; - const { firstName, lastName, username } = c.req.valid('json'); - const updatedUser = await this.iamService.updateProfile(user.id, { firstName, lastName, username }); - if (!updatedUser) { - return c.json('Username already in use', StatusCodes.BAD_REQUEST); - } - return c.json({ user: updatedUser }, StatusCodes.OK); - }) + .put( + '/update/profile', + requireAuth, + openApi(updateProfile), + zValidator('json', updateProfileDto), + limiter({ limit: 30, minutes: 60 }), + async (c) => { + const user = c.var.user; + const { firstName, lastName, username } = c.req.valid('json'); + const updatedUser = await this.iamService.updateProfile(user.id, { firstName, lastName, username }); + if (!updatedUser) { + return c.json('Username already in use', StatusCodes.BAD_REQUEST); + } + return c.json({ user: updatedUser }, StatusCodes.OK); + }, + ) .post('/verify/password', requireAuth, zValidator('json', verifyPasswordDto), limiter({ limit: 10, minutes: 60 }), async (c) => { const user = c.var.user; const { password } = c.req.valid('json'); diff --git a/src/lib/server/api/controllers/iam.routes.ts b/src/lib/server/api/controllers/iam.routes.ts new file mode 100644 index 0000000..bee1b65 --- /dev/null +++ b/src/lib/server/api/controllers/iam.routes.ts @@ -0,0 +1,48 @@ +import { StatusCodes } from '$lib/constants/status-codes'; +import { unauthorizedSchema } from '$lib/server/api/common/exceptions'; +import { selectUserSchema } from '$lib/server/api/databases/tables/users.table'; +import { updateProfileDto } from '$lib/server/api/dtos/update-profile.dto'; +import { defineOpenApiOperation } from 'hono-zod-openapi'; +import { createErrorSchema } from 'stoker/openapi/schemas'; + +const tags = ['IAM']; + +export const iam = defineOpenApiOperation({ + tags, + responses: { + [StatusCodes.OK]: { + description: 'User profile', + schema: selectUserSchema, + mediaType: 'application/json', + }, + [StatusCodes.UNAUTHORIZED]: { + description: 'Unauthorized', + schema: createErrorSchema(unauthorizedSchema), + mediaType: 'application/json', + }, + }, +}); + +export const updateProfile = defineOpenApiOperation({ + tags, + request: { + json: updateProfileDto, + }, + responses: { + [StatusCodes.OK]: { + description: 'Updated User', + schema: selectUserSchema, + mediaType: 'application/json', + }, + [StatusCodes.UNPROCESSABLE_ENTITY]: { + description: 'The validation error(s)', + schema: createErrorSchema(updateProfileDto), + mediaType: 'application/json', + }, + [StatusCodes.UNAUTHORIZED]: { + description: 'Unauthorized', + schema: createErrorSchema(unauthorizedSchema), + mediaType: 'application/json', + }, + }, +}); diff --git a/src/lib/server/api/databases/tables/users.table.ts b/src/lib/server/api/databases/tables/users.table.ts index c244bbd..6ae09df 100644 --- a/src/lib/server/api/databases/tables/users.table.ts +++ b/src/lib/server/api/databases/tables/users.table.ts @@ -1,8 +1,9 @@ -import { createId as cuid2 } from '@paralleldrive/cuid2' -import { type InferSelectModel, relations } from 'drizzle-orm' -import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core' -import { timestamps } from '../../common/utils/table' -import { user_roles } from './userRoles.table' +import { createId as cuid2 } from '@paralleldrive/cuid2'; +import { type InferSelectModel, relations } from 'drizzle-orm'; +import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core'; +import { createSelectSchema } from 'drizzle-zod'; +import { timestamps } from '../../common/utils/table'; +import { user_roles } from './userRoles.table'; export const usersTable = pgTable('users', { id: uuid('id').primaryKey().defaultRandom(), @@ -20,10 +21,12 @@ export const usersTable = pgTable('users', { mfa_enabled: boolean('mfa_enabled').notNull().default(false), theme: text('theme').default('system'), ...timestamps, -}) +}); export const userRelations = relations(usersTable, ({ many }) => ({ user_roles: many(user_roles), -})) +})); -export type Users = InferSelectModel +export const selectUserSchema = createSelectSchema(usersTable); + +export type Users = InferSelectModel; diff --git a/src/lib/server/api/index.ts b/src/lib/server/api/index.ts index 28095bf..a143232 100644 --- a/src/lib/server/api/index.ts +++ b/src/lib/server/api/index.ts @@ -1,5 +1,6 @@ import 'reflect-metadata'; import createApp from '$lib/server/api/common/create-app'; +import configureOpenAPI from '$lib/server/api/configure-open-api'; import { CollectionController } from '$lib/server/api/controllers/collection.controller'; import { MfaController } from '$lib/server/api/controllers/mfa.controller'; import { OAuthController } from '$lib/server/api/controllers/oauth.controller'; @@ -7,15 +8,17 @@ import { SignupController } from '$lib/server/api/controllers/signup.controller' import { UserController } from '$lib/server/api/controllers/user.controller'; import { WishlistController } from '$lib/server/api/controllers/wishlist.controller'; import { AuthCleanupJobs } from '$lib/server/api/jobs/auth-cleanup.job'; +import { extendZodWithOpenApi } from 'hono-zod-openapi'; import { hc } from 'hono/client'; import { container } from 'tsyringe'; +import { z } from 'zod'; import { config } from './common/config'; import { IamController } from './controllers/iam.controller'; import { LoginController } from './controllers/login.controller'; -export const app = createApp(); +extendZodWithOpenApi(z); -// configureOpenAPI(app); +export const app = createApp(); /* -------------------------------------------------------------------------- */ /* Routes */ @@ -31,6 +34,9 @@ const routes = app .route('/mfa', container.resolve(MfaController).routes()) .get('/', (c) => c.json({ message: 'Server is healthy' })); +// @ts-ignore - this is a workaround for https://github.com/paolostyle/hono-zod-openapi/issues/2 +configureOpenAPI(app); + /* -------------------------------------------------------------------------- */ /* Cron Jobs */ /* -------------------------------------------------------------------------- */ diff --git a/src/lib/server/api/repositories/users.repository.ts b/src/lib/server/api/repositories/users.repository.ts index 240c66c..4ee1ba3 100644 --- a/src/lib/server/api/repositories/users.repository.ts +++ b/src/lib/server/api/repositories/users.repository.ts @@ -1,8 +1,8 @@ -import { usersTable } from '$lib/server/api/databases/tables/users.table' -import { DrizzleService } from '$lib/server/api/services/drizzle.service' -import { type InferInsertModel, eq } from 'drizzle-orm' -import { inject, injectable } from 'tsyringe' -import { takeFirstOrThrow } from '../common/utils/repository' +import { usersTable } from '$lib/server/api/databases/tables/users.table'; +import { DrizzleService } from '$lib/server/api/services/drizzle.service'; +import { type InferInsertModel, eq } from 'drizzle-orm'; +import { inject, injectable } from 'tsyringe'; +import { takeFirstOrThrow } from '../common/utils/repository'; /* -------------------------------------------------------------------------- */ /* Repository */ @@ -20,8 +20,8 @@ storing data. They should not contain any business logic, only database queries. In our case the method 'trxHost' is used to set the transaction context. */ -export type CreateUser = InferInsertModel -export type UpdateUser = Partial +export type CreateUser = InferInsertModel; +export type UpdateUser = Partial; @injectable() export class UsersRepository { @@ -30,36 +30,36 @@ export class UsersRepository { async findOneById(id: string, db = this.drizzle.db) { return db.query.usersTable.findFirst({ where: eq(usersTable.id, id), - }) + }); } async findOneByIdOrThrow(id: string, db = this.drizzle.db) { - const user = await this.findOneById(id) - if (!user) throw Error('User not found') - return user + const user = await this.findOneById(id); + if (!user) throw Error('User not found'); + return user; } async findOneByUsername(username: string, db = this.drizzle.db) { return db.query.usersTable.findFirst({ where: eq(usersTable.username, username), - }) + }); } async findOneByEmail(email: string, db = this.drizzle.db) { return db.query.usersTable.findFirst({ where: eq(usersTable.email, email), - }) + }); } async create(data: CreateUser, db = this.drizzle.db) { - return db.insert(usersTable).values(data).returning().then(takeFirstOrThrow) + return db.insert(usersTable).values(data).returning().then(takeFirstOrThrow); } async update(id: string, data: UpdateUser, db = this.drizzle.db) { - return db.update(usersTable).set(data).where(eq(usersTable.id, id)).returning().then(takeFirstOrThrow) + return db.update(usersTable).set(data).where(eq(usersTable.id, id)).returning().then(takeFirstOrThrow); } async delete(id: string, db = this.drizzle.db) { - return db.delete(usersTable).where(eq(usersTable.id, id)).returning().then(takeFirstOrThrow) + return db.delete(usersTable).where(eq(usersTable.id, id)).returning().then(takeFirstOrThrow); } } diff --git a/src/routes/(app)/(protected)/settings/profile/+page.server.ts b/src/routes/(app)/(protected)/settings/profile/+page.server.ts index 1841c32..3651da5 100644 --- a/src/routes/(app)/(protected)/settings/profile/+page.server.ts +++ b/src/routes/(app)/(protected)/settings/profile/+page.server.ts @@ -1,102 +1,102 @@ -import { notSignedInMessage } from '$lib/flashMessages' -import { usersTable } from '$lib/server/api/databases/tables' -import { db } from '$lib/server/api/packages/drizzle' -import { type Actions, fail } from '@sveltejs/kit' -import { eq } from 'drizzle-orm' -import { redirect } from 'sveltekit-flash-message/server' -import { zod } from 'sveltekit-superforms/adapters' -import { message, setError, superValidate } from 'sveltekit-superforms/server' -import { z } from 'zod' -import type { PageServerLoad } from './$types' -import { updateEmailSchema, updateProfileSchema } from './schemas' +import { notSignedInMessage } from '$lib/flashMessages'; +import { usersTable } from '$lib/server/api/databases/tables'; +import { db } from '$lib/server/api/packages/drizzle'; +import { type Actions, fail } from '@sveltejs/kit'; +import { eq } from 'drizzle-orm'; +import { redirect } from 'sveltekit-flash-message/server'; +import { zod } from 'sveltekit-superforms/adapters'; +import { message, setError, superValidate } from 'sveltekit-superforms/server'; +import { z } from 'zod'; +import type { PageServerLoad } from './$types'; +import { updateEmailFormSchema, updateProfileFormSchema } from './schemas'; export const load: PageServerLoad = async (event) => { - const { locals } = event + const { locals } = event; - const authedUser = await locals.getAuthedUser() + const authedUser = await locals.getAuthedUser(); if (!authedUser) { - throw redirect(302, '/login', notSignedInMessage, event) + throw redirect(302, '/login', notSignedInMessage, event); } - const profileForm = await superValidate(zod(updateProfileSchema), { + const updateProfileForm = await superValidate(zod(updateProfileFormSchema), { defaults: { firstName: authedUser?.firstName ?? '', lastName: authedUser?.lastName ?? '', username: authedUser?.username ?? '', }, - }) - const emailForm = await superValidate(zod(updateEmailSchema), { + }); + const updateEmailForm = await superValidate(zod(updateEmailFormSchema), { defaults: { email: authedUser?.email ?? '', }, - }) + }); // const twoFactorDetails = await db.query.twoFactor.findFirst({ // where: eq(twoFactor.userId, authedUser!.id!), // }); return { - profileForm, - emailForm, + updateProfileForm, + updateEmailForm, hasSetupTwoFactor: false, //!!twoFactorDetails?.enabled, - } -} + }; +}; const changeEmailIfNotEmpty = z.object({ email: z.string().trim().max(64, { message: 'Email must be less than 64 characters' }).email({ message: 'Please enter a valid email' }), -}) +}); export const actions: Actions = { profileUpdate: async (event) => { - const { locals } = event + const { locals } = event; - const authedUser = await locals.getAuthedUser() + const authedUser = await locals.getAuthedUser(); if (!authedUser) { - redirect(302, '/login', notSignedInMessage, event) + redirect(302, '/login', notSignedInMessage, event); } - const form = await superValidate(event, zod(updateProfileSchema)) + const form = await superValidate(event, zod(updateProfileFormSchema)); - const { error } = await locals.api.me.update.profile.$put({ json: form.data }).then(locals.parseApiResponse) - console.log('data from profile update', error) + const { error } = await locals.api.me.update.profile.$put({ json: form.data }).then(locals.parseApiResponse); + console.log('data from profile update', error); if (error) { - return setError(form, 'username', error) + return setError(form, 'username', error); } if (!form.valid) { return fail(400, { form, - }) + }); } - console.log('profile updated successfully') - return message(form, { type: 'success', message: 'Profile updated successfully!' }) + console.log('profile updated successfully'); + return message(form, { type: 'success', message: 'Profile updated successfully!' }); }, changeEmail: async (event) => { - const form = await superValidate(event, zod(updateEmailSchema)) + const form = await superValidate(event, zod(updateEmailFormSchema)); - const newEmail = form.data?.email + const newEmail = form.data?.email; if (!form.valid || !newEmail || (newEmail !== '' && !changeEmailIfNotEmpty.safeParse(form.data).success)) { return fail(400, { form, - }) + }); } if (!event.locals.user) { - redirect(302, '/login', notSignedInMessage, event) + redirect(302, '/login', notSignedInMessage, event); } - const user = event.locals.user + const user = event.locals.user; const existingUser = await db.query.usersTable.findFirst({ where: eq(usersTable.email, newEmail), - }) + }); if (existingUser && existingUser.id !== user.id) { - return setError(form, 'email', 'That email is already taken') + return setError(form, 'email', 'That email is already taken'); } - await db.update(usersTable).set({ email: form.data.email }).where(eq(usersTable.id, user.id)) + await db.update(usersTable).set({ email: form.data.email }).where(eq(usersTable.id, user.id)); // if (user.email !== form.data.email) { // Send email to confirm new email? @@ -114,6 +114,6 @@ export const actions: Actions = { // }); // } - return message(form, { type: 'success', message: 'Email updated successfully!' }) + return message(form, { type: 'success', message: 'Email updated successfully!' }); }, -} +}; diff --git a/src/routes/(app)/(protected)/settings/profile/+page.svelte b/src/routes/(app)/(protected)/settings/profile/+page.svelte index 7943447..0bab340 100644 --- a/src/routes/(app)/(protected)/settings/profile/+page.svelte +++ b/src/routes/(app)/(protected)/settings/profile/+page.svelte @@ -1,26 +1,47 @@ + + -
+

Your Profile


-
- - - {#if $profileErrors.username} - {$profileErrors.username} - {/if} -
-
- - - {#if $profileErrors.firstName} - {$profileErrors.firstName} - {/if} -
-
- - - {#if $profileErrors.lastName} - {$profileErrors.lastName} - {/if} -
- + + + Username + + + + + + + + First Name + + + + + + + + Last Name + + + + + + submitProfileForm()} class="w-full">Update Profile
-
+
- - - {#if $emailErrors.email} - {$emailErrors.email} - {/if} - - {#if !$emailForm.email} + + + Email address + + + + + + submitEmailForm()} class="w-full">Update Email + {#if !$updateEmailFormData.email} Heads up! diff --git a/src/routes/(app)/(protected)/settings/profile/schemas.ts b/src/routes/(app)/(protected)/settings/profile/schemas.ts index 096a96f..47b3fad 100644 --- a/src/routes/(app)/(protected)/settings/profile/schemas.ts +++ b/src/routes/(app)/(protected)/settings/profile/schemas.ts @@ -1,12 +1,12 @@ -import { z } from 'zod' +import { z } from 'zod'; -export const updateEmailSchema = z.object({ +export const updateEmailFormSchema = z.object({ email: z.string().trim().max(64, { message: 'Email must be less than 64 characters' }).email({ message: 'Please enter a valid email' }), -}) +}); -export type UpdateEmailSchema = z.infer +export type UpdateEmailSchema = z.infer; -export const updateProfileSchema = z.object({ +export const updateProfileFormSchema = z.object({ firstName: z .string() .trim() @@ -15,6 +15,6 @@ export const updateProfileSchema = z.object({ .optional(), lastName: z.string().trim().min(3, { message: 'Must be at least 3 characters' }).max(50, { message: 'Must be less than 50 characters' }).optional(), username: z.string().trim().min(3, { message: 'Must be at least 3 characters' }).max(50, { message: 'Must be less than 50 characters' }), -}) +}); -export type UpdateProfileSchema = z.infer +export type UpdateProfileSchema = z.infer;