autocomit
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
# See http://help.github.com/ignore-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
|
||||
|
||||
# System files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"ldpv2-frontend": {
|
||||
"projectType": "application",
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:application",
|
||||
"options": {
|
||||
"outputPath": "dist/ldpv2-frontend",
|
||||
"index": "src/index.html",
|
||||
"browser": "src/main.ts",
|
||||
"polyfills": ["zone.js"],
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"assets": ["src/assets"],
|
||||
"styles": ["src/styles.scss"],
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "500kb",
|
||||
"maximumError": "1mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "2kb",
|
||||
"maximumError": "4kb"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all"
|
||||
},
|
||||
"development": {
|
||||
"optimization": false,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"buildTarget": "ldpv2-frontend:build:production"
|
||||
},
|
||||
"development": {
|
||||
"buildTarget": "ldpv2-frontend:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development",
|
||||
"options": {
|
||||
"proxyConfig": "proxy.conf.json"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"polyfills": ["zone.js", "zone.js/testing"],
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"assets": ["src/assets"],
|
||||
"styles": ["src/styles.scss"],
|
||||
"scripts": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+291
@@ -0,0 +1,291 @@
|
||||
#!/bin/bash
|
||||
|
||||
BASE="/home/claude/ldpv2-monorepo/frontend/src/app"
|
||||
|
||||
# ========== MODELS ==========
|
||||
|
||||
cat > "$BASE/shared/models/user.model.ts" << 'TS'
|
||||
export interface User {
|
||||
id: string;
|
||||
username: string;
|
||||
email: string;
|
||||
role: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface LoginRequest {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface RegisterRequest {
|
||||
username: string;
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface AuthResponse {
|
||||
token: string;
|
||||
type: string;
|
||||
user: User;
|
||||
}
|
||||
TS
|
||||
|
||||
cat > "$BASE/shared/models/environment.model.ts" << 'TS'
|
||||
export interface Environment {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
isProduction: boolean;
|
||||
criticalityLevel?: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface CreateEnvironmentRequest {
|
||||
name: string;
|
||||
description?: string;
|
||||
isProduction?: boolean;
|
||||
criticalityLevel?: number;
|
||||
}
|
||||
|
||||
export interface UpdateEnvironmentRequest {
|
||||
name?: string;
|
||||
description?: string;
|
||||
isProduction?: boolean;
|
||||
criticalityLevel?: number;
|
||||
}
|
||||
|
||||
export interface Page<T> {
|
||||
content: T[];
|
||||
pageable: {
|
||||
pageNumber: number;
|
||||
pageSize: number;
|
||||
sort: {
|
||||
sorted: boolean;
|
||||
unsorted: boolean;
|
||||
};
|
||||
};
|
||||
totalElements: number;
|
||||
totalPages: number;
|
||||
last: boolean;
|
||||
first: boolean;
|
||||
size: number;
|
||||
number: number;
|
||||
numberOfElements: number;
|
||||
empty: boolean;
|
||||
}
|
||||
TS
|
||||
|
||||
echo "Models created"
|
||||
|
||||
# ========== SERVICES ==========
|
||||
|
||||
cat > "$BASE/core/auth/auth.service.ts" << 'TS'
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable, BehaviorSubject, tap } from 'rxjs';
|
||||
import { LoginRequest, RegisterRequest, AuthResponse, User } from '../../shared/models/user.model';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AuthService {
|
||||
private readonly TOKEN_KEY = 'auth_token';
|
||||
private readonly USER_KEY = 'current_user';
|
||||
private currentUserSubject = new BehaviorSubject<User | null>(this.getUserFromStorage());
|
||||
|
||||
public currentUser$ = this.currentUserSubject.asObservable();
|
||||
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
login(credentials: LoginRequest): Observable<AuthResponse> {
|
||||
return this.http.post<AuthResponse>('/api/auth/login', credentials).pipe(
|
||||
tap(response => {
|
||||
this.setSession(response);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
register(data: RegisterRequest): Observable<AuthResponse> {
|
||||
return this.http.post<AuthResponse>('/api/auth/register', data).pipe(
|
||||
tap(response => {
|
||||
this.setSession(response);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
logout(): void {
|
||||
localStorage.removeItem(this.TOKEN_KEY);
|
||||
localStorage.removeItem(this.USER_KEY);
|
||||
this.currentUserSubject.next(null);
|
||||
this.router.navigate(['/login']);
|
||||
}
|
||||
|
||||
isAuthenticated(): boolean {
|
||||
return !!this.getToken();
|
||||
}
|
||||
|
||||
getToken(): string | null {
|
||||
return localStorage.getItem(this.TOKEN_KEY);
|
||||
}
|
||||
|
||||
getCurrentUser(): User | null {
|
||||
return this.currentUserSubject.value;
|
||||
}
|
||||
|
||||
private setSession(authResponse: AuthResponse): void {
|
||||
localStorage.setItem(this.TOKEN_KEY, authResponse.token);
|
||||
localStorage.setItem(this.USER_KEY, JSON.stringify(authResponse.user));
|
||||
this.currentUserSubject.next(authResponse.user);
|
||||
}
|
||||
|
||||
private getUserFromStorage(): User | null {
|
||||
const userJson = localStorage.getItem(this.USER_KEY);
|
||||
return userJson ? JSON.parse(userJson) : null;
|
||||
}
|
||||
}
|
||||
TS
|
||||
|
||||
cat > "$BASE/features/environments/environment.service.ts" << 'TS'
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import {
|
||||
Environment,
|
||||
CreateEnvironmentRequest,
|
||||
UpdateEnvironmentRequest,
|
||||
Page
|
||||
} from '../../shared/models/environment.model';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class EnvironmentService {
|
||||
private readonly API_URL = '/api/environments';
|
||||
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
getEnvironments(
|
||||
page: number = 0,
|
||||
size: number = 20,
|
||||
sortBy: string = 'name',
|
||||
sortDirection: string = 'asc'
|
||||
): Observable<Page<Environment>> {
|
||||
const params = new HttpParams()
|
||||
.set('page', page.toString())
|
||||
.set('size', size.toString())
|
||||
.set('sortBy', sortBy)
|
||||
.set('sortDirection', sortDirection);
|
||||
|
||||
return this.http.get<Page<Environment>>(this.API_URL, { params });
|
||||
}
|
||||
|
||||
searchEnvironments(query: string, page: number = 0, size: number = 20): Observable<Page<Environment>> {
|
||||
const params = new HttpParams()
|
||||
.set('query', query)
|
||||
.set('page', page.toString())
|
||||
.set('size', size.toString());
|
||||
|
||||
return this.http.get<Page<Environment>>(`${this.API_URL}/search`, { params });
|
||||
}
|
||||
|
||||
getEnvironment(id: string): Observable<Environment> {
|
||||
return this.http.get<Environment>(`${this.API_URL}/${id}`);
|
||||
}
|
||||
|
||||
createEnvironment(data: CreateEnvironmentRequest): Observable<Environment> {
|
||||
return this.http.post<Environment>(this.API_URL, data);
|
||||
}
|
||||
|
||||
updateEnvironment(id: string, data: UpdateEnvironmentRequest): Observable<Environment> {
|
||||
return this.http.put<Environment>(`${this.API_URL}/${id}`, data);
|
||||
}
|
||||
|
||||
deleteEnvironment(id: string): Observable<void> {
|
||||
return this.http.delete<void>(`${this.API_URL}/${id}`);
|
||||
}
|
||||
}
|
||||
TS
|
||||
|
||||
echo "Services created"
|
||||
|
||||
# ========== INTERCEPTORS ==========
|
||||
|
||||
cat > "$BASE/core/interceptors/jwt.interceptor.ts" << 'TS'
|
||||
import { HttpInterceptorFn } from '@angular/common/http';
|
||||
import { inject } from '@angular/core';
|
||||
import { AuthService } from '../auth/auth.service';
|
||||
|
||||
export const jwtInterceptor: HttpInterceptorFn = (req, next) => {
|
||||
const authService = inject(AuthService);
|
||||
const token = authService.getToken();
|
||||
|
||||
if (token && !req.url.includes('/auth/')) {
|
||||
req = req.clone({
|
||||
setHeaders: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return next(req);
|
||||
};
|
||||
TS
|
||||
|
||||
cat > "$BASE/core/interceptors/error.interceptor.ts" << 'TS'
|
||||
import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';
|
||||
import { inject } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { catchError, throwError } from 'rxjs';
|
||||
|
||||
export const errorInterceptor: HttpInterceptorFn = (req, next) => {
|
||||
const router = inject(Router);
|
||||
|
||||
return next(req).pipe(
|
||||
catchError((error: HttpErrorResponse) => {
|
||||
if (error.status === 401) {
|
||||
// Unauthorized - redirect to login
|
||||
localStorage.removeItem('auth_token');
|
||||
localStorage.removeItem('current_user');
|
||||
router.navigate(['/login']);
|
||||
}
|
||||
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
};
|
||||
TS
|
||||
|
||||
echo "Interceptors created"
|
||||
|
||||
# ========== GUARDS ==========
|
||||
|
||||
cat > "$BASE/core/guards/auth.guard.ts" << 'TS'
|
||||
import { inject } from '@angular/core';
|
||||
import { Router, CanActivateFn } from '@angular/router';
|
||||
import { AuthService } from '../auth/auth.service';
|
||||
|
||||
export const authGuard: CanActivateFn = (route, state) => {
|
||||
const authService = inject(AuthService);
|
||||
const router = inject(Router);
|
||||
|
||||
if (authService.isAuthenticated()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Store the attempted URL for redirecting
|
||||
router.navigate(['/login'], { queryParams: { returnUrl: state.url } });
|
||||
return false;
|
||||
};
|
||||
TS
|
||||
|
||||
echo "Guards created"
|
||||
|
||||
echo "✓ All Angular TypeScript files created successfully!"
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "ldpv2-frontend",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^18.0.0",
|
||||
"@angular/common": "^18.0.0",
|
||||
"@angular/compiler": "^18.0.0",
|
||||
"@angular/core": "^18.0.0",
|
||||
"@angular/forms": "^18.0.0",
|
||||
"@angular/material": "^18.0.0",
|
||||
"@angular/platform-browser": "^18.0.0",
|
||||
"@angular/platform-browser-dynamic": "^18.0.0",
|
||||
"@angular/router": "^18.0.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^18.0.0",
|
||||
"@angular/cli": "^18.0.0",
|
||||
"@angular/compiler-cli": "^18.0.0",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"jasmine-core": "~5.1.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.4.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"/api": {
|
||||
"target": "http://localhost:8080",
|
||||
"secure": false,
|
||||
"changeOrigin": true,
|
||||
"logLevel": "debug"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { RouterOutlet } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
standalone: true,
|
||||
imports: [RouterOutlet],
|
||||
template: '<router-outlet></router-outlet>',
|
||||
styles: []
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'LDPv2 - Lifecycle Data Platform';
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { provideHttpClient, withInterceptors } from '@angular/common/http';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
import { routes } from './app.routes';
|
||||
import { jwtInterceptor } from './core/interceptors/jwt.interceptor';
|
||||
import { errorInterceptor } from './core/interceptors/error.interceptor';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||
provideRouter(routes),
|
||||
provideHttpClient(
|
||||
withInterceptors([jwtInterceptor, errorInterceptor])
|
||||
),
|
||||
provideAnimations()
|
||||
]
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Routes } from '@angular/router';
|
||||
import { authGuard } from './core/guards/auth.guard';
|
||||
|
||||
export const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: '/environments',
|
||||
pathMatch: 'full'
|
||||
},
|
||||
{
|
||||
path: 'login',
|
||||
loadComponent: () => import('./core/auth/login/login.component').then(m => m.LoginComponent)
|
||||
},
|
||||
{
|
||||
path: 'environments',
|
||||
canActivate: [authGuard],
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
loadComponent: () => import('./features/environments/environment-list/environment-list.component')
|
||||
.then(m => m.EnvironmentListComponent)
|
||||
},
|
||||
{
|
||||
path: 'new',
|
||||
loadComponent: () => import('./features/environments/environment-form/environment-form.component')
|
||||
.then(m => m.EnvironmentFormComponent)
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
loadComponent: () => import('./features/environments/environment-detail/environment-detail.component')
|
||||
.then(m => m.EnvironmentDetailComponent)
|
||||
},
|
||||
{
|
||||
path: ':id/edit',
|
||||
loadComponent: () => import('./features/environments/environment-form/environment-form.component')
|
||||
.then(m => m.EnvironmentFormComponent)
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,25 @@
|
||||
export interface User {
|
||||
id: string;
|
||||
username: string;
|
||||
email: string;
|
||||
role: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface LoginRequest {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface RegisterRequest {
|
||||
username: string;
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface AuthResponse {
|
||||
token: string;
|
||||
type: string;
|
||||
user: User;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>LDPv2 - Lifecycle Data Platform</title>
|
||||
<base href="/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,6 @@
|
||||
import { bootstrapApplication } from '@angular/platform-browser';
|
||||
import { AppComponent } from './app/app.component';
|
||||
import { appConfig } from './app/app.config';
|
||||
|
||||
bootstrapApplication(AppComponent, appConfig)
|
||||
.catch((err) => console.error(err));
|
||||
@@ -0,0 +1,39 @@
|
||||
/* Global Styles */
|
||||
@import '@angular/material/prebuilt-themes/indigo-pink.css';
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Roboto, "Helvetica Neue", sans-serif;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.mat-mdc-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #f44336;
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.success-message {
|
||||
color: #4caf50;
|
||||
font-size: 14px;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/app",
|
||||
"types": []
|
||||
},
|
||||
"files": ["src/main.ts"],
|
||||
"include": ["src/**/*.d.ts"]
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist/out-tsc",
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"useDefineForClassFields": false,
|
||||
"lib": ["ES2022", "dom"]
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"strictTemplates": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/spec",
|
||||
"types": ["jasmine"]
|
||||
},
|
||||
"include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user