Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
Warnings:

- Added the required column `folderId` to the `Note` table without a default value. This is not possible if the table is not empty.

*/
-- AlterTable
ALTER TABLE "Note" ADD COLUMN "folderId" TEXT NOT NULL;

-- CreateTable
CREATE TABLE "Folder" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"color" TEXT NOT NULL DEFAULT '#ffffff',
"isDeleted" BOOLEAN NOT NULL DEFAULT false,
"deletedAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"userId" INTEGER NOT NULL,

CONSTRAINT "Folder_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE INDEX "Folder_userId_idx" ON "Folder"("userId");

-- CreateIndex
CREATE INDEX "Folder_updatedAt_idx" ON "Folder"("updatedAt");

-- AddForeignKey
ALTER TABLE "Note" ADD CONSTRAINT "Note_folderId_fkey" FOREIGN KEY ("folderId") REFERENCES "Folder"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "Folder" ADD CONSTRAINT "Folder_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
27 changes: 24 additions & 3 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ datasource db {
}

model User {
id Int @id @default(autoincrement())
email String @unique
id Int @id @default(autoincrement())
email String @unique
name String?
password_hash String
hashed_refresh_token String?
// Relations
notes Note[]
folders Folder[]
}

model Note {
Expand All @@ -28,10 +30,29 @@ model Note {
content Json
searchContent String @default("")
version Int @default(1)
updatedAt DateTime
folderId String?
folder Folder? @relation(fields: [folderId], references: [id], onDelete: SetNull)
updatedAt DateTime @updatedAt
isDeleted Boolean @default(false)
userId Int
user User @relation(fields: [userId], references: [id])

@@index([userId, updatedAt, searchContent]) // Critical composite for fast delta indexing
}

model Folder {
id String @id @default(uuid())
name String
color String @default("#ffffff")
isDeleted Boolean @default(false)
deletedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
userId Int
user User @relation(fields: [userId], references: [id])
// Relations
notes Note[]

@@index([userId])
@@index([updatedAt])
}
4 changes: 3 additions & 1 deletion src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ import { NotesModule } from './notes/notes.module';
import { AuthModule } from './auth/auth.module';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { TransformResponseInterceptor } from './interceptors/transformResponse.interceptor';
import { FoldersModule } from './folders/folders.module';

@Module({
imports: [
ConfigModule.forRoot({
envFilePath: '.env.development.local',
}),
NotesModule,
AuthModule
AuthModule,
FoldersModule
],
controllers: [AppController],
providers: [
Expand Down
34 changes: 34 additions & 0 deletions src/folders/dto/base-folder.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Type } from 'class-transformer';
import {
IsBoolean,
IsDate,
IsNotEmpty,
IsOptional,
IsString,
IsUUID,
} from 'class-validator';

export class BaseFolderDto {
@IsUUID()
@IsNotEmpty()
id!: string; // This will be generated on the client side

@IsString()
@IsNotEmpty()
name!: string;

@IsOptional()
@IsString()
color!: string;

@IsDate()
@Type(() => Date)
updatedAt!: Date;

@IsBoolean()
isDeleted!: boolean;

@IsDate()
@Type(() => Date)
deletedAt: Date;
}
3 changes: 3 additions & 0 deletions src/folders/dto/create-folder.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { BaseFolderDto } from './base-folder.dto';

export class CreateFolderDto extends BaseFolderDto {}
3 changes: 3 additions & 0 deletions src/folders/dto/update-folder.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { BaseFolderDto } from './base-folder.dto';

export class UpdateFolderDto extends BaseFolderDto {}
1 change: 1 addition & 0 deletions src/folders/entities/folder.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export class Folder {}
20 changes: 20 additions & 0 deletions src/folders/folders.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Test, TestingModule } from '@nestjs/testing';
import { FoldersController } from './folders.controller';
import { FoldersService } from './folders.service';

describe('FoldersController', () => {
let controller: FoldersController;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [FoldersController],
providers: [FoldersService],
}).compile();

controller = module.get<FoldersController>(FoldersController);
});

it('should be defined', () => {
expect(controller).toBeDefined();
});
});
48 changes: 48 additions & 0 deletions src/folders/folders.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
UseGuards,
} from '@nestjs/common';
import { FoldersService } from './folders.service';
import { CreateFolderDto } from './dto/create-folder.dto';
import { UpdateFolderDto } from './dto/update-folder.dto';
import { type RequestUser } from '@/auth/types/JwtPayload';
import { GetUser } from '@/auth/decorators/get-user.decorator';
import { JwtAuthGuard } from '@/auth/guards/jwt.auth-guard';

@UseGuards(JwtAuthGuard)
@Controller('folders')
export class FoldersController {
constructor(private readonly foldersService: FoldersService) {}

@Post()
create(
@GetUser() user: RequestUser,
@Body() createFolderDto: CreateFolderDto,
) {
return this.foldersService.create(user.id, createFolderDto);
}

@Get(':id')
findOne(@GetUser() user: RequestUser, @Param('id') id: string) {
return this.foldersService.findOne(user.id, id);
}

@Patch(':id')
update(
@GetUser() user: RequestUser,
@Body() updateFolderDto: UpdateFolderDto,
) {
return this.foldersService.update(user.id, updateFolderDto);
}

@Delete(':id')
remove(@GetUser() user: RequestUser, @Param('id') id: string) {
return this.foldersService.remove(user.id, id);
}
}
10 changes: 10 additions & 0 deletions src/folders/folders.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { FoldersService } from './folders.service';
import { FoldersController } from './folders.controller';
import { Prisma } from '@/prisma/prisma.service';

@Module({
controllers: [FoldersController],
providers: [FoldersService, Prisma],
})
export class FoldersModule {}
20 changes: 20 additions & 0 deletions src/folders/folders.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Test, TestingModule } from '@nestjs/testing';
import { FoldersService } from './folders.service';
import { Prisma } from '@/prisma/prisma.service';

describe('FoldersService', () => {
let service: FoldersService;
let prisma: Prisma;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [FoldersService],
}).compile();

service = module.get<FoldersService>(FoldersService);
});

it('should be defined', () => {
expect(service).toBeDefined();
});
});
85 changes: 85 additions & 0 deletions src/folders/folders.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { CreateFolderDto } from './dto/create-folder.dto';
import { UpdateFolderDto } from './dto/update-folder.dto';
import { Prisma } from '@/prisma/prisma.service';

@Injectable()
export class FoldersService {
constructor(private prisma: Prisma) {}

async create(userId: number, createFolderDto: CreateFolderDto) {
const found = await this.prisma.folder.findUnique({
where: {
id: createFolderDto.id,
userId: userId,
isDeleted: false,
},
select: {
id: true,
name: true,
color: true,
updatedAt: true,
isDeleted: true,
},
});
if (found) {
return found;
}
const created = await this.prisma.folder.create({
data: {
...createFolderDto,
userId: userId,
},
});
return created;
}

async findOne(userId: number, folderId: string) {
const found = await this.prisma.folder.findFirst({
where: {
id: folderId,
userId,
},
});
if (!found) {
throw new NotFoundException('Folder not found');
}
return found;
}

async update(userId: number, updateFolderDto: UpdateFolderDto) {
const found = await this.prisma.folder.findFirst({
where: {
id: updateFolderDto.id,
userId,
},
});
if (!found) {
throw new NotFoundException('Folder not found');
}
const updated = await this.prisma.folder.update({
where: {
id: found.id,
userId,
},
data: {
...updateFolderDto,
userId,
},
});
return updated;
}

async remove(userId: number, folderId: string) {
const folderToDelete = await this.prisma.folder.delete({
where: {
id: folderId,
userId,
},
});
if (!folderToDelete) {
throw new NotFoundException('Folder Not found');
}
return { data: null, message: 'Folder successfully deleted' };
}
}
5 changes: 5 additions & 0 deletions src/notes/dto/base-note.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
IsUUID,
IsBoolean,
IsObject,
IsOptional,
} from 'class-validator';
import { Prisma } from 'generated/prisma/browser';

Expand All @@ -18,6 +19,10 @@ export class BaseNoteDto {
@IsNotEmpty()
title!: string;

@IsOptional()
@IsString()
folderId!: string | null;

@IsObject()
@IsNotEmpty()
content!: Prisma.InputJsonValue; // To Map seamlessly to Prisma's native JSON typing
Expand Down
7 changes: 7 additions & 0 deletions src/notes/dto/sync-notes.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
ValidateNested,
} from 'class-validator';
import { BaseNoteDto } from './base-note.dto';
import { BaseFolderDto } from '@/folders/dto/base-folder.dto';

export class SyncNotesDto {
@IsDate()
Expand All @@ -18,6 +19,12 @@ export class SyncNotesDto {
@IsOptional()
cursor?: string;

@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => BaseFolderDto)
folders?: BaseFolderDto[];

@IsArray()
@ValidateNested({ each: true })
@Type(() => BaseNoteDto)
Expand Down
2 changes: 1 addition & 1 deletion src/notes/notes.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ import { SyncService } from './sync.service';
controllers: [NotesController],
providers: [NotesService, SyncService, Prisma],
})
export class NotesModule { }
export class NotesModule {}
Loading