Compare commits
10 Commits
feature/e2
...
19ca0c086c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19ca0c086c | ||
|
|
e09110346e | ||
|
|
327c641137 | ||
|
|
08d4d0bccc | ||
|
|
efee1a0239 | ||
|
|
d99c410876 | ||
|
|
532299c864 | ||
|
|
ea74d34363 | ||
|
|
4b025b9ec7 | ||
|
|
5c37de40c6 |
12
.gitignore
vendored
12
.gitignore
vendored
@@ -1,7 +1,7 @@
|
||||
# compiled output
|
||||
/dist
|
||||
/node_modules
|
||||
/build
|
||||
dist
|
||||
node_modules
|
||||
build
|
||||
|
||||
# Logs
|
||||
logs
|
||||
@@ -16,11 +16,11 @@ lerna-debug.log*
|
||||
.DS_Store
|
||||
|
||||
# Tests
|
||||
/coverage
|
||||
/.nyc_output
|
||||
coverage
|
||||
.nyc_output
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
|
||||
17
admin/.editorconfig
Normal file
17
admin/.editorconfig
Normal file
@@ -0,0 +1,17 @@
|
||||
# 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
Normal file
43
admin/.gitignore
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
# 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
Normal file
4
admin/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
|
||||
"recommendations": ["angular.ng-template"]
|
||||
}
|
||||
20
admin/.vscode/launch.json
vendored
Normal file
20
admin/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
// 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
Normal file
42
admin/.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
// 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
59
admin/README.md
Normal file
59
admin/README.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# 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.
|
||||
85
admin/angular.json
Normal file
85
admin/angular.json
Normal file
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"$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
Normal file
9652
admin/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
47
admin/package.json
Normal file
47
admin/package.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
8
admin/proxy.conf.json
Normal file
8
admin/proxy.conf.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"/api": {
|
||||
"target": "http://localhost:3000",
|
||||
"secure": false,
|
||||
"logLevel": "debug",
|
||||
"changeOrigin": true
|
||||
}
|
||||
}
|
||||
BIN
admin/public/favicon.ico
Normal file
BIN
admin/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
23
admin/src/app/app.config.ts
Normal file
23
admin/src/app/app.config.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
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 },
|
||||
],
|
||||
};
|
||||
0
admin/src/app/app.css
Normal file
0
admin/src/app/app.css
Normal file
5
admin/src/app/app.html
Normal file
5
admin/src/app/app.html
Normal file
@@ -0,0 +1,5 @@
|
||||
<div>
|
||||
<button (click)="logout()">Logout</button>
|
||||
</div>
|
||||
<app-main-menu></app-main-menu>
|
||||
<router-outlet />
|
||||
10
admin/src/app/app.routes.ts
Normal file
10
admin/src/app/app.routes.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
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
|
||||
];
|
||||
25
admin/src/app/app.spec.ts
Normal file
25
admin/src/app/app.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
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');
|
||||
});
|
||||
});
|
||||
21
admin/src/app/app.ts
Normal file
21
admin/src/app/app.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
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']);
|
||||
}
|
||||
}
|
||||
23
admin/src/app/auth/auth.guard.ts
Normal file
23
admin/src/app/auth/auth.guard.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
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']);
|
||||
}
|
||||
}
|
||||
}
|
||||
36
admin/src/app/auth/auth.service.ts
Normal file
36
admin/src/app/auth/auth.service.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
29
admin/src/app/auth/jwt.interceptor.ts
Normal file
29
admin/src/app/auth/jwt.interceptor.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
8
admin/src/app/components/home/home.component.ts
Normal file
8
admin/src/app/components/home/home.component.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
template: '<h1>Welcome to the Admin Panel!</h1>',
|
||||
standalone: true,
|
||||
})
|
||||
export class HomeComponent {}
|
||||
15
admin/src/app/components/login/login.component.html
Normal file
15
admin/src/app/components/login/login.component.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<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>
|
||||
36
admin/src/app/components/login/login.component.ts
Normal file
36
admin/src/app/components/login/login.component.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
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'),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
0
admin/src/app/components/main-menu/main-menu.css
Normal file
0
admin/src/app/components/main-menu/main-menu.css
Normal file
1
admin/src/app/components/main-menu/main-menu.html
Normal file
1
admin/src/app/components/main-menu/main-menu.html
Normal file
@@ -0,0 +1 @@
|
||||
<p>main-menu works!</p>
|
||||
23
admin/src/app/components/main-menu/main-menu.spec.ts
Normal file
23
admin/src/app/components/main-menu/main-menu.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
11
admin/src/app/components/main-menu/main-menu.ts
Normal file
11
admin/src/app/components/main-menu/main-menu.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-main-menu',
|
||||
imports: [],
|
||||
templateUrl: './main-menu.html',
|
||||
styleUrl: './main-menu.css',
|
||||
})
|
||||
export class MainMenu {
|
||||
|
||||
}
|
||||
0
admin/src/app/components/navbar/navbar.css
Normal file
0
admin/src/app/components/navbar/navbar.css
Normal file
19
admin/src/app/components/navbar/navbar.html
Normal file
19
admin/src/app/components/navbar/navbar.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<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>
|
||||
23
admin/src/app/components/navbar/navbar.spec.ts
Normal file
23
admin/src/app/components/navbar/navbar.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
11
admin/src/app/components/navbar/navbar.ts
Normal file
11
admin/src/app/components/navbar/navbar.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-navbar',
|
||||
imports: [],
|
||||
templateUrl: './navbar.html',
|
||||
styleUrl: './navbar.css',
|
||||
})
|
||||
export class Navbar {
|
||||
|
||||
}
|
||||
13
admin/src/index.html
Normal file
13
admin/src/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!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>
|
||||
6
admin/src/main.ts
Normal file
6
admin/src/main.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
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
admin/src/styles.css
Normal file
1
admin/src/styles.css
Normal file
@@ -0,0 +1 @@
|
||||
/* You can add global styles to this file, and also import other style files */
|
||||
15
admin/tsconfig.app.json
Normal file
15
admin/tsconfig.app.json
Normal file
@@ -0,0 +1,15 @@
|
||||
/* 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"
|
||||
]
|
||||
}
|
||||
34
admin/tsconfig.json
Normal file
34
admin/tsconfig.json
Normal file
@@ -0,0 +1,34 @@
|
||||
/* 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
14
admin/tsconfig.spec.json
Normal file
14
admin/tsconfig.spec.json
Normal file
@@ -0,0 +1,14 @@
|
||||
/* 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"
|
||||
]
|
||||
}
|
||||
78
package-lock.json → server/package-lock.json
generated
78
package-lock.json → server/package-lock.json
generated
@@ -15,6 +15,7 @@
|
||||
"@nestjs/jwt": "^11.0.1",
|
||||
"@nestjs/passport": "^11.0.5",
|
||||
"@nestjs/platform-express": "^11.0.1",
|
||||
"@nestjs/swagger": "^11.2.1",
|
||||
"@nestjs/typeorm": "^11.0.0",
|
||||
"bcrypt": "^5.1.1",
|
||||
"class-transformer": "^0.5.1",
|
||||
@@ -2104,6 +2105,12 @@
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@microsoft/tsdoc": {
|
||||
"version": "0.15.1",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz",
|
||||
"integrity": "sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "0.2.12",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
|
||||
@@ -2460,6 +2467,26 @@
|
||||
"@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/mapped-types": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.1.0.tgz",
|
||||
"integrity": "sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@nestjs/common": "^10.0.0 || ^11.0.0",
|
||||
"class-transformer": "^0.4.0 || ^0.5.0",
|
||||
"class-validator": "^0.13.0 || ^0.14.0",
|
||||
"reflect-metadata": "^0.1.12 || ^0.2.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"class-transformer": {
|
||||
"optional": true
|
||||
},
|
||||
"class-validator": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/passport": {
|
||||
"version": "11.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/passport/-/passport-11.0.5.tgz",
|
||||
@@ -2589,6 +2616,39 @@
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/swagger": {
|
||||
"version": "11.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-11.2.1.tgz",
|
||||
"integrity": "sha512-1MS7xf0pzc1mofG53xrrtrurnziafPUHkqzRm4YUVPA/egeiMaSerQBD/feiAeQ2BnX0WiLsTX4HQFO0icvOjQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@microsoft/tsdoc": "0.15.1",
|
||||
"@nestjs/mapped-types": "2.1.0",
|
||||
"js-yaml": "4.1.0",
|
||||
"lodash": "4.17.21",
|
||||
"path-to-regexp": "8.3.0",
|
||||
"swagger-ui-dist": "5.29.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@fastify/static": "^8.0.0",
|
||||
"@nestjs/common": "^11.0.1",
|
||||
"@nestjs/core": "^11.0.1",
|
||||
"class-transformer": "*",
|
||||
"class-validator": "*",
|
||||
"reflect-metadata": "^0.1.12 || ^0.2.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@fastify/static": {
|
||||
"optional": true
|
||||
},
|
||||
"class-transformer": {
|
||||
"optional": true
|
||||
},
|
||||
"class-validator": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/testing": {
|
||||
"version": "11.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.1.7.tgz",
|
||||
@@ -2730,6 +2790,13 @@
|
||||
"url": "https://opencollective.com/pkgr"
|
||||
}
|
||||
},
|
||||
"node_modules/@scarf/scarf": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz",
|
||||
"integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@sinclair/typebox": {
|
||||
"version": "0.34.41",
|
||||
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz",
|
||||
@@ -4181,7 +4248,6 @@
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||
"dev": true,
|
||||
"license": "Python-2.0"
|
||||
},
|
||||
"node_modules/array-timsort": {
|
||||
@@ -7741,7 +7807,6 @@
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
@@ -10117,6 +10182,15 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/swagger-ui-dist": {
|
||||
"version": "5.29.4",
|
||||
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.29.4.tgz",
|
||||
"integrity": "sha512-gJFDz/gyLOCQtWwAgqs6Rk78z9ONnqTnlW11gimG9nLap8drKa3AJBKpzIQMIjl5PD2Ix+Tn+mc/tfoT2tgsng==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@scarf/scarf": "=1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/symbol-observable": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",
|
||||
@@ -29,6 +29,7 @@
|
||||
"@nestjs/jwt": "^11.0.1",
|
||||
"@nestjs/passport": "^11.0.5",
|
||||
"@nestjs/platform-express": "^11.0.1",
|
||||
"@nestjs/swagger": "^11.2.1",
|
||||
"@nestjs/typeorm": "^11.0.0",
|
||||
"bcrypt": "^5.1.1",
|
||||
"class-transformer": "^0.5.1",
|
||||
@@ -8,6 +8,7 @@ import { AuthModule } from './auth/auth.module';
|
||||
import { User } from './entity/user';
|
||||
import { UserGroup } from './entity/user-group';
|
||||
import { UserRole } from './entity/user-role';
|
||||
import { LoggerModule } from './logger/logger.module';
|
||||
|
||||
const moduleTypeOrm = TypeOrmModule.forRootAsync({
|
||||
imports: [ConfigModule],
|
||||
@@ -28,7 +29,13 @@ const moduleTypeOrm = TypeOrmModule.forRootAsync({
|
||||
});
|
||||
|
||||
@Module({
|
||||
imports: [ConfigModule.forRoot(), moduleTypeOrm, UserModule, AuthModule],
|
||||
imports: [
|
||||
ConfigModule.forRoot(),
|
||||
moduleTypeOrm,
|
||||
UserModule,
|
||||
AuthModule,
|
||||
LoggerModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
})
|
||||
@@ -1,9 +1,12 @@
|
||||
import { IsString } from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class LoginRequestDto {
|
||||
@IsString()
|
||||
@ApiProperty()
|
||||
username: string;
|
||||
|
||||
@IsString()
|
||||
@ApiProperty()
|
||||
password: string;
|
||||
}
|
||||
24
server/src/logger/dvbooking-logger.service.ts
Normal file
24
server/src/logger/dvbooking-logger.service.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
9
server/src/logger/logger.module.ts
Normal file
9
server/src/logger/logger.module.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { DvbookingLoggerService } from './dvbooking-logger.service';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [DvbookingLoggerService],
|
||||
exports: [DvbookingLoggerService],
|
||||
})
|
||||
export class LoggerModule {}
|
||||
23
server/src/main.ts
Normal file
23
server/src/main.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
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,14 +1,18 @@
|
||||
import { IsString, IsEmail, MinLength } from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class CreateUserDto {
|
||||
@IsString()
|
||||
@MinLength(3)
|
||||
@ApiProperty()
|
||||
username: string;
|
||||
|
||||
@IsEmail()
|
||||
@ApiProperty()
|
||||
email: string;
|
||||
|
||||
@IsString()
|
||||
@MinLength(6)
|
||||
@ApiProperty()
|
||||
password: string;
|
||||
}
|
||||
@@ -1,17 +1,24 @@
|
||||
import { IsString, IsEmail, MinLength, IsOptional } from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class UpdateUserDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MinLength(3)
|
||||
@ApiProperty()
|
||||
username?: string;
|
||||
|
||||
|
||||
@IsOptional()
|
||||
@IsEmail()
|
||||
@ApiProperty()
|
||||
email?: string;
|
||||
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MinLength(6)
|
||||
@ApiProperty()
|
||||
password?: string;
|
||||
|
||||
}
|
||||
@@ -4,19 +4,23 @@ import { Repository } from 'typeorm';
|
||||
import { User } from '../entity/user';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import { FindOptionsRelations } from 'typeorm/find-options/FindOptionsRelations';
|
||||
import { DvbookingLoggerService } from '../logger/dvbooking-logger.service';
|
||||
|
||||
@Injectable()
|
||||
export class UserService {
|
||||
constructor(
|
||||
@InjectRepository(User)
|
||||
private usersRepository: Repository<User>,
|
||||
private readonly logger: DvbookingLoggerService,
|
||||
) {}
|
||||
|
||||
findAll(): Promise<User[]> {
|
||||
this.logger.log('Finding all users', 'UserService');
|
||||
return this.usersRepository.find();
|
||||
}
|
||||
|
||||
findOne(id: number): Promise<User | null> {
|
||||
this.logger.log(`Finding user with id: ${id}`, 'UserService');
|
||||
return this.usersRepository.findOneBy({ id });
|
||||
}
|
||||
|
||||
@@ -24,10 +28,12 @@ export class UserService {
|
||||
username: string,
|
||||
relations: FindOptionsRelations<User>,
|
||||
): Promise<User | null> {
|
||||
this.logger.log(`Finding user with username: ${username}`, 'UserService');
|
||||
return this.usersRepository.findOne({ where: { username }, relations });
|
||||
}
|
||||
|
||||
async create(user: Partial<User>): Promise<User> {
|
||||
this.logger.log('Creating a new user', 'UserService');
|
||||
if (user.password) {
|
||||
user.password = await bcrypt.hash(user.password, 12);
|
||||
}
|
||||
@@ -36,6 +42,7 @@ export class UserService {
|
||||
}
|
||||
|
||||
async update(id: number, user: Partial<User>): Promise<User | null> {
|
||||
this.logger.log(`Updating user with id: ${id}`, 'UserService');
|
||||
if (user.password) {
|
||||
user.password = await bcrypt.hash(user.password, 12);
|
||||
}
|
||||
@@ -44,6 +51,7 @@ export class UserService {
|
||||
}
|
||||
|
||||
async remove(id: number): Promise<void> {
|
||||
this.logger.log(`Removing user with id: ${id}`, 'UserService');
|
||||
await this.usersRepository.delete(id);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
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();
|
||||
Reference in New Issue
Block a user