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"; import { env } from "@/env";
export default { export default {
schema: "./src/server/db/schema.ts", schema: "./src/server/db/schema/index.ts",
dialect: "postgresql", dialect: "postgresql",
dbCredentials: { dbCredentials: {
url: env.DATABASE_URL, url: env.DATABASE_URL,

View File

@ -1,9 +1,10 @@
import { z } from "zod"; import { z } from "zod";
import { editorContentSchema } from ".";
export const articleSchema = z.object({ export const articleSchema = z.object({
title: z.string().min(1), title: z.string().min(1),
slug: z.string().min(1), slug: z.string().min(1),
content: z.any().optional(), content: editorContentSchema.optional(),
authorId: z.string().optional(), authorId: z.string().optional(),
categoryId: z.string().optional(), categoryId: z.string().optional(),
published: z.boolean(), 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 { createCallerFactory, createTRPCRouter } from "@/server/api/trpc";
import { usersRouter } from "./routers/users"; import {
import { authorRouter } from "./routers/author"; articleRouter,
import { appRouter as globalRouter } from "./routers/app"; categoryRouter,
// import { authRouter } from "./routers/auth"; usersRouter,
/** authorRouter,
* This is the primary router for your server. appRouter as globalRouter,
* commentRouter,
* All routers added in /api/routers should be manually added here. } from "./routers";
*/
export const appRouter = createTRPCRouter({ export const appRouter = createTRPCRouter({
article: articleRouter, article: articleRouter,
category: categoryRouter, category: categoryRouter,
comment: commentRouter,
users: usersRouter, users: usersRouter,
author: authorRouter, author: authorRouter,
app: globalRouter, app: globalRouter,
// auth: authRouter,
}); });
// export type definition of API // 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 { articleRouter } from "./article";
// import { createTRPCRouter, publicProcedure } from "../trpc"; import { usersRouter } from "./users";
// import { z } from "zod"; import { authorRouter } from "./author";
// import { eq } from "drizzle-orm"; import { appRouter } from "./app";
// import { users } from "@/server/db/schema"; import { commentRouter } from "./comment";
// import argon from "argon2"; import { categoryRouter } from "./category";
export {
articleRouter,
categoryRouter,
commentRouter,
usersRouter,
authorRouter,
appRouter,
};
// export const authRouter = createTRPCRouter({ // export const authRouter = createTRPCRouter({
// register: publicProcedure // 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 { relations, sql } from "drizzle-orm";
import { import {
boolean,
index, index,
integer, integer,
jsonb,
pgTableCreator,
primaryKey, primaryKey,
text, text,
timestamp, timestamp,
varchar, varchar,
} from "drizzle-orm/pg-core"; } from "drizzle-orm/pg-core";
import { User } from "next-auth";
import { createId, createTable } from "./schema-utils";
import { type AdapterAccount } from "next-auth/adapters"; 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", { export const users = createTable("user", {
id: varchar("id", { length: 255 }) id: varchar("id", { length: 255 })
.notNull() .notNull()
.primaryKey() .primaryKey()
.$defaultFn(() => crypto.randomUUID()), .$defaultFn(() => createId()),
name: varchar("name", { length: 255 }), name: varchar("name", { length: 255 }),
email: varchar("email", { length: 255 }).notNull(), email: varchar("email", { length: 255 }).notNull(),
role: integer("role").default(1).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();