diff --git a/apps/api/src/prisma/migrations/20231225074847_init/migration.sql b/apps/api/src/prisma/migrations/20231225074847_init/migration.sql deleted file mode 100644 index 139a8a73..00000000 --- a/apps/api/src/prisma/migrations/20231225074847_init/migration.sql +++ /dev/null @@ -1,76 +0,0 @@ --- CreateEnum -CREATE TYPE "Scope" AS ENUM ('CREATE', 'READ', 'UPDATE', 'DELETE'); - --- CreateTable -CREATE TABLE "User" ( - "id" TEXT NOT NULL, - "email" TEXT NOT NULL, - "name" TEXT, - "profilePictureUrl" TEXT, - "isActive" BOOLEAN NOT NULL DEFAULT true, - "isOnboardingFinished" BOOLEAN NOT NULL DEFAULT false, - "isAdmin" BOOLEAN NOT NULL DEFAULT false, - - CONSTRAINT "User_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Project" ( - "id" TEXT NOT NULL, - "name" TEXT NOT NULL, - "description" TEXT, - "ownerId" TEXT NOT NULL, - - CONSTRAINT "Project_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Secret" ( - "id" TEXT NOT NULL, - "name" TEXT NOT NULL, - "value" TEXT NOT NULL, - "projectId" TEXT NOT NULL, - - CONSTRAINT "Secret_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "ApiKey" ( - "id" TEXT NOT NULL, - "name" TEXT NOT NULL, - "value" TEXT NOT NULL, - "projectId" TEXT NOT NULL, - "scopes" "Scope"[], - "userId" TEXT NOT NULL, - - CONSTRAINT "ApiKey_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Otp" ( - "code" TEXT NOT NULL, - "userId" TEXT NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "expiresAt" TIMESTAMP(3) NOT NULL -); - --- CreateIndex -CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); - --- CreateIndex -CREATE UNIQUE INDEX "Otp_code_key" ON "Otp"("code"); - --- AddForeignKey -ALTER TABLE "Project" ADD CONSTRAINT "Project_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Secret" ADD CONSTRAINT "Secret_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "ApiKey" ADD CONSTRAINT "ApiKey_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "ApiKey" ADD CONSTRAINT "ApiKey_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Otp" ADD CONSTRAINT "Otp_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/apps/api/src/prisma/migrations/20231226184616_init/migration.sql b/apps/api/src/prisma/migrations/20231226184616_init/migration.sql new file mode 100644 index 00000000..acd55959 --- /dev/null +++ b/apps/api/src/prisma/migrations/20231226184616_init/migration.sql @@ -0,0 +1,134 @@ +-- CreateEnum +CREATE TYPE "ProjectRole" AS ENUM ('OWNER', 'MAINTAINER', 'VIEWER'); + +-- CreateEnum +CREATE TYPE "ApiKeyRole" AS ENUM ('CREATE_PROJECT', 'READ_PROJECT', 'UPDATE_PROJECT', 'DELETE_PROJECT', 'CREATE_SECRET', 'READ_SECRET', 'UPDATE_SECRET', 'DELETE_SECRET', 'ADD_USER', 'REMOVE_USER', 'UPDATE_USER_ROLE', 'CREATE_API_KEY', 'READ_API_KEY', 'UPDATE_API_KEY', 'DELETE_API_KEY'); + +-- CreateTable +CREATE TABLE "User" ( + "id" TEXT NOT NULL, + "email" TEXT NOT NULL, + "name" TEXT, + "profilePictureUrl" TEXT, + "isActive" BOOLEAN NOT NULL DEFAULT true, + "isOnboardingFinished" BOOLEAN NOT NULL DEFAULT false, + "isAdmin" BOOLEAN NOT NULL DEFAULT false, + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Subscription" ( + "id" TEXT NOT NULL, + "plan" TEXT NOT NULL, + "isActive" BOOLEAN NOT NULL DEFAULT true, + "userId" TEXT NOT NULL, + + CONSTRAINT "Subscription_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Project" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "publicKey" TEXT NOT NULL, + "privateKey" TEXT, + "lastUpdatedById" TEXT NOT NULL, + + CONSTRAINT "Project_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ProjectMember" ( + "id" TEXT NOT NULL, + "role" "ProjectRole" NOT NULL, + "userId" TEXT NOT NULL, + "projectId" TEXT NOT NULL, + + CONSTRAINT "ProjectMember_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ApiKeyScope" ( + "id" TEXT NOT NULL, + "role" "ApiKeyRole" NOT NULL, + "apiKeyId" TEXT NOT NULL, + "projectId" TEXT NOT NULL, + + CONSTRAINT "ApiKeyScope_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Secret" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "value" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "rotateAt" TIMESTAMP(3), + "lastUpdatedById" TEXT NOT NULL, + "projectId" TEXT NOT NULL, + + CONSTRAINT "Secret_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ApiKey" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "value" TEXT NOT NULL, + "expiresAt" TIMESTAMP(3), + "userId" TEXT NOT NULL, + + CONSTRAINT "ApiKey_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Otp" ( + "code" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "expiresAt" TIMESTAMP(3) NOT NULL +); + +-- CreateIndex +CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); + +-- CreateIndex +CREATE UNIQUE INDEX "Subscription_userId_key" ON "Subscription"("userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Otp_code_key" ON "Otp"("code"); + +-- AddForeignKey +ALTER TABLE "Subscription" ADD CONSTRAINT "Subscription_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Project" ADD CONSTRAINT "Project_lastUpdatedById_fkey" FOREIGN KEY ("lastUpdatedById") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ProjectMember" ADD CONSTRAINT "ProjectMember_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ProjectMember" ADD CONSTRAINT "ProjectMember_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ApiKeyScope" ADD CONSTRAINT "ApiKeyScope_apiKeyId_fkey" FOREIGN KEY ("apiKeyId") REFERENCES "ApiKey"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ApiKeyScope" ADD CONSTRAINT "ApiKeyScope_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Secret" ADD CONSTRAINT "Secret_lastUpdatedById_fkey" FOREIGN KEY ("lastUpdatedById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Secret" ADD CONSTRAINT "Secret_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ApiKey" ADD CONSTRAINT "ApiKey_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Otp" ADD CONSTRAINT "Otp_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/apps/api/src/prisma/schema.prisma b/apps/api/src/prisma/schema.prisma index 10d2a8ff..e9de60f3 100644 --- a/apps/api/src/prisma/schema.prisma +++ b/apps/api/src/prisma/schema.prisma @@ -7,64 +7,123 @@ datasource db { url = env("DATABASE_URL") } -enum Scope { - CREATE - READ - UPDATE - DELETE +enum ProjectRole { + OWNER // Can do everything + MAINTAINER // Can do everything except deleting the project + VIEWER // Can only view the project and its secrets +} + +enum ApiKeyRole { + CREATE_PROJECT + READ_PROJECT + UPDATE_PROJECT + DELETE_PROJECT + CREATE_SECRET + READ_SECRET + UPDATE_SECRET + DELETE_SECRET + ADD_USER + REMOVE_USER + UPDATE_USER_ROLE + CREATE_API_KEY + READ_API_KEY + UPDATE_API_KEY + DELETE_API_KEY } model User { - id String @id @default(cuid()) - email String @unique + id String @id @default(cuid()) + email String @unique name String? profilePictureUrl String? - isActive Boolean @default(true) - isOnboardingFinished Boolean @default(false) - isAdmin Boolean @default(false) + isActive Boolean @default(true) + isOnboardingFinished Boolean @default(false) + isAdmin Boolean @default(false) + subscription Subscription? - projects Project[] + projects ProjectMember[] apiKeys ApiKey[] otps Otp[] + Secret Secret[] // Stores the secrets the user updated + Project Project[] // Stores the projects the user updated +} + +model Subscription { + id String @id @default(cuid()) + plan String + isActive Boolean @default(true) + + user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade) + userId String @unique } model Project { - id String @id @default(cuid()) + id String @id @default(cuid()) name String description String? - owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade) - ownerId String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + publicKey String + privateKey String? // We store this only if the user wants us to do so! - secrets Secret[] - ApiKey ApiKey[] + lastUpdatedBy User @relation(fields: [lastUpdatedById], references: [id], onDelete: Cascade, onUpdate: Cascade) + lastUpdatedById String + + members ProjectMember[] + secrets Secret[] + apiKeyScopes ApiKeyScope[] } -model Secret { - id String @id @default(cuid()) - name String - value String +model ProjectMember { + id String @id @default(cuid()) + role ProjectRole + user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade) + userId String + project Project @relation(fields: [projectId], references: [id], onDelete: Cascade, onUpdate: Cascade) + projectId String +} + +model ApiKeyScope { + id String @id @default(cuid()) + role ApiKeyRole + + apiKey ApiKey @relation(fields: [apiKeyId], references: [id], onDelete: Cascade, onUpdate: Cascade) + apiKeyId String projectId String - project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) + project Project @relation(fields: [projectId], references: [id], onDelete: Cascade, onUpdate: Cascade) } -model ApiKey { - id String @id @default(cuid()) - name String - value String +model Secret { + id String @id @default(cuid()) + name String + value String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + rotateAt DateTime? + + lastUpdatedBy User @relation(fields: [lastUpdatedById], references: [id]) + lastUpdatedById String projectId String - project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) + project Project @relation(fields: [projectId], references: [id], onDelete: Cascade, onUpdate: Cascade) +} + +model ApiKey { + id String @id @default(cuid()) + name String + value String + expiresAt DateTime? - scopes Scope[] + apiKeyScopes ApiKeyScope[] - user User @relation(fields: [userId], references: [id], onDelete: Cascade) + user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade) userId String } model Otp { code String @unique - user User @relation(fields: [userId], references: [id], onDelete: Cascade) + user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade) userId String createdAt DateTime @default(now()) expiresAt DateTime diff --git a/apps/api/src/project/dto/create.project/create.project.spec.ts b/apps/api/src/project/dto/create.project/create.project.spec.ts new file mode 100644 index 00000000..458a7d51 --- /dev/null +++ b/apps/api/src/project/dto/create.project/create.project.spec.ts @@ -0,0 +1,7 @@ +import { CreateProject } from './create.project'; + +describe('CreateProject', () => { + it('should be defined', () => { + expect(new CreateProject()).toBeDefined(); + }); +}); diff --git a/apps/api/src/project/dto/create.project/create.project.ts b/apps/api/src/project/dto/create.project/create.project.ts new file mode 100644 index 00000000..a8c0af42 --- /dev/null +++ b/apps/api/src/project/dto/create.project/create.project.ts @@ -0,0 +1 @@ +export class CreateProject {} diff --git a/apps/api/src/project/dto/update.project/update.project.spec.ts b/apps/api/src/project/dto/update.project/update.project.spec.ts new file mode 100644 index 00000000..b4d5cb57 --- /dev/null +++ b/apps/api/src/project/dto/update.project/update.project.spec.ts @@ -0,0 +1,7 @@ +import { UpdateProject } from './update.project'; + +describe('UpdateProject', () => { + it('should be defined', () => { + expect(new UpdateProject()).toBeDefined(); + }); +}); diff --git a/apps/api/src/project/dto/update.project/update.project.ts b/apps/api/src/project/dto/update.project/update.project.ts new file mode 100644 index 00000000..abb141e3 --- /dev/null +++ b/apps/api/src/project/dto/update.project/update.project.ts @@ -0,0 +1 @@ +export class UpdateProject {} diff --git a/apps/api/src/project/project.controller.spec.ts b/apps/api/src/project/project.controller.spec.ts new file mode 100644 index 00000000..9a56a5b0 --- /dev/null +++ b/apps/api/src/project/project.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ProjectController } from './project.controller'; + +describe('ProjectController', () => { + let controller: ProjectController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [ProjectController], + }).compile(); + + controller = module.get(ProjectController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/apps/api/src/project/project.controller.ts b/apps/api/src/project/project.controller.ts new file mode 100644 index 00000000..66980aef --- /dev/null +++ b/apps/api/src/project/project.controller.ts @@ -0,0 +1,4 @@ +import { Controller } from '@nestjs/common'; + +@Controller('project') +export class ProjectController {} diff --git a/apps/api/src/project/project.module.ts b/apps/api/src/project/project.module.ts new file mode 100644 index 00000000..94abb574 --- /dev/null +++ b/apps/api/src/project/project.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { ProjectService } from './project.service'; +import { ProjectController } from './project.controller'; + +@Module({ + providers: [ProjectService], + controllers: [ProjectController] +}) +export class ProjectModule {} diff --git a/apps/api/src/project/project.service.spec.ts b/apps/api/src/project/project.service.spec.ts new file mode 100644 index 00000000..126b6cc2 --- /dev/null +++ b/apps/api/src/project/project.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ProjectService } from './project.service'; + +describe('ProjectService', () => { + let service: ProjectService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ProjectService], + }).compile(); + + service = module.get(ProjectService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/apps/api/src/project/project.service.ts b/apps/api/src/project/project.service.ts new file mode 100644 index 00000000..3274dd0a --- /dev/null +++ b/apps/api/src/project/project.service.ts @@ -0,0 +1,4 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class ProjectService {} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4eac1ce6..1620a7d1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -226,12 +226,6 @@ importers: specifier: ^2.3.0 version: 2.6.2 - packages/sdk-js: - dependencies: - tslib: - specifier: ^2.3.0 - version: 2.6.2 - packages/sdk-node: {} packages: