Merge pull request '[#40] BACKEND USER, Company' (#13) from task/40-backend-user-company into main

Reviewed-on: #13
This commit was merged in pull request #13.
This commit is contained in:
2025-06-14 23:14:29 +00:00
32 changed files with 6337 additions and 860 deletions
+4
View File
@@ -0,0 +1,4 @@
#ignore each file in folder that starts with Archive_
Archive_*
#ignore each folder that starts with Archive_
Archive_*/**
+2
View File
@@ -0,0 +1,2 @@
node_modules
/dist/generated/prisma
-1
View File
@@ -1 +0,0 @@
Ez a backend readme fájlja
+2223 -6
View File
File diff suppressed because it is too large Load Diff
+46 -47
View File
@@ -1,4 +1,6 @@
# ipaddr.js — an IPv6 and IPv4 address manipulation library [![Build Status](https://travis-ci.org/whitequark/ipaddr.js.svg)](https://travis-ci.org/whitequark/ipaddr.js)
# ipaddr.js — an IPv6 and IPv4 address manipulation library
[![Build Status](https://github.com/whitequark/ipaddr.js/workflows/CI%20Tests/badge.svg)](https://github.com/whitequark/ipaddr.js/actions?query=workflow%3A%22CI+Tests%22)
ipaddr.js is a small (1.9K minified and gzipped) library for manipulating
IP addresses in JavaScript environments. It runs on both CommonJS runtimes
@@ -19,25 +21,34 @@ or
`bower install ipaddr.js`
## Older Node support
Use 2.x release for nodejs versions 10+.
Use the 1.x release for versions of nodejs older than 10.
## API
ipaddr.js defines one object in the global scope: `ipaddr`. In CommonJS,
it is exported from the module:
```js
var ipaddr = require('ipaddr.js');
const ipaddr = require('ipaddr.js');
```
The API consists of several global methods and two classes: ipaddr.IPv6 and ipaddr.IPv4.
### Global methods
There are three global methods defined: `ipaddr.isValid`, `ipaddr.parse` and
`ipaddr.process`. All of them receive a string as a single parameter.
There are four global methods defined: `ipaddr.isValid`, `ipaddr.isValidCIDR`,
`ipaddr.parse`, and `ipaddr.process`. All of them receive a string as a single
parameter.
The `ipaddr.isValid` method returns `true` if the address is a valid IPv4 or
IPv6 address, and `false` otherwise. It does not throw any exceptions.
The `ipaddr.isValidCIDR` method returns `true` if the address is a valid IPv4 or
IPv6 address in CIDR notation, and `false` otherwise. It does not throw any exceptions.
The `ipaddr.parse` method returns an object representing the IP address,
or throws an `Error` if the passed string is not a valid representation of an
IP address.
@@ -67,40 +78,34 @@ Note that this method:
* returns a compact representation (when it is applicable)
A `match(range, bits)` method can be used to check if the address falls into a
certain CIDR range.
Note that an address can be (obviously) matched only against an address of the same type.
certain CIDR range. Note that an address can be (obviously) matched only against an address of the same type.
For example:
```js
var addr = ipaddr.parse("2001:db8:1234::1");
var range = ipaddr.parse("2001:db8::");
const addr = ipaddr.parse('2001:db8:1234::1');
const range = ipaddr.parse('2001:db8::');
addr.match(range, 32); // => true
```
Alternatively, `match` can also be called as `match([range, bits])`. In this way,
it can be used together with the `parseCIDR(string)` method, which parses an IP
address together with a CIDR range.
Alternatively, `match` can also be called as `match([range, bits])`. In this way, it can be used together with the `parseCIDR(string)` method, which parses an IP address together with a CIDR range.
For example:
```js
var addr = ipaddr.parse("2001:db8:1234::1");
const addr = ipaddr.parse('2001:db8:1234::1');
addr.match(ipaddr.parseCIDR("2001:db8::/32")); // => true
addr.match(ipaddr.parseCIDR('2001:db8::/32')); // => true
```
A `range()` method returns one of predefined names for several special ranges defined
by IP protocols. The exact names (and their respective CIDR ranges) can be looked up
in the source: [IPv6 ranges] and [IPv4 ranges]. Some common ones include `"unicast"`
(the default one) and `"reserved"`.
A `range()` method returns one of predefined names for several special ranges defined by IP protocols. The exact names (and their respective CIDR ranges) can be looked up in the source: [IPv6 ranges] and [IPv4 ranges]. Some common ones include `"unicast"` (the default one) and `"reserved"`.
You can match against your own range list by using
`ipaddr.subnetMatch(address, rangeList, defaultName)` method. It can work with a mix of IPv6 or IPv4 addresses, and accepts a name-to-subnet map as the range list. For example:
```js
var rangeList = {
const rangeList = {
documentationOnly: [ ipaddr.parse('2001:db8::'), 32 ],
tunnelProviders: [
[ ipaddr.parse('2001:470::'), 32 ], // he.net
@@ -110,38 +115,32 @@ var rangeList = {
ipaddr.subnetMatch(ipaddr.parse('2001:470:8:66::1'), rangeList, 'unknown'); // => "tunnelProviders"
```
The addresses can be converted to their byte representation with `toByteArray()`.
(Actually, JavaScript mostly does not know about byte buffers. They are emulated with
arrays of numbers, each in range of 0..255.)
The addresses can be converted to their byte representation with `toByteArray()`. (Actually, JavaScript mostly does not know about byte buffers. They are emulated with arrays of numbers, each in range of 0..255.)
```js
var bytes = ipaddr.parse('2a00:1450:8007::68').toByteArray(); // ipv6.google.com
const bytes = ipaddr.parse('2a00:1450:8007::68').toByteArray(); // ipv6.google.com
bytes // => [42, 0x00, 0x14, 0x50, 0x80, 0x07, 0x00, <zeroes...>, 0x00, 0x68 ]
```
The `ipaddr.IPv4` and `ipaddr.IPv6` objects have some methods defined, too. All of them
have the same interface for both protocols, and are similar to global methods.
The `ipaddr.IPv4` and `ipaddr.IPv6` objects have some methods defined, too. All of them have the same interface for both protocols, and are similar to global methods.
`ipaddr.IPvX.isValid(string)` can be used to check if the string is a valid address
for particular protocol, and `ipaddr.IPvX.parse(string)` is the error-throwing parser.
`ipaddr.IPvX.isValid(string)` can be used to check if the string is a valid address for particular protocol, and `ipaddr.IPvX.parse(string)` is the error-throwing parser.
`ipaddr.IPvX.isValid(string)` uses the same format for parsing as the POSIX `inet_ntoa` function, which accepts unusual formats like `0xc0.168.1.1` or `0x10000000`. The function `ipaddr.IPv4.isValidFourPartDecimal(string)` validates the IPv4 address and also ensures that it is written in four-part decimal format.
[IPv6 ranges]: https://github.com/whitequark/ipaddr.js/blob/master/src/ipaddr.coffee#L186
[IPv4 ranges]: https://github.com/whitequark/ipaddr.js/blob/master/src/ipaddr.coffee#L71
[IPv6 ranges]: https://github.com/whitequark/ipaddr.js/blob/master/lib/ipaddr.js#L530
[IPv4 ranges]: https://github.com/whitequark/ipaddr.js/blob/master/lib/ipaddr.js#L182
#### IPv6 properties
Sometimes you will want to convert IPv6 not to a compact string representation (with
the `::` substitution); the `toNormalizedString()` method will return an address where
all zeroes are explicit.
Sometimes you will want to convert IPv6 not to a compact string representation (with the `::` substitution); the `toNormalizedString()` method will return an address where all zeroes are explicit.
For example:
```js
var addr = ipaddr.parse("2001:0db8::0001");
addr.toString(); // => "2001:db8::1"
addr.toNormalizedString(); // => "2001:db8:0:0:0:0:0:1"
const addr = ipaddr.parse('2001:0db8::0001');
addr.toString(); // => '2001:db8::1'
addr.toNormalizedString(); // => '2001:db8:0:0:0:0:0:1'
```
The `isIPv4MappedAddress()` method will return `true` if this address is an IPv4-mapped
@@ -150,14 +149,14 @@ one, and `toIPv4Address()` will return an IPv4 object address.
To access the underlying binary representation of the address, use `addr.parts`.
```js
var addr = ipaddr.parse("2001:db8:10::1234:DEAD");
const addr = ipaddr.parse('2001:db8:10::1234:DEAD');
addr.parts // => [0x2001, 0xdb8, 0x10, 0, 0, 0, 0x1234, 0xdead]
```
A IPv6 zone index can be accessed via `addr.zoneId`:
```js
var addr = ipaddr.parse("2001:db8::%eth0");
const addr = ipaddr.parse('2001:db8::%eth0');
addr.zoneId // => 'eth0'
```
@@ -168,7 +167,7 @@ addr.zoneId // => 'eth0'
To access the underlying representation of the address, use `addr.octets`.
```js
var addr = ipaddr.parse("192.168.1.1");
const addr = ipaddr.parse('192.168.1.1');
addr.octets // => [192, 168, 1, 1]
```
@@ -183,17 +182,17 @@ ipaddr.IPv4.parse('255.192.164.0').prefixLengthFromSubnetMask() == null
`subnetMaskFromPrefixLength()` will return an IPv4 netmask for a valid CIDR prefix length.
```js
ipaddr.IPv4.subnetMaskFromPrefixLength(24) == "255.255.255.0"
ipaddr.IPv4.subnetMaskFromPrefixLength(29) == "255.255.255.248"
ipaddr.IPv4.subnetMaskFromPrefixLength(24) == '255.255.255.0'
ipaddr.IPv4.subnetMaskFromPrefixLength(29) == '255.255.255.248'
```
`broadcastAddressFromCIDR()` will return the broadcast address for a given IPv4 interface and netmask in CIDR notation.
```js
ipaddr.IPv4.broadcastAddressFromCIDR("172.0.0.1/24") == "172.0.0.255"
ipaddr.IPv4.broadcastAddressFromCIDR('172.0.0.1/24') == '172.0.0.255'
```
`networkAddressFromCIDR()` will return the network address for a given IPv4 interface and netmask in CIDR notation.
```js
ipaddr.IPv4.networkAddressFromCIDR("172.0.0.1/24") == "172.0.0.0"
ipaddr.IPv4.networkAddressFromCIDR('172.0.0.1/24') == '172.0.0.0'
```
#### Conversion
@@ -206,28 +205,28 @@ while for IPv6 it has to be an array of sixteen 8-bit values.
For example:
```js
var addr = ipaddr.fromByteArray([0x7f, 0, 0, 1]);
addr.toString(); // => "127.0.0.1"
const addr = ipaddr.fromByteArray([0x7f, 0, 0, 1]);
addr.toString(); // => '127.0.0.1'
```
or
```js
var addr = ipaddr.fromByteArray([0x20, 1, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1])
addr.toString(); // => "2001:db8::1"
const addr = ipaddr.fromByteArray([0x20, 1, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1])
addr.toString(); // => '2001:db8::1'
```
Both objects also offer a `toByteArray()` method, which returns an array in network byte order (MSB).
For example:
```js
var addr = ipaddr.parse("127.0.0.1");
const addr = ipaddr.parse('127.0.0.1');
addr.toByteArray(); // => [0x7f, 0, 0, 1]
```
or
```js
var addr = ipaddr.parse("2001:db8::1");
const addr = ipaddr.parse('2001:db8::1');
addr.toByteArray(); // => [0x20, 1, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
```
File diff suppressed because one or more lines are too long
+1028 -645
View File
File diff suppressed because it is too large Load Diff
+13 -10
View File
@@ -1,6 +1,7 @@
declare module "ipaddr.js" {
type IPv4Range = 'unicast' | 'unspecified' | 'broadcast' | 'multicast' | 'linkLocal' | 'loopback' | 'carrierGradeNat' | 'private' | 'reserved';
type IPv6Range = 'unicast' | 'unspecified' | 'linkLocal' | 'multicast' | 'loopback' | 'uniqueLocal' | 'ipv4Mapped' | 'rfc6145' | 'rfc6052' | '6to4' | 'teredo' | 'reserved';
type IPvXRangeDefaults = 'unicast' | 'unspecified' | 'multicast' | 'linkLocal' | 'loopback' | 'reserved' | 'benchmarking' | 'amt';
type IPv4Range = IPvXRangeDefaults | 'broadcast' | 'carrierGradeNat' | 'private' | 'as112';
type IPv6Range = IPvXRangeDefaults | 'uniqueLocal' | 'ipv4Mapped' | 'rfc6145' | 'rfc6052' | '6to4' | 'teredo' | 'as112v6' | 'orchid2' | 'droneRemoteIdProtocolEntityTags';
interface RangeList<T> {
[name: string]: [T, number] | [T, number][];
@@ -15,19 +16,20 @@ declare module "ipaddr.js" {
}
namespace Address {
export function isValid(addr: string): boolean;
export function fromByteArray(bytes: number[]): IPv4 | IPv6;
export function isValid(addr: string): boolean;
export function isValidCIDR(addr: string): boolean;
export function parse(addr: string): IPv4 | IPv6;
export function parseCIDR(mask: string): [IPv4 | IPv6, number];
export function process(addr: string): IPv4 | IPv6;
export function subnetMatch(addr: IPv4, rangeList: RangeList<IPv4>, defaultName?: string): string;
export function subnetMatch(addr: IPv6, rangeList: RangeList<IPv6>, defaultName?: string): string;
export function subnetMatch(addr: IPv4 | IPv6, rangeList: RangeList<IPv4 | IPv6>, defaultName?: string): string;
export class IPv4 extends IP {
static broadcastAddressFromCIDR(addr: string): IPv4;
static isIPv4(addr: string): boolean;
static isValidFourPartDecimal(addr: string): boolean;
static isValid(addr: string): boolean;
static isValidCIDR(addr: string): boolean;
static isValidFourPartDecimal(addr: string): boolean;
static networkAddressFromCIDR(addr: string): IPv4;
static parse(addr: string): IPv4;
static parseCIDR(addr: string): [IPv4, number];
@@ -36,8 +38,7 @@ declare module "ipaddr.js" {
octets: number[]
kind(): 'ipv4';
match(addr: IPv4, bits: number): boolean;
match(mask: [IPv4, number]): boolean;
match(what: IPv4 | IPv6 | [IPv4 | IPv6, number], bits?: number): boolean;
range(): IPv4Range;
subnetMatch(rangeList: RangeList<IPv4>, defaultName?: string): string;
toIPv4MappedAddress(): IPv6;
@@ -47,6 +48,8 @@ declare module "ipaddr.js" {
static broadcastAddressFromCIDR(addr: string): IPv6;
static isIPv6(addr: string): boolean;
static isValid(addr: string): boolean;
static isValidCIDR(addr: string): boolean;
static networkAddressFromCIDR(addr: string): IPv6;
static parse(addr: string): IPv6;
static parseCIDR(addr: string): [IPv6, number];
static subnetMaskFromPrefixLength(prefix: number): IPv6;
@@ -56,11 +59,11 @@ declare module "ipaddr.js" {
isIPv4MappedAddress(): boolean;
kind(): 'ipv6';
match(addr: IPv6, bits: number): boolean;
match(mask: [IPv6, number]): boolean;
match(what: IPv4 | IPv6 | [IPv4 | IPv6, number], bits?: number): boolean;
range(): IPv6Range;
subnetMatch(rangeList: RangeList<IPv6>, defaultName?: string): string;
toIPv4Address(): IPv4;
toRFC5952String(): string;
}
}
+9 -7
View File
@@ -1,22 +1,24 @@
{
"name": "ipaddr.js",
"description": "A library for manipulating IPv4 and IPv6 addresses in JavaScript.",
"version": "1.9.1",
"version": "2.2.0",
"author": "whitequark <whitequark@whitequark.org>",
"directories": {
"lib": "./lib"
},
"dependencies": {},
"devDependencies": {
"coffee-script": "~1.12.6",
"nodeunit": "^0.11.3",
"uglify-js": "~3.0.19"
"eslint": "^8.57.0",
"uglify-es": "*"
},
"scripts": {
"test": "cake build test"
"lint": "npx eslint lib",
"lintfix": "npx eslint --fix lib test",
"build": "npx uglifyjs --compress --mangle --wrap=window -o ipaddr.min.js lib/ipaddr.js",
"test": "node --test"
},
"files": [
"lib/",
"lib",
"LICENSE",
"ipaddr.min.js"
],
@@ -28,7 +30,7 @@
"repository": "git://github.com/whitequark/ipaddr.js",
"main": "./lib/ipaddr.js",
"engines": {
"node": ">= 0.10"
"node": ">= 10"
},
"license": "MIT",
"types": "./lib/ipaddr.js.d.ts"
+2240 -8
View File
File diff suppressed because it is too large Load Diff
+24 -8
View File
@@ -1,20 +1,36 @@
{
"name": "serpentrace_backend",
"version": "1.0.0",
"main": "index.mjs",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start:dev": "nodemon ./src/index.mjs",
"start": "nodemon ./src/index.mjs"
"start": "node dist/index.js",
"dev": "nodemon --exec ts-node src/index.ts",
"build": "tsc -p tsconfig.json && node ./prisma/prisma-migrate-all.js",
"lint": "eslint . --ext .ts",
"migrate:all": "node ./prisma/prisma-migrate-all.js"
},
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"express": "^5.1.0"
"@types/bcryptjs": "^2.4.6",
"@types/cookie-parser": "^1.4.9",
"@types/cors": "^2.8.19",
"@types/express": "^5.0.2",
"@types/jsonwebtoken": "^9.0.9",
"@types/node": "^22.15.21",
"bcrypt": "^6.0.0",
"cookie-parser": "^1.4.7",
"cors": "^2.8.5",
"express": "^5.1.0",
"jsonwebtoken": "^9.0.2",
"minio": "^8.0.5",
"typeorm": "^0.3.24",
"typescript": "^5.8.3",
"winston": "^3.17.0"
},
"devDependencies": {
"nodemon": "^3.1.10"
},
"type": "module"
"nodemon": "^3.1.10",
"ts-node": "^10.9.2"
}
}
+25
View File
@@ -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;
}
+52
View File
@@ -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[];
}
+116
View File
@@ -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);
}
}
+113
View File
@@ -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;
}
}
}
-14
View File
@@ -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");
});
+23
View File
@@ -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);
}
+13
View File
@@ -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 };
+113
View File
@@ -0,0 +1,113 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "libReplacement": true, /* Enable lib replacement. */
"experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
"emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "commonjs", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
// "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
// "noUncheckedSideEffectImports": true, /* Check side effect imports. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
"outDir": "./dist", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
// "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
// "erasableSyntaxOnly": true, /* Do not allow runtime constructs that are not part of ECMAScript. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}
+14 -6
View File
@@ -1,10 +1,18 @@
POSTGRES_USER=Admin
POSTGRES_PASSWORD=QwEr12345.
POSTGRES_DB_1=Default
POSTGRES_DB_2=USERS
POSTGRES_DB_3=QUESTIONS
POSTGRES_DB_4=STATISTICS
DB_PASSWORD=QwEr12345.
Default_User=Admin
Default_Password=Admin.Admin
# Database configuration
DB_1=Default
DB_2=QUESTIONS
DB_3=STATISTICS
DB_4=USERS
DB_PSWD_1=42vM2ftPy1YetSz9AdAHkayxKvQHuV9Wh0nT8c5DKYt
DB_PSWD_2=3cYzPBoLtPindO53Eh3cA80DqqjYvq7CJOzA2Eik00k
DB_PSWD_3=HyJcDLDW3ZYXbMLzlkL4zWqWwyhExQ4XvVCuT8ihld0
DB_PSWD_4=965o6d3Mz7YlgI8GkJkiZD6PIpTZfaBzIheZX3nIpY3
# Ports configuration
BACKEND_PORT=3000
FRONTEND_PORT=5173
DATABASE_PORT=80
-55
View File
@@ -1,55 +0,0 @@
-- Adminer 5.3.0 PostgreSQL 17.5 dump
DROP TABLE IF EXISTS "Code";
DROP SEQUENCE IF EXISTS "Code_CODE_ID_seq";
CREATE SEQUENCE "Code_CODE_ID_seq" INCREMENT 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1;
CREATE TABLE "public"."Code" (
"CODE_ID" integer DEFAULT nextval('"Code_CODE_ID_seq"') NOT NULL,
"CODE" character varying(60) NOT NULL,
"Time" timestamp NOT NULL,
CONSTRAINT "Code_pkey" PRIMARY KEY ("CODE_ID")
)
WITH (oids = false);
DROP TABLE IF EXISTS "Company";
DROP SEQUENCE IF EXISTS "Company_C_ID_seq";
CREATE SEQUENCE "Company_C_ID_seq" INCREMENT 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1;
CREATE TABLE "public"."Company" (
"C_ID" integer DEFAULT nextval('"Company_C_ID_seq"') NOT NULL,
"Name" character varying(30) NOT NULL,
"ContactName" character varying(30) NOT NULL,
"ContactEmail" character varying(30) NOT NULL,
"FirstAPI" text NOT NULL,
"TokenAPI" text NOT NULL,
CONSTRAINT "Company_pkey" PRIMARY KEY ("C_ID")
)
WITH (oids = false);
DROP TABLE IF EXISTS "User";
DROP SEQUENCE IF EXISTS "User_id_seq";
CREATE SEQUENCE "User_id_seq" INCREMENT 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1;
CREATE TABLE "public"."User" (
"id" integer DEFAULT nextval('"User_id_seq"') NOT NULL,
"UName" character varying(30) NOT NULL,
"PSWD" character varying(30) NOT NULL,
"RegDate" timestamp NOT NULL,
"Email" character varying(40) NOT NULL,
"Name" character varying(30) NOT NULL,
"AUTH" boolean DEFAULT false NOT NULL,
"REG_CODE" integer NOT NULL,
"C_Id" integer NOT NULL,
"C_Token" character varying(60) NOT NULL,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
)
WITH (oids = false);
ALTER TABLE ONLY "public"."User" ADD CONSTRAINT "User_C_Id_fkey" FOREIGN KEY ("C_Id") REFERENCES "Company"("C_ID") ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE;
ALTER TABLE ONLY "public"."User" ADD CONSTRAINT "User_REG_CODE_fkey" FOREIGN KEY ("REG_CODE") REFERENCES "Code"("CODE_ID") ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE;
-- 2025-05-20 22:16:50 UTC
+13 -10
View File
@@ -10,18 +10,21 @@ COPY SerpentRace_Frontend/ ./
RUN npm run build
################ BACKEND BUILD #################
FROM base AS backend-build
WORKDIR /usr/local/app/backend
COPY SerpentRace_Backend/package.json SerpentRace_Backend/package-lock.json ./
RUN npm ci
COPY SerpentRace_Backend/ ./
# Copy frontend build output to backend static directory
COPY --from=client-build /usr/local/app/frontend/dist ./src/static
# FROM base AS backend-build
# WORKDIR /usr/local/app/backend
# COPY SerpentRace_Backend/package.json SerpentRace_Backend/package-lock.json ./
# RUN npm ci
# COPY SerpentRace_Backend/ ./
# # Copy frontend build output to backend static directory
# COPY --from=client-build /usr/local/app/frontend/dist ./src/static
# generate js files from ts files
RUN npm run build
################ PRODUCTION IMAGE #################
FROM backend-build AS prod
WORKDIR /usr/local/app/backend
ENV NODE_ENV=production
RUN npm ci --only=production
ENV NODE_ENV=development
RUN npm ci
EXPOSE 3000
CMD ["node", "src/index.mjs"]
CMD ["node", "dist/index.js"]
+48 -38
View File
@@ -7,32 +7,34 @@ services:
volumes:
- /var/run/docker.sock:/var/run/docker.sock
backend:
build:
context: ..
dockerfile: SerpentRace_Docker/Dockerfile
target: prod
environment:
POSTGRES_HOST: db
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB_1}
DB_1: ${POSTGRES_DB_2}
DB_2: ${POSTGRES_DB_3}
DB_3: ${POSTGRES_DB_4}
command: ["npx", "nodemon", "src/index.mjs"]
develop:
watch:
- path: ../SerpentRace_Backend/src
action: sync
target: /usr/local/app/backend/src
- path: ../SerpentRace_Backend/package.json
action: rebuild
labels:
traefik.http.routers.backend.rule: Host(`localhost`) && PathPrefix(`/api`)
traefik.http.services.backend.loadbalancer.server.port: 3000
depends_on:
- db
# backend:
# build:
# context: ..
# dockerfile: SerpentRace_Docker/Dockerfile
# target: prod
# environment:
# DATABASE_URL_1: "mysql://${DB_1}:${DB_PSWD_1}@db:3306/${DB_1}"
# DATABASE_URL_2: "mysql://${DB_2}:${DB_PSWD_2}@db:3306/${DB_2}"
# DATABASE_URL_3: "mysql://${DB_3}:${DB_PSWD_3}@db:3306/${DB_3}"
# DATABASE_URL_4: "mysql://${DB_4}:${DB_PSWD_4}@db:3306/${DB_4}"
# command: ["npx", "nodemon", "src/index.ts", "--watch", "src", "--ext", "ts"]
# develop:
# watch:
# - path: ../SerpentRace_Backend/src
# action: sync
# target: /usr/local/app/backend/src
# - path: ../SerpentRace_Backend/prisma
# action: sync
# target: /usr/local/app/backend/prisma
# actions:
# - migrate:all
# - path: ../SerpentRace_Backend/package.json
# action: rebuild
# labels:
# traefik.http.routers.backend.rule: Host(`api.localhost`)
# traefik.http.services.backend.loadbalancer.server.port: 3000
# depends_on:
# - db
frontend:
build:
@@ -53,25 +55,33 @@ services:
traefik.http.services.frontend.loadbalancer.server.port: 5173
db:
image: postgres
image: mariadb:latest
restart: always
shm_size: 128mb
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB_1}
DB_1: ${POSTGRES_DB_2}
DB_2: ${POSTGRES_DB_3}
DB_3: ${POSTGRES_DB_4}
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
MYSQL_DATABASE: ${DB_1}
MYSQL_USER: ${Default_User}
MYSQL_PASSWORD: ${Default_Password}
DB_1: ${DB_1}
DB_2: ${DB_2}
DB_3: ${DB_3}
DB_4: ${DB_4}
PSWD_1: ${DB_PSWD_1}
PSWD_2: ${DB_PSWD_2}
PSWD_3: ${DB_PSWD_3}
PSWD_4: ${DB_PSWD_4}
volumes:
- ./init-multi-db.sh:/docker-entrypoint-initdb.d/init-multi-db.sh:ro
- ./Default.sql:/docker-entrypoint-initdb.d/Default.sql:ro
- ./init-db-users.sh:/docker-entrypoint-initdb.d/init-db-users.sh:ro
- db_data:/var/lib/mysql
adminer:
image: adminer
restart: always
ports:
- "8080:8080"
labels:
traefik.http.routers.adminer.rule: Host(`db.localhost`)
traefik.http.services.adminer.loadbalancer.server.port: 8080
traefik.http.services.adminer.loadbalancer.server.port: 8080
volumes:
db_data:
+17
View File
@@ -0,0 +1,17 @@
#!/bin/bash
set -e
mariadb -uroot -p"$MYSQL_ROOT_PASSWORD" <<EOSQL
CREATE USER IF NOT EXISTS '$DB_1'@'%' IDENTIFIED BY '$PSWD_1';
GRANT ALL PRIVILEGES ON \`$DB_1\`.* TO '$DB_1'@'%';
CREATE USER IF NOT EXISTS '$DB_2'@'%' IDENTIFIED BY '$PSWD_2';
GRANT ALL PRIVILEGES ON \`$DB_2\`.* TO '$DB_2'@'%';
CREATE USER IF NOT EXISTS '$DB_3'@'%' IDENTIFIED BY '$PSWD_3';
GRANT ALL PRIVILEGES ON \`$DB_3\`.* TO '$DB_3'@'%';
CREATE USER IF NOT EXISTS '$DB_4'@'%' IDENTIFIED BY '$PSWD_4';
GRANT ALL PRIVILEGES ON \`$DB_4\`.* TO '$DB_4'@'%';
FLUSH PRIVILEGES;
EOSQL
+5 -4
View File
@@ -1,6 +1,7 @@
#!/bin/bash
set -e
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
CREATE DATABASE "${DB_1}";
CREATE DATABASE "${DB_2}";
CREATE DATABASE "${DB_3}";
mariadb -uroot -p"$MYSQL_ROOT_PASSWORD" <<EOSQL
CREATE DATABASE IF NOT EXISTS \`${DB_2}\`;
CREATE DATABASE IF NOT EXISTS \`${DB_3}\`;
CREATE DATABASE IF NOT EXISTS \`${DB_4}\`;
EOSQL
+6
View File
@@ -0,0 +1,6 @@
{
"name": "SzeSnake",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}