Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version 1.4.0 #37

Merged
merged 11 commits into from
Jan 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 40 additions & 38 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,38 +1,40 @@
NODE_ENV = development
PORT = 3001
TZ=Europe/Amsterdam

TYPEORM_CONNECTION = mysql
TYPEORM_HOST = localhost
TYPEORM_PORT = 5432
TYPEORM_DATABASE = crm
TYPEORM_USERNAME = parelpracht
TYPEORM_PASSWORD =
TYPEORM_SYNCHRONIZE = true
TYPEORM_LOGGING = false
TYPEORM_MIGRATIONS = dist/migration/**/*.js
TYPEORM_SUBSCRIBERS = dist/subscriber/**/*.js

SESSION_SECRET = secret

MAIL_HOST =
MAIL_PORT = 465
MAIL_USER =
MAIL_PASSWORD =
MAIL_FROM =

SERVER_HOST = http://localhost:3000

# If these environment variables are left empty, LDAP will be disabled
LDAP_URL =
LDAP_BINDDN =
LDAP_BINDCREDENTIALS =
LDAP_SEARCHBASE =
LDAP_SEARCHFILTER =

# The following environment variables are used by the DirectMail plugin
# This is optional and thus these variables can be removed (if not used)
DIRECTMAIL_URL=
DIRECTMAIL_USERNAME=parelpracht
DIRECTMAIL_PASSWORD=
DIRECTMAIL_PRODUCT_ID=
NODE_ENV = development
PORT = 3001
TZ=Europe/Amsterdam

TYPEORM_CONNECTION = mysql
TYPEORM_HOST = 127.0.0.1
TYPEORM_PORT = 3306
TYPEORM_DATABASE = parelpracht
TYPEORM_USERNAME = root
TYPEORM_PASSWORD = parelpracht
TYPEORM_SYNCHRONIZE = true
TYPEORM_LOGGING = false
TYPEORM_MIGRATIONS = dist/migration/**/*.js
TYPEORM_SUBSCRIBERS = dist/subscriber/**/*.js
TYPEORM_SSL_ENABLED = false
TYPEORM_SSL_CACERTS = /etc/ssl/certs/ca-certificates.crt

SESSION_SECRET = secret

MAIL_HOST =
MAIL_PORT = 465
MAIL_USER =
MAIL_PASSWORD =
MAIL_FROM =

SERVER_HOST = http://localhost:3000

# If these environment variables are left empty, LDAP will be disabled
LDAP_URL =
LDAP_BINDDN =
LDAP_BINDCREDENTIALS =
LDAP_SEARCHBASE =
LDAP_SEARCHFILTER =

# The following environment variables are used by the DirectMail plugin
# This is optional and thus these variables can be removed (if not used)
DIRECTMAIL_URL=
DIRECTMAIL_USERNAME=parelpracht
DIRECTMAIL_PASSWORD=
DIRECTMAIL_PRODUCT_ID=
3 changes: 2 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module.exports = {
plugins: ['@typescript-eslint', 'import'],
extends: ['airbnb-typescript/base'],
rules: {
'linebreak-style': ['error', 'windows'],
'linebreak-style': ['error', 'unix'],
'lines-between-class-members': [
'error',
'always',
Expand All @@ -20,6 +20,7 @@ module.exports = {
'arrow-body-style': 'off',
'import/no-cycle': 'off',
'indent': 'off',
'@typescript-eslint/no-redeclare': 'off',
'@typescript-eslint/indent': [
'error',
2,
Expand Down
92 changes: 48 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,44 +1,48 @@
<h1 style="text-align: center">
<img alt="" src="https://raw.githubusercontent.com/GEWIS/parelpracht-client/develop/public/ParelPracht-blacksvg.svg?raw=true" style="width: 25%">
<br>
ParelPracht
</h1>

ParelPracht is the successor of Goudglans, the custom Customer Relation Management system of Study Association GEWIS.
This new system is built during the second lockdown of the corona pandemic.
Its main goal is to automate tedious tasks and to keep a clear and concise overview of the current collaborations.
This is achieved by creating nice structured insights tables and graphs and automating the generation of contracts, proposals and invoices.

This is the back-end of ParelPracht. [The front-end can be found here](https://github.com/GEWIS/parelpracht-client).

## Installation
1. Clone the repository.
2. Run `npm install`.
3. Copy `.env.example` to `.env` and fill / replace the keys with their corresponding values. Note that the email-keys
are important to be able to install the application (see step 5).
4. Run `npm run dev`. This runs the client in development mode. Node will bind to port `3001`. You can find the API
documentation at [http://localhost:3001/api/swagger-ui/](http://localhost:3001/api/swagger-ui/).
5. Make a POST-request to `/v1/setup` with your credentials. The required payload can be found in the Swagger
documentation. This request will create a local administrator account with the given credentials. You will receive an
email (on the given address via the given mail server at step 3) to set your password.
6. In the `VAT` table, add the desired VAT categories and percentages.

You can also build the application with `npm run build`. This puts a production build in the `./build` directory.

## Deployment
1. Clone the repository in a folder called `parelpracht-client` and clone the backend repository in a folder called `parelpracht-server`. Make sure that both folders are in the same parent folder.
2. Change the image locations to the correct locations in `docker-compose.yml` (for both the frontend and backend).
3. Fill in the correct (environment) variables in `docker-compose.yml`.
4. Run `docker-compose` in `./parelpracht-client`.

## Copyright

Copyright © 2022 The 39th board of GEWIS - Some rights reserved. Created by Roy Kakkenberg, Koen de Nooij, Jealy van den
Aker, Max Opperman, Wouter van der Heijden en Irne Verwijst. You can use our software freely within the limits of
our license. However, we worked very hard on this project and invested a lot of time in it, so we ask you to leave our
copyright mark in place when modifying our software. Of course, you are free to add your own.

## License
[GNU AGPLv3](./LICENSE)


<h1 style="text-align: center">
<img alt="" src="https://raw.githubusercontent.com/GEWIS/parelpracht-client/develop/public/ParelPracht-blacksvg.svg?raw=true" style="width: 25%">
<br>
ParelPracht
</h1>

ParelPracht is the successor of Goudglans, the custom Customer Relation
Management system of Study Association GEWIS. This new system is built during
the second lockdown of the corona pandemic. Its main goal is to automate tedious
tasks and to keep a clear and concise overview of the current collaborations.
This is achieved by creating nice structured insights tables and graphs and
automating the generation of contracts, proposals and invoices.

This is the back-end of ParelPracht. The front-end can be found [here](https://github.com/GEWIS/parelpracht-client).

## Development
1. Clone the repository with `git clone [email protected]:GEWIS/parelpracht-server`
2. Install the dependencies with `npm install`.
3. Copy `.env.example` to `.env` and add the remaining environment variables.
4. Start the application with `npm run dev`

It is suggested to use a local MariaDB instance. If you do not have a local
instance, you can use the docker compose file: `docker compose -f
docker-compose-mariadb.yaml up -d`. The environment variables in the
`.env.example` are adjusted to use this container configuration.

## Setup
When running the application, you will first need to create a superuser. This is
done with the `/setup` endpoint.

1. Go to the [swagger docs](http://localhost:3001/api/swagger-ui/).
2. Navigate to `/setup` endpoint, and fill out the data for the request.
3. Check the console for the confirmation link.

Note: the confirmation link will only be logged in development mode. In
production, an actual mail will be send with the confirmation link to the
indicated email address.

## Copyright
Copyright © 2022 The 39th board of GEWIS - Some rights reserved. Created by Roy
Kakkenberg, Koen de Nooij, Jealy van den Aker, Max Opperman, Wouter van der
Heijden en Irne Verwijst. You can use our software freely within the limits of
our license. However, we worked very hard on this project and invested a lot of
time in it, so we ask you to leave our copyright mark in place when modifying
our software. Of course, you are free to add your own.

## License
[GNU AGPLv3](./LICENSE)
10 changes: 10 additions & 0 deletions docker-compose-mariadb.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
services:
mariadb:
image: mariadb:latest
container_name: mariadb
environment:
MARIADB_ROOT_PASSWORD: parelpracht
MARIADB_DATABASE: parelpracht
ports:
- "3306:3306"

2 changes: 1 addition & 1 deletion src/controllers/CompanyController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export class CompanyController extends Controller {
@Security('local', ['ADMIN'])
@Response<WrappedApiError>(401)
public async deleteCompany(
id: number, @Request() req: express.Request,
id: number,
): Promise<void> {
return new CompanyService().deleteCompany(id);
}
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/ContactController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export class ContactController extends Controller {
@Security('local', ['GENERAL', 'ADMIN'])
@Response<WrappedApiError>(401)
public async deleteContact(
id: number, @Request() req: express.Request,
id: number,
): Promise<void> {
return new ContactService().deleteContact(id);
}
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/ContractController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ export class ContractController extends Controller {
@Security('local', ['GENERAL', 'ADMIN'])
@Response<WrappedApiError>(401)
public async deleteContract(
id: number, @Request() req: express.Request,
id: number,
): Promise<void> {
return new ContractService().deleteContract(id);
}
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/InvoiceController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ export class InvoiceController extends Controller {
@Security('local', ['GENERAL', 'ADMIN'])
@Response<WrappedApiError>(401)
public async deleteInvoice(
id: number, @Request() req: express.Request,
id: number,
): Promise<void> {
return new InvoiceService().deleteInvoice(id);
}
Expand Down
4 changes: 2 additions & 2 deletions src/controllers/ProductCategoryController.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
Body, Controller, Delete, Get, Post, Put, Query, Request, Response, Route, Security, Tags,
Body, Controller, Delete, Get, Post, Put, Request, Response, Route, Security, Tags,
} from 'tsoa';
import express from 'express';
import { body } from 'express-validator';
Expand Down Expand Up @@ -98,7 +98,7 @@ export class ProductCategoryController extends Controller {
@Security('local', ['ADMIN'])
@Response<WrappedApiError>(401)
public async deleteCategory(
id: number, @Request() req: express.Request,
id: number,
): Promise<void> {
return new ProductCategoryService().deleteCategory(id);
}
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/ProductController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ export class ProductController extends Controller {
@Security('local', ['GENERAL', 'ADMIN'])
@Response<WrappedApiError>(401)
public async deleteProduct(
id: number, @Request() req: express.Request,
id: number,
): Promise<void> {
return new ProductService().deleteProduct(id);
}
Expand Down
18 changes: 11 additions & 7 deletions src/controllers/RootController.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import express from 'express';
import {
Body,
Controller, Get, Post, Query, Request, Response, Route, Security,
} from 'tsoa';
import { Body, Controller, Get, Post, Query, Request, Response, Route, Security } from 'tsoa';
import { body } from 'express-validator';
import { WrappedApiError } from '../helpers/error';
import { validate } from '../helpers/validation';
Expand All @@ -23,13 +20,18 @@ export interface GeneralPrivateInfo {

export interface GeneralPublicInfo {
loginMethod: LoginMethods;
setupDone: boolean,
}

@Route('')
export class RootController extends Controller {
@Post('setup')
public async postSetup(@Body() params: SetupParams): Promise<void> {
return new ServerSettingsService().initialSetup(params);
public async postSetup(@Body() params: SetupParams, @Request() req: express.Request): Promise<void> {
const user = await new ServerSettingsService().initialSetup(params);
if (user === undefined) {
return;
}
await new AuthService().login(user, req);
}

@Get('authStatus')
Expand Down Expand Up @@ -105,16 +107,18 @@ export class RootController extends Controller {

@Get('getPublicGeneralInfo')
@Response<WrappedApiError>(400)
public getPublicGeneralInfo(): GeneralPublicInfo {
public async getPublicGeneralInfo(): Promise<GeneralPublicInfo> {
let loginMethod: LoginMethods;
if (ldapEnabled()) {
loginMethod = 'ldap';
} else {
loginMethod = 'local';
}

const setupDone: boolean = (await new ServerSettingsService().getSetting('SETUP_DONE'))?.value === 'true';
return {
loginMethod,
setupDone,
};
}
}
2 changes: 1 addition & 1 deletion src/controllers/VATController.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
Body, Controller, Get, Post, Put, Query, Request, Response, Route, Security, Tags,
Body, Controller, Get, Post, Put, Request, Response, Route, Security, Tags,
} from 'tsoa';
import express from 'express';
import { body } from 'express-validator';
Expand Down
8 changes: 8 additions & 0 deletions src/database.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DataSource } from 'typeorm';
import fs from 'fs';
import { Company } from './entity/Company';
import { Contact } from './entity/Contact';
import { Contract } from './entity/Contract';
Expand Down Expand Up @@ -32,6 +33,13 @@ const AppDataSource = new DataSource({
type: process.env.TYPEORM_CONNECTION as 'postgres' | 'mariadb' | 'mysql',
username: process.env.TYPEORM_USERNAME,
password: process.env.TYPEORM_PASSWORD,
...(process.env.TYPEORM_SSL_ENABLED === 'true' && process.env.TYPEORM_SSL_CACERTS
? {
ssl: {
ca: fs.readFileSync(process.env.TYPEORM_SSL_CACERTS),
},
}
: {}),
synchronize: process.env.TYPEORM_SYNCHRONIZE === 'true',
logging: process.env.TYPEORM_LOGGING === 'true',
entities: [Company, Contact, Contract, IdentityApiKey, IdentityLDAP, IdentityLocal, Invoice, Product,
Expand Down
4 changes: 2 additions & 2 deletions src/entity/activity/ContractActivity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class ContractActivity extends BaseActivity {
/** Contract related to this activity */
@ManyToOne(() => Contract, (contract) => contract.activities, {
onDelete: 'CASCADE',
})
})
@JoinColumn({ name: 'contractId' })
contract!: Contract;

Expand All @@ -25,7 +25,7 @@ export class ContractActivity extends BaseActivity {
enum: ContractStatus,
nullable: true,
update: false,
})
})
subType!: ContractStatus | null;

getRelatedEntity(): BaseEnt {
Expand Down
4 changes: 2 additions & 2 deletions src/entity/activity/InvoiceActivity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class InvoiceActivity extends BaseActivity {
/** Invoice related to this activity */
@ManyToOne(() => Invoice, (invoice) => invoice.activities, {
onDelete: 'CASCADE',
})
})
@JoinColumn({ name: 'invoiceId' })
invoice!: Invoice;

Expand All @@ -27,7 +27,7 @@ export class InvoiceActivity extends BaseActivity {
enum: InvoiceStatus,
nullable: true,
update: false,
})
})
subType!: InvoiceStatus | null;

getRelatedEntity(): BaseEnt {
Expand Down
4 changes: 2 additions & 2 deletions src/entity/activity/ProductInstanceActivity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class ProductInstanceActivity extends BaseActivity {
/** ProductInstance related to this activity */
@ManyToOne(() => ProductInstance, (productInstance) => productInstance.activities, {
onDelete: 'CASCADE',
})
})
@JoinColumn({ name: 'productInstanceId' })
productInstance!: ProductInstance;

Expand All @@ -27,7 +27,7 @@ export class ProductInstanceActivity extends BaseActivity {
enum: ProductInstanceStatus,
nullable: true,
update: false,
})
})
subType!: ProductInstanceStatus | null;

getRelatedEntity(): BaseEnt {
Expand Down
Loading
Loading