Mutation implementations (#5)

* split up files

* timestamps, scalars and dayjs

* lib server directory

* move bun types to dev

* move storybook dark mode to dev

* got timestamps working

* fix reference

* separate schema into files and add mutations
This commit is contained in:
Baobeld 2024-12-11 15:59:02 -05:00 committed by GitHub
parent 5395e7f904
commit 37d901a86c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 321 additions and 80 deletions

BIN
bun.lockb

Binary file not shown.

View file

@ -31,6 +31,7 @@
"@sveltejs/adapter-auto": "^3.0.0", "@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/kit": "^2.9.0", "@sveltejs/kit": "^2.9.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0", "@sveltejs/vite-plugin-svelte": "^5.0.0",
"@types/bun": "^1.1.14",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"eslint": "^9.7.0", "eslint": "^9.7.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
@ -41,6 +42,7 @@
"prettier-plugin-tailwindcss": "^0.6.5", "prettier-plugin-tailwindcss": "^0.6.5",
"prisma": "^6.0.1", "prisma": "^6.0.1",
"storybook": "^8.4.7", "storybook": "^8.4.7",
"storybook-dark-mode": "^4.0.2",
"svelte": "^5.0.0", "svelte": "^5.0.0",
"svelte-check": "^4.0.0", "svelte-check": "^4.0.0",
"tailwindcss": "^3.4.9", "tailwindcss": "^3.4.9",
@ -54,12 +56,11 @@
"@pothos/plugin-prisma": "^4.4.0", "@pothos/plugin-prisma": "^4.4.0",
"@prisma/client": "6.0.1", "@prisma/client": "6.0.1",
"@tailwindcss/typography": "^0.5.15", "@tailwindcss/typography": "^0.5.15",
"@types/bun": "^1.1.14", "dayjs": "^1.11.13",
"graphql": "^16.9.0", "graphql": "^16.9.0",
"graphql-yoga": "^5.10.4", "graphql-yoga": "^5.10.4",
"pino": "^9.5.0", "pino": "^9.5.0",
"pino-pretty": "^13.0.0", "pino-pretty": "^13.0.0",
"storybook-dark-mode": "^4.0.2",
"zod": "^3.24.0" "zod": "^3.24.0"
} }
} }

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,37 @@
/*
Warnings:
- Made the column `content` on table `Post` required. This step will fail if there are existing NULL values in that column.
- Made the column `name` on table `User` required. This step will fail if there are existing NULL values in that column.
*/
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Post" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"title" TEXT NOT NULL,
"content" TEXT NOT NULL,
"published" BOOLEAN DEFAULT false,
"authorId" INTEGER NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Post_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_Post" ("authorId", "content", "id", "published", "title") SELECT "authorId", "content", "id", "published", "title" FROM "Post";
DROP TABLE "Post";
ALTER TABLE "new_Post" RENAME TO "Post";
CREATE TABLE "new_User" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"email" TEXT,
"name" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO "new_User" ("email", "id", "name") SELECT "email", "id", "name" FROM "User";
DROP TABLE "User";
ALTER TABLE "new_User" RENAME TO "User";
CREATE UNIQUE INDEX "User_id_key" ON "User"("id");
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;

View file

@ -15,17 +15,21 @@ datasource db {
} }
model User { model User {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement()) @unique
email String @unique email String? @unique
name String? name String
posts Post[] posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
} }
model Post { model Post {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
title String title String
content String? content String
published Boolean @default(false) published Boolean? @default(false)
author User @relation(fields: [authorId], references: [id]) author User @relation(fields: [authorId], references: [id])
authorId Int authorId Int
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
} }

View file

@ -1,65 +0,0 @@
import { prisma } from '$lib/prisma';
import { Context } from '$lib/yoga/context';
import SchemaBuilder from '@pothos/core';
import PrismaPlugin, { type PrismaTypesFromClient } from '@pothos/plugin-prisma';
type ContextType = ReturnType<typeof Context>;
export const builder = new SchemaBuilder<{
Context: ContextType;
PrismaTypes: PrismaTypesFromClient<typeof prisma>;
}>({
plugins: [PrismaPlugin],
prisma: {
client: prisma,
// defaults to false, uses /// comments from prisma schema as descriptions
// for object types, relations and exposed fields.
// descriptions can be omitted by setting description to false
exposeDescriptions: false,
// use where clause from prismaRelatedConnection for totalCount (defaults to true)
filterConnectionTotalCount: true,
// warn when not using a query parameter correctly
onUnusedQuery: process.env.NODE_ENV === 'production' ? null : 'warn'
}
});
const User = builder.prismaObject('User', {
fields: (t) => ({
id: t.exposeID('id'),
email: t.exposeString('email'),
name: t.exposeString('name'),
posts: t.relation('posts')
})
});
const Post = builder.prismaObject('Post', {
fields: (t) => ({
id: t.exposeID('id'),
title: t.exposeString('title'),
content: t.exposeString('content'),
published: t.exposeBoolean('published'),
author: t.relation('author')
})
});
builder.queryType({
fields: (t) => ({
version: t.string({
resolve: (parent, args, context) => context.config.app_version
}),
users: t.prismaField({
type: [User],
resolve: async () => {
return await prisma.user.findMany();
}
}),
posts: t.prismaField({
type: [Post],
resolve: async () => {
return await prisma.post.findMany();
}
})
})
});
export const Schema = builder.toSchema();

View file

@ -1,4 +1,4 @@
import { logger } from '$lib/logger'; import { logger } from '$lib/server/logger';
import { z } from 'zod'; import { z } from 'zod';
export interface Configuration { export interface Configuration {

View file

@ -0,0 +1,28 @@
import { prisma } from '$lib/server/prisma';
import type { Context } from '$lib/server/yoga';
import SchemaBuilder from '@pothos/core';
import PrismaPlugin, { type PrismaTypesFromClient } from '@pothos/plugin-prisma';
import type { Scalars } from './schema/Scalars';
type PothosType = {
Context: ReturnType<typeof Context>;
PrismaTypes: PrismaTypesFromClient<typeof prisma>;
Scalars: Scalars;
};
SchemaBuilder.allowPluginReRegistration = true;
export const builder = new SchemaBuilder<PothosType>({
plugins: [PrismaPlugin],
prisma: {
client: prisma,
// defaults to false, uses /// comments from prisma schema as descriptions
// for object types, relations and exposed fields.
// descriptions can be omitted by setting description to false
exposeDescriptions: false,
// use where clause from prismaRelatedConnection for totalCount (defaults to true)
filterConnectionTotalCount: true,
// warn when not using a query parameter correctly
onUnusedQuery: process.env.NODE_ENV === 'production' ? null : 'warn'
}
});

View file

@ -0,0 +1 @@
export * from './schema';

View file

@ -0,0 +1,14 @@
import { builder } from '../../builder';
export const DateScalar = builder.scalarType('Date', {
description: 'Date Scalar in ISO format',
serialize: (date) => {
return date.toISOString();
},
parseValue: (date) => {
if (typeof date !== 'string') {
throw new Error('Cyka blyat');
}
return new Date(date);
}
});

View file

@ -0,0 +1,8 @@
export * from './Date';
export type Scalars = {
Date: {
Input: Date;
Output: Date;
};
};

View file

@ -0,0 +1,18 @@
import { builder } from '../builder';
builder.queryType({});
builder.queryField('version', (t) =>
t.string({
description: 'Application version',
resolve: (parent, args, context) => context.config.app_version
})
);
builder.mutationType({});
import './Scalars';
import './posts';
import './users';
export const Schema = builder.toSchema();

View file

@ -0,0 +1,110 @@
import { prisma } from '$lib/server/prisma';
import { builder } from '../builder';
export const Post = builder.prismaObject('Post', {
fields: (t) => ({
id: t.exposeID('id'),
title: t.exposeString('title'),
content: t.exposeString('content'),
published: t.exposeBoolean('published'),
author: t.relation('author'),
createdAt: t.expose('createdAt', {
type: 'Date'
}),
updatedAt: t.expose('updatedAt', {
type: 'Date'
})
})
});
const CreatePost = builder.inputType('CreatePost', {
fields: (t) => ({
title: t.string({
required: true
}),
content: t.string({
required: true
}),
published: t.boolean(),
authorId: t.id({
required: true
})
})
});
const UpdatePost = builder.inputType('UpdatePost', {
fields: (t) => ({
id: t.id({
required: true
}),
title: t.string(),
content: t.string(),
published: t.boolean(),
authorId: t.id()
})
});
builder.queryFields((t) => ({
posts: t.prismaField({
type: [Post],
resolve: async () => {
return await prisma.post.findMany();
}
})
}));
builder.mutationFields((t) => ({
createPost: t.field({
type: Post,
args: {
input: t.arg({ required: true, type: CreatePost })
},
resolve: async (parent, args) => {
const author = await prisma.user.findUnique({
where: { id: Number(args.input.authorId) }
});
if (!author) {
throw new Error('Author does not exist!');
}
const post = await prisma.post.create({
data: {
title: args.input.title,
content: args.input.content,
published: args.input.published,
author: {
connect: {
id: author.id
}
}
}
});
return post;
}
}),
updatePost: t.field({
type: Post,
args: {
input: t.arg({ required: true, type: UpdatePost })
},
resolve: async (parent, args) => {
const post = await prisma.post.update({
where: {
id: Number(args.input.id)
},
data: {
title: args.input.title ?? undefined,
content: args.input.content ?? undefined,
published: args.input.published,
...(args.input.authorId && {
author: {
connect: {
id: Number(args.input.authorId)
}
}
})
}
});
return post;
}
})
}));

View file

@ -0,0 +1,83 @@
import { prisma } from '$lib/server/prisma';
import { builder } from '../builder';
export const User = builder.prismaObject('User', {
fields: (t) => ({
id: t.exposeID('id'),
email: t.exposeString('email'),
name: t.exposeString('name'),
posts: t.relation('posts'),
createdAt: t.expose('createdAt', {
type: 'Date'
}),
updatedAt: t.expose('updatedAt', {
type: 'Date'
})
})
});
const CreateUser = builder.inputType('CreateUser', {
fields: (t) => ({
email: t.string({
required: true
}),
name: t.string({
required: true
})
})
});
const UpdateUser = builder.inputType('UpdateUser', {
fields: (t) => ({
id: t.id({
required: true
}),
email: t.string(),
name: t.string()
})
});
builder.queryFields((t) => ({
users: t.prismaField({
type: [User],
resolve: async () => {
return await prisma.user.findMany();
}
})
}));
builder.mutationFields((t) => ({
createUser: t.field({
type: User,
args: {
input: t.arg({ required: true, type: CreateUser })
},
resolve: async (parent, args) => {
const post = await prisma.user.create({
data: {
email: args.input.email,
name: args.input.name
}
});
return post;
}
}),
updateUser: t.field({
type: User,
args: {
input: t.arg({ required: true, type: UpdateUser })
},
resolve: async (parent, args) => {
const post = await prisma.user.update({
where: {
id: Number(args.input.id)
},
data: {
email: args.input.email,
name: args.input.name ?? undefined
}
});
return post;
}
})
}));

View file

@ -1,4 +1,4 @@
import { Config } from '$lib/config'; import { Config } from '$lib/server/config';
import type { YogaInitialContext } from 'graphql-yoga'; import type { YogaInitialContext } from 'graphql-yoga';
export const Context = (initialContext: YogaInitialContext) => ({ export const Context = (initialContext: YogaInitialContext) => ({

View file

@ -0,0 +1,2 @@
export * from './context';
export * from './server';

View file

@ -1,5 +1,5 @@
import { yogaLogger } from '$lib/logger'; import { yogaLogger } from '$lib/server/logger';
import { Schema } from '$lib/pothos'; import { Schema } from '$lib/server/pothos';
import type { RequestEvent } from '@sveltejs/kit'; import type { RequestEvent } from '@sveltejs/kit';
import { createYoga } from 'graphql-yoga'; import { createYoga } from 'graphql-yoga';
import { Context } from './context'; import { Context } from './context';

View file

@ -1,3 +1,3 @@
import { Yoga } from '$lib/yoga'; import { Yoga } from '$lib/server/yoga';
export { Yoga as GET, Yoga as POST }; export { Yoga as GET, Yoga as POST };