restructured drizzle schema; added comments schema and server side operations
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 0s

This commit is contained in:
mr-shortman 2025-03-18 21:43:41 +01:00
parent 91a5241220
commit 6a32dcced7
15 changed files with 279 additions and 110 deletions

View File

@ -3,7 +3,7 @@ import { type Config } from "drizzle-kit";
import { env } from "@/env";
export default {
schema: "./src/server/db/schema.ts",
schema: "./src/server/db/schema/index.ts",
dialect: "postgresql",
dbCredentials: {
url: env.DATABASE_URL,

View File

@ -1,9 +1,10 @@
import { z } from "zod";
import { editorContentSchema } from ".";
export const articleSchema = z.object({
title: z.string().min(1),
slug: z.string().min(1),
content: z.any().optional(),
content: editorContentSchema.optional(),
authorId: z.string().optional(),
categoryId: z.string().optional(),
published: z.boolean(),

View File

@ -0,0 +1,7 @@
import { z } from "zod";
import { editorContentSchema } from ".";
export const commentSchema = z.object({
content: editorContentSchema,
parentId: z.string().optional(),
});

View File

@ -0,0 +1,3 @@
import { z } from "zod";
export const editorContentSchema = z.any(); // TODO: define editor content schema

View File

@ -1,7 +0,0 @@
"use server";
import { signIn } from "@/server/auth";
export async function loginOAuth(provider: string) {
return await signIn(provider);
}

View File

@ -0,0 +1,43 @@
"use server";
import { appRoutes } from "@/config";
import { commentSchema } from "@/lib/validation/zod/comment";
import { api } from "@/trpc/server";
import { revalidatePath } from "next/cache";
import { z } from "zod";
export async function createComment(
comment: z.infer<typeof commentSchema>,
articleId: string,
) {
const commentId = await api.comment.create({
comment,
articleId,
});
if (!commentId)
return {
success: false,
message: "Error creating comment",
};
revalidatePath(appRoutes.article(articleId));
return {
success: true,
};
}
export async function deleteComment(commentId: string) {
const articleId = (
await api.comment.delete({
commentId,
})
)?.articleId;
if (!articleId)
return {
success: false,
message: "Error deleting comment",
};
revalidatePath(appRoutes.article(articleId));
return {
success: true,
};
}

View File

@ -1,22 +1,20 @@
import { articleRouter } from "./routers/article";
import { categoryRouter } from "@/server/api/routers/category";
import { createCallerFactory, createTRPCRouter } from "@/server/api/trpc";
import { usersRouter } from "./routers/users";
import { authorRouter } from "./routers/author";
import { appRouter as globalRouter } from "./routers/app";
// import { authRouter } from "./routers/auth";
/**
* This is the primary router for your server.
*
* All routers added in /api/routers should be manually added here.
*/
import {
articleRouter,
categoryRouter,
usersRouter,
authorRouter,
appRouter as globalRouter,
commentRouter,
} from "./routers";
export const appRouter = createTRPCRouter({
article: articleRouter,
category: categoryRouter,
comment: commentRouter,
users: usersRouter,
author: authorRouter,
app: globalRouter,
// auth: authRouter,
});
// export type definition of API

View File

@ -0,0 +1,49 @@
import { and, eq } from "drizzle-orm";
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";
import { articles, comments } from "@/server/db/schema";
import { z } from "zod";
import { commentSchema } from "@/lib/validation/zod/comment";
export const commentRouter = createTRPCRouter({
create: protectedProcedure
.input(
z.object({
comment: commentSchema,
articleId: z.string(),
}),
)
.mutation(async ({ ctx, input }) => {
const [comment] = await ctx.db
.insert(comments)
.values({
...input.comment,
authorId: ctx.session.user.id,
articleId: input.articleId,
})
.returning({
id: comments.id,
});
return comment?.id;
}),
delete: protectedProcedure
.input(
z.object({
commentId: z.string(),
}),
)
.mutation(async ({ ctx, input }) => {
const [comment] = await ctx.db
.delete(comments)
.where(
and(
eq(comments.id, input.commentId),
eq(comments.authorId, ctx.session.user.id),
),
)
.returning({
id: comments.id,
articleId: comments.articleId,
});
return comment;
}),
});

View File

@ -1,9 +1,18 @@
// import { passwordSchema, userSchema } from "@/lib/validation/zod/user";
// import { createTRPCRouter, publicProcedure } from "../trpc";
// import { z } from "zod";
// import { eq } from "drizzle-orm";
// import { users } from "@/server/db/schema";
// import argon from "argon2";
import { articleRouter } from "./article";
import { usersRouter } from "./users";
import { authorRouter } from "./author";
import { appRouter } from "./app";
import { commentRouter } from "./comment";
import { categoryRouter } from "./category";
export {
articleRouter,
categoryRouter,
commentRouter,
usersRouter,
authorRouter,
appRouter,
};
// export const authRouter = createTRPCRouter({
// register: publicProcedure
@ -52,3 +61,10 @@
// }
// }),
// });
// "use server";
// import { signIn } from "@/server/auth";
// export async function loginOAuth(provider: string) {
// return await signIn(provider);
// }

View File

@ -0,0 +1,52 @@
import { boolean, index, jsonb, timestamp, varchar } from "drizzle-orm/pg-core";
import { createId, createTable } from "./schema-utils";
import { JSONContent } from "novel";
import { relations, sql } from "drizzle-orm";
import { categories, Category } from "./category";
import { User } from "next-auth";
import { users } from "./auth";
import { comments } from "./comments";
export const articles = createTable(
"article",
{
id: varchar("id", { length: 255 })
.primaryKey()
.$defaultFn(() => createId())
.notNull(),
title: varchar("title", { length: 256 }).notNull(),
slug: varchar("slug", { length: 256 }).unique().notNull(),
authorId: varchar("author_id", { length: 255 }),
content: jsonb("content").$type<JSONContent>(),
categoryId: varchar("category_id", { length: 255 }),
published: boolean("published").default(false),
createdAt: timestamp("created_at", { withTimezone: true })
.default(sql`CURRENT_TIMESTAMP`)
.notNull(),
updatedAt: timestamp("updated_at", { withTimezone: true }).$onUpdate(
() => new Date(),
),
},
(example) => ({
titleIndex: index("article_title_idx").on(example.title),
slugIndex: index("article_slug_idx").on(example.slug),
createdAtIndex: index("article_created_at_idx").on(example.createdAt),
}),
);
export const articleRelations = relations(articles, ({ one, many }) => ({
category: one(categories, {
fields: [articles.categoryId],
references: [categories.id],
}),
author: one(users, {
fields: [articles.authorId],
references: [users.id],
}),
comments: many(comments),
}));
export type Article = typeof articles.$inferSelect & {
author?: User;
category?: Category;
};

View File

@ -1,101 +1,21 @@
import { createId } from "@paralleldrive/cuid2";
import { relations, sql } from "drizzle-orm";
import {
boolean,
index,
integer,
jsonb,
pgTableCreator,
primaryKey,
text,
timestamp,
varchar,
} from "drizzle-orm/pg-core";
import { User } from "next-auth";
import { createId, createTable } from "./schema-utils";
import { type AdapterAccount } from "next-auth/adapters";
import { JSONContent } from "novel";
export const createTable = pgTableCreator((name) => `logipedia_${name}`);
export const articles = createTable(
"article",
{
id: varchar("id", { length: 255 })
.primaryKey()
.$defaultFn(() => createId())
.notNull(),
title: varchar("title", { length: 256 }).notNull(),
slug: varchar("slug", { length: 256 }).unique().notNull(),
authorId: varchar("author_id", { length: 255 }),
content: jsonb("content").$type<JSONContent>(),
categoryId: varchar("category_id", { length: 255 }),
published: boolean("published").default(false),
createdAt: timestamp("created_at", { withTimezone: true })
.default(sql`CURRENT_TIMESTAMP`)
.notNull(),
updatedAt: timestamp("updated_at", { withTimezone: true }).$onUpdate(
() => new Date(),
),
},
(example) => ({
titleIndex: index("article_title_idx").on(example.title),
slugIndex: index("article_slug_idx").on(example.slug),
createdAtIndex: index("article_created_at_idx").on(example.createdAt),
}),
);
export type Article = typeof articles.$inferSelect & {
author?: User;
category?: Category;
};
export const articleRelations = relations(articles, ({ one }) => ({
category: one(categories, {
fields: [articles.categoryId],
references: [categories.id],
}),
author: one(users, {
fields: [articles.authorId],
references: [users.id],
}),
}));
export const categories = createTable(
"category",
{
id: varchar("id", { length: 255 })
.primaryKey()
.$defaultFn(() => createId())
.notNull(),
name: varchar("name", { length: 256 }).notNull(),
description: text("description"),
slug: varchar("slug", { length: 256 }).unique().notNull(),
image: varchar("image", { length: 255 }),
createdAt: timestamp("created_at", { withTimezone: true })
.default(sql`CURRENT_TIMESTAMP`)
.notNull(),
updatedAt: timestamp("updated_at", { withTimezone: true }).$onUpdate(
() => new Date(),
),
},
(example) => ({
nameIndex: index("category_name_idx").on(example.name),
slugameIndex: index("category_slug_idx").on(example.slug),
createdAtIndex: index("category_created_at_idx").on(example.createdAt),
}),
);
export type Category = typeof categories.$inferSelect & {
articles?: Article[];
};
export const categoryRelations = relations(categories, ({ many }) => ({
articles: many(articles),
}));
export const users = createTable("user", {
id: varchar("id", { length: 255 })
.notNull()
.primaryKey()
.$defaultFn(() => crypto.randomUUID()),
.$defaultFn(() => createId()),
name: varchar("name", { length: 255 }),
email: varchar("email", { length: 255 }).notNull(),
role: integer("role").default(1).notNull(),

View File

@ -0,0 +1,37 @@
import { index, text, timestamp, varchar } from "drizzle-orm/pg-core";
import { createId, createTable } from "./schema-utils";
import { relations, sql } from "drizzle-orm";
import { Article, articles } from "./article";
export const categories = createTable(
"category",
{
id: varchar("id", { length: 255 })
.primaryKey()
.$defaultFn(() => createId())
.notNull(),
name: varchar("name", { length: 256 }).notNull(),
description: text("description"),
slug: varchar("slug", { length: 256 }).unique().notNull(),
image: varchar("image", { length: 255 }),
createdAt: timestamp("created_at", { withTimezone: true })
.default(sql`CURRENT_TIMESTAMP`)
.notNull(),
updatedAt: timestamp("updated_at", { withTimezone: true }).$onUpdate(
() => new Date(),
),
},
(example) => ({
nameIndex: index("category_name_idx").on(example.name),
slugameIndex: index("category_slug_idx").on(example.slug),
createdAtIndex: index("category_created_at_idx").on(example.createdAt),
}),
);
export type Category = typeof categories.$inferSelect & {
articles?: Article[];
};
export const categoryRelations = relations(categories, ({ many }) => ({
articles: many(articles),
}));

View File

@ -0,0 +1,39 @@
import { index, jsonb, timestamp, varchar } from "drizzle-orm/pg-core";
import { articles } from "./article";
import { JSONContent } from "novel";
import { relations, sql } from "drizzle-orm";
import { createId, createTable } from "./schema-utils";
import { users } from "./auth";
export const comments = createTable(
"comment",
{
id: varchar("id", { length: 255 })
.primaryKey()
.$defaultFn(() => createId())
.notNull(),
articleId: varchar("article_id", { length: 255 })
.notNull()
.references(() => articles.id),
authorId: varchar("author_id", { length: 255 })
.notNull()
.references(() => users.id),
parentId: varchar("parent_id", { length: 255 }),
content: jsonb("content").$type<JSONContent>(),
createdAt: timestamp("created_at", { withTimezone: true })
.default(sql`CURRENT_TIMESTAMP`)
.notNull(),
updatedAt: timestamp("updated_at", { withTimezone: true }).$onUpdate(
() => new Date(),
),
},
(example) => ({
articleIdIndex: index("comment_article_id_idx").on(example.articleId),
authorIdIndex: index("comment_author_id_idx").on(example.authorId),
createdAtIndex: index("comment_created_at_idx").on(example.createdAt),
}),
);
export const commentsRelations = relations(comments, ({ many }) => ({
comments: many(comments),
}));

View File

@ -0,0 +1,4 @@
export * from "./auth";
export * from "./article";
export * from "./category";
export * from "./comments";

View File

@ -0,0 +1,7 @@
import { createId as creatIdCuuid2 } from "@paralleldrive/cuid2";
import { pgTableCreator } from "drizzle-orm/pg-core";
export const createTable = pgTableCreator((name) => `logipedia_${name}`);
export const createId = () => creatIdCuuid2();