Compare commits

...

2 Commits

Author SHA1 Message Date
Justin xzHome
346e79622d Register and login and assign jwt token 2025-07-13 23:34:43 +09:00
Justin xzHome
c754d1de6e updateProductSchema added 2025-07-09 22:59:32 +09:00
12 changed files with 414 additions and 4 deletions

View File

@@ -2,7 +2,7 @@ import { defineConfig } from 'drizzle-kit';
export default defineConfig({
out: './drizzle',
schema: ['./src/db/productsSchema.ts'],
schema: ['./src/db/productsSchema.ts', './src/db/usersSchema.ts'],
dialect: 'mysql',
dbCredentials: {
url: process.env.DATABASE_URL!,

View File

@@ -0,0 +1,10 @@
CREATE TABLE `users` (
`id` int AUTO_INCREMENT NOT NULL,
`email` varchar(255) NOT NULL,
`password` varchar(255) NOT NULL,
`role` varchar(255) NOT NULL DEFAULT 'user',
`name` varchar(255) NOT NULL,
`address` text,
CONSTRAINT `users_id` PRIMARY KEY(`id`),
CONSTRAINT `users_email_unique` UNIQUE(`email`)
);

View File

@@ -0,0 +1,137 @@
{
"version": "5",
"dialect": "mysql",
"id": "2c28ca58-2027-4c36-8c76-dae7fdda9cad",
"prevId": "145ca097-6129-4cdc-aa8c-00b6057bf887",
"tables": {
"products": {
"name": "products",
"columns": {
"id": {
"name": "id",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": true
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"image": {
"name": "image",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"price": {
"name": "price",
"type": "double",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"products_id": {
"name": "products_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"users": {
"name": "users",
"columns": {
"id": {
"name": "id",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": true
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"password": {
"name": "password",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"role": {
"name": "role",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'user'"
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"address": {
"name": "address",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"users_id": {
"name": "users_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {
"users_email_unique": {
"name": "users_email_unique",
"columns": [
"email"
]
}
},
"checkConstraint": {}
}
},
"views": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"tables": {},
"indexes": {}
}
}

View File

@@ -8,6 +8,13 @@
"when": 1751792977207,
"tag": "0000_spooky_dormammu",
"breakpoints": true
},
{
"idx": 1,
"version": "5",
"when": 1752400394255,
"tag": "0001_peaceful_carnage",
"breakpoints": true
}
]
}

150
api/package-lock.json generated
View File

@@ -9,15 +9,19 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"bcryptjs": "^3.0.2",
"drizzle-orm": "^0.44.2",
"drizzle-zod": "^0.8.2",
"express": "^5.1.0",
"jsonwebtoken": "^9.0.2",
"lodash": "^4.17.21",
"mysql2": "^3.14.1",
"zod": "^3.25.74"
},
"devDependencies": {
"@types/bcryptjs": "^2.4.6",
"@types/express": "^5.0.3",
"@types/jsonwebtoken": "^9.0.10",
"@types/lodash": "^4.17.20",
"drizzle-kit": "^0.31.4",
"tsx": "^4.20.3",
@@ -892,6 +896,13 @@
"node": ">=18"
}
},
"node_modules/@types/bcryptjs": {
"version": "2.4.6",
"resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz",
"integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/body-parser": {
"version": "1.19.6",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
@@ -945,6 +956,17 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/jsonwebtoken": {
"version": "9.0.10",
"resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz",
"integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/ms": "*",
"@types/node": "*"
}
},
"node_modules/@types/lodash": {
"version": "4.17.20",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz",
@@ -959,6 +981,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/ms": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
"integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "24.0.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.10.tgz",
@@ -1028,6 +1057,15 @@
"node": ">= 6.0.0"
}
},
"node_modules/bcryptjs": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.2.tgz",
"integrity": "sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog==",
"license": "BSD-3-Clause",
"bin": {
"bcrypt": "bin/bcrypt"
}
},
"node_modules/body-parser": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
@@ -1048,6 +1086,12 @@
"node": ">=18"
}
},
"node_modules/buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
"license": "BSD-3-Clause"
},
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -1332,6 +1376,15 @@
"node": ">= 0.4"
}
},
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"license": "Apache-2.0",
"dependencies": {
"safe-buffer": "^5.0.1"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -1706,12 +1759,97 @@
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
"license": "MIT"
},
"node_modules/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
"license": "MIT",
"dependencies": {
"jws": "^3.2.2",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
"lodash.isnumber": "^3.0.3",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"ms": "^2.1.1",
"semver": "^7.5.4"
},
"engines": {
"node": ">=12",
"npm": ">=6"
}
},
"node_modules/jwa": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz",
"integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==",
"license": "MIT",
"dependencies": {
"buffer-equal-constant-time": "^1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"node_modules/jws": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
"license": "MIT",
"dependencies": {
"jwa": "^1.4.1",
"safe-buffer": "^5.0.1"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"license": "MIT"
},
"node_modules/lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
"license": "MIT"
},
"node_modules/lodash.isboolean": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
"license": "MIT"
},
"node_modules/lodash.isinteger": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
"license": "MIT"
},
"node_modules/lodash.isnumber": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
"license": "MIT"
},
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
"license": "MIT"
},
"node_modules/lodash.isstring": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
"license": "MIT"
},
"node_modules/lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
"license": "MIT"
},
"node_modules/long": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
@@ -1995,6 +2133,18 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/semver": {
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/send": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",

View File

@@ -12,18 +12,23 @@
"build": "tsc",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:push": "drizzle-kit push",
"db:studio": "drizzle-kit studio"
},
"dependencies": {
"bcryptjs": "^3.0.2",
"drizzle-orm": "^0.44.2",
"drizzle-zod": "^0.8.2",
"express": "^5.1.0",
"jsonwebtoken": "^9.0.2",
"lodash": "^4.17.21",
"mysql2": "^3.14.1",
"zod": "^3.25.74"
},
"devDependencies": {
"@types/bcryptjs": "^2.4.6",
"@types/express": "^5.0.3",
"@types/jsonwebtoken": "^9.0.10",
"@types/lodash": "^4.17.20",
"drizzle-kit": "^0.31.4",
"tsx": "^4.20.3",

View File

@@ -19,3 +19,7 @@ export const createProductSchema = createInsertSchema(productsTable).omit({
id: true,
});
export const updateProductSchema = createUpdateSchema(productsTable).omit({
id: true,
})
.partial();

23
api/src/db/usersSchema.ts Normal file
View File

@@ -0,0 +1,23 @@
import { int, mysqlTable, text, bigint, varchar, double } from 'drizzle-orm/mysql-core';
import { createInsertSchema , createSelectSchema, createUpdateSchema } from 'drizzle-zod';
export const usersTable = mysqlTable('users', {
id: int().autoincrement().primaryKey(),
email: varchar({ length: 255 }).notNull().unique(),
password: varchar({ length: 255 }).notNull(),
role: varchar({ length: 255 }).notNull().default('user'),
name: varchar({ length: 255 }).notNull(),
address: text(),
});
export const createUserSchema = createInsertSchema(usersTable).omit({
id: true,
role: true,
});
export const loginSchema = createSelectSchema(usersTable).pick({
email: true,
password: true,
});

View File

@@ -1,5 +1,6 @@
import express, {json, urlencoded} from 'express';
import productsRoutes from './routes/products/index'
import authRoutes from './routes/auth/index'
const port = 3000;
const app = express();
@@ -15,6 +16,7 @@ app.get('/', (req, res) => {
app.use ('/products', productsRoutes);
app.use ('/auth', authRoutes);
app.listen(port, () => {
console.log(`Example app listening on port ${port}`);

View File

@@ -0,0 +1,72 @@
import { createUserSchema, loginSchema, usersTable } from "../../db/usersSchema";
import { validateData } from "../../middlewares/validationMiddleware";
import { Router } from "express";
import {db} from '../../db/index';
import {eq} from 'drizzle-orm';
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken'
const router = Router();
router.post ("/register", validateData(createUserSchema), async (req, res) => {
try{
console.log (req.cleanBody);
const data = req.cleanBody;
data.password = await bcrypt.hash(data.password, 10);
const userId = await db
.insert(usersTable)
.values(data)
.$returningId();
// const [user] = await db
// .select()
// .from(usersTable)
// .where(eq(usersTable.id, userId));
res.status(201).json({userId});
return;
}catch(e){
res.status(500);
return;
}
});
router.post ("/login", validateData(loginSchema) , async (req, res) => {
try{
const {email, password} = req.cleanBody;
console.log ({email, password});
const [user] = await db
.select()
.from(usersTable)
.where(eq(usersTable.email, email));
console.log(user);
if (!user){
res.status(401).json({error: "Authentication error"});
return;
}
const matched = await bcrypt.compare(password, user.password);
console.log(matched);
if (!matched){
res.status(401).json({error: "Authentication error"});
return;
}
const token = jwt.sign(
{userId: user.id, role: user.role},
'your-secret',
{expiresIn: '12h'}
);
//@ts-ignore
delete user.password;
res.status(200).json({token, user});
}catch(e){
res.status(500);
return;
}
});
export default router;

View File

@@ -7,7 +7,7 @@ import { listProducts,
} from "./productsController";
import { validateData } from "../../middlewares/validationMiddleware";
import { z, ZodObject, ZodTypeAny } from 'zod/v4';
import { productsTable, createProductSchema } from "../../db/productsSchema";
import { productsTable, createProductSchema, updateProductSchema } from "../../db/productsSchema";
type ProductType = z.infer<typeof createProductSchema>;
@@ -18,7 +18,7 @@ const router = Router();
router.get('/', listProducts);
router.get('/:id', getProductById);
router.post('/', validateData(createProductSchema), createProduct);
router.put('/:id', updateProduct);
router.put('/:id', validateData(updateProductSchema), updateProduct);
router.delete('/:id', deleteProduct);
export default router;

View File

@@ -52,7 +52,7 @@ export async function createProduct(req: Request, res: Response) {
export async function updateProduct(req: Request, res: Response) {
try{
const id = Number(req.params.id);
const updatedFields = req.body;
const updatedFields = req.cleanBody;
const result = await db
.update(productsTable)