feat(auth): membuat user registration, login, sama pengecekan profile user yang sudah dilogin

This commit is contained in:
Rafi Athallah
2026-05-26 11:57:00 +07:00
parent 724cfd50e2
commit 4b9bc534cc
9 changed files with 506 additions and 15 deletions
+4
View File
@@ -4,3 +4,7 @@ NODE_ENV=development
# Database # Database
DATABASE_URL="mysql://db_username:db_pass@db_host:3306/internify_lms_db" DATABASE_URL="mysql://db_username:db_pass@db_host:3306/internify_lms_db"
# Authentication Config
JWT_SECRET="your_jwt_secret_key_here"
JWT_EXPIRES_IN="7d"
+159
View File
@@ -10,15 +10,19 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@prisma/client": "^6.19.3", "@prisma/client": "^6.19.3",
"bcryptjs": "^3.0.3",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"express": "^4.19.2", "express": "^4.19.2",
"helmet": "^7.1.0", "helmet": "^7.1.0",
"jsonwebtoken": "^9.0.3",
"morgan": "^1.10.0" "morgan": "^1.10.0"
}, },
"devDependencies": { "devDependencies": {
"@types/bcryptjs": "^2.4.6",
"@types/cors": "^2.8.17", "@types/cors": "^2.8.17",
"@types/express": "^4.17.21", "@types/express": "^4.17.21",
"@types/jsonwebtoken": "^9.0.10",
"@types/morgan": "^1.9.9", "@types/morgan": "^1.9.9",
"@types/node": "^20.12.12", "@types/node": "^20.12.12",
"prisma": "^6.19.3", "prisma": "^6.19.3",
@@ -660,6 +664,13 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"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": { "node_modules/@types/body-parser": {
"version": "1.19.6", "version": "1.19.6",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
@@ -724,6 +735,17 @@
"dev": true, "dev": true,
"license": "MIT" "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/mime": { "node_modules/@types/mime": {
"version": "1.3.5", "version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
@@ -741,12 +763,20 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"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": { "node_modules/@types/node": {
"version": "20.19.41", "version": "20.19.41",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz",
"integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==", "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"undici-types": "~6.21.0" "undici-types": "~6.21.0"
} }
@@ -929,6 +959,15 @@
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/bcryptjs": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz",
"integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==",
"license": "BSD-3-Clause",
"bin": {
"bcrypt": "bin/bcrypt"
}
},
"node_modules/binary-extensions": { "node_modules/binary-extensions": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@@ -989,6 +1028,12 @@
"node": ">=8" "node": ">=8"
} }
}, },
"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": { "node_modules/buffer-from": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -1313,6 +1358,15 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"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": { "node_modules/ee-first": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -1915,6 +1969,97 @@
"jiti": "lib/jiti-cli.mjs" "jiti": "lib/jiti-cli.mjs"
} }
}, },
"node_modules/jsonwebtoken": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
"integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==",
"license": "MIT",
"dependencies": {
"jws": "^4.0.1",
"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/jsonwebtoken/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/jwa": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
"integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
"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": "4.0.1",
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
"integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
"license": "MIT",
"dependencies": {
"jwa": "^2.0.1",
"safe-buffer": "^5.0.1"
}
},
"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/lru-cache": { "node_modules/lru-cache": {
"version": "10.4.3", "version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
@@ -2303,6 +2448,7 @@
"devOptional": true, "devOptional": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"dependencies": { "dependencies": {
"@prisma/config": "6.19.3", "@prisma/config": "6.19.3",
"@prisma/engines": "6.19.3" "@prisma/engines": "6.19.3"
@@ -2480,6 +2626,18 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/semver": {
"version": "7.8.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz",
"integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/send": { "node_modules/send": {
"version": "0.19.2", "version": "0.19.2",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
@@ -3076,6 +3234,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
+4
View File
@@ -18,15 +18,19 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@prisma/client": "^6.19.3", "@prisma/client": "^6.19.3",
"bcryptjs": "^3.0.3",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"express": "^4.19.2", "express": "^4.19.2",
"helmet": "^7.1.0", "helmet": "^7.1.0",
"jsonwebtoken": "^9.0.3",
"morgan": "^1.10.0" "morgan": "^1.10.0"
}, },
"devDependencies": { "devDependencies": {
"@types/bcryptjs": "^2.4.6",
"@types/cors": "^2.8.17", "@types/cors": "^2.8.17",
"@types/express": "^4.17.21", "@types/express": "^4.17.21",
"@types/jsonwebtoken": "^9.0.10",
"@types/morgan": "^1.9.9", "@types/morgan": "^1.9.9",
"@types/node": "^20.12.12", "@types/node": "^20.12.12",
"prisma": "^6.19.3", "prisma": "^6.19.3",
+1 -5
View File
@@ -2,7 +2,7 @@
// This file was generated by Prisma and assumes you have installed the following: // This file was generated by Prisma and assumes you have installed the following:
// npm install --save-dev prisma dotenv // npm install --save-dev prisma dotenv
import 'dotenv/config'; import 'dotenv/config';
import { defineConfig, env } from 'prisma/config'; import { defineConfig } from 'prisma/config';
export default defineConfig({ export default defineConfig({
schema: 'prisma/schema.prisma', schema: 'prisma/schema.prisma',
@@ -10,8 +10,4 @@ export default defineConfig({
path: 'prisma/migrations', path: 'prisma/migrations',
seed: 'tsx prisma/seed.ts', seed: 'tsx prisma/seed.ts',
}, },
engine: 'classic',
datasource: {
url: env('DATABASE_URL'),
},
}); });
+28 -1
View File
@@ -1,12 +1,39 @@
import 'dotenv/config'; import 'dotenv/config';
import { PrismaClient } from '../src/generated/prisma/client'; import { PrismaClient } from '../src/generated/prisma/client';
import bcrypt from 'bcryptjs';
const prisma = new PrismaClient(); const prisma = new PrismaClient();
async function main() { async function main() {
console.log('Start seeding Internify database...'); console.log('Start seeding Internify database...');
// Seeding process ... const adminEmail = 'admin@internify.com';
const adminPassword = 'AdminInternify123!';
const passwordHash = await bcrypt.hash(adminPassword, 10);
// Cek Apakah ada si Admin
const existingAdmin = await prisma.user.findUnique({
where: { email: adminEmail }
});
if (!existingAdmin) {
const adminUser = await prisma.user.create({
data: {
email: adminEmail,
passwordHash: passwordHash,
role: 'ADMIN',
admin: {
create: {
namaDepan: 'Super',
namaBelakang: 'Admin',
}
}
}
});
console.log(`Admin user created: ${adminEmail} (password: ${adminPassword})`);
} else {
console.log(`Admin user already exists: ${adminEmail}`);
}
console.log('Seeding finished'); console.log('Seeding finished');
} }
+5
View File
@@ -0,0 +1,5 @@
import { PrismaClient } from '../generated/prisma/client';
const prisma = new PrismaClient();
export default prisma;
+67
View File
@@ -0,0 +1,67 @@
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import prisma from '../lib/prisma';
import { CustomError } from './errorHandler';
export interface AuthenticatedRequest extends Request {
user?: {
id: string;
email: string;
role: 'ADMIN' | 'STUDENT';
};
}
export const protect = async (req: AuthenticatedRequest, _res: Response, next: NextFunction) => {
let token: string | undefined;
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) {
token = req.headers.authorization.split(' ')[1];
}
if (!token) {
const error: CustomError = new Error('Not authorized to access this route, token is missing');
error.statusCode = 401;
return next(error);
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'internify_secret_key_2026_dev') as {
id: string;
email: string;
role: 'ADMIN' | 'STUDENT';
};
const user = await prisma.user.findUnique({
where: { id: decoded.id },
select: {
id: true,
email: true,
role: true,
},
});
if (!user) {
const error: CustomError = new Error('User belonging to this token no longer exists');
error.statusCode = 401;
return next(error);
}
req.user = user as { id: string; email: string; role: 'ADMIN' | 'STUDENT' };
next();
} catch (err) {
const error: CustomError = new Error('Not authorized to access this route, token is invalid or expired');
error.statusCode = 401;
return next(error);
}
};
export const restrictTo = (...roles: ('ADMIN' | 'STUDENT')[]) => {
return (req: AuthenticatedRequest, _res: Response, next: NextFunction) => {
if (!req.user || !roles.includes(req.user.role)) {
const error: CustomError = new Error('You do not have permission to perform this action');
error.statusCode = 403;
return next(error);
}
next();
};
};
+200
View File
@@ -0,0 +1,200 @@
import { Router, Request, Response, NextFunction } from 'express';
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
import prisma from '../lib/prisma';
import { protect, AuthenticatedRequest } from '../middleware/auth';
import { CustomError } from '../middleware/errorHandler';
const router = Router();
const generateToken = (payload: { id: string; email: string; role: string }) => {
return jwt.sign(
payload,
process.env.JWT_SECRET || 'internify_secret_key_2026_dev',
{ expiresIn: (process.env.JWT_EXPIRES_IN || '7d') as any }
);
};
/**
* @route POST /api/auth/register
* @desc Mendaftarkan user baru dengan role STUDENT (Mahasiswa)
* @access Public
*/
router.post('/register', async (req: Request, res: Response, next: NextFunction) => {
try {
const {
email,
password,
namaDepan,
namaBelakang,
kontak,
jurusan,
universitas,
negara,
cvPath,
portofolioPath,
motivasi,
relevantSkills,
} = req.body;
if (!email || !password || !namaDepan || !kontak || !jurusan || !universitas || !negara) {
const error: CustomError = new Error('Mohon lengkapi semua field wajib yang diperlukan');
error.statusCode = 400;
return next(error);
}
const existingUser = await prisma.user.findUnique({
where: { email },
});
if (existingUser) {
const error: CustomError = new Error('Email sudah terdaftar');
error.statusCode = 400;
return next(error);
}
const salt = await bcrypt.genSalt(10);
const passwordHash = await bcrypt.hash(password, salt);
const newUser = await prisma.$transaction(async (tx) => {
const user = await tx.user.create({
data: {
email,
passwordHash,
role: 'STUDENT',
},
});
await tx.mahasiswa.create({
data: {
idUser: user.id,
namaDepan,
namaBelakang: namaBelakang || null,
kontak,
jurusan,
universitas,
negara,
cvPath: cvPath || '',
portofolioPath: portofolioPath || '',
motivasi: motivasi || '',
relevantSkills: relevantSkills || '',
},
});
return user;
});
res.status(201).json({
success: true,
message: 'Registrasi mahasiswa berhasil',
data: {
id: newUser.id,
email: newUser.email,
role: newUser.role,
createdAt: newUser.createdAt,
},
});
} catch (error) {
next(error);
}
});
/**
* @route POST /api/auth/login
* @desc Autentikasi user dan dapatin token
* @access Public
*/
router.post('/login', async (req: Request, res: Response, next: NextFunction) => {
try {
const { email, password } = req.body;
if (!email || !password) {
const error: CustomError = new Error('Mohon masukkan email dan password');
error.statusCode = 400;
return next(error);
}
const user = await prisma.user.findUnique({
where: { email },
include: {
admin: true,
mahasiswa: true,
},
});
if (!user) {
const error: CustomError = new Error('Email atau password salah');
error.statusCode = 401;
return next(error);
}
const isMatch = await bcrypt.compare(password, user.passwordHash);
if (!isMatch) {
const error: CustomError = new Error('Email atau password salah');
error.statusCode = 401;
return next(error);
}
const profile = user.role === 'ADMIN' ? user.admin : user.mahasiswa;
const token = generateToken({
id: user.id,
email: user.email,
role: user.role,
});
res.status(200).json({
success: true,
token,
user: {
id: user.id,
email: user.email,
role: user.role,
profile,
},
});
} catch (error) {
next(error);
}
});
/**
* @route GET /api/auth/profile
* @desc Dapatin profile user yang sedang login
* @access Private
*/
router.get('/profile', protect, async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
try {
const userId = req.user?.id;
const user = await prisma.user.findUnique({
where: { id: userId },
include: {
admin: true,
mahasiswa: true,
},
});
if (!user) {
const error: CustomError = new Error('User tidak ditemukan');
error.statusCode = 404;
return next(error);
}
const profile = user.role === 'ADMIN' ? user.admin : user.mahasiswa;
res.status(200).json({
success: true,
user: {
id: user.id,
email: user.email,
role: user.role,
profile,
},
});
} catch (error) {
next(error);
}
});
export default router;
+29
View File
@@ -1,7 +1,11 @@
import { Router, Request, Response, NextFunction } from 'express'; import { Router, Request, Response, NextFunction } from 'express';
import prisma from '../lib/prisma';
import authRouter from './auth';
const router = Router(); const router = Router();
router.use('/auth', authRouter);
/** /**
* @route GET /api/health * @route GET /api/health
* @desc Endpoint untuk memeriksa kesehatan/status server * @desc Endpoint untuk memeriksa kesehatan/status server
@@ -15,6 +19,31 @@ router.get('/health', (_req: Request, res: Response) => {
}); });
}); });
/**
* @route GET /api/users
* @desc Mendapatkan daftar semua user dari database menggunakan Prisma
* @access Public
*/
router.get('/users', async (_req: Request, res: Response, next: NextFunction) => {
try {
const users = await prisma.user.findMany({
select: {
id: true,
email: true,
role: true,
createdAt: true,
},
});
res.status(200).json({
success: true,
count: users.length,
data: users,
});
} catch (error) {
next(error);
}
});
/** /**
* @route GET /api/error-test * @route GET /api/error-test
* @desc Mensimulasikan error server internal untuk memverifikasi middleware penanganan error global * @desc Mensimulasikan error server internal untuk memverifikasi middleware penanganan error global