From 47950e8c7e92a669c4f71864ed511ff93db5fd22 Mon Sep 17 00:00:00 2001 From: Michal Kleszcz Date: Thu, 4 May 2023 15:49:13 +0200 Subject: [PATCH] Add missing GraphQL mutations --- apps/app/package.json | 2 + packages/graphql/schema/environment.ts | 56 +++++++++++++ packages/graphql/schema/project.ts | 11 +++ packages/graphql/schema/service.ts | 56 +++++++++++++ packages/graphql/schema/version.ts | 80 +++++++++++++++++++ .../migration.sql | 8 ++ .../migration.sql | 8 ++ packages/prisma/prisma/schema.prisma | 2 + pnpm-lock.yaml | 24 ++++++ 9 files changed, 247 insertions(+) create mode 100644 packages/prisma/prisma/migrations/20230504123618_unique_project_name/migration.sql create mode 100644 packages/prisma/prisma/migrations/20230504125832_unique_environment_name/migration.sql diff --git a/apps/app/package.json b/apps/app/package.json index 315b4ea..cfd3481 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -13,6 +13,8 @@ }, "dependencies": { "@apollo/client": "^3.7.13", + "@headlessui/react": "^1.7.14", + "@heroicons/react": "^2.0.17", "@types/node": "18.16.3", "@types/react": "18.2.0", "@types/react-dom": "18.2.1", diff --git a/packages/graphql/schema/environment.ts b/packages/graphql/schema/environment.ts index e27e6c9..59a1c2a 100644 --- a/packages/graphql/schema/environment.ts +++ b/packages/graphql/schema/environment.ts @@ -1,3 +1,5 @@ +import { ZodError } from 'zod'; + import prismaClient from '@vm/prisma/client'; import { builder } from '../builder'; @@ -26,3 +28,57 @@ builder.prismaNode('Environment', { }), }), }); + +const EnvironmentInput = builder.inputType('EnvironmentInput', { + fields: (t) => ({ + name: t.string({ + required: true, + }), + projectId: t.string({ + required: true, + validate: { + uuid: true, + refine: [ + async (id) => { + const count = await prismaClient.project.count({ + where: { + id, + }, + }); + return count === 1; + }, + { message: 'ProjectId is invalid' }, + ], + }, + }), + }), + validate: [ + async (args) => { + const name = args['name']; + const projectId = args['projectId']; + if (!name || !projectId) return false; + const count = await prismaClient.environment.count({ + where: { + name, + projectId, + }, + }); + return count === 0; + }, + { message: 'Environment name must be unique' }, + ], +}); + +builder.mutationField('createEnvironment', (t) => + t.prismaField({ + type: 'Environment', + errors: { + types: [ZodError], + }, + args: { input: t.arg({ type: EnvironmentInput, required: true }) }, + resolve: (root, _parent, args) => + prismaClient.environment.create({ + data: args.input, + }), + }) +); diff --git a/packages/graphql/schema/project.ts b/packages/graphql/schema/project.ts index 9051b1a..fcf17db 100644 --- a/packages/graphql/schema/project.ts +++ b/packages/graphql/schema/project.ts @@ -29,6 +29,17 @@ const ProjectInput = builder.inputType('ProjectInput', { required: true, validate: { maxLength: 255, + refine: [ + async (val) => { + const count = await prismaClient.project.count({ + where: { + name: val, + }, + }); + return count === 0; + }, + { message: 'Project name must be unique' }, + ], }, }), }), diff --git a/packages/graphql/schema/service.ts b/packages/graphql/schema/service.ts index c2e2331..f20e5c9 100644 --- a/packages/graphql/schema/service.ts +++ b/packages/graphql/schema/service.ts @@ -1,3 +1,5 @@ +import { ZodError } from 'zod'; + import prismaClient from '@vm/prisma/client'; import { builder } from '../builder'; @@ -26,3 +28,57 @@ builder.prismaNode('Service', { }), }), }); + +const ServiceInput = builder.inputType('ServiceInput', { + fields: (t) => ({ + name: t.string({ + required: true, + }), + projectId: t.string({ + required: true, + validate: { + uuid: true, + refine: [ + async (id) => { + const count = await prismaClient.project.count({ + where: { + id, + }, + }); + return count === 1; + }, + { message: 'ProjectId is invalid' }, + ], + }, + }), + }), + validate: [ + async (args) => { + const name = args['name']; + const projectId = args['projectId']; + if (!name || !projectId) return false; + const count = await prismaClient.service.count({ + where: { + name, + projectId, + }, + }); + return count === 0; + }, + { message: 'Service name must be unique' }, + ], +}); + +builder.mutationField('createService', (t) => + t.prismaField({ + type: 'Service', + errors: { + types: [ZodError], + }, + args: { input: t.arg({ type: ServiceInput, required: true }) }, + resolve: (root, _parent, args) => + prismaClient.service.create({ + data: args.input, + }), + }) +); diff --git a/packages/graphql/schema/version.ts b/packages/graphql/schema/version.ts index d96f755..4d456cb 100644 --- a/packages/graphql/schema/version.ts +++ b/packages/graphql/schema/version.ts @@ -1,3 +1,5 @@ +import { ZodError } from 'zod'; + import prismaClient from '@vm/prisma/client'; import { builder } from '../builder'; @@ -18,3 +20,81 @@ builder.prismaNode('Version', { }), }), }); + +const VersionInput = builder.inputType('VersionInput', { + fields: (t) => ({ + name: t.string({ + required: true, + }), + environmentId: t.string({ + required: true, + validate: { + uuid: true, + refine: [ + async (id) => { + const count = await prismaClient.environment.count({ + where: { + id, + }, + }); + return count === 1; + }, + { message: 'EnvironmentId is invalid' }, + ], + }, + }), + isCurrent: t.boolean({ + required: true, + validate: { + type: 'boolean', + }, + }), + }), + validate: [ + async (args) => { + const name = args['name']; + const environmentId = args['environmentId']; + if (!name || !environmentId) return false; + const count = await prismaClient.version.count({ + where: { + name, + environmentId, + }, + }); + return count === 0; + }, + { message: 'Version name must be unique' }, + ], +}); + +builder.mutationField('createVersion', (t) => + t.prismaField({ + type: 'Version', + errors: { + types: [ZodError], + }, + args: { input: t.arg({ type: VersionInput, required: true }) }, + resolve: async (root, _parent, args) => { + const { input } = args; + const getCreateVersionQuery = () => + prismaClient.version.create({ + data: input, + }); + if (!input.isCurrent) { + return getCreateVersionQuery(); + } + const [_, version] = await prismaClient.$transaction([ + prismaClient.version.updateMany({ + where: { + environmentId: input.environmentId, + }, + data: { + isCurrent: false, + }, + }), + getCreateVersionQuery(), + ]); + return version; + }, + }) +); diff --git a/packages/prisma/prisma/migrations/20230504123618_unique_project_name/migration.sql b/packages/prisma/prisma/migrations/20230504123618_unique_project_name/migration.sql new file mode 100644 index 0000000..81f11ed --- /dev/null +++ b/packages/prisma/prisma/migrations/20230504123618_unique_project_name/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - A unique constraint covering the columns `[name]` on the table `Project` will be added. If there are existing duplicate values, this will fail. + +*/ +-- CreateIndex +CREATE UNIQUE INDEX "Project_name_key" ON "Project"("name"); diff --git a/packages/prisma/prisma/migrations/20230504125832_unique_environment_name/migration.sql b/packages/prisma/prisma/migrations/20230504125832_unique_environment_name/migration.sql new file mode 100644 index 0000000..0d80fb0 --- /dev/null +++ b/packages/prisma/prisma/migrations/20230504125832_unique_environment_name/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - A unique constraint covering the columns `[name,projectId]` on the table `Environment` will be added. If there are existing duplicate values, this will fail. + +*/ +-- CreateIndex +CREATE UNIQUE INDEX "Environment_name_projectId_key" ON "Environment"("name", "projectId"); diff --git a/packages/prisma/prisma/schema.prisma b/packages/prisma/prisma/schema.prisma index 1029ed4..313b89e 100644 --- a/packages/prisma/prisma/schema.prisma +++ b/packages/prisma/prisma/schema.prisma @@ -19,6 +19,7 @@ model Project { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt services Service[] + @@unique(fields: [name], name: "uniqueProjectName") } model Service { @@ -40,6 +41,7 @@ model Environment { versions Version[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt + @@unique(fields: [name, projectId], name: "uniqueEnvironmentName") } model Version { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5003eb5..7bb8868 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -46,6 +46,8 @@ importers: apps/app: specifiers: '@apollo/client': ^3.7.13 + '@headlessui/react': ^1.7.14 + '@heroicons/react': ^2.0.17 '@prisma/nextjs-monorepo-workaround-plugin': ^4.13.0 '@types/node': 18.16.3 '@types/react': 18.2.0 @@ -65,6 +67,8 @@ importers: typescript: 5.0.4 dependencies: '@apollo/client': 3.7.13_gdcq4dv6opitr3wbfwyjmanyra + '@headlessui/react': 1.7.14_biqbaboplfbrettd7655fr4n2y + '@heroicons/react': 2.0.17_react@18.2.0 '@types/node': 18.16.3 '@types/react': 18.2.0 '@types/react-dom': 18.2.1 @@ -1721,6 +1725,26 @@ packages: tslib: 2.5.0 dev: false + /@headlessui/react/1.7.14_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-znzdq9PG8rkwcu9oQ2FwIy0ZFtP9Z7ycS+BAqJ3R5EIqC/0bJGvhT7193rFf+45i9nnPsYvCQVW4V/bB9Xc+gA==} + engines: {node: '>=10'} + peerDependencies: + react: ^16 || ^17 || ^18 + react-dom: ^16 || ^17 || ^18 + dependencies: + client-only: 0.0.1 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + + /@heroicons/react/2.0.17_react@18.2.0: + resolution: {integrity: sha512-90GMZktkA53YbNzHp6asVEDevUQCMtxWH+2UK2S8OpnLEu7qckTJPhNxNQG52xIR1WFTwFqtH6bt7a60ZNcLLA==} + peerDependencies: + react: '>= 16' + dependencies: + react: 18.2.0 + dev: false + /@humanwhocodes/config-array/0.11.8: resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==} engines: {node: '>=10.10.0'}