diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 48ca6fbb5d1debf221a15c1bc2606816109255fc..f5788453e7e148b54e4c11a1b5a5a4b6e028413b 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -20,7 +20,7 @@ lint: disabled: - git-diff-check enabled: - - checkov@3.2.336 + - checkov@3.2.341 - dotenv-linter@3.3.0 - hadolint@2.12.1-beta - markdownlint@0.43.0 diff --git a/angular/RestClient/src/app/app.component.ts b/angular/RestClient/src/app/app.component.ts index f6060eca2fe714c3b89bb6ee62c3e02b26f37a94..a1ed6618427b136f4c7574b3fa019fd416e7adf7 100644 --- a/angular/RestClient/src/app/app.component.ts +++ b/angular/RestClient/src/app/app.component.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; import { RouterOutlet } from '@angular/router'; -import { NavigationComponent } from './core/navigation/navigation.component'; +import { NavigationComponent } from '@shared'; @Component({ selector: 'app-root', diff --git a/angular/RestClient/src/app/app.config.ts b/angular/RestClient/src/app/app.config.ts index c0664f634af159df0208b1a877011ce66c1471ac..18eaee48c05caac28365f5076338fd28cc8cd20c 100644 --- a/angular/RestClient/src/app/app.config.ts +++ b/angular/RestClient/src/app/app.config.ts @@ -9,7 +9,7 @@ import { import { provideAnimationsAsync } from '@angular/platform-browser/animations/async'; import { ReactiveFormsModule } from '@angular/forms'; // Added import for ReactiveFormsModule import { provideNativeDateAdapter } from '@angular/material/core'; -import { authRequest } from './security/auth.interceptor'; +import { authRequest } from '@core/interceptors'; export const appConfig: ApplicationConfig = { providers: [ diff --git a/angular/RestClient/src/app/app.routes.ts b/angular/RestClient/src/app/app.routes.ts index 4be490fc5813f4e0bca3846bfd83e67c06ce0654..2853c5e2d788dcaa89454b0004ca0fcf1b86e8e0 100644 --- a/angular/RestClient/src/app/app.routes.ts +++ b/angular/RestClient/src/app/app.routes.ts @@ -1,177 +1,41 @@ -import { Route, Routes } from '@angular/router'; -import { HotelListComponent } from './core/features/hotel/hotel-list/hotel-list.component'; -import { BookingComponent } from './core/features/bookings/booking/booking.component'; -import { HotelRegisterComponent } from './core/features/hotel/hotel-register/hotel-register.component'; -import { MainPageComponent } from './core/features/user/main-page/main-page.component'; -import { BookingListComponent } from './core/features/bookings/booking-list/booking-list.component'; -import { UserBookingListComponent } from './core/features/user/user-booking-list/user-booking-list.component'; -import { UserFormComponent } from './core/features/user/user-form/user-form.component'; +import { HotelListComponent } from './features/hotels/hotel-list/hotel-list.component'; +import { BookingComponent } from './features/bookings/booking/booking.component'; +import { HotelRegisterComponent } from './features/hotels/hotel-register/hotel-register.component'; +import { MainPageComponent } from './features/users/main-page/main-page.component'; +import { UserBookingListComponent } from './features/bookings/user-booking-list/user-booking-list.component'; +import { UserFormComponent } from './features/users/user-form/user-form.component'; import { UnauthorizedComponent } from './page/unauthorized/unauthorized.component'; -import { rolGuard } from './security/rol.guard'; -import { UserRol, UserRolesArray } from './types'; - -interface RouteData { - expectedRole: UserRol | UserRol[]; -} - -type AppRoute = Omit<Route, 'data'> & { - data?: RouteData; -}; +import { AppRoute, UserRolesArray } from './core/models'; +import { rolGuard, rolGuardChild } from '@core/guards'; export const routes: AppRoute[] = [ // Auth { - path: 'login', - component: UserFormComponent, - }, - { - path: 'register', - component: UserFormComponent, + path: '', + loadChildren: () => import('./features/auth').then((m) => m.AUTH_ROUTES), }, - // Hoteles { - path: 'hotels', // Ruta para la lista de hoteles - component: HotelListComponent, - }, - { - path: 'hotels/register', // Registrar nuevo hotel - component: HotelRegisterComponent, - canActivate: [rolGuard], - data: { expectedRole: 'HOTEL_ADMIN' }, - }, - { - path: 'hotels/:id', // Hotel concreto - component: HotelRegisterComponent, + path: 'hotels', + loadChildren: () => + import('./features/hotels').then((m) => m.HOTELS_ROUTES), }, - // Usuario { - path: 'me', // Main - canActivate: [rolGuard], - data: { expectedRole: UserRolesArray }, - component: UserFormComponent, - }, - { - path: 'me/edit', // Main - component: UserFormComponent, - canActivate: [rolGuard], - data: { expectedRole: UserRolesArray }, - }, - { - path: 'me/change-passwd', // Main - component: UserFormComponent, - canActivate: [rolGuard], - data: { expectedRole: UserRolesArray }, - }, - // Usuario HOTEL admin - { - path: 'me/hotels', - component: HotelListComponent, - canActivate: [rolGuard], - data: { expectedRole: 'HOTEL_ADMIN' }, - }, - { - path: 'me/hotels/:id', - component: HotelRegisterComponent, - canActivate: [rolGuard], - data: { expectedRole: 'HOTEL_ADMIN' }, - }, - // { - // path: 'me/hotels/:id/bookings', - // component: BookingListComponent, - // }, - // { - // path: 'me/hotels/:id/rooms/:id/bookings', - // component: BookingListComponent, - // }, - - // Usuario Cliente - { - path: 'me/bookings', - component: UserBookingListComponent, - canActivate: [rolGuard], - data: { expectedRole: 'CLIENT' }, - }, - { - path: 'me/bookings/:id', - component: BookingComponent, + path: 'me', canActivate: [rolGuard], - data: { expectedRole: 'CLIENT' }, + canActivateChild: [rolGuardChild], + loadChildren: () => import('./features/users').then((m) => m.USERS_ROUTES), }, - { - path: 'me/bookings/new', - component: BookingComponent, - canActivate: [rolGuard], - data: { expectedRole: 'CLIENT' }, - }, - // Administrador { - path: 'admin', // Main - component: UserFormComponent, - canActivate: [rolGuard], - data: { expectedRole: 'ADMIN' }, - }, - { - path: 'admin/users', // Main - component: MainPageComponent, - canActivate: [rolGuard], - data: { expectedRole: 'ADMIN' }, - }, - { - path: 'admin/users/:id', // Main - component: UserFormComponent, - canActivate: [rolGuard], - data: { expectedRole: 'ADMIN' }, - }, - { - path: 'admin/users/:id/edit', // Main - component: UserFormComponent, + path: 'admin', canActivate: [rolGuard], + canActivateChild: [rolGuardChild], data: { expectedRole: 'ADMIN' }, + loadChildren: () => import('./features/admin').then((m) => m.ADMIN_ROUTES), }, - { - path: 'admin/users/:id/change-passwd', // Main - component: UserFormComponent, - canActivate: [rolGuard], - data: { expectedRole: 'ADMIN' }, - }, - { - path: 'admin/users/:id/bookings', - component: UserBookingListComponent, - canActivate: [rolGuard], - data: { expectedRole: 'ADMIN' }, - }, - // { - // path: 'admin/users/:id/bookings/:bookingId', - // component: BookingComponent, - // }, - { - path: 'admin/users/:id/hotels', - component: HotelListComponent, - canActivate: [rolGuard], - data: { expectedRole: 'ADMIN' }, - }, - { - path: 'admin/users/:userId/hotels/:id', - component: HotelRegisterComponent, - canActivate: [rolGuard], - data: { expectedRole: 'ADMIN' }, - }, - // { - // path: 'admin/users/:userId/hotels/:id/bookings', - // component: BookingListComponent, - // canActivate: [rolGuard], - // data: { expectedRole: 'ADMIN' }, - // }, - // { - // path: 'admin/users/:userId/hotels/:hotelId/rooms/:id/bookings', - // component: BookingListComponent, - // canActivate: [rolGuard], - // data: { expectedRole: 'ADMIN' }, - // }, - + // Página no autorizada { path: 'unauthorized', component: UnauthorizedComponent, diff --git a/angular/RestClient/src/app/core/guards/index.ts b/angular/RestClient/src/app/core/guards/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..7c221e12c653c53b072a570f871bdbdc37d3909e --- /dev/null +++ b/angular/RestClient/src/app/core/guards/index.ts @@ -0,0 +1 @@ +export * from './rol.guard'; diff --git a/angular/RestClient/src/app/security/rol.guard.spec.ts b/angular/RestClient/src/app/core/guards/rol.guard.spec.ts similarity index 100% rename from angular/RestClient/src/app/security/rol.guard.spec.ts rename to angular/RestClient/src/app/core/guards/rol.guard.spec.ts diff --git a/angular/RestClient/src/app/core/guards/rol.guard.ts b/angular/RestClient/src/app/core/guards/rol.guard.ts new file mode 100644 index 0000000000000000000000000000000000000000..39aca9bb6fe82f1a6dc9abe6e7d92eb0bccb9944 --- /dev/null +++ b/angular/RestClient/src/app/core/guards/rol.guard.ts @@ -0,0 +1,78 @@ +import { inject } from '@angular/core'; +import { + ActivatedRouteSnapshot, + CanActivateChildFn, + CanActivateFn, + Router, +} from '@angular/router'; +import { Session, UserRol } from '@core/models'; +import { SessionService } from '@core/services'; + +import { map } from 'rxjs'; + +/** + * Obtiene el rol de la ruta padre si el hijo no define uno. + * Navega hacia los ancestros buscando un rol especificado en `data`. + */ +function getInheritedRole(route: ActivatedRouteSnapshot): string | undefined { + let parent = route.parent; // Comenzar desde el padre inmediato + while (parent) { + if (parent.data && parent.data['rol']) { + return parent.data['rol']; // Retorna el primer rol encontrado + } + parent = parent.parent; // Continuar hacia arriba en el árbol + } + return undefined; // No se encontró un rol definido en los ancestros +} + +function verifyRol(expectedRole: UserRol) { + const sessionService = inject(SessionService); + const router = inject(Router); + // Verifica si el usuario tiene sesión activa + const session = sessionService.isValid(); + + if (!session) { + console.log('no session'); + router.navigate(['/login']); + return false; + } + return sessionService.getSession().pipe( + map((session: Session | null) => { + if (!session) return false; + + if ( + Array.isArray(expectedRole) && + (expectedRole as UserRol[]).includes(session.rol) + ) { + console.log('Rol in Rol arry'); + return true; + } else if (session.rol === expectedRole) { + console.log('Rol valido'); + return true; + } + console.log('Unautorizado'); + + // Redirige si el usuario no tiene el rol necesario + router.navigate(['/unauthorized']); + return false; + }) + ); +} + +export const rolGuard: CanActivateFn = (route, state) => { + // Obtén el rol esperado desde los datos de la ruta + const expectedRole = route.data?.['expectedRole']; + return verifyRol(expectedRole); +}; + +export const rolGuardChild: CanActivateChildFn = (childRoute, state) => { + // Obtener el rol de la ruta hija si está especificado y en caso + // de no especificarse se busca en el/los padres + let requiredRol = childRoute.data['rol'] ?? getInheritedRole(childRoute); + + // Si no hay rol especificado se supone libre de verificación + if (!requiredRol) return true; + + // Verificar si el usuario tiene el rol requerido + return verifyRol(requiredRol); +}; diff --git a/angular/RestClient/src/app/core/interceptors/auth.interceptor.spec.ts b/angular/RestClient/src/app/core/interceptors/auth.interceptor.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..50f38c8abbeb5059524cfd0077b10a09260936d7 --- /dev/null +++ b/angular/RestClient/src/app/core/interceptors/auth.interceptor.spec.ts @@ -0,0 +1,17 @@ +import { TestBed } from '@angular/core/testing'; +import { HttpInterceptorFn } from '@angular/common/http'; + +import { authInterceptor } from './auth.interceptor'; + +describe('authInterceptor', () => { + const interceptor: HttpInterceptorFn = (req, next) => + TestBed.runInInjectionContext(() => authInterceptor(req, next)); + + beforeEach(() => { + TestBed.configureTestingModule({}); + }); + + it('should be created', () => { + expect(interceptor).toBeTruthy(); + }); +}); diff --git a/angular/RestClient/src/app/security/auth.interceptor.ts b/angular/RestClient/src/app/core/interceptors/auth.interceptor.ts similarity index 88% rename from angular/RestClient/src/app/security/auth.interceptor.ts rename to angular/RestClient/src/app/core/interceptors/auth.interceptor.ts index 4a3dadfdaa637c526d0beb436546a9117359f49f..11c9d68089278f997f5c0d4bc0360144f9cf1af9 100644 --- a/angular/RestClient/src/app/security/auth.interceptor.ts +++ b/angular/RestClient/src/app/core/interceptors/auth.interceptor.ts @@ -1,7 +1,6 @@ import { HttpInterceptorFn } from '@angular/common/http'; import { inject } from '@angular/core'; -import { LocalStorageService } from '../shared/local-storage.service'; -import { SessionService } from '../shared/session.service'; +import { SessionService } from '@core/services/session/session.service'; const excluded = ['/login', '/register']; diff --git a/angular/RestClient/src/app/core/interceptors/index.ts b/angular/RestClient/src/app/core/interceptors/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..c59f58f0962748f55ad8510233508f2e2c9ad3e0 --- /dev/null +++ b/angular/RestClient/src/app/core/interceptors/index.ts @@ -0,0 +1 @@ +export * from './auth.interceptor'; diff --git a/angular/RestClient/src/app/types/Address.d.ts b/angular/RestClient/src/app/core/models/Address.interface.ts similarity index 100% rename from angular/RestClient/src/app/types/Address.d.ts rename to angular/RestClient/src/app/core/models/Address.interface.ts diff --git a/angular/RestClient/src/app/types/Booking.d.ts b/angular/RestClient/src/app/core/models/Booking.interface.ts similarity index 57% rename from angular/RestClient/src/app/types/Booking.d.ts rename to angular/RestClient/src/app/core/models/Booking.interface.ts index 797c8aafb11b15aff5ac7cb9a3f4b510d7628284..e18c7467f930fb9b2410fb8e377c1d3faa4cdf71 100644 --- a/angular/RestClient/src/app/types/Booking.d.ts +++ b/angular/RestClient/src/app/core/models/Booking.interface.ts @@ -1,5 +1,5 @@ -import { Room } from './Room'; -import { User } from './User'; +import { Room } from './Room.interface'; +import { User } from './User.interface'; export interface Booking { id: number; diff --git a/angular/RestClient/src/app/types/Hotel.d.ts b/angular/RestClient/src/app/core/models/Hotel.interface.ts similarity index 51% rename from angular/RestClient/src/app/types/Hotel.d.ts rename to angular/RestClient/src/app/core/models/Hotel.interface.ts index c4183aa7f7055c24016b6b4325038a16cdaca09c..38c70511fefaeb20d3ddde3baf3f469c42d7f79e 100644 --- a/angular/RestClient/src/app/types/Hotel.d.ts +++ b/angular/RestClient/src/app/core/models/Hotel.interface.ts @@ -1,5 +1,5 @@ -import { Address } from './Address'; -import { Room } from './Room'; +import { Address } from './Address.interface'; +import { Room } from './Room.interface'; export interface Hotel { id: number; diff --git a/angular/RestClient/src/app/types/Room.d.ts b/angular/RestClient/src/app/core/models/Room.interface.ts similarity index 67% rename from angular/RestClient/src/app/types/Room.d.ts rename to angular/RestClient/src/app/core/models/Room.interface.ts index 0c54fdcbfee2f4da7aa34f3727dd21421acbb048..a00b1e53c6ad8312f0f2edf78a99c891c4506716 100644 --- a/angular/RestClient/src/app/types/Room.d.ts +++ b/angular/RestClient/src/app/core/models/Room.interface.ts @@ -1,4 +1,7 @@ export type RoomType = 'SINGLE' | 'DOUBLE' | 'SUITE'; + +export const roomTypeArray: RoomType[] = ['SINGLE', 'DOUBLE', 'SUITE']; + export interface Room { id: number; roomNumber: String; diff --git a/angular/RestClient/src/app/core/models/Route.interface.ts b/angular/RestClient/src/app/core/models/Route.interface.ts new file mode 100644 index 0000000000000000000000000000000000000000..4653cda109a68fd4945cd14e81a1b65ccb5eef26 --- /dev/null +++ b/angular/RestClient/src/app/core/models/Route.interface.ts @@ -0,0 +1,10 @@ +import { Route } from '@angular/router'; +import { UserRol } from './User.interface'; + +interface RouteData { + expectedRole: UserRol | UserRol[]; +} + +export type AppRoute = Omit<Route, 'data'> & { + data?: RouteData; +}; diff --git a/angular/RestClient/src/app/types/Session.d.ts b/angular/RestClient/src/app/core/models/Session.interface.ts similarity index 62% rename from angular/RestClient/src/app/types/Session.d.ts rename to angular/RestClient/src/app/core/models/Session.interface.ts index 4dd1f742866d586be8be2ac02487d23ede92a486..59c7781b33b4275813576b9620cc8c80657c6042 100644 --- a/angular/RestClient/src/app/types/Session.d.ts +++ b/angular/RestClient/src/app/core/models/Session.interface.ts @@ -1,3 +1,5 @@ +import { UserRol } from './User.interface'; + export interface Session { id: number; name: string; @@ -5,7 +7,7 @@ export interface Session { rol: UserRol; } -interface PersistenToken { +export interface PersistenToken { token: string; session?: Session; } diff --git a/angular/RestClient/src/app/types/User.d.ts b/angular/RestClient/src/app/core/models/User.interface.ts similarity index 86% rename from angular/RestClient/src/app/types/User.d.ts rename to angular/RestClient/src/app/core/models/User.interface.ts index 277f806a74fb1b2f854d733cc10acca67f18b840..3854af0d5782e5fd5ff36412f94bd73c998b50c3 100644 --- a/angular/RestClient/src/app/types/User.d.ts +++ b/angular/RestClient/src/app/core/models/User.interface.ts @@ -15,6 +15,7 @@ export interface HotelAdmin extends User { } export type UserRol = 'ADMIN' | 'CLIENT' | 'HOTEL_ADMIN'; +export const UserRolesArray: UserRol[] = ['CLIENT', 'HOTEL_ADMIN', 'ADMIN']; export type UserStateFilter = 'All' | UserState; diff --git a/angular/RestClient/src/app/core/models/index.ts b/angular/RestClient/src/app/core/models/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..311b36b9fe22a4aa133d6cf0ec105a301eb89bd6 --- /dev/null +++ b/angular/RestClient/src/app/core/models/index.ts @@ -0,0 +1,9 @@ +export * from './User.interface'; +export * from './Address.interface'; +export * from './Hotel.interface'; +export * from './Room.interface'; +export * from './Room.interface'; +export * from './Booking.interface'; +export * from './User.interface'; +export * from './Session.interface'; +export * from './Route.interface'; diff --git a/angular/RestClient/src/app/shared/auth-client.service.spec.ts b/angular/RestClient/src/app/core/services/api/auth/auth-client.service.spec.ts similarity index 86% rename from angular/RestClient/src/app/shared/auth-client.service.spec.ts rename to angular/RestClient/src/app/core/services/api/auth/auth-client.service.spec.ts index 718c264fe9fa26ade0a95ea726d68cf899584f1d..74e8f4811bfa8256bdccee865e2b86389b9c25f5 100644 --- a/angular/RestClient/src/app/shared/auth-client.service.spec.ts +++ b/angular/RestClient/src/app/core/services/api/auth/auth-client.service.spec.ts @@ -1,9 +1,11 @@ import { TestBed } from '@angular/core/testing'; import { AuthClientService } from './auth-client.service'; +import { Session } from '@core/models'; describe('AuthClientService', () => { let service: AuthClientService; + let s: Session; beforeEach(() => { TestBed.configureTestingModule({}); diff --git a/angular/RestClient/src/app/shared/auth-client.service.ts b/angular/RestClient/src/app/core/services/api/auth/auth-client.service.ts similarity index 94% rename from angular/RestClient/src/app/shared/auth-client.service.ts rename to angular/RestClient/src/app/core/services/api/auth/auth-client.service.ts index 99d3ebbfc0c91cc88ba2f4266cd1f895ccbfebd9..b2b499bd6777e4279bea462a654c645225126a6e 100644 --- a/angular/RestClient/src/app/shared/auth-client.service.ts +++ b/angular/RestClient/src/app/core/services/api/auth/auth-client.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { environment } from '../../environments/environment'; +import { environment } from '../../../../../environments/environment'; import { HttpClient } from '@angular/common/http'; @Injectable({ diff --git a/angular/RestClient/src/app/shared/booking-client.service.spec.ts b/angular/RestClient/src/app/core/services/api/bookings/booking-client.service.spec.ts similarity index 100% rename from angular/RestClient/src/app/shared/booking-client.service.spec.ts rename to angular/RestClient/src/app/core/services/api/bookings/booking-client.service.spec.ts diff --git a/angular/RestClient/src/app/shared/booking-client.service.ts b/angular/RestClient/src/app/core/services/api/bookings/booking-client.service.ts similarity index 87% rename from angular/RestClient/src/app/shared/booking-client.service.ts rename to angular/RestClient/src/app/core/services/api/bookings/booking-client.service.ts index a15867105f00cf0bd6ea093a4a64bebbb3fd048f..e5046766faeb5e6998ba18683852f0aa4046ea6f 100644 --- a/angular/RestClient/src/app/shared/booking-client.service.ts +++ b/angular/RestClient/src/app/core/services/api/bookings/booking-client.service.ts @@ -1,8 +1,8 @@ import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable } from 'rxjs'; -import { environment } from '../../environments/environment'; -import { Booking } from '../types/Booking'; // Ajusta la ruta a tu modelo Booking +import { environment } from '../../../../../environments/environment'; +import { Booking } from '../../../models/Booking.interface'; // Ajusta la ruta a tu modelo Booking @Injectable({ providedIn: 'root', diff --git a/angular/RestClient/src/app/shared/hotel-client.service.spec.ts b/angular/RestClient/src/app/core/services/api/hotels/hotel-client.service.spec.ts similarity index 100% rename from angular/RestClient/src/app/shared/hotel-client.service.spec.ts rename to angular/RestClient/src/app/core/services/api/hotels/hotel-client.service.spec.ts diff --git a/angular/RestClient/src/app/shared/hotel-client.service.ts b/angular/RestClient/src/app/core/services/api/hotels/hotel-client.service.ts similarity index 84% rename from angular/RestClient/src/app/shared/hotel-client.service.ts rename to angular/RestClient/src/app/core/services/api/hotels/hotel-client.service.ts index 5c50231651d1e0bc61b036e3e1220d468009a4f8..5b5336c075d5e77346631fea7aea5ee766ef0d4c 100644 --- a/angular/RestClient/src/app/shared/hotel-client.service.ts +++ b/angular/RestClient/src/app/core/services/api/hotels/hotel-client.service.ts @@ -1,8 +1,8 @@ import { Injectable } from '@angular/core'; -import { environment } from '../../environments/environment'; +import { environment } from '../../../../../environments/environment'; import { HttpClient } from '@angular/common/http'; -import { Hotel, Room } from '../types'; -import { SessionService } from './session.service'; +import { Hotel, Room } from '../../../models'; +import { SessionService } from '../../session/session.service'; import { catchError, map, switchMap, throwError } from 'rxjs'; @Injectable({ @@ -28,13 +28,15 @@ export class HotelClientService { return this.http.get<Hotel[]>(url, { params: { start, end } }); } - getAllHotelsByUser(userId:number, startDate?: Date, endDate?: Date) { + getAllHotelsByUser(userId: number, startDate?: Date, endDate?: Date) { const url = `${this.URI}`; if (!startDate || !endDate) - return this.http.get<Hotel[]>(url, { params: { managerId:userId } }); + return this.http.get<Hotel[]>(url, { params: { managerId: userId } }); const start = new Date(startDate).toISOString().split('T')[0]; const end = new Date(endDate).toISOString().split('T')[0]; - return this.http.get<Hotel[]>(url, { params: { managerId:userId, start, end } }); + return this.http.get<Hotel[]>(url, { + params: { managerId: userId, start, end }, + }); } deleteHotel(id: number) { diff --git a/angular/RestClient/src/app/shared/user-client.service.spec.ts b/angular/RestClient/src/app/core/services/api/users/user-client.service.spec.ts similarity index 100% rename from angular/RestClient/src/app/shared/user-client.service.spec.ts rename to angular/RestClient/src/app/core/services/api/users/user-client.service.spec.ts diff --git a/angular/RestClient/src/app/shared/user-client.service.ts b/angular/RestClient/src/app/core/services/api/users/user-client.service.ts similarity index 87% rename from angular/RestClient/src/app/shared/user-client.service.ts rename to angular/RestClient/src/app/core/services/api/users/user-client.service.ts index 63575df4f440973090cde151f50137fb002b9d5d..ec9e5d7d4222c7152195c7ed9f4e7521dab1e865 100644 --- a/angular/RestClient/src/app/shared/user-client.service.ts +++ b/angular/RestClient/src/app/core/services/api/users/user-client.service.ts @@ -1,8 +1,8 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { environment } from '../../environments/environment'; -import { Client, Session, User, UserState } from '../types'; -import { SessionService } from './session.service'; +import { environment } from '../../../../../environments/environment'; +import { Client, Session, User, UserState } from '../../../models'; +import { SessionService } from '../../session/session.service'; import { tap } from 'rxjs'; @Injectable({ diff --git a/angular/RestClient/src/app/core/services/index.ts b/angular/RestClient/src/app/core/services/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..deceec994328a11a1cff59c7492ee2cfca98d087 --- /dev/null +++ b/angular/RestClient/src/app/core/services/index.ts @@ -0,0 +1,6 @@ +export * from './api/auth/auth-client.service'; +export * from './api/bookings/booking-client.service'; +export * from './api/hotels/hotel-client.service'; +export * from './api/users/user-client.service'; +export * from './session/session.service'; +export * from './storage/local-storage.service'; diff --git a/angular/RestClient/src/app/shared/session.service.spec.ts b/angular/RestClient/src/app/core/services/session/session.service.spec.ts similarity index 100% rename from angular/RestClient/src/app/shared/session.service.spec.ts rename to angular/RestClient/src/app/core/services/session/session.service.spec.ts diff --git a/angular/RestClient/src/app/shared/session.service.ts b/angular/RestClient/src/app/core/services/session/session.service.ts similarity index 95% rename from angular/RestClient/src/app/shared/session.service.ts rename to angular/RestClient/src/app/core/services/session/session.service.ts index ea460cdfac612b165b7f944967472da25f7a6cca..743250250b2b302a5cc7fe2f491debf70c7145fb 100644 --- a/angular/RestClient/src/app/shared/session.service.ts +++ b/angular/RestClient/src/app/core/services/session/session.service.ts @@ -1,10 +1,10 @@ import { Injectable } from '@angular/core'; -import { LocalStorageService } from './local-storage.service'; -import { PersistenToken, Session, User, UserRol } from '../types'; +import { LocalStorageService } from '../storage/local-storage.service'; +import { PersistenToken, Session, UserRol } from '../../models'; import { BehaviorSubject, Observable, throwError } from 'rxjs'; import { catchError, map, tap } from 'rxjs/operators'; import { jwtDecode } from 'jwt-decode'; -import { AuthClientService } from './auth-client.service'; +import { AuthClientService } from '../api/auth/auth-client.service'; import { Router } from '@angular/router'; interface JWTDecoded { diff --git a/angular/RestClient/src/app/shared/local-storage.service.spec.ts b/angular/RestClient/src/app/core/services/storage/local-storage.service.spec.ts similarity index 100% rename from angular/RestClient/src/app/shared/local-storage.service.spec.ts rename to angular/RestClient/src/app/core/services/storage/local-storage.service.spec.ts diff --git a/angular/RestClient/src/app/shared/local-storage.service.ts b/angular/RestClient/src/app/core/services/storage/local-storage.service.ts similarity index 100% rename from angular/RestClient/src/app/shared/local-storage.service.ts rename to angular/RestClient/src/app/core/services/storage/local-storage.service.ts diff --git a/angular/RestClient/src/app/features/admin/index.ts b/angular/RestClient/src/app/features/admin/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..75585f85e1a443e22e77ca151df63981df56b2c4 --- /dev/null +++ b/angular/RestClient/src/app/features/admin/index.ts @@ -0,0 +1 @@ +export * from '../users/admin.routes'; diff --git a/angular/RestClient/src/app/features/auth/auth.routes.ts b/angular/RestClient/src/app/features/auth/auth.routes.ts new file mode 100644 index 0000000000000000000000000000000000000000..86d8183271d8f149246cffcce0128561545a52cb --- /dev/null +++ b/angular/RestClient/src/app/features/auth/auth.routes.ts @@ -0,0 +1,13 @@ +import { AppRoute } from '@core/models'; +import { UserFormComponent } from 'app/features/users'; + +export const AUTH_ROUTES: AppRoute[] = [ + { + path: 'login', + component: UserFormComponent, + }, + { + path: 'register', + component: UserFormComponent, + }, +]; diff --git a/angular/RestClient/src/app/features/auth/index.ts b/angular/RestClient/src/app/features/auth/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..bb519ed37118e740f94bc05f1a56483aa0cfbe95 --- /dev/null +++ b/angular/RestClient/src/app/features/auth/index.ts @@ -0,0 +1,3 @@ +export * from './auth.routes'; +export * from './login/login.component'; +export * from './register/register.component'; diff --git a/angular/RestClient/src/app/core/features/auth/login/login.component.css b/angular/RestClient/src/app/features/auth/login/login.component.css similarity index 100% rename from angular/RestClient/src/app/core/features/auth/login/login.component.css rename to angular/RestClient/src/app/features/auth/login/login.component.css diff --git a/angular/RestClient/src/app/core/features/auth/login/login.component.html b/angular/RestClient/src/app/features/auth/login/login.component.html similarity index 100% rename from angular/RestClient/src/app/core/features/auth/login/login.component.html rename to angular/RestClient/src/app/features/auth/login/login.component.html diff --git a/angular/RestClient/src/app/core/features/auth/login/login.component.spec.ts b/angular/RestClient/src/app/features/auth/login/login.component.spec.ts similarity index 100% rename from angular/RestClient/src/app/core/features/auth/login/login.component.spec.ts rename to angular/RestClient/src/app/features/auth/login/login.component.spec.ts diff --git a/angular/RestClient/src/app/core/features/auth/login/login.component.ts b/angular/RestClient/src/app/features/auth/login/login.component.ts similarity index 91% rename from angular/RestClient/src/app/core/features/auth/login/login.component.ts rename to angular/RestClient/src/app/features/auth/login/login.component.ts index d3bed7a4132fa619c110a0ebd74c3e8225e16216..c28e44d79ac016d6848e77571201b86e14c89031 100644 --- a/angular/RestClient/src/app/core/features/auth/login/login.component.ts +++ b/angular/RestClient/src/app/features/auth/login/login.component.ts @@ -12,8 +12,8 @@ import { MatFormFieldModule } from '@angular/material/form-field'; import { MatSelectModule } from '@angular/material/select'; import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { Router } from '@angular/router'; -import { SessionService } from '../../../../shared/session.service'; -import { UserRol, UserRolesArray } from '../../../../types'; +import { SessionService } from '../../../core/services/session/session.service'; +import { UserRol, UserRolesArray } from '../../../core/models'; @Component({ standalone: true, diff --git a/angular/RestClient/src/app/core/features/auth/register/register.component.css b/angular/RestClient/src/app/features/auth/register/register.component.css similarity index 100% rename from angular/RestClient/src/app/core/features/auth/register/register.component.css rename to angular/RestClient/src/app/features/auth/register/register.component.css diff --git a/angular/RestClient/src/app/core/features/auth/register/register.component.html b/angular/RestClient/src/app/features/auth/register/register.component.html similarity index 100% rename from angular/RestClient/src/app/core/features/auth/register/register.component.html rename to angular/RestClient/src/app/features/auth/register/register.component.html diff --git a/angular/RestClient/src/app/core/features/auth/register/register.component.spec.ts b/angular/RestClient/src/app/features/auth/register/register.component.spec.ts similarity index 100% rename from angular/RestClient/src/app/core/features/auth/register/register.component.spec.ts rename to angular/RestClient/src/app/features/auth/register/register.component.spec.ts diff --git a/angular/RestClient/src/app/core/features/auth/register/register.component.ts b/angular/RestClient/src/app/features/auth/register/register.component.ts similarity index 91% rename from angular/RestClient/src/app/core/features/auth/register/register.component.ts rename to angular/RestClient/src/app/features/auth/register/register.component.ts index e594c19b132e84967d2509e2c3e4a5053a66f048..62d97d6b8697d5e3dbf001929c5be12bbf70cdee 100644 --- a/angular/RestClient/src/app/core/features/auth/register/register.component.ts +++ b/angular/RestClient/src/app/features/auth/register/register.component.ts @@ -12,8 +12,8 @@ import { MatFormFieldModule } from '@angular/material/form-field'; import { MatSelectModule } from '@angular/material/select'; import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { Router } from '@angular/router'; -import { AuthClientService } from '../../../../shared/auth-client.service'; -import { SessionService } from '../../../../shared/session.service'; +import { AuthClientService } from '../../../core/services/api/auth/auth-client.service'; +import { SessionService } from '../../../core/services/session/session.service'; // TODO agregar selector de roles @@ -60,7 +60,7 @@ export class RegisterComponent { this.authClient.register(name, email, password).subscribe({ next: (res: any) => { console.log({ res }); - this.sessionManager.login(res); + this.sessionManager.login(res, ''); alert('Usuario registrado con éxito.'); this.router.navigate(['/']); // Redirigir al login }, diff --git a/angular/RestClient/src/app/core/features/bookings/booking-list/booking-list.component.css b/angular/RestClient/src/app/features/bookings/booking-list/booking-list.component.css similarity index 100% rename from angular/RestClient/src/app/core/features/bookings/booking-list/booking-list.component.css rename to angular/RestClient/src/app/features/bookings/booking-list/booking-list.component.css diff --git a/angular/RestClient/src/app/core/features/bookings/booking-list/booking-list.component.html b/angular/RestClient/src/app/features/bookings/booking-list/booking-list.component.html similarity index 100% rename from angular/RestClient/src/app/core/features/bookings/booking-list/booking-list.component.html rename to angular/RestClient/src/app/features/bookings/booking-list/booking-list.component.html diff --git a/angular/RestClient/src/app/core/features/bookings/booking-list/booking-list.component.spec.ts b/angular/RestClient/src/app/features/bookings/booking-list/booking-list.component.spec.ts similarity index 100% rename from angular/RestClient/src/app/core/features/bookings/booking-list/booking-list.component.spec.ts rename to angular/RestClient/src/app/features/bookings/booking-list/booking-list.component.spec.ts diff --git a/angular/RestClient/src/app/core/features/bookings/booking-list/booking-list.component.ts b/angular/RestClient/src/app/features/bookings/booking-list/booking-list.component.ts similarity index 91% rename from angular/RestClient/src/app/core/features/bookings/booking-list/booking-list.component.ts rename to angular/RestClient/src/app/features/bookings/booking-list/booking-list.component.ts index 0e573456823b7cc0cbb4e098075f835141678a21..545cfce5222e6772780f2a51cec5f9b64cd8c894 100644 --- a/angular/RestClient/src/app/core/features/bookings/booking-list/booking-list.component.ts +++ b/angular/RestClient/src/app/features/bookings/booking-list/booking-list.component.ts @@ -7,12 +7,12 @@ import { import { FormsModule } from '@angular/forms'; import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; -import { Hotel, Room, RoomType, roomTypeArray } from '../../../../types'; +import { Hotel, Room, RoomType, roomTypeArray } from '../../../core/models'; import { Router } from '@angular/router'; import { MatCardModule } from '@angular/material/card'; import { MatChipsModule } from '@angular/material/chips'; -import { LocalStorageService } from '../../../../shared/local-storage.service'; -import { HotelClientService } from '../../../../shared/hotel-client.service'; +import { LocalStorageService } from '../../../core/services/storage/local-storage.service'; +import { HotelClientService } from '../../../core/services/api/hotels/hotel-client.service'; type SelectableRoomType = 'All' | RoomType; const selectableRoomTypeArray: SelectableRoomType[] = ['All', ...roomTypeArray]; diff --git a/angular/RestClient/src/app/core/features/bookings/booking/booking.component.css b/angular/RestClient/src/app/features/bookings/booking/booking.component.css similarity index 100% rename from angular/RestClient/src/app/core/features/bookings/booking/booking.component.css rename to angular/RestClient/src/app/features/bookings/booking/booking.component.css diff --git a/angular/RestClient/src/app/core/features/bookings/booking/booking.component.html b/angular/RestClient/src/app/features/bookings/booking/booking.component.html similarity index 100% rename from angular/RestClient/src/app/core/features/bookings/booking/booking.component.html rename to angular/RestClient/src/app/features/bookings/booking/booking.component.html diff --git a/angular/RestClient/src/app/core/features/bookings/booking/booking.component.spec.ts b/angular/RestClient/src/app/features/bookings/booking/booking.component.spec.ts similarity index 100% rename from angular/RestClient/src/app/core/features/bookings/booking/booking.component.spec.ts rename to angular/RestClient/src/app/features/bookings/booking/booking.component.spec.ts diff --git a/angular/RestClient/src/app/core/features/bookings/booking/booking.component.ts b/angular/RestClient/src/app/features/bookings/booking/booking.component.ts similarity index 89% rename from angular/RestClient/src/app/core/features/bookings/booking/booking.component.ts rename to angular/RestClient/src/app/features/bookings/booking/booking.component.ts index 6691027e241bff16a2b25fb71e23a1309ec1929e..f37e6576c13158c3bcdcca482c179c82b03904bb 100644 --- a/angular/RestClient/src/app/core/features/bookings/booking/booking.component.ts +++ b/angular/RestClient/src/app/features/bookings/booking/booking.component.ts @@ -7,11 +7,11 @@ import { } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; -import { Booking, User } from '../../../../types'; -import { LocalStorageService } from '../../../../shared/local-storage.service'; -import { BookingClientService } from '../../../../shared/booking-client.service'; -import { UserClientService } from '../../../../shared/user-client.service'; -import { SessionService } from '../../../../shared/session.service'; +import { Booking, User } from '../../../core/models'; +import { LocalStorageService } from '../../../core/services/storage/local-storage.service'; +import { BookingClientService } from '../../../core/services/api/bookings/booking-client.service'; +import { UserClientService } from '../../../core/services/api/users/user-client.service'; +import { SessionService } from '../../../core/services/session/session.service'; type communication = { roomId: number; diff --git a/angular/RestClient/src/app/features/bookings/bookings.routes.ts b/angular/RestClient/src/app/features/bookings/bookings.routes.ts new file mode 100644 index 0000000000000000000000000000000000000000..a35688a9184f63633ed96f4e1a92d2d7ae402e3e --- /dev/null +++ b/angular/RestClient/src/app/features/bookings/bookings.routes.ts @@ -0,0 +1,22 @@ +import { AppRoute } from '@core/models'; +import { BookingComponent } from './booking/booking.component'; +import { UserBookingListComponent } from './user-booking-list/user-booking-list.component'; + +export const BOOKINGS_ROUTES: AppRoute[] = []; +export const CLIENT_BOOKINGS_ROUTES: AppRoute[] = [ + { + path: '', + component: UserBookingListComponent, + data: { expectedRole: 'CLIENT' }, + }, + { + path: ':id', + component: BookingComponent, + data: { expectedRole: 'CLIENT' }, + }, + { + path: 'new', + component: BookingComponent, + data: { expectedRole: 'CLIENT' }, + }, +]; diff --git a/angular/RestClient/src/app/features/bookings/index.ts b/angular/RestClient/src/app/features/bookings/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..aa2b48a8a5e51016f6fb70658f3bbe0ab78aad8a --- /dev/null +++ b/angular/RestClient/src/app/features/bookings/index.ts @@ -0,0 +1,4 @@ +export * from './bookings.routes'; +export * from './booking/booking.component'; +export * from './booking-list/booking-list.component'; +export * from './user-booking-list/user-booking-list.component'; diff --git a/angular/RestClient/src/app/core/features/user/user-booking-list/user-booking-list.component.css b/angular/RestClient/src/app/features/bookings/user-booking-list/user-booking-list.component.css similarity index 100% rename from angular/RestClient/src/app/core/features/user/user-booking-list/user-booking-list.component.css rename to angular/RestClient/src/app/features/bookings/user-booking-list/user-booking-list.component.css diff --git a/angular/RestClient/src/app/core/features/user/user-booking-list/user-booking-list.component.html b/angular/RestClient/src/app/features/bookings/user-booking-list/user-booking-list.component.html similarity index 100% rename from angular/RestClient/src/app/core/features/user/user-booking-list/user-booking-list.component.html rename to angular/RestClient/src/app/features/bookings/user-booking-list/user-booking-list.component.html diff --git a/angular/RestClient/src/app/core/features/user/user-booking-list/user-booking-list.component.spec.ts b/angular/RestClient/src/app/features/bookings/user-booking-list/user-booking-list.component.spec.ts similarity index 100% rename from angular/RestClient/src/app/core/features/user/user-booking-list/user-booking-list.component.spec.ts rename to angular/RestClient/src/app/features/bookings/user-booking-list/user-booking-list.component.spec.ts diff --git a/angular/RestClient/src/app/core/features/user/user-booking-list/user-booking-list.component.ts b/angular/RestClient/src/app/features/bookings/user-booking-list/user-booking-list.component.ts similarity index 92% rename from angular/RestClient/src/app/core/features/user/user-booking-list/user-booking-list.component.ts rename to angular/RestClient/src/app/features/bookings/user-booking-list/user-booking-list.component.ts index f5ca26c52374160a4a16fe933a03cec8a8fed823..a76d5b38b9face0358016a6088f8c4e8bf8349d1 100644 --- a/angular/RestClient/src/app/core/features/user/user-booking-list/user-booking-list.component.ts +++ b/angular/RestClient/src/app/features/bookings/user-booking-list/user-booking-list.component.ts @@ -1,12 +1,12 @@ import { Component } from '@angular/core'; -import { Booking, User } from '../../../../types'; +import { Booking, User } from '../../../core/models'; import { ActivatedRoute, RouterModule } from '@angular/router'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; -import { UserClientService } from '../../../../shared/user-client.service'; -import { BookingClientService } from '../../../../shared/booking-client.service'; -import { SessionService } from '../../../../shared/session.service'; +import { UserClientService } from '../../../core/services/api/users/user-client.service'; +import { BookingClientService } from '../../../core/services/api/bookings/booking-client.service'; +import { SessionService } from '../../../core/services/session/session.service'; import { Observable } from 'rxjs'; type state = 'all' | 'active' | 'inactive'; diff --git a/angular/RestClient/src/app/core/features/hotel/hotel-list/hotel-list.component.css b/angular/RestClient/src/app/features/hotels/hotel-list/hotel-list.component.css similarity index 100% rename from angular/RestClient/src/app/core/features/hotel/hotel-list/hotel-list.component.css rename to angular/RestClient/src/app/features/hotels/hotel-list/hotel-list.component.css diff --git a/angular/RestClient/src/app/core/features/hotel/hotel-list/hotel-list.component.html b/angular/RestClient/src/app/features/hotels/hotel-list/hotel-list.component.html similarity index 100% rename from angular/RestClient/src/app/core/features/hotel/hotel-list/hotel-list.component.html rename to angular/RestClient/src/app/features/hotels/hotel-list/hotel-list.component.html diff --git a/angular/RestClient/src/app/core/features/hotel/hotel-list/hotel-list.component.spec.ts b/angular/RestClient/src/app/features/hotels/hotel-list/hotel-list.component.spec.ts similarity index 100% rename from angular/RestClient/src/app/core/features/hotel/hotel-list/hotel-list.component.spec.ts rename to angular/RestClient/src/app/features/hotels/hotel-list/hotel-list.component.spec.ts diff --git a/angular/RestClient/src/app/core/features/hotel/hotel-list/hotel-list.component.ts b/angular/RestClient/src/app/features/hotels/hotel-list/hotel-list.component.ts similarity index 90% rename from angular/RestClient/src/app/core/features/hotel/hotel-list/hotel-list.component.ts rename to angular/RestClient/src/app/features/hotels/hotel-list/hotel-list.component.ts index 1b6e34d096b35b4ef53be036aa767258592dee54..4cf5d5c40b4d5931eb994a05befe425ec0d20fc1 100644 --- a/angular/RestClient/src/app/core/features/hotel/hotel-list/hotel-list.component.ts +++ b/angular/RestClient/src/app/features/hotels/hotel-list/hotel-list.component.ts @@ -1,6 +1,6 @@ import { Component, NgModule } from '@angular/core'; import { RouterModule, Router, ActivatedRoute, Data } from '@angular/router'; -import { Hotel, Room, RoomType, roomTypeArray } from '../../../../types'; +import { Hotel, Room, RoomType, roomTypeArray } from '../../../core/models'; import { MatAccordion, MatExpansionPanel, @@ -15,16 +15,16 @@ import { NgbAccordionModule, NgbDatepickerModule, } from '@ng-bootstrap/ng-bootstrap'; -import { HotelClientService } from '../../../../shared/hotel-client.service'; +import { HotelClientService } from '../../../core/services/api/hotels/hotel-client.service'; import { MatCardModule } from '@angular/material/card'; import { MatIconModule } from '@angular/material/icon'; import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatDatepickerModule } from '@angular/material/datepicker'; import { MatSelectModule } from '@angular/material/select'; -import { LocalStorageService } from '../../../../shared/local-storage.service'; -import { SessionService } from '../../../../shared/session.service'; -import { getBasePath } from '../../../../utils/utils'; +import { LocalStorageService } from '../../../core/services/storage/local-storage.service'; +import { SessionService } from '../../../core/services/session/session.service'; +import { getBasePath } from '../../../utils/utils'; type SelectableRoomType = 'All' | RoomType; const selectableRoomTypeArray: SelectableRoomType[] = ['All', ...roomTypeArray]; @@ -66,7 +66,7 @@ export class HotelListComponent { roomTypes = selectableRoomTypeArray; rooms: Room[] = []; trateRooms: Room[] = []; - userId = 0 + userId = 0; constructor( private fb: FormBuilder, @@ -76,8 +76,8 @@ export class HotelListComponent { private storage: LocalStorageService, private sessionService: SessionService ) { - const isHotelManger = this.route.snapshot.url[0].path === 'me' - const isAdmin = this.route.snapshot.url[0].path === 'admin' + const isHotelManger = this.route.snapshot.url[0].path === 'me'; + const isAdmin = this.route.snapshot.url[0].path === 'admin'; this.isManaging = isHotelManger || isAdmin; const today = new Date(); @@ -93,7 +93,7 @@ export class HotelListComponent { next: (session) => { if (session && session.rol !== 'CLIENT') { this.isEditing = true; - this.userId = isHotelManger + this.userId = isHotelManger ? session.id : Number(this.route.snapshot.paramMap.get('id')); } @@ -146,11 +146,11 @@ export class HotelListComponent { getHotels() { const { start, end } = this.dateRangeForm.value.dateRange; - + const observable = this.isManaging ? this.hotelClient.getAllHotelsByUser(this.userId, start, end) - : this.hotelClient.getAllHotels(start, end) - console.log({...this}) + : this.hotelClient.getAllHotels(start, end); + console.log({ ...this }); observable.subscribe({ next: (resp) => { if (!!resp && (resp as never[]).length >= 0) { diff --git a/angular/RestClient/src/app/core/features/hotel/hotel-register/hotel-register.component.css b/angular/RestClient/src/app/features/hotels/hotel-register/hotel-register.component.css similarity index 100% rename from angular/RestClient/src/app/core/features/hotel/hotel-register/hotel-register.component.css rename to angular/RestClient/src/app/features/hotels/hotel-register/hotel-register.component.css diff --git a/angular/RestClient/src/app/core/features/hotel/hotel-register/hotel-register.component.html b/angular/RestClient/src/app/features/hotels/hotel-register/hotel-register.component.html similarity index 100% rename from angular/RestClient/src/app/core/features/hotel/hotel-register/hotel-register.component.html rename to angular/RestClient/src/app/features/hotels/hotel-register/hotel-register.component.html diff --git a/angular/RestClient/src/app/core/features/hotel/hotel-register/hotel-register.component.spec.ts b/angular/RestClient/src/app/features/hotels/hotel-register/hotel-register.component.spec.ts similarity index 100% rename from angular/RestClient/src/app/core/features/hotel/hotel-register/hotel-register.component.spec.ts rename to angular/RestClient/src/app/features/hotels/hotel-register/hotel-register.component.spec.ts diff --git a/angular/RestClient/src/app/core/features/hotel/hotel-register/hotel-register.component.ts b/angular/RestClient/src/app/features/hotels/hotel-register/hotel-register.component.ts similarity index 96% rename from angular/RestClient/src/app/core/features/hotel/hotel-register/hotel-register.component.ts rename to angular/RestClient/src/app/features/hotels/hotel-register/hotel-register.component.ts index 9408ea3e1082fb50c4277a6912870e6472979518..598619033cb3f2f6633021c40770e90e5ce722ab 100644 --- a/angular/RestClient/src/app/core/features/hotel/hotel-register/hotel-register.component.ts +++ b/angular/RestClient/src/app/features/hotels/hotel-register/hotel-register.component.ts @@ -13,9 +13,9 @@ import { MatFormFieldModule } from '@angular/material/form-field'; import { MatSelectModule } from '@angular/material/select'; import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { CommonModule } from '@angular/common'; -import { Address, Hotel, Room } from '../../../../types'; +import { Address, Hotel, Room } from '../../../core/models'; import { ActivatedRoute, Router } from '@angular/router'; -import { HotelClientService } from '../../../../shared/hotel-client.service'; +import { HotelClientService } from '../../../core/services/api/hotels/hotel-client.service'; import { MatIconModule } from '@angular/material/icon'; const emptyRoom: Room = { diff --git a/angular/RestClient/src/app/features/hotels/hotels.routes.ts b/angular/RestClient/src/app/features/hotels/hotels.routes.ts new file mode 100644 index 0000000000000000000000000000000000000000..9e000411a9680f7e8c0e8242d2f819e41bc959d6 --- /dev/null +++ b/angular/RestClient/src/app/features/hotels/hotels.routes.ts @@ -0,0 +1,45 @@ +import { AppRoute } from '@core/models'; +import { HotelRegisterComponent } from './hotel-register/hotel-register.component'; +import { HotelListComponent } from './hotel-list/hotel-list.component'; +import { rolGuard } from '@core/guards'; + +export const HOTELS_ROUTES: AppRoute[] = [ + { + path: '', // Ruta para la lista de hoteles + component: HotelListComponent, + }, + { + path: 'register', // Registrar nuevo hotel + component: HotelRegisterComponent, + canActivate: [rolGuard], + data: { expectedRole: 'HOTEL_ADMIN' }, + }, + { + path: ':id', // Hotel concreto + component: HotelRegisterComponent, + }, +]; + +export const MANAGERS_ROUTES: AppRoute[] = [ + { + path: 'hotels', + children: [ + { + path: '', + component: HotelListComponent, + }, + { + path: ':id', + component: HotelRegisterComponent, + }, + // { + // path: ':id/bookings', + // component: BookingListComponent, + // }, + // { + // path: ':id/rooms/:id/bookings', + // component: BookingListComponent, + // }, + ], + }, +]; diff --git a/angular/RestClient/src/app/features/hotels/index.ts b/angular/RestClient/src/app/features/hotels/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..3af2b5ac86e2a4b41ca3f1e5b79cc989999238b2 --- /dev/null +++ b/angular/RestClient/src/app/features/hotels/index.ts @@ -0,0 +1,3 @@ +export * from './hotels.routes'; +export * from './hotel-list/hotel-list.component'; +export * from './hotel-register/hotel-register.component'; diff --git a/angular/RestClient/src/app/features/users/admin.routes.ts b/angular/RestClient/src/app/features/users/admin.routes.ts new file mode 100644 index 0000000000000000000000000000000000000000..ff360470360e7b4b7d1d88192cf6cc1275500a8c --- /dev/null +++ b/angular/RestClient/src/app/features/users/admin.routes.ts @@ -0,0 +1,46 @@ +import { AppRoute } from '@core/models'; +import { MainPageComponent } from 'app/features/users/main-page/main-page.component'; +import { UserFormComponent } from 'app/features/users/user-form/user-form.component'; +import { USERS_ROUTES } from 'app/features/users/users.routes'; + +function getRoutesWithoutRol(routes: AppRoute[]) { + return routes.map((r) => { + if (r.data?.expectedRole) { + const { data, ...rest } = r; + return { ...rest }; + } + return r; + }); +} + +export const ADMIN_ROUTES: AppRoute[] = [ + { + path: '', // Main + component: UserFormComponent, + }, + { + path: 'users', + children: [ + { + path: '', + component: MainPageComponent, + }, + { + path: ':id', + children: getRoutesWithoutRol(USERS_ROUTES), + }, + ], + }, + // { + // path: 'users/:id/bookings/:bookingId', + // component: BookingComponent, + // }, + // { + // path: 'users/:userId/hotels/:id/bookings', + // component: BookingListComponent, + // }, + // { + // path: 'users/:userId/hotels/:hotelId/rooms/:id/bookings', + // component: BookingListComponent, + // }, +]; diff --git a/angular/RestClient/src/app/features/users/index.ts b/angular/RestClient/src/app/features/users/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..baccb6244faa93ab6973fb42ef34ffd14eee9bc5 --- /dev/null +++ b/angular/RestClient/src/app/features/users/index.ts @@ -0,0 +1,3 @@ +export * from './users.routes'; +export * from './main-page/main-page.component'; +export * from './user-form/user-form.component'; diff --git a/angular/RestClient/src/app/core/features/user/main-page/main-page.component.css b/angular/RestClient/src/app/features/users/main-page/main-page.component.css similarity index 100% rename from angular/RestClient/src/app/core/features/user/main-page/main-page.component.css rename to angular/RestClient/src/app/features/users/main-page/main-page.component.css diff --git a/angular/RestClient/src/app/core/features/user/main-page/main-page.component.html b/angular/RestClient/src/app/features/users/main-page/main-page.component.html similarity index 100% rename from angular/RestClient/src/app/core/features/user/main-page/main-page.component.html rename to angular/RestClient/src/app/features/users/main-page/main-page.component.html diff --git a/angular/RestClient/src/app/core/features/user/main-page/main-page.component.spec.ts b/angular/RestClient/src/app/features/users/main-page/main-page.component.spec.ts similarity index 100% rename from angular/RestClient/src/app/core/features/user/main-page/main-page.component.spec.ts rename to angular/RestClient/src/app/features/users/main-page/main-page.component.spec.ts diff --git a/angular/RestClient/src/app/core/features/user/main-page/main-page.component.ts b/angular/RestClient/src/app/features/users/main-page/main-page.component.ts similarity index 90% rename from angular/RestClient/src/app/core/features/user/main-page/main-page.component.ts rename to angular/RestClient/src/app/features/users/main-page/main-page.component.ts index 09770a81445c949b6754000aeb014d66933e95c1..aaeee74453a7d72a3d51759987084aaab75fcf7c 100644 --- a/angular/RestClient/src/app/core/features/user/main-page/main-page.component.ts +++ b/angular/RestClient/src/app/features/users/main-page/main-page.component.ts @@ -1,10 +1,10 @@ import { Component, OnInit, ViewChild } from '@angular/core'; -import { Client, User, UserStateFilter } from '../../../../types'; +import { Client, User, UserStateFilter } from '../../../core/models'; import { FormsModule } from '@angular/forms'; import { CommonModule } from '@angular/common'; import { Router, RouterModule } from '@angular/router'; -import { UserClientService } from '../../../../shared/user-client.service'; -import { users } from '../../../../../mocks/users'; // Renombrado para claridad +import { UserClientService } from '../../../core/services/api/users/user-client.service'; +import { users } from '../../../../mocks/users'; // Renombrado para claridad import { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator'; import { MatCardModule } from '@angular/material/card'; diff --git a/angular/RestClient/src/app/core/features/user/user-form/user-form.component.css b/angular/RestClient/src/app/features/users/user-form/user-form.component.css similarity index 100% rename from angular/RestClient/src/app/core/features/user/user-form/user-form.component.css rename to angular/RestClient/src/app/features/users/user-form/user-form.component.css diff --git a/angular/RestClient/src/app/core/features/user/user-form/user-form.component.html b/angular/RestClient/src/app/features/users/user-form/user-form.component.html similarity index 100% rename from angular/RestClient/src/app/core/features/user/user-form/user-form.component.html rename to angular/RestClient/src/app/features/users/user-form/user-form.component.html diff --git a/angular/RestClient/src/app/core/features/user/user-form/user-form.component.spec.ts b/angular/RestClient/src/app/features/users/user-form/user-form.component.spec.ts similarity index 87% rename from angular/RestClient/src/app/core/features/user/user-form/user-form.component.spec.ts rename to angular/RestClient/src/app/features/users/user-form/user-form.component.spec.ts index 292f234075dbcba5482794cf23e4a231e666ba17..b2c1c438bf3079058be1e1c81ec8f81130cc75c8 100644 --- a/angular/RestClient/src/app/core/features/user/user-form/user-form.component.spec.ts +++ b/angular/RestClient/src/app/features/users/user-form/user-form.component.spec.ts @@ -2,7 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { HttpResponse } from '@angular/common/http'; import { UserFormComponent } from './user-form.component'; -import { UserClientService } from '../../../../shared/user-client.service'; +import { UserClientService } from '../../../core/services/api/users/user-client.service'; import { of } from 'rxjs'; describe('UserFormComponent', () => { @@ -31,8 +31,12 @@ describe('UserFormComponent', () => { }) ); - spyOn(userService, 'updateUser').and.returnValue(of(new HttpResponse({ body: 'User updated successfully' }))); - spyOn(userService, 'updatePassword').and.returnValue(of(new HttpResponse({ body: 'Password updated successfully' }))); + spyOn(userService, 'updateUser').and.returnValue( + of(new HttpResponse({ body: 'User updated successfully' })) + ); + spyOn(userService, 'updatePassword').and.returnValue( + of(new HttpResponse({ body: 'Password updated successfully' })) + ); fixture.detectChanges(); }); diff --git a/angular/RestClient/src/app/core/features/user/user-form/user-form.component.ts b/angular/RestClient/src/app/features/users/user-form/user-form.component.ts similarity index 96% rename from angular/RestClient/src/app/core/features/user/user-form/user-form.component.ts rename to angular/RestClient/src/app/features/users/user-form/user-form.component.ts index 1c6d272d01c5a9b899b1c77a84773fe47c43c7bb..c57800fc59228402d653658462be49b380d40be3 100644 --- a/angular/RestClient/src/app/core/features/user/user-form/user-form.component.ts +++ b/angular/RestClient/src/app/features/users/user-form/user-form.component.ts @@ -6,18 +6,18 @@ import { ReactiveFormsModule, FormsModule, } from '@angular/forms'; -import { UserClientService } from '../../../../shared/user-client.service'; +import { UserClientService } from '../../../core/services/api/users/user-client.service'; import { ActivatedRoute, Router, RouterModule } from '@angular/router'; import { MatCardModule } from '@angular/material/card'; import { MatInputModule } from '@angular/material/input'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatSlideToggleModule } from '@angular/material/slide-toggle'; -import { SessionService } from '../../../../shared/session.service'; -import { Session, UserRol, UserRolesArray } from '../../../../types'; +import { SessionService } from '../../../core/services/session/session.service'; +import { Session, UserRol, UserRolesArray } from '../../../core/models'; import { MatSelectModule } from '@angular/material/select'; import { Observable } from 'rxjs'; -import { getBasePath } from '../../../../utils/utils'; -import { environment } from '../../../../../environments/environment'; +import { getBasePath } from '../../../utils/utils'; +import { environment } from '../../../../environments/environment'; type EditMode = | 'Login' diff --git a/angular/RestClient/src/app/features/users/users.routes.ts b/angular/RestClient/src/app/features/users/users.routes.ts new file mode 100644 index 0000000000000000000000000000000000000000..84c240ff1e34363caf5c9d69c643b30a9ccb38ef --- /dev/null +++ b/angular/RestClient/src/app/features/users/users.routes.ts @@ -0,0 +1,35 @@ +import { AppRoute, UserRolesArray } from '@core/models'; +import { UserFormComponent } from './user-form/user-form.component'; + +export const USERS_ROUTES: AppRoute[] = [ + // Common + { + path: '', + data: { expectedRole: UserRolesArray }, + component: UserFormComponent, + }, + { + path: 'edit', + component: UserFormComponent, + data: { expectedRole: UserRolesArray }, + }, + { + path: 'change-passwd', + component: UserFormComponent, + data: { expectedRole: UserRolesArray }, + }, + { + // Usuario administrador de hoteles + path: 'hotels', + data: { expectedRole: 'HOTEL_ADMIN' }, + loadChildren: () => + import('app/features/hotels').then((m) => m.MANAGERS_ROUTES), + }, + { + // Usuario cliente + path: 'bookings', + data: { expectedRole: 'CLIENT' }, + loadChildren: () => + import('app/features/bookings').then((m) => m.CLIENT_BOOKINGS_ROUTES), + }, +]; diff --git a/angular/RestClient/src/app/page/unauthorized/unauthorized.component.ts b/angular/RestClient/src/app/page/unauthorized/unauthorized.component.ts index 061a73dbe09d717285d19188179a450cd780a699..67f9df6fc352fa34000a0864d9cd71b11745fecd 100644 --- a/angular/RestClient/src/app/page/unauthorized/unauthorized.component.ts +++ b/angular/RestClient/src/app/page/unauthorized/unauthorized.component.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; import { RouterModule } from '@angular/router'; -import { SessionService } from '../../shared/session.service'; +import { SessionService } from '../../core/services/session/session.service'; import { MatIconModule } from '@angular/material/icon'; import { MatButtonModule } from '@angular/material/button'; diff --git a/angular/RestClient/src/app/security/rol.guard.ts b/angular/RestClient/src/app/security/rol.guard.ts deleted file mode 100644 index 53017db2422c5f9e8c62c37622786bc4988673bf..0000000000000000000000000000000000000000 --- a/angular/RestClient/src/app/security/rol.guard.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { inject } from '@angular/core'; -import { CanActivateFn, Router } from '@angular/router'; -import { SessionService } from '../shared/session.service'; -import { UserRol, Session } from '../types'; -import { map } from 'rxjs'; - -export const rolGuard: CanActivateFn = (route, state) => { - const sessionService = inject(SessionService); - const router = inject(Router); - // Obtén el rol esperado desde los datos de la ruta - const expectedRole = route.data?.['expectedRole']; - - // Verifica si el usuario tiene sesión activa - const session = sessionService.isValid(); - - if (!session) { - console.log('no session'); - router.navigate(['/login']); - return false; - } - - return sessionService.getSession().pipe( - map((session: Session | null) => { - if (!session) return false; - - if ( - Array.isArray(expectedRole) && - (expectedRole as UserRol[]).includes(session.rol) - ) { - console.log('Rol in Rol arry'); - return true; - } else if (session.rol === expectedRole) { - console.log('Rol valido'); - return true; - } - console.log('Unautorizado'); - - // Redirige si el usuario no tiene el rol necesario - router.navigate(['/unauthorized']); - return false; - }) - ); -}; diff --git a/angular/RestClient/src/app/shared/index.ts b/angular/RestClient/src/app/shared/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..025765515b7065a3a53b37ce41931562927ffa5d --- /dev/null +++ b/angular/RestClient/src/app/shared/index.ts @@ -0,0 +1 @@ +export * from './navigation/navigation.component'; diff --git a/angular/RestClient/src/app/core/navigation/navigation.component.css b/angular/RestClient/src/app/shared/navigation/navigation.component.css similarity index 100% rename from angular/RestClient/src/app/core/navigation/navigation.component.css rename to angular/RestClient/src/app/shared/navigation/navigation.component.css diff --git a/angular/RestClient/src/app/core/navigation/navigation.component.html b/angular/RestClient/src/app/shared/navigation/navigation.component.html similarity index 100% rename from angular/RestClient/src/app/core/navigation/navigation.component.html rename to angular/RestClient/src/app/shared/navigation/navigation.component.html diff --git a/angular/RestClient/src/app/core/navigation/navigation.component.spec.ts b/angular/RestClient/src/app/shared/navigation/navigation.component.spec.ts similarity index 100% rename from angular/RestClient/src/app/core/navigation/navigation.component.spec.ts rename to angular/RestClient/src/app/shared/navigation/navigation.component.spec.ts diff --git a/angular/RestClient/src/app/core/navigation/navigation.component.ts b/angular/RestClient/src/app/shared/navigation/navigation.component.ts similarity index 90% rename from angular/RestClient/src/app/core/navigation/navigation.component.ts rename to angular/RestClient/src/app/shared/navigation/navigation.component.ts index effeb032ae23e20086be4661a92971ac5543cb36..12f4164e9cffed5cbf23380f2c47d894fef82cea 100644 --- a/angular/RestClient/src/app/core/navigation/navigation.component.ts +++ b/angular/RestClient/src/app/shared/navigation/navigation.component.ts @@ -3,11 +3,8 @@ import { RouterModule } from '@angular/router'; import { MatButtonModule } from '@angular/material/button'; import { MatMenuModule, MatMenuTrigger } from '@angular/material/menu'; import { MatIconModule } from '@angular/material/icon'; -import { Session, User, UserRol } from '../../types'; -import { SessionService } from '../../shared/session.service'; -import { UserClientService } from '../../shared/user-client.service'; -import { AuthClientService } from '../../shared/auth-client.service'; -import { Observable } from 'rxjs'; +import { Session, UserRol } from '@core/models'; +import { SessionService } from '@core/services'; var comp_id = 0; diff --git a/angular/RestClient/src/app/types/index.ts b/angular/RestClient/src/app/types/index.ts deleted file mode 100644 index 09338254dd1b87fdc9aa00cb7342f0865fc382de..0000000000000000000000000000000000000000 --- a/angular/RestClient/src/app/types/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { RoomType } from './Room'; -import { UserRol } from './User'; - -export type * from './User'; -export type * from './Address'; -export type * from './Hotel'; -export type * from './Room'; -export const roomTypeArray: RoomType[] = ['SINGLE', 'DOUBLE', 'SUITE']; -export type * from './Booking'; -export type * from './User'; -export const UserRolesArray: UserRol[] = ['CLIENT', 'HOTEL_ADMIN', 'ADMIN']; -export type * from './Session'; diff --git a/angular/RestClient/src/app/utils/index.ts b/angular/RestClient/src/app/utils/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..04bca77e0dec46fccb655a9203c2c8ee57ba7ed2 --- /dev/null +++ b/angular/RestClient/src/app/utils/index.ts @@ -0,0 +1 @@ +export * from './utils'; diff --git a/angular/RestClient/src/environments/environment.development.ts b/angular/RestClient/src/environments/environment.development.ts new file mode 100644 index 0000000000000000000000000000000000000000..f274e5edf297c322280400000710ffbfd88f7711 --- /dev/null +++ b/angular/RestClient/src/environments/environment.development.ts @@ -0,0 +1 @@ +export const environment = {}; diff --git a/angular/RestClient/src/mocks/users.ts b/angular/RestClient/src/mocks/users.ts index f6fafe67e13993f0e5b1a48c5443d43f6d3c2c3d..c21e6712745692d04764ac71105d66eed363c533 100644 --- a/angular/RestClient/src/mocks/users.ts +++ b/angular/RestClient/src/mocks/users.ts @@ -1,4 +1,4 @@ -import { Client, User } from '../app/types'; +import { Client, User } from '../app/core/models'; export const users: Client[] = [ { diff --git a/angular/RestClient/suggested_struct.md b/angular/RestClient/suggested_struct.md new file mode 100644 index 0000000000000000000000000000000000000000..e1d72cf57a09fa96ddc0983a895549d4dbc68028 --- /dev/null +++ b/angular/RestClient/suggested_struct.md @@ -0,0 +1,179 @@ +¡Gracias por compartir la estructura de tu proyecto! La organización que tienes ya está bastante bien y sigue un esquema modular lógico. Sin embargo, puedo ofrecerte algunas **sugerencias para mejorar la organización** y seguir las mejores prácticas modernas de Angular, especialmente para proyectos grandes como este. + +--- + +### **Propuesta de Ajustes para Mejorar Escalabilidad y Mantenimiento** + +1. **Separa el código relacionado con "Core" y "Features" claramente** + Actualmente, dentro de `core` tienes una carpeta llamada `features`, lo cual puede generar confusión porque los módulos funcionales suelen estar en un nivel separado del núcleo. + + **Propuesta:** + Mueve `features` al nivel raíz de `app/`. Así, `core` queda solo para configuraciones globales y servicios singleton, mientras que los módulos de funcionalidad se agrupan claramente en `features`. + + ``` + app/ + ├── core/ + │ ├── guards/ + │ ├── interceptors/ + │ ├── services/ + │ ├── interfaces/ (puede fusionarse con `types` si prefieres) + │ └── core.module.ts + ├── features/ (antes dentro de core/features) + │ ├── auth/ + │ ├── bookings/ + │ ├── hotel/ + │ ├── user/ + │ └── ... + ``` + +--- + +2. **Reorganiza `shared` para dividir los servicios y componentes reutilizables** + Actualmente, `shared` contiene todos los servicios reutilizables. Esto está bien, pero puedes separar los servicios de otros posibles elementos reutilizables (como pipes, directivas, o componentes). Esto mejora la claridad y hace que sea más fácil expandir `shared` en el futuro. + + **Propuesta:** + ``` + shared/ + ├── components/ # Componentes reutilizables (header, footer, spinner, etc.) + ├── directives/ # Directivas personalizadas (si las usas en el futuro) + ├── pipes/ # Pipes personalizados + ├── services/ # Servicios reutilizables (actualmente en `shared`) + │ ├── auth-client.service.ts + │ ├── booking-client.service.ts + │ ├── hotel-client.service.ts + │ ├── local-storage.service.ts + │ ├── session.service.ts + │ └── user-client.service.ts + └── shared.module.ts # Opcional, exporta recursos reutilizables + ``` + +--- + +3. **Agrupa los tests de Guards, Interceptors y otros elementos de seguridad** + Los guards e interceptores relacionados con seguridad están correctamente en `security`. Sin embargo, para seguir un esquema uniforme, puedes agrupar sus tests (`*.spec.ts`) en subcarpetas junto con los archivos que prueban. + + **Propuesta:** + ``` + security/ + ├── guards/ + │ ├── rol.guard.ts + │ └── rol.guard.spec.ts + ├── interceptors/ + │ ├── auth.interceptor.ts + │ └── auth.interceptor.spec.ts + ``` + +--- + +4. **Claridad en los módulos funcionales dentro de `features`** + Tus módulos funcionales (`auth`, `bookings`, `hotel`, `user`) están bien estructurados. Sin embargo, puedes incluir un archivo `routing` y agrupar los componentes y servicios que son específicos de cada módulo. + + **Propuesta para un módulo funcional (ejemplo: `auth`)**: + ``` + auth/ + ├── components/ + │ ├── login/ + │ │ ├── login.component.ts + │ │ ├── login.component.html + │ │ └── login.component.css + │ ├── register/ + │ ├── register.component.ts + │ ├── register.component.html + │ └── register.component.css + ├── auth-routing.module.ts # Rutas específicas para el módulo + ├── auth.module.ts # Módulo propio de Auth + └── services/ + ├── auth.service.ts # Servicios específicos del módulo + └── token.service.ts + ``` + + Esto hace que los servicios y componentes específicos de un módulo no se mezclen con los globales. + +--- + +5. **Centraliza y organiza los tipos (o interfaces)** + Actualmente tienes una carpeta `types` con interfaces relacionadas con el dominio (`Address`, `Booking`, etc.). Esto está bien, pero puedes agruparlas en módulos de funcionalidades correspondientes si no son usadas globalmente. + + **Propuesta:** + - Si **son compartidas por varios módulos**, mantenlas en `core/interfaces/`. + - Si **son específicas de un módulo**, muévelas al módulo correspondiente dentro de `features`. + + **Ejemplo para `Booking.d.ts`:** + Si solo es usado por el módulo `bookings`, ubícalo así: + ``` + features/bookings/interfaces/booking.interface.ts + ``` + + Y actualiza el import en cualquier lugar donde lo uses: + ```typescript + import { Booking } from '../features/bookings/interfaces/booking.interface'; + ``` + +--- + +6. **Páginas específicas (como `unauthorized`)** + La carpeta `page/unauthorized` está correcta, pero en proyectos grandes suele ser más común incluir este tipo de páginas en un módulo funcional. Por ejemplo, si `unauthorized` está relacionado con autenticación, podría ir dentro de `features/auth`. + + **Propuesta:** + ``` + features/auth/ + ├── unauthorized/ + │ ├── unauthorized.component.ts + │ ├── unauthorized.component.html + │ └── unauthorized.component.css + ``` + + Si está pensada como una página global (accesible desde cualquier lugar), puedes dejarla en `core/` o en su propio módulo funcional como `pages/unauthorized`. + +--- + +### **Versión Reorganizada** +Tu estructura reorganizada podría verse así: +``` +src/ +├── app/ +│ ├── core/ +│ │ ├── guards/ +│ │ │ ├── rol.guard.ts +│ │ │ └── rol.guard.spec.ts +│ │ ├── interceptors/ +│ │ │ ├── auth.interceptor.ts +│ │ │ └── auth.interceptor.spec.ts +│ │ ├── services/ # Servicios singleton +│ │ ├── interfaces/ # Interfaces globales +│ │ └── core.module.ts +│ ├── shared/ +│ │ ├── components/ +│ │ │ ├── header/ +│ │ │ └── footer/ +│ │ ├── directives/ +│ │ ├── pipes/ +│ │ ├── services/ +│ │ └── shared.module.ts +│ ├── features/ +│ │ ├── auth/ +│ │ │ ├── components/ +│ │ │ ├── services/ +│ │ │ ├── interfaces/ +│ │ │ ├── unauthorized/ +│ │ │ ├── auth-routing.module.ts +│ │ │ └── auth.module.ts +│ │ ├── bookings/ +│ │ ├── hotel/ +│ │ └── user/ +│ ├── app-routing.module.ts +│ ├── app.component.ts +│ └── app.module.ts +├── environments/ +├── styles/ +└── utils/ +``` + +--- + +### **Beneficios de esta Reorganización** +1. **Claridad:** Diferenciación clara entre módulos funcionales (`features`), servicios globales (`core`), y recursos reutilizables (`shared`). +2. **Escalabilidad:** Módulos funcionales pueden crecer sin afectar otras partes del proyecto. +3. **Mantenibilidad:** Cada archivo está en un lugar lógico, lo que facilita encontrar y modificar código. + +Si necesitas ejemplos de código para implementar alguna parte, ¡házmelo saber! 😊 diff --git a/angular/RestClient/suggested_tree b/angular/RestClient/suggested_tree new file mode 100644 index 0000000000000000000000000000000000000000..25ac5d163023fcd24cfa22e63d69b8429b7c4b21 --- /dev/null +++ b/angular/RestClient/suggested_tree @@ -0,0 +1,38 @@ +src/ +├── app/ +│ ├── core/ +│ │ ├── guards/ +│ │ │ ├── rol.guard.ts +│ │ │ └── rol.guard.spec.ts +│ │ ├── interceptors/ +│ │ │ ├── auth.interceptor.ts +│ │ │ └── auth.interceptor.spec.ts +│ │ ├── services/ # Servicios singleton +│ │ ├── interfaces/ # Interfaces globales +│ │ └── core.module.ts +│ ├── shared/ +│ │ ├── components/ +│ │ │ ├── header/ +│ │ │ └── footer/ +│ │ ├── directives/ +│ │ ├── pipes/ +│ │ ├── services/ +│ │ └── shared.module.ts +│ ├── features/ +│ │ ├── auth/ +│ │ │ ├── components/ +│ │ │ ├── services/ +│ │ │ ├── interfaces/ +│ │ │ ├── unauthorized/ +│ │ │ ├── auth-routing.module.ts +│ │ │ └── auth.module.ts +│ │ ├── bookings/ +│ │ ├── hotel/ +│ │ └── user/ +│ ├── app-routing.module.ts +│ ├── app.component.ts +│ └── app.module.ts +├── environments/ +├── styles/ +└── utils/ + diff --git a/angular/RestClient/tsconfig.json b/angular/RestClient/tsconfig.json index a8bb65b6e220b8a16a8cba8241833468f1145586..2f5f1c7db6874459db03595ba7a9f58e3e2f1a3d 100644 --- a/angular/RestClient/tsconfig.json +++ b/angular/RestClient/tsconfig.json @@ -19,10 +19,16 @@ "importHelpers": true, "target": "ES2022", "module": "ES2022", - "lib": [ - "ES2022", - "dom" - ] + "lib": ["ES2022", "dom"], + "baseUrl": "./src", // Define la base del proyecto + "paths": { + "@core/*": ["app/core/*"], // Alias para el core + "@shared": ["app/shared"], // Alias para shared + "@shared/*": ["app/shared/*"], // Alias para shared + "@features/*": ["app/core/features/*"], // Alias para features + "@utils/*": ["app/utils/*"], // Alias para utilidades + "@pages/*": ["app/pages/*"] // Alias para pages + } }, "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, diff --git a/java/roomBooking/.gitignore b/java/roomBooking/.gitignore deleted file mode 100644 index 549e00a2a96fa9d7c5dbc9859664a78d980158c2..0000000000000000000000000000000000000000 --- a/java/roomBooking/.gitignore +++ /dev/null @@ -1,33 +0,0 @@ -HELP.md -target/ -!.mvn/wrapper/maven-wrapper.jar -!**/src/main/**/target/ -!**/src/test/**/target/ - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ -build/ -!**/src/main/**/build/ -!**/src/test/**/build/ - -### VS Code ### -.vscode/ diff --git a/java/roomBooking/.mvn/wrapper/maven-wrapper.properties b/java/roomBooking/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index d58dfb70bab565a697e6854eb012d17e0fd39bd4..0000000000000000000000000000000000000000 --- a/java/roomBooking/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1,19 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -wrapperVersion=3.3.2 -distributionType=only-script -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip diff --git a/java/roomBooking/Dockerfile b/java/roomBooking/Dockerfile deleted file mode 100644 index 8d0b79d6514534deddac782fc9367bcdbcaf89c3..0000000000000000000000000000000000000000 --- a/java/roomBooking/Dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -FROM maven:3-openjdk-17 AS maven -WORKDIR /app -COPY ./ ./ -RUN mvn -Dmaven.test.skip clean package -FROM openjdk:17-jdk-oracle -ARG JAR_FILE=/app/target/*.jar -COPY --from=maven ${JAR_FILE} app.jar -ENV PORT 8080 -EXPOSE $PORT -ENTRYPOINT ["java","-jar", "/app.jar"] \ No newline at end of file diff --git a/java/roomBooking/mvnw b/java/roomBooking/mvnw deleted file mode 100755 index 19529ddf8c6eaa08c5c75ff80652d21ce4b72f8c..0000000000000000000000000000000000000000 --- a/java/roomBooking/mvnw +++ /dev/null @@ -1,259 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Apache Maven Wrapper startup batch script, version 3.3.2 -# -# Optional ENV vars -# ----------------- -# JAVA_HOME - location of a JDK home dir, required when download maven via java source -# MVNW_REPOURL - repo url base for downloading maven distribution -# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven -# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output -# ---------------------------------------------------------------------------- - -set -euf -[ "${MVNW_VERBOSE-}" != debug ] || set -x - -# OS specific support. -native_path() { printf %s\\n "$1"; } -case "$(uname)" in -CYGWIN* | MINGW*) - [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" - native_path() { cygpath --path --windows "$1"; } - ;; -esac - -# set JAVACMD and JAVACCMD -set_java_home() { - # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched - if [ -n "${JAVA_HOME-}" ]; then - if [ -x "$JAVA_HOME/jre/sh/java" ]; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - JAVACCMD="$JAVA_HOME/jre/sh/javac" - else - JAVACMD="$JAVA_HOME/bin/java" - JAVACCMD="$JAVA_HOME/bin/javac" - - if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then - echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 - echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 - return 1 - fi - fi - else - JAVACMD="$( - 'set' +e - 'unset' -f command 2>/dev/null - 'command' -v java - )" || : - JAVACCMD="$( - 'set' +e - 'unset' -f command 2>/dev/null - 'command' -v javac - )" || : - - if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then - echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 - return 1 - fi - fi -} - -# hash string like Java String::hashCode -hash_string() { - str="${1:-}" h=0 - while [ -n "$str" ]; do - char="${str%"${str#?}"}" - h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) - str="${str#?}" - done - printf %x\\n $h -} - -verbose() { :; } -[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } - -die() { - printf %s\\n "$1" >&2 - exit 1 -} - -trim() { - # MWRAPPER-139: - # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. - # Needed for removing poorly interpreted newline sequences when running in more - # exotic environments such as mingw bash on Windows. - printf "%s" "${1}" | tr -d '[:space:]' -} - -# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties -while IFS="=" read -r key value; do - case "${key-}" in - distributionUrl) distributionUrl=$(trim "${value-}") ;; - distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; - esac -done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" -[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" - -case "${distributionUrl##*/}" in -maven-mvnd-*bin.*) - MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ - case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in - *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; - :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; - :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; - :Linux*x86_64*) distributionPlatform=linux-amd64 ;; - *) - echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 - distributionPlatform=linux-amd64 - ;; - esac - distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" - ;; -maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; -*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; -esac - -# apply MVNW_REPOURL and calculate MAVEN_HOME -# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash> -[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" -distributionUrlName="${distributionUrl##*/}" -distributionUrlNameMain="${distributionUrlName%.*}" -distributionUrlNameMain="${distributionUrlNameMain%-bin}" -MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" -MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" - -exec_maven() { - unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : - exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" -} - -if [ -d "$MAVEN_HOME" ]; then - verbose "found existing MAVEN_HOME at $MAVEN_HOME" - exec_maven "$@" -fi - -case "${distributionUrl-}" in -*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; -*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; -esac - -# prepare tmp dir -if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then - clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } - trap clean HUP INT TERM EXIT -else - die "cannot create temp dir" -fi - -mkdir -p -- "${MAVEN_HOME%/*}" - -# Download and Install Apache Maven -verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." -verbose "Downloading from: $distributionUrl" -verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" - -# select .zip or .tar.gz -if ! command -v unzip >/dev/null; then - distributionUrl="${distributionUrl%.zip}.tar.gz" - distributionUrlName="${distributionUrl##*/}" -fi - -# verbose opt -__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' -[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v - -# normalize http auth -case "${MVNW_PASSWORD:+has-password}" in -'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; -has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; -esac - -if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then - verbose "Found wget ... using wget" - wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" -elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then - verbose "Found curl ... using curl" - curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" -elif set_java_home; then - verbose "Falling back to use Java to download" - javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" - targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" - cat >"$javaSource" <<-END - public class Downloader extends java.net.Authenticator - { - protected java.net.PasswordAuthentication getPasswordAuthentication() - { - return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); - } - public static void main( String[] args ) throws Exception - { - setDefault( new Downloader() ); - java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); - } - } - END - # For Cygwin/MinGW, switch paths to Windows format before running javac and java - verbose " - Compiling Downloader.java ..." - "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" - verbose " - Running Downloader.java ..." - "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" -fi - -# If specified, validate the SHA-256 sum of the Maven distribution zip file -if [ -n "${distributionSha256Sum-}" ]; then - distributionSha256Result=false - if [ "$MVN_CMD" = mvnd.sh ]; then - echo "Checksum validation is not supported for maven-mvnd." >&2 - echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 - exit 1 - elif command -v sha256sum >/dev/null; then - if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then - distributionSha256Result=true - fi - elif command -v shasum >/dev/null; then - if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then - distributionSha256Result=true - fi - else - echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 - echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 - exit 1 - fi - if [ $distributionSha256Result = false ]; then - echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 - echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 - exit 1 - fi -fi - -# unzip and move -if command -v unzip >/dev/null; then - unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" -else - tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" -fi -printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" -mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" - -clean || : -exec_maven "$@" diff --git a/java/roomBooking/mvnw.cmd b/java/roomBooking/mvnw.cmd deleted file mode 100644 index 249bdf3822221aa612d1da2605316cabd7b07e50..0000000000000000000000000000000000000000 --- a/java/roomBooking/mvnw.cmd +++ /dev/null @@ -1,149 +0,0 @@ -<# : batch portion -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.3.2 -@REM -@REM Optional ENV vars -@REM MVNW_REPOURL - repo url base for downloading maven distribution -@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven -@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output -@REM ---------------------------------------------------------------------------- - -@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) -@SET __MVNW_CMD__= -@SET __MVNW_ERROR__= -@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% -@SET PSModulePath= -@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( - IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) -) -@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% -@SET __MVNW_PSMODULEP_SAVE= -@SET __MVNW_ARG0_NAME__= -@SET MVNW_USERNAME= -@SET MVNW_PASSWORD= -@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) -@echo Cannot start maven from wrapper >&2 && exit /b 1 -@GOTO :EOF -: end batch / begin powershell #> - -$ErrorActionPreference = "Stop" -if ($env:MVNW_VERBOSE -eq "true") { - $VerbosePreference = "Continue" -} - -# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties -$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl -if (!$distributionUrl) { - Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" -} - -switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { - "maven-mvnd-*" { - $USE_MVND = $true - $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" - $MVN_CMD = "mvnd.cmd" - break - } - default { - $USE_MVND = $false - $MVN_CMD = $script -replace '^mvnw','mvn' - break - } -} - -# apply MVNW_REPOURL and calculate MAVEN_HOME -# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash> -if ($env:MVNW_REPOURL) { - $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } - $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" -} -$distributionUrlName = $distributionUrl -replace '^.*/','' -$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' -$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" -if ($env:MAVEN_USER_HOME) { - $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" -} -$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' -$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" - -if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { - Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" - Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" - exit $? -} - -if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { - Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" -} - -# prepare tmp dir -$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile -$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" -$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null -trap { - if ($TMP_DOWNLOAD_DIR.Exists) { - try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } - catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } - } -} - -New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null - -# Download and Install Apache Maven -Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." -Write-Verbose "Downloading from: $distributionUrl" -Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" - -$webclient = New-Object System.Net.WebClient -if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { - $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) -} -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null - -# If specified, validate the SHA-256 sum of the Maven distribution zip file -$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum -if ($distributionSha256Sum) { - if ($USE_MVND) { - Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." - } - Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash - if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { - Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." - } -} - -# unzip and move -Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null -Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null -try { - Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null -} catch { - if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { - Write-Error "fail to move MAVEN_HOME" - } -} finally { - try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } - catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } -} - -Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/java/roomBooking/pom.xml b/java/roomBooking/pom.xml deleted file mode 100644 index 30de244f0964028a4aa0b50df0ff2cf4f4b1102f..0000000000000000000000000000000000000000 --- a/java/roomBooking/pom.xml +++ /dev/null @@ -1,77 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> - <modelVersion>4.0.0</modelVersion> - <parent> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-parent</artifactId> - <version>3.3.4</version> - <relativePath/> <!-- lookup parent from repository --> - </parent> - <groupId>com.uva</groupId> - <artifactId>roomBooking</artifactId> - <version>0.0.1-SNAPSHOT</version> - <name>roomBooking</name> - <description>Room Booking rest</description> - <url/> - <licenses> - <license/> - </licenses> - <developers> - <developer/> - </developers> - <scm> - <connection/> - <developerConnection/> - <tag/> - <url/> - </scm> - <properties> - <java.version>17</java.version> - </properties> - <dependencies> - <dependency> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-data-jpa</artifactId> - </dependency> - <dependency> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-data-rest</artifactId> - </dependency> - - <dependency> - <groupId>com.mysql</groupId> - <artifactId>mysql-connector-j</artifactId> - <scope>runtime</scope> - </dependency> - <dependency> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-test</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-security</artifactId> - </dependency> - <dependency> - <groupId>com.auth0</groupId> - <artifactId>java-jwt</artifactId> - <version>4.4.0</version> - </dependency> - <dependency> - <groupId>jakarta.servlet</groupId> - <artifactId>jakarta.servlet-api</artifactId> - <scope>provided</scope> - </dependency> - </dependencies> - - <build> - <plugins> - <plugin> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-maven-plugin</artifactId> - </plugin> - </plugins> - </build> - -</project> \ No newline at end of file diff --git a/java/roomBooking/src/main/java/com/uva/monolith/RoomBookingApplication.java b/java/roomBooking/src/main/java/com/uva/monolith/RoomBookingApplication.java deleted file mode 100644 index 0a5db248da6e6be909302e323931f79151b0ed62..0000000000000000000000000000000000000000 --- a/java/roomBooking/src/main/java/com/uva/monolith/RoomBookingApplication.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.uva.monolith; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class RoomBookingApplication { - - public static void main(String[] args) { - SpringApplication.run(RoomBookingApplication.class, args); - } - -} diff --git a/java/roomBooking/src/main/java/com/uva/monolith/config/SecurityConfig.java b/java/roomBooking/src/main/java/com/uva/monolith/config/SecurityConfig.java deleted file mode 100644 index 8264f84a2e9f57dabaedc5892ea8a3ca0e931232..0000000000000000000000000000000000000000 --- a/java/roomBooking/src/main/java/com/uva/monolith/config/SecurityConfig.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.uva.monolith.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpMethod; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; - -import com.uva.monolith.filter.JwtAuthenticationFilter; -import com.uva.monolith.services.users.models.UserRol; - -@Configuration -@EnableWebSecurity -public class SecurityConfig { - - private final JwtAuthenticationFilter jwtAuthenticationFilter; - - public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) { - this.jwtAuthenticationFilter = jwtAuthenticationFilter; - } - - @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - http.csrf(csrf -> csrf.disable()) - .authorizeHttpRequests(authorize -> authorize - // Permitir OPTIONS sin autenticación - .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() - // Acceso restringido a usuarios y administradores - .requestMatchers("users", "users/**").hasAnyRole( - UserRol.CLIENT.toString(), UserRol.HOTEL_ADMIN.toString(), UserRol.ADMIN.toString()) - // Acceso restringido a gestores de hoteles y administradores - .requestMatchers(HttpMethod.GET, "hotels", "hotels/*").hasAnyRole( - UserRol.CLIENT.toString(), UserRol.HOTEL_ADMIN.toString(), UserRol.ADMIN.toString()) - - .requestMatchers("hotels", "hotels/**") - .hasAnyRole(UserRol.ADMIN.toString(), UserRol.HOTEL_ADMIN.toString()) - // Acceso restringido a cualquier usuario del sistema - .requestMatchers("bookings", "bookings/**") - .hasAnyRole(UserRol.ADMIN.toString(), UserRol.HOTEL_ADMIN.toString(), UserRol.CLIENT.toString()) - // Rechazar el resto - .anyRequest().denyAll()) - // Registra el filtro antes del filtro estándar de autenticación - .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); - - return http.build(); - } -} diff --git a/java/roomBooking/src/main/java/com/uva/monolith/exceptions/GlobalExceptionHandler.java b/java/roomBooking/src/main/java/com/uva/monolith/exceptions/GlobalExceptionHandler.java deleted file mode 100644 index 9428c51a9c63c3623d44752c9e3cbe6cf78ac19f..0000000000000000000000000000000000000000 --- a/java/roomBooking/src/main/java/com/uva/monolith/exceptions/GlobalExceptionHandler.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.uva.monolith.exceptions; - -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; - -import java.time.LocalDateTime; -import java.util.HashMap; -import java.util.Map; - -@ControllerAdvice -public class GlobalExceptionHandler { - - @ExceptionHandler(HotelNotFoundException.class) - public ResponseEntity<Map<String, Object>> handleHotelNotFound(HotelNotFoundException ex) { - Map<String, Object> body = new HashMap<>(); - body.put("timestamp", LocalDateTime.now()); - body.put("message", ex.getMessage()); - - return new ResponseEntity<>(body, HttpStatus.NOT_FOUND); - } - - @ExceptionHandler(InvalidRequestException.class) - public ResponseEntity<Map<String, Object>> handleInvalidRequest(InvalidRequestException ex) { - Map<String, Object> body = new HashMap<>(); - body.put("timestamp", LocalDateTime.now()); - body.put("message", ex.getMessage()); - - return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST); - } - - @ExceptionHandler(InvalidDateRangeException.class) - public ResponseEntity<Map<String, Object>> handleInvalidDateRange(InvalidDateRangeException ex) { - Map<String, Object> body = new HashMap<>(); - body.put("timestamp", LocalDateTime.now()); - body.put("message", ex.getMessage()); - - return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST); - } - - @ExceptionHandler(Exception.class) - public ResponseEntity<Map<String, Object>> handleGeneralException(Exception ex) { - Map<String, Object> body = new HashMap<>(); - body.put("timestamp", LocalDateTime.now()); - body.put("message", "An unexpected error occurred: " + ex.getMessage()); - - return new ResponseEntity<>(body, HttpStatus.INTERNAL_SERVER_ERROR); - } -} diff --git a/java/roomBooking/src/main/java/com/uva/monolith/exceptions/HotelNotFoundException.java b/java/roomBooking/src/main/java/com/uva/monolith/exceptions/HotelNotFoundException.java deleted file mode 100644 index 129a0b1086b4b78eb1f1725b9f241f51ce5540f8..0000000000000000000000000000000000000000 --- a/java/roomBooking/src/main/java/com/uva/monolith/exceptions/HotelNotFoundException.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.uva.monolith.exceptions; - -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - -@ResponseStatus(HttpStatus.NOT_FOUND) // Devuelve un 404 cuando se lanza la excepción -public class HotelNotFoundException extends RuntimeException { - public HotelNotFoundException(int id) { - super("Hotel not found with id: " + id); - } -} diff --git a/java/roomBooking/src/main/java/com/uva/monolith/exceptions/InvalidDateRangeException.java b/java/roomBooking/src/main/java/com/uva/monolith/exceptions/InvalidDateRangeException.java deleted file mode 100644 index 5fea986ef1e9279c459bc5aff10932049f283333..0000000000000000000000000000000000000000 --- a/java/roomBooking/src/main/java/com/uva/monolith/exceptions/InvalidDateRangeException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.uva.monolith.exceptions; - -public class InvalidDateRangeException extends RuntimeException { - public InvalidDateRangeException(String message) { - super(message); - } -} diff --git a/java/roomBooking/src/main/java/com/uva/monolith/exceptions/InvalidRequestException.java b/java/roomBooking/src/main/java/com/uva/monolith/exceptions/InvalidRequestException.java deleted file mode 100644 index ca09e054420dd174c4d2c3424dcc8fe74b6c8576..0000000000000000000000000000000000000000 --- a/java/roomBooking/src/main/java/com/uva/monolith/exceptions/InvalidRequestException.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.uva.monolith.exceptions; - -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - -@ResponseStatus(HttpStatus.BAD_REQUEST) -public class InvalidRequestException extends RuntimeException { - public InvalidRequestException(String message) { - super(message); - } -} diff --git a/java/roomBooking/src/main/java/com/uva/monolith/filter/JwtAuthenticationFilter.java b/java/roomBooking/src/main/java/com/uva/monolith/filter/JwtAuthenticationFilter.java deleted file mode 100644 index 3b088ce78caee67736a2f1bc7f24f60e49b5a0fa..0000000000000000000000000000000000000000 --- a/java/roomBooking/src/main/java/com/uva/monolith/filter/JwtAuthenticationFilter.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.uva.monolith.filter; - -import com.auth0.jwt.JWT; -import com.auth0.jwt.JWTVerifier; -import com.auth0.jwt.algorithms.Algorithm; -import com.auth0.jwt.interfaces.DecodedJWT; -import com.auth0.jwt.exceptions.JWTVerificationException; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; -import org.springframework.stereotype.Component; - -import com.uva.monolith.services.users.models.UserRol; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.Filter; - -import java.io.IOException; -import java.time.LocalDateTime; -import java.util.Collections; - -@Component -public class JwtAuthenticationFilter implements Filter { - - @Value("${security.jwt.secret-key}") - private String secretKey; - - @Value("${security.jwt.kid}") - private String kid; - - @Value("${security.jwt.expiration-time}") - private long jwtExpiration; - - private Algorithm getSigningAlgorithm() { - return Algorithm.HMAC256(secretKey); // Usar HMAC256 con la clave secreta - } - - private String getTokenFromRequest(HttpServletRequest request) { - String authHeader = request.getHeader("Authorization"); - if (authHeader == null || !authHeader.startsWith("Bearer ")) { - return null; - } - return authHeader.substring(7); - } - - private DecodedJWT validateAndDecodeToken(String token) { - try { - JWTVerifier verifier = JWT.require(getSigningAlgorithm()).build(); - return verifier.verify(token); // Verifica y decodifica el token - } catch (JWTVerificationException ex) { - System.out.println( - "[" + LocalDateTime.now().toString() + "] Error de verificación del token: " + ex.getMessage()); - return null; - } - } - - private String getEmailFromToken(DecodedJWT jwt) { - return jwt.getClaim("email").asString(); - } - - private UserRol getRoleFromToken(DecodedJWT jwt) { - String role = jwt.getClaim("rol").asString(); - return UserRol.valueOf(role); - } - - private String formatRole(UserRol rol) { - return String.format("ROLE_%s", rol.toString()); - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { - HttpServletRequest httpRequest = (HttpServletRequest) request; - String token = getTokenFromRequest(httpRequest); - - System.out.print("[" + LocalDateTime.now().toString() + "] TOKEN: " + token); - - if (token != null) { - DecodedJWT jwt = validateAndDecodeToken(token); - System.out.print(" " + jwt.toString() + " "); - if (jwt != null) { - String email = getEmailFromToken(jwt); - UserRol role = getRoleFromToken(jwt); - System.out.print(" email=" + email + " role=" + role + " "); - - if (email != null && role != null && SecurityContextHolder.getContext().getAuthentication() == null) { - // Crear la autoridad con el rol del token - SimpleGrantedAuthority authority = new SimpleGrantedAuthority(formatRole(role)); - - // Crear autenticación - UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(email, - null, Collections.singletonList(authority)); - authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest)); - - // Establecer autenticación en el contexto de seguridad - SecurityContextHolder.getContext().setAuthentication(authentication); - } - } - } - - // Continuar con el resto de filtros - chain.doFilter(request, response); - } -} diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/bookings/controllers/BookingController.java b/java/roomBooking/src/main/java/com/uva/monolith/services/bookings/controllers/BookingController.java deleted file mode 100644 index 61feb5104e87b7fcd333e36f07493532f397dbcf..0000000000000000000000000000000000000000 --- a/java/roomBooking/src/main/java/com/uva/monolith/services/bookings/controllers/BookingController.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.uva.monolith.services.bookings.controllers; - -import com.uva.monolith.services.bookings.models.Booking; -import com.uva.monolith.services.bookings.services.BookingService; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.time.LocalDate; -import java.util.List; - -@RestController -@RequestMapping("/bookings") -@CrossOrigin(origins = "*") -public class BookingController { - - private final BookingService bookingService; - - public BookingController(BookingService bookingService) { - this.bookingService = bookingService; - } - - @GetMapping - public List<Booking> getAllBookings( - @RequestParam(required = false) LocalDate start, - @RequestParam(required = false) LocalDate end, - @RequestParam(required = false) Integer roomId, - @RequestParam(required = false) Integer userId) { - return bookingService.getBookings(start, end, roomId, userId); - } - - @PostMapping - public Booking createBooking(@RequestBody Booking booking) { - return bookingService.createBooking(booking); - } - - @GetMapping("/{id}") - public Booking getBookingById(@PathVariable Integer id) { - return bookingService.getBookingById(id); - } - - @DeleteMapping("/{id}") - public ResponseEntity<Void> deleteBooking(@PathVariable Integer id) { - try { - bookingService.deleteBooking(id); - return new ResponseEntity<>(HttpStatus.ACCEPTED); - } catch (RuntimeException e) { - return new ResponseEntity<>(HttpStatus.NOT_FOUND); - } - } - - @DeleteMapping("/hotel/{hotelId}") - public ResponseEntity<Void> deleteBookingsByHotelId(@PathVariable Integer hotelId) { - try { - bookingService.deleteBookingsByHotelId(hotelId); - return new ResponseEntity<>(HttpStatus.ACCEPTED); - } catch (RuntimeException e) { - return new ResponseEntity<>(HttpStatus.NOT_FOUND); - } - } - -} diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/bookings/models/Booking.java b/java/roomBooking/src/main/java/com/uva/monolith/services/bookings/models/Booking.java deleted file mode 100644 index 533ee0c7a1fa053dce449b855f1e46e08dabbe66..0000000000000000000000000000000000000000 --- a/java/roomBooking/src/main/java/com/uva/monolith/services/bookings/models/Booking.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.uva.monolith.services.bookings.models; - -import jakarta.persistence.Basic; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.Table; -import java.time.LocalDate; - -import com.uva.monolith.services.hotels.models.Room; -import com.uva.monolith.services.users.models.Client; - -@Entity -@Table(name = "bookings") -public class Booking { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Basic(optional = false) - private int id; - @JoinColumn(name = "user_id", referencedColumnName = "id") - @ManyToOne(optional = false, fetch = FetchType.EAGER, cascade = CascadeType.MERGE) - private Client userId; - @JoinColumn(name = "room_id", referencedColumnName = "id") - @ManyToOne(optional = false, fetch = FetchType.EAGER, cascade = CascadeType.MERGE) - private Room roomId; - @Column(name = "start_date", nullable = false) - private LocalDate startDate; - @Column(name = "end_date", nullable = false) - private LocalDate endDate; - - public Booking() { - } - - public Booking(int id, Client userId, Room roomID, LocalDate startDate, LocalDate endDate) { - this.id = id; - this.userId = userId; - this.roomId = roomID; - this.startDate = startDate; - this.endDate = endDate; - } - - public void setId(int id) { - this.id = id; - } - - public int getId() { - return this.id; - } - - public void setUserId(Client userId) { - this.userId = userId; - } - - public Client getUserId() { - return this.userId; - } - - public void setRoomId(Room roomID) { - this.roomId = roomID; - } - - public Room getRoomId() { - return this.roomId; - } - - public void setStartDate(LocalDate startDate) { - this.startDate = startDate; - } - - public LocalDate getStartDate() { - return this.startDate; - } - - public void setEndDate(LocalDate endDate) { - this.endDate = endDate; - } - - public LocalDate getEndDate() { - return this.endDate; - } - -} diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/bookings/repositories/BookingRepository.java b/java/roomBooking/src/main/java/com/uva/monolith/services/bookings/repositories/BookingRepository.java deleted file mode 100644 index b5ace65939b898798e6e5416fedda793388f2615..0000000000000000000000000000000000000000 --- a/java/roomBooking/src/main/java/com/uva/monolith/services/bookings/repositories/BookingRepository.java +++ /dev/null @@ -1,41 +0,0 @@ -// BookingRepository.java -package com.uva.monolith.services.bookings.repositories; - -import jakarta.transaction.Transactional; - -import java.time.LocalDate; -import java.util.List; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; - -import com.uva.monolith.services.bookings.models.Booking; - -public interface BookingRepository extends JpaRepository<Booking, Integer> { - @Query("SELECT b FROM Booking b WHERE b.userId.id = ?1") - List<Booking> findByUserId(int userId); - - @Query("SELECT b FROM Booking b WHERE b.roomId.id = ?1") - List<Booking> findByRoomId(int roomId); - - @Query("SELECT b FROM Booking b WHERE b.startDate >= ?1 AND b.endDate <= ?2") - List<Booking> findByDateRange(@Param("startDate") LocalDate startDate, - @Param("endDate") LocalDate endDate); - - @Query("SELECT b FROM Booking b WHERE b.roomId.id = ?1 AND b.startDate < ?2 AND b.endDate > ?3") - List<Booking> findByRoomIdAndDateRange(@Param("roomId") int roomId, @Param("startDate") LocalDate startDate, - @Param("endDate") LocalDate endDate); - - @Transactional - @Modifying - @Query("DELETE FROM Booking b WHERE b.id = ?1") - void deleteBookingById(@Param("id") Integer id); - - @Transactional - @Modifying - @Query("DELETE FROM Booking b WHERE b.roomId.hotel.id = ?1") - void deleteAllByHotelId(int hotelId); - -} diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/bookings/services/BookingService.java b/java/roomBooking/src/main/java/com/uva/monolith/services/bookings/services/BookingService.java deleted file mode 100644 index 36c51e602fe7cf9bfe9b88f5261104a3c068c782..0000000000000000000000000000000000000000 --- a/java/roomBooking/src/main/java/com/uva/monolith/services/bookings/services/BookingService.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.uva.monolith.services.bookings.services; - -import com.uva.monolith.services.bookings.models.Booking; -import com.uva.monolith.services.bookings.repositories.BookingRepository; -import com.uva.monolith.services.hotels.models.Room; -import com.uva.monolith.services.hotels.repositories.RoomRepository; -import com.uva.monolith.services.users.models.Client; -import com.uva.monolith.services.users.repositories.ClientRepository; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.time.LocalDate; -import java.util.List; - -@Service -public class BookingService { - - @Autowired - private BookingRepository bookingRepository; - - @Autowired - private RoomRepository roomRepository; - - @Autowired - private ClientRepository clientRepository; - - public List<Booking> getBookings(LocalDate start, LocalDate end, Integer roomId, Integer userId) { - List<Booking> bookings = null; - - if (start != null && end != null) { - bookings = bookingRepository.findByDateRange(start, end); - } - if (roomId != null) { - if (bookings == null) { - bookings = bookingRepository.findByRoomId(roomId); - } else { - bookings = bookings.stream() - .filter(booking -> booking.getRoomId().getId() == roomId) - .toList(); - } - } - if (userId != null) { - if (bookings == null) { - bookings = bookingRepository.findByUserId(userId); - } else { - bookings = bookings.stream() - .filter(booking -> booking.getUserId().getId() == userId) - .toList(); - } - } - if (start == null && end == null && roomId == null && userId == null) { - bookings = bookingRepository.findAll(); - } - - return bookings; - } - - public Booking createBooking(Booking booking) { - Client user = clientRepository.findById(booking.getUserId().getId()) - .orElseThrow(() -> new RuntimeException("User not found")); - Room room = roomRepository.findById(booking.getRoomId().getId()) - .orElseThrow(() -> new RuntimeException("Room not found")); - - // Check availability - List<Booking> existingBookings = bookingRepository.findByRoomIdAndDateRange( - room.getId(), booking.getStartDate(), booking.getEndDate()); - - if (!existingBookings.isEmpty()) { - throw new RuntimeException("Room is not available for the selected dates"); - } - - booking.setUserId(user); - booking.setRoomId(room); - return bookingRepository.save(booking); - } - - public Booking getBookingById(Integer id) { - return bookingRepository.findById(id) - .orElseThrow(() -> new RuntimeException("Booking not found")); - } - - public void deleteBooking(Integer id) { - if (!bookingRepository.existsById(id)) { - throw new RuntimeException("Booking not found"); - } - bookingRepository.deleteBookingById(id); - } - - public void deleteBookingsByHotelId(int hotelId) { - bookingRepository.deleteAllByHotelId(hotelId); - } -} diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/controllers/HotelController.java b/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/controllers/HotelController.java deleted file mode 100644 index 781cb6278b0b40d237b603d7cfc61a63b7b2af0f..0000000000000000000000000000000000000000 --- a/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/controllers/HotelController.java +++ /dev/null @@ -1,146 +0,0 @@ -package com.uva.monolith.services.hotels.controllers; - -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.time.LocalDate; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.bind.annotation.*; - -import com.uva.monolith.exceptions.HotelNotFoundException; -import com.uva.monolith.exceptions.InvalidDateRangeException; -import com.uva.monolith.exceptions.InvalidRequestException; -import com.uva.monolith.services.bookings.repositories.BookingRepository; -import com.uva.monolith.services.hotels.models.Hotel; -import com.uva.monolith.services.hotels.models.Room; -import com.uva.monolith.services.hotels.repositories.HotelRepository; -import com.uva.monolith.services.hotels.repositories.RoomRepository; -import com.uva.monolith.services.users.models.HotelManager; -import com.uva.monolith.services.users.repositories.HotelManagerRepository; - -@RestController -@RequestMapping("hotels") -@CrossOrigin(origins = "*") -public class HotelController { - @Autowired - private HotelManagerRepository hotelManagerRepository; - private final HotelRepository hotelRepository; - private final RoomRepository roomRepository; - private final BookingRepository bookingRepository; - - public HotelController(HotelRepository hotelRepository, RoomRepository roomRepository, - BookingRepository bookingRepository) { - this.hotelRepository = hotelRepository; - this.roomRepository = roomRepository; - this.bookingRepository = bookingRepository; - } - - // Obtener todos los hoteles - @GetMapping - public List<Hotel> getAllHotels( - @RequestParam(required = false) Integer managerId, - @RequestParam(required = false) LocalDate start, - @RequestParam(required = false) LocalDate end) { - List<Hotel> hotels = (managerId != null) - ? hotelRepository.findAllByHotelManager(managerId) - : hotelRepository.findAll(); - if (start != null && end != null) { - // Filtramos para los hoteles que - // tengan habitaciones disponibles para ese rango de fechas - System.out.println(start); - System.out.println(end); - hotels = hotels.stream().map(h -> { - if (h.getRooms().size() == 0) - return h; - h.setRooms(roomRepository.findAvailableRoomsByHotelAndDates(h.getId(), start, end)); - return h; - }).filter(h -> h.getRooms().size() >= 0).toList(); - } - return hotels; - } - - // Añadir un hotel con sus habitaciones - @PostMapping - public ResponseEntity<Hotel> addHotel(@RequestBody Hotel hotel) { - Optional<HotelManager> hm = hotelManagerRepository.findById(hotel.getHotelManager().getId()); - if (!hm.isPresent()) { - return new ResponseEntity<>(HttpStatus.BAD_REQUEST); - } - hotel.setHotelManager(hm.get()); - Hotel savedHotel = hotelRepository.save(hotel); - return new ResponseEntity<>(savedHotel, HttpStatus.CREATED); - } - - // Obtener un hotel por su ID - @GetMapping("/{id}") - public Hotel getHotelById(@PathVariable int id) { - return hotelRepository.findById(id) - .orElseThrow(() -> new HotelNotFoundException(id)); - } - - // Borrar un hotel junto con sus habitaciones (borrado en cascada) - @DeleteMapping("/{id}") - @Transactional - public ResponseEntity<Void> deleteHotel(@PathVariable Integer id) { - Hotel target = hotelRepository.findById(id) - .orElseThrow(() -> new HotelNotFoundException(id)); - bookingRepository.deleteAllByHotelId(id); - HotelManager hm = target.getHotelManager(); - hm.getHotels().removeIf(h -> h.getId() == target.getId()); - hotelManagerRepository.save(hm); - bookingRepository.flush(); - hotelRepository.delete(target); - return new ResponseEntity<>(HttpStatus.NO_CONTENT); - } - - // Obtener habitaciones de un hotel según disponibilidad y fechas - @GetMapping("/{hotelId}/rooms") - public ResponseEntity<List<Room>> getRoomsFromHotel( - @PathVariable int hotelId, - @RequestParam(required = false) LocalDate start, - @RequestParam(required = false) LocalDate end) { - - List<Room> rooms; - if (start != null && end != null) { - if (!start.isBefore(end)) { - throw new InvalidDateRangeException("La fecha de inicio debe ser anterior a la fecha de fin"); - } - rooms = roomRepository.findAvailableRoomsByHotelAndDates(hotelId, start, end); - } else { - rooms = roomRepository.findAllByHotelId(hotelId); - } - return new ResponseEntity<>(rooms, HttpStatus.OK); - } - - // Actualizar disponibilidad de una habitación específica en un hotel - @PatchMapping("/{hotelId}/rooms/{roomId}") - public ResponseEntity<Room> updateRoomAvailability( - @PathVariable int hotelId, - @PathVariable int roomId, - @RequestBody Map<String, Boolean> body) { - - if (!body.containsKey("available")) { - throw new InvalidRequestException("El campo 'available' es obligatorio"); - } - - Room targetRoom = roomRepository.findByIdAndHotelId(roomId, hotelId) - .orElseThrow(() -> new IllegalArgumentException("Habitación no encontrada")); - - targetRoom.setAvailable(body.get("available")); - roomRepository.save(targetRoom); - - return new ResponseEntity<>(targetRoom, HttpStatus.OK); - } - - // Obtener los detalles de una habitación específica en un hotel - @GetMapping("/{hotelId}/rooms/{roomId}") - public Room getRoomByIdFromHotel( - @PathVariable int hotelId, @PathVariable int roomId) { - return roomRepository.findByIdAndHotelId(roomId, hotelId) - .orElseThrow(() -> new HotelNotFoundException(hotelId)); - } -} diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/models/Address.java b/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/models/Address.java deleted file mode 100644 index 5f31a2a530da46c00460ad6cc6151b0769c1da61..0000000000000000000000000000000000000000 --- a/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/models/Address.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.uva.monolith.services.hotels.models; - -import com.fasterxml.jackson.annotation.JsonIgnore; - -import jakarta.persistence.Basic; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.OneToOne; -import jakarta.persistence.Table; - -@Entity -@Table(name = "addresses") -public class Address { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Basic(optional = false) - private int id; - - @Basic(optional = false) - @Column(name = "street_kind") - private String streetKind; - - @Basic(optional = false) - @Column(name = "street_name") - private String streetName; - - @Basic(optional = false) - private int number; - - @Basic(optional = false) - @Column(name = "post_code") - private String postCode; - - @Basic(optional = true) - @Column(name = "other_info") - private String otherInfo; - - @JsonIgnore - @OneToOne(mappedBy = "address", fetch = FetchType.EAGER, cascade = CascadeType.ALL) - private Hotel hotel; - - public Address() { - } - - public Address(String streetKind, String streetName, int number, String postCode, String otherInfo, Hotel hotel) { - setStreetKind(streetKind); - setStreetName(streetName); - setNumber(number); - setPostCode(postCode); - setOtherInfo(otherInfo); - setHotel(hotel); - } - - public int getId() { - return this.id; - } - - public void setId(int id) { - this.id = id; - } - - public String getStreetKind() { - return this.streetKind; - } - - public void setStreetKind(String streetKind) { - this.streetKind = streetKind; - } - - public String getStreetName() { - return this.streetName; - } - - public void setStreetName(String streetName) { - this.streetName = streetName; - } - - public int getNumber() { - return this.number; - } - - public void setNumber(int number) { - this.number = number; - } - - public String getPostCode() { - return this.postCode; - } - - public void setPostCode(String postCode) { - this.postCode = postCode; - } - - public String getOtherInfo() { - return this.otherInfo; - } - - public void setOtherInfo(String otherInfo) { - this.otherInfo = otherInfo; - } - - public Hotel getHotel() { - return this.hotel; - } - - public void setHotel(Hotel hotel) { - this.hotel = hotel; - } -} diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/models/Hotel.java b/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/models/Hotel.java deleted file mode 100644 index 21f5cec8b44f9fae1a566b7af92964d30b654546..0000000000000000000000000000000000000000 --- a/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/models/Hotel.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.uva.monolith.services.hotels.models; - -import java.util.List; - -import com.uva.monolith.services.users.models.HotelManager; - -import jakarta.persistence.Basic; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.OneToMany; -import jakarta.persistence.OneToOne; -import jakarta.persistence.Table; - -@Entity -@Table(name = "hotels") -// @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, -// property = "id") -public class Hotel { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Basic(optional = false) - private int id; - - @Basic(optional = false) - private String name; - - @JoinColumn(name = "address_id", referencedColumnName = "id") - @OneToOne(optional = false, cascade = CascadeType.ALL, fetch = FetchType.EAGER) - private Address address; - - @OneToMany(mappedBy = "hotel", fetch = FetchType.EAGER, cascade = CascadeType.ALL) - private List<Room> rooms; - - @ManyToOne(optional = false) - @JoinColumn(name = "hotel_manager", referencedColumnName = "id") - private HotelManager hotelManager; - - public Hotel() { - } - - public Hotel(int id, String name, Address address, List<Room> rooms, HotelManager hotelManager) { - setId(id); - setName(name); - setAddress(address); - setRooms(rooms); - setHotelManager(hotelManager); - } - - public int getId() { - return this.id; - } - - public void setId(int id) { - this.id = id; - } - - public String getName() { - return this.name; - } - - public void setName(String name) { - this.name = name; - } - - public Address getAddress() { - return this.address; - } - - public void setAddress(Address address) { - this.address = address; - } - - public List<Room> getRooms() { - return this.rooms; - } - - public void setRooms(List<Room> rooms) { - this.rooms = rooms; - rooms.forEach(room -> room.setHotel(this)); - } - - public void setHotelManager(HotelManager hotelManager) { - this.hotelManager = hotelManager; - } - - public HotelManager getHotelManager() { - return hotelManager; - } -} diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/models/Room.java b/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/models/Room.java deleted file mode 100644 index 72a6a728f729dc7b93b727606172de7cbe385ebb..0000000000000000000000000000000000000000 --- a/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/models/Room.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.uva.monolith.services.hotels.models; - -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.uva.monolith.services.bookings.models.Booking; - -import jakarta.persistence.Basic; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.OneToMany; -import jakarta.persistence.Table; - -@Entity -@Table(name = "rooms") -// @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, -// property = "id") -public class Room { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Basic(optional = false) - private int id; - - @ManyToOne - @JoinColumn(name = "hotel_id", referencedColumnName = "id") - @JsonIgnore - private Hotel hotel; - @Column(name = "room_number", nullable = false) - private String roomNumber; - @Column(name = "type", nullable = false) - private RoomType type; - @Column(name = "available", nullable = false) - private boolean available; - @JsonIgnore - @OneToMany(mappedBy = "roomId", fetch = FetchType.EAGER, cascade = CascadeType.ALL) - private List<Booking> bookings; - - public Room() { - } - - public Room(int id, Hotel hotelId, String roomNumber, RoomType type, boolean available, List<Booking> bookings) { - this.id = id; - this.hotel = hotelId; - this.roomNumber = roomNumber; - this.type = type; - this.available = available; - this.bookings = bookings; - } - - public void setId(int id) { - this.id = id; - } - - public int getId() { - return this.id; - } - - public void setHotel(Hotel hotelId) { - this.hotel = hotelId; - } - - public Hotel getHotel() { - return this.hotel; - } - - public void setRoomNumber(String roomNumber) { - this.roomNumber = roomNumber; - } - - public String getRoomNumber() { - return this.roomNumber; - } - - public void setType(RoomType type) { - this.type = type; - } - - public RoomType getType() { - return this.type; - } - - public void setAvailable(boolean available) { - this.available = available; - } - - public boolean isAvailable() { - return this.available; - } - - public List<Booking> getBookings() { - return this.bookings; - } - - public void setBookings(List<Booking> bookings) { - this.bookings = bookings; - } -} diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/models/RoomType.java b/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/models/RoomType.java deleted file mode 100644 index b9e82584850795afa7c7392248e3a6472ce24ac0..0000000000000000000000000000000000000000 --- a/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/models/RoomType.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.uva.monolith.services.hotels.models; - -public enum RoomType { - SINGLE, - DOUBLE, - SUITE -} diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/repositories/HotelRepository.java b/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/repositories/HotelRepository.java deleted file mode 100644 index eddb6640a275284cb70bde60a29afd33039ba454..0000000000000000000000000000000000000000 --- a/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/repositories/HotelRepository.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.uva.monolith.services.hotels.repositories; - -import java.util.List; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; - -import com.uva.monolith.services.hotels.models.Hotel; - -public interface HotelRepository extends JpaRepository<Hotel, Integer> { - @Query("SELECT h FROM Hotel h WHERE h.hotelManager.id = ?1") - List<Hotel> findAllByHotelManager(Integer hotelManager); -} diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/repositories/RoomRepository.java b/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/repositories/RoomRepository.java deleted file mode 100644 index 15cc3d370129e0753b9ac9b1eb24136c93bf5405..0000000000000000000000000000000000000000 --- a/java/roomBooking/src/main/java/com/uva/monolith/services/hotels/repositories/RoomRepository.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.uva.monolith.services.hotels.repositories; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; - -import com.uva.monolith.services.hotels.models.Room; - -import java.time.LocalDate; -import java.util.List; -import java.util.Optional; - -public interface RoomRepository extends JpaRepository<Room, Integer> { - - Optional<Room> findByIdAndHotelId(int id, int hotelId); - - // Encontrar todas las habitaciones de un hotel - List<Room> findAllByHotelId(int hotelId); - - // Encontrar habitaciones disponibles de un hotel en un rango de fechas - @Query(""" - SELECT r FROM Room r - WHERE r.hotel.id = ?1 - AND r.available = true - AND NOT EXISTS ( - SELECT b FROM Booking b - WHERE b.roomId.id = r.id - AND ( - b.endDate >= ?2 - OR - ?3 >= b.startDate - ) - ) - """) - List<Room> findAvailableRoomsByHotelAndDates( - int hotelId, LocalDate startDate, LocalDate endDate); -} diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/users/controllers/UserController.java b/java/roomBooking/src/main/java/com/uva/monolith/services/users/controllers/UserController.java deleted file mode 100644 index 15fc498b79e239020aba6833ae9dbfaa8eeb59ee..0000000000000000000000000000000000000000 --- a/java/roomBooking/src/main/java/com/uva/monolith/services/users/controllers/UserController.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.uva.monolith.services.users.controllers; - -import java.util.List; -import java.util.Map; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.CrossOrigin; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PatchMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.client.HttpClientErrorException; - -import com.uva.monolith.services.users.models.User; -import com.uva.monolith.services.users.models.UserStatus; -import com.uva.monolith.services.users.services.UserService; - -@RestController -@RequestMapping("users") -@CrossOrigin(origins = "*") -public class UserController { - - @Autowired - private UserService userService; - - @GetMapping - public ResponseEntity<List<User>> getAllUsers() { - List<User> users = userService.getAllUsers(); - return ResponseEntity.ok(users); - } - - @GetMapping(params = { "email" }) - public ResponseEntity<?> getUserByEmail(@RequestParam String email) { - try { - return ResponseEntity.ok(userService.getUserByEmail(email)); - } catch (HttpClientErrorException e) { - if (e.getStatusCode() == HttpStatus.NOT_FOUND) - return new ResponseEntity<String>(HttpStatus.NOT_FOUND); - throw e; - } - } - - @PostMapping - public ResponseEntity<?> addUser(@RequestBody User user) { - userService.registerNewUser(user); - return new ResponseEntity<>(HttpStatus.ACCEPTED); - } - - @GetMapping("/{id}") - public ResponseEntity<?> getUserById(@PathVariable int id) { - return ResponseEntity.ok(userService.getUserById(id)); - } - - @PutMapping("/{id}") - public ResponseEntity<?> updateUserData(@PathVariable int id, @RequestBody Map<String, String> json) { - System.err.println(json.entrySet().size()); - json.keySet().forEach(k -> System.err.println(k)); - String name = json.get("name"); - String email = json.get("email"); - if (name == null || email == null) { - return new ResponseEntity<String>("Missing required fields", HttpStatus.BAD_REQUEST); - } - try { - User user = userService.updateUserData(id, name, email); - return new ResponseEntity<User>(user, HttpStatus.OK); - } catch (HttpClientErrorException e) { - if (e.getStatusCode() == HttpStatus.NOT_FOUND) - return new ResponseEntity<String>(HttpStatus.NOT_FOUND); - throw e; - } - } - - @PatchMapping("/{id}") - public ResponseEntity<?> updateUserState(@PathVariable int id, @RequestBody Map<String, String> json) { - - String strStatus = json.get("status"); - if (strStatus == null) { - return new ResponseEntity<String>("Missing required fields", HttpStatus.BAD_REQUEST); - } - try { - UserStatus userStatus = UserStatus.valueOf(strStatus); - return ResponseEntity.ok(userService.updateUserStatus(id, userStatus)); - } catch (IllegalArgumentException e) { - return new ResponseEntity<String>("Unknown user state", HttpStatus.BAD_REQUEST); - } catch (HttpClientErrorException e) { - if (e.getStatusCode() == HttpStatus.NOT_FOUND) - return new ResponseEntity<String>(HttpStatus.NOT_FOUND); - throw e; - } - - } - - @DeleteMapping("/{id}") - public ResponseEntity<?> deleteUser(@PathVariable Integer id) { - try { - return ResponseEntity.ok(userService.deleteUserById(id)); - } catch (HttpClientErrorException e) { - if (e.getStatusCode() == HttpStatus.NOT_FOUND) - return new ResponseEntity<String>(HttpStatus.NOT_FOUND); - throw e; - } - } - -} diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/AuthResponse.java b/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/AuthResponse.java deleted file mode 100644 index 8f334813beee8cb95caa275c66e46c9c539f2bd5..0000000000000000000000000000000000000000 --- a/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/AuthResponse.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.uva.monolith.services.users.models; - -public class AuthResponse { - - private int id; - private String username; - private String email; - private String password; - private UserRol rol; - - public int getId() { - return this.id; - } - - public void setId(int id) { - this.id = id; - } - - public String getUsername() { - return this.username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getEmail() { - return this.email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getPassword() { - return this.password; - } - - public void setPassword(String password) { - this.password = password; - } - - public UserRol getRol() { - return this.rol; - } - - public void setRol(UserRol rol) { - this.rol = rol; - } - -} diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/Client.java b/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/Client.java deleted file mode 100644 index e106ecd3789a0237602e3194feacab7ddcbf4dfd..0000000000000000000000000000000000000000 --- a/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/Client.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.uva.monolith.services.users.models; - -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.uva.monolith.services.bookings.models.Booking; - -import jakarta.persistence.Basic; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; -import jakarta.persistence.FetchType; -import jakarta.persistence.OneToMany; -import jakarta.persistence.Table; - -@Entity -@Table(name = "user_client") -public class Client extends User { - - @Basic(optional = false) - @Column(nullable = false) - @Enumerated(EnumType.STRING) - private UserStatus status; - - @JsonIgnore - @OneToMany(mappedBy = "userId", fetch = FetchType.EAGER, cascade = CascadeType.ALL) - private List<Booking> bookings; - - public Client() { - super(); - bookings = new ArrayList<>(); - status = UserStatus.NO_BOOKINGS; - } - - public Client(int id, String name, String email, String password, UserStatus status, - List<Booking> bookings) { - super(id, name, email, password, UserRol.CLIENT); - setStatus(status); - setBookings(bookings); - } - - public UserStatus getStatus() { - if (getBookings() == null || getBookings().isEmpty()) - return UserStatus.NO_BOOKINGS; - boolean activeBookings = getBookings().stream() - .anyMatch(booking -> !booking.getEndDate().isBefore(LocalDate.now())); // reserva >= ahora - return activeBookings ? UserStatus.WITH_ACTIVE_BOOKINGS : UserStatus.WITH_INACTIVE_BOOKINGS; - } - - public void setStatus(UserStatus status) { - this.status = status; - } - - public List<Booking> getBookings() { - return this.bookings; - } - - public void setBookings(List<Booking> bookings) { - this.bookings = bookings; - } -} diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/HotelManager.java b/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/HotelManager.java deleted file mode 100644 index 0e6f4b0aafa35ab8b23d202814c1fabefdcf86ed..0000000000000000000000000000000000000000 --- a/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/HotelManager.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.uva.monolith.services.users.models; - -import java.util.ArrayList; -import java.util.List; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.uva.monolith.services.hotels.models.Hotel; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.OneToMany; -import jakarta.persistence.Table; - -@Entity -@Table(name = "hotel_manager_user") -public class HotelManager extends User { - - @JsonIgnore - @OneToMany(mappedBy = "hotelManager", fetch = FetchType.EAGER, cascade = CascadeType.ALL) - private List<Hotel> hotels; - - public HotelManager() { - super(); - hotels = new ArrayList<>(); - } - - public HotelManager(int id, String name, String email, String password, List<Hotel> hotels) { - super(id, name, email, password, UserRol.HOTEL_ADMIN); - setHotels(hotels); - } - - public List<Hotel> getHotels() { - return this.hotels; - } - - public void setHotels(List<Hotel> hotels) { - this.hotels = hotels; - } -} diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/User.java b/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/User.java deleted file mode 100644 index ecfd33d1e39f6760f39fb0ad48fa195cd71432ea..0000000000000000000000000000000000000000 --- a/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/User.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.uva.monolith.services.users.models; - -import com.fasterxml.jackson.annotation.JsonIgnore; - -import jakarta.persistence.Basic; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.Inheritance; -import jakarta.persistence.InheritanceType; -import jakarta.persistence.Table; - -@Entity -@Inheritance(strategy = InheritanceType.JOINED) -@Table(name = "users") -public class User { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Basic(optional = false) - @Column(nullable = false) - private int id; - - @Basic(optional = false) - @Column(nullable = false) - private String name; - - @Basic(optional = false) - @Column(nullable = false, unique = true) - private String email; - - @Basic(optional = false) - @Column(nullable = false) - private String password; - - @Basic(optional = false) - @Column(nullable = false) - @Enumerated(EnumType.STRING) - private UserRol rol = UserRol.CLIENT; - - public User() { - } - - public User(int id, String name, String email, String password, UserRol rol) { - setId(id); - setName(name); - setEmail(email); - setRol(rol); - } - - public int getId() { - return this.id; - } - - public void setId(int id) { - this.id = id; - } - - public String getName() { - return this.name; - } - - public void setName(String name) { - this.name = name; - } - - public String getEmail() { - return this.email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getPassword() { - return password; - } - - public void setPassword(String rawPassword) { - this.password = rawPassword; - } - - public UserRol getRol() { - return this.rol; - } - - public void setRol(UserRol rol) { - this.rol = rol; - } -} diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/UserRol.java b/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/UserRol.java deleted file mode 100644 index f408ba5ef9d34d96c32d3c42a6c2c51b1c6f22b1..0000000000000000000000000000000000000000 --- a/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/UserRol.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.uva.monolith.services.users.models; - -public enum UserRol { - ADMIN, HOTEL_ADMIN, CLIENT -} diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/UserStatus.java b/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/UserStatus.java deleted file mode 100644 index 362b8688260d4c13dc4a8eae205411c9d5533d79..0000000000000000000000000000000000000000 --- a/java/roomBooking/src/main/java/com/uva/monolith/services/users/models/UserStatus.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.uva.monolith.services.users.models; - -public enum UserStatus { - NO_BOOKINGS, WITH_ACTIVE_BOOKINGS, WITH_INACTIVE_BOOKINGS; -} diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/users/repositories/ClientRepository.java b/java/roomBooking/src/main/java/com/uva/monolith/services/users/repositories/ClientRepository.java deleted file mode 100644 index 1c1b46fbe665075b8f817367ff14ee65cf69ff76..0000000000000000000000000000000000000000 --- a/java/roomBooking/src/main/java/com/uva/monolith/services/users/repositories/ClientRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.uva.monolith.services.users.repositories; - -import java.util.Optional; -import org.springframework.data.jpa.repository.JpaRepository; - -import com.uva.monolith.services.users.models.Client; - -public interface ClientRepository extends JpaRepository<Client, Integer> { - Optional<Client> findByEmail(String email); -} \ No newline at end of file diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/users/repositories/HotelManagerRepository.java b/java/roomBooking/src/main/java/com/uva/monolith/services/users/repositories/HotelManagerRepository.java deleted file mode 100644 index 092a251b199fdecd80a2654fc3e6c96d1b7eb7f4..0000000000000000000000000000000000000000 --- a/java/roomBooking/src/main/java/com/uva/monolith/services/users/repositories/HotelManagerRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.uva.monolith.services.users.repositories; - -import java.util.Optional; -import org.springframework.data.jpa.repository.JpaRepository; - -import com.uva.monolith.services.users.models.HotelManager; - -public interface HotelManagerRepository extends JpaRepository<HotelManager, Integer> { - Optional<HotelManager> findByEmail(String email); -} \ No newline at end of file diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/users/repositories/UserRepository.java b/java/roomBooking/src/main/java/com/uva/monolith/services/users/repositories/UserRepository.java deleted file mode 100644 index e5b44c976f095719854aa8070abc843b898036fe..0000000000000000000000000000000000000000 --- a/java/roomBooking/src/main/java/com/uva/monolith/services/users/repositories/UserRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.uva.monolith.services.users.repositories; - -import java.util.Optional; - -import org.springframework.data.jpa.repository.JpaRepository; - -import com.uva.monolith.services.users.models.User; - -public interface UserRepository extends JpaRepository<User, Integer> { - Optional<User> findByEmail(String email); -} diff --git a/java/roomBooking/src/main/java/com/uva/monolith/services/users/services/UserService.java b/java/roomBooking/src/main/java/com/uva/monolith/services/users/services/UserService.java deleted file mode 100644 index bdd5b9db80a7c699844cfcd48669171ffbe6b8f0..0000000000000000000000000000000000000000 --- a/java/roomBooking/src/main/java/com/uva/monolith/services/users/services/UserService.java +++ /dev/null @@ -1,132 +0,0 @@ -package com.uva.monolith.services.users.services; - -import java.time.LocalDate; -import java.util.List; -import java.util.Optional; - -import org.springframework.beans.BeanUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Service; -import org.springframework.web.client.HttpClientErrorException; - -import com.uva.monolith.services.users.models.AuthResponse; -import com.uva.monolith.services.users.models.Client; -import com.uva.monolith.services.users.models.HotelManager; -import com.uva.monolith.services.users.models.User; -import com.uva.monolith.services.users.models.UserRol; -import com.uva.monolith.services.users.models.UserStatus; -import com.uva.monolith.services.users.repositories.ClientRepository; -import com.uva.monolith.services.users.repositories.HotelManagerRepository; -import com.uva.monolith.services.users.repositories.UserRepository; - -@Service -public class UserService { - - @Autowired - private UserRepository userRepository; - - @Autowired - private ClientRepository clientRepository; - - @Autowired - private HotelManagerRepository hotelManagerRepository; - - public List<User> getAllUsers() { - return userRepository.findAll(); - } - - private User assertUser(Optional<? extends User> opUser) { - return opUser.orElseThrow(() -> new HttpClientErrorException(HttpStatus.NOT_FOUND)); - } - - public User getUserById(int id) { - return assertUser(userRepository.findById(id)); - } - - public AuthResponse getUserByEmail(String email) { - User u = assertUser(userRepository.findByEmail(email)); - AuthResponse auth = new AuthResponse(); - BeanUtils.copyProperties(u, auth); - return auth; - } - - public User registerNewUser(User registerRequest) { - User newUser; - - // Aseguramos que tenga un rol, por defecto es cliente - if (registerRequest.getRol() == null) - registerRequest.setRol(UserRol.CLIENT); - - switch (registerRequest.getRol()) { - case HOTEL_ADMIN: - HotelManager hm = new HotelManager(); - BeanUtils.copyProperties(registerRequest, hm); - newUser = hotelManagerRepository.save(hm); - break; - - case ADMIN: - User admin = new User(); - BeanUtils.copyProperties(registerRequest, admin); - newUser = admin; // userAPI.save(admin); - break; - - case CLIENT: // Por defecto cliente normal - default: - Client client = new Client(); - BeanUtils.copyProperties(registerRequest, client); - client.setRol(UserRol.CLIENT); - newUser = clientRepository.save(client); - break; - } - return newUser; - } - - public User updateUserData(int id, String name, String email) { - User user = getUserById(id); - user.setName(name); - user.setEmail(email); - return userRepository.save(user); - } - - public User updateUserStatus(int id, UserStatus status) { - - Client user = (Client) assertUser(clientRepository.findById(id)); - - boolean activeBookings = user.getBookings().stream() - .anyMatch(booking -> !booking.getEndDate().isBefore(LocalDate.now())); // reserva >= ahora - boolean inactiveBookings = user.getBookings().stream() - .anyMatch(booking -> booking.getStartDate().isBefore(LocalDate.now())); // reserva < ahora - - switch (status) { - case NO_BOOKINGS: - if (!user.getBookings().isEmpty()) - throw new IllegalArgumentException("Invalid State: The user has at least one booking"); - break; - case WITH_ACTIVE_BOOKINGS: - if (user.getBookings().isEmpty()) - throw new IllegalArgumentException("Invalid State: The user don't has bookings"); - if (!activeBookings) - throw new IllegalArgumentException("Invalid State: The user don't has active bookings"); - break; - case WITH_INACTIVE_BOOKINGS: - if (user.getBookings().isEmpty()) - throw new IllegalArgumentException("Invalid State: The user don't has bookings"); - if (!inactiveBookings) - throw new IllegalArgumentException("Invalid State: The user don't has inactive bookings"); - break; - default: - break; - } - user.setStatus(status); - return userRepository.save(user); - } - - public User deleteUserById(int id) { - User user = getUserById(id); - // TODO eliminar reservas de usuario ahora mismo no por el modo cascada pero a - // futuro sí, después de la disgregación en microservicios - userRepository.deleteById(id); - return user; - } -} diff --git a/java/roomBooking/src/main/resources/application.properties b/java/roomBooking/src/main/resources/application.properties deleted file mode 100644 index e9b75a35a39f7947153b79b234d6377a853078d4..0000000000000000000000000000000000000000 --- a/java/roomBooking/src/main/resources/application.properties +++ /dev/null @@ -1,18 +0,0 @@ -spring.application.name=roomBooking -spring.jpa.hibernate.ddl-auto=update -spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect -spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:3306/RoomsBooking?createDatabaseIfNotExist=true -spring.datasource.username=user -spring.datasource.password=password -spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver -spring.security.user.name=admin -spring.security.user.password=admin - -# Usar esto para alternar entre las exposición del room repository ya que no es necesario su uso pero por defecto, al no cubrir su ruta, se expone -# spring.data.rest.base-path=false -external.services.auth.host=localhost:8101 - -security.jwt.secret-key=MiClaveDeSeguridadMuyLargaParaQueNoFalleSpringBoot -# 1h in millisecond -security.jwt.expiration-time=3600000 -security.jwt.kid=cYz3kNRLAirxVhHXQ5rh5xKrOwHwZVui \ No newline at end of file diff --git a/java/roomBooking/src/test/java/com/uva/roomBooking/RoomBookingApplicationTests.java b/java/roomBooking/src/test/java/com/uva/roomBooking/RoomBookingApplicationTests.java deleted file mode 100644 index 3b50599492c36017391c0dcabd81573f123a809a..0000000000000000000000000000000000000000 --- a/java/roomBooking/src/test/java/com/uva/roomBooking/RoomBookingApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.uva.roomBooking; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class RoomBookingApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/java/services/auth/src/main/java/com/uva/authentication/api/UserAPI.java b/java/services/auth/src/main/java/com/uva/authentication/api/UserAPI.java index d61a328f4353b10ee58bec766bb2ba24b047cd82..6239c85caa1aeca7f4fa838df9a940e11d0907a0 100644 --- a/java/services/auth/src/main/java/com/uva/authentication/api/UserAPI.java +++ b/java/services/auth/src/main/java/com/uva/authentication/api/UserAPI.java @@ -53,7 +53,7 @@ public class UserAPI { */ public User registerUser(RegisterRequest registerRequest) { String url = USER_API_URL; - System.out.println(registerRequest + " " + registerRequest.getPassword()); + System.out.println(url + " " + registerRequest); ResponseEntity<User> userResponse = restTemplate.postForEntity(url, registerRequest, User.class); if (!userResponse.getStatusCode().is2xxSuccessful()) { String errorMessage = "Failed to register user: " + userResponse.getStatusCode() + ". " + userResponse.getBody(); @@ -80,4 +80,10 @@ public class UserAPI { restTemplate.put(url, body, id); } + public void deleteUser(User user) { + String url = USER_API_URL + "/{id}"; + + restTemplate.delete(url, user.getId()); + } + } diff --git a/java/services/auth/src/main/java/com/uva/authentication/config/RestTemplateConfig.java b/java/services/auth/src/main/java/com/uva/authentication/config/RestTemplateConfig.java index 9ab4fa43b1587585cfdf4c1cd6d80b9556a78606..dbcefe7ec34f65cca38b60db09be7355a98b73a7 100644 --- a/java/services/auth/src/main/java/com/uva/authentication/config/RestTemplateConfig.java +++ b/java/services/auth/src/main/java/com/uva/authentication/config/RestTemplateConfig.java @@ -7,19 +7,16 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; -import com.uva.authentication.interceptor.AuthHttpInterceptor; - @Configuration public class RestTemplateConfig { @Autowired - private AuthHttpInterceptor jwtInterceptor; + private RestTemplateInterceptor interceptor; @Bean RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); - restTemplate.setInterceptors(List.of(jwtInterceptor)); - + restTemplate.setInterceptors(List.of(interceptor)); return restTemplate; } diff --git a/java/services/auth/src/main/java/com/uva/authentication/config/RestTemplateInterceptor.java b/java/services/auth/src/main/java/com/uva/authentication/config/RestTemplateInterceptor.java new file mode 100644 index 0000000000000000000000000000000000000000..25b59946b262e6e8026f5f05ebd098e7dc9674be --- /dev/null +++ b/java/services/auth/src/main/java/com/uva/authentication/config/RestTemplateInterceptor.java @@ -0,0 +1,33 @@ +package com.uva.authentication.config; + +import org.springframework.http.HttpRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.stereotype.Component; + +import com.uva.authentication.utils.JwtUtil; + +import java.io.IOException; + +@Component +public class RestTemplateInterceptor implements ClientHttpRequestInterceptor { + + @Autowired + private JwtUtil jwtUtil; + + @Override + public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) + throws IOException { + + // Añadir el encabezado "Authorization" con el valor "Bearer <token>" + HttpHeaders headers = request.getHeaders(); + headers.add("Authorization", + "Bearer " + jwtUtil.getOwnInternalToken()); + + // Continuar con la solicitud + return execution.execute(request, body); + } +} diff --git a/java/services/auth/src/main/java/com/uva/authentication/controllers/AuthController.java b/java/services/auth/src/main/java/com/uva/authentication/controllers/AuthController.java index 83041cc56f5019ea047e41c56dae8224828f792a..7dfa56acc48bad46f862f259c15f90b8e172ad01 100644 --- a/java/services/auth/src/main/java/com/uva/authentication/controllers/AuthController.java +++ b/java/services/auth/src/main/java/com/uva/authentication/controllers/AuthController.java @@ -14,6 +14,7 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @RestController +@RequestMapping("auth") @CrossOrigin(origins = "*") public class AuthController { @@ -23,55 +24,52 @@ public class AuthController { @PostMapping("/login") public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) { try { - String token = authService.login(loginRequest); - return ResponseEntity.ok(new JwtAuthResponse(token)); + return authService.login(loginRequest); } catch (HttpClientErrorException e) { if (e.getStatusCode() == HttpStatus.FORBIDDEN) { - return new ResponseEntity<String>(e.getMessage(), HttpStatus.FORBIDDEN); + return new ResponseEntity<>(e.getMessage(), HttpStatus.FORBIDDEN); } } - return new ResponseEntity<String>("Algo no fue bien", HttpStatus.UNAUTHORIZED); + return new ResponseEntity<>("Algo no fue bien", HttpStatus.UNAUTHORIZED); } @PostMapping("/register") public ResponseEntity<?> register(@RequestBody RegisterRequest registerRequest) { try { - String token = authService.register(registerRequest); - return ResponseEntity.ok(new JwtAuthResponse(token)); + return authService.register(registerRequest); } catch (HttpClientErrorException e) { - if (e.getStatusCode() == HttpStatus.CONFLICT) { - return new ResponseEntity<String>(e.getMessage(), HttpStatus.CONFLICT); - } - e.printStackTrace(System.err); + if (e.getStatusCode() == HttpStatus.CONFLICT) + return new ResponseEntity<>(e.getMessage(), HttpStatus.CONFLICT); } - return new ResponseEntity<String>("Algo no fue bien", HttpStatus.UNAUTHORIZED); + return new ResponseEntity<>("Algo no fue bien", HttpStatus.UNAUTHORIZED); } - private boolean validStrings(String... args) { - for (String arg : args) { - if (arg == null || arg.isBlank()) - return false; - } - return true; + @PostMapping("/password") + public ResponseEntity<?> changePassword(@RequestBody Map<String, String> json, + @RequestHeader(value = "Authorization", required = false) String authorization) { + if (authorization == null || !authorization.startsWith("Bearer ")) + return new ResponseEntity<>(HttpStatus.FORBIDDEN); + + String token = authorization.substring(7); + + String actualPassword = json.get("password"); + String newPassword = json.get("newPassword"); + + return authService.changePassword(token, actualPassword, newPassword); } - @PostMapping("/password") - public ResponseEntity<?> postMethodName(@RequestBody Map<String, String> json) { - // TODO adaptar a comportamiento de admin - String email = json.get("email"); - String actualPassword = json.get("actual"); - String newPassword = json.get("new"); + @PostMapping("/delete/{id}") + public Object postMethodName(@PathVariable int id, @RequestBody Map<String, String> json, + @RequestHeader(value = "Authorization", required = false) String authorization) { + if (authorization == null || !authorization.startsWith("Bearer ")) + return new ResponseEntity<>(HttpStatus.FORBIDDEN); - if (!validStrings(email, actualPassword, newPassword)) - return new ResponseEntity<Void>(HttpStatus.BAD_REQUEST); + String token = authorization.substring(7); - try { - // TODO extraer información del token? - String token = authService.changePassword(email, actualPassword, newPassword); - return ResponseEntity.ok(new JwtAuthResponse(token)); - } catch (Exception e) { - return new ResponseEntity<Void>(HttpStatus.BAD_REQUEST); - } + String actualPassword = json.get("password"); + + return authService.deleteUser(token, id, actualPassword); } + } diff --git a/java/services/auth/src/main/java/com/uva/authentication/controllers/TokenController.java b/java/services/auth/src/main/java/com/uva/authentication/controllers/TokenController.java new file mode 100644 index 0000000000000000000000000000000000000000..3e33376dd18629f48bd6863bd8beb06465455901 --- /dev/null +++ b/java/services/auth/src/main/java/com/uva/authentication/controllers/TokenController.java @@ -0,0 +1,47 @@ +package com.uva.authentication.controllers; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.fasterxml.jackson.databind.JsonNode; +import com.uva.authentication.models.JwtAuth; +import com.uva.authentication.services.TokenService; + +@RestController +@RequestMapping("/token") +public class TokenController { + + @Autowired + private TokenService tokenService; + + @PostMapping("/validate") + public ResponseEntity<?> validateToken(@RequestBody JwtAuth tokenRequest) { + boolean isValid = tokenService.validateToken(tokenRequest.getToken()); + if (isValid) { + return ResponseEntity.ok("Token is valid"); + } else { + return new ResponseEntity<>("Token not valid or expired", HttpStatus.UNAUTHORIZED); + } + } + + @PostMapping("/info") + public ResponseEntity<?> getTokenInfo(@RequestBody JwtAuth tokenRequest) { + return tokenService.getTokenInf(tokenRequest.getToken()); + } + + @PostMapping("/service") + public ResponseEntity<?> identifyService(@RequestBody JsonNode request) { + JsonNode name = request.get("service"); + + if (name == null) + return new ResponseEntity<>("Missing required fields", HttpStatus.BAD_REQUEST); + + return tokenService.identifyService(name.asText()); + } + +} diff --git a/java/services/auth/src/main/java/com/uva/authentication/exceptions/GlobalExceptionHandler.java b/java/services/auth/src/main/java/com/uva/authentication/exceptions/GlobalExceptionHandler.java deleted file mode 100644 index 6d4b8d58dbb5560de50b5b2149cbe3f181687446..0000000000000000000000000000000000000000 --- a/java/services/auth/src/main/java/com/uva/authentication/exceptions/GlobalExceptionHandler.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.uva.authentication.exceptions; - -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; - -import java.time.LocalDateTime; -import java.util.HashMap; -import java.util.Map; - -@ControllerAdvice -public class GlobalExceptionHandler { - - @ExceptionHandler(HotelNotFoundException.class) - public ResponseEntity<Map<String, Object>> handleHotelNotFound(HotelNotFoundException ex) { - Map<String, Object> body = new HashMap<>(); - body.put("timestamp", LocalDateTime.now()); - body.put("message", ex.getMessage()); - - return new ResponseEntity<>(body, HttpStatus.NOT_FOUND); - } - - @ExceptionHandler(InvalidRequestException.class) - public ResponseEntity<Map<String, Object>> handleInvalidRequest(InvalidRequestException ex) { - Map<String, Object> body = new HashMap<>(); - body.put("timestamp", LocalDateTime.now()); - body.put("message", ex.getMessage()); - - return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST); - } - - @ExceptionHandler(InvalidDateRangeException.class) - public ResponseEntity<Map<String, Object>> handleInvalidDateRange(InvalidDateRangeException ex) { - Map<String, Object> body = new HashMap<>(); - body.put("timestamp", LocalDateTime.now()); - body.put("message", ex.getMessage()); - - return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST); - } - - // @ExceptionHandler(Exception.class) - // public ResponseEntity<Map<String, Object>> handleGeneralException(Exception - // ex) { - // Map<String, Object> body = new HashMap<>(); - // body.put("timestamp", LocalDateTime.now()); - // body.put("message", "An unexpected error occurred: " + ex.getMessage()); - - // return new ResponseEntity<>(body, HttpStatus.INTERNAL_SERVER_ERROR); - // } -} diff --git a/java/services/auth/src/main/java/com/uva/authentication/exceptions/HotelNotFoundException.java b/java/services/auth/src/main/java/com/uva/authentication/exceptions/HotelNotFoundException.java deleted file mode 100644 index c642139b421a5cf864218fa2d0063f955335c5b7..0000000000000000000000000000000000000000 --- a/java/services/auth/src/main/java/com/uva/authentication/exceptions/HotelNotFoundException.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.uva.authentication.exceptions; - -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - -@ResponseStatus(HttpStatus.NOT_FOUND) // Devuelve un 404 cuando se lanza la excepción -public class HotelNotFoundException extends RuntimeException { - public HotelNotFoundException(int id) { - super("Hotel not found with id: " + id); - } -} diff --git a/java/services/auth/src/main/java/com/uva/authentication/exceptions/InvalidDateRangeException.java b/java/services/auth/src/main/java/com/uva/authentication/exceptions/InvalidDateRangeException.java deleted file mode 100644 index c3dc917fb03495480007365b117e185521cf7bf2..0000000000000000000000000000000000000000 --- a/java/services/auth/src/main/java/com/uva/authentication/exceptions/InvalidDateRangeException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.uva.authentication.exceptions; - -public class InvalidDateRangeException extends RuntimeException { - public InvalidDateRangeException(String message) { - super(message); - } -} diff --git a/java/services/auth/src/main/java/com/uva/authentication/exceptions/InvalidRequestException.java b/java/services/auth/src/main/java/com/uva/authentication/exceptions/InvalidRequestException.java deleted file mode 100644 index 499a320e58ecd7576cbfff39101db14395f0edbe..0000000000000000000000000000000000000000 --- a/java/services/auth/src/main/java/com/uva/authentication/exceptions/InvalidRequestException.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.uva.authentication.exceptions; - -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - -@ResponseStatus(HttpStatus.BAD_REQUEST) -public class InvalidRequestException extends RuntimeException { - public InvalidRequestException(String message) { - super(message); - } -} diff --git a/java/services/auth/src/main/java/com/uva/authentication/interceptor/AuthHttpInterceptor.java b/java/services/auth/src/main/java/com/uva/authentication/interceptor/AuthHttpInterceptor.java deleted file mode 100644 index b64a0f69a673dcac07e93616c3affad5f6be3be5..0000000000000000000000000000000000000000 --- a/java/services/auth/src/main/java/com/uva/authentication/interceptor/AuthHttpInterceptor.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.uva.authentication.interceptor; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpRequest; -import org.springframework.http.client.ClientHttpRequestExecution; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.http.client.ClientHttpRequestInterceptor; -import org.springframework.stereotype.Component; - -import com.uva.authentication.models.remote.User; -import com.uva.authentication.models.remote.UserRol; -import com.uva.authentication.utils.JwtUtil; - -import java.io.IOException; - -@Component -public class AuthHttpInterceptor implements ClientHttpRequestInterceptor { - - @Autowired - private JwtUtil jwtUtil; - - private String token; - private final User USER = new User(-1, "auth", "auth@dev.com", null, UserRol.ADMIN); - - private String getAccessToken() { - if (token == null || token.isEmpty()) { - // TODO cambiar también si el token ha caducado - token = jwtUtil.generateToken(USER); - } - return token; - - } - - @Override - public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) - throws IOException { - // Generar o cargar el JWT token desde el bean JwtUtil - String jwtToken = getAccessToken(); - - // System.out.println("Using token " + jwtToken); - - // Agregar el token al encabezado Authorization - request.getHeaders().add("Authorization", "Bearer " + jwtToken); - - // Continuar con la ejecución de la solicitud - return execution.execute(request, body); - } -} diff --git a/java/services/auth/src/main/java/com/uva/authentication/models/AuthResponse.java b/java/services/auth/src/main/java/com/uva/authentication/models/JwtAuth.java similarity index 51% rename from java/services/auth/src/main/java/com/uva/authentication/models/AuthResponse.java rename to java/services/auth/src/main/java/com/uva/authentication/models/JwtAuth.java index 148730189d6c5a29df507bc3cd10eef7a9b274e6..1736439a1b93980155336707350f695a9f56c0ed 100644 --- a/java/services/auth/src/main/java/com/uva/authentication/models/AuthResponse.java +++ b/java/services/auth/src/main/java/com/uva/authentication/models/JwtAuth.java @@ -1,20 +1,16 @@ package com.uva.authentication.models; -import com.uva.authentication.models.remote.UserRol; - import lombok.AllArgsConstructor; +import lombok.Data; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -@AllArgsConstructor -@NoArgsConstructor -@Setter @Getter -public class AuthResponse { - private int id; - private String name; - private String email; - private String password; - private UserRol rol; +@Setter +@Data +@NoArgsConstructor +@AllArgsConstructor +public class JwtAuth { + private String token; } diff --git a/java/services/auth/src/main/java/com/uva/authentication/models/JwtAuthResponse.java b/java/services/auth/src/main/java/com/uva/authentication/models/JwtAuthResponse.java deleted file mode 100644 index a9566954c35ce71864881e320843e270a998b9da..0000000000000000000000000000000000000000 --- a/java/services/auth/src/main/java/com/uva/authentication/models/JwtAuthResponse.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.uva.authentication.models; - -public class JwtAuthResponse { - private String token; - - public JwtAuthResponse(String token) { - this.token = token; - } - - // Getter - public String getToken() { - return token; - } -} diff --git a/java/services/auth/src/main/java/com/uva/authentication/models/TokenData.java b/java/services/auth/src/main/java/com/uva/authentication/models/TokenData.java new file mode 100644 index 0000000000000000000000000000000000000000..d503f9193f58a44dee2a0b184ee654af4893999f --- /dev/null +++ b/java/services/auth/src/main/java/com/uva/authentication/models/TokenData.java @@ -0,0 +1,55 @@ +package com.uva.authentication.models; + +import java.lang.reflect.Field; + +import com.auth0.jwt.interfaces.Claim; +import com.auth0.jwt.interfaces.DecodedJWT; + +import lombok.Data; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Data +public class TokenData { + private Integer id; + private String name; + private String email; + private String rol; + private String service; + + private String subject; + private String audience; + private Long ttl; + + public TokenData(DecodedJWT decoded, long ttl) { + + subject = decoded.getSubject(); + audience = decoded.getAudience().get(0); + this.ttl = ttl; + + for (Field field : this.getClass().getDeclaredFields()) { + field.setAccessible(true); + + // Verificamos si el campo está en el mapa y asignamos el valor + Claim claim = decoded.getClaim(field.getName()); + if (!claim.isMissing()) { + try { + // Dependiendo del tipo de campo, asignamos el valor + if (field.getType() == Integer.class) { + field.set(this, Integer.parseInt(claim.asString())); + } else if (field.getType() == String.class) { + field.set(this, claim.asString()); + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } + } + + public boolean isAdmin() { + return rol != null && rol == "ADMIN"; + } +} \ No newline at end of file diff --git a/java/services/auth/src/main/java/com/uva/authentication/models/TokenRequest.java b/java/services/auth/src/main/java/com/uva/authentication/models/TokenRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..e63522e1a8660e71573fdb018aaae5f5596517ce --- /dev/null +++ b/java/services/auth/src/main/java/com/uva/authentication/models/TokenRequest.java @@ -0,0 +1,16 @@ +package com.uva.authentication.models; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@Data +@NoArgsConstructor +@AllArgsConstructor +public class TokenRequest { + private String token; +} diff --git a/java/services/auth/src/main/java/com/uva/authentication/models/remote/User.java b/java/services/auth/src/main/java/com/uva/authentication/models/remote/User.java index ae8de366340950b1bb5af476600408446a2d3f32..10049fc82e340fab5885cbd523f0962fc03292f5 100644 --- a/java/services/auth/src/main/java/com/uva/authentication/models/remote/User.java +++ b/java/services/auth/src/main/java/com/uva/authentication/models/remote/User.java @@ -16,6 +16,7 @@ public class User extends RegisterRequest { public User(int id, String email, String password, String name, UserRol rol) { super(); + this.id = id; setEmail(email); setName(name); setPassword(password); diff --git a/java/services/auth/src/main/java/com/uva/authentication/models/remote/UserRol.java b/java/services/auth/src/main/java/com/uva/authentication/models/remote/UserRol.java index 2cb39bb4f6174faf20c7174269c835f7c6b80cc2..fe4d90dd1dd595f4c09ec699b452910352b406d5 100644 --- a/java/services/auth/src/main/java/com/uva/authentication/models/remote/UserRol.java +++ b/java/services/auth/src/main/java/com/uva/authentication/models/remote/UserRol.java @@ -1,5 +1,5 @@ package com.uva.authentication.models.remote; public enum UserRol { - ADMIN, AUTH, HOTEL_ADMIN, CLIENT + ADMIN, HOTEL_ADMIN, CLIENT } diff --git a/java/services/auth/src/main/java/com/uva/authentication/services/AuthService.java b/java/services/auth/src/main/java/com/uva/authentication/services/AuthService.java index 67cf0b7308c3979274697da16bc0f0f103227aa9..03f2b6c132f4bf9531f891d34471d6484197a9dd 100644 --- a/java/services/auth/src/main/java/com/uva/authentication/services/AuthService.java +++ b/java/services/auth/src/main/java/com/uva/authentication/services/AuthService.java @@ -3,12 +3,16 @@ package com.uva.authentication.services; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.servlet.function.EntityResponse; import com.uva.authentication.api.UserAPI; +import com.uva.authentication.models.JwtAuth; import com.uva.authentication.models.LoginRequest; import com.uva.authentication.models.RegisterRequest; +import com.uva.authentication.models.TokenData; import com.uva.authentication.models.remote.User; import com.uva.authentication.utils.JwtUtil; import com.uva.authentication.utils.SecurityUtils; @@ -35,17 +39,17 @@ public class AuthService { * @return token for identify the user * @throws HttpClientErrorException(FORBIDDEN) if the credentials are invalid */ - public String login(LoginRequest loginRequest) { + public ResponseEntity<?> login(LoginRequest loginRequest) { User user = userAPI.getUserByEmail(loginRequest.getEmail()); - if (!authenticateUser(loginRequest, user)) { + if (!authenticateUser(loginRequest, user)) throw new HttpClientErrorException(HttpStatus.FORBIDDEN, "Invalid credentials"); - } - return jwtUtil.generateToken(user); + String token = jwtUtil.generateToken(user); + return ResponseEntity.ok(new JwtAuth(token)); } - public String register(RegisterRequest registerRequest) { + public ResponseEntity<?> register(RegisterRequest registerRequest) { String plainTextPassword = registerRequest.getPassword(); // Ciframos la contraseña String hashPass = SecurityUtils.encrypt(plainTextPassword); @@ -56,21 +60,66 @@ public class AuthService { BeanUtils.copyProperties(user, logReq); // Recuperamos la contraseña y lo loggeamos logReq.setPassword(plainTextPassword); - System.err.println(logReq); return login(logReq); } - public String changePassword(String email, String actualPass, String newPass) { + private boolean validStrings(String... args) { + for (String arg : args) { + if (arg == null || arg.isBlank()) + return false; + } + return true; + } + + private User getUser(String email, String password) { User user = userAPI.getUserByEmail(email); - // Validamos la anterior contraseña - if (SecurityUtils.checkPassword(actualPass, user.getPassword())) { + boolean correctPassword = SecurityUtils.checkPassword(password, user.getPassword()); + return correctPassword ? user : null; + } + + public ResponseEntity<?> changePassword(String token, String actualPass, String newPass) { + TokenData decoded = jwtUtil.decodeToken(token); + if (decoded == null) + return new ResponseEntity<>(HttpStatus.FORBIDDEN); + + String email = decoded.getEmail(); + User user = getUser(email, actualPass); + + boolean changePasswordAllowed = decoded.isAdmin() || user != null; + + if (user != null && !validStrings(actualPass, newPass)) + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + + if (changePasswordAllowed) { // Actualizamos la nueva String hashPass = SecurityUtils.encrypt(newPass); userAPI.changePassword(user, hashPass); // Hacemos un login con los nuevos datos return login(new LoginRequest(email, newPass)); } else { - throw new HttpClientErrorException(HttpStatus.FORBIDDEN, "Invalid credentials"); + return new ResponseEntity<>("Invalid credentials", HttpStatus.FORBIDDEN); + } + } + + public ResponseEntity<?> deleteUser(String token, int id, String password) { + TokenData decoded = jwtUtil.decodeToken(token); + if (decoded == null) + return new ResponseEntity<>(HttpStatus.FORBIDDEN); + + String email = decoded.getEmail(); + User user = getUser(email, password); + + boolean changePasswordAllowed = decoded.isAdmin() + || (user != null && user.getId() == id); + + if (user != null && !validStrings(password)) + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + + if (changePasswordAllowed) { + userAPI.deleteUser(user); + return new ResponseEntity<>(HttpStatus.ACCEPTED); + } else { + return new ResponseEntity<>("Invalid credentials", HttpStatus.FORBIDDEN); } } } diff --git a/java/services/auth/src/main/java/com/uva/authentication/services/TokenService.java b/java/services/auth/src/main/java/com/uva/authentication/services/TokenService.java new file mode 100644 index 0000000000000000000000000000000000000000..a2a00805f914c4aa95e29e244ece3826db25796c --- /dev/null +++ b/java/services/auth/src/main/java/com/uva/authentication/services/TokenService.java @@ -0,0 +1,35 @@ +package com.uva.authentication.services; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; + +import com.uva.authentication.models.JwtAuth; +import com.uva.authentication.models.TokenData; +import com.uva.authentication.utils.JwtUtil; + +@Service +public class TokenService { + + @Autowired + private JwtUtil jwtUtil; + + public boolean validateToken(String token) { + return jwtUtil.validate(token) != null; + } + + public ResponseEntity<?> identifyService(String name) { + if (name == null) + return new ResponseEntity<>("Token has expire or is malformed", HttpStatus.FORBIDDEN); + String token = jwtUtil.generateInternalToken(name); + return ResponseEntity.ok(new JwtAuth(token)); + } + + public ResponseEntity<?> getTokenInf(String token) { + TokenData decoded = jwtUtil.decodeToken(token); + if (decoded == null) + return new ResponseEntity<>("Token has expire or is malformed", HttpStatus.FORBIDDEN); + return ResponseEntity.ok(decoded); + } +} diff --git a/java/services/auth/src/main/java/com/uva/authentication/utils/JwtUtil.java b/java/services/auth/src/main/java/com/uva/authentication/utils/JwtUtil.java index 7b5bbf50066d94d8de2fced81890d5ce8ea93569..d633043cce2da7d19a75b7bf32db38bd52905e5f 100644 --- a/java/services/auth/src/main/java/com/uva/authentication/utils/JwtUtil.java +++ b/java/services/auth/src/main/java/com/uva/authentication/utils/JwtUtil.java @@ -1,44 +1,117 @@ package com.uva.authentication.utils; import java.util.Date; - import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; +import org.springframework.web.client.HttpClientErrorException; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; +import com.uva.authentication.models.TokenData; import com.uva.authentication.models.remote.User; +import com.auth0.jwt.interfaces.DecodedJWT; + +import java.time.Instant; + @Component public class JwtUtil { + @Value("${security.jwt.kid}") + private String kid; + @Value("${security.jwt.secret-key}") private String secretKey; - @Value("${security.jwt.kid}") - private String kid; + @Value("${security.jwt.internal.expiration}") + private long intJwtExpiration; + + @Value("${security.jwt.external.expiration}") + private long extJwtExpiration; + + private String token; + + private static final String SERVICE = "AUTH_SERVICES"; - @Value("${security.jwt.expiration-time}") - private long jwtExpiration; + public String getOwnInternalToken() { + + // Si no hay token, no es valido o quedan 10 seg para caducar se genera otro + if (token == null || validate(token) == null || + decodeToken(token).getTtl() <= 10) { + token = generateInternalToken(SERVICE); + } + + return token; - public long getExpirationTime() { - return jwtExpiration; + } + + public String generateInternalToken(String service) { + String email = service.toLowerCase() + "@internal.com"; + Algorithm algorithm = Algorithm.HMAC256(secretKey); + + return JWT + .create() + + .withKeyId(kid) + .withIssuedAt(new Date()) + .withExpiresAt(new Date(System.currentTimeMillis() + intJwtExpiration * 1000)) + + .withSubject(service) + .withAudience("INTERNAL") + + // DATA + .withClaim("service", service) + .withClaim("email", email) + + .sign(algorithm); } public String generateToken(User user) { Algorithm algorithm = Algorithm.HMAC256(secretKey); + return JWT .create() + .withKeyId(kid) + .withIssuedAt(new Date()) + .withExpiresAt(new Date(System.currentTimeMillis() + extJwtExpiration * 1000)) + + .withSubject(SERVICE) + .withAudience("EXTERNAL") + + // DATA .withClaim("id", user.getId()) .withClaim("name", user.getName()) .withClaim("email", user.getEmail()) .withClaim("rol", user.getRol().toString()) - .withIssuedAt(new Date()) - .withExpiresAt(new Date(System.currentTimeMillis() + jwtExpiration * 1000)) + .sign(algorithm); } - // TODO estaría guapo recuperar métodos de validación para el token de petición - // para este servicio + public DecodedJWT validate(String token) { + try { + return JWT.require(Algorithm.HMAC256(secretKey)).build().verify(token); + } catch (Exception e) { + return null; + } + } + + public TokenData decodeToken(String token) { + DecodedJWT decoded = validate(token); + if (decoded == null) + return null; + return new TokenData(decoded, calculateTTL(decoded)); + } + + private long calculateTTL(DecodedJWT decodedJWT) { + if (decodedJWT == null) + throw new HttpClientErrorException(HttpStatus.FORBIDDEN); + + long exp = decodedJWT.getExpiresAt().toInstant().getEpochSecond(); + long now = Instant.now().getEpochSecond(); + + return exp - now; + } + } diff --git a/java/services/auth/src/main/resources/application.properties b/java/services/auth/src/main/resources/application.properties index c9d72501a6b64ab7f91a39562614adfa601ef98e..ad4a9a515522af4efcda2a87f59dc1902879136f 100644 --- a/java/services/auth/src/main/resources/application.properties +++ b/java/services/auth/src/main/resources/application.properties @@ -3,7 +3,8 @@ server.port=8101 security.jwt.secret-key=MiClaveDeSeguridadMuyLargaParaQueNoFalleSpringBoot # 1h in millisecond -security.jwt.expiration-time=3600000 +security.jwt.external.expiration=3600 +security.jwt.internal.expiration=20 security.jwt.kid=cYz3kNRLAirxVhHXQ5rh5xKrOwHwZVui external.services.users.url=http://localhost:8201/users \ No newline at end of file diff --git a/java/services/bookings/src/main/java/com/uva/apis/bookings/api/ClientApi.java b/java/services/bookings/src/main/java/com/uva/apis/bookings/api/ClientApi.java index 470ddcff6aa8f7d959d4f20722221eb3e14397fd..c0333cb24ec5d41fceb703fa6c0d9d3e35662b58 100644 --- a/java/services/bookings/src/main/java/com/uva/apis/bookings/api/ClientApi.java +++ b/java/services/bookings/src/main/java/com/uva/apis/bookings/api/ClientApi.java @@ -17,7 +17,7 @@ public class ClientApi { private String CLIENTS_API_URL; public boolean existsById(int id) { - String url = CLIENTS_API_URL + "/{id}"; + String url = CLIENTS_API_URL + "/" + id; ResponseEntity<Void> response = restTemplate.getForEntity(url, Void.class, id); return response.getStatusCode() == HttpStatus.OK; } diff --git a/java/services/bookings/src/main/java/com/uva/apis/bookings/controllers/BookingController.java b/java/services/bookings/src/main/java/com/uva/apis/bookings/controllers/BookingController.java index 44ee237a73f601bfd4104774866cf6388555a41c..597c657f9d4fed32aa5f3b82a7bf0c44fe5b0203 100644 --- a/java/services/bookings/src/main/java/com/uva/apis/bookings/controllers/BookingController.java +++ b/java/services/bookings/src/main/java/com/uva/apis/bookings/controllers/BookingController.java @@ -2,6 +2,7 @@ package com.uva.apis.bookings.controllers; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -39,12 +40,27 @@ public class BookingController { return bookingService.getBookingById(id); } - @DeleteMapping /// ?hotelId={hotelId} - public ResponseEntity<Void> deleteBooking(@RequestParam int hotelId) { + @DeleteMapping /// ?hotelId={hotelId}|managerId={managerId} + public ResponseEntity<?> deleteBooking( + @RequestParam(required = false) Integer hotelId, + @RequestParam(required = false) Integer managerId) { try { - bookingService.deleteBookingsByHotelId(hotelId); - return new ResponseEntity<>(HttpStatus.ACCEPTED); + List<Booking> bookings; + String message; + if (managerId != null) { + bookings = bookingService.deleteAllByManagerId(managerId); + message = "No bookings for this manager"; + } else if (hotelId != null) { + bookings = bookingService.deleteBookingsByHotelId(hotelId); + message = "No bookings for this hotel"; + } else { + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + return !bookings.isEmpty() + ? new ResponseEntity<>(bookings, HttpStatus.OK) + : new ResponseEntity<>(message, HttpStatus.BAD_REQUEST); } catch (RuntimeException e) { + e.printStackTrace(System.err); return new ResponseEntity<>(HttpStatus.NOT_FOUND); } } diff --git a/java/services/bookings/src/main/java/com/uva/apis/bookings/models/Booking.java b/java/services/bookings/src/main/java/com/uva/apis/bookings/models/Booking.java index 8af8688534991b81c0b2b79aa01344bd5b2b86fa..fee28fd4b673f11db95d7b6296e5faee21cadd8d 100644 --- a/java/services/bookings/src/main/java/com/uva/apis/bookings/models/Booking.java +++ b/java/services/bookings/src/main/java/com/uva/apis/bookings/models/Booking.java @@ -31,6 +31,9 @@ public class Booking { @Column(name = "user_id", nullable = false) private int userId; + @Column(name = "manager_id", nullable = false) + private int managerId; + @Column(name = "hotel_id", nullable = false) private int hotelId; diff --git a/java/services/bookings/src/main/java/com/uva/apis/bookings/repositories/BookingRepository.java b/java/services/bookings/src/main/java/com/uva/apis/bookings/repositories/BookingRepository.java index 646fa074709a0c14dd109c579d624f5ce20245e2..678b67eb4ad17734c90d10d3d74430991acec01d 100644 --- a/java/services/bookings/src/main/java/com/uva/apis/bookings/repositories/BookingRepository.java +++ b/java/services/bookings/src/main/java/com/uva/apis/bookings/repositories/BookingRepository.java @@ -10,6 +10,8 @@ import org.springframework.data.repository.query.Param; import com.uva.apis.bookings.models.Booking; +import jakarta.transaction.Transactional; + public interface BookingRepository extends JpaRepository<Booking, Integer> { List<Booking> findByUserId(int userId); @@ -26,8 +28,14 @@ public interface BookingRepository extends JpaRepository<Booking, Integer> { void deleteById(int id); + @Transactional void deleteAllByHotelId(int hotelId); List<Booking> findByHotelId(Integer roomId); + @Transactional + void deleteAllByManagerId(int managerId); + + List<Booking> findByManagerId(int managerId); + } diff --git a/java/services/bookings/src/main/java/com/uva/apis/bookings/services/BookingService.java b/java/services/bookings/src/main/java/com/uva/apis/bookings/services/BookingService.java index 8d9425c87d1ead527a7b7c22040f4cbc7aa9577a..161a43540baa4f9ddc977ce48c13b1f03bf05c3e 100644 --- a/java/services/bookings/src/main/java/com/uva/apis/bookings/services/BookingService.java +++ b/java/services/bookings/src/main/java/com/uva/apis/bookings/services/BookingService.java @@ -11,6 +11,7 @@ import com.uva.apis.bookings.models.Booking; import com.uva.apis.bookings.repositories.BookingRepository; import java.time.LocalDate; +import java.util.ArrayList; import java.util.List; @Service @@ -117,8 +118,22 @@ public class BookingService { bookingRepository.deleteById(id); } - public void deleteBookingsByHotelId(int hotelId) { + public List<Booking> deleteBookingsByHotelId(int hotelId) { // Extraer reservas realizadas al hotel + List<Booking> bookings = bookingRepository.findByHotelId(hotelId); + if (bookings.isEmpty()) { + return new ArrayList<>(); + } bookingRepository.deleteAllByHotelId(hotelId); + return bookings; + } + + public List<Booking> deleteAllByManagerId(int managerId) { + List<Booking> bookings = bookingRepository.findByManagerId(managerId); + if (bookings.isEmpty()) { + return new ArrayList<>(); + } + bookingRepository.deleteAllByManagerId(managerId); + return bookings; } } diff --git a/java/services/bookings/src/main/resources/application.properties b/java/services/bookings/src/main/resources/application.properties index 9ea8ffad1265e6413ef92d11b6787e8d891ef811..2731e248d85507dc8440a1341f0de987f8127527 100644 --- a/java/services/bookings/src/main/resources/application.properties +++ b/java/services/bookings/src/main/resources/application.properties @@ -14,5 +14,5 @@ security.jwt.secret-key=MiClaveDeSeguridadMuyLargaParaQueNoFalleSpringBoot security.jwt.expiration-time=3600000 security.jwt.kid=cYz3kNRLAirxVhHXQ5rh5xKrOwHwZVui -external.services.clients.url=localhost:8201/users/clients -external.services.hotels.url=localhost:8301/hotels \ No newline at end of file +external.services.clients.url=http://localhost:8201/users/clients +external.services.hotels.url=http://localhost:8301/hotels \ No newline at end of file diff --git a/java/services/hotels/src/main/java/com/uva/monolith/api/BookingAPI.java b/java/services/hotels/src/main/java/com/uva/monolith/api/BookingAPI.java index 0630e49ca97e40d6ad2eafc4c7e802b2a4f02e3a..369066bd537e131c333b04ae0415f7527cff6885 100644 --- a/java/services/hotels/src/main/java/com/uva/monolith/api/BookingAPI.java +++ b/java/services/hotels/src/main/java/com/uva/monolith/api/BookingAPI.java @@ -45,4 +45,9 @@ public class BookingAPI { return notAvailableRooms; } + + public void deleteAllByManagerId(Integer managerId) { + String url = BOOKING_API_URL + "?managerId={managerId}"; + restTemplate.delete(url, managerId); + } } \ No newline at end of file diff --git a/java/services/hotels/src/main/java/com/uva/monolith/api/HotelManagerAPI.java b/java/services/hotels/src/main/java/com/uva/monolith/api/ManagerAPI.java similarity index 73% rename from java/services/hotels/src/main/java/com/uva/monolith/api/HotelManagerAPI.java rename to java/services/hotels/src/main/java/com/uva/monolith/api/ManagerAPI.java index f4b0c18bb3fdfbadd919807368121500b92153ce..fda96949c48eb0cd159fd181efe6c845eff08767 100644 --- a/java/services/hotels/src/main/java/com/uva/monolith/api/HotelManagerAPI.java +++ b/java/services/hotels/src/main/java/com/uva/monolith/api/ManagerAPI.java @@ -1,7 +1,5 @@ package com.uva.monolith.api; -import java.util.Map; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; @@ -9,8 +7,10 @@ import org.springframework.stereotype.Component; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; +import com.fasterxml.jackson.databind.JsonNode; + @Component -public class HotelManagerAPI { +public class ManagerAPI { @Autowired private RestTemplate restTemplate; @@ -18,11 +18,12 @@ public class HotelManagerAPI { @Value("${external.services.managers.url}") private String MANAGERS_API_URL; - public Boolean existsHotelManagerById(int id) { + public Boolean existsManagerById(int id) { try { String url = MANAGERS_API_URL + "/{id}"; - return restTemplate.getForObject(url, Map.class, id).containsKey("id"); + return restTemplate.getForEntity(url, JsonNode.class, id).getStatusCode() == HttpStatus.OK; } catch (HttpClientErrorException e) { + e.printStackTrace(System.err); if (e.getStatusCode() != HttpStatus.NOT_FOUND) throw e; return false; diff --git a/java/services/hotels/src/main/java/com/uva/monolith/services/hotels/controllers/HotelController.java b/java/services/hotels/src/main/java/com/uva/monolith/services/hotels/controllers/HotelController.java index 39c1b314c15ad3f49a9fad76dde88f45cc9b7458..de293524b54a865ab0d58a9877936cd8aa4145d0 100644 --- a/java/services/hotels/src/main/java/com/uva/monolith/services/hotels/controllers/HotelController.java +++ b/java/services/hotels/src/main/java/com/uva/monolith/services/hotels/controllers/HotelController.java @@ -12,7 +12,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import com.uva.monolith.api.BookingAPI; -import com.uva.monolith.api.HotelManagerAPI; +import com.uva.monolith.api.ManagerAPI; import com.uva.monolith.exceptions.HotelNotFoundException; import com.uva.monolith.exceptions.InvalidDateRangeException; import com.uva.monolith.exceptions.InvalidRequestException; @@ -32,7 +32,7 @@ public class HotelController { @Autowired private BookingAPI bookingAPI; @Autowired - private HotelManagerAPI hotelManagerAPI; + private ManagerAPI managerAPI; // Obtener todos los hoteles @GetMapping @@ -62,13 +62,19 @@ public class HotelController { // Añadir un hotel con sus habitaciones @PostMapping public ResponseEntity<?> addHotel(@RequestBody Hotel hotel) { - boolean exist = hotelManagerAPI.existsHotelManagerById(hotel.getManagerId()); - if (!exist) { - return new ResponseEntity<>( - "No existe el manager con id " + String.valueOf(hotel.getManagerId()), HttpStatus.BAD_REQUEST); + try { + + boolean exist = managerAPI.existsManagerById(hotel.getManagerId()); + String message = "No existe el manager con id " + String.valueOf(hotel.getManagerId()); + if (!exist) { + return new ResponseEntity<>(message, HttpStatus.BAD_REQUEST); + } + Hotel savedHotel = hotelRepository.save(hotel); + return new ResponseEntity<>(savedHotel, HttpStatus.CREATED); + } catch (Exception e) { + e.printStackTrace(System.err); + throw e; } - Hotel savedHotel = hotelRepository.save(hotel); - return new ResponseEntity<>(savedHotel, HttpStatus.CREATED); } // Obtener un hotel por su ID @@ -78,6 +84,19 @@ public class HotelController { .orElseThrow(() -> new HotelNotFoundException(id)); } + // Borrar hoteles administrados por un manager concreto + @DeleteMapping + public ResponseEntity<?> deleteHotelsByManagerId( + @RequestParam(required = true) Integer managerId) { + List<Hotel> hotels = hotelRepository.findAllByManagerId(managerId); + if (hotels.isEmpty()) { + return new ResponseEntity<>("No hay hoteles para el manager con id " + managerId, HttpStatus.BAD_REQUEST); + } + bookingAPI.deleteAllByManagerId(managerId); + hotelRepository.deleteAll(hotels); + return new ResponseEntity<>(hotels, HttpStatus.OK); + } + // Borrar un hotel junto con sus habitaciones (borrado en cascada) @DeleteMapping("/{id}") @Transactional diff --git a/java/services/users/pom.xml b/java/services/users/pom.xml index 0eca2504ee970c5403bc3a785fa7003c78f82c8e..9ed5a3ede8fd48e11b7b74bba7499f2e5f6d13a5 100644 --- a/java/services/users/pom.xml +++ b/java/services/users/pom.xml @@ -52,11 +52,6 @@ <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> - <dependency> - <groupId>com.auth0</groupId> - <artifactId>java-jwt</artifactId> - <version>4.4.0</version> - </dependency> <dependency> <groupId>jakarta.servlet</groupId> <artifactId>jakarta.servlet-api</artifactId> diff --git a/java/services/users/src/main/java/com/uva/api/apis/BookingAPI.java b/java/services/users/src/main/java/com/uva/api/apis/BookingAPI.java index 6dad693f2215a1b0c0614e2d99a7ecb3ca88227a..aafffb5094fa3cf1fad685bdb02b889ffbc107e9 100644 --- a/java/services/users/src/main/java/com/uva/api/apis/BookingAPI.java +++ b/java/services/users/src/main/java/com/uva/api/apis/BookingAPI.java @@ -16,17 +16,19 @@ public class BookingAPI { @Autowired private RestTemplate restTemplate; - @Value("${external.services.bookings.url}") + @Value("${services.external.bookings.url}") private String BOOKING_API_URL; - public List<Booking> getAllBookingsByUserId(int id) { + public void deleteAllByUserId(int id) { String url = BOOKING_API_URL + "?userId={id}"; + restTemplate.delete(url, id); + } - Booking[] bookingsArray = restTemplate - .getForObject(url, Booking[].class, id); - - return Arrays.asList(bookingsArray); + public List<Booking> getAllByUserId(int id) { + String url = BOOKING_API_URL + "?userId={id}"; + Booking[] bookings = restTemplate.getForObject(url, Booking[].class, id); + return Arrays.asList(bookings); } } diff --git a/java/services/users/src/main/java/com/uva/api/apis/HotelApi.java b/java/services/users/src/main/java/com/uva/api/apis/HotelApi.java index f6e3899272a2d5e16a3d474c904276ef80ab787d..c6541c58c931cfba57ecc717e1c43bea8fd64a40 100644 --- a/java/services/users/src/main/java/com/uva/api/apis/HotelApi.java +++ b/java/services/users/src/main/java/com/uva/api/apis/HotelApi.java @@ -1,5 +1,21 @@ package com.uva.api.apis; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +@Component public class HotelApi { + @Autowired + private RestTemplate restTemplate; + + @Value("${services.external.hotels.url}") + private String HOTELS_API; + + public void deleteAllByManagerId(Integer id) { + String url = HOTELS_API + "?managerId={id}"; + restTemplate.delete(url, id); + } } diff --git a/java/services/users/src/main/java/com/uva/api/apis/TokenAPI.java b/java/services/users/src/main/java/com/uva/api/apis/TokenAPI.java new file mode 100644 index 0000000000000000000000000000000000000000..01941ad762dfdfc3128583d0de2d4680d41b480f --- /dev/null +++ b/java/services/users/src/main/java/com/uva/api/apis/TokenAPI.java @@ -0,0 +1,44 @@ +package com.uva.api.apis; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +import com.fasterxml.jackson.databind.JsonNode; +import com.uva.api.models.remote.JwtData; + +@Component +public class TokenAPI { + + @Autowired + private RestTemplate restTemplate; + + @Value("${spring.application.name}") + private String service; + + @Value("${services.internal.token.url}") + private String TOKEN_API_URL; + + public JwtData getServiceToken() { + String url = TOKEN_API_URL + "/service"; + Map<String, String> body = new HashMap<>(); + body.put("service", service); + String token = restTemplate.postForObject(url, body, JsonNode.class) + .get("token").asText(); + return decodeToken(token); + } + + public JwtData decodeToken(String token) { + String url = TOKEN_API_URL + "/info"; + Map<String, String> body = new HashMap<>(); + body.put("token", token); + JwtData response = restTemplate.postForObject(url, body, JwtData.class); + response.setToken(token); + return response; + } + +} diff --git a/java/services/users/src/main/java/com/uva/api/config/RestTemplateInterceptor.java b/java/services/users/src/main/java/com/uva/api/config/RestTemplateInterceptor.java new file mode 100644 index 0000000000000000000000000000000000000000..7a6e85f4c3fcfa7d8ce890130417e5807a9124ce --- /dev/null +++ b/java/services/users/src/main/java/com/uva/api/config/RestTemplateInterceptor.java @@ -0,0 +1,31 @@ +package com.uva.api.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.stereotype.Component; + +import com.uva.api.services.TokenService; + +import java.io.IOException; + +@Component +public class RestTemplateInterceptor implements ClientHttpRequestInterceptor { + + @Autowired + private TokenService service; + + @Override + public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) + throws IOException { + + String jwtToken = service.getServiceToken(); + System.out.println("Using token " + jwtToken); + + request.getHeaders().add("Authorization", "Bearer " + jwtToken); + + return execution.execute(request, body); + } +} diff --git a/java/services/users/src/main/java/com/uva/api/config/SecurityConfig.java b/java/services/users/src/main/java/com/uva/api/config/SecurityConfig.java index 5aa0f6e0f22a1b524146320a77777881415510c9..d9720e394866dadef3daa91b42a659cf01d25885 100644 --- a/java/services/users/src/main/java/com/uva/api/config/SecurityConfig.java +++ b/java/services/users/src/main/java/com/uva/api/config/SecurityConfig.java @@ -23,32 +23,32 @@ public class SecurityConfig { @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - http.csrf(csrf -> csrf.disable()); - // .authorizeHttpRequests(authorize -> authorize - // // Permitir OPTIONS sin autenticación - // .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() - // // Acceso restringido a usuarios y administradores - // .requestMatchers("users", "users/**").hasAnyRole( - // UserRol.CLIENT.toString(), - // UserRol.HOTEL_ADMIN.toString(), - // UserRol.ADMIN.toString()) - // // Acceso restringido a gestores de hoteles y administradores - // .requestMatchers(HttpMethod.GET, "hotels", "hotels/*").hasAnyRole( - // UserRol.CLIENT.toString(), - // UserRol.HOTEL_ADMIN.toString(), - // UserRol.ADMIN.toString()) - - // .requestMatchers("hotels", "hotels/**") - // .hasAnyRole(UserRol.ADMIN.toString(), UserRol.HOTEL_ADMIN.toString()) - // // Acceso restringido a cualquier usuario del sistema - // .requestMatchers("bookings", "bookings/**") - // .hasAnyRole(UserRol.ADMIN.toString(), UserRol.HOTEL_ADMIN.toString(), - // UserRol.CLIENT.toString()) - // // Rechazar el resto - // .anyRequest().denyAll()) - // // Registra el filtro antes del filtro estándar de autenticación - // .addFilterBefore(jwtAuthenticationFilter, - // UsernamePasswordAuthenticationFilter.class); + http.csrf(csrf -> csrf.disable()) + // .authorizeHttpRequests(authorize -> authorize + // // Permitir OPTIONS sin autenticación + // .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() + // // Acceso restringido a usuarios y administradores + // .requestMatchers("users", "users/**").hasAnyRole( + // UserRol.CLIENT.toString(), + // UserRol.HOTEL_ADMIN.toString(), + // UserRol.ADMIN.toString()) + // // Acceso restringido a gestores de hoteles y administradores + // .requestMatchers(HttpMethod.GET, "hotels", "hotels/*").hasAnyRole( + // UserRol.CLIENT.toString(), + // UserRol.HOTEL_ADMIN.toString(), + // UserRol.ADMIN.toString()) + + // .requestMatchers("hotels", "hotels/**") + // .hasAnyRole(UserRol.ADMIN.toString(), UserRol.HOTEL_ADMIN.toString()) + // // Acceso restringido a cualquier usuario del sistema + // .requestMatchers("bookings", "bookings/**") + // .hasAnyRole(UserRol.ADMIN.toString(), UserRol.HOTEL_ADMIN.toString(), + // UserRol.CLIENT.toString()) + // // Rechazar el resto + // .anyRequest().denyAll()) + // // Registra el filtro antes del filtro estándar de autenticación + .addFilterBefore(jwtAuthenticationFilter, + UsernamePasswordAuthenticationFilter.class); return http.build(); } diff --git a/java/services/users/src/main/java/com/uva/api/controllers/ClientController.java b/java/services/users/src/main/java/com/uva/api/controllers/ClientController.java new file mode 100644 index 0000000000000000000000000000000000000000..b0e50a3661aaa6719805f8964aca782d8a39f0d7 --- /dev/null +++ b/java/services/users/src/main/java/com/uva/api/controllers/ClientController.java @@ -0,0 +1,69 @@ +package com.uva.api.controllers; + +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.HttpClientErrorException; + +import com.uva.api.models.ClientStatus; +import com.uva.api.services.ClientService; + +@RestController +@RequestMapping("users/clients") +@CrossOrigin(origins = "*") +public class ClientController { + + @Autowired + private ClientService clientService; + + // Clients + @GetMapping + public ResponseEntity<?> getAllClients() { + return clientService.findAll(); + } + + @GetMapping("/{id}") + public ResponseEntity<?> getClientById(@PathVariable int id) { + return clientService.findById(id); + } + + @PatchMapping("/{id}") + public ResponseEntity<?> updateClientState(@PathVariable int id, @RequestBody Map<String, String> json) { + + String strStatus = json.get("status"); + if (strStatus == null) { + return new ResponseEntity<String>("Missing required fields", HttpStatus.BAD_REQUEST); + } + try { + ClientStatus clientStatus = ClientStatus.valueOf(strStatus); + return ResponseEntity.ok(clientService.updateClientStatus(id, clientStatus)); + } catch (IllegalArgumentException e) { + return new ResponseEntity<String>("Unknown Client state", HttpStatus.BAD_REQUEST); + } catch (HttpClientErrorException e) { + if (e.getStatusCode() == HttpStatus.NOT_FOUND) + return new ResponseEntity<String>(HttpStatus.NOT_FOUND); + throw e; + } + } + + @DeleteMapping("/{id}") + public ResponseEntity<?> deleteClient(@PathVariable Integer id) { + try { + return ResponseEntity.ok(clientService.deleteById(id)); + } catch (HttpClientErrorException e) { + if (e.getStatusCode() == HttpStatus.NOT_FOUND) + return new ResponseEntity<String>(HttpStatus.NOT_FOUND); + throw e; + } + } +} diff --git a/java/services/users/src/main/java/com/uva/api/controllers/ManagerController.java b/java/services/users/src/main/java/com/uva/api/controllers/ManagerController.java new file mode 100644 index 0000000000000000000000000000000000000000..d5af94d7079356035a34268b2f0e7d7067969fd7 --- /dev/null +++ b/java/services/users/src/main/java/com/uva/api/controllers/ManagerController.java @@ -0,0 +1,48 @@ +package com.uva.api.controllers; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.HttpClientErrorException; + +import com.uva.api.models.Manager; +import com.uva.api.services.ManagerService; + +@RestController +@RequestMapping("users/managers") +@CrossOrigin(origins = "*") +public class ManagerController { + + @Autowired + private ManagerService managerService; + + @GetMapping + public ResponseEntity<List<Manager>> getAllHotelManagers() { + List<Manager> users = managerService.findAll(); + return ResponseEntity.ok(users); + } + + @GetMapping("/{id}") + public ResponseEntity<Manager> getHotelManagerById(@PathVariable Integer id) { + return ResponseEntity.ok(managerService.findById(id)); + } + + @DeleteMapping("/{id}") + public ResponseEntity<?> deleteHotelManager(@PathVariable Integer id) { + try { + return ResponseEntity.ok(managerService.deleteById(id)); + } catch (HttpClientErrorException e) { + if (e.getStatusCode() == HttpStatus.NOT_FOUND) + return new ResponseEntity<String>(HttpStatus.NOT_FOUND); + throw e; + } + } + +} diff --git a/java/services/users/src/main/java/com/uva/api/controllers/UserController.java b/java/services/users/src/main/java/com/uva/api/controllers/UserController.java index 121670cb98bb3e26a6e9f49b3d5914d70f0b3863..38cae78c05aac1345b7ee2121f658f639d68c6cc 100644 --- a/java/services/users/src/main/java/com/uva/api/controllers/UserController.java +++ b/java/services/users/src/main/java/com/uva/api/controllers/UserController.java @@ -1,16 +1,13 @@ package com.uva.api.controllers; -import java.util.List; import java.util.Map; -import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; @@ -18,16 +15,8 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.client.HttpClientErrorException; - import com.fasterxml.jackson.databind.JsonNode; -import com.uva.api.models.AuthResponse; -import com.uva.api.models.Client; -import com.uva.api.models.Manager; -import com.uva.api.models.User; -import com.uva.api.models.ClientStatus; -import com.uva.api.services.ClientService; -import com.uva.api.services.ManagerService; +import com.uva.api.models.AuthDTO; import com.uva.api.services.UserService; import com.uva.api.utils.Utils; @@ -39,19 +28,9 @@ public class UserController { @Autowired private UserService userService; - @Autowired - private ClientService clientService; - - @Autowired - private ManagerService managerService; - - // Common @PostMapping - public ResponseEntity<?> addUser(@RequestBody AuthResponse body) { - User user = new User(); - BeanUtils.copyProperties(body, user); - userService.registerNewUser(user); - return new ResponseEntity<User>(user, HttpStatus.ACCEPTED); + public ResponseEntity<?> addUser(@RequestBody AuthDTO body) { + return userService.registerNewUser(body); } @PutMapping("/{id}") @@ -63,14 +42,8 @@ public class UserController { if (!Utils.notEmptyStrings(name, email)) { return new ResponseEntity<String>("Missing required fields", HttpStatus.BAD_REQUEST); } - try { - User user = userService.updateUserData(id, name, email); - return new ResponseEntity<User>(user, HttpStatus.OK); - } catch (HttpClientErrorException e) { - if (e.getStatusCode() == HttpStatus.NOT_FOUND) - return new ResponseEntity<String>(HttpStatus.NOT_FOUND); - throw e; - } + + return userService.updateUserData(id, name, email); } @PutMapping("/{id}/password") @@ -81,112 +54,67 @@ public class UserController { return new ResponseEntity<String>("Missing required fields", HttpStatus.BAD_REQUEST); } - try { - User user = userService.changePassword(id, password); - return new ResponseEntity<User>(user, HttpStatus.OK); - } catch (HttpClientErrorException e) { - if (e.getStatusCode() == HttpStatus.NOT_FOUND) - return new ResponseEntity<String>(HttpStatus.NOT_FOUND); - throw e; - } + return userService.changePassword(id, password); } + // TODO aplicarr verificación + // @Autowired + // private TokenService ser; + + // private String validate(String token) { + // JWTData decoded = ser.decodeToken(token); + // if (decoded == null) { + // return "Invalid token format"; + // } + // UserRol rol = decoded.getRol(); + // String audience = decoded.getAudience(); + // boolean a = (rol == null || rol != UserRol.ADMIN); + // boolean b = (audience == null || !audience.equals("INTERNAL")); + // if (a && b) { + // return "Invalid " + a + " " + b; + // } + // return null; + + // } + + // @GetMapping(params = { "email" }) + // public ResponseEntity<?> getUserByEmail(@RequestParam String email, + // @RequestHeader(value = "Authorization", required = true) String + // authorization) { + // try { + // if (authorization == null) { + // return new ResponseEntity<String>("Missing required fields", + // HttpStatus.BAD_REQUEST); + // } + // String m = validate(authorization.substring(7)); + // if (m != null) { + // return new ResponseEntity<String>(m, HttpStatus.BAD_REQUEST); + // } + // return ResponseEntity.ok(userService.getUserByEmail(email)); + // } catch (HttpClientErrorException e) { + // if (e.getStatusCode() == HttpStatus.NOT_FOUND) + // return new ResponseEntity<String>(HttpStatus.NOT_FOUND); + // throw e; + // } + // } + @GetMapping(params = { "email" }) public ResponseEntity<?> getUserByEmail(@RequestParam String email) { - try { - return ResponseEntity.ok(userService.getUserByEmail(email)); - } catch (HttpClientErrorException e) { - if (e.getStatusCode() == HttpStatus.NOT_FOUND) - return new ResponseEntity<String>(HttpStatus.NOT_FOUND); - throw e; - } + return userService.getUserByEmail(email); } @GetMapping - public ResponseEntity<List<User>> getAllUsers() { - List<User> users = userService.getAllUsers(); - return ResponseEntity.ok(users); + public ResponseEntity<?> getAllUsers() { + return userService.getAllUsers(); } @GetMapping("/{id}") public ResponseEntity<?> getUserById(@PathVariable int id) { - return ResponseEntity.ok(userService.getUserById(id)); + return userService.getUserById(id); } @DeleteMapping("/{id}") - public ResponseEntity<?> deleteUser(@PathVariable Integer id) { - try { - return ResponseEntity.ok(userService.deleteUserById(id)); - } catch (HttpClientErrorException e) { - if (e.getStatusCode() == HttpStatus.NOT_FOUND) - return new ResponseEntity<String>(HttpStatus.NOT_FOUND); - throw e; - } - } - - // Clients - @GetMapping("/clients") - public ResponseEntity<List<Client>> getAllClients() { - List<Client> users = clientService.findAll(); - return ResponseEntity.ok(users); - } - - @GetMapping("/clients/{id}") - public ResponseEntity<Client> getClientById(@PathVariable int id) { - return ResponseEntity.ok(clientService.findById(id)); - } - - @PatchMapping("/clients/{id}") - public ResponseEntity<?> updateClientState(@PathVariable int id, @RequestBody Map<String, String> json) { - - String strStatus = json.get("status"); - if (strStatus == null) { - return new ResponseEntity<String>("Missing required fields", HttpStatus.BAD_REQUEST); - } - try { - ClientStatus clientStatus = ClientStatus.valueOf(strStatus); - return ResponseEntity.ok(clientService.updateClientStatus(id, clientStatus)); - } catch (IllegalArgumentException e) { - return new ResponseEntity<String>("Unknown Client state", HttpStatus.BAD_REQUEST); - } catch (HttpClientErrorException e) { - if (e.getStatusCode() == HttpStatus.NOT_FOUND) - return new ResponseEntity<String>(HttpStatus.NOT_FOUND); - throw e; - } - } - - @DeleteMapping("/clients/{id}") - public ResponseEntity<?> deleteClient(@PathVariable Integer id) { - try { - return ResponseEntity.ok(clientService.deleteById(id)); - } catch (HttpClientErrorException e) { - if (e.getStatusCode() == HttpStatus.NOT_FOUND) - return new ResponseEntity<String>(HttpStatus.NOT_FOUND); - throw e; - } + public ResponseEntity<?> deleteUser(@PathVariable int id) { + return userService.deleteUserById(id); } - - // HotelManagers - @GetMapping("/managers") - public ResponseEntity<List<Manager>> getAllHotelManagers() { - List<Manager> users = managerService.findAll(); - return ResponseEntity.ok(users); - } - - @GetMapping("/managers/{id}") - public ResponseEntity<Manager> getHotelManagerById(@PathVariable int id) { - return ResponseEntity.ok(managerService.findById(id)); - } - - @DeleteMapping("/managers/{id}") - public ResponseEntity<?> deleteHotelManager(@PathVariable Integer id) { - try { - return ResponseEntity.ok(managerService.deleteById(id)); - } catch (HttpClientErrorException e) { - if (e.getStatusCode() == HttpStatus.NOT_FOUND) - return new ResponseEntity<String>(HttpStatus.NOT_FOUND); - throw e; - } - } - } diff --git a/java/services/users/src/main/java/com/uva/api/exceptions/GlobalExceptionHandler.java b/java/services/users/src/main/java/com/uva/api/exceptions/GlobalExceptionHandler.java index 5681a607c7b0885255a2259e809cb4bb29bf456d..426b2fb250097e33499079693c456069dabc801c 100644 --- a/java/services/users/src/main/java/com/uva/api/exceptions/GlobalExceptionHandler.java +++ b/java/services/users/src/main/java/com/uva/api/exceptions/GlobalExceptionHandler.java @@ -12,8 +12,8 @@ import java.util.Map; @ControllerAdvice public class GlobalExceptionHandler { - @ExceptionHandler(HotelNotFoundException.class) - public ResponseEntity<Map<String, Object>> handleHotelNotFound(HotelNotFoundException ex) { + @ExceptionHandler(UserNotFoundException.class) + public ResponseEntity<Map<String, Object>> handleUserNotFound(UserNotFoundException ex) { Map<String, Object> body = new HashMap<>(); body.put("timestamp", LocalDateTime.now()); body.put("message", ex.getMessage()); @@ -21,24 +21,6 @@ public class GlobalExceptionHandler { return new ResponseEntity<>(body, HttpStatus.NOT_FOUND); } - @ExceptionHandler(InvalidRequestException.class) - public ResponseEntity<Map<String, Object>> handleInvalidRequest(InvalidRequestException ex) { - Map<String, Object> body = new HashMap<>(); - body.put("timestamp", LocalDateTime.now()); - body.put("message", ex.getMessage()); - - return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST); - } - - @ExceptionHandler(InvalidDateRangeException.class) - public ResponseEntity<Map<String, Object>> handleInvalidDateRange(InvalidDateRangeException ex) { - Map<String, Object> body = new HashMap<>(); - body.put("timestamp", LocalDateTime.now()); - body.put("message", ex.getMessage()); - - return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST); - } - @ExceptionHandler(Exception.class) public ResponseEntity<Map<String, Object>> handleGeneralException(Exception ex) { Map<String, Object> body = new HashMap<>(); diff --git a/java/services/users/src/main/java/com/uva/api/exceptions/InvalidDateRangeException.java b/java/services/users/src/main/java/com/uva/api/exceptions/InvalidDateRangeException.java deleted file mode 100644 index 58bf97d7b6dceb1db771de7058da6f159480a5b9..0000000000000000000000000000000000000000 --- a/java/services/users/src/main/java/com/uva/api/exceptions/InvalidDateRangeException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.uva.api.exceptions; - -public class InvalidDateRangeException extends RuntimeException { - public InvalidDateRangeException(String message) { - super(message); - } -} diff --git a/java/services/users/src/main/java/com/uva/api/exceptions/InvalidRequestException.java b/java/services/users/src/main/java/com/uva/api/exceptions/InvalidRequestException.java deleted file mode 100644 index 677cc4b7cb71bb20c3a9644ff2a8d3552546ea2c..0000000000000000000000000000000000000000 --- a/java/services/users/src/main/java/com/uva/api/exceptions/InvalidRequestException.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.uva.api.exceptions; - -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - -@ResponseStatus(HttpStatus.BAD_REQUEST) -public class InvalidRequestException extends RuntimeException { - public InvalidRequestException(String message) { - super(message); - } -} diff --git a/java/services/users/src/main/java/com/uva/api/exceptions/HotelNotFoundException.java b/java/services/users/src/main/java/com/uva/api/exceptions/UserNotFoundException.java similarity index 60% rename from java/services/users/src/main/java/com/uva/api/exceptions/HotelNotFoundException.java rename to java/services/users/src/main/java/com/uva/api/exceptions/UserNotFoundException.java index dc466f6f5f91da47dbe73c34b97059c817937f13..9d12fb2fe156607f5558986e92d513f85b386d6c 100644 --- a/java/services/users/src/main/java/com/uva/api/exceptions/HotelNotFoundException.java +++ b/java/services/users/src/main/java/com/uva/api/exceptions/UserNotFoundException.java @@ -4,8 +4,8 @@ import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; @ResponseStatus(HttpStatus.NOT_FOUND) // Devuelve un 404 cuando se lanza la excepción -public class HotelNotFoundException extends RuntimeException { - public HotelNotFoundException(int id) { - super("Hotel not found with id: " + id); +public class UserNotFoundException extends RuntimeException { + public UserNotFoundException(int id) { + super("User not found with id: " + id); } } diff --git a/java/services/users/src/main/java/com/uva/api/filter/JwtAuthenticationFilter.java b/java/services/users/src/main/java/com/uva/api/filter/JwtAuthenticationFilter.java index a68cb2098ad5b2002c208e7a3ccbb1bdbfbd26f5..18ef31e44129a2333fdbf7d90b52e9c908e02c8c 100644 --- a/java/services/users/src/main/java/com/uva/api/filter/JwtAuthenticationFilter.java +++ b/java/services/users/src/main/java/com/uva/api/filter/JwtAuthenticationFilter.java @@ -1,12 +1,10 @@ package com.uva.api.filter; -import com.auth0.jwt.JWT; -import com.auth0.jwt.JWTVerifier; -import com.auth0.jwt.algorithms.Algorithm; -import com.auth0.jwt.interfaces.DecodedJWT; import com.uva.api.models.UserRol; -import com.auth0.jwt.exceptions.JWTVerificationException; +import com.uva.api.models.remote.JwtData; +import com.uva.api.services.TokenService; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.authority.SimpleGrantedAuthority; @@ -19,21 +17,19 @@ import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.Filter; import java.io.IOException; import java.time.LocalDateTime; import java.util.Collections; +import java.util.Map; @Component public class JwtAuthenticationFilter implements Filter { - @Value("${security.jwt.secret-key}") - private String secretKey; - - private Algorithm getSigningAlgorithm() { - return Algorithm.HMAC256(secretKey); // Usar HMAC256 con la clave secreta - } + @Autowired + private TokenService service; private String getTokenFromRequest(HttpServletRequest request) { String authHeader = request.getHeader("Authorization"); @@ -43,26 +39,17 @@ public class JwtAuthenticationFilter implements Filter { return authHeader.substring(7); } - private DecodedJWT validateAndDecodeToken(String token) { + private JwtData validateAndDecodeToken(String token) { try { - JWTVerifier verifier = JWT.require(getSigningAlgorithm()).build(); - return verifier.verify(token); // Verifica y decodifica el token - } catch (JWTVerificationException ex) { - System.out.println( - "[" + LocalDateTime.now().toString() + "] Error de verificación del token: " + ex.getMessage()); + return service.decodeToken(token); + } catch (Exception ex) { + System.err.println( + "[" + LocalDateTime.now().toString() + "] Error de verificación del token"); + ex.printStackTrace(System.err); return null; } } - private String getEmailFromToken(DecodedJWT jwt) { - return jwt.getClaim("email").asString(); - } - - private UserRol getRoleFromToken(DecodedJWT jwt) { - String role = jwt.getClaim("rol").asString(); - return UserRol.valueOf(role); - } - private String formatRole(UserRol rol) { return String.format("ROLE_%s", rol.toString()); } @@ -70,34 +57,42 @@ public class JwtAuthenticationFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + HttpServletRequest httpRequest = (HttpServletRequest) request; String token = getTokenFromRequest(httpRequest); - System.out.print("[" + LocalDateTime.now().toString() + "] TOKEN: " + token); + System.out.println("[" + LocalDateTime.now().toString() + "] TOKEN: " + token); if (token != null) { - DecodedJWT jwt = validateAndDecodeToken(token); - System.out.print(" " + jwt.toString() + " "); + JwtData jwt = validateAndDecodeToken(token); if (jwt != null) { - String email = getEmailFromToken(jwt); - UserRol role = getRoleFromToken(jwt); - System.out.print(" email=" + email + " role=" + role + " "); - - if (email != null && role != null && SecurityContextHolder.getContext().getAuthentication() == null) { - // Crear la autoridad con el rol del token - SimpleGrantedAuthority authority = new SimpleGrantedAuthority(formatRole(role)); - - // Crear autenticación - UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(email, - null, Collections.singletonList(authority)); - authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest)); - - // Establecer autenticación en el contexto de seguridad - SecurityContextHolder.getContext().setAuthentication(authentication); - } + System.out.println("-->" + jwt + "<--"); } } + // String email = getEmailFromToken(jwt); + // UserRol role = getRoleFromToken(jwt); + // System.out.print(" email=" + email + " role=" + role + " "); + + // if (email != null && role != null && + // SecurityContextHolder.getContext().getAuthentication() == null) { + // // Crear la autoridad con el rol del token + // SimpleGrantedAuthority authority = new + // SimpleGrantedAuthority(formatRole(role)); + + // // Crear autenticación + // UsernamePasswordAuthenticationToken authentication = new + // UsernamePasswordAuthenticationToken(email, + // null, Collections.singletonList(authority)); + // authentication.setDetails(new + // WebAuthenticationDetailsSource().buildDetails(httpRequest)); + + // // Establecer autenticación en el contexto de seguridad + // SecurityContextHolder.getContext().setAuthentication(authentication); + // } + // } + // } + // Continuar con el resto de filtros chain.doFilter(request, response); } diff --git a/java/services/users/src/main/java/com/uva/api/interceptor/AuthHttpInterceptor.java b/java/services/users/src/main/java/com/uva/api/interceptor/AuthHttpInterceptor.java deleted file mode 100644 index 6e1a9256e6caf024c94dfd4ac49ab17cb982eb87..0000000000000000000000000000000000000000 --- a/java/services/users/src/main/java/com/uva/api/interceptor/AuthHttpInterceptor.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.uva.api.interceptor; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpRequest; -import org.springframework.http.client.ClientHttpRequestExecution; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.http.client.ClientHttpRequestInterceptor; -import org.springframework.stereotype.Component; - -import com.uva.api.models.UserRol; -import com.uva.api.utils.JwtUtil; - -import java.io.IOException; - -@Component -public class AuthHttpInterceptor implements ClientHttpRequestInterceptor { - - @Autowired - private JwtUtil jwtUtil; - - private String token; - - private String getAccessToken() { - if (token == null || token.isEmpty()) { - // TODO cambiar también si el token ha caducado - token = jwtUtil.generateToken("auth", "auth@dev.com", UserRol.ADMIN); - } - return token; - - } - - @Override - public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) - throws IOException { - // Generar o cargar el JWT token desde el bean JwtUtil - String jwtToken = getAccessToken(); - - // System.out.println("Using token " + jwtToken); - - // Agregar el token al encabezado Authorization - request.getHeaders().add("Authorization", "Bearer " + jwtToken); - - // Continuar con la ejecución de la solicitud - return execution.execute(request, body); - } -} diff --git a/java/services/users/src/main/java/com/uva/api/models/AuthResponse.java b/java/services/users/src/main/java/com/uva/api/models/AuthDTO.java similarity index 82% rename from java/services/users/src/main/java/com/uva/api/models/AuthResponse.java rename to java/services/users/src/main/java/com/uva/api/models/AuthDTO.java index ca8edf2a813699de08326948c55306d2dfbf567e..a340fc6852ccc45d1a608ad6a483e0851e88c1fa 100644 --- a/java/services/users/src/main/java/com/uva/api/models/AuthResponse.java +++ b/java/services/users/src/main/java/com/uva/api/models/AuthDTO.java @@ -9,11 +9,11 @@ import lombok.Setter; @AllArgsConstructor @Getter @Setter -public class AuthResponse { +public class AuthDTO { private int id; private String name; private String email; private String password; - private UserRol rol; + private UserRol rol = UserRol.CLIENT; } diff --git a/java/services/users/src/main/java/com/uva/api/models/Client.java b/java/services/users/src/main/java/com/uva/api/models/Client.java index 5205bebb8c355c5e9f65b1a7cb4d7c89d53b2a58..71f3a3f207782ca891acfa1cac513ec7fe9fb356 100644 --- a/java/services/users/src/main/java/com/uva/api/models/Client.java +++ b/java/services/users/src/main/java/com/uva/api/models/Client.java @@ -1,17 +1,14 @@ package com.uva.api.models; -import java.time.LocalDate; import java.util.List; import com.uva.api.models.remote.Booking; -import jakarta.persistence.Basic; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import jakarta.persistence.Table; -import jakarta.persistence.Transient; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -29,29 +26,23 @@ import lombok.ToString; @EqualsAndHashCode(callSuper = true) public class Client extends User { - @Basic(optional = false) @Column(nullable = false) @Enumerated(EnumType.STRING) private ClientStatus status = ClientStatus.NO_BOOKINGS; - @Transient - private List<Booking> bookings; public Client(int id, String name, String email, String password, ClientStatus status, List<Booking> bookings) { super(id, name, email, password, UserRol.CLIENT); setStatus(status); - setBookings(bookings); } - public ClientStatus getStatus() { - if (getBookings() == null || getBookings().isEmpty()) - return ClientStatus.NO_BOOKINGS; - boolean activeBookings = getBookings().stream() - .anyMatch(booking -> !booking.getEndDate().isBefore(LocalDate.now())); // reserva >= ahora - return activeBookings ? ClientStatus.WITH_ACTIVE_BOOKINGS : ClientStatus.WITH_INACTIVE_BOOKINGS; - } - - public void setStatus(ClientStatus status) { - this.status = status; - } + // public ClientStatus getStatus() { + // if (getBookings() == null || getBookings().isEmpty()) + // return ClientStatus.NO_BOOKINGS; + // boolean activeBookings = getBookings().stream() + // .anyMatch(booking -> !booking.getEndDate().isBefore(LocalDate.now())); // + // reserva >= ahora + // return activeBookings ? ClientStatus.WITH_ACTIVE_BOOKINGS : + // ClientStatus.WITH_INACTIVE_BOOKINGS; + // } } diff --git a/java/services/users/src/main/java/com/uva/api/models/Manager.java b/java/services/users/src/main/java/com/uva/api/models/Manager.java index d0b1780186159691d8372adc561515b198e3a242..f72e762b13b73585534608c5e3be25b5ed623ba0 100644 --- a/java/services/users/src/main/java/com/uva/api/models/Manager.java +++ b/java/services/users/src/main/java/com/uva/api/models/Manager.java @@ -1,5 +1,6 @@ package com.uva.api.models; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.JsonNode; import jakarta.persistence.Entity; @@ -23,6 +24,7 @@ import lombok.ToString; public class Manager extends User { @Transient + @JsonIgnore private JsonNode hotels; public Manager(int id, String name, String email, String password, JsonNode hotels) { diff --git a/java/services/users/src/main/java/com/uva/api/models/remote/JwtData.java b/java/services/users/src/main/java/com/uva/api/models/remote/JwtData.java new file mode 100644 index 0000000000000000000000000000000000000000..21774c76be754aacdb2005499b96591ce3459fd9 --- /dev/null +++ b/java/services/users/src/main/java/com/uva/api/models/remote/JwtData.java @@ -0,0 +1,36 @@ +package com.uva.api.models.remote; + +import java.util.Date; + +import com.uva.api.models.UserRol; + +import lombok.Data; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@Data +@ToString +public class JwtData { + + private String token; + + private Integer id; + private String name; + private String email; + private UserRol rol; + private String service; + + private String subject; + private String audience; + private Long ttl; + + private Date issuedAt; + private Date expiresAt; + + public boolean isAdmin() { + return rol != null && rol == UserRol.ADMIN; + } +} \ No newline at end of file diff --git a/java/services/users/src/main/java/com/uva/api/services/ClientService.java b/java/services/users/src/main/java/com/uva/api/services/ClientService.java index c3f352300a3a531a6548310911e7f1512f273cdd..81d3f92c2f2cac8108da9263eca42d413118fc9d 100644 --- a/java/services/users/src/main/java/com/uva/api/services/ClientService.java +++ b/java/services/users/src/main/java/com/uva/api/services/ClientService.java @@ -5,14 +5,15 @@ import java.util.List; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import com.uva.api.apis.BookingAPI; import com.uva.api.models.Client; import com.uva.api.models.User; import com.uva.api.models.UserRol; -import com.uva.api.models.ClientStatus; import com.uva.api.models.remote.Booking; +import com.uva.api.models.ClientStatus; import com.uva.api.repositories.ClientRepository; import com.uva.api.utils.Utils; @@ -25,19 +26,18 @@ public class ClientService { @Autowired private BookingAPI bookingAPI; - public List<Client> findAll() { - return clientRepository.findAll(); + public ResponseEntity<?> findAll() { + return ResponseEntity.ok(clientRepository.findAll()); } - public Client findById(int id) { + public ResponseEntity<?> findById(int id) { Client client = Utils.assertUser(clientRepository.findById(id)); - List<Booking> bookings = bookingAPI.getAllBookingsByUserId(client.getId()); - client.setBookings(bookings); - return client; + return ResponseEntity.ok(client); } public Client deleteById(int id) { Client client = Utils.assertUser(clientRepository.findById(id)); + bookingAPI.deleteAllByUserId(id); clientRepository.delete(client); return client; } @@ -50,27 +50,30 @@ public class ClientService { return clientRepository.save(client); } + // TODO No entiendo donde debería ir esto public User updateClientStatus(int id, ClientStatus status) { Client user = Utils.assertUser(clientRepository.findById(id)); - boolean activeBookings = user.getBookings().stream() + List<Booking> bookings = bookingAPI.getAllByUserId(id); + + boolean activeBookings = bookings.stream() .anyMatch(booking -> !booking.getEndDate().isBefore(LocalDate.now())); // reserva >= ahora - boolean inactiveBookings = user.getBookings().stream() + boolean inactiveBookings = bookings.stream() .anyMatch(booking -> booking.getEndDate().isBefore(LocalDate.now())); // reserva < ahora switch (status) { case NO_BOOKINGS: - if (!user.getBookings().isEmpty()) + if (!bookings.isEmpty()) throw new IllegalArgumentException("Invalid State: The user has at least one booking"); break; case WITH_ACTIVE_BOOKINGS: - if (user.getBookings().isEmpty()) + if (bookings.isEmpty()) throw new IllegalArgumentException("Invalid State: The user don't has bookings"); if (!activeBookings) throw new IllegalArgumentException("Invalid State: The user don't has active bookings"); break; case WITH_INACTIVE_BOOKINGS: - if (user.getBookings().isEmpty()) + if (bookings.isEmpty()) throw new IllegalArgumentException("Invalid State: The user don't has bookings"); if (!inactiveBookings) throw new IllegalArgumentException("Invalid State: The user don't has inactive bookings"); diff --git a/java/services/users/src/main/java/com/uva/api/services/ManagerService.java b/java/services/users/src/main/java/com/uva/api/services/ManagerService.java index 098fc043649d85383c5371d2f995f4a908bc63c3..87d8f38c546f2080522205927400a3dcaa42672b 100644 --- a/java/services/users/src/main/java/com/uva/api/services/ManagerService.java +++ b/java/services/users/src/main/java/com/uva/api/services/ManagerService.java @@ -6,6 +6,7 @@ import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import com.uva.api.apis.HotelApi; import com.uva.api.models.Manager; import com.uva.api.models.User; import com.uva.api.repositories.ManagerRepository; @@ -14,6 +15,9 @@ import com.uva.api.utils.Utils; @Service public class ManagerService { + @Autowired + private HotelApi hotelApi; + @Autowired private ManagerRepository managerRepository; @@ -34,6 +38,7 @@ public class ManagerService { public Object deleteById(Integer id) { Manager manager = Utils.assertUser(managerRepository.findById(id)); + hotelApi.deleteAllByManagerId(id); managerRepository.delete(manager); return manager; } diff --git a/java/services/users/src/main/java/com/uva/api/services/TokenService.java b/java/services/users/src/main/java/com/uva/api/services/TokenService.java new file mode 100644 index 0000000000000000000000000000000000000000..4d90bbe4e3f3cecfa444d084c98b01c830163844 --- /dev/null +++ b/java/services/users/src/main/java/com/uva/api/services/TokenService.java @@ -0,0 +1,48 @@ +package com.uva.api.services; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.uva.api.apis.TokenAPI; +import com.uva.api.models.remote.JwtData; + +@Service +public class TokenService { + + @Autowired + private TokenAPI api; + + private JwtData ownToken; + private Map<String, JwtData> cache = new HashMap<>(); + + private boolean expireSoon(JwtData decoded) { + return (decoded.getExpiresAt().getTime() - System.currentTimeMillis()) / 1000 <= 10; + } + + public String getServiceToken() { + if (ownToken == null || expireSoon(ownToken)) { + System.out.println("Generando token"); + long s = System.currentTimeMillis(); + ownToken = api.getServiceToken(); + long t = System.currentTimeMillis() - s; + System.out.println("Token Generando en " + t + " ms"); + } + return ownToken.getToken(); + } + + public JwtData decodeToken(String token) { + if (cache.containsKey(token)) + return cache.get(token); + System.out.println("Actualizando token"); + long s = System.currentTimeMillis(); + JwtData decoded = api.decodeToken(token); + long t = System.currentTimeMillis() - s; + System.out.println("Actualizando token en " + t + " ms"); + cache.put(token, decoded); + return decoded; + } + +} diff --git a/java/services/users/src/main/java/com/uva/api/services/UserService.java b/java/services/users/src/main/java/com/uva/api/services/UserService.java index deb2e39905bd7f2b6bc80c9eef4e7ae31eb90d01..99f21a10bdcbf39d24b18c16f453d8df5f3523a4 100644 --- a/java/services/users/src/main/java/com/uva/api/services/UserService.java +++ b/java/services/users/src/main/java/com/uva/api/services/UserService.java @@ -4,10 +4,10 @@ import java.util.List; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; - import com.uva.api.utils.Utils; -import com.uva.api.models.AuthResponse; +import com.uva.api.models.AuthDTO; import com.uva.api.models.User; import com.uva.api.models.UserRol; import com.uva.api.repositories.UserRepository; @@ -24,63 +24,83 @@ public class UserService { @Autowired private ManagerService managerService; - public List<User> getAllUsers() { - return userRepository.findAll(); + public ResponseEntity<List<User>> getAllUsers() { + List<User> users = userRepository.findAll(); + return ResponseEntity.ok(users); } - public User getUserById(int id) { + private User assertUserById(int id) { return Utils.assertUser(userRepository.findById(id)); } - public AuthResponse getUserByEmail(String email) { + public ResponseEntity<User> getUserById(int id) { + User user = assertUserById(id); + return ResponseEntity.ok(user); + } + + public ResponseEntity<AuthDTO> getUserByEmail(String email) { User u = Utils.assertUser(userRepository.findByEmail(email)); - AuthResponse auth = new AuthResponse(); + AuthDTO auth = new AuthDTO(); BeanUtils.copyProperties(u, auth); - return auth; + return ResponseEntity.ok(auth); } - public User registerNewUser(User registerRequest) { - User newUser; + public ResponseEntity<User> registerNewUser(AuthDTO request) { + User user = new User(); + BeanUtils.copyProperties(request, user); // Aseguramos que tenga un rol, por defecto es cliente - if (registerRequest.getRol() == null) - registerRequest.setRol(UserRol.CLIENT); + if (user.getRol() == null) + user.setRol(UserRol.CLIENT); - switch (registerRequest.getRol()) { + switch (user.getRol()) { case ADMIN: // Not extracted due to its complexity, it's the same as for the user User admin = new User(); - BeanUtils.copyProperties(registerRequest, admin); - newUser = userRepository.save(admin); + BeanUtils.copyProperties(user, admin); + user = userRepository.save(admin); break; case HOTEL_ADMIN: - newUser = managerService.save(registerRequest); + user = managerService.save(user); break; case CLIENT: // By default default: - newUser = clientService.save(registerRequest); + user = clientService.save(user); break; } - return newUser; + return ResponseEntity.ok(user); } - public User updateUserData(int id, String name, String email) { - User user = getUserById(id); + public ResponseEntity<User> updateUserData(int id, String name, String email) { + User user = assertUserById(id); user.setName(name); user.setEmail(email); - return userRepository.save(user); + user = userRepository.save(user); + return ResponseEntity.ok(user); } - public User changePassword(int id, String password) { - User user = getUserById(id); + public ResponseEntity<User> changePassword(int id, String password) { + User user = assertUserById(id); user.setPassword(password); - return userRepository.save(user); + user = userRepository.save(user); + return ResponseEntity.ok(user); } - public User deleteUserById(int id) { - User user = getUserById(id); - userRepository.deleteById(id); - return user; + public ResponseEntity<User> deleteUserById(int id) { + User user = assertUserById(id); + switch (user.getRol()) { + case CLIENT: + clientService.deleteById(id); + break; + case HOTEL_ADMIN: + managerService.deleteById(id); + break; + case ADMIN: + default: + userRepository.deleteById(id); + break; + } + return ResponseEntity.ok(user); } } diff --git a/java/services/users/src/main/java/com/uva/api/utils/JwtUtil.java b/java/services/users/src/main/java/com/uva/api/utils/JwtUtil.java deleted file mode 100644 index e1d665c2c2332def70988441255a3ee20bf29c5f..0000000000000000000000000000000000000000 --- a/java/services/users/src/main/java/com/uva/api/utils/JwtUtil.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.uva.api.utils; - -import java.util.Date; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -import com.auth0.jwt.JWT; -import com.auth0.jwt.algorithms.Algorithm; -import com.uva.api.models.UserRol; - -@Component -public class JwtUtil { - - @Value("${security.jwt.secret-key}") - private String secretKey; - - @Value("${security.jwt.kid}") - private String kid; - - @Value("${security.jwt.expiration-time}") - private long jwtExpiration; - - public long getExpirationTime() { - return jwtExpiration; - } - - public String generateToken(String name, String email, UserRol rol) { - Algorithm algorithm = Algorithm.HMAC256(secretKey); - return JWT - .create() - .withKeyId(kid) - .withClaim("name", name) - .withClaim("email", email) - .withClaim("rol", rol.toString()) - .withIssuedAt(new Date()) - .withExpiresAt(new Date(System.currentTimeMillis() + jwtExpiration * 1000)) - .sign(algorithm); - } - - // TODO estaría guapo recuperar métodos de validación para el token de petición - // para este servicio -} diff --git a/java/services/users/src/main/resources/application.properties b/java/services/users/src/main/resources/application.properties index 3d3c5062f7694a2d5cfda9e7f09371cbee1358bf..6ad30a8252d53371d2accfd475e783772d1ea2ca 100644 --- a/java/services/users/src/main/resources/application.properties +++ b/java/services/users/src/main/resources/application.properties @@ -7,7 +7,6 @@ spring.datasource.username=user spring.datasource.password=password spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver -spring.neo4j.security.hostname-verification-enabled=false spring.security.user.name=user spring.security.user.password=password @@ -16,4 +15,6 @@ security.jwt.secret-key=MiClaveDeSeguridadMuyLargaParaQueNoFalleSpringBoot security.jwt.expiration-time=3600000 security.jwt.kid=cYz3kNRLAirxVhHXQ5rh5xKrOwHwZVui -external.services.bookings.url=localhost:8401/bookings \ No newline at end of file +services.internal.token.url=http://localhost:8101/token/ +services.external.hotels.url=http://localhost:8301/hotels +services.external.bookings.url=http://localhost:8401/bookings \ No newline at end of file diff --git a/poblate/mocks/bookings.json b/poblate/mocks/bookings.json new file mode 100644 index 0000000000000000000000000000000000000000..bf93e638b17471bc9585c37e0689f6808da03285 --- /dev/null +++ b/poblate/mocks/bookings.json @@ -0,0 +1,16 @@ +[ + { + "clientId": 1, + "hotelId": 1, + "roomId": 2, + "startDate": "2024-10-20", + "endDate": "2024-10-25" + }, + { + "clientId": 3, + "hotelId": 2, + "roomId": 3, + "startDate": "2025-02-22", + "endDate": "2025-02-27" + } +] diff --git a/poblate/mocks/hotels.json b/poblate/mocks/hotels.json new file mode 100644 index 0000000000000000000000000000000000000000..4331cfea0d6964414b2b702b1cfafeb90a133821 --- /dev/null +++ b/poblate/mocks/hotels.json @@ -0,0 +1,48 @@ +[ + { + "name": "Ocean View Hotel", + "address": { + "streetKind": "Avenida", + "streetName": "Marítima", + "number": 123, + "postCode": "45678", + "otherInfo": "Frente al mar" + }, + "managerId": 2, + "rooms": [ + { + "roomNumber": "101", + "type": "SINGLE", + "available": true + }, + { + "roomNumber": "102", + "type": "DOUBLE", + "available": false + } + ] + }, + { + "name": "Mountain Retreat", + "address": { + "streetKind": "Calle", + "streetName": "Bosque", + "number": 42, + "postCode": "98765", + "otherInfo": "Cerca del parque nacional" + }, + "managerId": 2, + "rooms": [ + { + "roomNumber": "201", + "type": "DOUBLE", + "available": true + }, + { + "roomNumber": "202", + "type": "SUITE", + "available": true + } + ] + } +] diff --git a/poblate/mocks/users.json b/poblate/mocks/users.json new file mode 100644 index 0000000000000000000000000000000000000000..72cd088b876a8b8d5a4adc4e588f01bdf61c3995 --- /dev/null +++ b/poblate/mocks/users.json @@ -0,0 +1,20 @@ +[ + { + "name": "John Doe", + "email": "john.doe@example.com", + "password": "securePassword123", + "rol": "CLIENT" + }, + { + "name": "Jane Smith", + "email": "jane.smith@example.com", + "password": "securePassword456", + "rol": "HOTEL_ADMIN" + }, + { + "name": "Alice Johnson", + "email": "alice.johnson@example.com", + "password": "securePassword789", + "rol": "CLIENT" + } +]