diff --git a/api/drizzle.config.ts b/api/drizzle.config.ts index 0537e80..d90951c 100644 --- a/api/drizzle.config.ts +++ b/api/drizzle.config.ts @@ -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!, diff --git a/api/drizzle/0001_peaceful_carnage.sql b/api/drizzle/0001_peaceful_carnage.sql new file mode 100644 index 0000000..b1d8fe7 --- /dev/null +++ b/api/drizzle/0001_peaceful_carnage.sql @@ -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`) +); diff --git a/api/drizzle/meta/0001_snapshot.json b/api/drizzle/meta/0001_snapshot.json new file mode 100644 index 0000000..d18e694 --- /dev/null +++ b/api/drizzle/meta/0001_snapshot.json @@ -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": {} + } +} \ No newline at end of file diff --git a/api/drizzle/meta/_journal.json b/api/drizzle/meta/_journal.json index 40c997e..7b5e6cb 100644 --- a/api/drizzle/meta/_journal.json +++ b/api/drizzle/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1751792977207, "tag": "0000_spooky_dormammu", "breakpoints": true + }, + { + "idx": 1, + "version": "5", + "when": 1752400394255, + "tag": "0001_peaceful_carnage", + "breakpoints": true } ] } \ No newline at end of file diff --git a/api/package-lock.json b/api/package-lock.json index b9c407a..9ae7b8b 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -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", diff --git a/api/package.json b/api/package.json index d7074ce..4b5b5e0 100644 --- a/api/package.json +++ b/api/package.json @@ -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", diff --git a/api/src/db/usersSchema.ts b/api/src/db/usersSchema.ts new file mode 100644 index 0000000..4aa7f05 --- /dev/null +++ b/api/src/db/usersSchema.ts @@ -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, +}); diff --git a/api/src/index.ts b/api/src/index.ts index 00ac53e..92de642 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -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}`); diff --git a/api/src/routes/auth/index.ts b/api/src/routes/auth/index.ts new file mode 100644 index 0000000..ba8babd --- /dev/null +++ b/api/src/routes/auth/index.ts @@ -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; \ No newline at end of file