mirror of
https://github.com/khairul169/code-share.git
synced 2026-10-24 11:53:58 +07:00
feat: add project schema
This commit is contained in:
@@ -7,15 +7,26 @@ import { serveHtml } from "./serve-html";
|
||||
import { serveJs } from "./serve-js";
|
||||
import { getMimeType } from "~/server/lib/mime";
|
||||
import { postcss } from "./postcss";
|
||||
import { project } from "~/server/db/schema/project";
|
||||
|
||||
const get = async (req: Request, res: Response) => {
|
||||
const { slug, ...pathParams } = req.params as any;
|
||||
const path = pathParams[0];
|
||||
|
||||
const fileData = await db.query.file.findFirst({
|
||||
where: and(eq(file.path, path), isNull(file.deletedAt)),
|
||||
const projectData = await db.query.project.findFirst({
|
||||
where: and(eq(project.slug, slug), isNull(project.deletedAt)),
|
||||
});
|
||||
if (!projectData) {
|
||||
return res.status(404).send("Project not found!");
|
||||
}
|
||||
|
||||
const fileData = await db.query.file.findFirst({
|
||||
where: and(
|
||||
eq(file.projectId, projectData.id),
|
||||
eq(file.path, path),
|
||||
isNull(file.deletedAt)
|
||||
),
|
||||
});
|
||||
if (!fileData) {
|
||||
return res.status(404).send("File not found!");
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import db from "~/server/db";
|
||||
import { FileSchema, file } from "~/server/db/schema/file";
|
||||
import { and, eq, isNull } from "drizzle-orm";
|
||||
|
||||
const preventHtmlDirectAccess = `<script>if (window === window.parent) {window.location.href = '/';}</script>`;
|
||||
import { IS_DEV } from "~/server/lib/consts";
|
||||
|
||||
export const serveHtml = async (fileData: FileSchema) => {
|
||||
const layout = await db.query.file.findFirst({
|
||||
where: and(
|
||||
eq(file.projectId, fileData.projectId),
|
||||
eq(file.filename, "_layout.html"),
|
||||
fileData.parentId
|
||||
? eq(file.parentId, fileData.parentId)
|
||||
@@ -22,10 +22,14 @@ export const serveHtml = async (fileData: FileSchema) => {
|
||||
|
||||
const bodyOpeningTagIdx = content.indexOf("<body");
|
||||
const firstScriptTagIdx = content.indexOf("<script", bodyOpeningTagIdx);
|
||||
const injectScripts = [
|
||||
'<script src="/js/hook-console.js"></script>',
|
||||
preventHtmlDirectAccess,
|
||||
];
|
||||
const injectScripts = ['<script src="/js/hook-console.js"></script>'];
|
||||
|
||||
if (!IS_DEV) {
|
||||
// prevent direct access
|
||||
injectScripts.push(
|
||||
`<script>if (window === window.parent) {window.location.href = '/';}</script>`
|
||||
);
|
||||
}
|
||||
|
||||
const importMaps = [
|
||||
{ name: "react", url: "https://esm.sh/[email protected]" },
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export { user } from "./user";
|
||||
export { file } from "./file";
|
||||
export { project, projectRelations } from "./project";
|
||||
export { file, fileRelations } from "./file";
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
import { sql } from "drizzle-orm";
|
||||
import { relations, sql } from "drizzle-orm";
|
||||
import {
|
||||
integer,
|
||||
sqliteTable,
|
||||
text,
|
||||
foreignKey,
|
||||
index,
|
||||
} from "drizzle-orm/sqlite-core";
|
||||
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
|
||||
import { z } from "zod";
|
||||
import { user } from "./user";
|
||||
import { project } from "./project";
|
||||
|
||||
export const file = sqliteTable(
|
||||
"files",
|
||||
{
|
||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
||||
parentId: integer("parent_id"),
|
||||
userId: integer("user_id")
|
||||
projectId: integer("project_id")
|
||||
.notNull()
|
||||
.references(() => user.id),
|
||||
path: text("path").notNull().unique(),
|
||||
.references(() => project.id),
|
||||
parentId: integer("parent_id"),
|
||||
path: text("path").notNull(),
|
||||
filename: text("filename").notNull(),
|
||||
isDirectory: integer("is_directory", { mode: "boolean" })
|
||||
.notNull()
|
||||
@@ -38,9 +39,18 @@ export const file = sqliteTable(
|
||||
foreignColumns: [table.id],
|
||||
name: "parent_id_fk",
|
||||
}),
|
||||
pathIdx: index("file_path_idx").on(table.path),
|
||||
filenameIdx: index("file_name_idx").on(table.filename),
|
||||
})
|
||||
);
|
||||
|
||||
export const fileRelations = relations(file, ({ one }) => ({
|
||||
project: one(project, {
|
||||
fields: [file.projectId],
|
||||
references: [project.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const insertFileSchema = createInsertSchema(file);
|
||||
export const selectFileSchema = createSelectSchema(file);
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { relations, sql } from "drizzle-orm";
|
||||
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
||||
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
|
||||
import { z } from "zod";
|
||||
import { user } from "./user";
|
||||
import { file } from "./file";
|
||||
|
||||
export const project = sqliteTable("projects", {
|
||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
||||
userId: integer("user_id")
|
||||
.notNull()
|
||||
.references(() => user.id),
|
||||
slug: text("slug").notNull().unique(),
|
||||
title: text("title").notNull(),
|
||||
createdAt: text("created_at")
|
||||
.notNull()
|
||||
.default(sql`CURRENT_TIMESTAMP`),
|
||||
deletedAt: text("deleted_at"),
|
||||
});
|
||||
|
||||
export const projectRelations = relations(project, ({ one, many }) => ({
|
||||
files: many(file),
|
||||
user: one(user, {
|
||||
fields: [project.userId],
|
||||
references: [user.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const insertProjectSchema = createInsertSchema(project);
|
||||
export const selectProjectSchema = createSelectSchema(project);
|
||||
|
||||
export type ProjectSchema = z.infer<typeof selectProjectSchema>;
|
||||
@@ -5,6 +5,7 @@ import { z } from "zod";
|
||||
|
||||
export const user = sqliteTable("users", {
|
||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
||||
name: text("name").notNull(),
|
||||
email: text("email").notNull().unique(),
|
||||
password: text("password").notNull(),
|
||||
createdAt: text("created_at")
|
||||
|
||||
+61
-46
@@ -1,68 +1,83 @@
|
||||
import db from ".";
|
||||
import { hashPassword } from "../lib/crypto";
|
||||
import { uid } from "../lib/utils";
|
||||
import { file } from "./schema/file";
|
||||
import { project } from "./schema/project";
|
||||
import { user } from "./schema/user";
|
||||
|
||||
const main = async () => {
|
||||
const [adminUser] = await db
|
||||
.insert(user)
|
||||
.values({
|
||||
name: "Admin",
|
||||
email: "[email protected]",
|
||||
password: await hashPassword("123456"),
|
||||
})
|
||||
.returning();
|
||||
|
||||
// await db
|
||||
// .insert(file)
|
||||
// .values([
|
||||
// {
|
||||
// userId: adminUser.id,
|
||||
// path: "index.html",
|
||||
// filename: "index.html",
|
||||
// content: '<p class="text-lg text-red-500">Hello world!</p>',
|
||||
// },
|
||||
// {
|
||||
// userId: adminUser.id,
|
||||
// path: "styles.css",
|
||||
// filename: "styles.css",
|
||||
// content: "body { padding: 16px; }",
|
||||
// },
|
||||
// {
|
||||
// userId: adminUser.id,
|
||||
// path: "script.js",
|
||||
// filename: "script.js",
|
||||
// content: "console.log('hello world!');",
|
||||
// },
|
||||
// {
|
||||
// userId: adminUser.id,
|
||||
// path: "_layout.html",
|
||||
// filename: "_layout.html",
|
||||
// content: `<!DOCTYPE html>
|
||||
// <html lang="en">
|
||||
// <head>
|
||||
// <meta charset="UTF-8">
|
||||
// <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
// <title>Document</title>
|
||||
const [vanillaProject] = await db
|
||||
.insert(project)
|
||||
.values({ userId: adminUser.id, slug: uid(), title: "Vanilla Project" })
|
||||
.returning();
|
||||
|
||||
// <link rel="stylesheet" href="styles.css">
|
||||
// vanilla html css js template
|
||||
await db
|
||||
.insert(file)
|
||||
.values([
|
||||
{
|
||||
projectId: vanillaProject.id,
|
||||
path: "index.html",
|
||||
filename: "index.html",
|
||||
content: "<p>Hello world!</p>",
|
||||
isPinned: true,
|
||||
},
|
||||
{
|
||||
projectId: vanillaProject.id,
|
||||
path: "styles.css",
|
||||
filename: "styles.css",
|
||||
content: "body { padding: 16px; }",
|
||||
isPinned: true,
|
||||
},
|
||||
{
|
||||
projectId: vanillaProject.id,
|
||||
path: "scripts.js",
|
||||
filename: "scripts.js",
|
||||
content: "console.log('hello world!');",
|
||||
isPinned: true,
|
||||
},
|
||||
{
|
||||
projectId: vanillaProject.id,
|
||||
path: "_layout.html",
|
||||
filename: "_layout.html",
|
||||
content: `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
|
||||
// <script src="https://cdn.tailwindcss.com"></script>
|
||||
// </head>
|
||||
// <body>
|
||||
// {CONTENT}
|
||||
// <script src="script.js" type="module" defer></script>
|
||||
// </body>
|
||||
// </html>`,
|
||||
// },
|
||||
// ])
|
||||
// .execute();
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
{CONTENT}
|
||||
<script src="scripts.js"></script>
|
||||
</body>
|
||||
</html>`,
|
||||
},
|
||||
])
|
||||
.execute();
|
||||
|
||||
const [reactProject] = await db
|
||||
.insert(project)
|
||||
.values({ userId: adminUser.id, slug: uid(), title: "React Project" })
|
||||
.returning();
|
||||
|
||||
// react template
|
||||
await db
|
||||
.insert(file)
|
||||
.values([
|
||||
{
|
||||
userId: adminUser.id,
|
||||
projectId: reactProject.id,
|
||||
path: "index.html",
|
||||
filename: "index.html",
|
||||
content: `<!doctype html>
|
||||
@@ -84,7 +99,7 @@ const main = async () => {
|
||||
`,
|
||||
},
|
||||
{
|
||||
userId: adminUser.id,
|
||||
projectId: reactProject.id,
|
||||
path: "globals.css",
|
||||
filename: "globals.css",
|
||||
content: `@tailwind base;
|
||||
@@ -97,7 +112,7 @@ body {
|
||||
`,
|
||||
},
|
||||
{
|
||||
userId: adminUser.id,
|
||||
projectId: reactProject.id,
|
||||
path: "index.jsx",
|
||||
filename: "index.jsx",
|
||||
content: `import React from "react";
|
||||
@@ -109,7 +124,7 @@ root.render(<App />);
|
||||
`,
|
||||
},
|
||||
{
|
||||
userId: adminUser.id,
|
||||
projectId: reactProject.id,
|
||||
path: "App.jsx",
|
||||
filename: "App.jsx",
|
||||
isPinned: true,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import cuid2 from "@paralleldrive/cuid2";
|
||||
|
||||
export const fileExists = (path: string) => {
|
||||
try {
|
||||
@@ -13,3 +14,7 @@ export const fileExists = (path: string) => {
|
||||
export const getProjectDir = () => {
|
||||
return path.resolve(process.cwd(), "storage/tmp/project1");
|
||||
};
|
||||
|
||||
export const uid = cuid2.init({
|
||||
length: 8,
|
||||
});
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { router } from "../api/trpc";
|
||||
import project from "./project";
|
||||
import file from "./file";
|
||||
|
||||
export const appRouter = router({
|
||||
project,
|
||||
file,
|
||||
});
|
||||
|
||||
|
||||
@@ -8,17 +8,19 @@ import { TRPCError } from "@trpc/server";
|
||||
const fileRouter = router({
|
||||
getAll: procedure
|
||||
.input(
|
||||
z
|
||||
.object({ id: z.number().array().min(1), isPinned: z.boolean() })
|
||||
.partial()
|
||||
.optional()
|
||||
z.object({
|
||||
projectId: z.number(),
|
||||
id: z.number().array().min(1).optional(),
|
||||
isPinned: z.boolean().optional(),
|
||||
})
|
||||
)
|
||||
.query(async ({ input: opt }) => {
|
||||
const files = await db.query.file.findMany({
|
||||
where: and(
|
||||
eq(file.projectId, opt.projectId),
|
||||
isNull(file.deletedAt),
|
||||
opt?.id && opt.id.length > 0 ? inArray(file.id, opt.id) : undefined,
|
||||
opt?.isPinned ? eq(file.isPinned, true) : undefined
|
||||
opt.id && opt.id.length > 0 ? inArray(file.id, opt.id) : undefined,
|
||||
opt.isPinned ? eq(file.isPinned, true) : undefined
|
||||
),
|
||||
orderBy: [desc(file.isDirectory), asc(file.filename)],
|
||||
columns: !file.isPinned ? { content: true } : undefined,
|
||||
@@ -55,7 +57,7 @@ const fileRouter = router({
|
||||
}
|
||||
|
||||
const data: z.infer<typeof insertFileSchema> = {
|
||||
userId: 1,
|
||||
projectId: 1,
|
||||
parentId: input.parentId,
|
||||
path: basePath + input.filename,
|
||||
filename: input.filename,
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import { and, desc, eq, isNull, sql } from "drizzle-orm";
|
||||
import db from "../db";
|
||||
import { procedure, router } from "../api/trpc";
|
||||
import {
|
||||
project,
|
||||
insertProjectSchema,
|
||||
selectProjectSchema,
|
||||
} from "../db/schema/project";
|
||||
import { z } from "zod";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { uid } from "../lib/utils";
|
||||
|
||||
const projectRouter = router({
|
||||
getAll: procedure.query(async () => {
|
||||
const where = and(isNull(project.deletedAt));
|
||||
|
||||
const projects = await db.query.project.findMany({
|
||||
where,
|
||||
with: {
|
||||
user: { columns: { password: false } },
|
||||
},
|
||||
orderBy: [desc(project.id)],
|
||||
});
|
||||
|
||||
return projects;
|
||||
}),
|
||||
|
||||
getById: procedure
|
||||
.input(z.number().or(z.string()))
|
||||
.query(async ({ input }) => {
|
||||
const where = and(
|
||||
typeof input === "string"
|
||||
? eq(project.slug, input)
|
||||
: eq(project.id, input),
|
||||
isNull(project.deletedAt)
|
||||
);
|
||||
|
||||
return db.query.project.findFirst({
|
||||
where,
|
||||
with: {
|
||||
user: { columns: { password: false } },
|
||||
},
|
||||
});
|
||||
}),
|
||||
|
||||
create: procedure
|
||||
.input(
|
||||
insertProjectSchema.pick({
|
||||
title: true,
|
||||
})
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
const data: z.infer<typeof insertProjectSchema> = {
|
||||
userId: 1,
|
||||
title: input.title,
|
||||
slug: uid(),
|
||||
};
|
||||
|
||||
const [result] = await db.insert(project).values(data).returning();
|
||||
return result;
|
||||
}),
|
||||
|
||||
update: procedure
|
||||
.input(selectProjectSchema.partial().required({ id: true }))
|
||||
.mutation(async ({ input }) => {
|
||||
const projectData = await db.query.project.findFirst({
|
||||
where: and(eq(project.id, input.id), isNull(project.deletedAt)),
|
||||
});
|
||||
if (!projectData) {
|
||||
throw new TRPCError({ code: "NOT_FOUND" });
|
||||
}
|
||||
|
||||
const [result] = await db
|
||||
.update(project)
|
||||
.set(input)
|
||||
.where(and(eq(project.id, input.id), isNull(project.deletedAt)))
|
||||
.returning();
|
||||
|
||||
return result;
|
||||
}),
|
||||
|
||||
delete: procedure.input(z.number()).mutation(async ({ input }) => {
|
||||
const projectData = await db.query.project.findFirst({
|
||||
where: and(eq(project.id, input), isNull(project.deletedAt)),
|
||||
});
|
||||
if (!projectData) {
|
||||
throw new TRPCError({ code: "NOT_FOUND" });
|
||||
}
|
||||
|
||||
const [result] = await db
|
||||
.update(project)
|
||||
.set({ deletedAt: sql`CURRENT_TIMESTAMP` })
|
||||
.where(eq(project.id, projectData.id))
|
||||
.returning();
|
||||
|
||||
return result;
|
||||
}),
|
||||
});
|
||||
|
||||
export default projectRouter;
|
||||
Reference in New Issue
Block a user