diff --git a/admin/package.json b/admin/package.json
index 6c0d488..c8339cd 100644
--- a/admin/package.json
+++ b/admin/package.json
@@ -3,7 +3,7 @@
"version": "0.0.0",
"scripts": {
"ng": "ng",
- "start": "ng serve",
+ "start": "ng serve --proxy-config proxy.conf.json",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
diff --git a/admin/proxy.conf.json b/admin/proxy.conf.json
new file mode 100644
index 0000000..9ed18d7
--- /dev/null
+++ b/admin/proxy.conf.json
@@ -0,0 +1,8 @@
+{
+ "/api": {
+ "target": "http://localhost:3000",
+ "secure": false,
+ "logLevel": "debug",
+ "changeOrigin": true
+ }
+}
diff --git a/admin/src/app/app.config.ts b/admin/src/app/app.config.ts
index 2e06ce8..10723fe 100644
--- a/admin/src/app/app.config.ts
+++ b/admin/src/app/app.config.ts
@@ -1,12 +1,23 @@
-import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZonelessChangeDetection } from '@angular/core';
+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)
- ]
+ provideRouter(routes),
+ provideHttpClient(withInterceptorsFromDi()),
+ AuthService,
+ AuthGuard,
+ { provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
+ ],
};
diff --git a/admin/src/app/app.html b/admin/src/app/app.html
index 69650d9..4dbe0a4 100644
--- a/admin/src/app/app.html
+++ b/admin/src/app/app.html
@@ -1,14 +1,5 @@
-
-
+
+
-
-
-
-
-
-
-
-
-
-
+
diff --git a/admin/src/app/app.routes.ts b/admin/src/app/app.routes.ts
index dc39edb..b4405e1 100644
--- a/admin/src/app/app.routes.ts
+++ b/admin/src/app/app.routes.ts
@@ -1,3 +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 = [];
+export const routes: Routes = [
+ { path: 'login', component: LoginComponent },
+ { path: '', component: HomeComponent, canActivate: [AuthGuard] },
+ { path: '**', redirectTo: '' } // Redirect to home for any other route
+];
diff --git a/admin/src/app/app.ts b/admin/src/app/app.ts
index 5fbee67..5a1e70b 100644
--- a/admin/src/app/app.ts
+++ b/admin/src/app/app.ts
@@ -1,13 +1,22 @@
import { Component, signal } from '@angular/core';
-import { RouterOutlet } from '@angular/router';
+import { Router, RouterOutlet } from '@angular/router';
+import { MainMenu } from './components/main-menu/main-menu';
+import { AuthService } from './auth/auth.service';
import { AdminLayout } from './layout/admin-layout/admin-layout';
@Component({
selector: 'app-root',
- imports: [RouterOutlet, AdminLayout],
+ imports: [RouterOutlet, AdminLayout,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']);
+ }
}
diff --git a/admin/src/app/auth/auth.guard.ts b/admin/src/app/auth/auth.guard.ts
new file mode 100644
index 0000000..90a754c
--- /dev/null
+++ b/admin/src/app/auth/auth.guard.ts
@@ -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
| Promise | boolean | UrlTree {
+ if (this.authService.isLoggedIn()) {
+ return true;
+ } else {
+ // Redirect to the login page
+ return this.router.createUrlTree(['/login']);
+ }
+ }
+}
diff --git a/admin/src/app/auth/auth.service.ts b/admin/src/app/auth/auth.service.ts
new file mode 100644
index 0000000..3b1ee26
--- /dev/null
+++ b/admin/src/app/auth/auth.service.ts
@@ -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 {
+ 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);
+ }
+}
diff --git a/admin/src/app/auth/jwt.interceptor.ts b/admin/src/app/auth/jwt.interceptor.ts
new file mode 100644
index 0000000..96d864a
--- /dev/null
+++ b/admin/src/app/auth/jwt.interceptor.ts
@@ -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,
+ next: HttpHandler
+ ): Observable> {
+ const token = this.authService.getToken();
+ if (token) {
+ request = request.clone({
+ setHeaders: {
+ Authorization: `Bearer ${token}`,
+ },
+ });
+ }
+ return next.handle(request);
+ }
+}
diff --git a/admin/src/app/components/home/home.component.ts b/admin/src/app/components/home/home.component.ts
new file mode 100644
index 0000000..39e92b3
--- /dev/null
+++ b/admin/src/app/components/home/home.component.ts
@@ -0,0 +1,8 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'app-home',
+ template: 'Welcome to the Admin Panel!
',
+ standalone: true,
+})
+export class HomeComponent {}
diff --git a/admin/src/app/components/login/login.component.html b/admin/src/app/components/login/login.component.html
new file mode 100644
index 0000000..f12c5a1
--- /dev/null
+++ b/admin/src/app/components/login/login.component.html
@@ -0,0 +1,15 @@
+
+
Login
+
+
{{ errorMessage }}
+
diff --git a/admin/src/app/components/login/login.component.ts b/admin/src/app/components/login/login.component.ts
new file mode 100644
index 0000000..a77faea
--- /dev/null
+++ b/admin/src/app/components/login/login.component.ts
@@ -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'),
+ });
+ }
+ }
+}
diff --git a/admin/src/app/components/main-menu/main-menu.css b/admin/src/app/components/main-menu/main-menu.css
new file mode 100644
index 0000000..e69de29
diff --git a/admin/src/app/components/main-menu/main-menu.html b/admin/src/app/components/main-menu/main-menu.html
new file mode 100644
index 0000000..28bd6d6
--- /dev/null
+++ b/admin/src/app/components/main-menu/main-menu.html
@@ -0,0 +1 @@
+main-menu works!
diff --git a/admin/src/app/components/main-menu/main-menu.spec.ts b/admin/src/app/components/main-menu/main-menu.spec.ts
new file mode 100644
index 0000000..86c5590
--- /dev/null
+++ b/admin/src/app/components/main-menu/main-menu.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { MainMenu } from './main-menu';
+
+describe('MainMenu', () => {
+ let component: MainMenu;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [MainMenu]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(MainMenu);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/admin/src/app/components/main-menu/main-menu.ts b/admin/src/app/components/main-menu/main-menu.ts
new file mode 100644
index 0000000..abdd861
--- /dev/null
+++ b/admin/src/app/components/main-menu/main-menu.ts
@@ -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 {
+
+}
diff --git a/admin/src/app/components/navbar/navbar.css b/admin/src/app/components/navbar/navbar.css
new file mode 100644
index 0000000..e69de29
diff --git a/admin/src/app/components/navbar/navbar.html b/admin/src/app/components/navbar/navbar.html
new file mode 100644
index 0000000..26b93fa
--- /dev/null
+++ b/admin/src/app/components/navbar/navbar.html
@@ -0,0 +1,19 @@
+
diff --git a/admin/src/app/components/navbar/navbar.spec.ts b/admin/src/app/components/navbar/navbar.spec.ts
new file mode 100644
index 0000000..8893c93
--- /dev/null
+++ b/admin/src/app/components/navbar/navbar.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { Navbar } from './navbar';
+
+describe('Navbar', () => {
+ let component: Navbar;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [Navbar]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(Navbar);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/admin/src/app/components/navbar/navbar.ts b/admin/src/app/components/navbar/navbar.ts
new file mode 100644
index 0000000..016b44d
--- /dev/null
+++ b/admin/src/app/components/navbar/navbar.ts
@@ -0,0 +1,11 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'app-navbar',
+ imports: [],
+ templateUrl: './navbar.html',
+ styleUrl: './navbar.css',
+})
+export class Navbar {
+
+}
diff --git a/server/src/main.ts b/server/src/main.ts
index def5b75..b0ac276 100644
--- a/server/src/main.ts
+++ b/server/src/main.ts
@@ -7,6 +7,8 @@ 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')