mirror of
https://github.com/khairul169/code-share.git
synced 2026-10-24 11:53:58 +07:00
feat: add homepage & auth
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { Request } from "express";
|
||||
import { CreateExpressContextOptions } from "@trpc/server/adapters/express";
|
||||
|
||||
export const createContext = async ({ req }: { req: Request }) => {
|
||||
return {};
|
||||
export const createContext = async (ctx: CreateExpressContextOptions) => {
|
||||
return ctx;
|
||||
};
|
||||
|
||||
export type Context = Awaited<ReturnType<typeof createContext>>;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { PageContext } from "vike/types";
|
||||
|
||||
const trpcServer = async (ctx: PageContext) => {
|
||||
const createCaller = createCallerFactory(appRouter);
|
||||
const context = await createContext({ req: ctx.req });
|
||||
const context = await createContext(ctx);
|
||||
return createCaller(context);
|
||||
};
|
||||
|
||||
|
||||
@@ -17,4 +17,5 @@ export const user = sqliteTable("users", {
|
||||
export const insertUserSchema = createInsertSchema(user);
|
||||
export const selectUserSchema = createSelectSchema(user);
|
||||
|
||||
export type UserSchema = z.infer<typeof selectUserSchema>;
|
||||
export type UserWithPassword = z.infer<typeof selectUserSchema>;
|
||||
export type UserSchema = Omit<UserWithPassword, "password">;
|
||||
|
||||
+4
-1
@@ -1,8 +1,10 @@
|
||||
import "dotenv/config";
|
||||
import express from "express";
|
||||
import { renderPage } from "vike/server";
|
||||
import { IS_DEV } from "./lib/consts";
|
||||
import cookieParser from "cookie-parser";
|
||||
import api from "./api";
|
||||
import { authMiddleware } from "./middlewares/auth";
|
||||
|
||||
async function createServer() {
|
||||
const app = express();
|
||||
@@ -24,12 +26,13 @@ async function createServer() {
|
||||
}
|
||||
|
||||
app.use(cookieParser());
|
||||
app.use(authMiddleware);
|
||||
|
||||
app.use("/api", api);
|
||||
|
||||
app.use("*", async (req, res, next) => {
|
||||
const url = req.originalUrl;
|
||||
const pageContext = { req, cookies: req.cookies };
|
||||
const pageContext = { req, res, cookies: req.cookies, user: req.user };
|
||||
const ctx = await renderPage({ urlOriginal: url, ...pageContext });
|
||||
|
||||
const { httpResponse } = ctx;
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import jwt from "jsonwebtoken";
|
||||
|
||||
const JWT_KEY =
|
||||
process.env.JWT_KEY || "e2b185bd471dda7a14cb8d9cfb8d1c568ac27926";
|
||||
|
||||
export type TokenData = {
|
||||
id: number;
|
||||
};
|
||||
|
||||
export const createToken = (payload: TokenData) => {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
jwt.sign(payload, JWT_KEY, { expiresIn: "30d" }, function (err, token) {
|
||||
if (err) return reject(err);
|
||||
resolve(token as string);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const verifyToken = (token: string) => {
|
||||
return new Promise<TokenData>((resolve, reject) => {
|
||||
jwt.verify(token, JWT_KEY, function (err, decoded) {
|
||||
if (err) return reject(err);
|
||||
resolve(decoded as TokenData);
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import { verifyToken } from "../lib/jwt";
|
||||
import db from "../db";
|
||||
import { and, eq, isNull } from "drizzle-orm";
|
||||
import { user } from "../db/schema/user";
|
||||
|
||||
export const authMiddleware = async (
|
||||
req: Request,
|
||||
_res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const token = req.cookies["auth-token"];
|
||||
if (!token) {
|
||||
throw new Error("No token in cookies.");
|
||||
}
|
||||
|
||||
const data = await verifyToken(token);
|
||||
if (!data?.id) {
|
||||
throw new Error("Invalid token!");
|
||||
}
|
||||
|
||||
const userData = await db.query.user.findFirst({
|
||||
where: and(eq(user.id, data.id), isNull(user.deletedAt)),
|
||||
columns: { password: false },
|
||||
});
|
||||
if (!userData) {
|
||||
throw new Error("User is not found!");
|
||||
}
|
||||
|
||||
// store userdata to every request
|
||||
(req as any).user = userData;
|
||||
} catch (err) {
|
||||
//
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
@@ -1,8 +1,10 @@
|
||||
import { router } from "../api/trpc";
|
||||
import auth from "./auth";
|
||||
import project from "./project";
|
||||
import file from "./file";
|
||||
|
||||
export const appRouter = router({
|
||||
auth,
|
||||
project,
|
||||
file,
|
||||
});
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { z } from "zod";
|
||||
import { procedure, router } from "../api/trpc";
|
||||
import db from "../db";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { and, eq, isNull } from "drizzle-orm";
|
||||
import { user } from "../db/schema/user";
|
||||
import { verifyPassword } from "../lib/crypto";
|
||||
import { createToken } from "../lib/jwt";
|
||||
|
||||
const authRouter = router({
|
||||
login: procedure
|
||||
.input(
|
||||
z.object({
|
||||
email: z.string().email(),
|
||||
password: z.string().min(1),
|
||||
})
|
||||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const userData = await db.query.user.findFirst({
|
||||
where: and(eq(user.email, input.email), isNull(user.deletedAt)),
|
||||
});
|
||||
|
||||
if (!userData) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Email is not found!",
|
||||
});
|
||||
}
|
||||
|
||||
if (!(await verifyPassword(userData.password, input.password))) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Invalid password!",
|
||||
});
|
||||
}
|
||||
|
||||
// set user token
|
||||
const token = await createToken({ id: userData.id });
|
||||
ctx.res.cookie("auth-token", token, { httpOnly: true });
|
||||
|
||||
return { ...userData, password: undefined };
|
||||
}),
|
||||
|
||||
logout: procedure.mutation(({ ctx }) => {
|
||||
ctx.res.cookie("auth-token", null, {
|
||||
httpOnly: true,
|
||||
expires: new Date(0),
|
||||
});
|
||||
|
||||
return true;
|
||||
}),
|
||||
});
|
||||
|
||||
export default authRouter;
|
||||
+73
-11
@@ -9,6 +9,11 @@ import {
|
||||
import { z } from "zod";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { uid } from "../lib/utils";
|
||||
import { insertUserSchema, user } from "../db/schema/user";
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { ucwords } from "~/lib/utils";
|
||||
import { hashPassword } from "../lib/crypto";
|
||||
import { createToken } from "../lib/jwt";
|
||||
|
||||
const projectRouter = router({
|
||||
getAll: procedure.query(async () => {
|
||||
@@ -45,19 +50,59 @@ const projectRouter = router({
|
||||
|
||||
create: procedure
|
||||
.input(
|
||||
insertProjectSchema.pick({
|
||||
title: true,
|
||||
})
|
||||
insertProjectSchema
|
||||
.pick({
|
||||
title: true,
|
||||
})
|
||||
.merge(
|
||||
z.object({
|
||||
forkFromId: z.number().optional(),
|
||||
user: insertUserSchema
|
||||
.pick({
|
||||
name: true,
|
||||
email: true,
|
||||
password: true,
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
)
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
const data: z.infer<typeof insertProjectSchema> = {
|
||||
userId: 1,
|
||||
title: input.title,
|
||||
slug: uid(),
|
||||
};
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const title =
|
||||
input.title.length > 0 ? input.title : ucwords(faker.lorem.words(2));
|
||||
let userId = 0;
|
||||
|
||||
const [result] = await db.insert(project).values(data).returning();
|
||||
return result;
|
||||
return db.transaction(async (tx) => {
|
||||
if (input.user) {
|
||||
const [usr] = await tx
|
||||
.insert(user)
|
||||
.values({
|
||||
...input.user,
|
||||
password: await hashPassword(input.user.password),
|
||||
})
|
||||
.returning();
|
||||
|
||||
userId = usr.id;
|
||||
|
||||
// set user token
|
||||
const token = await createToken({ id: userId });
|
||||
ctx.res.cookie("auth-token", token, { httpOnly: true });
|
||||
}
|
||||
|
||||
if (!userId) {
|
||||
throw new Error("Invalid userId!");
|
||||
}
|
||||
|
||||
const data: z.infer<typeof insertProjectSchema> = {
|
||||
userId,
|
||||
title,
|
||||
slug: uid(),
|
||||
};
|
||||
|
||||
const [result] = await tx.insert(project).values(data).returning();
|
||||
|
||||
return result;
|
||||
});
|
||||
}),
|
||||
|
||||
update: procedure
|
||||
@@ -95,6 +140,23 @@ const projectRouter = router({
|
||||
|
||||
return result;
|
||||
}),
|
||||
|
||||
getTemplates: procedure.query(() => {
|
||||
return [
|
||||
{
|
||||
title: "Empty Project",
|
||||
projectId: 0,
|
||||
},
|
||||
{
|
||||
title: "Vanilla HTML+CSS+JS",
|
||||
projectId: 1,
|
||||
},
|
||||
{
|
||||
title: "React + Tailwindcss",
|
||||
projectId: 2,
|
||||
},
|
||||
];
|
||||
}),
|
||||
});
|
||||
|
||||
export default projectRouter;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { UserSchema } from "./db/schema/user";
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
user?: UserSchema | null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
Reference in New Issue
Block a user