Compare commits
5 Commits
19ca0c086c
...
feature/e2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c169faf288 | ||
|
|
b599e79273 | ||
|
|
45a69eea8a | ||
|
|
2f54770720 | ||
|
|
bdf16a3189 |
6
.env.e2e
Normal file
6
.env.e2e
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
DATABASE_USER=test
|
||||||
|
DATABASE_PASS=test
|
||||||
|
DATABASE_HOST=localhost
|
||||||
|
DATABASE_NAME=test
|
||||||
|
DATABASE_PORT=4401
|
||||||
|
JWT_SECRET="secret"
|
||||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -1,7 +1,7 @@
|
|||||||
# compiled output
|
# compiled output
|
||||||
dist
|
/dist
|
||||||
node_modules
|
/node_modules
|
||||||
build
|
/build
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
@@ -16,11 +16,11 @@ lerna-debug.log*
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
# Tests
|
# Tests
|
||||||
coverage
|
/coverage
|
||||||
.nyc_output
|
/.nyc_output
|
||||||
|
|
||||||
# IDEs and editors
|
# IDEs and editors
|
||||||
.idea
|
/.idea
|
||||||
.project
|
.project
|
||||||
.classpath
|
.classpath
|
||||||
.c9/
|
.c9/
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
# Editor configuration, see https://editorconfig.org
|
|
||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
charset = utf-8
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 2
|
|
||||||
insert_final_newline = true
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
|
|
||||||
[*.ts]
|
|
||||||
quote_type = single
|
|
||||||
ij_typescript_use_double_quotes = false
|
|
||||||
|
|
||||||
[*.md]
|
|
||||||
max_line_length = off
|
|
||||||
trim_trailing_whitespace = false
|
|
||||||
43
admin/.gitignore
vendored
43
admin/.gitignore
vendored
@@ -1,43 +0,0 @@
|
|||||||
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
|
|
||||||
|
|
||||||
# Compiled output
|
|
||||||
/dist
|
|
||||||
/tmp
|
|
||||||
/out-tsc
|
|
||||||
/bazel-out
|
|
||||||
|
|
||||||
# Node
|
|
||||||
/node_modules
|
|
||||||
npm-debug.log
|
|
||||||
yarn-error.log
|
|
||||||
|
|
||||||
# IDEs and editors
|
|
||||||
.idea/
|
|
||||||
.project
|
|
||||||
.classpath
|
|
||||||
.c9/
|
|
||||||
*.launch
|
|
||||||
.settings/
|
|
||||||
*.sublime-workspace
|
|
||||||
|
|
||||||
# Visual Studio Code
|
|
||||||
.vscode/*
|
|
||||||
!.vscode/settings.json
|
|
||||||
!.vscode/tasks.json
|
|
||||||
!.vscode/launch.json
|
|
||||||
!.vscode/extensions.json
|
|
||||||
.history/*
|
|
||||||
|
|
||||||
# Miscellaneous
|
|
||||||
/.angular/cache
|
|
||||||
.sass-cache/
|
|
||||||
/connect.lock
|
|
||||||
/coverage
|
|
||||||
/libpeerconnection.log
|
|
||||||
testem.log
|
|
||||||
/typings
|
|
||||||
__screenshots__/
|
|
||||||
|
|
||||||
# System files
|
|
||||||
.DS_Store
|
|
||||||
Thumbs.db
|
|
||||||
4
admin/.vscode/extensions.json
vendored
4
admin/.vscode/extensions.json
vendored
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
|
|
||||||
"recommendations": ["angular.ng-template"]
|
|
||||||
}
|
|
||||||
20
admin/.vscode/launch.json
vendored
20
admin/.vscode/launch.json
vendored
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"name": "ng serve",
|
|
||||||
"type": "chrome",
|
|
||||||
"request": "launch",
|
|
||||||
"preLaunchTask": "npm: start",
|
|
||||||
"url": "http://localhost:4200/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ng test",
|
|
||||||
"type": "chrome",
|
|
||||||
"request": "launch",
|
|
||||||
"preLaunchTask": "npm: test",
|
|
||||||
"url": "http://localhost:9876/debug.html"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
42
admin/.vscode/tasks.json
vendored
42
admin/.vscode/tasks.json
vendored
@@ -1,42 +0,0 @@
|
|||||||
{
|
|
||||||
// For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
|
|
||||||
"version": "2.0.0",
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"type": "npm",
|
|
||||||
"script": "start",
|
|
||||||
"isBackground": true,
|
|
||||||
"problemMatcher": {
|
|
||||||
"owner": "typescript",
|
|
||||||
"pattern": "$tsc",
|
|
||||||
"background": {
|
|
||||||
"activeOnStart": true,
|
|
||||||
"beginsPattern": {
|
|
||||||
"regexp": "(.*?)"
|
|
||||||
},
|
|
||||||
"endsPattern": {
|
|
||||||
"regexp": "bundle generation complete"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "npm",
|
|
||||||
"script": "test",
|
|
||||||
"isBackground": true,
|
|
||||||
"problemMatcher": {
|
|
||||||
"owner": "typescript",
|
|
||||||
"pattern": "$tsc",
|
|
||||||
"background": {
|
|
||||||
"activeOnStart": true,
|
|
||||||
"beginsPattern": {
|
|
||||||
"regexp": "(.*?)"
|
|
||||||
},
|
|
||||||
"endsPattern": {
|
|
||||||
"regexp": "bundle generation complete"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
# Admin
|
|
||||||
|
|
||||||
This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 20.3.9.
|
|
||||||
|
|
||||||
## Development server
|
|
||||||
|
|
||||||
To start a local development server, run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ng serve
|
|
||||||
```
|
|
||||||
|
|
||||||
Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files.
|
|
||||||
|
|
||||||
## Code scaffolding
|
|
||||||
|
|
||||||
Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ng generate component component-name
|
|
||||||
```
|
|
||||||
|
|
||||||
For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ng generate --help
|
|
||||||
```
|
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
To build the project run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ng build
|
|
||||||
```
|
|
||||||
|
|
||||||
This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed.
|
|
||||||
|
|
||||||
## Running unit tests
|
|
||||||
|
|
||||||
To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ng test
|
|
||||||
```
|
|
||||||
|
|
||||||
## Running end-to-end tests
|
|
||||||
|
|
||||||
For end-to-end (e2e) testing, run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ng e2e
|
|
||||||
```
|
|
||||||
|
|
||||||
Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
|
|
||||||
|
|
||||||
## Additional Resources
|
|
||||||
|
|
||||||
For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
|
||||||
"version": 1,
|
|
||||||
"newProjectRoot": "projects",
|
|
||||||
"projects": {
|
|
||||||
"admin": {
|
|
||||||
"projectType": "application",
|
|
||||||
"schematics": {},
|
|
||||||
"root": "",
|
|
||||||
"sourceRoot": "src",
|
|
||||||
"prefix": "app",
|
|
||||||
"architect": {
|
|
||||||
"build": {
|
|
||||||
"builder": "@angular/build:application",
|
|
||||||
"options": {
|
|
||||||
"browser": "src/main.ts",
|
|
||||||
"tsConfig": "tsconfig.app.json",
|
|
||||||
"assets": [
|
|
||||||
{
|
|
||||||
"glob": "**/*",
|
|
||||||
"input": "public"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"styles": [
|
|
||||||
"src/styles.css"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"configurations": {
|
|
||||||
"production": {
|
|
||||||
"budgets": [
|
|
||||||
{
|
|
||||||
"type": "initial",
|
|
||||||
"maximumWarning": "500kB",
|
|
||||||
"maximumError": "1MB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "anyComponentStyle",
|
|
||||||
"maximumWarning": "4kB",
|
|
||||||
"maximumError": "8kB"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputHashing": "all"
|
|
||||||
},
|
|
||||||
"development": {
|
|
||||||
"optimization": false,
|
|
||||||
"extractLicenses": false,
|
|
||||||
"sourceMap": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"defaultConfiguration": "production"
|
|
||||||
},
|
|
||||||
"serve": {
|
|
||||||
"builder": "@angular/build:dev-server",
|
|
||||||
"configurations": {
|
|
||||||
"production": {
|
|
||||||
"buildTarget": "admin:build:production"
|
|
||||||
},
|
|
||||||
"development": {
|
|
||||||
"buildTarget": "admin:build:development"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"defaultConfiguration": "development"
|
|
||||||
},
|
|
||||||
"extract-i18n": {
|
|
||||||
"builder": "@angular/build:extract-i18n"
|
|
||||||
},
|
|
||||||
"test": {
|
|
||||||
"builder": "@angular/build:karma",
|
|
||||||
"options": {
|
|
||||||
"tsConfig": "tsconfig.spec.json",
|
|
||||||
"assets": [
|
|
||||||
{
|
|
||||||
"glob": "**/*",
|
|
||||||
"input": "public"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"styles": [
|
|
||||||
"src/styles.css"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
9652
admin/package-lock.json
generated
9652
admin/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,47 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "admin",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"scripts": {
|
|
||||||
"ng": "ng",
|
|
||||||
"start": "ng serve --proxy-config proxy.conf.json",
|
|
||||||
"build": "ng build",
|
|
||||||
"watch": "ng build --watch --configuration development",
|
|
||||||
"test": "ng test"
|
|
||||||
},
|
|
||||||
"prettier": {
|
|
||||||
"printWidth": 100,
|
|
||||||
"singleQuote": true,
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": "*.html",
|
|
||||||
"options": {
|
|
||||||
"parser": "angular"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"private": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@angular/common": "^20.3.0",
|
|
||||||
"@angular/compiler": "^20.3.0",
|
|
||||||
"@angular/core": "^20.3.0",
|
|
||||||
"@angular/forms": "^20.3.0",
|
|
||||||
"@angular/platform-browser": "^20.3.0",
|
|
||||||
"@angular/router": "^20.3.0",
|
|
||||||
"rxjs": "~7.8.0",
|
|
||||||
"tslib": "^2.3.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@angular/build": "^20.3.9",
|
|
||||||
"@angular/cli": "^20.3.9",
|
|
||||||
"@angular/compiler-cli": "^20.3.0",
|
|
||||||
"@types/jasmine": "~5.1.0",
|
|
||||||
"jasmine-core": "~5.9.0",
|
|
||||||
"karma": "~6.4.0",
|
|
||||||
"karma-chrome-launcher": "~3.2.0",
|
|
||||||
"karma-coverage": "~2.2.0",
|
|
||||||
"karma-jasmine": "~5.1.0",
|
|
||||||
"karma-jasmine-html-reporter": "~2.1.0",
|
|
||||||
"typescript": "~5.9.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"/api": {
|
|
||||||
"target": "http://localhost:3000",
|
|
||||||
"secure": false,
|
|
||||||
"logLevel": "debug",
|
|
||||||
"changeOrigin": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,23 +0,0 @@
|
|||||||
import {
|
|
||||||
ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection,
|
|
||||||
provideZonelessChangeDetection,
|
|
||||||
} from '@angular/core';
|
|
||||||
import { provideRouter } from '@angular/router';
|
|
||||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
|
|
||||||
import { HTTP_INTERCEPTORS } from '@angular/common/http';
|
|
||||||
import { routes } from './app.routes';
|
|
||||||
import { JwtInterceptor } from './auth/jwt.interceptor';
|
|
||||||
import { AuthService } from './auth/auth.service';
|
|
||||||
import { AuthGuard } from './auth/auth.guard';
|
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
|
||||||
providers: [
|
|
||||||
provideBrowserGlobalErrorListeners(),
|
|
||||||
provideZonelessChangeDetection(),
|
|
||||||
provideRouter(routes),
|
|
||||||
provideHttpClient(withInterceptorsFromDi()),
|
|
||||||
AuthService,
|
|
||||||
AuthGuard,
|
|
||||||
{ provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<div>
|
|
||||||
<button (click)="logout()">Logout</button>
|
|
||||||
</div>
|
|
||||||
<app-main-menu></app-main-menu>
|
|
||||||
<router-outlet />
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import { Routes } from '@angular/router';
|
|
||||||
import { LoginComponent } from './components/login/login.component';
|
|
||||||
import { AuthGuard } from './auth/auth.guard';
|
|
||||||
import { HomeComponent } from './components/home/home.component'; // Assuming you have a HomeComponent
|
|
||||||
|
|
||||||
export const routes: Routes = [
|
|
||||||
{ path: 'login', component: LoginComponent },
|
|
||||||
{ path: '', component: HomeComponent, canActivate: [AuthGuard] },
|
|
||||||
{ path: '**', redirectTo: '' } // Redirect to home for any other route
|
|
||||||
];
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { provideZonelessChangeDetection } from '@angular/core';
|
|
||||||
import { TestBed } from '@angular/core/testing';
|
|
||||||
import { App } from './app';
|
|
||||||
|
|
||||||
describe('App', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [App],
|
|
||||||
providers: [provideZonelessChangeDetection()]
|
|
||||||
}).compileComponents();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create the app', () => {
|
|
||||||
const fixture = TestBed.createComponent(App);
|
|
||||||
const app = fixture.componentInstance;
|
|
||||||
expect(app).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render title', () => {
|
|
||||||
const fixture = TestBed.createComponent(App);
|
|
||||||
fixture.detectChanges();
|
|
||||||
const compiled = fixture.nativeElement as HTMLElement;
|
|
||||||
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, admin');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import { Component, signal } from '@angular/core';
|
|
||||||
import { Router, RouterOutlet } from '@angular/router';
|
|
||||||
import { MainMenu } from './components/main-menu/main-menu';
|
|
||||||
import { AuthService } from './auth/auth.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-root',
|
|
||||||
imports: [RouterOutlet, MainMenu],
|
|
||||||
templateUrl: './app.html',
|
|
||||||
styleUrl: './app.css'
|
|
||||||
})
|
|
||||||
export class App {
|
|
||||||
protected readonly title = signal('admin');
|
|
||||||
|
|
||||||
constructor(private authService: AuthService, private router: Router) {}
|
|
||||||
|
|
||||||
logout(): void {
|
|
||||||
this.authService.logout();
|
|
||||||
this.router.navigate(['/login']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { AuthService } from './auth.service';
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root',
|
|
||||||
})
|
|
||||||
export class AuthGuard implements CanActivate {
|
|
||||||
constructor(private authService: AuthService, private router: Router) {}
|
|
||||||
|
|
||||||
canActivate(
|
|
||||||
route: ActivatedRouteSnapshot,
|
|
||||||
state: RouterStateSnapshot
|
|
||||||
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
|
|
||||||
if (this.authService.isLoggedIn()) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
// Redirect to the login page
|
|
||||||
return this.router.createUrlTree(['/login']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { HttpClient } from '@angular/common/http';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { tap } from 'rxjs/operators';
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root',
|
|
||||||
})
|
|
||||||
export class AuthService {
|
|
||||||
private readonly TOKEN_KEY = 'access_token';
|
|
||||||
private apiUrl = 'http://localhost:4200/api/auth'; // Adjust if your server URL is different
|
|
||||||
|
|
||||||
constructor(private http: HttpClient) {}
|
|
||||||
|
|
||||||
login(credentials: { username: string; password: string }): Observable<any> {
|
|
||||||
return this.http.post<{ access_token: string }>(`${this.apiUrl}/login`, credentials).pipe(
|
|
||||||
tap((response) => this.setToken(response.access_token))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
logout(): void {
|
|
||||||
localStorage.removeItem(this.TOKEN_KEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
getToken(): string | null {
|
|
||||||
return localStorage.getItem(this.TOKEN_KEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
isLoggedIn(): boolean {
|
|
||||||
return this.getToken() !== null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private setToken(token: string): void {
|
|
||||||
localStorage.setItem(this.TOKEN_KEY, token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import {
|
|
||||||
HttpEvent,
|
|
||||||
HttpHandler,
|
|
||||||
HttpInterceptor,
|
|
||||||
HttpRequest,
|
|
||||||
} from '@angular/common/http';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { AuthService } from './auth.service';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class JwtInterceptor implements HttpInterceptor {
|
|
||||||
constructor(private authService: AuthService) {}
|
|
||||||
|
|
||||||
intercept(
|
|
||||||
request: HttpRequest<any>,
|
|
||||||
next: HttpHandler
|
|
||||||
): Observable<HttpEvent<any>> {
|
|
||||||
const token = this.authService.getToken();
|
|
||||||
if (token) {
|
|
||||||
request = request.clone({
|
|
||||||
setHeaders: {
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return next.handle(request);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-home',
|
|
||||||
template: '<h1>Welcome to the Admin Panel!</h1>',
|
|
||||||
standalone: true,
|
|
||||||
})
|
|
||||||
export class HomeComponent {}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
<div>
|
|
||||||
<h2>Login</h2>
|
|
||||||
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
|
|
||||||
<div>
|
|
||||||
<label for="username">Username:</label>
|
|
||||||
<input id="username" formControlName="username" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="password">Password:</label>
|
|
||||||
<input id="password" type="password" formControlName="password" />
|
|
||||||
</div>
|
|
||||||
<button type="submit" [disabled]="loginForm.invalid">Log In</button>
|
|
||||||
</form>
|
|
||||||
<p *ngIf="errorMessage">{{ errorMessage }}</p>
|
|
||||||
</div>
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
import { AuthService } from '../../auth/auth.service';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-login',
|
|
||||||
templateUrl: './login.component.html',
|
|
||||||
standalone: true,
|
|
||||||
imports: [ReactiveFormsModule, CommonModule],
|
|
||||||
})
|
|
||||||
export class LoginComponent {
|
|
||||||
loginForm: FormGroup;
|
|
||||||
errorMessage: string = '';
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private fb: FormBuilder,
|
|
||||||
private authService: AuthService,
|
|
||||||
private router: Router
|
|
||||||
) {
|
|
||||||
this.loginForm = this.fb.group({
|
|
||||||
username: ['', Validators.required],
|
|
||||||
password: ['', Validators.required],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onSubmit(): void {
|
|
||||||
if (this.loginForm.valid) {
|
|
||||||
this.authService.login(this.loginForm.value).subscribe({
|
|
||||||
next: () => this.router.navigate(['/']),
|
|
||||||
error: (err) => (this.errorMessage = 'Invalid username or password'),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<p>main-menu works!</p>
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { MainMenu } from './main-menu';
|
|
||||||
|
|
||||||
describe('MainMenu', () => {
|
|
||||||
let component: MainMenu;
|
|
||||||
let fixture: ComponentFixture<MainMenu>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [MainMenu]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(MainMenu);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-main-menu',
|
|
||||||
imports: [],
|
|
||||||
templateUrl: './main-menu.html',
|
|
||||||
styleUrl: './main-menu.css',
|
|
||||||
})
|
|
||||||
export class MainMenu {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<div class="navbar bg-base-100 shadow-sm">
|
|
||||||
<div class="flex-1">
|
|
||||||
<a class="btn btn-ghost text-xl">daisyUI</a>
|
|
||||||
</div>
|
|
||||||
<div class="flex-none">
|
|
||||||
<ul class="menu menu-horizontal px-1">
|
|
||||||
<li><a>Link</a></li>
|
|
||||||
<li>
|
|
||||||
<details>
|
|
||||||
<summary>Parent</summary>
|
|
||||||
<ul class="bg-base-100 rounded-t-none p-2">
|
|
||||||
<li><a>Link 1</a></li>
|
|
||||||
<li><a>Link 2</a></li>
|
|
||||||
</ul>
|
|
||||||
</details>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { Navbar } from './navbar';
|
|
||||||
|
|
||||||
describe('Navbar', () => {
|
|
||||||
let component: Navbar;
|
|
||||||
let fixture: ComponentFixture<Navbar>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [Navbar]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(Navbar);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-navbar',
|
|
||||||
imports: [],
|
|
||||||
templateUrl: './navbar.html',
|
|
||||||
styleUrl: './navbar.css',
|
|
||||||
})
|
|
||||||
export class Navbar {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Admin</title>
|
|
||||||
<base href="/">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<app-root></app-root>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { bootstrapApplication } from '@angular/platform-browser';
|
|
||||||
import { appConfig } from './app/app.config';
|
|
||||||
import { App } from './app/app';
|
|
||||||
|
|
||||||
bootstrapApplication(App, appConfig)
|
|
||||||
.catch((err) => console.error(err));
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
/* You can add global styles to this file, and also import other style files */
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
|
||||||
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
|
||||||
{
|
|
||||||
"extends": "./tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "./out-tsc/app",
|
|
||||||
"types": []
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"src/**/*.ts"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"src/**/*.spec.ts"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
|
||||||
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
|
||||||
{
|
|
||||||
"compileOnSave": false,
|
|
||||||
"compilerOptions": {
|
|
||||||
"strict": true,
|
|
||||||
"noImplicitOverride": true,
|
|
||||||
"noPropertyAccessFromIndexSignature": true,
|
|
||||||
"noImplicitReturns": true,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"experimentalDecorators": true,
|
|
||||||
"importHelpers": true,
|
|
||||||
"target": "ES2022",
|
|
||||||
"module": "preserve"
|
|
||||||
},
|
|
||||||
"angularCompilerOptions": {
|
|
||||||
"enableI18nLegacyMessageIdFormat": false,
|
|
||||||
"strictInjectionParameters": true,
|
|
||||||
"strictInputAccessModifiers": true,
|
|
||||||
"typeCheckHostBindings": true,
|
|
||||||
"strictTemplates": true
|
|
||||||
},
|
|
||||||
"files": [],
|
|
||||||
"references": [
|
|
||||||
{
|
|
||||||
"path": "./tsconfig.app.json"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "./tsconfig.spec.json"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
|
||||||
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
|
||||||
{
|
|
||||||
"extends": "./tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "./out-tsc/spec",
|
|
||||||
"types": [
|
|
||||||
"jasmine"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"src/**/*.ts"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -13,3 +13,22 @@ Content-Type: application/json
|
|||||||
GET http://localhost:3000/users
|
GET http://localhost:3000/users
|
||||||
Accept: application/json
|
Accept: application/json
|
||||||
Authorization: Bearer {{auth_token}}
|
Authorization: Bearer {{auth_token}}
|
||||||
|
|
||||||
|
### POST create user
|
||||||
|
POST http://localhost:3000/users
|
||||||
|
Content-Type: application/json
|
||||||
|
Accept: application/json
|
||||||
|
Authorization: Bearer {{auth_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"username": "test1",
|
||||||
|
"password": "123456",
|
||||||
|
"email": "test1@gmail.com",
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"id": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
17
environment/e2e/docker-compose.yaml
Normal file
17
environment/e2e/docker-compose.yaml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
version: '3.8'
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:18
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: test
|
||||||
|
POSTGRES_PASSWORD: test
|
||||||
|
POSTGRES_DB: test
|
||||||
|
ports:
|
||||||
|
- '4401:5432'
|
||||||
|
volumes:
|
||||||
|
- e2epgdata:/var/lib/postgresql
|
||||||
|
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
e2epgdata: {}
|
||||||
1436
server/package-lock.json → package-lock.json
generated
1436
server/package-lock.json → package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -29,7 +29,6 @@
|
|||||||
"@nestjs/jwt": "^11.0.1",
|
"@nestjs/jwt": "^11.0.1",
|
||||||
"@nestjs/passport": "^11.0.5",
|
"@nestjs/passport": "^11.0.5",
|
||||||
"@nestjs/platform-express": "^11.0.1",
|
"@nestjs/platform-express": "^11.0.1",
|
||||||
"@nestjs/swagger": "^11.2.1",
|
|
||||||
"@nestjs/typeorm": "^11.0.0",
|
"@nestjs/typeorm": "^11.0.0",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
@@ -53,6 +52,7 @@
|
|||||||
"@types/node": "^22.10.7",
|
"@types/node": "^22.10.7",
|
||||||
"@types/passport-jwt": "^4.0.0",
|
"@types/passport-jwt": "^4.0.0",
|
||||||
"@types/supertest": "^6.0.2",
|
"@types/supertest": "^6.0.2",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
"eslint": "^9.18.0",
|
"eslint": "^9.18.0",
|
||||||
"eslint-config-prettier": "^10.0.1",
|
"eslint-config-prettier": "^10.0.1",
|
||||||
"eslint-plugin-prettier": "^5.2.2",
|
"eslint-plugin-prettier": "^5.2.2",
|
||||||
@@ -61,6 +61,7 @@
|
|||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"supertest": "^7.0.0",
|
"supertest": "^7.0.0",
|
||||||
|
"testcontainers": "^10.9.0",
|
||||||
"ts-jest": "^29.2.5",
|
"ts-jest": "^29.2.5",
|
||||||
"ts-loader": "^9.5.2",
|
"ts-loader": "^9.5.2",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { ConsoleLogger, Injectable } from '@nestjs/common';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class DvbookingLoggerService extends ConsoleLogger {
|
|
||||||
log(message: string, context?: string) {
|
|
||||||
super.log(message, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
error(message: string, trace?: string, context?: string) {
|
|
||||||
super.error(message, trace, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
warn(message: string, context?: string) {
|
|
||||||
super.warn(message, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
debug(message: string, context?: string) {
|
|
||||||
super.debug(message, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
verbose(message: string, context?: string) {
|
|
||||||
super.verbose(message, context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { Global, Module } from '@nestjs/common';
|
|
||||||
import { DvbookingLoggerService } from './dvbooking-logger.service';
|
|
||||||
|
|
||||||
@Global()
|
|
||||||
@Module({
|
|
||||||
providers: [DvbookingLoggerService],
|
|
||||||
exports: [DvbookingLoggerService],
|
|
||||||
})
|
|
||||||
export class LoggerModule {}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import { NestFactory } from '@nestjs/core';
|
|
||||||
import { AppModule } from './app.module';
|
|
||||||
import { DvbookingLoggerService } from './logger/dvbooking-logger.service';
|
|
||||||
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
|
||||||
|
|
||||||
async function bootstrap() {
|
|
||||||
const app = await NestFactory.create(AppModule);
|
|
||||||
app.useLogger(app.get(DvbookingLoggerService));
|
|
||||||
|
|
||||||
app.setGlobalPrefix('api');
|
|
||||||
|
|
||||||
const config = new DocumentBuilder()
|
|
||||||
.setTitle('DV Booking API')
|
|
||||||
.setDescription('The DV Booking API description')
|
|
||||||
.setVersion('1.0')
|
|
||||||
.addTag('dvbooking')
|
|
||||||
.build();
|
|
||||||
const document = SwaggerModule.createDocument(app, config);
|
|
||||||
SwaggerModule.setup('api', app, document);
|
|
||||||
|
|
||||||
await app.listen(process.env.PORT ?? 3000);
|
|
||||||
}
|
|
||||||
bootstrap();
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import { Module } from '@nestjs/common';
|
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
||||||
import { UserService } from './user.service';
|
|
||||||
import { UserController } from './user.controller';
|
|
||||||
import { User } from '../entity/user';
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
imports: [TypeOrmModule.forFeature([User])],
|
|
||||||
providers: [UserService],
|
|
||||||
controllers: [UserController],
|
|
||||||
exports: [UserService],
|
|
||||||
})
|
|
||||||
export class UserModule {}
|
|
||||||
@@ -8,12 +8,12 @@ import { AuthModule } from './auth/auth.module';
|
|||||||
import { User } from './entity/user';
|
import { User } from './entity/user';
|
||||||
import { UserGroup } from './entity/user-group';
|
import { UserGroup } from './entity/user-group';
|
||||||
import { UserRole } from './entity/user-role';
|
import { UserRole } from './entity/user-role';
|
||||||
import { LoggerModule } from './logger/logger.module';
|
|
||||||
|
|
||||||
const moduleTypeOrm = TypeOrmModule.forRootAsync({
|
const moduleTypeOrm = TypeOrmModule.forRootAsync({
|
||||||
imports: [ConfigModule],
|
imports: [ConfigModule],
|
||||||
inject: [ConfigService],
|
inject: [ConfigService],
|
||||||
useFactory: (configService: ConfigService) => {
|
useFactory: (configService: ConfigService) => {
|
||||||
|
// console.log("config Service", configService)
|
||||||
return {
|
return {
|
||||||
type: 'postgres',
|
type: 'postgres',
|
||||||
host: configService.get<string>('DATABASE_HOST'),
|
host: configService.get<string>('DATABASE_HOST'),
|
||||||
@@ -28,14 +28,16 @@ const moduleTypeOrm = TypeOrmModule.forRootAsync({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const envFilePath =
|
||||||
|
process.env.NODE_ENV === 'test' ? '.env.e2e' : '.env';
|
||||||
|
|
||||||
|
// throw new Error("envFilePath:"+envFilePath);
|
||||||
|
const moduleConfig = ConfigModule.forRoot({
|
||||||
|
envFilePath,
|
||||||
|
});
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [moduleConfig, moduleTypeOrm, UserModule, AuthModule],
|
||||||
ConfigModule.forRoot(),
|
|
||||||
moduleTypeOrm,
|
|
||||||
UserModule,
|
|
||||||
AuthModule,
|
|
||||||
LoggerModule,
|
|
||||||
],
|
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [AppService],
|
providers: [AppService],
|
||||||
})
|
})
|
||||||
@@ -1,12 +1,9 @@
|
|||||||
import { IsString } from 'class-validator';
|
import { IsString } from 'class-validator';
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
|
||||||
|
|
||||||
export class LoginRequestDto {
|
export class LoginRequestDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
@ApiProperty()
|
|
||||||
username: string;
|
username: string;
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
@ApiProperty()
|
|
||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
@@ -5,8 +5,14 @@ import { User } from './entity/user';
|
|||||||
import * as dotenv from 'dotenv';
|
import * as dotenv from 'dotenv';
|
||||||
import { UserGroup } from './entity/user-group';
|
import { UserGroup } from './entity/user-group';
|
||||||
import { UserRole } from './entity/user-role';
|
import { UserRole } from './entity/user-role';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
dotenv.config();
|
let envFilePath = path.resolve(process.cwd(), '.env');
|
||||||
|
if (process.env.DATA_SOURCE_ENV) {
|
||||||
|
envFilePath = path.resolve(process.cwd(), process.env.DATA_SOURCE_ENV);
|
||||||
|
}
|
||||||
|
|
||||||
|
dotenv.config({ path: envFilePath });
|
||||||
|
|
||||||
export const AppDataSource = new DataSource({
|
export const AppDataSource = new DataSource({
|
||||||
type: 'postgres',
|
type: 'postgres',
|
||||||
@@ -18,8 +24,6 @@ export const AppDataSource = new DataSource({
|
|||||||
synchronize: false,
|
synchronize: false,
|
||||||
logging: false,
|
logging: false,
|
||||||
entities: [User, UserGroup, UserRole],
|
entities: [User, UserGroup, UserRole],
|
||||||
migrations: [
|
migrations: ['src/migration/**/*.ts'],
|
||||||
'src/migration/**/*.ts'
|
|
||||||
],
|
|
||||||
subscribers: [],
|
subscribers: [],
|
||||||
});
|
});
|
||||||
8
src/main.ts
Normal file
8
src/main.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { NestFactory } from '@nestjs/core';
|
||||||
|
import { AppModule } from './app.module';
|
||||||
|
|
||||||
|
async function bootstrap() {
|
||||||
|
const app = await NestFactory.create(AppModule);
|
||||||
|
await app.listen(process.env.PORT ?? 3000);
|
||||||
|
}
|
||||||
|
bootstrap();
|
||||||
@@ -1,18 +1,17 @@
|
|||||||
import { IsString, IsEmail, MinLength } from 'class-validator';
|
import { IsString, IsEmail, MinLength, IsArray } from 'class-validator';
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
|
||||||
|
|
||||||
export class CreateUserDto {
|
export class CreateUserDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
@MinLength(3)
|
@MinLength(3)
|
||||||
@ApiProperty()
|
|
||||||
username: string;
|
username: string;
|
||||||
|
|
||||||
@IsEmail()
|
@IsEmail()
|
||||||
@ApiProperty()
|
|
||||||
email: string;
|
email: string;
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
@MinLength(6)
|
@MinLength(6)
|
||||||
@ApiProperty()
|
|
||||||
password: string;
|
password: string;
|
||||||
|
|
||||||
|
@IsArray()
|
||||||
|
groups: [{ id: number }];
|
||||||
}
|
}
|
||||||
@@ -1,24 +1,17 @@
|
|||||||
import { IsString, IsEmail, MinLength, IsOptional } from 'class-validator';
|
import { IsString, IsEmail, MinLength, IsOptional } from 'class-validator';
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
|
||||||
|
|
||||||
export class UpdateUserDto {
|
export class UpdateUserDto {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
@MinLength(3)
|
@MinLength(3)
|
||||||
@ApiProperty()
|
|
||||||
username?: string;
|
username?: string;
|
||||||
|
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsEmail()
|
@IsEmail()
|
||||||
@ApiProperty()
|
|
||||||
email?: string;
|
email?: string;
|
||||||
|
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
@MinLength(6)
|
@MinLength(6)
|
||||||
@ApiProperty()
|
|
||||||
password?: string;
|
password?: string;
|
||||||
|
|
||||||
}
|
}
|
||||||
44
src/user/user-group.service.ts
Normal file
44
src/user/user-group.service.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { UserGroup } from '../entity/user-group';
|
||||||
|
import { FindOptionsRelations } from 'typeorm/find-options/FindOptionsRelations';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UserGroupService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(UserGroup)
|
||||||
|
private userGroupRepository: Repository<UserGroup>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
findByName(
|
||||||
|
name: string,
|
||||||
|
relations?: FindOptionsRelations<UserGroup>,
|
||||||
|
): Promise<UserGroup | null> {
|
||||||
|
return this.userGroupRepository.findOne({ where: { name }, relations });
|
||||||
|
}
|
||||||
|
findAll(): Promise<UserGroup[]> {
|
||||||
|
return this.userGroupRepository.find();
|
||||||
|
}
|
||||||
|
|
||||||
|
findOne(id: number): Promise<UserGroup | null> {
|
||||||
|
return this.userGroupRepository.findOneBy({ id });
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(userGroup: Partial<UserGroup>): Promise<UserGroup> {
|
||||||
|
const newUserGroup = this.userGroupRepository.create(userGroup);
|
||||||
|
return this.userGroupRepository.save(newUserGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(
|
||||||
|
id: number,
|
||||||
|
userGroup: Partial<UserGroup>,
|
||||||
|
): Promise<UserGroup | null> {
|
||||||
|
await this.userGroupRepository.update(id, userGroup);
|
||||||
|
return this.userGroupRepository.findOneBy({ id });
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(id: number): Promise<void> {
|
||||||
|
await this.userGroupRepository.delete(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/user/user-role.service.ts
Normal file
37
src/user/user-role.service.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { UserRole } from '../entity/user-role';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UserRoleService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(UserRole)
|
||||||
|
private userGroupRepository: Repository<UserRole>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
findAll(): Promise<UserRole[]> {
|
||||||
|
return this.userGroupRepository.find();
|
||||||
|
}
|
||||||
|
|
||||||
|
findOne(id: number): Promise<UserRole | null> {
|
||||||
|
return this.userGroupRepository.findOneBy({ id });
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(userGroup: Partial<UserRole>): Promise<UserRole> {
|
||||||
|
const newUserRole = this.userGroupRepository.create(userGroup);
|
||||||
|
return this.userGroupRepository.save(newUserRole);
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(
|
||||||
|
id: number,
|
||||||
|
userRole: Partial<UserRole>,
|
||||||
|
): Promise<UserRole | null> {
|
||||||
|
await this.userGroupRepository.update(id, userRole);
|
||||||
|
return this.userGroupRepository.findOneBy({ id });
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(id: number): Promise<void> {
|
||||||
|
await this.userGroupRepository.delete(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,6 +33,7 @@ export class UserController {
|
|||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
findAll(): Promise<User[]> {
|
findAll(): Promise<User[]> {
|
||||||
|
console.log("findall", process.env);
|
||||||
return this.userService.findAll();
|
return this.userService.findAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
17
src/user/user.module.ts
Normal file
17
src/user/user.module.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { UserService } from './user.service';
|
||||||
|
import { UserController } from './user.controller';
|
||||||
|
import { User } from '../entity/user';
|
||||||
|
import { UserGroup } from '../entity/user-group';
|
||||||
|
import { UserRole } from '../entity/user-role';
|
||||||
|
import { UserGroupService } from './user-group.service';
|
||||||
|
import { UserRoleService } from './user-role.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([User, UserGroup, UserRole])],
|
||||||
|
providers: [UserService, UserGroupService, UserRoleService],
|
||||||
|
controllers: [UserController],
|
||||||
|
exports: [UserService, UserGroupService, UserRoleService],
|
||||||
|
})
|
||||||
|
export class UserModule {}
|
||||||
@@ -1,48 +1,59 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { User } from '../entity/user';
|
import { User } from '../entity/user';
|
||||||
import * as bcrypt from 'bcrypt';
|
import * as bcrypt from 'bcrypt';
|
||||||
import { FindOptionsRelations } from 'typeorm/find-options/FindOptionsRelations';
|
import { FindOptionsRelations } from 'typeorm/find-options/FindOptionsRelations';
|
||||||
import { DvbookingLoggerService } from '../logger/dvbooking-logger.service';
|
import { UserGroupService } from './user-group.service';
|
||||||
|
import { CreateUserDto } from './dto/create-user.dto';
|
||||||
|
import { UserGroup } from '../entity/user-group';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserService {
|
export class UserService {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(User)
|
@InjectRepository(User)
|
||||||
private usersRepository: Repository<User>,
|
private usersRepository: Repository<User>,
|
||||||
private readonly logger: DvbookingLoggerService,
|
private userGroupService: UserGroupService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
findAll(): Promise<User[]> {
|
findAll(): Promise<User[]> {
|
||||||
this.logger.log('Finding all users', 'UserService');
|
|
||||||
return this.usersRepository.find();
|
return this.usersRepository.find();
|
||||||
}
|
}
|
||||||
|
|
||||||
findOne(id: number): Promise<User | null> {
|
findOne(id: number): Promise<User | null> {
|
||||||
this.logger.log(`Finding user with id: ${id}`, 'UserService');
|
|
||||||
return this.usersRepository.findOneBy({ id });
|
return this.usersRepository.findOneBy({ id });
|
||||||
}
|
}
|
||||||
|
|
||||||
findByUsername(
|
findByUsername(
|
||||||
username: string,
|
username: string,
|
||||||
relations: FindOptionsRelations<User>,
|
relations?: FindOptionsRelations<User>,
|
||||||
): Promise<User | null> {
|
): Promise<User | null> {
|
||||||
this.logger.log(`Finding user with username: ${username}`, 'UserService');
|
|
||||||
return this.usersRepository.findOne({ where: { username }, relations });
|
return this.usersRepository.findOne({ where: { username }, relations });
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(user: Partial<User>): Promise<User> {
|
async create(userDto: Partial<CreateUserDto>): Promise<User> {
|
||||||
this.logger.log('Creating a new user', 'UserService');
|
const { groups, ...user } = userDto;
|
||||||
if (user.password) {
|
if (user.password) {
|
||||||
user.password = await bcrypt.hash(user.password, 12);
|
user.password = await bcrypt.hash(user.password, 12);
|
||||||
}
|
}
|
||||||
|
if (groups) {
|
||||||
|
const userGroups: UserGroup[] = [];
|
||||||
|
for (const group of groups) {
|
||||||
|
const userGroup = await this.userGroupService.findOne(group.id);
|
||||||
|
if (userGroup) {
|
||||||
|
userGroups.push(userGroup);
|
||||||
|
} else {
|
||||||
|
throw new NotFoundException('User group does not exist:' + group.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(user as Partial<User>).groups = userGroups;
|
||||||
|
}
|
||||||
const newUser = this.usersRepository.create(user);
|
const newUser = this.usersRepository.create(user);
|
||||||
|
|
||||||
return this.usersRepository.save(newUser);
|
return this.usersRepository.save(newUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(id: number, user: Partial<User>): Promise<User | null> {
|
async update(id: number, user: Partial<User>): Promise<User | null> {
|
||||||
this.logger.log(`Updating user with id: ${id}`, 'UserService');
|
|
||||||
if (user.password) {
|
if (user.password) {
|
||||||
user.password = await bcrypt.hash(user.password, 12);
|
user.password = await bcrypt.hash(user.password, 12);
|
||||||
}
|
}
|
||||||
@@ -51,7 +62,6 @@ export class UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async remove(id: number): Promise<void> {
|
async remove(id: number): Promise<void> {
|
||||||
this.logger.log(`Removing user with id: ${id}`, 'UserService');
|
|
||||||
await this.usersRepository.delete(id);
|
await this.usersRepository.delete(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
8
test/client/dvbooking.api-context.ts
Normal file
8
test/client/dvbooking.api-context.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { User } from '../../src/entity/user';
|
||||||
|
import { INestApplication } from '@nestjs/common';
|
||||||
|
|
||||||
|
export interface DvbookingApiContext {
|
||||||
|
token: string;
|
||||||
|
user: User;
|
||||||
|
app: INestApplication<any>;
|
||||||
|
}
|
||||||
78
test/client/dvbooking.api.ts
Normal file
78
test/client/dvbooking.api.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { DvbookingApiContext } from './dvbooking.api-context';
|
||||||
|
import { CreateUserDto } from '../../src/user/dto/create-user.dto';
|
||||||
|
import { TestingModule } from '@nestjs/testing';
|
||||||
|
import { UserService } from '../../src/user/user.service';
|
||||||
|
import { UserGroupService } from '../../src/user/user-group.service';
|
||||||
|
import { INestApplication } from '@nestjs/common';
|
||||||
|
import request from 'supertest';
|
||||||
|
|
||||||
|
export class DvbookingApi {
|
||||||
|
public context: DvbookingApiContext;
|
||||||
|
|
||||||
|
systemUser: { username: string; password: string; token?: string } = {
|
||||||
|
username: 'admin',
|
||||||
|
password: '123456',
|
||||||
|
token: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
private userService: UserService;
|
||||||
|
private userGroupService: UserGroupService;
|
||||||
|
constructor(
|
||||||
|
private app: INestApplication<any>,
|
||||||
|
private moduleFixture: TestingModule,
|
||||||
|
) {
|
||||||
|
this.userService = moduleFixture.get<UserService>(UserService);
|
||||||
|
this.userGroupService =
|
||||||
|
moduleFixture.get<UserGroupService>(UserGroupService);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async init() {
|
||||||
|
const response = await request(this.app.getHttpServer())
|
||||||
|
.post('/auth/login')
|
||||||
|
.send({
|
||||||
|
username: this.systemUser.username,
|
||||||
|
password: this.systemUser.password,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.systemUser.token = (
|
||||||
|
response.body as { access_token: string }
|
||||||
|
).access_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async destroy() {
|
||||||
|
// do cleanup
|
||||||
|
}
|
||||||
|
|
||||||
|
public async loginWithGroup(
|
||||||
|
username?: string,
|
||||||
|
groupName?: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const group = await this.userGroupService.findByName(groupName);
|
||||||
|
if (!username) {
|
||||||
|
username = 'e2e-user-' + Math.floor(100 * Math.random());
|
||||||
|
}
|
||||||
|
const password = 'password';
|
||||||
|
const user = await this.createUser({
|
||||||
|
username: username,
|
||||||
|
email: 'user@dvbooking.hu',
|
||||||
|
password: 'password',
|
||||||
|
groups: [{ id: group!.id }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await request(this.app.getHttpServer())
|
||||||
|
.post('/auth/login')
|
||||||
|
.send({ username, password });
|
||||||
|
|
||||||
|
const token = (response.body as { access_token: string }).access_token;
|
||||||
|
|
||||||
|
this.context = {
|
||||||
|
user,
|
||||||
|
token,
|
||||||
|
app: this.app,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createUser(user: CreateUserDto) {
|
||||||
|
return await this.userService.create(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
17
test/client/dvbooking.http-client.ts
Normal file
17
test/client/dvbooking.http-client.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import request from 'supertest';
|
||||||
|
import { DvbookingApiContext } from './dvbooking.api-context';
|
||||||
|
|
||||||
|
export class DvBookingHttpClient {
|
||||||
|
constructor(private context: DvbookingApiContext) {}
|
||||||
|
|
||||||
|
private createRequest() {
|
||||||
|
return request(this.context.app.getHttpServer());
|
||||||
|
}
|
||||||
|
public async httpPost(path: string, data?: string | object) {
|
||||||
|
return await this.createRequest().post(path).send(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async httpGet(path: string) {
|
||||||
|
return await this.createRequest().get(path).send();
|
||||||
|
}
|
||||||
|
}
|
||||||
21
test/client/user.api-client.ts
Normal file
21
test/client/user.api-client.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { DvbookingApiContext } from './dvbooking.api-context';
|
||||||
|
import { DvBookingHttpClient } from './dvbooking.http-client';
|
||||||
|
import { User } from '../../src/entity/user';
|
||||||
|
|
||||||
|
export class UserApiClient {
|
||||||
|
private http: DvBookingHttpClient;
|
||||||
|
|
||||||
|
constructor(context: DvbookingApiContext) {
|
||||||
|
this.http = new DvBookingHttpClient(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async find() {
|
||||||
|
const response = await this.http.httpGet('/users');
|
||||||
|
return response.body as User[];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async findById(id: number) {
|
||||||
|
const response = await this.http.httpGet('/users/' + id);
|
||||||
|
return response.body as User[];
|
||||||
|
}
|
||||||
|
}
|
||||||
43
test/global-setup.ts
Normal file
43
test/global-setup.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { exec } from 'child_process';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { DockerComposeEnvironment, Wait } from 'testcontainers';
|
||||||
|
|
||||||
|
export default async () => {
|
||||||
|
const composeFilePath = path.resolve(__dirname, '../environment/e2e');
|
||||||
|
|
||||||
|
const environment = await new DockerComposeEnvironment(
|
||||||
|
composeFilePath,
|
||||||
|
'docker-compose.yaml',
|
||||||
|
)
|
||||||
|
.withWaitStrategy('postgres_1', Wait.forHealthCheck())
|
||||||
|
.up();
|
||||||
|
|
||||||
|
// Store the environment details for teardown
|
||||||
|
(global as any).__TESTCONTAINERS_ENVIRONMENT__ = environment;
|
||||||
|
|
||||||
|
// Run migrations
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
console.info('running migration');
|
||||||
|
exec(
|
||||||
|
'env && npm run migration:run',
|
||||||
|
{ env: { ...process.env, DATA_SOURCE_ENV: '.env.e2e' } },
|
||||||
|
(err, stdout, stderr) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(stderr);
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
console.log(stdout);
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// await new Promise(resolve => {setTimeout(resolve, 60000)})
|
||||||
|
};
|
||||||
|
|
||||||
|
// function readEnvFile() {
|
||||||
|
// const fs = require('fs');
|
||||||
|
// const dotenv = require('dotenv');
|
||||||
|
// const envConfig = dotenv.parse(fs.readFileSync('.env.e2e'));
|
||||||
|
// return envConfig;
|
||||||
|
// }
|
||||||
11
test/global-teardown.ts
Normal file
11
test/global-teardown.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { StartedDockerComposeEnvironment } from 'testcontainers';
|
||||||
|
|
||||||
|
export default async () => {
|
||||||
|
const environment: StartedDockerComposeEnvironment = (
|
||||||
|
global as any as { __TESTCONTAINERS_ENVIRONMENT__: any }
|
||||||
|
).__TESTCONTAINERS_ENVIRONMENT__ as StartedDockerComposeEnvironment;
|
||||||
|
|
||||||
|
if (environment) {
|
||||||
|
await environment.down();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -5,5 +5,7 @@
|
|||||||
"testRegex": ".e2e-spec.ts$",
|
"testRegex": ".e2e-spec.ts$",
|
||||||
"transform": {
|
"transform": {
|
||||||
"^.+\\.(t|j)s$": "ts-jest"
|
"^.+\\.(t|j)s$": "ts-jest"
|
||||||
}
|
},
|
||||||
|
"globalSetup": "<rootDir>/global-setup.ts",
|
||||||
|
"globalTeardown": "<rootDir>/global-teardown.ts"
|
||||||
}
|
}
|
||||||
128
test/user.e2e-spec.ts
Normal file
128
test/user.e2e-spec.ts
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { INestApplication, ValidationPipe } from '@nestjs/common';
|
||||||
|
import request from 'supertest';
|
||||||
|
import { AppModule } from '../src/app.module';
|
||||||
|
import { CreateUserDto } from '../src/user/dto/create-user.dto';
|
||||||
|
import { UserService } from '../src/user/user.service';
|
||||||
|
import { User } from '../src/entity/user';
|
||||||
|
|
||||||
|
import dotenv from 'dotenv';
|
||||||
|
import path from 'path';
|
||||||
|
import { DvbookingApi } from './client/dvbooking.api';
|
||||||
|
|
||||||
|
dotenv.config({ path: path.resolve(process.cwd(), '.env.e2e') });
|
||||||
|
|
||||||
|
describe('UserController (e2e)', () => {
|
||||||
|
process.env.DATA_SOURCE_ENV = '.env.e2e';
|
||||||
|
process.env.DATABASE_HOST = 'localhost';
|
||||||
|
process.env.DATABASE_PORT = '4401';
|
||||||
|
process.env.DATABASE_USER = 'test';
|
||||||
|
process.env.DATABASE_PASS = 'test';
|
||||||
|
|
||||||
|
let app: INestApplication;
|
||||||
|
let jwtToken: string;
|
||||||
|
let adminUserId: number;
|
||||||
|
let adminGroupId: number;
|
||||||
|
let api: DvbookingApi;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||||
|
imports: [AppModule],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
// process.env.DATA_SOURCE_ENV=".env.e2e";
|
||||||
|
app = moduleFixture.createNestApplication();
|
||||||
|
app.useGlobalPipes(new ValidationPipe());
|
||||||
|
await app.init();
|
||||||
|
api = new DvbookingApi(app, moduleFixture);
|
||||||
|
await api.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await api.destroy();
|
||||||
|
await app.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('/users', () => {
|
||||||
|
it('(GET) should get all users', async () => {
|
||||||
|
const response = await request(app.getHttpServer())
|
||||||
|
.get('/users')
|
||||||
|
.set('Authorization', `Bearer ${jwtToken}`);
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(Array.isArray(response.body)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('(POST) should create a user', async () => {
|
||||||
|
const createUserDto: CreateUserDto = {
|
||||||
|
username: 'e2e_user',
|
||||||
|
email: 'user@dvbooking.hu',
|
||||||
|
password: 'password',
|
||||||
|
groups: [{ id: adminGroupId }],
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await request(app.getHttpServer())
|
||||||
|
.post('/users')
|
||||||
|
.set('Authorization', `Bearer ${jwtToken}`)
|
||||||
|
.send(createUserDto);
|
||||||
|
|
||||||
|
expect(response.status).toBe(201);
|
||||||
|
expect(response.body.username).toEqual(createUserDto.username);
|
||||||
|
|
||||||
|
const userService = app.get<UserService>(UserService);
|
||||||
|
await userService.remove(response.body.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('/users/:id', () => {
|
||||||
|
let user: User;
|
||||||
|
let userService: UserService;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
userService = app.get<UserService>(UserService);
|
||||||
|
await api.loginWithGroup(undefined, 'admin');
|
||||||
|
user = api.context.user!;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
const userExists = await userService.findOne(user.id);
|
||||||
|
if (userExists) {
|
||||||
|
await userService.remove(user.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('(GET) should get a user by id', async () => {
|
||||||
|
const response = await request(app.getHttpServer())
|
||||||
|
.get(`/users/${user.id}`)
|
||||||
|
.set('Authorization', `Bearer ${jwtToken}`);
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(response.body.id).toEqual(user.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
// it('(PATCH) should update a user', async () => {
|
||||||
|
// const updateUserDto: UpdateUserDto = {
|
||||||
|
// username: 'e2e_updated_user',
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// const response = await request(app.getHttpServer())
|
||||||
|
// .patch(`/users/${user.id}`)
|
||||||
|
// .set('Authorization', `Bearer ${jwtToken}`)
|
||||||
|
// .send(updateUserDto);
|
||||||
|
//
|
||||||
|
// expect(response.status).toBe(200);
|
||||||
|
// expect(response.body.username).toEqual(updateUserDto.username);
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// it('(DELETE) should delete a user', async () => {
|
||||||
|
// const response = await request(app.getHttpServer())
|
||||||
|
// .delete(`/users/${user.id}`)
|
||||||
|
// .set('Authorization', `Bearer ${jwtToken}`);
|
||||||
|
//
|
||||||
|
// expect(response.status).toBe(200);
|
||||||
|
//
|
||||||
|
// const deletedUser = await userService.findOne(user.id);
|
||||||
|
// expect(deletedUser).toBeNull();
|
||||||
|
// });
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user