diff --git a/apps/app/src/pages/api/cli/graphql.ts b/apps/app/src/pages/api/cli/graphql.ts new file mode 100644 index 0000000..269d773 --- /dev/null +++ b/apps/app/src/pages/api/cli/graphql.ts @@ -0,0 +1,18 @@ +import { createYoga } from 'graphql-yoga'; +import type { NextApiRequest, NextApiResponse } from 'next'; + +import schema from '@vm/graphql/schema/cli'; + +export default createYoga<{ + req: NextApiRequest; + res: NextApiResponse; +}>({ + schema, + graphqlEndpoint: '/api/cli/graphql', +}); + +export const config = { + api: { + bodyParser: false, + }, +}; diff --git a/packages/graphql/schema/cli/index.ts b/packages/graphql/schema/cli/index.ts new file mode 100644 index 0000000..5288c59 --- /dev/null +++ b/packages/graphql/schema/cli/index.ts @@ -0,0 +1,9 @@ +import { GraphQLSchema } from 'graphql/type'; + +import { builder } from '../../builder'; +import '../../types'; +import '../nodes'; +import './version'; + +const schema: GraphQLSchema = builder.toSchema(); +export default schema; diff --git a/packages/graphql/schema/cli/version.ts b/packages/graphql/schema/cli/version.ts new file mode 100644 index 0000000..45bfed3 --- /dev/null +++ b/packages/graphql/schema/cli/version.ts @@ -0,0 +1,83 @@ +import { ZodError } from 'zod'; + +import prismaClient from '@vm/prisma/client'; + +import { builder } from '../../builder'; + +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/graphql/schema/environment.ts b/packages/graphql/schema/environment.ts index 59a1c2a..b7376fd 100644 --- a/packages/graphql/schema/environment.ts +++ b/packages/graphql/schema/environment.ts @@ -4,31 +4,6 @@ import prismaClient from '@vm/prisma/client'; import { builder } from '../builder'; -builder.prismaNode('Environment', { - id: { field: 'id' }, - fields: (t) => ({ - name: t.exposeString('name'), - project: t.relation('project'), - versions: t.prismaConnection({ - type: 'Version', - cursor: 'id', - resolve: (query, parent) => prismaClient.version.findMany({ ...query, where: { environmentId: parent.id } }), - }), - currentVersion: t.prismaField({ - type: 'Version', - nullable: true, - resolve: async (query, parent) => - prismaClient.version.findFirst({ - ...query, - where: { - isCurrent: true, - environmentId: parent.id, - }, - }), - }), - }), -}); - const EnvironmentInput = builder.inputType('EnvironmentInput', { fields: (t) => ({ name: t.string({ diff --git a/packages/graphql/schema/index.ts b/packages/graphql/schema/index.ts index 07730c8..effe2e7 100644 --- a/packages/graphql/schema/index.ts +++ b/packages/graphql/schema/index.ts @@ -3,9 +3,9 @@ import { GraphQLSchema } from 'graphql/type'; import { builder } from '../builder'; import '../types'; import './environment'; +import './nodes'; import './project'; import './service'; -import './serviceVersion'; import './version'; const schema: GraphQLSchema = builder.toSchema(); diff --git a/packages/graphql/schema/nodes/environment.ts b/packages/graphql/schema/nodes/environment.ts new file mode 100644 index 0000000..146796d --- /dev/null +++ b/packages/graphql/schema/nodes/environment.ts @@ -0,0 +1,28 @@ +import prismaClient from '@vm/prisma/client'; + +import { builder } from '../../builder'; + +builder.prismaNode('Environment', { + id: { field: 'id' }, + fields: (t) => ({ + name: t.exposeString('name'), + project: t.relation('project'), + versions: t.prismaConnection({ + type: 'Version', + cursor: 'id', + resolve: (query, parent) => prismaClient.version.findMany({ ...query, where: { environmentId: parent.id } }), + }), + currentVersion: t.prismaField({ + type: 'Version', + nullable: true, + resolve: async (query, parent) => + prismaClient.version.findFirst({ + ...query, + where: { + isCurrent: true, + environmentId: parent.id, + }, + }), + }), + }), +}); diff --git a/packages/graphql/schema/nodes/index.ts b/packages/graphql/schema/nodes/index.ts new file mode 100644 index 0000000..2d2400e --- /dev/null +++ b/packages/graphql/schema/nodes/index.ts @@ -0,0 +1,5 @@ +import './environment'; +import './project'; +import './service'; +import './serviceVersion'; +import './version'; diff --git a/packages/graphql/schema/nodes/project.ts b/packages/graphql/schema/nodes/project.ts new file mode 100644 index 0000000..82410ab --- /dev/null +++ b/packages/graphql/schema/nodes/project.ts @@ -0,0 +1,22 @@ +import prismaClient from '@vm/prisma/client'; + +import { builder } from '../../builder'; + +builder.prismaNode('Project', { + id: { field: 'id' }, + fields: (t) => ({ + name: t.exposeString('name'), + environments: t.prismaConnection({ + type: 'Environment', + cursor: 'id', + resolve: (query, parent, args, context, info) => + prismaClient.environment.findMany({ ...query, where: { projectId: parent.id } }), + }), + services: t.prismaConnection({ + type: 'Service', + cursor: 'id', + resolve: (query, parent, args, context, info) => + prismaClient.service.findMany({ ...query, where: { projectId: parent.id } }), + }), + }), +}); diff --git a/packages/graphql/schema/nodes/service.ts b/packages/graphql/schema/nodes/service.ts new file mode 100644 index 0000000..b76840a --- /dev/null +++ b/packages/graphql/schema/nodes/service.ts @@ -0,0 +1,28 @@ +import prismaClient from '@vm/prisma/client'; + +import { builder } from '../../builder'; + +builder.prismaNode('Service', { + id: { field: 'id' }, + fields: (t) => ({ + name: t.exposeString('name'), + project: t.relation('project'), + versions: t.prismaConnection({ + type: 'ServiceVersion', + cursor: 'id', + resolve: (query, parent) => prismaClient.serviceVersion.findMany({ ...query, where: { serviceId: parent.id } }), + }), + currentVersion: t.prismaField({ + type: 'ServiceVersion', + nullable: true, + resolve: async (query, parent) => + prismaClient.serviceVersion.findFirst({ + ...query, + where: { + isCurrent: true, + serviceId: parent.id, + }, + }), + }), + }), +}); diff --git a/packages/graphql/schema/serviceVersion.ts b/packages/graphql/schema/nodes/serviceVersion.ts similarity index 88% rename from packages/graphql/schema/serviceVersion.ts rename to packages/graphql/schema/nodes/serviceVersion.ts index d854f5a..166d175 100644 --- a/packages/graphql/schema/serviceVersion.ts +++ b/packages/graphql/schema/nodes/serviceVersion.ts @@ -1,4 +1,4 @@ -import { builder } from '../builder'; +import { builder } from '../../builder'; builder.prismaNode('ServiceVersion', { id: { field: 'id' }, diff --git a/packages/graphql/schema/nodes/version.ts b/packages/graphql/schema/nodes/version.ts new file mode 100644 index 0000000..c6cddeb --- /dev/null +++ b/packages/graphql/schema/nodes/version.ts @@ -0,0 +1,20 @@ +import prismaClient from '@vm/prisma/client'; + +import { builder } from '../../builder'; + +builder.prismaNode('Version', { + id: { field: 'id' }, + fields: (t) => ({ + name: t.exposeString('name'), + createdAt: t.expose('createdAt', { + type: 'DateTime', + }), + isCurrent: t.exposeBoolean('isCurrent'), + environment: t.relation('environment'), + serviceVersions: t.prismaConnection({ + type: 'ServiceVersion', + cursor: 'id', + resolve: (query, parent) => prismaClient.serviceVersion.findMany({ ...query, where: { versionId: parent.id } }), + }), + }), +}); diff --git a/packages/graphql/schema/project.ts b/packages/graphql/schema/project.ts index a752951..8d3e16a 100644 --- a/packages/graphql/schema/project.ts +++ b/packages/graphql/schema/project.ts @@ -4,25 +4,6 @@ import prismaClient from '@vm/prisma/client'; import { builder } from '../builder'; -builder.prismaNode('Project', { - id: { field: 'id' }, - fields: (t) => ({ - name: t.exposeString('name'), - environments: t.prismaConnection({ - type: 'Environment', - cursor: 'id', - resolve: (query, parent, args, context, info) => - prismaClient.environment.findMany({ ...query, where: { projectId: parent.id } }), - }), - services: t.prismaConnection({ - type: 'Service', - cursor: 'id', - resolve: (query, parent, args, context, info) => - prismaClient.service.findMany({ ...query, where: { projectId: parent.id } }), - }), - }), -}); - const ProjectInput = builder.inputType('ProjectInput', { fields: (t) => ({ name: t.string({ diff --git a/packages/graphql/schema/service.ts b/packages/graphql/schema/service.ts index f20e5c9..12ccffa 100644 --- a/packages/graphql/schema/service.ts +++ b/packages/graphql/schema/service.ts @@ -4,31 +4,6 @@ import prismaClient from '@vm/prisma/client'; import { builder } from '../builder'; -builder.prismaNode('Service', { - id: { field: 'id' }, - fields: (t) => ({ - name: t.exposeString('name'), - project: t.relation('project'), - versions: t.prismaConnection({ - type: 'ServiceVersion', - cursor: 'id', - resolve: (query, parent) => prismaClient.serviceVersion.findMany({ ...query, where: { serviceId: parent.id } }), - }), - currentVersion: t.prismaField({ - type: 'ServiceVersion', - nullable: true, - resolve: async (query, parent) => - prismaClient.serviceVersion.findFirst({ - ...query, - where: { - isCurrent: true, - serviceId: parent.id, - }, - }), - }), - }), -}); - const ServiceInput = builder.inputType('ServiceInput', { fields: (t) => ({ name: t.string({ diff --git a/packages/graphql/schema/version.ts b/packages/graphql/schema/version.ts index 4d456cb..cdc70fa 100644 --- a/packages/graphql/schema/version.ts +++ b/packages/graphql/schema/version.ts @@ -4,23 +4,6 @@ import prismaClient from '@vm/prisma/client'; import { builder } from '../builder'; -builder.prismaNode('Version', { - id: { field: 'id' }, - fields: (t) => ({ - name: t.exposeString('name'), - createdAt: t.expose('createdAt', { - type: 'DateTime', - }), - isCurrent: t.exposeBoolean('isCurrent'), - environment: t.relation('environment'), - serviceVersions: t.prismaConnection({ - type: 'ServiceVersion', - cursor: 'id', - resolve: (query, parent) => prismaClient.serviceVersion.findMany({ ...query, where: { versionId: parent.id } }), - }), - }), -}); - const VersionInput = builder.inputType('VersionInput', { fields: (t) => ({ name: t.string({