mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-02-16 20:13:49 +00:00
Restructure codebase to use src directory
This commit is contained in:
33
src/services/BeerComment/createNewBeerComment.ts
Normal file
33
src/services/BeerComment/createNewBeerComment.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
import { z } from 'zod';
|
||||
import BeerCommentValidationSchema from './schema/CreateBeerCommentValidationSchema';
|
||||
|
||||
const CreateNewBeerCommentServiceSchema = BeerCommentValidationSchema.extend({
|
||||
userId: z.string().uuid(),
|
||||
beerPostId: z.string().uuid(),
|
||||
});
|
||||
|
||||
const createNewBeerComment = async ({
|
||||
content,
|
||||
rating,
|
||||
beerPostId,
|
||||
userId,
|
||||
}: z.infer<typeof CreateNewBeerCommentServiceSchema>) => {
|
||||
return DBClient.instance.beerComment.create({
|
||||
data: {
|
||||
content,
|
||||
rating,
|
||||
beerPost: { connect: { id: beerPostId } },
|
||||
postedBy: { connect: { id: userId } },
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
content: true,
|
||||
rating: true,
|
||||
postedBy: { select: { id: true, username: true } },
|
||||
createdAt: true,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export default createNewBeerComment;
|
||||
28
src/services/BeerComment/getAllBeerComments.ts
Normal file
28
src/services/BeerComment/getAllBeerComments.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
import beerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult';
|
||||
import { z } from 'zod';
|
||||
import BeerCommentQueryResult from './schema/BeerCommentQueryResult';
|
||||
|
||||
const getAllBeerComments = async (
|
||||
{ id }: Pick<z.infer<typeof beerPostQueryResult>, 'id'>,
|
||||
{ pageSize, pageNum = 0 }: { pageSize: number; pageNum?: number },
|
||||
) => {
|
||||
const skip = (pageNum - 1) * pageSize;
|
||||
const beerComments: z.infer<typeof BeerCommentQueryResult>[] =
|
||||
await DBClient.instance.beerComment.findMany({
|
||||
skip,
|
||||
take: pageSize,
|
||||
where: { beerPostId: id },
|
||||
select: {
|
||||
id: true,
|
||||
content: true,
|
||||
rating: true,
|
||||
createdAt: true,
|
||||
postedBy: { select: { id: true, username: true, createdAt: true } },
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
return beerComments;
|
||||
};
|
||||
|
||||
export default getAllBeerComments;
|
||||
11
src/services/BeerComment/getBeerCommentCount.ts
Normal file
11
src/services/BeerComment/getBeerCommentCount.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
|
||||
const getBeerCommentCount = async (beerPostId: string) => {
|
||||
const count = await DBClient.instance.beerComment.count({
|
||||
where: { beerPostId },
|
||||
});
|
||||
|
||||
return count;
|
||||
};
|
||||
|
||||
export default getBeerCommentCount;
|
||||
14
src/services/BeerComment/schema/BeerCommentQueryResult.ts
Normal file
14
src/services/BeerComment/schema/BeerCommentQueryResult.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const BeerCommentQueryResult = z.object({
|
||||
id: z.string().uuid(),
|
||||
content: z.string().min(1).max(500),
|
||||
rating: z.number().int().min(1).max(5),
|
||||
createdAt: z.coerce.date(),
|
||||
postedBy: z.object({
|
||||
id: z.string().uuid(),
|
||||
username: z.string().min(1).max(50),
|
||||
}),
|
||||
});
|
||||
|
||||
export default BeerCommentQueryResult;
|
||||
@@ -0,0 +1,15 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const BeerCommentValidationSchema = z.object({
|
||||
content: z
|
||||
.string()
|
||||
.min(1, { message: 'Comment must not be empty.' })
|
||||
.max(300, { message: 'Comment must be less than 300 characters.' }),
|
||||
rating: z
|
||||
.number()
|
||||
.int()
|
||||
.min(1, { message: 'Rating must be greater than 1.' })
|
||||
.max(5, { message: 'Rating must be less than 5.' }),
|
||||
});
|
||||
|
||||
export default BeerCommentValidationSchema;
|
||||
46
src/services/BeerPost/createNewBeerPost.ts
Normal file
46
src/services/BeerPost/createNewBeerPost.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
import { z } from 'zod';
|
||||
import beerPostQueryResult from './schema/BeerPostQueryResult';
|
||||
import CreateBeerPostValidationSchema from './schema/CreateBeerPostValidationSchema';
|
||||
|
||||
const CreateBeerPostWithUserSchema = CreateBeerPostValidationSchema.extend({
|
||||
userId: z.string().uuid(),
|
||||
});
|
||||
|
||||
const createNewBeerPost = async ({
|
||||
name,
|
||||
description,
|
||||
abv,
|
||||
ibu,
|
||||
typeId,
|
||||
breweryId,
|
||||
userId,
|
||||
}: z.infer<typeof CreateBeerPostWithUserSchema>) => {
|
||||
const newBeerPost: z.infer<typeof beerPostQueryResult> =
|
||||
await DBClient.instance.beerPost.create({
|
||||
data: {
|
||||
name,
|
||||
description,
|
||||
abv,
|
||||
ibu,
|
||||
type: { connect: { id: typeId } },
|
||||
postedBy: { connect: { id: userId } },
|
||||
brewery: { connect: { id: breweryId } },
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
description: true,
|
||||
abv: true,
|
||||
ibu: true,
|
||||
createdAt: true,
|
||||
beerImages: { select: { id: true, path: true, caption: true, alt: true } },
|
||||
brewery: { select: { id: true, name: true } },
|
||||
type: { select: { id: true, name: true } },
|
||||
postedBy: { select: { id: true, username: true } },
|
||||
},
|
||||
});
|
||||
return newBeerPost;
|
||||
};
|
||||
|
||||
export default createNewBeerPost;
|
||||
14
src/services/BeerPost/editBeerPostById.ts
Normal file
14
src/services/BeerPost/editBeerPostById.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
import { z } from 'zod';
|
||||
import EditBeerPostValidationSchema from './schema/EditBeerPostValidationSchema';
|
||||
|
||||
const schema = EditBeerPostValidationSchema.omit({ id: true });
|
||||
|
||||
export default async function editBeerPostById(id: string, data: z.infer<typeof schema>) {
|
||||
const beerPost = await DBClient.instance.beerPost.update({
|
||||
where: { id },
|
||||
data,
|
||||
});
|
||||
|
||||
return beerPost;
|
||||
}
|
||||
32
src/services/BeerPost/getAllBeerPosts.ts
Normal file
32
src/services/BeerPost/getAllBeerPosts.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
import beerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult';
|
||||
import { z } from 'zod';
|
||||
|
||||
const prisma = DBClient.instance;
|
||||
|
||||
const getAllBeerPosts = async (pageNum: number, pageSize: number) => {
|
||||
const skip = (pageNum - 1) * pageSize;
|
||||
|
||||
const beerPosts: z.infer<typeof beerPostQueryResult>[] = await prisma.beerPost.findMany(
|
||||
{
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
ibu: true,
|
||||
abv: true,
|
||||
description: true,
|
||||
createdAt: true,
|
||||
type: { select: { name: true, id: true } },
|
||||
brewery: { select: { name: true, id: true } },
|
||||
postedBy: { select: { id: true, username: true } },
|
||||
beerImages: { select: { path: true, caption: true, id: true, alt: true } },
|
||||
},
|
||||
take: pageSize,
|
||||
skip,
|
||||
},
|
||||
);
|
||||
|
||||
return beerPosts;
|
||||
};
|
||||
|
||||
export default getAllBeerPosts;
|
||||
28
src/services/BeerPost/getBeerPostById.ts
Normal file
28
src/services/BeerPost/getBeerPostById.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
import beerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult';
|
||||
import { z } from 'zod';
|
||||
|
||||
const prisma = DBClient.instance;
|
||||
|
||||
const getBeerPostById = async (id: string) => {
|
||||
const beerPost: z.infer<typeof beerPostQueryResult> | null =
|
||||
await prisma.beerPost.findFirst({
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
ibu: true,
|
||||
abv: true,
|
||||
createdAt: true,
|
||||
description: true,
|
||||
postedBy: { select: { username: true, id: true } },
|
||||
brewery: { select: { name: true, id: true } },
|
||||
type: { select: { name: true, id: true } },
|
||||
beerImages: { select: { alt: true, path: true, caption: true, id: true } },
|
||||
},
|
||||
where: { id },
|
||||
});
|
||||
|
||||
return beerPost;
|
||||
};
|
||||
|
||||
export default getBeerPostById;
|
||||
22
src/services/BeerPost/getBeerRecommendations.ts
Normal file
22
src/services/BeerPost/getBeerRecommendations.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
import beerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult';
|
||||
import { z } from 'zod';
|
||||
|
||||
const getBeerRecommendations = async (
|
||||
beerPost: Pick<z.infer<typeof beerPostQueryResult>, 'type' | 'brewery' | 'id'>,
|
||||
) => {
|
||||
const beerRecommendations = await DBClient.instance.beerPost.findMany({
|
||||
where: {
|
||||
OR: [{ typeId: beerPost.type.id }, { breweryId: beerPost.brewery.id }],
|
||||
NOT: { id: beerPost.id },
|
||||
},
|
||||
include: {
|
||||
beerImages: { select: { id: true, path: true, caption: true, alt: true } },
|
||||
brewery: { select: { id: true, name: true } },
|
||||
},
|
||||
});
|
||||
|
||||
return beerRecommendations;
|
||||
};
|
||||
|
||||
export default getBeerRecommendations;
|
||||
18
src/services/BeerPost/schema/BeerPostQueryResult.ts
Normal file
18
src/services/BeerPost/schema/BeerPostQueryResult.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const beerPostQueryResult = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
brewery: z.object({ id: z.string(), name: z.string() }),
|
||||
description: z.string(),
|
||||
beerImages: z.array(
|
||||
z.object({ path: z.string(), caption: z.string(), id: z.string(), alt: z.string() }),
|
||||
),
|
||||
ibu: z.number(),
|
||||
abv: z.number(),
|
||||
type: z.object({ id: z.string(), name: z.string() }),
|
||||
postedBy: z.object({ id: z.string(), username: z.string() }),
|
||||
createdAt: z.coerce.date(),
|
||||
});
|
||||
|
||||
export default beerPostQueryResult;
|
||||
@@ -0,0 +1,15 @@
|
||||
import { BeerPost } from '@prisma/client';
|
||||
|
||||
type BeerRecommendationQueryResult = BeerPost & {
|
||||
brewery: {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
beerImages: {
|
||||
id: string;
|
||||
alt: string;
|
||||
url: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
export default BeerRecommendationQueryResult;
|
||||
@@ -0,0 +1,43 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const CreateBeerPostValidationSchema = z.object({
|
||||
name: z
|
||||
.string({
|
||||
required_error: 'Beer name is required.',
|
||||
invalid_type_error: 'Beer name must be a string.',
|
||||
})
|
||||
.min(1, { message: 'Beer name is required.' })
|
||||
.max(100, { message: 'Beer name is too long.' }),
|
||||
description: z
|
||||
.string()
|
||||
.min(1, { message: 'Description is required.' })
|
||||
.max(500, { message: 'Description is too long.' }),
|
||||
abv: z
|
||||
.number({
|
||||
required_error: 'ABV is required.',
|
||||
invalid_type_error: 'ABV must be a number.',
|
||||
})
|
||||
.min(0.1, { message: 'ABV must be greater than 0.1.' })
|
||||
.max(50, { message: 'ABV must be less than 50.' }),
|
||||
ibu: z
|
||||
.number({
|
||||
required_error: 'IBU is required.',
|
||||
invalid_type_error: 'IBU must be a number.',
|
||||
})
|
||||
.min(2, { message: 'IBU must be greater than 2.' })
|
||||
.max(100, { message: 'IBU must be less than 100.' }),
|
||||
typeId: z
|
||||
.string({
|
||||
required_error: 'Type id is required.',
|
||||
invalid_type_error: 'Type id must be a string.',
|
||||
})
|
||||
.uuid({ message: 'Invalid type id.' }),
|
||||
breweryId: z
|
||||
.string({
|
||||
required_error: 'Brewery id is required.',
|
||||
invalid_type_error: 'Brewery id must be a string.',
|
||||
})
|
||||
.uuid({ message: 'Invalid brewery id.' }),
|
||||
});
|
||||
|
||||
export default CreateBeerPostValidationSchema;
|
||||
@@ -0,0 +1,9 @@
|
||||
import { z } from 'zod';
|
||||
import CreateBeerPostValidationSchema from './CreateBeerPostValidationSchema';
|
||||
|
||||
const EditBeerPostValidationSchema = CreateBeerPostValidationSchema.omit({
|
||||
breweryId: true,
|
||||
typeId: true,
|
||||
}).extend({ id: z.string().uuid() });
|
||||
|
||||
export default EditBeerPostValidationSchema;
|
||||
20
src/services/BeerPostLike/createBeerPostLike.ts
Normal file
20
src/services/BeerPostLike/createBeerPostLike.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
import { z } from 'zod';
|
||||
import GetUserSchema from '../User/schema/GetUserSchema';
|
||||
|
||||
const createBeerPostLike = async ({
|
||||
id,
|
||||
user,
|
||||
}: {
|
||||
id: string;
|
||||
user: z.infer<typeof GetUserSchema>;
|
||||
}) => {
|
||||
return DBClient.instance.beerPostLike.create({
|
||||
data: {
|
||||
beerPost: { connect: { id } },
|
||||
likedBy: { connect: { id: user.id } },
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export default createBeerPostLike;
|
||||
6
src/services/BeerPostLike/findBeerPostLikeById.ts
Normal file
6
src/services/BeerPostLike/findBeerPostLikeById.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
|
||||
const findBeerPostLikeById = async (beerPostId: string, likedById: string) =>
|
||||
DBClient.instance.beerPostLike.findFirst({ where: { beerPostId, likedById } });
|
||||
|
||||
export default findBeerPostLikeById;
|
||||
11
src/services/BeerPostLike/getBeerPostLikeCount.ts
Normal file
11
src/services/BeerPostLike/getBeerPostLikeCount.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
|
||||
const getBeerPostLikeCount = async (beerPostId: string) => {
|
||||
const count = await DBClient.instance.beerPostLike.count({
|
||||
where: { beerPostId },
|
||||
});
|
||||
|
||||
return count;
|
||||
};
|
||||
|
||||
export default getBeerPostLikeCount;
|
||||
11
src/services/BeerPostLike/removeBeerPostLikeById.ts
Normal file
11
src/services/BeerPostLike/removeBeerPostLikeById.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
|
||||
const removeBeerPostLikeById = async (id: string) => {
|
||||
return DBClient.instance.beerPostLike.delete({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export default removeBeerPostLikeById;
|
||||
22
src/services/BreweryPost/getAllBreweryPosts.ts
Normal file
22
src/services/BreweryPost/getAllBreweryPosts.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
import BreweryPostQueryResult from '@/services/BreweryPost/types/BreweryPostQueryResult';
|
||||
import { z } from 'zod';
|
||||
|
||||
const prisma = DBClient.instance;
|
||||
|
||||
const getAllBreweryPosts = async () => {
|
||||
const breweryPosts: z.infer<typeof BreweryPostQueryResult>[] =
|
||||
await prisma.breweryPost.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
location: true,
|
||||
name: true,
|
||||
postedBy: { select: { username: true, id: true } },
|
||||
breweryImages: { select: { path: true, caption: true, id: true, alt: true } },
|
||||
},
|
||||
});
|
||||
|
||||
return breweryPosts;
|
||||
};
|
||||
|
||||
export default getAllBreweryPosts;
|
||||
23
src/services/BreweryPost/getBreweryPostById.ts
Normal file
23
src/services/BreweryPost/getBreweryPostById.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
import BreweryPostQueryResult from '@/services/BreweryPost/types/BreweryPostQueryResult';
|
||||
import { z } from 'zod';
|
||||
|
||||
const prisma = DBClient.instance;
|
||||
|
||||
const getBreweryPostById = async (id: string) => {
|
||||
const breweryPost: z.infer<typeof BreweryPostQueryResult> | null =
|
||||
await prisma.breweryPost.findFirst({
|
||||
select: {
|
||||
id: true,
|
||||
location: true,
|
||||
name: true,
|
||||
breweryImages: { select: { path: true, caption: true, id: true, alt: true } },
|
||||
postedBy: { select: { username: true, id: true } },
|
||||
},
|
||||
where: { id },
|
||||
});
|
||||
|
||||
return breweryPost;
|
||||
};
|
||||
|
||||
export default getBreweryPostById;
|
||||
13
src/services/BreweryPost/types/BreweryPostQueryResult.ts
Normal file
13
src/services/BreweryPost/types/BreweryPostQueryResult.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const BreweryPostQueryResult = z.object({
|
||||
id: z.string(),
|
||||
location: z.string(),
|
||||
name: z.string(),
|
||||
postedBy: z.object({ id: z.string(), username: z.string() }),
|
||||
breweryImages: z.array(
|
||||
z.object({ path: z.string(), caption: z.string(), id: z.string(), alt: z.string() }),
|
||||
),
|
||||
});
|
||||
|
||||
export default BreweryPostQueryResult;
|
||||
40
src/services/User/createNewUser.ts
Normal file
40
src/services/User/createNewUser.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { hashPassword } from '@/config/auth/passwordFns';
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
import { z } from 'zod';
|
||||
import CreateUserValidationSchema from './schema/CreateUserValidationSchema';
|
||||
import GetUserSchema from './schema/GetUserSchema';
|
||||
|
||||
const createNewUser = async ({
|
||||
email,
|
||||
password,
|
||||
firstName,
|
||||
lastName,
|
||||
dateOfBirth,
|
||||
username,
|
||||
}: z.infer<typeof CreateUserValidationSchema>) => {
|
||||
const hash = await hashPassword(password);
|
||||
const user: z.infer<typeof GetUserSchema> = await DBClient.instance.user.create({
|
||||
data: {
|
||||
username,
|
||||
email,
|
||||
hash,
|
||||
firstName,
|
||||
lastName,
|
||||
dateOfBirth: new Date(dateOfBirth),
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
email: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
dateOfBirth: true,
|
||||
createdAt: true,
|
||||
isAccountVerified: true,
|
||||
},
|
||||
});
|
||||
|
||||
return user;
|
||||
};
|
||||
|
||||
export default createNewUser;
|
||||
13
src/services/User/findUserByEmail.ts
Normal file
13
src/services/User/findUserByEmail.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
|
||||
const findUserByEmail = async (email: string) =>
|
||||
DBClient.instance.user.findFirst({
|
||||
where: { email },
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
hash: true,
|
||||
},
|
||||
});
|
||||
|
||||
export default findUserByEmail;
|
||||
24
src/services/User/findUserById.ts
Normal file
24
src/services/User/findUserById.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
import { z } from 'zod';
|
||||
import GetUserSchema from './schema/GetUserSchema';
|
||||
|
||||
const findUserById = async (id: string) => {
|
||||
const user: z.infer<typeof GetUserSchema> | null =
|
||||
await DBClient.instance.user.findUnique({
|
||||
where: { id },
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
email: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
dateOfBirth: true,
|
||||
createdAt: true,
|
||||
isAccountVerified: true,
|
||||
},
|
||||
});
|
||||
|
||||
return user;
|
||||
};
|
||||
|
||||
export default findUserById;
|
||||
13
src/services/User/findUserByUsername.ts
Normal file
13
src/services/User/findUserByUsername.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
|
||||
const findUserByUsername = async (username: string) =>
|
||||
DBClient.instance.user.findFirst({
|
||||
where: { username },
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
hash: true,
|
||||
},
|
||||
});
|
||||
|
||||
export default findUserByUsername;
|
||||
53
src/services/User/schema/CreateUserValidationSchema.ts
Normal file
53
src/services/User/schema/CreateUserValidationSchema.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import sub from 'date-fns/sub';
|
||||
import { z } from 'zod';
|
||||
|
||||
const minimumDateOfBirth = sub(new Date(), { years: 19 });
|
||||
const CreateUserValidationSchema = z
|
||||
.object({
|
||||
email: z.string().email({ message: 'Email must be a valid email address.' }),
|
||||
// use special characters, numbers, and uppercase letters
|
||||
password: z
|
||||
.string()
|
||||
.min(8, { message: 'Password must be at least 8 characters.' })
|
||||
.refine((password) => /[A-Z]/.test(password), {
|
||||
message: 'Password must contain at least one uppercase letter.',
|
||||
})
|
||||
.refine((password) => /[0-9]/.test(password), {
|
||||
message: 'Password must contain at least one number.',
|
||||
})
|
||||
.refine((password) => /[^a-zA-Z0-9]/.test(password), {
|
||||
message: 'Password must contain at least one special character.',
|
||||
}),
|
||||
confirmPassword: z.string(),
|
||||
firstName: z
|
||||
.string()
|
||||
.min(1, { message: 'First name must not be empty.' })
|
||||
.max(20, { message: 'First name must be less than 20 characters.' })
|
||||
.refine((firstName) => /^[a-zA-Z]+$/.test(firstName), {
|
||||
message: 'First name must only contain letters.',
|
||||
}),
|
||||
lastName: z
|
||||
.string()
|
||||
.min(1, { message: 'Last name must not be empty.' })
|
||||
.max(20, { message: 'Last name must be less than 20 characters.' })
|
||||
.refine((lastName) => /^[a-zA-Z]+$/.test(lastName), {
|
||||
message: 'Last name must only contain letters.',
|
||||
}),
|
||||
username: z
|
||||
.string()
|
||||
.min(1, { message: 'Username must not be empty.' })
|
||||
.max(20, { message: 'Username must be less than 20 characters.' }),
|
||||
dateOfBirth: z.string().refine(
|
||||
(dateOfBirth) => {
|
||||
const parsedDateOfBirth = new Date(dateOfBirth);
|
||||
return parsedDateOfBirth <= minimumDateOfBirth;
|
||||
},
|
||||
{ message: 'You must be at least 19 years old to register.' },
|
||||
),
|
||||
})
|
||||
.refine((data) => data.password === data.confirmPassword, {
|
||||
message: 'Passwords do not match.',
|
||||
path: ['confirmPassword'],
|
||||
});
|
||||
|
||||
export default CreateUserValidationSchema;
|
||||
15
src/services/User/schema/GetUserSchema.ts
Normal file
15
src/services/User/schema/GetUserSchema.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const GetUserSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
username: z.string(),
|
||||
createdAt: z.coerce.date(),
|
||||
updatedAt: z.coerce.date().optional(),
|
||||
email: z.string().email(),
|
||||
firstName: z.string(),
|
||||
lastName: z.string(),
|
||||
dateOfBirth: z.coerce.date(),
|
||||
isAccountVerified: z.boolean(),
|
||||
});
|
||||
|
||||
export default GetUserSchema;
|
||||
18
src/services/User/schema/LoginValidationSchema.ts
Normal file
18
src/services/User/schema/LoginValidationSchema.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const LoginValidationSchema = z.object({
|
||||
username: z
|
||||
.string({
|
||||
required_error: 'Username is required.',
|
||||
invalid_type_error: 'Username must be a string.',
|
||||
})
|
||||
.min(1, { message: 'Username is required.' }),
|
||||
password: z
|
||||
.string({
|
||||
required_error: 'Password is required.',
|
||||
invalid_type_error: 'Password must be a string.',
|
||||
})
|
||||
.min(1, { message: 'Password is required.' }),
|
||||
});
|
||||
|
||||
export default LoginValidationSchema;
|
||||
26
src/services/User/sendConfirmationEmail.ts
Normal file
26
src/services/User/sendConfirmationEmail.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { generateConfirmationToken } from '@/config/jwt';
|
||||
import sendEmail from '@/config/sparkpost/sendEmail';
|
||||
|
||||
import Welcome from '@/emails/Welcome';
|
||||
import { render } from '@react-email/render';
|
||||
import { z } from 'zod';
|
||||
import { BASE_URL } from '@/config/env';
|
||||
import GetUserSchema from './schema/GetUserSchema';
|
||||
|
||||
type UserSchema = z.infer<typeof GetUserSchema>;
|
||||
|
||||
const sendConfirmationEmail = async ({ id, username, email }: UserSchema) => {
|
||||
const confirmationToken = generateConfirmationToken({ id, username });
|
||||
|
||||
const subject = 'Confirm your email';
|
||||
const name = username;
|
||||
const url = `${BASE_URL}/api/users/confirm?token=${confirmationToken}`;
|
||||
const address = email;
|
||||
|
||||
const html = render(Welcome({ name, url, subject })!);
|
||||
const text = render(Welcome({ name, url, subject })!, { plainText: true });
|
||||
|
||||
await sendEmail({ address, subject, text, html });
|
||||
};
|
||||
|
||||
export default sendConfirmationEmail;
|
||||
24
src/services/User/updateUserToBeConfirmedById.ts
Normal file
24
src/services/User/updateUserToBeConfirmedById.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import GetUserSchema from '@/services/User/schema/GetUserSchema';
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
import { z } from 'zod';
|
||||
|
||||
const updateUserToBeConfirmedById = async (id: string) => {
|
||||
const user: z.infer<typeof GetUserSchema> = await DBClient.instance.user.update({
|
||||
where: { id },
|
||||
data: { isAccountVerified: true, updatedAt: new Date() },
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
email: true,
|
||||
isAccountVerified: true,
|
||||
createdAt: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
dateOfBirth: true,
|
||||
},
|
||||
});
|
||||
|
||||
return user;
|
||||
};
|
||||
|
||||
export default updateUserToBeConfirmedById;
|
||||
Reference in New Issue
Block a user