Skip to content

Commit

Permalink
Implement initial dream API routes
Browse files Browse the repository at this point in the history
  • Loading branch information
bombies committed Oct 25, 2023
1 parent dd5bd0e commit 27fd87b
Show file tree
Hide file tree
Showing 10 changed files with 269 additions and 11 deletions.
6 changes: 6 additions & 0 deletions src/app/(site)/(internal)/dashboard/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import {FC, PropsWithChildren} from "react";
import DashboardSidebar from "@/app/(site)/(internal)/dashboard/components/DashboardSidebar";
import {Metadata} from "next";

export const metadata: Metadata = {
title: 'Dream Logger - Dashboard',
description: 'Your Dream Logger dashboard'
}

const DashboardLayout: FC<PropsWithChildren> = ({children}) => {
return (
Expand Down
4 changes: 4 additions & 0 deletions src/app/(site)/(internal)/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
"use client"

import {FC, Fragment} from "react";
import {Spacer} from "@nextui-org/react";

const DashboardHomePage: FC = () => {
return (
<Fragment>
<h1 className="font-bold text-5xl phone:text-4xl">Your Dreams</h1>
<Spacer y={12} />
<div className="flex phone:flex-col phone:items-center justify-evenly">
</div>
</Fragment>
)
}
Expand Down
7 changes: 2 additions & 5 deletions src/app/api/auth/register/register.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@ import {Member} from "@prisma/client";
import prisma from "@/libs/prisma";
import bcrypt from 'bcrypt';
import {NextResponse} from "next/server";
import {buildResponse} from "@/app/api/utils/types";
import {buildFailedValidationResponse, buildResponse} from "@/app/api/utils/types";

class RegisterService {
public async registerUser(dto: RegisterUserDto): Promise<NextResponse<Member | undefined>> {
const dtoValidated = RegisterUserDtoSchema.safeParse(dto);
if (!dtoValidated.success)
return buildResponse({
status: 400,
message: dtoValidated.error.message
})
return buildFailedValidationResponse(dtoValidated.error)

const existingUser = await prisma.member.findFirst(({
where: {
Expand Down
16 changes: 16 additions & 0 deletions src/app/api/me/dreams/characters/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {authenticated} from "@/app/api/utils/api-utils";
import dreamsService from "@/app/api/me/dreams/dreams.service";
import {PostDreamCharacterDto} from "@/app/api/me/dreams/dreams.dto";

export const GET = async () => {
return authenticated((session) => (
dreamsService.fetchCharacters(session)
))
}

export const POST = async (req: Request) => {
return authenticated(async (session) => {
const body: PostDreamCharacterDto = await req.json()
return dreamsService.createCharacter(session, body)
})
}
58 changes: 58 additions & 0 deletions src/app/api/me/dreams/dreams.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import {z} from "zod";

export type PostDreamDto = {
title: string,
description: string,
comments?: string,
tags?: string[]
characters?: string[]
}

export const DREAM_TITLE_MIN = 1
export const DREAM_TITLE_MAX = 500
export const DREAM_DESC_MIN = 1
export const DREAM_DESC_MAX = 5000
export const DREAM_COMMENTS_MAX = 1000

export const PostDreamSchema = z.object({
title: z.string()
.min(DREAM_TITLE_MIN, `The title can't be less than ${DREAM_TITLE_MIN} character!`)
.max(DREAM_TITLE_MAX, `The title can't be more than ${DREAM_TITLE_MAX} characters!`),

description: z.string()
.min(DREAM_DESC_MIN, `The description can't be less than ${DREAM_DESC_MIN} character!`)
.max(DREAM_DESC_MAX, `The description can't be more than ${DREAM_DESC_MAX} characters!`),

comments: z.string()
.max(DREAM_COMMENTS_MAX, `The comment can't be more than ${DREAM_COMMENTS_MAX} characters!`)
.optional(),

tags: z.array(z.string()).optional(),
characters: z.array(z.string()).optional()
}).strict()

export type PostDreamTagDto = {
tag: string,
}

export const DREAM_TAG_MIN = 1
export const DREAM_TAG_MAX = 64

export const PostDreamTagSchema = z.object({
tag: z.string()
.min(DREAM_TAG_MIN, `The tag can't be less than ${DREAM_TAG_MIN} character!`)
.max(DREAM_TAG_MAX, `The tag can't be more than ${DREAM_TAG_MAX} characters!`)
}).strict()

export type PostDreamCharacterDto = {
name: string,
}

export const DREAM_CHARACTER_MIN = 1
export const DREAM_CHARACTER_MAX = 256

export const PostDreamCharacterSchema = z.object({
name: z.string()
.min(DREAM_CHARACTER_MIN, `The name can't be less than ${DREAM_CHARACTER_MIN} character!`)
.max(DREAM_CHARACTER_MAX, `The name can't be more than ${DREAM_CHARACTER_MAX} characters!`)
}).strict()
109 changes: 109 additions & 0 deletions src/app/api/me/dreams/dreams.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import {buildFailedValidationResponse, buildResponse} from "@/app/api/utils/types";
import {Dream, DreamCharacter, DreamTag} from "@prisma/client";
import prisma from "@/libs/prisma";
import {NextResponse} from "next/server";
import {Session} from "next-auth";
import {
PostDreamCharacterDto,
PostDreamCharacterSchema,
PostDreamDto,
PostDreamSchema, PostDreamTagDto
} from "@/app/api/me/dreams/dreams.dto";

class DreamsService {

public async fetchDreams(session: Session): Promise<NextResponse<Dream[] | undefined>> {
const member = session.user
const dreams = await prisma.dream.findMany({
where: {
userId: member.id
}
});

return buildResponse({
data: dreams
})
}

public async createDream(session: Session, dto: PostDreamDto): Promise<NextResponse<Dream | undefined>> {
const dtoValidated = PostDreamSchema.safeParse(dto)
if (!dtoValidated.success)
return buildFailedValidationResponse(dtoValidated.error)

const dream = await prisma.dream.create({
data: {
title: dto.title,
description: dto.description,
comments: dto.comments,
userId: session.user.id,
tags: {connect: dto.tags?.map(id => ({id})) ?? []},
characters: {connect: dto.characters?.map(id => ({id})) ?? []}
}
})

return buildResponse({
data: dream
})
}

public async fetchCharacters(session: Session) {
const characters = await prisma.dreamCharacter.findMany({
where: {
userId: session.user.id
}
})

return buildResponse({
data: characters
})
}

public async createCharacter(session: Session, dto: PostDreamCharacterDto): Promise<NextResponse<DreamCharacter | undefined>> {
const dtoValidated = PostDreamCharacterSchema.safeParse(dto)
if (!dtoValidated.success)
return buildFailedValidationResponse(dtoValidated.error)

const character = await prisma.dreamCharacter.create({
data: {
name: dto.name.toLowerCase(),
userId: session.user.id,
}
})

return buildResponse({
data: character
})
}

public async fetchTags(session: Session) {
const tags = await prisma.dreamTag.findMany({
where: {
userId: session.user.id
}
})

return buildResponse({
data: tags
})
}

public async createTag(session: Session, dto: PostDreamTagDto): Promise<NextResponse<DreamTag | undefined>> {
const dtoValidated = PostDreamCharacterSchema.safeParse(dto)
if (!dtoValidated.success)
return buildFailedValidationResponse(dtoValidated.error)

const tag = await prisma.dreamTag.create({
data: {
tag: dto.tag.toLowerCase(),
userId: session.user.id,
}
})

return buildResponse({
data: tag
})
}
}

const dreamsService = new DreamsService()
export default dreamsService
16 changes: 16 additions & 0 deletions src/app/api/me/dreams/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {authenticated} from "@/app/api/utils/api-utils";
import dreamsService from "@/app/api/me/dreams/dreams.service";
import {PostDreamDto} from "@/app/api/me/dreams/dreams.dto";

export const GET = async () => {
return authenticated((session) => (
dreamsService.fetchDreams(session)
))
}

export const POST = async (req: Request) => {
return authenticated(async (session) => {
const body: PostDreamDto = await req.json()
return dreamsService.createDream(session, body)
})
}
16 changes: 16 additions & 0 deletions src/app/api/me/dreams/tags/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {authenticated} from "@/app/api/utils/api-utils";
import dreamsService from "@/app/api/me/dreams/dreams.service";
import {PostDreamTagDto} from "@/app/api/me/dreams/dreams.dto";

export const GET = async () => {
return authenticated((session) => (
dreamsService.fetchTags(session)
))
}

export const POST = async (req: Request) => {
return authenticated(async (session) => {
const body: PostDreamTagDto = await req.json()
return dreamsService.createTag(session, body)
})
}
39 changes: 33 additions & 6 deletions src/app/api/utils/api-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,40 @@ import {NextResponse} from "next/server";
import {getServerSession, Session} from "next-auth";
import authOptions from "@/app/api/auth/[...nextauth]/utils";
import {buildResponse} from "@/app/api/utils/types";
import {Member} from "@prisma/client";
import prisma from "@/libs/prisma";

export const authenticated = async (logic: (session: Session) => Promise<NextResponse>): Promise<NextResponse> => {
const session = await getServerSession(authOptions)
if (!session || !session.user)
export const authenticated = async (logic: (session: Session, member?: Member) => Promise<NextResponse>, options?: {
fetchMember?: boolean
}): Promise<NextResponse> => {
try {
const session = await getServerSession(authOptions)
if (!session || !session.user)
return buildResponse({
status: 403,
message: "Unauthorized!"
})

if (options?.fetchMember) {
const member = await prisma.member.findUnique({
where: {
email: session.user.email
}
})

if (!member)
return buildResponse({
status: 404,
message: "Couldn't find information for your user!"
})
}

return logic(session)
} catch (e) {
console.error(e)
return buildResponse({
status: 403,
message: "Unauthorized!"
status: 404,
message: "Internal Server Error"
})
return logic(session)
}
}
9 changes: 9 additions & 0 deletions src/app/api/utils/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {NextResponse} from "next/server";
import {ZodError} from "zod";

export type RouteResponseType<T> = {
status?: number,
Expand All @@ -23,4 +24,12 @@ export const buildResponse = <T>(response: RouteResponseType<T>): NextResponse<T
return new RouteResponse(response).toNextResponse()
}

export const buildFailedValidationResponse = <T>(error: ZodError): NextResponse<T | undefined> => {
return new RouteResponse({
status: 400,
message: error.message,
data: undefined
}).toNextResponse()
}

export type NextRouteResponse<T extends RouteResponse<T>> = NextResponse<T>

0 comments on commit 27fd87b

Please sign in to comment.