feat: basic api setup and boilerplate
This commit is contained in:
5
backend/.gitignore
vendored
Normal file
5
backend/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
node_modules
|
||||||
|
# Keep environment variables out of version control
|
||||||
|
.env
|
||||||
|
|
||||||
|
/src/generated/prisma
|
||||||
35
backend/package.json
Normal file
35
backend/package.json
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"name": "gg-node-backend",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "nodemon src/app.js",
|
||||||
|
"start": "node src/app.js",
|
||||||
|
"prisma": "prisma",
|
||||||
|
"migrate": "npx prisma migrate dev",
|
||||||
|
"generate": "npx prisma generate"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"@editorjs/editorjs": "^2.31.4",
|
||||||
|
"@editorjs/header": "^2.8.8",
|
||||||
|
"@editorjs/list": "^2.0.9",
|
||||||
|
"@prisma/client": "^6.19.2",
|
||||||
|
"bcrypt": "^6.0.0",
|
||||||
|
"bcryptjs": "^3.0.3",
|
||||||
|
"cors": "^2.8.6",
|
||||||
|
"dotenv": "^17.3.1",
|
||||||
|
"express": "^5.2.1",
|
||||||
|
"express-session": "^1.19.0",
|
||||||
|
"jsonwebtoken": "^9.0.3",
|
||||||
|
"multer": "^2.1.1",
|
||||||
|
"prisma": "^6.19.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"nodemon": "^3.1.11"
|
||||||
|
}
|
||||||
|
}
|
||||||
14
backend/prisma.config.js
Normal file
14
backend/prisma.config.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// This file was generated by Prisma, and assumes you have installed the following:
|
||||||
|
// npm install --save-dev prisma dotenv
|
||||||
|
import "dotenv/config";
|
||||||
|
import {defineConfig} from "prisma/config";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
schema: "prisma/schema.prisma",
|
||||||
|
migrations: {
|
||||||
|
path: "prisma/migrations",
|
||||||
|
},
|
||||||
|
datasource: {
|
||||||
|
url: process.env["DATABASE_URL"],
|
||||||
|
},
|
||||||
|
});
|
||||||
79
backend/prisma/migrations/20260223061336_init/migration.sql
Normal file
79
backend/prisma/migrations/20260223061336_init/migration.sql
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Doctor" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"doctorId" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"designation" TEXT,
|
||||||
|
"workingStatus" TEXT,
|
||||||
|
"qualification" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Doctor_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Department" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"departmentId" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"para1" TEXT,
|
||||||
|
"para2" TEXT,
|
||||||
|
"para3" TEXT,
|
||||||
|
"facilities" TEXT,
|
||||||
|
"services" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Department_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "DoctorDepartment" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"doctorId" INTEGER NOT NULL,
|
||||||
|
"departmentId" INTEGER NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "DoctorDepartment_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "DoctorTiming" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"doctorDepartmentId" INTEGER NOT NULL,
|
||||||
|
"monday" TEXT,
|
||||||
|
"tuesday" TEXT,
|
||||||
|
"wednesday" TEXT,
|
||||||
|
"thursday" TEXT,
|
||||||
|
"friday" TEXT,
|
||||||
|
"saturday" TEXT,
|
||||||
|
"sunday" TEXT,
|
||||||
|
"additional" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "DoctorTiming_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Doctor_doctorId_key" ON "Doctor"("doctorId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Department_departmentId_key" ON "Department"("departmentId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "DoctorDepartment_doctorId_departmentId_key" ON "DoctorDepartment"("doctorId", "departmentId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "DoctorTiming_doctorDepartmentId_key" ON "DoctorTiming"("doctorDepartmentId");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "DoctorDepartment" ADD CONSTRAINT "DoctorDepartment_doctorId_fkey" FOREIGN KEY ("doctorId") REFERENCES "Doctor"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "DoctorDepartment" ADD CONSTRAINT "DoctorDepartment_departmentId_fkey" FOREIGN KEY ("departmentId") REFERENCES "Department"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "DoctorTiming" ADD CONSTRAINT "DoctorTiming_doctorDepartmentId_fkey" FOREIGN KEY ("doctorDepartmentId") REFERENCES "DoctorDepartment"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "User" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"username" TEXT NOT NULL,
|
||||||
|
"password" TEXT NOT NULL,
|
||||||
|
"role" TEXT DEFAULT 'admin',
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
|
||||||
13
backend/prisma/migrations/20260311120120_blog/migration.sql
Normal file
13
backend/prisma/migrations/20260311120120_blog/migration.sql
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Blog" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"title" TEXT NOT NULL,
|
||||||
|
"writer" TEXT,
|
||||||
|
"image" TEXT,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"content" JSONB NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Blog_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
3
backend/prisma/migrations/migration_lock.toml
Normal file
3
backend/prisma/migrations/migration_lock.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Please do not edit this file manually
|
||||||
|
# It should be added in your version-control system (e.g., Git)
|
||||||
|
provider = "postgresql"
|
||||||
100
backend/prisma/schema.prisma
Normal file
100
backend/prisma/schema.prisma
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
|
||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "postgresql"
|
||||||
|
url = env("DATABASE_URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
model User {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
username String @unique
|
||||||
|
password String
|
||||||
|
role String? @default("admin")
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
model Doctor {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
doctorId String @unique
|
||||||
|
name String
|
||||||
|
designation String?
|
||||||
|
workingStatus String?
|
||||||
|
qualification String?
|
||||||
|
|
||||||
|
departments DoctorDepartment[]
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
model Department {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
departmentId String @unique
|
||||||
|
name String
|
||||||
|
|
||||||
|
para1 String?
|
||||||
|
para2 String?
|
||||||
|
para3 String?
|
||||||
|
facilities String?
|
||||||
|
services String?
|
||||||
|
|
||||||
|
doctors DoctorDepartment[]
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
model DoctorDepartment {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
|
||||||
|
doctorId Int
|
||||||
|
departmentId Int
|
||||||
|
|
||||||
|
doctor Doctor @relation(fields: [doctorId], references: [id])
|
||||||
|
department Department @relation(fields: [departmentId], references: [id])
|
||||||
|
|
||||||
|
timing DoctorTiming?
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
@@unique([doctorId, departmentId])
|
||||||
|
}
|
||||||
|
|
||||||
|
model DoctorTiming {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
|
||||||
|
doctorDepartmentId Int @unique
|
||||||
|
doctorDepartment DoctorDepartment @relation(fields: [doctorDepartmentId], references: [id])
|
||||||
|
|
||||||
|
monday String?
|
||||||
|
tuesday String?
|
||||||
|
wednesday String?
|
||||||
|
thursday String?
|
||||||
|
friday String?
|
||||||
|
saturday String?
|
||||||
|
sunday String?
|
||||||
|
additional String?
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
model Blog {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
title String
|
||||||
|
writer String?
|
||||||
|
image String?
|
||||||
|
content Json
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
42
backend/src/app.js
Normal file
42
backend/src/app.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import express from "express";
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
import cors from "cors";
|
||||||
|
|
||||||
|
import departmentRoutes from "./routes/department.routes.js";
|
||||||
|
import authRoutes from "./routes/auth.routes.js";
|
||||||
|
import blogRoutes from "./routes/blog.routes.js";
|
||||||
|
import uploadRoutes from "./routes/upload.routes.js";
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
const allowedOrigins = process.env.CORS_ALLOWED_ORIGINS
|
||||||
|
? process.env.CORS_ALLOWED_ORIGINS.split(" ")
|
||||||
|
: ["http://localhost:3001"];
|
||||||
|
|
||||||
|
const corsOptions = {
|
||||||
|
origin: function (origin, callback) {
|
||||||
|
if (!origin || allowedOrigins.includes(origin)) {
|
||||||
|
callback(null, true);
|
||||||
|
} else {
|
||||||
|
callback(new Error("Not allowed by CORS"));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: ["GET", "POST", "PUT", "DELETE", "PATCH"],
|
||||||
|
allowedHeaders: "*",
|
||||||
|
};
|
||||||
|
|
||||||
|
app.use(express.json());
|
||||||
|
app.use(cors(corsOptions));
|
||||||
|
|
||||||
|
app.use("/api/departments", departmentRoutes);
|
||||||
|
app.use("/api/auth", authRoutes);
|
||||||
|
app.use("/api/blogs", blogRoutes);
|
||||||
|
app.use("/uploads", express.static("uploads"));
|
||||||
|
app.use("/api/upload", uploadRoutes);
|
||||||
|
|
||||||
|
const PORT = process.env.PORT || 3000;
|
||||||
|
app.listen(PORT, () => {
|
||||||
|
console.log(`Server running on port ${PORT}`);
|
||||||
|
});
|
||||||
76
backend/src/controllers/auth.controller.js
Normal file
76
backend/src/controllers/auth.controller.js
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import prisma from "../prisma/client.js";
|
||||||
|
import {generateToken} from "../utils/jwt.js";
|
||||||
|
import {hashPassword, comparePassword} from "../utils/password.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REGISTER
|
||||||
|
* POST /api/auth/register
|
||||||
|
*/
|
||||||
|
export async function register(req, res) {
|
||||||
|
const {username, password, role} = req.body;
|
||||||
|
|
||||||
|
if (!username || !password) {
|
||||||
|
return res.status(400).json({error: "Username and password required"});
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingUser = await prisma.user.findUnique({
|
||||||
|
where: {username},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingUser) {
|
||||||
|
return res.status(409).json({error: "Username already exists"});
|
||||||
|
}
|
||||||
|
|
||||||
|
const hashedPassword = await hashPassword(password);
|
||||||
|
|
||||||
|
const user = await prisma.user.create({
|
||||||
|
data: {
|
||||||
|
username,
|
||||||
|
password: hashedPassword,
|
||||||
|
role: role || "admin",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(201).json({
|
||||||
|
message: "User registered successfully",
|
||||||
|
user: {
|
||||||
|
id: user.id,
|
||||||
|
username: user.username,
|
||||||
|
role: user.role,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LOGIN
|
||||||
|
* POST /api/auth/login
|
||||||
|
*/
|
||||||
|
export async function login(req, res) {
|
||||||
|
const {username, password} = req.body;
|
||||||
|
|
||||||
|
if (!username || !password) {
|
||||||
|
return res.status(400).json({error: "Username and password required"});
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: {username},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(401).json({error: "Invalid credentials"});
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValid = await comparePassword(password, user.password);
|
||||||
|
|
||||||
|
if (!isValid) {
|
||||||
|
return res.status(401).json({error: "Invalid credentials"});
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = generateToken({
|
||||||
|
userId: user.id,
|
||||||
|
username: user.username,
|
||||||
|
role: user.role,
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({token});
|
||||||
|
}
|
||||||
110
backend/src/controllers/blog.controller.js
Normal file
110
backend/src/controllers/blog.controller.js
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
import prisma from "../prisma/client.js";
|
||||||
|
|
||||||
|
/* CREATE BLOG */
|
||||||
|
|
||||||
|
export async function createBlog(req, res) {
|
||||||
|
const {title, writer, image, content, isActive} = req.body;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const blog = await prisma.blog.create({
|
||||||
|
data: {
|
||||||
|
title,
|
||||||
|
writer,
|
||||||
|
image,
|
||||||
|
content,
|
||||||
|
isActive,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json(blog);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({error: "Blog creation failed"});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GET ALL BLOGS (Public) */
|
||||||
|
|
||||||
|
export async function getBlogs(req, res) {
|
||||||
|
try {
|
||||||
|
const blogs = await prisma.blog.findMany({
|
||||||
|
where: {isActive: true},
|
||||||
|
orderBy: {createdAt: "desc"},
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json(blogs);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({error: error.message});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GET ALL BLOGS (Admin) */
|
||||||
|
|
||||||
|
export async function getAllBlogs(req, res) {
|
||||||
|
try {
|
||||||
|
const blogs = await prisma.blog.findMany({
|
||||||
|
orderBy: {createdAt: "desc"},
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json(blogs);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({error: error.message});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GET SINGLE BLOG */
|
||||||
|
|
||||||
|
export async function getBlog(req, res) {
|
||||||
|
try {
|
||||||
|
const id = Number(req.params.id);
|
||||||
|
|
||||||
|
const blog = await prisma.blog.findUnique({
|
||||||
|
where: {id},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!blog) {
|
||||||
|
return res.status(404).json({error: "Blog not found"});
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json(blog);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({error: error.message});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* UPDATE BLOG */
|
||||||
|
|
||||||
|
export async function updateBlog(req, res) {
|
||||||
|
try {
|
||||||
|
const {title, writer, image, content} = req.body;
|
||||||
|
|
||||||
|
const blog = await prisma.blog.update({
|
||||||
|
where: {id: Number(req.params.id)},
|
||||||
|
data: {
|
||||||
|
title,
|
||||||
|
writer,
|
||||||
|
image,
|
||||||
|
content,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json(blog);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({error: error.message});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* DELETE BLOG */
|
||||||
|
|
||||||
|
export async function deleteBlog(req, res) {
|
||||||
|
try {
|
||||||
|
const id = Number(req.params.id);
|
||||||
|
|
||||||
|
await prisma.blog.delete({
|
||||||
|
where: {id},
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({message: "Blog deleted successfully"});
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({error: error.message});
|
||||||
|
}
|
||||||
|
}
|
||||||
66
backend/src/controllers/department.controller.js
Normal file
66
backend/src/controllers/department.controller.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import prisma from "../prisma/client.js";
|
||||||
|
|
||||||
|
export const getAllDepartments = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const departments = await prisma.department.findMany({
|
||||||
|
orderBy: {name: "asc"},
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = departments.map((dep) => ({
|
||||||
|
departmentId: dep.departmentId,
|
||||||
|
Department: dep.name,
|
||||||
|
para1: dep.para1 ?? "",
|
||||||
|
para2: dep.para2 ?? "",
|
||||||
|
para3: dep.para3 ?? "",
|
||||||
|
facilities: dep.facilities ?? "",
|
||||||
|
services: dep.services ?? "",
|
||||||
|
}));
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
data: response,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: "Failed to fetch departments",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function createDepartment(req, res) {
|
||||||
|
try {
|
||||||
|
const {departmentId, name, para1, para2, para3, facilities, services} =
|
||||||
|
req.body;
|
||||||
|
|
||||||
|
if (!departmentId || !name) {
|
||||||
|
return res
|
||||||
|
.status(400)
|
||||||
|
.json({error: "departmentId and name are required"});
|
||||||
|
}
|
||||||
|
|
||||||
|
const department = await prisma.department.create({
|
||||||
|
data: {
|
||||||
|
departmentId,
|
||||||
|
name,
|
||||||
|
para1,
|
||||||
|
para2,
|
||||||
|
para3,
|
||||||
|
facilities,
|
||||||
|
services,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(201).json({
|
||||||
|
message: "Department created successfully",
|
||||||
|
data: department,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code === "P2002") {
|
||||||
|
return res.status(409).json({error: "Department already exists"});
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(500).json({error: "Failed to create department"});
|
||||||
|
}
|
||||||
|
}
|
||||||
15
backend/src/controllers/upload.controller.js
Normal file
15
backend/src/controllers/upload.controller.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import multer from "multer";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
const storage = multer.diskStorage({
|
||||||
|
destination: function (req, file, cb) {
|
||||||
|
cb(null, "uploads/blog");
|
||||||
|
},
|
||||||
|
|
||||||
|
filename: function (req, file, cb) {
|
||||||
|
const fileName = Date.now() + path.extname(file.originalname);
|
||||||
|
cb(null, fileName);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const upload = multer({storage});
|
||||||
19
backend/src/middleware/auth.js
Normal file
19
backend/src/middleware/auth.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import {verifyToken} from "../utils/jwt.js";
|
||||||
|
|
||||||
|
export default function jwtAuthMiddleware(req, res, next) {
|
||||||
|
const authHeader = req.headers.authorization;
|
||||||
|
|
||||||
|
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
||||||
|
return res.status(401).json({error: "No token provided"});
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = authHeader.split(" ")[1];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const user = verifyToken(token);
|
||||||
|
req.user = user;
|
||||||
|
next();
|
||||||
|
} catch (err) {
|
||||||
|
return res.status(401).json({error: "Invalid or expired token"});
|
||||||
|
}
|
||||||
|
}
|
||||||
5
backend/src/prisma/client.js
Normal file
5
backend/src/prisma/client.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import {PrismaClient} from "@prisma/client";
|
||||||
|
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
export default prisma;
|
||||||
9
backend/src/routes/auth.routes.js
Normal file
9
backend/src/routes/auth.routes.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import express from "express";
|
||||||
|
import {register, login} from "../controllers/auth.controller.js";
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.post("/register", register);
|
||||||
|
router.post("/login", login);
|
||||||
|
|
||||||
|
export default router;
|
||||||
27
backend/src/routes/blog.routes.js
Normal file
27
backend/src/routes/blog.routes.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import express from "express";
|
||||||
|
import {
|
||||||
|
createBlog,
|
||||||
|
getBlogs,
|
||||||
|
getBlog,
|
||||||
|
updateBlog,
|
||||||
|
deleteBlog,
|
||||||
|
getAllBlogs,
|
||||||
|
} from "../controllers/blog.controller.js";
|
||||||
|
|
||||||
|
import jwtAuthMiddleware from "../middleware/auth.js";
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
/* PUBLIC */
|
||||||
|
|
||||||
|
router.get("/", getBlogs);
|
||||||
|
router.get("/:id", getBlog);
|
||||||
|
|
||||||
|
// Protected
|
||||||
|
|
||||||
|
router.get("/admin/all", jwtAuthMiddleware, getAllBlogs);
|
||||||
|
router.post("/", jwtAuthMiddleware, createBlog);
|
||||||
|
router.put("/:id", jwtAuthMiddleware, updateBlog);
|
||||||
|
router.delete("/:id", jwtAuthMiddleware, deleteBlog);
|
||||||
|
|
||||||
|
export default router;
|
||||||
16
backend/src/routes/department.routes.js
Normal file
16
backend/src/routes/department.routes.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import express from "express";
|
||||||
|
import {
|
||||||
|
getAllDepartments,
|
||||||
|
createDepartment,
|
||||||
|
} from "../controllers/department.controller.js";
|
||||||
|
import jwtAuthMiddleware from "../middleware/auth.js";
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
// Public
|
||||||
|
router.get("/getAll", getAllDepartments);
|
||||||
|
|
||||||
|
// Protected
|
||||||
|
router.post("/", jwtAuthMiddleware, createDepartment);
|
||||||
|
|
||||||
|
export default router;
|
||||||
15
backend/src/routes/upload.routes.js
Normal file
15
backend/src/routes/upload.routes.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import express from "express";
|
||||||
|
import {upload} from "../controllers/upload.controller.js";
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.post("/image", upload.single("image"), (req, res) => {
|
||||||
|
res.json({
|
||||||
|
success: 1,
|
||||||
|
file: {
|
||||||
|
url: `http://localhost:3000/uploads/blog/${req.file.filename}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
12
backend/src/utils/jwt.js
Normal file
12
backend/src/utils/jwt.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import jwt from "jsonwebtoken";
|
||||||
|
import "dotenv/config";
|
||||||
|
|
||||||
|
const SECRET = process.env.JWT_SECRET;
|
||||||
|
|
||||||
|
export function generateToken(payload) {
|
||||||
|
return jwt.sign(payload, SECRET, {expiresIn: "24h"});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function verifyToken(token) {
|
||||||
|
return jwt.verify(token, SECRET);
|
||||||
|
}
|
||||||
9
backend/src/utils/password.js
Normal file
9
backend/src/utils/password.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import bcrypt from "bcryptjs";
|
||||||
|
|
||||||
|
export async function hashPassword(password) {
|
||||||
|
return bcrypt.hash(password, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function comparePassword(password, hash) {
|
||||||
|
return bcrypt.compare(password, hash);
|
||||||
|
}
|
||||||
BIN
backend/uploads/blog/1773296409367.png
Normal file
BIN
backend/uploads/blog/1773296409367.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 171 KiB |
BIN
backend/uploads/blog/1773298604982.png
Normal file
BIN
backend/uploads/blog/1773298604982.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 MiB |
BIN
backend/uploads/blog/1773298612512.png
Normal file
BIN
backend/uploads/blog/1773298612512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 171 KiB |
Reference in New Issue
Block a user