[#40] BACKEND USER, Company
https://project.mdnd-it.cc/work_packages/40
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
import { DataSource, DataSourceOptions } from 'typeorm';
|
||||
import { User } from './user.entity';
|
||||
import { Company } from './company.entity';
|
||||
import { QBank } from './qbank.entity';
|
||||
|
||||
// Get the database type from environment variables or default to MariaDB
|
||||
const dbType = process.env.DB_TYPE || 'mariadb';
|
||||
|
||||
// Create the options object with correct typing
|
||||
const options: DataSourceOptions = {
|
||||
type: dbType as any, // Using 'as any' to bypass TypeScript's strict type checking
|
||||
host: process.env.DB_HOST || 'localhost',
|
||||
port: Number(process.env.DB_PORT) || 3306,
|
||||
username: process.env.DB_USERNAME || 'root',
|
||||
password: process.env.DB_PASSWORD || '',
|
||||
database: process.env.DB_DATABASE || 'serpent_race',
|
||||
synchronize: process.env.NODE_ENV !== 'production',
|
||||
logging: process.env.NODE_ENV !== 'production',
|
||||
entities: [User, Company, QBank],
|
||||
migrations: [],
|
||||
subscribers: [],
|
||||
};
|
||||
|
||||
// Create the DataSource with properly typed options
|
||||
export const AppDataSourceOne = new DataSource(options);
|
||||
@@ -0,0 +1,39 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
OneToMany
|
||||
} from 'typeorm';
|
||||
import { User } from './user.entity';
|
||||
|
||||
@Entity('companies')
|
||||
export class Company {
|
||||
@PrimaryGeneratedColumn()
|
||||
CompanyId!: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: false })
|
||||
Name!: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: false })
|
||||
ContactFirstName!: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: false })
|
||||
ContactLastName!: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: false })
|
||||
ContactEmail!: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: false })
|
||||
FirstAPI!: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: false })
|
||||
TokenAPI!: string;
|
||||
|
||||
@CreateDateColumn()
|
||||
RegDate!: Date;
|
||||
|
||||
// Relation to User entity (optional)
|
||||
@OneToMany(() => User, user => user.company)
|
||||
users!: User[];
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, OneToMany } from 'typeorm';
|
||||
import { User } from './user.entity';
|
||||
|
||||
@Entity('q_bank')
|
||||
export class QBank {
|
||||
@PrimaryGeneratedColumn()
|
||||
QBankId!: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 255 })
|
||||
Title!: string;
|
||||
|
||||
@Column()
|
||||
Creator!: number;
|
||||
|
||||
@Column({ type: 'datetime' })
|
||||
Creation_Date!: Date;
|
||||
|
||||
@Column({ type: 'int' })
|
||||
no_question!: number;
|
||||
|
||||
@ManyToOne(() => User, user => user.questionBanks)
|
||||
@JoinColumn({ name: 'Creator' })
|
||||
creator!: User;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
OneToMany,
|
||||
ManyToOne,
|
||||
JoinColumn
|
||||
} from 'typeorm';
|
||||
import { Company } from './company.entity';
|
||||
import { QBank } from './qbank.entity';
|
||||
|
||||
// User entity for the SerpentRace API
|
||||
// This entity represents a user in the system, with fields for personal information,
|
||||
|
||||
@Entity('users')
|
||||
|
||||
export class User {
|
||||
@PrimaryGeneratedColumn()
|
||||
id!: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: false, unique: true })
|
||||
Username!: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: false })
|
||||
FirstName!: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: false })
|
||||
LastName!: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: false })
|
||||
Password!: string; // Consider hashing this password before storing
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: false, unique: true })
|
||||
Email!: string;
|
||||
|
||||
@CreateDateColumn()
|
||||
RegDate!: Date;
|
||||
|
||||
@Column({ nullable: true })
|
||||
CompanyId: number = 0;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
||||
CompanyToken: string = '';
|
||||
|
||||
@ManyToOne(() => Company, (company) => company.users)
|
||||
@JoinColumn({ name: 'CompanyId' })
|
||||
company!: Company;
|
||||
|
||||
@OneToMany(() => QBank, qbank => qbank.creator)
|
||||
questionBanks!: QBank[];
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
//User class for the all the user related operations in the SerpentRace Backend
|
||||
import { comparePasswords, hashPassword } from '../middleware/secure';
|
||||
import { FindOptionsWhere, Equal } from 'typeorm';
|
||||
import { AppDataSourceOne } from '../DB/Datasource';
|
||||
import { User } from '../DB/user.entity';
|
||||
import { Company } from '../DB/company.entity';
|
||||
import { Request, Response } from 'express';
|
||||
|
||||
export class CompanyService {
|
||||
private companyRepository = AppDataSourceOne.getRepository(Company);
|
||||
|
||||
async createCompany(company: Company): Promise<Company> {
|
||||
return this.companyRepository.save(company);
|
||||
}
|
||||
|
||||
private async findCompanyById(id: number): Promise<Company | null> {
|
||||
const where: FindOptionsWhere<Company> = { CompanyId: Equal(id) };
|
||||
return this.companyRepository.findOneBy(where);
|
||||
}
|
||||
|
||||
private async findCompanyByName(name: string): Promise<Company | null> {
|
||||
const where: FindOptionsWhere<Company> = { Name: Equal(name) };
|
||||
return this.companyRepository.findOneBy(where);
|
||||
}
|
||||
|
||||
async getCompanyDetails(req: Request, res: Response): Promise<void> {
|
||||
const companyId = parseInt(req.params.companyid);
|
||||
if (isNaN(companyId)) {
|
||||
res.status(400).json({ message: 'Invalid company ID' });
|
||||
return;
|
||||
}
|
||||
|
||||
const company = await this.findCompanyById(companyId);
|
||||
if (!company) {
|
||||
res.status(404).json({ message: 'Company not found' });
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).json(company);
|
||||
}
|
||||
|
||||
async updateCompany(req: Request, res: Response): Promise<void> {
|
||||
const companyId = parseInt(req.params.companyid);
|
||||
if (isNaN(companyId)) {
|
||||
res.status(400).json({ message: 'Invalid company ID' });
|
||||
return;
|
||||
}
|
||||
|
||||
const company = await this.findCompanyById(companyId);
|
||||
if (!company) {
|
||||
res.status(404).json({ message: 'Company not found' });
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedData = req.body;
|
||||
Object.assign(company, updatedData);
|
||||
|
||||
const updatedCompany = await this.companyRepository.save(company);
|
||||
res.status(200).json(updatedCompany);
|
||||
}
|
||||
async deleteCompany(req: Request, res: Response): Promise<void> {
|
||||
const companyId = parseInt(req.params.companyid);
|
||||
if (isNaN(companyId)) {
|
||||
res.status(400).json({ message: 'Invalid company ID' });
|
||||
return;
|
||||
}
|
||||
|
||||
const company = await this.findCompanyById(companyId);
|
||||
if (!company) {
|
||||
res.status(404).json({ message: 'Company not found' });
|
||||
return;
|
||||
}
|
||||
|
||||
await this.companyRepository.remove(company);
|
||||
res.status(204).send();
|
||||
}
|
||||
|
||||
private async addUserToCompany(userId: number, companyId: number): Promise<void> {
|
||||
const user = await AppDataSourceOne.getRepository(User).findOneBy({ id: userId });
|
||||
if (!user) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
const company = await this.findCompanyById(companyId);
|
||||
if (!company) {
|
||||
throw new Error('Company not found');
|
||||
}
|
||||
|
||||
user.CompanyId = companyId;
|
||||
await AppDataSourceOne.getRepository(User).save(user);
|
||||
// Optionally, you can also add the user to the company's user list if needed
|
||||
if (!company.users) {
|
||||
company.users = [];
|
||||
}
|
||||
company.users.push(user);
|
||||
}
|
||||
private async removeUserFromCompany(userId: number, companyId: number): Promise<void> {
|
||||
const user = await AppDataSourceOne.getRepository(User).findOneBy({ id: userId });
|
||||
if (!user) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
const company = await this.findCompanyById(companyId);
|
||||
if (!company) {
|
||||
throw new Error('Company not found');
|
||||
}
|
||||
|
||||
user.CompanyId = 0; // Remove association
|
||||
user.CompanyToken = ''; // Remove association
|
||||
await AppDataSourceOne.getRepository(User).save(user);
|
||||
|
||||
// Optionally, remove the user from the company's user list
|
||||
company.users = company.users?.filter(u => u.id !== userId) || [];
|
||||
await this.companyRepository.save(company);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
//User class for the all the user related operations in the SerpentRace Backend
|
||||
import { comparePasswords, hashPassword } from '../middleware/secure';
|
||||
import { FindOptionsWhere, Equal } from 'typeorm';
|
||||
import { AppDataSourceOne } from '../DB/Datasource';
|
||||
import { createToken } from '../middleware/auth';
|
||||
import { User } from '../DB/user.entity';
|
||||
import { Request, Response } from 'express';
|
||||
|
||||
export class UserService {
|
||||
private userRepository = AppDataSourceOne.getRepository(User);
|
||||
|
||||
async createUser(user : User): Promise<User> {
|
||||
// Hash the password before saving the user
|
||||
user.Password = await hashPassword(user.Password);
|
||||
return this.userRepository.save(user);
|
||||
}
|
||||
|
||||
private async findUserByEmail(email: string): Promise<User | null> {
|
||||
const where: FindOptionsWhere<User> = { Email: Equal(email) };
|
||||
return this.userRepository.findOneBy(where);
|
||||
}
|
||||
|
||||
private async findUserByUsername(username: string): Promise<User | null> {
|
||||
const where: FindOptionsWhere<User> = { Username: Equal(username) };
|
||||
return this.userRepository.findOneBy(where);
|
||||
}
|
||||
|
||||
private async findUserById(id: number): Promise<User | null> {
|
||||
const where: FindOptionsWhere<User> = { id: Equal(id) };
|
||||
return this.userRepository.findOneBy(where);
|
||||
}
|
||||
|
||||
async authenticateUser(req: Request, res: Response): Promise<void> {
|
||||
const { String_data, Password } = req.body;
|
||||
if (!String_data || !Password) {
|
||||
res.status(400).json({ message: 'Email and password are required' });
|
||||
return;
|
||||
}
|
||||
// Check if String_data is an email or username
|
||||
let user: User | null = null;
|
||||
if (String_data.includes('@')) {
|
||||
user = await this.findUserByEmail(String_data.toLowerCase());
|
||||
}
|
||||
else {
|
||||
user = await this.findUserByUsername(String_data);
|
||||
}
|
||||
// If user is not found or password does not match, return 401
|
||||
if (!user || !(await comparePasswords(Password, user.Password))) {
|
||||
res.status(401).json({ message: 'Invalid credentials' });
|
||||
return;
|
||||
}
|
||||
|
||||
const token = createToken(user);
|
||||
// Set the token in the response header
|
||||
res.cookie("jwt", token, {
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: "none",
|
||||
maxAge: 60 * 60 * 1000
|
||||
});
|
||||
res.status(200).json({ message: 'Authentication successful' });
|
||||
}
|
||||
|
||||
async getUserDetails(req: Request, res: Response): Promise<void> {
|
||||
const userId = req.user?.id;
|
||||
if (!userId) {
|
||||
res.status(401).json({ message: 'Unauthorized' });
|
||||
return;
|
||||
}
|
||||
const user = await this.userRepository.findOneBy({ id: userId });
|
||||
if (!user) {
|
||||
res.status(404).json({ message: 'User not found' });
|
||||
return;
|
||||
}
|
||||
res.json(user);
|
||||
}
|
||||
|
||||
async updateUser(req: Request, res: Response): Promise<void> {
|
||||
const userId = req.user?.id;
|
||||
if (!userId) {
|
||||
res.status(401).json({ message: 'Unauthorized' });
|
||||
return;
|
||||
}
|
||||
const user = await this.findUserById(userId);
|
||||
if (!user) {
|
||||
res.status(404).json({ message: 'User not found' });
|
||||
return;
|
||||
}
|
||||
|
||||
Object.assign(user, req.body);
|
||||
|
||||
if (req.body.Password) {
|
||||
user.Password = await hashPassword(req.body.Password);
|
||||
}
|
||||
await this.userRepository.save(user);
|
||||
res.json({ message: 'User details updated successfully' });
|
||||
}
|
||||
|
||||
async deleteUser(req: Request, res: Response): Promise<void> {
|
||||
const userId = req.user?.id;
|
||||
if (!userId) {
|
||||
res.status(401).json({ message: 'Unauthorized' });
|
||||
return;
|
||||
}
|
||||
const user = await this.findUserById(userId);
|
||||
if (!user) {
|
||||
res.status(404).json({ message: 'User not found' });
|
||||
return;
|
||||
}
|
||||
await this.userRepository.remove(user);
|
||||
res.json({ message: 'User deleted successfully' });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
//Router for user-related routes
|
||||
import { Router } from 'express';
|
||||
import { UserService } from './User';
|
||||
import { auth } from '../middleware/auth';
|
||||
import { User } from '../DB/user.entity';
|
||||
|
||||
const userRouter = Router();
|
||||
const userService = new UserService();
|
||||
// Route to create a new user
|
||||
userRouter.post('/create', async (req, res) => {
|
||||
try {
|
||||
const user = await userService.createUser(req.body);
|
||||
res.status(201).json({ message: 'User created successfully'});
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: 'Error creating user', error });
|
||||
}
|
||||
});
|
||||
|
||||
// Route to authenticate a user
|
||||
userRouter.post('/authenticate', async (req, res) => {
|
||||
try {
|
||||
await userService.authenticateUser(req, res);
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: 'Error authenticating user', error });
|
||||
}
|
||||
});
|
||||
|
||||
// Route to get user details (protected route)
|
||||
userRouter.get('/details', auth, async (req, res) => {
|
||||
try {
|
||||
await userService.getUserDetails(req, res);
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: 'Error fetching user details', error });
|
||||
}
|
||||
});
|
||||
|
||||
// Route to update user details (protected route)
|
||||
userRouter.put('/update', auth, async (req, res) =>{
|
||||
try{
|
||||
await userService.updateUser(req, res);
|
||||
}
|
||||
catch (error) {
|
||||
res.status(500).json({ message: 'Error updating user', error });
|
||||
}
|
||||
});
|
||||
// Route to delete a user (protected route)
|
||||
userRouter.delete('/delete', auth, async (req, res) => {
|
||||
try {
|
||||
await userService.deleteUser(req, res);
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: 'Error deleting user', error });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
//export default userRouter;
|
||||
export { userRouter };
|
||||
@@ -0,0 +1,9 @@
|
||||
import Express from 'express';
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
user?: any;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import express, { request } from 'express';
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
app.use(express.json())
|
||||
|
||||
app.listen(PORT, '0.0.0.0', ()=>{
|
||||
console.log(`Running on Port: ${PORT}`)
|
||||
});
|
||||
|
||||
app.get("/api/", (req, res) => {
|
||||
res.send("KÖRTE");
|
||||
});
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
console.log(process.env.DATABASE_URL_1 as string);
|
||||
import express from 'express';
|
||||
import cors from "cors";
|
||||
import cookieParser from "cookie-parser";
|
||||
import { mainRouter } from './router';
|
||||
import { AppDataSourceOne } from './DB/Datasource';
|
||||
|
||||
|
||||
const app = express();
|
||||
// Initialize the database connection
|
||||
AppDataSourceOne.initialize();
|
||||
|
||||
app.use(cors({
|
||||
origin: process.env.FRONTEND_URL,
|
||||
credentials: true
|
||||
}));
|
||||
app.use(cookieParser());
|
||||
|
||||
app.use('/', mainRouter);
|
||||
|
||||
app.listen(3000, () => {
|
||||
console.log('Server is running on port 3000');
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
export function auth(req: Request, res: Response, next: NextFunction): void {
|
||||
// Read token from cookie named 'jwt'
|
||||
const token = req.cookies.jwt;
|
||||
|
||||
if (!token) {
|
||||
res.status(401).json({ message: "No token provided" });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET || "secret");
|
||||
|
||||
if (typeof decoded === "string") {
|
||||
res.status(401).json({ message: "Invalid token payload" });
|
||||
return;
|
||||
}
|
||||
|
||||
req.user = decoded;
|
||||
|
||||
// Check if expiring soon & refresh if needed
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
if (decoded.exp && decoded.exp - now < 60 * 5) {
|
||||
const { iat, exp, ...payload } = decoded;
|
||||
const newToken = createToken(payload);
|
||||
res.cookie("jwt", newToken, {
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: "none",
|
||||
maxAge: 60 * 60 * 1000, // 1 hour
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
} catch (err) {
|
||||
res.status(401).json({ message: "Invalid token" });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
export function createToken(user: any): string {
|
||||
return jwt.sign({ id: user.id, username: user.username }, process.env.JWT_SECRET || 'secret', {
|
||||
expiresIn: '1h',
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
//hash password
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import bcrypt from 'bcryptjs';
|
||||
|
||||
export async function hashPassword(password: string): Promise<string> {
|
||||
const salt = await bcrypt.genSalt(10);
|
||||
return bcrypt.hash(password, salt);
|
||||
}
|
||||
|
||||
export async function comparePasswords(password: string, hash: string): Promise<boolean> {
|
||||
return bcrypt.compare(password, hash);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
//collection of all routes
|
||||
import { Router } from 'express';
|
||||
import { userRouter } from '../User/UserRouter';
|
||||
// import { companyRouter } from '../User/CompanyRouter';
|
||||
|
||||
const router = Router();
|
||||
// Use the user router for user-related routes
|
||||
router.use('/user', userRouter);
|
||||
// Use the company router for company-related routes
|
||||
// router.use('/company', companyRouter);
|
||||
// Add more routers as needed
|
||||
// Export the main router
|
||||
export { router as mainRouter };
|
||||
Reference in New Issue
Block a user