From d4a98f6f0befd1f6940902b3971151f8c012625b Mon Sep 17 00:00:00 2001 From: iverly Date: Mon, 27 Nov 2023 15:05:31 +0100 Subject: [PATCH 1/3] feat: add note schema Signed-off-by: iverly --- config/keto/namespaces/.gitkeep | 0 config/keto/namespaces/notes.json | 4 +++ packages/types/src/index.ts | 4 +++ packages/types/src/lib/generated/.gitkeep | 0 .../classes/note/dto/connect-note.dto.ts | 5 +++ .../classes/note/dto/create-note.dto.ts | 30 ++++++++++++++++++ .../lib/generated/classes/note/dto/index.ts | 5 +++ .../generated/classes/note/dto/note.dto.ts | 31 +++++++++++++++++++ .../classes/note/dto/update-note.dto.ts | 30 ++++++++++++++++++ .../generated/classes/note/entities/index.ts | 2 ++ .../classes/note/entities/note.entity.ts | 31 +++++++++++++++++++ .../interfaces/note/dto/connect-note.dto.ts | 5 +++ .../interfaces/note/dto/create-note.dto.ts | 10 ++++++ .../generated/interfaces/note/dto/index.ts | 5 +++ .../generated/interfaces/note/dto/note.dto.ts | 10 ++++++ .../interfaces/note/dto/update-note.dto.ts | 10 ++++++ .../interfaces/note/entities/index.ts | 2 ++ .../interfaces/note/entities/note.entity.ts | 10 ++++++ .../20231127140140_add_notes/migration.sql | 9 ++++++ prisma/migrations/migration_lock.toml | 2 +- prisma/schema.prisma | 26 +++++++++++++--- 21 files changed, 226 insertions(+), 5 deletions(-) delete mode 100644 config/keto/namespaces/.gitkeep create mode 100644 config/keto/namespaces/notes.json delete mode 100644 packages/types/src/lib/generated/.gitkeep create mode 100644 packages/types/src/lib/generated/classes/note/dto/connect-note.dto.ts create mode 100644 packages/types/src/lib/generated/classes/note/dto/create-note.dto.ts create mode 100644 packages/types/src/lib/generated/classes/note/dto/index.ts create mode 100644 packages/types/src/lib/generated/classes/note/dto/note.dto.ts create mode 100644 packages/types/src/lib/generated/classes/note/dto/update-note.dto.ts create mode 100644 packages/types/src/lib/generated/classes/note/entities/index.ts create mode 100644 packages/types/src/lib/generated/classes/note/entities/note.entity.ts create mode 100644 packages/types/src/lib/generated/interfaces/note/dto/connect-note.dto.ts create mode 100644 packages/types/src/lib/generated/interfaces/note/dto/create-note.dto.ts create mode 100644 packages/types/src/lib/generated/interfaces/note/dto/index.ts create mode 100644 packages/types/src/lib/generated/interfaces/note/dto/note.dto.ts create mode 100644 packages/types/src/lib/generated/interfaces/note/dto/update-note.dto.ts create mode 100644 packages/types/src/lib/generated/interfaces/note/entities/index.ts create mode 100644 packages/types/src/lib/generated/interfaces/note/entities/note.entity.ts create mode 100644 prisma/migrations/20231127140140_add_notes/migration.sql diff --git a/config/keto/namespaces/.gitkeep b/config/keto/namespaces/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/config/keto/namespaces/notes.json b/config/keto/namespaces/notes.json new file mode 100644 index 0000000..4796fcd --- /dev/null +++ b/config/keto/namespaces/notes.json @@ -0,0 +1,4 @@ +{ + "id": 1, + "name": "notes" +} diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 9d2997b..f5cb854 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -1 +1,5 @@ export * from './lib/id-token'; + +// Generated by prisma +export * from './lib/generated/classes/note/dto/index'; +export * from './lib/generated/classes/note/entities/index'; diff --git a/packages/types/src/lib/generated/.gitkeep b/packages/types/src/lib/generated/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/packages/types/src/lib/generated/classes/note/dto/connect-note.dto.ts b/packages/types/src/lib/generated/classes/note/dto/connect-note.dto.ts new file mode 100644 index 0000000..7c1a6aa --- /dev/null +++ b/packages/types/src/lib/generated/classes/note/dto/connect-note.dto.ts @@ -0,0 +1,5 @@ + + export class ConnectNoteDto { + id: string; + } + \ No newline at end of file diff --git a/packages/types/src/lib/generated/classes/note/dto/create-note.dto.ts b/packages/types/src/lib/generated/classes/note/dto/create-note.dto.ts new file mode 100644 index 0000000..cc0a790 --- /dev/null +++ b/packages/types/src/lib/generated/classes/note/dto/create-note.dto.ts @@ -0,0 +1,30 @@ + +import {Type} from 'class-transformer' +import {IsNotEmpty,IsString,Length} from 'class-validator' +import {ApiProperty,getSchemaPath} from '@nestjs/swagger' + + + + +export class CreateNoteDto { + @ApiProperty({ + description: `The note title`, + example: `My note`, + maxLength: 40, + minLength: 3, +}) +@IsNotEmpty() +@IsString() +@Length(3, 40) +title: string; +@ApiProperty({ + description: `The note body`, + example: `This is my note`, + maxLength: 200, + minLength: 3, +}) +@IsNotEmpty() +@IsString() +@Length(3, 200) +body: string; +} diff --git a/packages/types/src/lib/generated/classes/note/dto/index.ts b/packages/types/src/lib/generated/classes/note/dto/index.ts new file mode 100644 index 0000000..db00fa9 --- /dev/null +++ b/packages/types/src/lib/generated/classes/note/dto/index.ts @@ -0,0 +1,5 @@ + +export * from './connect-note.dto'; +export * from './create-note.dto'; +export * from './update-note.dto'; +export * from './note.dto'; \ No newline at end of file diff --git a/packages/types/src/lib/generated/classes/note/dto/note.dto.ts b/packages/types/src/lib/generated/classes/note/dto/note.dto.ts new file mode 100644 index 0000000..4149e7b --- /dev/null +++ b/packages/types/src/lib/generated/classes/note/dto/note.dto.ts @@ -0,0 +1,31 @@ + +import {ApiProperty} from '@nestjs/swagger' + + +export class NoteDto { + @ApiProperty({ + description: `The note id`, + example: `b1774d2f-05f7-4ea4-b427-0d808bdca583`, +}) +id: string ; +@ApiProperty({ + description: `The note title`, + example: `My note`, + maxLength: 40, + minLength: 3, +}) +title: string ; +@ApiProperty({ + description: `The note body`, + example: `This is my note`, + maxLength: 200, + minLength: 3, +}) +body: string ; +@ApiProperty({ + description: `When the project was created`, + type: `string`, + format: `date-time`, +}) +createdAt: Date ; +} diff --git a/packages/types/src/lib/generated/classes/note/dto/update-note.dto.ts b/packages/types/src/lib/generated/classes/note/dto/update-note.dto.ts new file mode 100644 index 0000000..142df7b --- /dev/null +++ b/packages/types/src/lib/generated/classes/note/dto/update-note.dto.ts @@ -0,0 +1,30 @@ + +import {Type} from 'class-transformer' +import {IsOptional,IsString,Length} from 'class-validator' +import {ApiProperty} from '@nestjs/swagger' + + + + +export class UpdateNoteDto { + @ApiProperty({ + description: `The note title`, + example: `My note`, + maxLength: 40, + minLength: 3, +}) +@IsOptional() +@IsString() +@Length(3, 40) +title?: string; +@ApiProperty({ + description: `The note body`, + example: `This is my note`, + maxLength: 200, + minLength: 3, +}) +@IsOptional() +@IsString() +@Length(3, 200) +body?: string; +} diff --git a/packages/types/src/lib/generated/classes/note/entities/index.ts b/packages/types/src/lib/generated/classes/note/entities/index.ts new file mode 100644 index 0000000..0c770bb --- /dev/null +++ b/packages/types/src/lib/generated/classes/note/entities/index.ts @@ -0,0 +1,2 @@ + +export * from './note.entity'; \ No newline at end of file diff --git a/packages/types/src/lib/generated/classes/note/entities/note.entity.ts b/packages/types/src/lib/generated/classes/note/entities/note.entity.ts new file mode 100644 index 0000000..331ca1e --- /dev/null +++ b/packages/types/src/lib/generated/classes/note/entities/note.entity.ts @@ -0,0 +1,31 @@ + +import {ApiProperty} from '@nestjs/swagger' + + +export class Note { + @ApiProperty({ + description: `The note id`, + example: `b1774d2f-05f7-4ea4-b427-0d808bdca583`, +}) +id: string ; +@ApiProperty({ + description: `The note title`, + example: `My note`, + maxLength: 40, + minLength: 3, +}) +title: string ; +@ApiProperty({ + description: `The note body`, + example: `This is my note`, + maxLength: 200, + minLength: 3, +}) +body: string ; +@ApiProperty({ + description: `When the project was created`, + type: `string`, + format: `date-time`, +}) +createdAt: Date ; +} diff --git a/packages/types/src/lib/generated/interfaces/note/dto/connect-note.dto.ts b/packages/types/src/lib/generated/interfaces/note/dto/connect-note.dto.ts new file mode 100644 index 0000000..02f31e2 --- /dev/null +++ b/packages/types/src/lib/generated/interfaces/note/dto/connect-note.dto.ts @@ -0,0 +1,5 @@ + + export interface IConnectNoteAttributes { + id: string; + } + \ No newline at end of file diff --git a/packages/types/src/lib/generated/interfaces/note/dto/create-note.dto.ts b/packages/types/src/lib/generated/interfaces/note/dto/create-note.dto.ts new file mode 100644 index 0000000..d5ff601 --- /dev/null +++ b/packages/types/src/lib/generated/interfaces/note/dto/create-note.dto.ts @@ -0,0 +1,10 @@ + + + + + + +export interface ICreateNoteAttributes { + title: string; +body: string; +} diff --git a/packages/types/src/lib/generated/interfaces/note/dto/index.ts b/packages/types/src/lib/generated/interfaces/note/dto/index.ts new file mode 100644 index 0000000..db00fa9 --- /dev/null +++ b/packages/types/src/lib/generated/interfaces/note/dto/index.ts @@ -0,0 +1,5 @@ + +export * from './connect-note.dto'; +export * from './create-note.dto'; +export * from './update-note.dto'; +export * from './note.dto'; \ No newline at end of file diff --git a/packages/types/src/lib/generated/interfaces/note/dto/note.dto.ts b/packages/types/src/lib/generated/interfaces/note/dto/note.dto.ts new file mode 100644 index 0000000..3ed8319 --- /dev/null +++ b/packages/types/src/lib/generated/interfaces/note/dto/note.dto.ts @@ -0,0 +1,10 @@ + + + + +export interface NoteAttributes { + id: string ; +title: string ; +body: string ; +createdAt: Date ; +} diff --git a/packages/types/src/lib/generated/interfaces/note/dto/update-note.dto.ts b/packages/types/src/lib/generated/interfaces/note/dto/update-note.dto.ts new file mode 100644 index 0000000..167c6c8 --- /dev/null +++ b/packages/types/src/lib/generated/interfaces/note/dto/update-note.dto.ts @@ -0,0 +1,10 @@ + + + + + + +export interface IUpdateNoteAttributes { + title?: string; +body?: string; +} diff --git a/packages/types/src/lib/generated/interfaces/note/entities/index.ts b/packages/types/src/lib/generated/interfaces/note/entities/index.ts new file mode 100644 index 0000000..0c770bb --- /dev/null +++ b/packages/types/src/lib/generated/interfaces/note/entities/index.ts @@ -0,0 +1,2 @@ + +export * from './note.entity'; \ No newline at end of file diff --git a/packages/types/src/lib/generated/interfaces/note/entities/note.entity.ts b/packages/types/src/lib/generated/interfaces/note/entities/note.entity.ts new file mode 100644 index 0000000..49253e9 --- /dev/null +++ b/packages/types/src/lib/generated/interfaces/note/entities/note.entity.ts @@ -0,0 +1,10 @@ + + + + +export interface INote { + id: string ; +title: string ; +body: string ; +createdAt: Date ; +} diff --git a/prisma/migrations/20231127140140_add_notes/migration.sql b/prisma/migrations/20231127140140_add_notes/migration.sql new file mode 100644 index 0000000..05efbdb --- /dev/null +++ b/prisma/migrations/20231127140140_add_notes/migration.sql @@ -0,0 +1,9 @@ +-- CreateTable +CREATE TABLE "notes" ( + "id" TEXT NOT NULL, + "title" TEXT NOT NULL, + "body" TEXT NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "notes_pkey" PRIMARY KEY ("id") +); diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml index 99e4f20..fbffa92 100644 --- a/prisma/migrations/migration_lock.toml +++ b/prisma/migrations/migration_lock.toml @@ -1,3 +1,3 @@ # Please do not edit this file manually # It should be added in your version-control system (i.e. Git) -provider = "postgresql" +provider = "postgresql" \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index ebbfb69..cf55df1 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -41,8 +41,26 @@ datasource db { //////////////////////////////////////////////// // Model definitions // //////////////////////////////////////////////// -model User { - id Int @id @default(autoincrement()) - email String @unique - name String? +model Note { + /// @DtoReadOnly + /// @description The note id + /// @example b1774d2f-05f7-4ea4-b427-0d808bdca583 + id String @id @default(uuid()) + /// @description The note title + /// @example My note + /// @minLength 3 + /// @maxLength 40 + /// @Length(3, 40) + title String + /// @description The note body + /// @example This is my note + /// @minLength 3 + /// @maxLength 200 + /// @Length(3, 200) + body String + /// @DtoReadOnly + /// @description When the project was created + createdAt DateTime @default(now()) @map("created_at") + + @@map("notes") } From d240439c65bfc1cbe2a8ae491c97019d78116344 Mon Sep 17 00:00:00 2001 From: iverly Date: Mon, 27 Nov 2023 15:51:54 +0100 Subject: [PATCH 2/3] feat: add notes module with a simple crud Signed-off-by: iverly --- apps/api/src/app/app.module.ts | 2 + packages/notes/.eslintrc.json | 18 ++ packages/notes/jest.config.ts | 11 ++ packages/notes/project.json | 23 +++ packages/notes/src/index.ts | 1 + packages/notes/src/lib/notes.controller.ts | 159 +++++++++++++++++ packages/notes/src/lib/notes.module.ts | 13 ++ packages/notes/src/lib/notes.service.ts | 196 +++++++++++++++++++++ packages/notes/tsconfig.json | 22 +++ packages/notes/tsconfig.lib.json | 16 ++ packages/notes/tsconfig.spec.json | 14 ++ tsconfig.base.json | 3 + 12 files changed, 478 insertions(+) create mode 100644 packages/notes/.eslintrc.json create mode 100644 packages/notes/jest.config.ts create mode 100644 packages/notes/project.json create mode 100644 packages/notes/src/index.ts create mode 100644 packages/notes/src/lib/notes.controller.ts create mode 100644 packages/notes/src/lib/notes.module.ts create mode 100644 packages/notes/src/lib/notes.service.ts create mode 100644 packages/notes/tsconfig.json create mode 100644 packages/notes/tsconfig.lib.json create mode 100644 packages/notes/tsconfig.spec.json diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts index f3adeab..a504f95 100644 --- a/apps/api/src/app/app.module.ts +++ b/apps/api/src/app/app.module.ts @@ -2,6 +2,7 @@ import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { AuthModule } from '@nx-next-nest-prisma-ory-template/auth'; import { DatabaseModule } from '@nx-next-nest-prisma-ory-template/database'; +import { NotesModule } from '@nx-next-nest-prisma-ory-template/notes'; import { OpenTelemetryModule } from '@nx-next-nest-prisma-ory-template/opentelemetry'; @Module({ @@ -10,6 +11,7 @@ import { OpenTelemetryModule } from '@nx-next-nest-prisma-ory-template/opentelem ConfigModule.forRoot(), AuthModule, DatabaseModule, + NotesModule, ], controllers: [], providers: [], diff --git a/packages/notes/.eslintrc.json b/packages/notes/.eslintrc.json new file mode 100644 index 0000000..9d9c0db --- /dev/null +++ b/packages/notes/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/notes/jest.config.ts b/packages/notes/jest.config.ts new file mode 100644 index 0000000..dceee13 --- /dev/null +++ b/packages/notes/jest.config.ts @@ -0,0 +1,11 @@ +/* eslint-disable */ +export default { + displayName: 'notes', + preset: '../../jest.preset.js', + testEnvironment: 'node', + transform: { + '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], + }, + moduleFileExtensions: ['ts', 'js', 'html'], + coverageDirectory: '../../coverage/packages/notes', +}; diff --git a/packages/notes/project.json b/packages/notes/project.json new file mode 100644 index 0000000..c9057a2 --- /dev/null +++ b/packages/notes/project.json @@ -0,0 +1,23 @@ +{ + "name": "notes", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/notes/src", + "projectType": "library", + "targets": { + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["packages/notes/**/*.ts"] + } + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "packages/notes/jest.config.ts" + } + } + }, + "tags": [] +} diff --git a/packages/notes/src/index.ts b/packages/notes/src/index.ts new file mode 100644 index 0000000..83b8c2c --- /dev/null +++ b/packages/notes/src/index.ts @@ -0,0 +1 @@ +export * from './lib/notes.module'; diff --git a/packages/notes/src/lib/notes.controller.ts b/packages/notes/src/lib/notes.controller.ts new file mode 100644 index 0000000..3ddc9a2 --- /dev/null +++ b/packages/notes/src/lib/notes.controller.ts @@ -0,0 +1,159 @@ +import { + Body, + Controller, + NotFoundException, + Param, + Req, + RequestMethod, +} from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; +import { ApiRouteAuthenticated } from '@nx-next-nest-prisma-ory-template/utils'; +import { NotesService } from './notes.service'; +import { FastifyRequest } from 'fastify'; +import { + CreateNoteDto, + Note, + NoteDto, + UpdateNoteDto, +} from '@nx-next-nest-prisma-ory-template/types'; + +@ApiTags('Notes') +@Controller('notes') +export class NotesController { + constructor(private notesService: NotesService) {} + + @ApiRouteAuthenticated({ + method: RequestMethod.POST, + operation: { + summary: 'Create a note', + description: 'Create a new note with the given name and type', + }, + response: { + status: 201, + type: NoteDto, + }, + }) + create( + @Body() body: CreateNoteDto, + @Req() request: FastifyRequest + ): Promise { + return this.notesService.create(body, request.user.sub); + } + + @ApiRouteAuthenticated({ + method: RequestMethod.GET, + operation: { + summary: 'List all notes', + description: 'Returns the list of all notes (limited to 1000)', + }, + response: { + status: 200, + schema: { + type: 'array', + items: { + type: 'object', + properties: { + id: { + type: 'string', + description: 'The note id', + example: 'b1774d2f-05f7-4ea4-b427-0d808bdca583', + }, + title: { + type: 'string', + description: 'The note title', + example: 'My note', + }, + body: { + type: 'string', + description: 'The note body', + example: 'This is the body of my note', + }, + createdAt: { + type: 'string', + description: 'The note creation date', + example: '2021-03-11T20:43:06.000Z', + }, + }, + }, + }, + }, + }) + async list(@Req() request: FastifyRequest): Promise { + const notes = await this.notesService.list(request.user.sub); + + if (!notes) { + throw new NotFoundException(); + } + + return notes; + } + + @ApiRouteAuthenticated({ + method: RequestMethod.GET, + path: ':noteId', + operation: { + summary: 'Get a specific note', + description: 'Returns the note with the given id', + }, + response: { + status: 200, + type: NoteDto, + }, + }) + async get(@Param('noteId') noteId: string): Promise { + const note = await this.notesService.get(noteId); + + if (!note) { + throw new NotFoundException(); + } + + return note; + } + + @ApiRouteAuthenticated({ + method: RequestMethod.PATCH, + path: ':noteId', + operation: { + summary: 'Update a specific note', + description: 'Updates the note with the given id', + }, + response: { + status: 200, + type: NoteDto, + }, + }) + async patch( + @Body() body: UpdateNoteDto, + @Param('noteId') noteId: string + ): Promise { + const note = await this.notesService.patch(noteId, body); + + if (!note) { + throw new NotFoundException(); + } + + return note; + } + + @ApiRouteAuthenticated({ + method: RequestMethod.DELETE, + path: ':noteId', + operation: { + summary: 'Delete a specific note', + description: 'Deletes the note with the given id', + }, + response: { + status: 200, + type: NoteDto, + }, + }) + async delete(@Param('noteId') noteId: string): Promise { + const note = await this.notesService.delete(noteId); + + if (!note) { + throw new NotFoundException(); + } + + return note; + } +} diff --git a/packages/notes/src/lib/notes.module.ts b/packages/notes/src/lib/notes.module.ts new file mode 100644 index 0000000..b26783a --- /dev/null +++ b/packages/notes/src/lib/notes.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { AuthModule } from '@nx-next-nest-prisma-ory-template/auth'; +import { DatabaseModule } from '@nx-next-nest-prisma-ory-template/database'; +import { NotesController } from './notes.controller'; +import { NotesService } from './notes.service'; + +@Module({ + imports: [AuthModule, DatabaseModule], + controllers: [NotesController], + providers: [NotesService], + exports: [], +}) +export class NotesModule {} diff --git a/packages/notes/src/lib/notes.service.ts b/packages/notes/src/lib/notes.service.ts new file mode 100644 index 0000000..4fb6a08 --- /dev/null +++ b/packages/notes/src/lib/notes.service.ts @@ -0,0 +1,196 @@ +import { Injectable } from '@nestjs/common'; +import { + Transaction, + TransactionStep, +} from '@nx-next-nest-prisma-ory-template/utils'; +import { KetoService } from '@nx-next-nest-prisma-ory-template/auth'; +import { PrismaService } from '@nx-next-nest-prisma-ory-template/database'; +import { + CreateNoteDto, + Note, + UpdateNoteDto, +} from '@nx-next-nest-prisma-ory-template/types'; + +@Injectable() +export class NotesService { + constructor(private prisma: PrismaService, private keto: KetoService) {} + + /** + * The `create` function creates a new note and associates it with a specified subject using a + * transaction. + * @param {CreateNoteDto} dto - The `dto` parameter is an object of type `CreateNoteDto` which contains + * the data needed to create a new note. It likely includes properties such as `title` and `body` which + * represent the title and content of the note. + * @param {string} subject - The `subject` parameter is a string that represents the owner or creator + * of the note. It is used to create a relation between the note and the owner in the `keto` service. + * @returns a Promise that resolves to a Note object. + */ + async create(dto: CreateNoteDto, subject: string): Promise { + const transaction = new Transaction(); + + let note: Note | null = null; + + transaction.addStep( + new TransactionStep( + async () => { + note = await this.prisma.note.create({ + data: { + title: dto.title, + body: dto.body, + }, + }); + }, + async () => { + if (note) { + await this.prisma.note.delete({ + where: { + id: note.id, + }, + }); + } + } + ) + ); + + transaction.addStep( + new TransactionStep( + async () => { + await this.keto.createRelation({ + namespace: 'notes', + object: note?.id, + relation: 'owner', + subject_id: subject, + }); + }, + () => null + ) + ); + + await transaction.commit(); + + if (!note) { + throw new Error('Failed to create project'); + } + + return note; + } + + /** + * The `list` function retrieves a list of notes associated with a specific subject + * @param {string} subject - A string representing the unique identifier of the subject that you want + * to retrieve notes for. + * @returns The `list` function is returning a `Promise` that resolves to an array of `Note` objects. + */ + async list(subject: string): Promise { + const relationships = await this.keto.getRelationships({ + pageSize: 1000, + namespace: 'notes', + relation: 'owner', + subjectId: subject, + }); + + if (!relationships.relation_tuples) { + return []; + } + + return this.prisma.note.findMany({ + where: { + id: { + in: relationships.relation_tuples?.map((r) => r.object), + }, + }, + }); + } + + /** + * The `get` function retrieves a note with a specific ID + * @param {string} id - A string representing the unique identifier of the note that you want to + * retrieve. + * @returns The `get` function is returning a `Promise` that resolves to either a `Note` object or + * `null`. + */ + get(id: string): Promise { + return this.prisma.note.findUnique({ + where: { + id, + }, + }); + } + + /** + * The `update` function updates a note with a specific ID + * @param {string} noteId - A string representing the unique identifier of the note that you want to + * update. + * @param {UpdateNoteDto} dto - The `dto` parameter is an object of type `UpdateNoteDto` which contains + * the data needed to update a note. It likely includes properties such as `title` and `body` which + * represent the title and content of the note. + * @returns The `update` function is returning a `Promise` that resolves to a `Note` object. + */ + patch(noteId: string, dto: UpdateNoteDto): Promise { + return this.prisma.note.update({ + where: { + id: noteId, + }, + data: { + title: dto.title, + body: dto.body, + }, + }); + } + + /** + * The `delete` function deletes a note with a specific ID + * @param {string} noteId - A string representing the unique identifier of the note that you want to + * delete. + * @returns The `delete` function is returning a `Promise` that resolves to a `Note` object. + */ + async delete(noteId: string): Promise { + const transaction = new Transaction(); + + let note: Note | null = null; + + transaction.addStep( + new TransactionStep( + async () => { + note = await this.prisma.note.delete({ + where: { + id: noteId, + }, + }); + }, + async () => { + if (note) { + await this.prisma.note.create({ + data: { + id: note.id, + title: note.title, + body: note.body, + createdAt: note.createdAt, + }, + }); + } + } + ) + ); + + transaction.addStep( + new TransactionStep( + async () => { + await this.keto.deleteRelation({ + namespace: 'notes', + object: noteId, + }); + }, + () => null + ) + ); + + await transaction.commit(); + + if (!note) { + throw new Error('Failed to create note'); + } + + return note; + } +} diff --git a/packages/notes/tsconfig.json b/packages/notes/tsconfig.json new file mode 100644 index 0000000..f5b8565 --- /dev/null +++ b/packages/notes/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/packages/notes/tsconfig.lib.json b/packages/notes/tsconfig.lib.json new file mode 100644 index 0000000..c297a24 --- /dev/null +++ b/packages/notes/tsconfig.lib.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true, + "types": ["node"], + "target": "es2021", + "strictNullChecks": true, + "noImplicitAny": true, + "strictBindCallApply": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts"], + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/packages/notes/tsconfig.spec.json b/packages/notes/tsconfig.spec.json new file mode 100644 index 0000000..9b2a121 --- /dev/null +++ b/packages/notes/tsconfig.spec.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/tsconfig.base.json b/tsconfig.base.json index 05031f9..55abf9b 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -25,6 +25,9 @@ "@nx-next-nest-prisma-ory-template/error": [ "packages/error/src/index.ts" ], + "@nx-next-nest-prisma-ory-template/notes": [ + "packages/notes/src/index.ts" + ], "@nx-next-nest-prisma-ory-template/opentelemetry": [ "packages/opentelemetry/src/index.ts" ], From 0560f157a7e4ff648cfdc64f2b529cbc309b39fe Mon Sep 17 00:00:00 2001 From: iverly Date: Mon, 27 Nov 2023 15:52:16 +0100 Subject: [PATCH 3/3] feat(oathkeeper): add api notes rules Signed-off-by: iverly --- config/oathkeeper/oathkeeper.yaml | 1 + config/oathkeeper/rules/api-notes.yaml | 63 ++++++++++++++++++++++++++ docker-compose.base.yaml | 1 + 3 files changed, 65 insertions(+) create mode 100644 config/oathkeeper/rules/api-notes.yaml diff --git a/config/oathkeeper/oathkeeper.yaml b/config/oathkeeper/oathkeeper.yaml index f5c41bf..cdbedb5 100644 --- a/config/oathkeeper/oathkeeper.yaml +++ b/config/oathkeeper/oathkeeper.yaml @@ -61,6 +61,7 @@ access_rules: - file:///etc/config/oathkeeper/rules/auth.yaml - file:///etc/config/oathkeeper/rules/kratos.yaml - file:///etc/config/oathkeeper/rules/dev.yaml + - file:///etc/config/oathkeeper/rules/api-notes.yaml authenticators: anonymous: diff --git a/config/oathkeeper/rules/api-notes.yaml b/config/oathkeeper/rules/api-notes.yaml new file mode 100644 index 0000000..01a0b7c --- /dev/null +++ b/config/oathkeeper/rules/api-notes.yaml @@ -0,0 +1,63 @@ +####################################### +# Notes Access Rules # +####################################### +- id: "api:create-note:protected" + upstream: + preserve_host: true + url: "http://api:3100" + match: + url: http://api.nx-next-nest-prisma-ory-template.<127\.0\.0\.1\.sslip\.io|com>/notes + methods: + - POST + authenticators: + - handler: cookie_session + authorizer: + handler: allow + mutators: + - handler: id_token + errors: + - handler: redirect + +- id: "api:note:protected" + upstream: + preserve_host: true + url: "http://api:3100" + match: + url: http://api.nx-next-nest-prisma-ory-template.<127\.0\.0\.1\.sslip\.io|com>/notes/<([0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})> + methods: + - GET + - PATCH + - DELETE + authenticators: + - handler: cookie_session + authorizer: + handler: remote_json + config: + payload: | + { + "namespace": "notes", + "object": "{{ printIndex .MatchContext.RegexpCaptureGroups 1 }}", + "relation": "owner", + "subject_id": "{{ print .Subject }}" + } + mutators: + - handler: id_token + errors: + - handler: redirect + +- id: "api:list-note:protected" + upstream: + preserve_host: true + url: "http://api:3100" + match: + url: http://api.nx-next-nest-prisma-ory-template.<127\.0\.0\.1\.sslip\.io|com>/notes + methods: + - GET + authenticators: + - handler: cookie_session + authorizer: + handler: allow + mutators: + - handler: id_token + errors: + - handler: redirect diff --git a/docker-compose.base.yaml b/docker-compose.base.yaml index eebdf59..4acfa20 100644 --- a/docker-compose.base.yaml +++ b/docker-compose.base.yaml @@ -18,6 +18,7 @@ services: - ./config/oathkeeper/id_token.jwks.json:/etc/config/oathkeeper/id_token.jwks.json - ./config/oathkeeper/rules/auth.yaml:/etc/config/oathkeeper/rules/auth.yaml - ./config/oathkeeper/rules/kratos.yaml:/etc/config/oathkeeper/rules/kratos.yaml + - ./config/oathkeeper/rules/api-notes.yaml:/etc/config/oathkeeper/rules/api-notes.yaml depends_on: - kratos