diff --git a/.env b/.env deleted file mode 100644 index 36615a4..0000000 --- a/.env +++ /dev/null @@ -1,4 +0,0 @@ -AUTH0_CLIENT_ID=WNUDfBrJg6C46KxbhEmd8nK70y8eBZEg -AUTH0_CLIENT_SECRET=Cgi8WarbeegjZfKDVpBPHbUZQnJAgXg8GAqSp0GG8Bhw-7a1a5EAQue15XqBxVDw -AUTH0_DOMAIN=https://dev-1m8rc8rkauzkk7c6.us.auth0.com/ -AUTH0_CALLBACK_URL=http://localhost:3000/api/auth/callback/auth0 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 549e00a..ba458bf 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,10 @@ target/ !**/src/main/**/target/ !**/src/test/**/target/ +### Environment Variables (DO NOT COMMIT) ### +.env.local +.env.*.local + ### STS ### .apt_generated .classpath diff --git a/BACKEND_DOCUMENTATION.md b/BACKEND_DOCUMENTATION.md new file mode 100644 index 0000000..951cdb1 --- /dev/null +++ b/BACKEND_DOCUMENTATION.md @@ -0,0 +1,483 @@ +# NBAT Backend Documentation + +**Version:** 2.0 +**Last Updated:** February 2, 2026 +**Status:** ✅ Production Ready + +--- + +## Quick Overview + +NBAT is a property rental platform (like Airbnb) built with Spring Boot. + +**Key Architecture:** + +- Auth0 handles **authentication** (verifying who you are) +- PostgreSQL database handles **authorization** (what you can do) +- CSRF tokens protect state-changing requests +- Roles are loaded from database on every request for instant updates + +--- + +## How It Works + +### Login Flow + +``` +1. User clicks login → Redirected to Auth0 +2. Auth0 verifies credentials → Redirects back +3. OAuth2LoginSuccessHandler runs: + - Checks if user exists in database + - Creates new user OR updates existing user + - New users get ROLE_TENANT automatically +4. DatabaseAuthoritiesFilter runs on EVERY request: + - Loads user's roles from database + - Updates SecurityContext with fresh roles +5. User can now access features based on their roles +``` + +### Request Flow + +``` +Browser Request + ↓ +CORS Check (is origin allowed?) + ↓ +CSRF Validation (for POST/PUT/DELETE) + ↓ +Authentication (is user logged in?) + ↓ +DatabaseAuthoritiesFilter (load roles from DB) + ↓ +Authorization (does user have required role?) + ↓ +Controller → Service → Repository → Database +``` + +--- + +## Project Structure + +``` +src/main/java/fr/codecake/nbat/ +│ +├── infrastructure/config/ +│ ├── SecurityConfiguration.java # CORS, CSRF, OAuth2 setup +│ ├── OAuth2LoginSuccessHandler.java # Syncs Auth0 user to DB on login +│ ├── DatabaseAuthoritiesFilter.java # Loads roles from DB on each request +│ └── DatabaseConfiguration.java +│ +├── user/ +│ ├── presentation/AuthResource.java # /api/auth/* endpoints +│ ├── application/UserService.java # User business logic +│ ├── domain/ +│ │ ├── User.java # nbat_user table +│ │ └── Authority.java # nbat_authority table +│ └── repository/ +│ ├── UserRepository.java +│ └── AuthorityRepository.java +│ +├── listing/ +│ ├── presentation/ +│ │ ├── TenantResource.java # Public listing endpoints +│ │ └── LandlordResource.java # Landlord-only endpoints +│ ├── application/ +│ │ ├── TenantService.java +│ │ └── LandlordService.java +│ ├── domain/ +│ │ ├── Listing.java +│ │ └── ListingPicture.java +│ └── repository/ +│ +└── booking/ + ├── presentation/BookingResource.java + ├── application/BookingService.java + ├── domain/Booking.java + └── repository/BookingRepository.java +``` + +--- + +## Database Schema + +All tables live in `nbat_schema`: + +### User & Role Tables + +```sql +-- User profiles +nbat_user ( + id, email, first_name, last_name, + image_url, public_id, created_date, last_modified_date +) + +-- Available roles: ROLE_TENANT, ROLE_LANDLORD +nbat_authority (name) + +-- Links users to their roles +nbat_user_authority (user_id, authority_name) +``` + +### Business Tables + +```sql +listing ( + id, landlord_fk, title, description, price, + location, beds, bedrooms, bathrooms, guests, category +) + +listing_picture ( + id, listing_fk, file, file_content_type, is_cover +) + +booking ( + id, listing_fk, tenant_fk, start_date, end_date, + total_price, number_of_travelers +) +``` + +--- + +## Security Components + +### 1. SecurityConfiguration.java + +**Main security setup** + +- Configures OAuth2 login with Auth0 +- Sets up CSRF protection (cookie + header) +- Configures CORS for frontend (localhost:4200) +- Defines public vs authenticated endpoints + +### 2. OAuth2LoginSuccessHandler.java + +**Runs after successful Auth0 login** + +```java +@Override +public void onAuthenticationSuccess(...) { + // 1. Get user email from Auth0 + // 2. Check if user exists in DB + // 3. Create new user (assign ROLE_TENANT) OR update existing +} +``` + +### 3. DatabaseAuthoritiesFilter.java + +**Runs on EVERY authenticated request** + +```java +@Override +protected void doFilterInternal(...) { + // 1. Get current user's email + // 2. Query database for their roles + // 3. Create new Authentication with DB roles + // 4. Replace old Authentication in SecurityContext +} +``` + +**Why this matters:** Role changes take effect immediately without re-login! + +### CSRF Protection + +- **Cookie:** `XSRF-TOKEN` (frontend reads this) +- **Header:** `X-CSRF-TOKEN` (frontend sends this) +- **Required for:** POST, PUT, DELETE requests +- **Not required for:** GET requests + +Frontend must: + +1. Read token from `XSRF-TOKEN` cookie +2. Send it as `X-CSRF-TOKEN` header on state-changing requests + +### CORS + +- **Allowed origins:** http://localhost:4200, http://localhost:4201 +- **Allowed methods:** GET, POST, PUT, DELETE, OPTIONS +- **Credentials:** Allowed (cookies can be sent) + +--- + +## How Roles Work + +### Default Behavior + +- New user logs in → Gets `ROLE_TENANT` automatically +- User calls `/api/auth/assign-landlord-role` → Gets `ROLE_LANDLORD` added +- Users can have both roles (tenant + landlord) + +### Role Requirements + +```java +// Public - anyone +GET /api/tenant-listing/get-all-by-category + +// Authenticated - any logged-in user +@PreAuthorize("hasAnyRole('TENANT', 'LANDLORD')") +POST /api/booking/create + +// Landlord only +@PreAuthorize("hasRole('LANDLORD')") +POST /api/landlord-listing/create +``` + +--- + +## API Endpoints + +### Authentication + +``` +GET /api/auth/get-authenticated-user # Get current user + roles +POST /api/auth/assign-landlord-role # Upgrade to landlord +POST /api/auth/logout # Logout +``` + +### Listings (Public) + +``` +GET /api/tenant-listing/get-all-by-category?category=ALL +POST /api/tenant-listing/search +GET /api/tenant-listing/get-one?publicId=xxx +``` + +### Bookings (Authenticated) + +``` +POST /api/booking/create # Create booking +GET /api/booking/check-availability # Check dates +GET /api/booking/get-booked-listing # My bookings +GET /api/booking/get-booked-listing-for-landlord # (Landlord only) +``` + +### Landlord (ROLE_LANDLORD Required) + +``` +POST /api/landlord-listing/create # Create listing +GET /api/landlord-listing/get-all # My listings +DELETE /api/landlord-listing/delete # Delete listing +``` + +--- + +## Configuration + +### application.yml + +```yaml +spring: + datasource: + url: jdbc:postgresql://localhost:5432/nbat_db + username: postgres + password: 0000 + + jpa: + hibernate: + ddl-auto: update # Auto-update schema + properties: + hibernate: + default_schema: nbat_schema # Use nbat_schema + +okta: + oauth2: + issuer: https://dev-1m8rc8rkauzkk7c6.us.auth0.com/ + client-id: YOUR_CLIENT_ID + client-secret: YOUR_CLIENT_SECRET + +server: + port: 8080 +``` + +### docker-compose.yml + +```yaml +services: + postgres: + image: postgres:16 + environment: + POSTGRES_DB: nbat_db + POSTGRES_USER: postgres + POSTGRES_PASSWORD: 0000 + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + - ./init.sql:/docker-entrypoint-initdb.d/init.sql + +volumes: + postgres_data: +``` + +### init.sql + +```sql +CREATE SCHEMA IF NOT EXISTS nbat_schema; +GRANT ALL ON SCHEMA nbat_schema TO postgres; +ALTER DATABASE nbat_db SET search_path TO nbat_schema, public; +``` + +--- + +## Development Setup + +### Prerequisites + +- Java 21 +- Docker & Docker Compose +- Maven + +### Quick Start + +```bash +# 1. Start database +docker-compose up -d + +# 2. Wait for postgres (check logs) +docker-compose logs -f postgres +# Look for: "database system is ready to accept connections" + +# 3. Run application +./mvnw spring-boot:run + +# Application runs on http://localhost:8080 +``` + +### Reset Database + +```bash +docker-compose down -v # Delete everything including data +docker-compose up -d # Fresh start +./mvnw spring-boot:run +``` + +### Check Database + +```bash +# Connect to postgres +docker exec -it nbat-postgres psql -U postgres -d nbat_db + +# Useful commands: +\dt nbat_schema.* # List tables +SELECT * FROM nbat_schema.nbat_user; # View users +SELECT * FROM nbat_schema.nbat_user_authority; # View roles +\q # Exit +``` + +--- + +## Error Responses + +All errors return JSON: + +```json +{ + "timestamp": "2026-02-02T12:00:00", + "status": 400, + "error": "Bad Request", + "message": "Specific error message", + "path": "/api/endpoint" +} +``` + +### Status Codes + +- **200 OK** - Success +- **201 Created** - Resource created +- **400 Bad Request** - Invalid input +- **401 Unauthorized** - Not logged in +- **403 Forbidden** - Missing required role +- **404 Not Found** - Resource not found +- **500 Server Error** - Something broke + +--- + +## Common Issues & Solutions + +### "relation nbat_schema.nbat_user does not exist" + +**Cause:** Schema not created +**Fix:** + +```bash +docker-compose down -v +docker-compose up -d +``` + +Ensure `init.sql` is mounted in docker-compose.yml + +### CSRF token errors + +**Cause:** Missing X-CSRF-TOKEN header +**Fix:** Frontend must read XSRF-TOKEN cookie and send as X-CSRF-TOKEN header + +### 403 Forbidden on landlord endpoints + +**Cause:** User doesn't have ROLE_LANDLORD +**Fix:** Call `/api/auth/assign-landlord-role` first + +### Roles not updating + +**Should never happen!** DatabaseAuthoritiesFilter loads fresh roles on every request +If it does: Check filter is registered in SecurityConfiguration + +--- + +## Technology Stack + +| Technology | Version | Purpose | +| --------------- | ------- | ---------------- | +| Spring Boot | 3.2.4 | Framework | +| Java | 21 | Language | +| PostgreSQL | 16 | Database | +| Hibernate/JPA | 6.4 | ORM | +| Spring Security | 6.2 | Security | +| Auth0 | Latest | Authentication | +| Docker | Latest | Containerization | + +--- + +## Key Files Explained + +### SecurityConfiguration.java + +Sets up all security: + +- OAuth2 login with Auth0 +- CSRF tokens (cookie + header) +- CORS configuration +- Public vs protected endpoints + +### OAuth2LoginSuccessHandler.java + +Runs after Auth0 login: + +- Gets user info from Auth0 +- Creates or updates user in database +- Assigns ROLE_TENANT to new users + +### DatabaseAuthoritiesFilter.java + +Runs on every request: + +- Loads user's current roles from database +- Updates SecurityContext with fresh roles +- Enables instant role changes without re-login + +### UserService.java + +User business logic: + +- `syncWithIdp()` - Syncs Auth0 user to DB +- `getAuthenticatedUserFromSecurityContext()` - Gets current user +- `assignRoleLandlord()` - Adds ROLE_LANDLORD + +--- + +## Summary + +**Authentication:** Auth0 (who you are) +**Authorization:** Database (what you can do) +**Security:** CSRF tokens + CORS +**Key Feature:** DatabaseAuthoritiesFilter loads roles on every request = instant role updates + +Simple, secure, and maintainable! diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index fbcb819..0000000 --- a/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM eclipse-temurin:17-jre-alpine -WORKDIR /app -COPY target/*.jar app.jar -EXPOSE 8080 -ENTRYPOINT ["java", "-jar", "app.jar"] \ No newline at end of file diff --git a/README.md b/README.md index ee09dde..5d47a59 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,56 @@ -# Nbat clone (fullstack project) Spring boot 3, Angular 17, PrimeNG, PostgreSQL, Auth0 (2025-2026 ODC) (Backend) +# NBAT Clone Backend -Spring boot backend of the nbat clone +## Quick Start for Teammates -### Key Features: -- 📅 Booking management for travelers -- 🔍 Search for houses by criteria (location, date, guests, beds, etc) -- 🔐 Authentication and Authorization (Role management) with Auth0 (OAuth2) - -## Usage ### Prerequisites -- [JDK 21](https://adoptium.net/temurin/releases/) -- [PostgreSQL](https://www.postgresql.org/download/) -- IDE ([VSCode](https://code.visualstudio.com/download), [IntelliJ](https://www.jetbrains.com/idea/download/)) -### Launch -#### Maven -``./mvnw spring-boot:run`` +- Java 21 +- Docker Desktop +- Maven (or use `./mvnw`) + +### Setup (First Time) + +1. **Clone the repo** + +```bash + git clone + cd Nbat-project-backend +``` + +2. **Set up environment variables** + +```bash + cp .env.example .env +``` + +3. **Edit `.env` and fill in YOUR Auth0 credentials** + - Go to https://manage.auth0.com/dashboard/us/dev-1m8rc8rkauzkk7c6/applications + - Copy your client ID and secret + - Paste into `.env` + +4. **Start PostgreSQL** + +```bash + docker-compose up -d +``` + +5. **Run the application** + +```bash + ./mvnw spring-boot:run +``` + +6. **Verify it works** + - Backend: http://localhost:8080 + - Database: localhost:5432 + +### Common Issues + +**"Auth0 credentials not found"** +→ Make sure you copied `.env.example` to `.env` and filled in values -#### IntelliJ -Go in IntelliJ add the environment variables and then run it. +**"Database connection failed"** +→ Make sure Docker is running: `docker-compose ps` -#### Important -compresser votre projet en fichier jar avec mvn clean package -DskipTests avant de construire l'image docker avec docker-compose up --build -La base de données est gerable au niveau local +**"Port 8080 already in use"** +→ Change `SERVER_PORT=8081` in your `.env` diff --git a/docker-compose.yml b/docker-compose.yml index 1b75a4f..f98b1d6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,15 +1,21 @@ -version: '3.8' - services: - backend: - build: . - container_name: nbat-backend - ports: - - "8080:8080" + postgres: + image: postgres:16 + container_name: nbat-postgres environment: - SPRING_DATASOURCE_URL: jdbc:postgresql://host.docker.internal:5432/nbat_db - SPRING_DATASOURCE_USERNAME: postgres - SPRING_DATASOURCE_PASSWORD: "0000" - SPRING_JPA_HIBERNATE_DDL_AUTO: update - extra_hosts: - - "host.docker.internal:host-gateway" \ No newline at end of file + POSTGRES_USER: postgres + POSTGRES_PASSWORD: 0000 + POSTGRES_DB: nbat_db + ports: + - "5432:5432" + volumes: + - ./init.sql:/docker-entrypoint-initdb.d/init.sql + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres -d nbat_db"] + interval: 10s + timeout: 5s + retries: 5 + +volumes: + postgres_data: + driver: local \ No newline at end of file diff --git a/init.sql b/init.sql new file mode 100644 index 0000000..2088a22 --- /dev/null +++ b/init.sql @@ -0,0 +1,8 @@ +-- Create the nbat_schema if it doesn't exist +CREATE SCHEMA IF NOT EXISTS nbat_schema; + +-- Grant all privileges on the schema to the postgres user +GRANT ALL ON SCHEMA nbat_schema TO postgres; + +-- Set the search path so the schema is used by default +ALTER DATABASE nbat_db SET search_path TO nbat_schema, public; \ No newline at end of file diff --git a/pom.xml b/pom.xml index f5bcf70..4857ef4 100644 --- a/pom.xml +++ b/pom.xml @@ -9,10 +9,10 @@ fr.codecake - airbnb-clone-back + nbat-back 0.0.1-SNAPSHOT - airbnb-clone-back - Airbnb Clone API + nbat-back + nbat API 21 1.5.5.Final @@ -43,10 +43,6 @@ okta-spring-boot-starter 3.0.6 - - org.liquibase - liquibase-core - org.mapstruct mapstruct @@ -87,6 +83,11 @@ spring-security-test test + + + org.springframework.boot + spring-boot-starter-websocket + @@ -115,14 +116,6 @@ - - org.liquibase - liquibase-maven-plugin - 4.24.0 - - src/main/resources/liquibase.properties - - diff --git a/src/main/java/fr/codecake/airbnbclone/infrastructure/config/SecurityConfiguration.java b/src/main/java/fr/codecake/airbnbclone/infrastructure/config/SecurityConfiguration.java deleted file mode 100644 index de2d36a..0000000 --- a/src/main/java/fr/codecake/airbnbclone/infrastructure/config/SecurityConfiguration.java +++ /dev/null @@ -1,71 +0,0 @@ -package fr.codecake.airbnbclone.infrastructure.config; - -import java.util.HashSet; -import java.util.Set; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpMethod; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; -import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; -import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.csrf.CookieCsrfTokenRepository; -import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler; - -@Configuration -@EnableMethodSecurity -public class SecurityConfiguration { - - private final ClientRegistrationRepository clientRegistrationRepository; - - public SecurityConfiguration(ClientRegistrationRepository clientRegistrationRepository) { - this.clientRegistrationRepository = clientRegistrationRepository; - } - - @Bean - public SecurityFilterChain configure(HttpSecurity http) throws Exception { - CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler(); - requestHandler.setCsrfRequestAttributeName(null); - http.authorizeHttpRequests(authorize -> authorize - .requestMatchers(HttpMethod.GET, "api/tenant-listing/get-all-by-category").permitAll() - .requestMatchers(HttpMethod.GET, "api/tenant-listing/get-one").permitAll() - .requestMatchers(HttpMethod.POST, "api/tenant-listing/search").permitAll() - .requestMatchers(HttpMethod.GET, "api/booking/check-availability").permitAll() - .requestMatchers(HttpMethod.GET, "assets/*").permitAll() - .anyRequest() - .authenticated()) - .csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) - .csrfTokenRequestHandler(requestHandler)) - .oauth2Login(oauth2 -> oauth2 - .authorizationEndpoint(authorization -> authorization - .authorizationRequestResolver( - new CustomAuthorizationRequestResolver(clientRegistrationRepository) - ) - ) - ) - .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())) - .oauth2Client(Customizer.withDefaults()); - - return http.build(); - } - - @Bean - public GrantedAuthoritiesMapper userAuthoritiesMapper() { - return authorities -> { - Set grantedAuthorities = new HashSet<>(); - - authorities.forEach(grantedAuthority -> { - if (grantedAuthority instanceof OidcUserAuthority oidcUserAuthority) { - grantedAuthorities - .addAll(SecurityUtils.extractAuthorityFromClaims(oidcUserAuthority.getUserInfo().getClaims())); - } - }); - return grantedAuthorities; - }; - } -} \ No newline at end of file diff --git a/src/main/java/fr/codecake/airbnbclone/infrastructure/config/SecurityUtils.java b/src/main/java/fr/codecake/airbnbclone/infrastructure/config/SecurityUtils.java deleted file mode 100644 index d04a9f7..0000000 --- a/src/main/java/fr/codecake/airbnbclone/infrastructure/config/SecurityUtils.java +++ /dev/null @@ -1,101 +0,0 @@ -package fr.codecake.airbnbclone.infrastructure.config; - -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; - -import fr.codecake.airbnbclone.user.domain.Authority; -import fr.codecake.airbnbclone.user.domain.User; - -public class SecurityUtils { - - public static final String ROLE_TENANT = "ROLE_TENANT"; - public static final String ROLE_LANDLORD = "ROLE_LANDLORD"; - - public static final String CLAIMS_NAMESPACE = "https://www.codecake.fr/roles"; - - @SuppressWarnings("unchecked") - public static User mapOauth2AttributesToUser(Map attributes) { - User user = new User(); - String sub = String.valueOf(attributes.get("sub")); - - String username = null; - - if (attributes.get("preferred_username") != null) { - username = ((String) attributes.get("preferred_username")).toLowerCase(); - } - - if (attributes.get("given_name") != null) { - user.setFirstName(((String) attributes.get("given_name"))); - } else if ((attributes.get("nickname") != null)) { - user.setFirstName(((String) attributes.get("nickname"))); - } - - if (attributes.get("family_name") != null) { - user.setLastName(((String) attributes.get("family_name"))); - } - - if (attributes.get("email") != null) { - user.setEmail(((String) attributes.get("email"))); - } else if (sub.contains("|") && (username != null && username.contains("@"))) { - user.setEmail(username); - } else { - user.setEmail(sub); - } - - if (attributes.get("picture") != null) { - user.setImageUrl(((String) attributes.get("picture"))); - } - - if(attributes.get(CLAIMS_NAMESPACE) != null) { - List authoritiesRaw = (List) attributes.get(CLAIMS_NAMESPACE); - Set authorities = authoritiesRaw.stream() - .map(authority -> { - Authority auth = new Authority(); - auth.setName(authority); - return auth; - }).collect(Collectors.toSet()); - user.setAuthorities(authorities); - } - return user; - } - - public static List extractAuthorityFromClaims(Map claims) { - return mapRolesToGrantedAuthorities(getRolesFromClaims(claims)); - } - - @SuppressWarnings("unchecked") - private static Collection getRolesFromClaims(Map claims) { - return (List) claims.get(CLAIMS_NAMESPACE); - } - - private static List mapRolesToGrantedAuthorities(Collection roles) { - if (roles == null || roles.isEmpty()) { - return List.of(new SimpleGrantedAuthority("ROLE_TENANT")); // Rôle par défaut - } - return roles.stream().filter(role -> role.startsWith("ROLE_")).map(SimpleGrantedAuthority::new).toList(); - } - - public static boolean hasCurrentUserAnyOfAuthorities(String ...authorities) { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - return (authentication != null && getAuthorities(authentication) - .anyMatch(authority -> Arrays.asList(authorities).contains(authority))); - } - - private static Stream getAuthorities(Authentication authentication) { - Collection authorities = authentication - instanceof JwtAuthenticationToken jwtAuthenticationToken && jwtAuthenticationToken.getToken() != null ? - extractAuthorityFromClaims(jwtAuthenticationToken.getToken().getClaims()) : authentication.getAuthorities(); - return authorities.stream().map(GrantedAuthority::getAuthority); - } -} diff --git a/src/main/java/fr/codecake/airbnbclone/listing/application/LandlordService.java b/src/main/java/fr/codecake/airbnbclone/listing/application/LandlordService.java deleted file mode 100644 index 1929121..0000000 --- a/src/main/java/fr/codecake/airbnbclone/listing/application/LandlordService.java +++ /dev/null @@ -1,86 +0,0 @@ -package fr.codecake.airbnbclone.listing.application; - -import fr.codecake.airbnbclone.listing.application.dto.CreatedListingDTO; -import fr.codecake.airbnbclone.listing.application.dto.DisplayCardListingDTO; -import fr.codecake.airbnbclone.listing.application.dto.ListingCreateBookingDTO; -import fr.codecake.airbnbclone.listing.application.dto.SaveListingDTO; -import fr.codecake.airbnbclone.listing.domain.Listing; -import fr.codecake.airbnbclone.listing.mapper.ListingMapper; -import fr.codecake.airbnbclone.listing.repository.ListingRepository; -import fr.codecake.airbnbclone.sharedkernel.service.State; -import fr.codecake.airbnbclone.user.application.Auth0Service; -import fr.codecake.airbnbclone.user.application.UserService; -import fr.codecake.airbnbclone.user.application.dto.ReadUserDTO; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -@Service -public class LandlordService { - - private final ListingRepository listingRepository; - - private final ListingMapper listingMapper; - private final UserService userService; - private final Auth0Service auth0Service; - private final PictureService pictureService; - - public LandlordService(ListingRepository listingRepository, ListingMapper listingMapper, UserService userService, Auth0Service auth0Service, PictureService pictureService) { - this.listingRepository = listingRepository; - this.listingMapper = listingMapper; - this.userService = userService; - this.auth0Service = auth0Service; - this.pictureService = pictureService; - } - - public CreatedListingDTO create(SaveListingDTO saveListingDTO) { - Listing newListing = listingMapper.saveListingDTOToListing(saveListingDTO); - - ReadUserDTO userConnected = userService.getAuthenticatedUserFromSecurityContext(); - newListing.setLandlordPublicId(userConnected.publicId()); - - Listing savedListing = listingRepository.saveAndFlush(newListing); - - pictureService.saveAll(saveListingDTO.getPictures(), savedListing); - - auth0Service.addLandlordRoleToUser(userConnected); - - return listingMapper.listingToCreatedListingDTO(savedListing); - } - - @Transactional(readOnly = true) - public List getAllProperties(ReadUserDTO landlord) { - List properties = listingRepository.findAllByLandlordPublicIdFetchCoverPicture(landlord.publicId()); - return listingMapper.listingToDisplayCardListingDTOs(properties); - } - - @Transactional - public State delete(UUID publicId, ReadUserDTO landlord) { - long deletedSuccessfuly = listingRepository.deleteByPublicIdAndLandlordPublicId(publicId, landlord.publicId()); - if (deletedSuccessfuly > 0) { - return State.builder().forSuccess(publicId); - } else { - return State.builder().forUnauthorized("User not authorized to delete this listing"); - } - } - - public Optional getByListingPublicId(UUID publicId) { - return listingRepository.findByPublicId(publicId).map(listingMapper::mapListingToListingCreateBookingDTO); - } - - public List getCardDisplayByListingPublicId(List allListingPublicIDs) { - return listingRepository.findAllByPublicIdIn(allListingPublicIDs) - .stream() - .map(listingMapper::listingToDisplayCardListingDTO) - .toList(); - } - - @Transactional(readOnly = true) - public Optional getByPublicIdAndLandlordPublicId(UUID listingPublicId, UUID landlordPublicId) { - return listingRepository.findOneByPublicIdAndLandlordPublicId(listingPublicId, landlordPublicId) - .map(listingMapper::listingToDisplayCardListingDTO); - } -} diff --git a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/CreatedListingDTO.java b/src/main/java/fr/codecake/airbnbclone/listing/application/dto/CreatedListingDTO.java deleted file mode 100644 index e9eb02c..0000000 --- a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/CreatedListingDTO.java +++ /dev/null @@ -1,4 +0,0 @@ -package fr.codecake.airbnbclone.listing.application.dto; - -public record CreatedListingDTO(String publicId) { -} diff --git a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/ListingCreateBookingDTO.java b/src/main/java/fr/codecake/airbnbclone/listing/application/dto/ListingCreateBookingDTO.java deleted file mode 100644 index 56863ad..0000000 --- a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/ListingCreateBookingDTO.java +++ /dev/null @@ -1,9 +0,0 @@ -package fr.codecake.airbnbclone.listing.application.dto; - -import fr.codecake.airbnbclone.listing.application.dto.vo.PriceVO; - -import java.util.UUID; - -public record ListingCreateBookingDTO( - UUID listingPublicId, PriceVO price) { -} diff --git a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/sub/DescriptionDTO.java b/src/main/java/fr/codecake/airbnbclone/listing/application/dto/sub/DescriptionDTO.java deleted file mode 100644 index 5e83ccd..0000000 --- a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/sub/DescriptionDTO.java +++ /dev/null @@ -1,11 +0,0 @@ -package fr.codecake.airbnbclone.listing.application.dto.sub; - -import fr.codecake.airbnbclone.listing.application.dto.vo.DescriptionVO; -import fr.codecake.airbnbclone.listing.application.dto.vo.TitleVO; -import jakarta.validation.constraints.NotNull; - -public record DescriptionDTO( - @NotNull TitleVO title, - @NotNull DescriptionVO description - ) { -} diff --git a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/sub/ListingInfoDTO.java b/src/main/java/fr/codecake/airbnbclone/listing/application/dto/sub/ListingInfoDTO.java deleted file mode 100644 index 4413140..0000000 --- a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/sub/ListingInfoDTO.java +++ /dev/null @@ -1,15 +0,0 @@ -package fr.codecake.airbnbclone.listing.application.dto.sub; - -import fr.codecake.airbnbclone.listing.application.dto.vo.BathsVO; -import fr.codecake.airbnbclone.listing.application.dto.vo.BedroomsVO; -import fr.codecake.airbnbclone.listing.application.dto.vo.BedsVO; -import fr.codecake.airbnbclone.listing.application.dto.vo.GuestsVO; -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotNull; - -public record ListingInfoDTO( - @NotNull @Valid GuestsVO guests, - @NotNull @Valid BedroomsVO bedrooms, - @NotNull @Valid BedsVO beds, - @NotNull @Valid BathsVO baths) { -} diff --git a/src/main/java/fr/codecake/airbnbclone/user/application/Auth0Service.java b/src/main/java/fr/codecake/airbnbclone/user/application/Auth0Service.java deleted file mode 100644 index 8f7d2dc..0000000 --- a/src/main/java/fr/codecake/airbnbclone/user/application/Auth0Service.java +++ /dev/null @@ -1,61 +0,0 @@ -package fr.codecake.airbnbclone.user.application; - -import com.auth0.client.auth.AuthAPI; -import com.auth0.client.mgmt.ManagementAPI; -import com.auth0.client.mgmt.filter.FieldsFilter; -import com.auth0.exception.Auth0Exception; -import com.auth0.json.auth.TokenHolder; -import com.auth0.json.mgmt.users.User; -import com.auth0.net.Response; -import com.auth0.net.TokenRequest; -import fr.codecake.airbnbclone.infrastructure.config.SecurityUtils; -import fr.codecake.airbnbclone.user.application.dto.ReadUserDTO; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; - -import java.util.List; -import java.util.UUID; - -@Service -public class Auth0Service { - - @Value("${okta.oauth2.client-id}") - private String clientId; - - @Value("${okta.oauth2.client-secret}") - private String clientSecret; - - @Value("${okta.oauth2.issuer}") - private String domain; - - @Value("${application.auth0.role-landlord-id}") - private String roleLandlordId; - - public void addLandlordRoleToUser(ReadUserDTO readUserDTO) { - if (readUserDTO.authorities().stream().noneMatch(role -> role.equals(SecurityUtils.ROLE_LANDLORD))) { - try { - String accessToken = this.getAccessToken(); - assignRoleById(accessToken, readUserDTO.email(), readUserDTO.publicId(), roleLandlordId); - } catch (Auth0Exception a) { - throw new UserException(String.format("not possible to assign %s to %s", roleLandlordId, readUserDTO.publicId())); - } - } - } - - private void assignRoleById(String accessToken, String email, UUID publicId, String roleIdToAdd) throws Auth0Exception { - ManagementAPI mgmt = ManagementAPI.newBuilder(domain, accessToken).build(); - Response> auth0userByEmail = mgmt.users().listByEmail(email, new FieldsFilter()).execute(); - User user = auth0userByEmail.getBody() - .stream().findFirst() - .orElseThrow(() -> new UserException(String.format("Cannot find user with public id %s", publicId))); - mgmt.roles().assignUsers(roleIdToAdd, List.of(user.getId())).execute(); - } - - private String getAccessToken() throws Auth0Exception { - AuthAPI authAPI = AuthAPI.newBuilder(domain, clientId, clientSecret).build(); - TokenRequest tokenRequest = authAPI.requestToken(domain + "api/v2/"); - TokenHolder holder = tokenRequest.execute().getBody(); - return holder.getAccessToken(); - } - -} diff --git a/src/main/java/fr/codecake/airbnbclone/user/application/UserService.java b/src/main/java/fr/codecake/airbnbclone/user/application/UserService.java deleted file mode 100644 index cd3a5f8..0000000 --- a/src/main/java/fr/codecake/airbnbclone/user/application/UserService.java +++ /dev/null @@ -1,83 +0,0 @@ -package fr.codecake.airbnbclone.user.application; - -import fr.codecake.airbnbclone.infrastructure.config.SecurityUtils; -import fr.codecake.airbnbclone.user.application.dto.ReadUserDTO; -import fr.codecake.airbnbclone.user.domain.User; -import fr.codecake.airbnbclone.user.mapper.UserMapper; -import fr.codecake.airbnbclone.user.repository.UserRepository; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.time.Instant; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; - -@Service -public class UserService { - - private static final String UPDATED_AT_KEY = "updated_at"; - private final UserRepository userRepository; - private final UserMapper userMapper; - - public UserService(UserRepository userRepository, UserMapper userMapper) { - this.userRepository = userRepository; - this.userMapper = userMapper; - } - - @Transactional(readOnly = true) - public ReadUserDTO getAuthenticatedUserFromSecurityContext() { - OAuth2User principal = (OAuth2User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); - User user = SecurityUtils.mapOauth2AttributesToUser(principal.getAttributes()); - return getByEmail(user.getEmail()).orElseThrow(); - } - - @Transactional(readOnly = true) - public Optional getByEmail(String email) { - Optional oneByEmail = userRepository.findOneByEmail(email); - return oneByEmail.map(userMapper::userToReadUserDTO); - } - - public void syncWithIdp(OAuth2User oAuth2User, boolean forceResync) { - Map attributes = oAuth2User.getAttributes(); - User user = SecurityUtils.mapOauth2AttributesToUser(attributes); - Optional existingUser = userRepository.findOneByEmail(user.getEmail()); - if (existingUser.isPresent()) { - if (attributes.get(UPDATED_AT_KEY) != null) { - Instant lastModifiedDate = existingUser.orElseThrow().getLastModifiedDate(); - Instant idpModifiedDate; - if (attributes.get(UPDATED_AT_KEY) instanceof Instant instant) { - idpModifiedDate = instant; - } else { - idpModifiedDate = Instant.ofEpochSecond((Integer) attributes.get(UPDATED_AT_KEY)); - } - if (idpModifiedDate.isAfter(lastModifiedDate) || forceResync) { - updateUser(user); - } - } - } else { - userRepository.saveAndFlush(user); - } - } - - private void updateUser(User user) { - Optional userToUpdateOpt = userRepository.findOneByEmail(user.getEmail()); - if (userToUpdateOpt.isPresent()) { - User userToUpdate = userToUpdateOpt.get(); - userToUpdate.setEmail(user.getEmail()); - userToUpdate.setFirstName(user.getFirstName()); - userToUpdate.setLastName(user.getLastName()); - userToUpdate.setAuthorities(user.getAuthorities()); - userToUpdate.setImageUrl(user.getImageUrl()); - userRepository.saveAndFlush(userToUpdate); - } - } - - public Optional getByPublicId(UUID publicId) { - Optional oneByPublicId = userRepository.findOneByPublicId(publicId); - return oneByPublicId.map(userMapper::userToReadUserDTO); - } - -} diff --git a/src/main/java/fr/codecake/airbnbclone/user/presentation/AuthResource.java b/src/main/java/fr/codecake/airbnbclone/user/presentation/AuthResource.java deleted file mode 100644 index 518f52d..0000000 --- a/src/main/java/fr/codecake/airbnbclone/user/presentation/AuthResource.java +++ /dev/null @@ -1,52 +0,0 @@ -package fr.codecake.airbnbclone.user.presentation; - -import fr.codecake.airbnbclone.user.application.UserService; -import fr.codecake.airbnbclone.user.application.dto.ReadUserDTO; -import jakarta.servlet.http.HttpServletRequest; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.web.bind.annotation.*; - -import java.text.MessageFormat; -import java.util.Map; - -@RestController -@RequestMapping("/api/auth") -public class AuthResource { - - private final UserService userService; - - private final ClientRegistration registration; - - public AuthResource(UserService userService, ClientRegistrationRepository registration) { - this.userService = userService; - this.registration = registration.findByRegistrationId("okta"); - } - - @GetMapping("/get-authenticated-user") - public ResponseEntity getAuthenticatedUser( - @AuthenticationPrincipal OAuth2User user, @RequestParam boolean forceResync) { - if(user == null) { - return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); - } else { - userService.syncWithIdp(user, forceResync); - ReadUserDTO connectedUser = userService.getAuthenticatedUserFromSecurityContext(); - return new ResponseEntity<>(connectedUser, HttpStatus.OK); - } - } - - @PostMapping("/logout") - public ResponseEntity> logout(HttpServletRequest request) { - String issuerUri = registration.getProviderDetails().getIssuerUri(); - String originUrl = request.getHeader(HttpHeaders.ORIGIN); - Object[] params = {issuerUri, registration.getClientId(), originUrl}; - String logoutUrl = MessageFormat.format("{0}v2/logout?client_id={1}&returnTo={2}", params); - request.getSession().invalidate(); - return ResponseEntity.ok().body(Map.of("logoutUrl", logoutUrl)); - } -} diff --git a/src/main/java/fr/codecake/airbnbclone/NbatCloneBackApplication.java b/src/main/java/fr/codecake/nbat/NbatCloneBackApplication.java similarity index 90% rename from src/main/java/fr/codecake/airbnbclone/NbatCloneBackApplication.java rename to src/main/java/fr/codecake/nbat/NbatCloneBackApplication.java index 4d00e1a..cb64bb1 100644 --- a/src/main/java/fr/codecake/airbnbclone/NbatCloneBackApplication.java +++ b/src/main/java/fr/codecake/nbat/NbatCloneBackApplication.java @@ -1,4 +1,4 @@ -package fr.codecake.airbnbclone; +package fr.codecake.nbat; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/src/main/java/fr/codecake/airbnbclone/booking/application/BookingService.java b/src/main/java/fr/codecake/nbat/booking/application/BookingService.java similarity index 57% rename from src/main/java/fr/codecake/airbnbclone/booking/application/BookingService.java rename to src/main/java/fr/codecake/nbat/booking/application/BookingService.java index 3d13795..58d17dc 100644 --- a/src/main/java/fr/codecake/airbnbclone/booking/application/BookingService.java +++ b/src/main/java/fr/codecake/nbat/booking/application/BookingService.java @@ -1,19 +1,19 @@ -package fr.codecake.airbnbclone.booking.application; - -import fr.codecake.airbnbclone.booking.application.dto.BookedDateDTO; -import fr.codecake.airbnbclone.booking.application.dto.BookedListingDTO; -import fr.codecake.airbnbclone.booking.application.dto.NewBookingDTO; -import fr.codecake.airbnbclone.booking.domain.Booking; -import fr.codecake.airbnbclone.booking.mapper.BookingMapper; -import fr.codecake.airbnbclone.booking.repository.BookingRepository; -import fr.codecake.airbnbclone.infrastructure.config.SecurityUtils; -import fr.codecake.airbnbclone.listing.application.LandlordService; -import fr.codecake.airbnbclone.listing.application.dto.DisplayCardListingDTO; -import fr.codecake.airbnbclone.listing.application.dto.ListingCreateBookingDTO; -import fr.codecake.airbnbclone.listing.application.dto.vo.PriceVO; -import fr.codecake.airbnbclone.sharedkernel.service.State; -import fr.codecake.airbnbclone.user.application.UserService; -import fr.codecake.airbnbclone.user.application.dto.ReadUserDTO; +package fr.codecake.nbat.booking.application; + +import fr.codecake.nbat.booking.application.dto.BookedDateDTO; +import fr.codecake.nbat.booking.application.dto.BookedListingDTO; +import fr.codecake.nbat.booking.application.dto.NewBookingDTO; +import fr.codecake.nbat.booking.domain.Booking; +import fr.codecake.nbat.booking.mapper.BookingMapper; +import fr.codecake.nbat.booking.repository.BookingRepository; +import fr.codecake.nbat.infrastructure.config.SecurityUtils; +import fr.codecake.nbat.listing.application.LandlordService; +import fr.codecake.nbat.listing.application.dto.DisplayCardListingDTO; +import fr.codecake.nbat.listing.application.dto.ListingCreateBookingDTO; +import fr.codecake.nbat.listing.application.dto.vo.PriceVO; +import fr.codecake.nbat.sharedkernel.service.State; +import fr.codecake.nbat.user.application.UserService; +import fr.codecake.nbat.user.application.dto.ReadUserDTO; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -31,7 +31,7 @@ public class BookingService { private final LandlordService landlordService; public BookingService(BookingRepository bookingRepository, BookingMapper bookingMapper, - UserService userService, LandlordService landlordService) { + UserService userService, LandlordService landlordService) { this.bookingRepository = bookingRepository; this.bookingMapper = bookingMapper; this.userService = userService; @@ -42,13 +42,15 @@ public BookingService(BookingRepository bookingRepository, BookingMapper booking public State create(NewBookingDTO newBookingDTO) { Booking booking = bookingMapper.newBookingToBooking(newBookingDTO); - Optional listingOpt = landlordService.getByListingPublicId(newBookingDTO.listingPublicId()); + Optional listingOpt = landlordService + .getByListingPublicId(newBookingDTO.listingPublicId()); if (listingOpt.isEmpty()) { return State.builder().forError("Landlord public id not found"); } - boolean alreadyBooked = bookingRepository.bookingExistsAtInterval(newBookingDTO.startDate(), newBookingDTO.endDate(), newBookingDTO.listingPublicId()); + boolean alreadyBooked = bookingRepository.bookingExistsAtInterval(newBookingDTO.startDate(), + newBookingDTO.endDate(), newBookingDTO.listingPublicId()); if (alreadyBooked) { return State.builder().forError("One booking already exists"); @@ -58,7 +60,7 @@ public State create(NewBookingDTO newBookingDTO) { booking.setFkListing(listingCreateBookingDTO.listingPublicId()); - ReadUserDTO connectedUser = userService.getAuthenticatedUserFromSecurityContext(); + ReadUserDTO connectedUser = userService.getAuthenticatedUserFromDatabaseUsingSecurityContextEmail(); booking.setFkTenant(connectedUser.publicId()); booking.setNumberOfTravelers(1); @@ -78,39 +80,47 @@ public List checkAvailability(UUID publicId) { @Transactional(readOnly = true) public List getBookedListing() { - ReadUserDTO connectedUser = userService.getAuthenticatedUserFromSecurityContext(); + ReadUserDTO connectedUser = userService.getAuthenticatedUserFromDatabaseUsingSecurityContextEmail(); List allBookings = bookingRepository.findAllByFkTenant(connectedUser.publicId()); List allListingPublicIDs = allBookings.stream().map(Booking::getFkListing).toList(); List allListings = landlordService.getCardDisplayByListingPublicId(allListingPublicIDs); return mapBookingToBookedListing(allBookings, allListings); } - private List mapBookingToBookedListing(List allBookings, List allListings) { - return allBookings.stream().map(booking -> { - DisplayCardListingDTO displayCardListingDTO = allListings - .stream() - .filter(listing -> listing.publicId().equals(booking.getFkListing())) - .findFirst() - .orElseThrow(); - BookedDateDTO dates = bookingMapper.bookingToCheckAvailability(booking); - return new BookedListingDTO(displayCardListingDTO.cover(), - displayCardListingDTO.location(), - dates, new PriceVO(booking.getTotalPrice()), - booking.getPublicId(), displayCardListingDTO.publicId()); - }).toList(); + private List mapBookingToBookedListing(List allBookings, + List allListings) { + return allBookings.stream() + .map(booking -> { + // Try to find the listing for this booking + Optional listingOpt = allListings + .stream() + .filter(listing -> listing.publicId().equals(booking.getFkListing())) + .findFirst(); + + // If listing doesn't exist (maybe it was deleted), skip this booking + if (listingOpt.isEmpty()) { + return null; + } + + DisplayCardListingDTO displayCardListingDTO = listingOpt.get(); + BookedDateDTO dates = bookingMapper.bookingToCheckAvailability(booking); + return new BookedListingDTO(displayCardListingDTO.cover(), + displayCardListingDTO.location(), + dates, new PriceVO(booking.getTotalPrice()), + booking.getPublicId(), displayCardListingDTO.publicId()); + }) + .filter(bookedListing -> bookedListing != null) // Remove any null entries + .toList(); } + // Service @Transactional - public State cancel(UUID bookingPublicId, UUID listingPublicId, boolean byLandlord) { - ReadUserDTO connectedUser = userService.getAuthenticatedUserFromSecurityContext(); - int deleteSuccess = 0; + public State cancel(UUID bookingPublicId) { + ReadUserDTO connectedUser = userService.getAuthenticatedUserFromDatabaseUsingSecurityContextEmail(); - if (SecurityUtils.hasCurrentUserAnyOfAuthorities(SecurityUtils.ROLE_LANDLORD) - && byLandlord) { - deleteSuccess = handleDeletionForLandlord(bookingPublicId, listingPublicId, connectedUser, deleteSuccess); - } else { - deleteSuccess = bookingRepository.deleteBookingByFkTenantAndPublicId(connectedUser.publicId(), bookingPublicId); - } + int deleteSuccess = bookingRepository.deleteBookingByFkTenantAndPublicId( + connectedUser.publicId(), + bookingPublicId); if (deleteSuccess >= 1) { return State.builder().forSuccess(bookingPublicId); @@ -119,17 +129,20 @@ public State cancel(UUID bookingPublicId, UUID listingPublicId, bo } } - private int handleDeletionForLandlord(UUID bookingPublicId, UUID listingPublicId, ReadUserDTO connectedUser, int deleteSuccess) { - Optional listingVerificationOpt = landlordService.getByPublicIdAndLandlordPublicId(listingPublicId, connectedUser.publicId()); + private int handleDeletionForLandlord(UUID bookingPublicId, UUID listingPublicId, ReadUserDTO connectedUser, + int deleteSuccess) { + Optional listingVerificationOpt = landlordService + .getByPublicIdAndLandlordPublicId(listingPublicId, connectedUser.publicId()); if (listingVerificationOpt.isPresent()) { - deleteSuccess = bookingRepository.deleteBookingByPublicIdAndFkListing(bookingPublicId, listingVerificationOpt.get().publicId()); + deleteSuccess = bookingRepository.deleteBookingByPublicIdAndFkListing(bookingPublicId, + listingVerificationOpt.get().publicId()); } return deleteSuccess; } @Transactional(readOnly = true) public List getBookedListingForLandlord() { - ReadUserDTO connectedUser = userService.getAuthenticatedUserFromSecurityContext(); + ReadUserDTO connectedUser = userService.getAuthenticatedUserFromDatabaseUsingSecurityContextEmail(); List allProperties = landlordService.getAllProperties(connectedUser); List allPropertyPublicIds = allProperties.stream().map(DisplayCardListingDTO::publicId).toList(); List allBookings = bookingRepository.findAllByFkListingIn(allPropertyPublicIds); diff --git a/src/main/java/fr/codecake/airbnbclone/booking/application/dto/BookedDateDTO.java b/src/main/java/fr/codecake/nbat/booking/application/dto/BookedDateDTO.java similarity index 77% rename from src/main/java/fr/codecake/airbnbclone/booking/application/dto/BookedDateDTO.java rename to src/main/java/fr/codecake/nbat/booking/application/dto/BookedDateDTO.java index d1896bb..0b21546 100644 --- a/src/main/java/fr/codecake/airbnbclone/booking/application/dto/BookedDateDTO.java +++ b/src/main/java/fr/codecake/nbat/booking/application/dto/BookedDateDTO.java @@ -1,4 +1,4 @@ -package fr.codecake.airbnbclone.booking.application.dto; +package fr.codecake.nbat.booking.application.dto; import java.time.OffsetDateTime; diff --git a/src/main/java/fr/codecake/airbnbclone/booking/application/dto/BookedListingDTO.java b/src/main/java/fr/codecake/nbat/booking/application/dto/BookedListingDTO.java similarity index 72% rename from src/main/java/fr/codecake/airbnbclone/booking/application/dto/BookedListingDTO.java rename to src/main/java/fr/codecake/nbat/booking/application/dto/BookedListingDTO.java index c97977e..c858393 100644 --- a/src/main/java/fr/codecake/airbnbclone/booking/application/dto/BookedListingDTO.java +++ b/src/main/java/fr/codecake/nbat/booking/application/dto/BookedListingDTO.java @@ -1,7 +1,7 @@ -package fr.codecake.airbnbclone.booking.application.dto; +package fr.codecake.nbat.booking.application.dto; -import fr.codecake.airbnbclone.listing.application.dto.sub.PictureDTO; -import fr.codecake.airbnbclone.listing.application.dto.vo.PriceVO; +import fr.codecake.nbat.listing.application.dto.sub.PictureDTO; +import fr.codecake.nbat.listing.application.dto.vo.PriceVO; import jakarta.validation.Valid; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/fr/codecake/airbnbclone/booking/application/dto/NewBookingDTO.java b/src/main/java/fr/codecake/nbat/booking/application/dto/NewBookingDTO.java similarity index 82% rename from src/main/java/fr/codecake/airbnbclone/booking/application/dto/NewBookingDTO.java rename to src/main/java/fr/codecake/nbat/booking/application/dto/NewBookingDTO.java index 14eb439..fbba1fb 100644 --- a/src/main/java/fr/codecake/airbnbclone/booking/application/dto/NewBookingDTO.java +++ b/src/main/java/fr/codecake/nbat/booking/application/dto/NewBookingDTO.java @@ -1,4 +1,4 @@ -package fr.codecake.airbnbclone.booking.application.dto; +package fr.codecake.nbat.booking.application.dto; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/fr/codecake/airbnbclone/booking/domain/Booking.java b/src/main/java/fr/codecake/nbat/booking/domain/Booking.java similarity index 96% rename from src/main/java/fr/codecake/airbnbclone/booking/domain/Booking.java rename to src/main/java/fr/codecake/nbat/booking/domain/Booking.java index dcddf40..d7f0706 100644 --- a/src/main/java/fr/codecake/airbnbclone/booking/domain/Booking.java +++ b/src/main/java/fr/codecake/nbat/booking/domain/Booking.java @@ -1,6 +1,6 @@ -package fr.codecake.airbnbclone.booking.domain; +package fr.codecake.nbat.booking.domain; -import fr.codecake.airbnbclone.sharedkernel.domain.AbstractAuditingEntity; +import fr.codecake.nbat.sharedkernel.domain.AbstractAuditingEntity; import jakarta.persistence.*; import org.hibernate.annotations.UuidGenerator; diff --git a/src/main/java/fr/codecake/airbnbclone/booking/mapper/BookingMapper.java b/src/main/java/fr/codecake/nbat/booking/mapper/BookingMapper.java similarity index 75% rename from src/main/java/fr/codecake/airbnbclone/booking/mapper/BookingMapper.java rename to src/main/java/fr/codecake/nbat/booking/mapper/BookingMapper.java index 5969df9..b35a907 100644 --- a/src/main/java/fr/codecake/airbnbclone/booking/mapper/BookingMapper.java +++ b/src/main/java/fr/codecake/nbat/booking/mapper/BookingMapper.java @@ -1,11 +1,11 @@ -package fr.codecake.airbnbclone.booking.mapper; +package fr.codecake.nbat.booking.mapper; import org.mapstruct.Mapper; import org.mapstruct.Mapping; -import fr.codecake.airbnbclone.booking.application.dto.BookedDateDTO; -import fr.codecake.airbnbclone.booking.application.dto.NewBookingDTO; -import fr.codecake.airbnbclone.booking.domain.Booking; +import fr.codecake.nbat.booking.application.dto.BookedDateDTO; +import fr.codecake.nbat.booking.application.dto.NewBookingDTO; +import fr.codecake.nbat.booking.domain.Booking; @Mapper(componentModel = "spring") public interface BookingMapper { @@ -20,4 +20,4 @@ public interface BookingMapper { Booking newBookingToBooking(NewBookingDTO newBookingDTO); BookedDateDTO bookingToCheckAvailability(Booking booking); -} \ No newline at end of file +} diff --git a/src/main/java/fr/codecake/airbnbclone/booking/presentation/BookingResource.java b/src/main/java/fr/codecake/nbat/booking/presentation/BookingResource.java similarity index 72% rename from src/main/java/fr/codecake/airbnbclone/booking/presentation/BookingResource.java rename to src/main/java/fr/codecake/nbat/booking/presentation/BookingResource.java index 87a2b2c..9ed258d 100644 --- a/src/main/java/fr/codecake/airbnbclone/booking/presentation/BookingResource.java +++ b/src/main/java/fr/codecake/nbat/booking/presentation/BookingResource.java @@ -1,4 +1,4 @@ -package fr.codecake.airbnbclone.booking.presentation; +package fr.codecake.nbat.booking.presentation; import java.util.List; import java.util.UUID; @@ -16,18 +16,18 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import fr.codecake.airbnbclone.booking.application.BookingService; -import fr.codecake.airbnbclone.booking.application.dto.BookedDateDTO; -import fr.codecake.airbnbclone.booking.application.dto.BookedListingDTO; -import fr.codecake.airbnbclone.booking.application.dto.NewBookingDTO; -import fr.codecake.airbnbclone.infrastructure.config.SecurityUtils; -import fr.codecake.airbnbclone.sharedkernel.service.State; -import fr.codecake.airbnbclone.sharedkernel.service.StatusNotification; +import fr.codecake.nbat.booking.application.BookingService; +import fr.codecake.nbat.booking.application.dto.BookedDateDTO; +import fr.codecake.nbat.booking.application.dto.BookedListingDTO; +import fr.codecake.nbat.booking.application.dto.NewBookingDTO; +import fr.codecake.nbat.infrastructure.config.SecurityUtils; +import fr.codecake.nbat.sharedkernel.service.State; +import fr.codecake.nbat.sharedkernel.service.StatusNotification; import jakarta.validation.Valid; @RestController @RequestMapping("/api/booking") -@CrossOrigin(origins = "http://localhost:4200") +@CrossOrigin(origins = { "http://localhost:4200", "http://localhost:4201" }, allowCredentials = "true") public class BookingResource { private final BookingService bookingService; @@ -40,7 +40,8 @@ public BookingResource(BookingService bookingService) { public ResponseEntity create(@Valid @RequestBody NewBookingDTO newBookingDTO) { State createState = bookingService.create(newBookingDTO); if (createState.getStatus().equals(StatusNotification.ERROR)) { - ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, "createState.getError()"); + ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, + "createState.getError()"); return ResponseEntity.of(problemDetail).build(); } else { return ResponseEntity.ok().build(); @@ -58,12 +59,12 @@ public ResponseEntity> getBookedListing() { } @DeleteMapping("cancel") - public ResponseEntity cancel(@RequestParam UUID bookingPublicId, - @RequestParam UUID listingPublicId, - @RequestParam boolean byLandlord) { - State cancelState = bookingService.cancel(bookingPublicId, listingPublicId, byLandlord); + @PreAuthorize("hasRole('ROLE_TENANT')") + public ResponseEntity cancel(@RequestParam UUID bookingPublicId) { + State cancelState = bookingService.cancel(bookingPublicId); if (cancelState.getStatus().equals(StatusNotification.ERROR)) { - ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, "cancelState.getError()"); + ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, + cancelState.getError()); return ResponseEntity.of(problemDetail).build(); } else { return ResponseEntity.ok(bookingPublicId); @@ -71,7 +72,7 @@ public ResponseEntity cancel(@RequestParam UUID bookingPublicId, } @GetMapping("get-booked-listing-for-landlord") - @PreAuthorize("hasAnyRole('" + SecurityUtils.ROLE_LANDLORD + "')") + @PreAuthorize("hasRole('ROLE_LANDLORD')") public ResponseEntity> getBookedListingForLandlord() { return ResponseEntity.ok(bookingService.getBookedListingForLandlord()); } diff --git a/src/main/java/fr/codecake/airbnbclone/booking/repository/BookingRepository.java b/src/main/java/fr/codecake/nbat/booking/repository/BookingRepository.java similarity index 92% rename from src/main/java/fr/codecake/airbnbclone/booking/repository/BookingRepository.java rename to src/main/java/fr/codecake/nbat/booking/repository/BookingRepository.java index f90d3bc..b51db15 100644 --- a/src/main/java/fr/codecake/airbnbclone/booking/repository/BookingRepository.java +++ b/src/main/java/fr/codecake/nbat/booking/repository/BookingRepository.java @@ -1,6 +1,6 @@ -package fr.codecake.airbnbclone.booking.repository; +package fr.codecake.nbat.booking.repository; -import fr.codecake.airbnbclone.booking.domain.Booking; +import fr.codecake.nbat.booking.domain.Booking; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; diff --git a/src/main/java/fr/codecake/airbnbclone/infrastructure/config/CustomAuthorizationRequestResolver.java b/src/main/java/fr/codecake/nbat/infrastructure/config/CustomAuthorizationRequestResolver.java similarity index 97% rename from src/main/java/fr/codecake/airbnbclone/infrastructure/config/CustomAuthorizationRequestResolver.java rename to src/main/java/fr/codecake/nbat/infrastructure/config/CustomAuthorizationRequestResolver.java index 975b44c..0d183d3 100644 --- a/src/main/java/fr/codecake/airbnbclone/infrastructure/config/CustomAuthorizationRequestResolver.java +++ b/src/main/java/fr/codecake/nbat/infrastructure/config/CustomAuthorizationRequestResolver.java @@ -1,4 +1,4 @@ -package fr.codecake.airbnbclone.infrastructure.config; +package fr.codecake.nbat.infrastructure.config; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver; diff --git a/src/main/java/fr/codecake/nbat/infrastructure/config/DatabaseAuthoritiesFilter.java b/src/main/java/fr/codecake/nbat/infrastructure/config/DatabaseAuthoritiesFilter.java new file mode 100644 index 0000000..23afaa6 --- /dev/null +++ b/src/main/java/fr/codecake/nbat/infrastructure/config/DatabaseAuthoritiesFilter.java @@ -0,0 +1,76 @@ +package fr.codecake.nbat.infrastructure.config; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.web.filter.OncePerRequestFilter; +import fr.codecake.nbat.user.application.UserService; +import fr.codecake.nbat.user.application.dto.ReadUserDTO; + +import java.io.IOException; +import java.util.Collection; +import java.util.stream.Collectors; + +/** + * Filter that reloads user authorities from the database for each authenticated + * request. + * This allows roles assigned after OAuth2 login to be recognized by Spring + * Security. + */ +public class DatabaseAuthoritiesFilter extends OncePerRequestFilter { + + private final UserService userService; + + public DatabaseAuthoritiesFilter(UserService userService) { + this.userService = userService; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + + System.out.println( + "🔍 DatabaseAuthoritiesFilter - Request: " + request.getMethod() + " " + request.getRequestURI()); + System.out.println("🔍 Auth before filter: " + (auth != null ? auth.getAuthorities() : "null")); + + // Only process authenticated requests with OAuth2 principal + if (auth != null && auth.isAuthenticated() && auth.getPrincipal() instanceof OAuth2User oAuth2User) { + try { + // Get user's current roles from database + ReadUserDTO userFromDb = userService.getAuthenticatedUserFromDatabaseUsingSecurityContextEmail(); + + if (userFromDb != null && userFromDb.authorities() != null) { + System.out.println("🔍 Authorities from DB: " + userFromDb.authorities()); + + // Convert authorities from DB to GrantedAuthority + Collection dbAuthorities = userFromDb.authorities() + .stream() + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); + + // Create a new authentication with updated authorities from DB + OAuth2AuthenticationWithDbAuthorities updatedAuth = new OAuth2AuthenticationWithDbAuthorities(auth, + dbAuthorities); + + // Update the security context with the new authentication + SecurityContextHolder.getContext().setAuthentication(updatedAuth); + + System.out.println("✅ Auth after filter: " + updatedAuth.getAuthorities()); + } + } catch (Exception e) { + // Log but don't fail - continue with original authentication + System.err.println("⚠️ Failed to reload authorities from database: " + e.getMessage()); + e.printStackTrace(); + } + } + + filterChain.doFilter(request, response); + } +} \ No newline at end of file diff --git a/src/main/java/fr/codecake/airbnbclone/infrastructure/config/DatabaseConfiguration.java b/src/main/java/fr/codecake/nbat/infrastructure/config/DatabaseConfiguration.java similarity index 63% rename from src/main/java/fr/codecake/airbnbclone/infrastructure/config/DatabaseConfiguration.java rename to src/main/java/fr/codecake/nbat/infrastructure/config/DatabaseConfiguration.java index 8db691c..bc14141 100644 --- a/src/main/java/fr/codecake/airbnbclone/infrastructure/config/DatabaseConfiguration.java +++ b/src/main/java/fr/codecake/nbat/infrastructure/config/DatabaseConfiguration.java @@ -1,4 +1,4 @@ -package fr.codecake.airbnbclone.infrastructure.config; +package fr.codecake.nbat.infrastructure.config; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @@ -6,9 +6,9 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; @Configuration -@EnableJpaRepositories({"fr.codecake.airbnbclone.user.repository", - "fr.codecake.airbnbclone.listing.repository", - "fr.codecake.airbnbclone.booking.repository"}) +@EnableJpaRepositories({"fr.codecake.nbat.user.repository", + "fr.codecake.nbat.listing.repository", + "fr.codecake.nbat.booking.repository"}) @EnableTransactionManagement @EnableJpaAuditing public class DatabaseConfiguration { diff --git a/src/main/java/fr/codecake/nbat/infrastructure/config/OAuth2AuthenticationWithDbAuthorities.java b/src/main/java/fr/codecake/nbat/infrastructure/config/OAuth2AuthenticationWithDbAuthorities.java new file mode 100644 index 0000000..4cc4f7b --- /dev/null +++ b/src/main/java/fr/codecake/nbat/infrastructure/config/OAuth2AuthenticationWithDbAuthorities.java @@ -0,0 +1,29 @@ +package fr.codecake.nbat.infrastructure.config; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; + +import java.util.Collection; + +/** + * Wrapper for OAuth2AuthenticationToken that uses authorities from the database + * instead of the OAuth2 token. + */ +public class OAuth2AuthenticationWithDbAuthorities extends OAuth2AuthenticationToken { + + private final Collection dbAuthorities; + + public OAuth2AuthenticationWithDbAuthorities(Authentication original, Collection dbAuthorities) { + super( + ((OAuth2AuthenticationToken) original).getPrincipal(), + dbAuthorities, + ((OAuth2AuthenticationToken) original).getAuthorizedClientRegistrationId()); + this.dbAuthorities = dbAuthorities; + } + + @Override + public Collection getAuthorities() { + return this.dbAuthorities; + } +} diff --git a/src/main/java/fr/codecake/nbat/infrastructure/config/OAuth2LoginSuccessHandler.java b/src/main/java/fr/codecake/nbat/infrastructure/config/OAuth2LoginSuccessHandler.java new file mode 100644 index 0000000..7553770 --- /dev/null +++ b/src/main/java/fr/codecake/nbat/infrastructure/config/OAuth2LoginSuccessHandler.java @@ -0,0 +1,52 @@ +package fr.codecake.nbat.infrastructure.config; + +import fr.codecake.nbat.user.application.UserService; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +public class OAuth2LoginSuccessHandler implements AuthenticationSuccessHandler { + + private final UserService userService; + + public OAuth2LoginSuccessHandler(UserService userService) { + this.userService = userService; + } + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException, ServletException { + try { + System.out.println("🔐 OAuth2 Login Success - Syncing user with IdP"); + System.out.println("📱 Authentication type: " + authentication.getClass().getSimpleName()); + + // Get the OAuth2User from authentication + OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); + System.out.println("👤 OAuth2User attributes: " + oAuth2User.getAttributes().keySet()); + + // Sync with IdP (creates new user with ROLE_TENANT or updates existing user) + System.out.println("🔄 Starting syncWithIdp..."); + userService.syncWithIdp(oAuth2User); + + System.out.println("✅ User synced successfully"); + + // Redirect to frontend or default landing page + response.sendRedirect("/"); + + } catch (Exception e) { + System.err.println("❌ ERROR in OAuth2LoginSuccessHandler: " + e.getMessage()); + System.err.println("❌ Exception type: " + e.getClass().getSimpleName()); + e.printStackTrace(); + + // Don't hide the error - let Spring handle it + throw new ServletException("Failed to sync user: " + e.getMessage(), e); + } + } +} diff --git a/src/main/java/fr/codecake/nbat/infrastructure/config/SecurityConfiguration.java b/src/main/java/fr/codecake/nbat/infrastructure/config/SecurityConfiguration.java new file mode 100644 index 0000000..5291b09 --- /dev/null +++ b/src/main/java/fr/codecake/nbat/infrastructure/config/SecurityConfiguration.java @@ -0,0 +1,109 @@ +package fr.codecake.nbat.infrastructure.config; + +import java.util.HashSet; +import java.util.Set; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.csrf.CookieCsrfTokenRepository; +import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import fr.codecake.nbat.user.application.UserService; + +@Configuration +@EnableMethodSecurity +public class SecurityConfiguration { + + private final ClientRegistrationRepository clientRegistrationRepository; + private final OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler; + private final UserService userService; + + public SecurityConfiguration(ClientRegistrationRepository clientRegistrationRepository, + OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler, + UserService userService) { + this.clientRegistrationRepository = clientRegistrationRepository; + this.oAuth2LoginSuccessHandler = oAuth2LoginSuccessHandler; + this.userService = userService; + } + + @Bean + public SecurityFilterChain configure(HttpSecurity http) throws Exception { + CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler(); + requestHandler.setCsrfRequestAttributeName(null); + http.cors(cors -> cors.configurationSource(corsConfigurationSource())) + .authorizeHttpRequests(authorize -> authorize + .requestMatchers(HttpMethod.GET, "/api/auth/get-authenticated-user") + .permitAll() + .requestMatchers(HttpMethod.POST, "/api/auth/logout").permitAll() + .requestMatchers(HttpMethod.POST, "/api/auth/assign-landlord-role") + .permitAll() + .requestMatchers(HttpMethod.GET, + "/api/tenant-listing/get-all-by-category") + .permitAll() + .requestMatchers(HttpMethod.GET, "/api/tenant-listing/get-one") + .permitAll() + .requestMatchers(HttpMethod.POST, "/api/tenant-listing/search") + .permitAll() + .requestMatchers(HttpMethod.GET, "/api/booking/check-availability") + .permitAll() + .requestMatchers(HttpMethod.POST, "/api/booking/create").authenticated() + .requestMatchers(HttpMethod.GET, "/api/booking/get-booked-listing") + .authenticated() + .requestMatchers(HttpMethod.GET, + "/api/booking/get-booked-listing-for-landlord") + .authenticated() + .requestMatchers("/api/landlord-listing/**").authenticated() + .requestMatchers("/assets/**").permitAll() + .anyRequest() + .authenticated()) + .csrf(csrf -> csrf + // create the CSRF token and store in a cookie with name: XSRF-TOKEN + .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) + .csrfTokenRequestHandler(requestHandler) + .ignoringRequestMatchers( + "/api/auth/logout", + "/api/auth/assign-landlord-role", + "/api/tenant-listing/search")) + .oauth2Login(oauth2 -> oauth2 + .authorizationEndpoint(authorization -> authorization + .authorizationRequestResolver( + new CustomAuthorizationRequestResolver( + clientRegistrationRepository))) + .successHandler(oAuth2LoginSuccessHandler)) + .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())) + .oauth2Client(Customizer.withDefaults()) + .sessionManagement(session -> session + .sessionCreationPolicy( + org.springframework.security.config.http.SessionCreationPolicy.ALWAYS)) + .addFilterBefore(new DatabaseAuthoritiesFilter(userService), + org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter.class); + + return http.build(); + } + + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowedOrigins(java.util.List.of("http://localhost:4200", "http://localhost:4201")); + configuration.setAllowedMethods(java.util.List.of("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH")); + configuration.setAllowedHeaders(java.util.List.of("*")); + configuration.setExposedHeaders(java.util.List.of("Authorization", "X-Total-Count", "X-Page-Number")); + configuration.setAllowCredentials(true); + configuration.setMaxAge(3600L); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + return source; + } +} diff --git a/src/main/java/fr/codecake/nbat/infrastructure/config/SecurityUtils.java b/src/main/java/fr/codecake/nbat/infrastructure/config/SecurityUtils.java new file mode 100644 index 0000000..3a461db --- /dev/null +++ b/src/main/java/fr/codecake/nbat/infrastructure/config/SecurityUtils.java @@ -0,0 +1,8 @@ +package fr.codecake.nbat.infrastructure.config; + +public class SecurityUtils { + + public static String ROLE_TENANT = "ROLE_TENANT"; + public static String ROLE_LANDLORD = "ROLE_LANDLORD"; + +} diff --git a/src/main/java/fr/codecake/nbat/listing/application/LandlordService.java b/src/main/java/fr/codecake/nbat/listing/application/LandlordService.java new file mode 100644 index 0000000..a95c9d7 --- /dev/null +++ b/src/main/java/fr/codecake/nbat/listing/application/LandlordService.java @@ -0,0 +1,154 @@ +package fr.codecake.nbat.listing.application; + +import fr.codecake.nbat.listing.application.dto.CreatedListingDTO; +import fr.codecake.nbat.listing.application.dto.DisplayCardListingDTO; +import fr.codecake.nbat.listing.application.dto.ListingCreateBookingDTO; +import fr.codecake.nbat.listing.application.dto.SaveListingDTO; +import fr.codecake.nbat.listing.domain.Listing; +import fr.codecake.nbat.listing.mapper.ListingMapper; +import fr.codecake.nbat.listing.repository.ListingRepository; +import fr.codecake.nbat.sharedkernel.service.State; +import fr.codecake.nbat.user.application.Auth0Service; +import fr.codecake.nbat.user.application.UserService; +import fr.codecake.nbat.user.application.dto.ReadUserDTO; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Service +public class LandlordService { + + private final ListingRepository listingRepository; + + private final ListingMapper listingMapper; + private final UserService userService; + private final Auth0Service auth0Service; + private final PictureService pictureService; + + public LandlordService(ListingRepository listingRepository, ListingMapper listingMapper, UserService userService, + Auth0Service auth0Service, PictureService pictureService) { + this.listingRepository = listingRepository; + this.listingMapper = listingMapper; + this.userService = userService; + this.auth0Service = auth0Service; + this.pictureService = pictureService; + } + + public CreatedListingDTO create(SaveListingDTO saveListingDTO) { + Listing newListing = listingMapper.saveListingDTOToListing(saveListingDTO); + + ReadUserDTO userConnected = userService.getAuthenticatedUserFromDatabaseUsingSecurityContextEmail(); + newListing.setLandlordPublicId(userConnected.publicId()); + + Listing savedListing = listingRepository.saveAndFlush(newListing); + + pictureService.saveAll(saveListingDTO.getPictures(), savedListing); + + auth0Service.addLandlordRoleToUser(userConnected); + + return listingMapper.listingToCreatedListingDTO(savedListing); + } + + @Transactional(readOnly = true) + public List getAllProperties(ReadUserDTO landlord) { + List properties = listingRepository.findAllByLandlordPublicIdFetchCoverPicture(landlord.publicId()); + return listingMapper.listingToDisplayCardListingDTOs(properties); + } + + @Transactional + public State delete(UUID publicId, ReadUserDTO landlord) { + long deletedSuccessfuly = listingRepository.deleteByPublicIdAndLandlordPublicId(publicId, landlord.publicId()); + if (deletedSuccessfuly > 0) { + return State.builder().forSuccess(publicId); + } else { + return State.builder().forUnauthorized("User not authorized to delete this listing"); + } + } + + public Optional getByListingPublicId(UUID publicId) { + return listingRepository.findByPublicId(publicId).map(listingMapper::mapListingToListingCreateBookingDTO); + } + + public List getCardDisplayByListingPublicId(List allListingPublicIDs) { + return listingRepository.findAllByPublicIdIn(allListingPublicIDs) + .stream() + .map(listingMapper::listingToDisplayCardListingDTO) + .toList(); + } + + @Transactional(readOnly = true) + public Optional getByPublicIdAndLandlordPublicId(UUID listingPublicId, + UUID landlordPublicId) { + return listingRepository.findOneByPublicIdAndLandlordPublicId(listingPublicId, landlordPublicId) + .map(listingMapper::listingToDisplayCardListingDTO); + } + + @Transactional(readOnly = true) + public State getOne(UUID publicId, + ReadUserDTO landlord) { + Optional listingOpt = listingRepository.findByPublicId(publicId); + + if (listingOpt.isEmpty()) { + return State.builder() + .forError("Listing not found"); + } + + Listing listing = listingOpt.get(); + + // Check if listing belongs to this landlord + if (!listing.getLandlordPublicId().equals(landlord.publicId())) { + return State.builder() + .forUnauthorized("You are not authorized to view this listing"); + } + + fr.codecake.nbat.listing.application.dto.DisplayListingDTO displayDTO = listingMapper + .listingToDisplayListingDTO(listing); + + return State.builder() + .forSuccess(displayDTO); + } + + @Transactional + public CreatedListingDTO update(UUID publicId, SaveListingDTO saveListingDTO, ReadUserDTO landlord) { + Optional listingOpt = listingRepository.findByPublicId(publicId); + + if (listingOpt.isEmpty()) { + throw new IllegalArgumentException("Listing not found"); + } + + Listing listing = listingOpt.get(); + + // Check if listing belongs to this landlord + if (!listing.getLandlordPublicId().equals(landlord.publicId())) { + throw new IllegalArgumentException("You are not authorized to update this listing"); + } + + // Update listing properties from DTO + listing.setTitle(saveListingDTO.getDescription().title().value()); + listing.setDescription(saveListingDTO.getDescription().description().value()); + listing.setLocation(saveListingDTO.getLocation()); + listing.setPrice(saveListingDTO.getPrice().value()); + listing.setBookingCategory(saveListingDTO.getCategory()); + + // Update listing info + listing.setBedrooms(saveListingDTO.getInfos().bedrooms().value()); + listing.setGuests(saveListingDTO.getInfos().guests().value()); + listing.setBeds(saveListingDTO.getInfos().beds().value()); + listing.setBathrooms(saveListingDTO.getInfos().baths().value()); + + Listing updatedListing = listingRepository.saveAndFlush(listing); + + // Update pictures if new ones provided + if (!saveListingDTO.getPictures().isEmpty()) { + // Delete old pictures + listing.getPictures().clear(); + // Save new pictures + pictureService.saveAll(saveListingDTO.getPictures(), updatedListing); + } + + return listingMapper.listingToCreatedListingDTO(updatedListing); + } +} diff --git a/src/main/java/fr/codecake/airbnbclone/listing/application/PictureService.java b/src/main/java/fr/codecake/nbat/listing/application/PictureService.java similarity index 75% rename from src/main/java/fr/codecake/airbnbclone/listing/application/PictureService.java rename to src/main/java/fr/codecake/nbat/listing/application/PictureService.java index b32d38b..5626a44 100644 --- a/src/main/java/fr/codecake/airbnbclone/listing/application/PictureService.java +++ b/src/main/java/fr/codecake/nbat/listing/application/PictureService.java @@ -1,10 +1,10 @@ -package fr.codecake.airbnbclone.listing.application; +package fr.codecake.nbat.listing.application; -import fr.codecake.airbnbclone.listing.application.dto.sub.PictureDTO; -import fr.codecake.airbnbclone.listing.domain.Listing; -import fr.codecake.airbnbclone.listing.domain.ListingPicture; -import fr.codecake.airbnbclone.listing.mapper.ListingPictureMapper; -import fr.codecake.airbnbclone.listing.repository.ListingPictureRepository; +import fr.codecake.nbat.listing.application.dto.sub.PictureDTO; +import fr.codecake.nbat.listing.domain.Listing; +import fr.codecake.nbat.listing.domain.ListingPicture; +import fr.codecake.nbat.listing.mapper.ListingPictureMapper; +import fr.codecake.nbat.listing.repository.ListingPictureRepository; import org.springframework.stereotype.Service; import java.util.List; diff --git a/src/main/java/fr/codecake/airbnbclone/listing/application/TenantService.java b/src/main/java/fr/codecake/nbat/listing/application/TenantService.java similarity index 80% rename from src/main/java/fr/codecake/airbnbclone/listing/application/TenantService.java rename to src/main/java/fr/codecake/nbat/listing/application/TenantService.java index 8c3f8eb..646947a 100644 --- a/src/main/java/fr/codecake/airbnbclone/listing/application/TenantService.java +++ b/src/main/java/fr/codecake/nbat/listing/application/TenantService.java @@ -1,17 +1,17 @@ -package fr.codecake.airbnbclone.listing.application; - -import fr.codecake.airbnbclone.booking.application.BookingService; -import fr.codecake.airbnbclone.listing.application.dto.DisplayCardListingDTO; -import fr.codecake.airbnbclone.listing.application.dto.DisplayListingDTO; -import fr.codecake.airbnbclone.listing.application.dto.SearchDTO; -import fr.codecake.airbnbclone.listing.application.dto.sub.LandlordListingDTO; -import fr.codecake.airbnbclone.listing.domain.BookingCategory; -import fr.codecake.airbnbclone.listing.domain.Listing; -import fr.codecake.airbnbclone.listing.mapper.ListingMapper; -import fr.codecake.airbnbclone.listing.repository.ListingRepository; -import fr.codecake.airbnbclone.sharedkernel.service.State; -import fr.codecake.airbnbclone.user.application.UserService; -import fr.codecake.airbnbclone.user.application.dto.ReadUserDTO; +package fr.codecake.nbat.listing.application; + +import fr.codecake.nbat.booking.application.BookingService; +import fr.codecake.nbat.listing.application.dto.DisplayCardListingDTO; +import fr.codecake.nbat.listing.application.dto.DisplayListingDTO; +import fr.codecake.nbat.listing.application.dto.SearchDTO; +import fr.codecake.nbat.listing.application.dto.sub.LandlordListingDTO; +import fr.codecake.nbat.listing.domain.BookingCategory; +import fr.codecake.nbat.listing.domain.Listing; +import fr.codecake.nbat.listing.mapper.ListingMapper; +import fr.codecake.nbat.listing.repository.ListingRepository; +import fr.codecake.nbat.sharedkernel.service.State; +import fr.codecake.nbat.user.application.UserService; +import fr.codecake.nbat.user.application.dto.ReadUserDTO; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; diff --git a/src/main/java/fr/codecake/nbat/listing/application/dto/CreatedListingDTO.java b/src/main/java/fr/codecake/nbat/listing/application/dto/CreatedListingDTO.java new file mode 100644 index 0000000..822fef7 --- /dev/null +++ b/src/main/java/fr/codecake/nbat/listing/application/dto/CreatedListingDTO.java @@ -0,0 +1,4 @@ +package fr.codecake.nbat.listing.application.dto; + +public record CreatedListingDTO(String publicId) { +} diff --git a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/DisplayCardListingDTO.java b/src/main/java/fr/codecake/nbat/listing/application/dto/DisplayCardListingDTO.java similarity index 54% rename from src/main/java/fr/codecake/airbnbclone/listing/application/dto/DisplayCardListingDTO.java rename to src/main/java/fr/codecake/nbat/listing/application/dto/DisplayCardListingDTO.java index f776e8e..d00c974 100644 --- a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/DisplayCardListingDTO.java +++ b/src/main/java/fr/codecake/nbat/listing/application/dto/DisplayCardListingDTO.java @@ -1,8 +1,8 @@ -package fr.codecake.airbnbclone.listing.application.dto; +package fr.codecake.nbat.listing.application.dto; -import fr.codecake.airbnbclone.listing.application.dto.sub.PictureDTO; -import fr.codecake.airbnbclone.listing.application.dto.vo.PriceVO; -import fr.codecake.airbnbclone.listing.domain.BookingCategory; +import fr.codecake.nbat.listing.application.dto.sub.PictureDTO; +import fr.codecake.nbat.listing.application.dto.vo.PriceVO; +import fr.codecake.nbat.listing.domain.BookingCategory; import java.util.UUID; diff --git a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/DisplayListingDTO.java b/src/main/java/fr/codecake/nbat/listing/application/dto/DisplayListingDTO.java similarity index 75% rename from src/main/java/fr/codecake/airbnbclone/listing/application/dto/DisplayListingDTO.java rename to src/main/java/fr/codecake/nbat/listing/application/dto/DisplayListingDTO.java index 5bf8b27..4c27187 100644 --- a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/DisplayListingDTO.java +++ b/src/main/java/fr/codecake/nbat/listing/application/dto/DisplayListingDTO.java @@ -1,11 +1,11 @@ -package fr.codecake.airbnbclone.listing.application.dto; - -import fr.codecake.airbnbclone.listing.application.dto.sub.DescriptionDTO; -import fr.codecake.airbnbclone.listing.application.dto.sub.LandlordListingDTO; -import fr.codecake.airbnbclone.listing.application.dto.sub.ListingInfoDTO; -import fr.codecake.airbnbclone.listing.application.dto.sub.PictureDTO; -import fr.codecake.airbnbclone.listing.application.dto.vo.PriceVO; -import fr.codecake.airbnbclone.listing.domain.BookingCategory; +package fr.codecake.nbat.listing.application.dto; + +import fr.codecake.nbat.listing.application.dto.sub.DescriptionDTO; +import fr.codecake.nbat.listing.application.dto.sub.LandlordListingDTO; +import fr.codecake.nbat.listing.application.dto.sub.ListingInfoDTO; +import fr.codecake.nbat.listing.application.dto.sub.PictureDTO; +import fr.codecake.nbat.listing.application.dto.vo.PriceVO; +import fr.codecake.nbat.listing.domain.BookingCategory; import java.util.List; diff --git a/src/main/java/fr/codecake/nbat/listing/application/dto/ListingCreateBookingDTO.java b/src/main/java/fr/codecake/nbat/listing/application/dto/ListingCreateBookingDTO.java new file mode 100644 index 0000000..29de651 --- /dev/null +++ b/src/main/java/fr/codecake/nbat/listing/application/dto/ListingCreateBookingDTO.java @@ -0,0 +1,9 @@ +package fr.codecake.nbat.listing.application.dto; + +import fr.codecake.nbat.listing.application.dto.vo.PriceVO; + +import java.util.UUID; + +public record ListingCreateBookingDTO( + UUID listingPublicId, PriceVO price) { +} diff --git a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/SaveListingDTO.java b/src/main/java/fr/codecake/nbat/listing/application/dto/SaveListingDTO.java similarity index 77% rename from src/main/java/fr/codecake/airbnbclone/listing/application/dto/SaveListingDTO.java rename to src/main/java/fr/codecake/nbat/listing/application/dto/SaveListingDTO.java index 9a65ce7..b43e0ee 100644 --- a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/SaveListingDTO.java +++ b/src/main/java/fr/codecake/nbat/listing/application/dto/SaveListingDTO.java @@ -1,10 +1,10 @@ -package fr.codecake.airbnbclone.listing.application.dto; +package fr.codecake.nbat.listing.application.dto; -import fr.codecake.airbnbclone.listing.application.dto.sub.DescriptionDTO; -import fr.codecake.airbnbclone.listing.application.dto.sub.ListingInfoDTO; -import fr.codecake.airbnbclone.listing.application.dto.sub.PictureDTO; -import fr.codecake.airbnbclone.listing.application.dto.vo.PriceVO; -import fr.codecake.airbnbclone.listing.domain.BookingCategory; +import fr.codecake.nbat.listing.application.dto.sub.DescriptionDTO; +import fr.codecake.nbat.listing.application.dto.sub.ListingInfoDTO; +import fr.codecake.nbat.listing.application.dto.sub.PictureDTO; +import fr.codecake.nbat.listing.application.dto.vo.PriceVO; +import fr.codecake.nbat.listing.domain.BookingCategory; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/SearchDTO.java b/src/main/java/fr/codecake/nbat/listing/application/dto/SearchDTO.java similarity index 54% rename from src/main/java/fr/codecake/airbnbclone/listing/application/dto/SearchDTO.java rename to src/main/java/fr/codecake/nbat/listing/application/dto/SearchDTO.java index b903e48..530b827 100644 --- a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/SearchDTO.java +++ b/src/main/java/fr/codecake/nbat/listing/application/dto/SearchDTO.java @@ -1,7 +1,7 @@ -package fr.codecake.airbnbclone.listing.application.dto; +package fr.codecake.nbat.listing.application.dto; -import fr.codecake.airbnbclone.booking.application.dto.BookedDateDTO; -import fr.codecake.airbnbclone.listing.application.dto.sub.ListingInfoDTO; +import fr.codecake.nbat.booking.application.dto.BookedDateDTO; +import fr.codecake.nbat.listing.application.dto.sub.ListingInfoDTO; import jakarta.validation.Valid; import jakarta.validation.constraints.NotEmpty; diff --git a/src/main/java/fr/codecake/nbat/listing/application/dto/sub/DescriptionDTO.java b/src/main/java/fr/codecake/nbat/listing/application/dto/sub/DescriptionDTO.java new file mode 100644 index 0000000..a82260a --- /dev/null +++ b/src/main/java/fr/codecake/nbat/listing/application/dto/sub/DescriptionDTO.java @@ -0,0 +1,11 @@ +package fr.codecake.nbat.listing.application.dto.sub; + +import fr.codecake.nbat.listing.application.dto.vo.DescriptionVO; +import fr.codecake.nbat.listing.application.dto.vo.TitleVO; +import jakarta.validation.constraints.NotNull; + +public record DescriptionDTO( + @NotNull TitleVO title, + @NotNull DescriptionVO description + ) { +} diff --git a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/sub/LandlordListingDTO.java b/src/main/java/fr/codecake/nbat/listing/application/dto/sub/LandlordListingDTO.java similarity index 73% rename from src/main/java/fr/codecake/airbnbclone/listing/application/dto/sub/LandlordListingDTO.java rename to src/main/java/fr/codecake/nbat/listing/application/dto/sub/LandlordListingDTO.java index 22c4adb..fc8a3d3 100644 --- a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/sub/LandlordListingDTO.java +++ b/src/main/java/fr/codecake/nbat/listing/application/dto/sub/LandlordListingDTO.java @@ -1,4 +1,4 @@ -package fr.codecake.airbnbclone.listing.application.dto.sub; +package fr.codecake.nbat.listing.application.dto.sub; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/fr/codecake/nbat/listing/application/dto/sub/ListingInfoDTO.java b/src/main/java/fr/codecake/nbat/listing/application/dto/sub/ListingInfoDTO.java new file mode 100644 index 0000000..9ff8b47 --- /dev/null +++ b/src/main/java/fr/codecake/nbat/listing/application/dto/sub/ListingInfoDTO.java @@ -0,0 +1,15 @@ +package fr.codecake.nbat.listing.application.dto.sub; + +import fr.codecake.nbat.listing.application.dto.vo.BathsVO; +import fr.codecake.nbat.listing.application.dto.vo.BedroomsVO; +import fr.codecake.nbat.listing.application.dto.vo.BedsVO; +import fr.codecake.nbat.listing.application.dto.vo.GuestsVO; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; + +public record ListingInfoDTO( + @NotNull @Valid GuestsVO guests, + @NotNull @Valid BedroomsVO bedrooms, + @NotNull @Valid BedsVO beds, + @NotNull @Valid BathsVO baths) { +} diff --git a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/sub/PictureDTO.java b/src/main/java/fr/codecake/nbat/listing/application/dto/sub/PictureDTO.java similarity index 93% rename from src/main/java/fr/codecake/airbnbclone/listing/application/dto/sub/PictureDTO.java rename to src/main/java/fr/codecake/nbat/listing/application/dto/sub/PictureDTO.java index 169daec..0946b69 100644 --- a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/sub/PictureDTO.java +++ b/src/main/java/fr/codecake/nbat/listing/application/dto/sub/PictureDTO.java @@ -1,4 +1,4 @@ -package fr.codecake.airbnbclone.listing.application.dto.sub; +package fr.codecake.nbat.listing.application.dto.sub; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/vo/BathsVO.java b/src/main/java/fr/codecake/nbat/listing/application/dto/vo/BathsVO.java similarity index 69% rename from src/main/java/fr/codecake/airbnbclone/listing/application/dto/vo/BathsVO.java rename to src/main/java/fr/codecake/nbat/listing/application/dto/vo/BathsVO.java index ca13f41..155bb29 100644 --- a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/vo/BathsVO.java +++ b/src/main/java/fr/codecake/nbat/listing/application/dto/vo/BathsVO.java @@ -1,4 +1,4 @@ -package fr.codecake.airbnbclone.listing.application.dto.vo; +package fr.codecake.nbat.listing.application.dto.vo; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/vo/BedroomsVO.java b/src/main/java/fr/codecake/nbat/listing/application/dto/vo/BedroomsVO.java similarity index 70% rename from src/main/java/fr/codecake/airbnbclone/listing/application/dto/vo/BedroomsVO.java rename to src/main/java/fr/codecake/nbat/listing/application/dto/vo/BedroomsVO.java index db6be25..5afc97a 100644 --- a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/vo/BedroomsVO.java +++ b/src/main/java/fr/codecake/nbat/listing/application/dto/vo/BedroomsVO.java @@ -1,4 +1,4 @@ -package fr.codecake.airbnbclone.listing.application.dto.vo; +package fr.codecake.nbat.listing.application.dto.vo; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/vo/BedsVO.java b/src/main/java/fr/codecake/nbat/listing/application/dto/vo/BedsVO.java similarity index 69% rename from src/main/java/fr/codecake/airbnbclone/listing/application/dto/vo/BedsVO.java rename to src/main/java/fr/codecake/nbat/listing/application/dto/vo/BedsVO.java index 87bc26d..6d75229 100644 --- a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/vo/BedsVO.java +++ b/src/main/java/fr/codecake/nbat/listing/application/dto/vo/BedsVO.java @@ -1,4 +1,4 @@ -package fr.codecake.airbnbclone.listing.application.dto.vo; +package fr.codecake.nbat.listing.application.dto.vo; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/vo/DescriptionVO.java b/src/main/java/fr/codecake/nbat/listing/application/dto/vo/DescriptionVO.java similarity index 71% rename from src/main/java/fr/codecake/airbnbclone/listing/application/dto/vo/DescriptionVO.java rename to src/main/java/fr/codecake/nbat/listing/application/dto/vo/DescriptionVO.java index 5e57c21..c000693 100644 --- a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/vo/DescriptionVO.java +++ b/src/main/java/fr/codecake/nbat/listing/application/dto/vo/DescriptionVO.java @@ -1,4 +1,4 @@ -package fr.codecake.airbnbclone.listing.application.dto.vo; +package fr.codecake.nbat.listing.application.dto.vo; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/vo/GuestsVO.java b/src/main/java/fr/codecake/nbat/listing/application/dto/vo/GuestsVO.java similarity index 69% rename from src/main/java/fr/codecake/airbnbclone/listing/application/dto/vo/GuestsVO.java rename to src/main/java/fr/codecake/nbat/listing/application/dto/vo/GuestsVO.java index 99aebbb..3f2ef19 100644 --- a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/vo/GuestsVO.java +++ b/src/main/java/fr/codecake/nbat/listing/application/dto/vo/GuestsVO.java @@ -1,4 +1,4 @@ -package fr.codecake.airbnbclone.listing.application.dto.vo; +package fr.codecake.nbat.listing.application.dto.vo; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/vo/PriceVO.java b/src/main/java/fr/codecake/nbat/listing/application/dto/vo/PriceVO.java similarity index 69% rename from src/main/java/fr/codecake/airbnbclone/listing/application/dto/vo/PriceVO.java rename to src/main/java/fr/codecake/nbat/listing/application/dto/vo/PriceVO.java index d904522..5cf80ca 100644 --- a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/vo/PriceVO.java +++ b/src/main/java/fr/codecake/nbat/listing/application/dto/vo/PriceVO.java @@ -1,4 +1,4 @@ -package fr.codecake.airbnbclone.listing.application.dto.vo; +package fr.codecake.nbat.listing.application.dto.vo; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/vo/TitleVO.java b/src/main/java/fr/codecake/nbat/listing/application/dto/vo/TitleVO.java similarity index 69% rename from src/main/java/fr/codecake/airbnbclone/listing/application/dto/vo/TitleVO.java rename to src/main/java/fr/codecake/nbat/listing/application/dto/vo/TitleVO.java index 90f75ca..63f3963 100644 --- a/src/main/java/fr/codecake/airbnbclone/listing/application/dto/vo/TitleVO.java +++ b/src/main/java/fr/codecake/nbat/listing/application/dto/vo/TitleVO.java @@ -1,4 +1,4 @@ -package fr.codecake.airbnbclone.listing.application.dto.vo; +package fr.codecake.nbat.listing.application.dto.vo; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/fr/codecake/airbnbclone/listing/domain/BookingCategory.java b/src/main/java/fr/codecake/nbat/listing/domain/BookingCategory.java similarity index 85% rename from src/main/java/fr/codecake/airbnbclone/listing/domain/BookingCategory.java rename to src/main/java/fr/codecake/nbat/listing/domain/BookingCategory.java index 272f2c0..d740059 100644 --- a/src/main/java/fr/codecake/airbnbclone/listing/domain/BookingCategory.java +++ b/src/main/java/fr/codecake/nbat/listing/domain/BookingCategory.java @@ -1,4 +1,4 @@ -package fr.codecake.airbnbclone.listing.domain; +package fr.codecake.nbat.listing.domain; public enum BookingCategory { ALL, diff --git a/src/main/java/fr/codecake/airbnbclone/listing/domain/Listing.java b/src/main/java/fr/codecake/nbat/listing/domain/Listing.java similarity index 97% rename from src/main/java/fr/codecake/airbnbclone/listing/domain/Listing.java rename to src/main/java/fr/codecake/nbat/listing/domain/Listing.java index 178fea5..de616b4 100644 --- a/src/main/java/fr/codecake/airbnbclone/listing/domain/Listing.java +++ b/src/main/java/fr/codecake/nbat/listing/domain/Listing.java @@ -1,6 +1,6 @@ -package fr.codecake.airbnbclone.listing.domain; +package fr.codecake.nbat.listing.domain; -import fr.codecake.airbnbclone.sharedkernel.domain.AbstractAuditingEntity; +import fr.codecake.nbat.sharedkernel.domain.AbstractAuditingEntity; import jakarta.persistence.*; import org.hibernate.annotations.UuidGenerator; diff --git a/src/main/java/fr/codecake/airbnbclone/listing/domain/ListingPicture.java b/src/main/java/fr/codecake/nbat/listing/domain/ListingPicture.java similarity index 94% rename from src/main/java/fr/codecake/airbnbclone/listing/domain/ListingPicture.java rename to src/main/java/fr/codecake/nbat/listing/domain/ListingPicture.java index 47adb7a..869bd29 100644 --- a/src/main/java/fr/codecake/airbnbclone/listing/domain/ListingPicture.java +++ b/src/main/java/fr/codecake/nbat/listing/domain/ListingPicture.java @@ -1,6 +1,6 @@ -package fr.codecake.airbnbclone.listing.domain; +package fr.codecake.nbat.listing.domain; -import fr.codecake.airbnbclone.sharedkernel.domain.AbstractAuditingEntity; +import fr.codecake.nbat.sharedkernel.domain.AbstractAuditingEntity; import jakarta.persistence.*; import java.util.Arrays; diff --git a/src/main/java/fr/codecake/airbnbclone/listing/mapper/ListingMapper.java b/src/main/java/fr/codecake/nbat/listing/mapper/ListingMapper.java similarity index 91% rename from src/main/java/fr/codecake/airbnbclone/listing/mapper/ListingMapper.java rename to src/main/java/fr/codecake/nbat/listing/mapper/ListingMapper.java index 67959ed..ce884c6 100644 --- a/src/main/java/fr/codecake/airbnbclone/listing/mapper/ListingMapper.java +++ b/src/main/java/fr/codecake/nbat/listing/mapper/ListingMapper.java @@ -1,8 +1,8 @@ -package fr.codecake.airbnbclone.listing.mapper; +package fr.codecake.nbat.listing.mapper; -import fr.codecake.airbnbclone.listing.application.dto.*; -import fr.codecake.airbnbclone.listing.application.dto.vo.PriceVO; -import fr.codecake.airbnbclone.listing.domain.Listing; +import fr.codecake.nbat.listing.application.dto.*; +import fr.codecake.nbat.listing.application.dto.vo.PriceVO; +import fr.codecake.nbat.listing.domain.Listing; import org.mapstruct.Mapper; import org.mapstruct.Mapping; diff --git a/src/main/java/fr/codecake/airbnbclone/listing/mapper/ListingPictureMapper.java b/src/main/java/fr/codecake/nbat/listing/mapper/ListingPictureMapper.java similarity index 84% rename from src/main/java/fr/codecake/airbnbclone/listing/mapper/ListingPictureMapper.java rename to src/main/java/fr/codecake/nbat/listing/mapper/ListingPictureMapper.java index f2e9232..d7de15f 100644 --- a/src/main/java/fr/codecake/airbnbclone/listing/mapper/ListingPictureMapper.java +++ b/src/main/java/fr/codecake/nbat/listing/mapper/ListingPictureMapper.java @@ -1,7 +1,7 @@ -package fr.codecake.airbnbclone.listing.mapper; +package fr.codecake.nbat.listing.mapper; -import fr.codecake.airbnbclone.listing.application.dto.sub.PictureDTO; -import fr.codecake.airbnbclone.listing.domain.ListingPicture; +import fr.codecake.nbat.listing.application.dto.sub.PictureDTO; +import fr.codecake.nbat.listing.domain.ListingPicture; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Named; diff --git a/src/main/java/fr/codecake/airbnbclone/listing/presentation/LandlordResource.java b/src/main/java/fr/codecake/nbat/listing/presentation/LandlordResource.java similarity index 54% rename from src/main/java/fr/codecake/airbnbclone/listing/presentation/LandlordResource.java rename to src/main/java/fr/codecake/nbat/listing/presentation/LandlordResource.java index 89c7c68..943bf75 100644 --- a/src/main/java/fr/codecake/airbnbclone/listing/presentation/LandlordResource.java +++ b/src/main/java/fr/codecake/nbat/listing/presentation/LandlordResource.java @@ -1,17 +1,17 @@ -package fr.codecake.airbnbclone.listing.presentation; +package fr.codecake.nbat.listing.presentation; import com.fasterxml.jackson.databind.ObjectMapper; -import fr.codecake.airbnbclone.infrastructure.config.SecurityUtils; -import fr.codecake.airbnbclone.listing.application.LandlordService; -import fr.codecake.airbnbclone.listing.application.dto.CreatedListingDTO; -import fr.codecake.airbnbclone.listing.application.dto.DisplayCardListingDTO; -import fr.codecake.airbnbclone.listing.application.dto.SaveListingDTO; -import fr.codecake.airbnbclone.listing.application.dto.sub.PictureDTO; -import fr.codecake.airbnbclone.sharedkernel.service.State; -import fr.codecake.airbnbclone.sharedkernel.service.StatusNotification; -import fr.codecake.airbnbclone.user.application.UserException; -import fr.codecake.airbnbclone.user.application.UserService; -import fr.codecake.airbnbclone.user.application.dto.ReadUserDTO; +import fr.codecake.nbat.infrastructure.config.SecurityUtils; +import fr.codecake.nbat.listing.application.LandlordService; +import fr.codecake.nbat.listing.application.dto.CreatedListingDTO; +import fr.codecake.nbat.listing.application.dto.DisplayCardListingDTO; +import fr.codecake.nbat.listing.application.dto.SaveListingDTO; +import fr.codecake.nbat.listing.application.dto.sub.PictureDTO; +import fr.codecake.nbat.sharedkernel.service.State; +import fr.codecake.nbat.sharedkernel.service.StatusNotification; +import fr.codecake.nbat.user.application.UserException; +import fr.codecake.nbat.user.application.UserService; +import fr.codecake.nbat.user.application.dto.ReadUserDTO; import jakarta.validation.ConstraintViolation; import jakarta.validation.Validator; import org.springframework.http.HttpStatus; @@ -32,6 +32,7 @@ @RestController @RequestMapping("/api/landlord-listing") +@CrossOrigin(origins = { "http://localhost:4200", "http://localhost:4201" }, allowCredentials = "true") public class LandlordResource { private final LandlordService landlordService; @@ -42,19 +43,17 @@ public class LandlordResource { private final ObjectMapper objectMapper = new ObjectMapper(); - public LandlordResource(LandlordService landlordService, Validator validator, UserService userService) { this.landlordService = landlordService; this.validator = validator; this.userService = userService; } - @PostMapping(value = "/create", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @PreAuthorize("hasRole('ROLE_LANDLORD')") public ResponseEntity create( MultipartHttpServletRequest request, - @RequestPart(name = "dto") String saveListingDTOString - ) throws IOException { + @RequestPart(name = "dto") String saveListingDTOString) throws IOException { List pictures = request.getFileMap() .values() .stream() @@ -79,29 +78,69 @@ public ResponseEntity create( } } - private static Function mapMultipartFileToPictureDTO() { return multipartFile -> { try { return new PictureDTO(multipartFile.getBytes(), multipartFile.getContentType(), false); } catch (IOException ioe) { - throw new UserException(String.format("Cannot parse multipart file: %s", multipartFile.getOriginalFilename())); + throw new UserException( + String.format("Cannot parse multipart file: %s", multipartFile.getOriginalFilename())); } }; } @GetMapping(value = "/get-all") - @PreAuthorize("hasAnyRole('" + SecurityUtils.ROLE_LANDLORD + "')") + @PreAuthorize("hasRole('ROLE_LANDLORD')") public ResponseEntity> getAll() { - ReadUserDTO connectedUser = userService.getAuthenticatedUserFromSecurityContext(); + ReadUserDTO connectedUser = userService.getAuthenticatedUserFromDatabaseUsingSecurityContextEmail(); List allProperties = landlordService.getAllProperties(connectedUser); return ResponseEntity.ok(allProperties); } + @GetMapping(value = "/get-one") + @PreAuthorize("hasRole('ROLE_LANDLORD')") + public ResponseEntity getOne(@RequestParam UUID publicId) { + ReadUserDTO connectedUser = userService.getAuthenticatedUserFromDatabaseUsingSecurityContextEmail(); + return ResponseEntity.ok(landlordService.getOne(publicId, connectedUser)); + } + + @PutMapping(value = "/update", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @PreAuthorize("hasRole('ROLE_LANDLORD')") + public ResponseEntity update( + MultipartHttpServletRequest request, + @RequestParam UUID publicId, + @RequestPart(name = "dto") String saveListingDTOString) throws IOException { + List pictures = request.getFileMap() + .values() + .stream() + .map(mapMultipartFileToPictureDTO()) + .toList(); + + SaveListingDTO saveListingDTO = objectMapper.readValue(saveListingDTOString, SaveListingDTO.class); + if (!pictures.isEmpty()) { + saveListingDTO.setPictures(pictures); + } + + Set> violations = validator.validate(saveListingDTO); + if (!violations.isEmpty()) { + String violationsJoined = violations.stream() + .map(violation -> violation.getPropertyPath() + " " + violation.getMessage()) + .collect(Collectors.joining()); + if (violationsJoined == null || violationsJoined.isEmpty()) { + violationsJoined = "Validation failed"; + } + ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, violationsJoined); + return ResponseEntity.of(problemDetail).build(); + } else { + ReadUserDTO connectedUser = userService.getAuthenticatedUserFromDatabaseUsingSecurityContextEmail(); + return ResponseEntity.ok(landlordService.update(publicId, saveListingDTO, connectedUser)); + } + } + @DeleteMapping("/delete") - @PreAuthorize("hasAnyRole('" + SecurityUtils.ROLE_LANDLORD + "')") + @PreAuthorize("hasRole('ROLE_LANDLORD')") public ResponseEntity delete(@RequestParam UUID publicId) { - ReadUserDTO connectedUser = userService.getAuthenticatedUserFromSecurityContext(); + ReadUserDTO connectedUser = userService.getAuthenticatedUserFromDatabaseUsingSecurityContextEmail(); State deleteState = landlordService.delete(publicId, connectedUser); if (deleteState.getStatus().equals(StatusNotification.OK)) { return ResponseEntity.ok(deleteState.getValue()); diff --git a/src/main/java/fr/codecake/airbnbclone/listing/presentation/TenantResource.java b/src/main/java/fr/codecake/nbat/listing/presentation/TenantResource.java similarity index 66% rename from src/main/java/fr/codecake/airbnbclone/listing/presentation/TenantResource.java rename to src/main/java/fr/codecake/nbat/listing/presentation/TenantResource.java index fa58596..b563c63 100644 --- a/src/main/java/fr/codecake/airbnbclone/listing/presentation/TenantResource.java +++ b/src/main/java/fr/codecake/nbat/listing/presentation/TenantResource.java @@ -1,12 +1,12 @@ -package fr.codecake.airbnbclone.listing.presentation; - -import fr.codecake.airbnbclone.listing.application.TenantService; -import fr.codecake.airbnbclone.listing.application.dto.DisplayCardListingDTO; -import fr.codecake.airbnbclone.listing.application.dto.DisplayListingDTO; -import fr.codecake.airbnbclone.listing.application.dto.SearchDTO; -import fr.codecake.airbnbclone.listing.domain.BookingCategory; -import fr.codecake.airbnbclone.sharedkernel.service.State; -import fr.codecake.airbnbclone.sharedkernel.service.StatusNotification; +package fr.codecake.nbat.listing.presentation; + +import fr.codecake.nbat.listing.application.TenantService; +import fr.codecake.nbat.listing.application.dto.DisplayCardListingDTO; +import fr.codecake.nbat.listing.application.dto.DisplayListingDTO; +import fr.codecake.nbat.listing.application.dto.SearchDTO; +import fr.codecake.nbat.listing.domain.BookingCategory; +import fr.codecake.nbat.sharedkernel.service.State; +import fr.codecake.nbat.sharedkernel.service.StatusNotification; import jakarta.validation.Valid; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -19,18 +19,18 @@ @RestController @RequestMapping("/api/tenant-listing") +@CrossOrigin(origins = { "http://localhost:4200", "http://localhost:4201" }, allowCredentials = "true") public class TenantResource { private final TenantService tenantService; - public TenantResource(TenantService tenantService) { this.tenantService = tenantService; } @GetMapping("/get-all-by-category") public ResponseEntity> findAllByBookingCategory(Pageable pageable, - @RequestParam BookingCategory category) { + @RequestParam BookingCategory category) { return ResponseEntity.ok(tenantService.getAllByCategory(pageable, category)); } @@ -40,7 +40,8 @@ public ResponseEntity getOne(@RequestParam UUID publicId) { if (displayListingState.getStatus().equals(StatusNotification.OK)) { return ResponseEntity.ok(displayListingState.getValue()); } else { - String errorMessage = displayListingState.getError() != null ? displayListingState.getError() : "Unknown error"; + String errorMessage = displayListingState.getError() != null ? displayListingState.getError() + : "Unknown error"; ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, errorMessage); return ResponseEntity.of(problemDetail).build(); } @@ -48,7 +49,7 @@ public ResponseEntity getOne(@RequestParam UUID publicId) { @PostMapping("/search") public ResponseEntity> search(Pageable pageable, - @Valid @RequestBody SearchDTO searchDTO) { + @Valid @RequestBody SearchDTO searchDTO) { return ResponseEntity.ok(tenantService.search(pageable, searchDTO)); } } diff --git a/src/main/java/fr/codecake/airbnbclone/listing/repository/ListingPictureRepository.java b/src/main/java/fr/codecake/nbat/listing/repository/ListingPictureRepository.java similarity index 57% rename from src/main/java/fr/codecake/airbnbclone/listing/repository/ListingPictureRepository.java rename to src/main/java/fr/codecake/nbat/listing/repository/ListingPictureRepository.java index 2fce088..970eae8 100644 --- a/src/main/java/fr/codecake/airbnbclone/listing/repository/ListingPictureRepository.java +++ b/src/main/java/fr/codecake/nbat/listing/repository/ListingPictureRepository.java @@ -1,6 +1,6 @@ -package fr.codecake.airbnbclone.listing.repository; +package fr.codecake.nbat.listing.repository; -import fr.codecake.airbnbclone.listing.domain.ListingPicture; +import fr.codecake.nbat.listing.domain.ListingPicture; import org.springframework.data.jpa.repository.JpaRepository; public interface ListingPictureRepository extends JpaRepository { diff --git a/src/main/java/fr/codecake/airbnbclone/listing/repository/ListingRepository.java b/src/main/java/fr/codecake/nbat/listing/repository/ListingRepository.java similarity index 90% rename from src/main/java/fr/codecake/airbnbclone/listing/repository/ListingRepository.java rename to src/main/java/fr/codecake/nbat/listing/repository/ListingRepository.java index cd4fb91..4dbfc47 100644 --- a/src/main/java/fr/codecake/airbnbclone/listing/repository/ListingRepository.java +++ b/src/main/java/fr/codecake/nbat/listing/repository/ListingRepository.java @@ -1,7 +1,7 @@ -package fr.codecake.airbnbclone.listing.repository; +package fr.codecake.nbat.listing.repository; -import fr.codecake.airbnbclone.listing.domain.BookingCategory; -import fr.codecake.airbnbclone.listing.domain.Listing; +import fr.codecake.nbat.listing.domain.BookingCategory; +import fr.codecake.nbat.listing.domain.Listing; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/fr/codecake/airbnbclone/sharedkernel/domain/AbstractAuditingEntity.java b/src/main/java/fr/codecake/nbat/sharedkernel/domain/AbstractAuditingEntity.java similarity index 95% rename from src/main/java/fr/codecake/airbnbclone/sharedkernel/domain/AbstractAuditingEntity.java rename to src/main/java/fr/codecake/nbat/sharedkernel/domain/AbstractAuditingEntity.java index ac11182..f9f9b12 100644 --- a/src/main/java/fr/codecake/airbnbclone/sharedkernel/domain/AbstractAuditingEntity.java +++ b/src/main/java/fr/codecake/nbat/sharedkernel/domain/AbstractAuditingEntity.java @@ -1,4 +1,4 @@ -package fr.codecake.airbnbclone.sharedkernel.domain; +package fr.codecake.nbat.sharedkernel.domain; import jakarta.persistence.Column; import jakarta.persistence.EntityListeners; diff --git a/src/main/java/fr/codecake/airbnbclone/sharedkernel/service/State.java b/src/main/java/fr/codecake/nbat/sharedkernel/service/State.java similarity index 93% rename from src/main/java/fr/codecake/airbnbclone/sharedkernel/service/State.java rename to src/main/java/fr/codecake/nbat/sharedkernel/service/State.java index 7c83dc8..f0d56e8 100644 --- a/src/main/java/fr/codecake/airbnbclone/sharedkernel/service/State.java +++ b/src/main/java/fr/codecake/nbat/sharedkernel/service/State.java @@ -1,4 +1,4 @@ -package fr.codecake.airbnbclone.sharedkernel.service; +package fr.codecake.nbat.sharedkernel.service; public class State { private T value; diff --git a/src/main/java/fr/codecake/airbnbclone/sharedkernel/service/StateBuilder.java b/src/main/java/fr/codecake/nbat/sharedkernel/service/StateBuilder.java similarity index 94% rename from src/main/java/fr/codecake/airbnbclone/sharedkernel/service/StateBuilder.java rename to src/main/java/fr/codecake/nbat/sharedkernel/service/StateBuilder.java index aee3e5c..d5d67b1 100644 --- a/src/main/java/fr/codecake/airbnbclone/sharedkernel/service/StateBuilder.java +++ b/src/main/java/fr/codecake/nbat/sharedkernel/service/StateBuilder.java @@ -1,4 +1,4 @@ -package fr.codecake.airbnbclone.sharedkernel.service; +package fr.codecake.nbat.sharedkernel.service; public class StateBuilder { private StatusNotification status; diff --git a/src/main/java/fr/codecake/airbnbclone/sharedkernel/service/StatusNotification.java b/src/main/java/fr/codecake/nbat/sharedkernel/service/StatusNotification.java similarity index 54% rename from src/main/java/fr/codecake/airbnbclone/sharedkernel/service/StatusNotification.java rename to src/main/java/fr/codecake/nbat/sharedkernel/service/StatusNotification.java index 4dd8c6f..ab34722 100644 --- a/src/main/java/fr/codecake/airbnbclone/sharedkernel/service/StatusNotification.java +++ b/src/main/java/fr/codecake/nbat/sharedkernel/service/StatusNotification.java @@ -1,4 +1,4 @@ -package fr.codecake.airbnbclone.sharedkernel.service; +package fr.codecake.nbat.sharedkernel.service; public enum StatusNotification { OK, ERROR, UNAUTHORIZED; diff --git a/src/main/java/fr/codecake/nbat/user/application/Auth0Service.java b/src/main/java/fr/codecake/nbat/user/application/Auth0Service.java new file mode 100644 index 0000000..6efaddf --- /dev/null +++ b/src/main/java/fr/codecake/nbat/user/application/Auth0Service.java @@ -0,0 +1,48 @@ +package fr.codecake.nbat.user.application; + +import fr.codecake.nbat.infrastructure.config.SecurityUtils; +import fr.codecake.nbat.user.application.dto.ReadUserDTO; +import fr.codecake.nbat.user.domain.Authority; +import fr.codecake.nbat.user.domain.User; +import fr.codecake.nbat.user.repository.AuthorityRepository; +import fr.codecake.nbat.user.repository.UserRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class Auth0Service { + + private final UserRepository userRepository; + private final AuthorityRepository authorityRepository; + + public Auth0Service(UserRepository userRepository, AuthorityRepository authorityRepository) { + this.userRepository = userRepository; + this.authorityRepository = authorityRepository; + } + + @Transactional + public void addLandlordRoleToUser(ReadUserDTO readUserDTO) { + if (readUserDTO.authorities().stream().noneMatch(role -> role.equals(SecurityUtils.ROLE_LANDLORD))) { + // save ROLE_LANDLORD to database + System.out.println("💾 Saving ROLE_LANDLORD to database for: " + readUserDTO.publicId()); + User user = userRepository.findOneByPublicId(readUserDTO.publicId()) + .orElseThrow(() -> new UserException("User not found: " + readUserDTO.publicId())); + + // find landlord authority if it's not exists create it + Authority landlordAuthority = authorityRepository.findByName(SecurityUtils.ROLE_LANDLORD) + .orElseGet(() -> { + System.out.println("ROLE_LANDLORD not found in DB, creating it..."); + Authority auth = new Authority(); + auth.setName(SecurityUtils.ROLE_LANDLORD); + return authorityRepository.save(auth); + }); + + // Add ROLE_LANDLORD authority to the suer + user.getAuthorities().add(landlordAuthority); + + // Save to database + userRepository.saveAndFlush(user); + System.out.println("✅ ROLE_LANDLORD saved to database"); + } + } +} diff --git a/src/main/java/fr/codecake/airbnbclone/user/application/UserException.java b/src/main/java/fr/codecake/nbat/user/application/UserException.java similarity index 72% rename from src/main/java/fr/codecake/airbnbclone/user/application/UserException.java rename to src/main/java/fr/codecake/nbat/user/application/UserException.java index b709841..5d82a13 100644 --- a/src/main/java/fr/codecake/airbnbclone/user/application/UserException.java +++ b/src/main/java/fr/codecake/nbat/user/application/UserException.java @@ -1,4 +1,4 @@ -package fr.codecake.airbnbclone.user.application; +package fr.codecake.nbat.user.application; public class UserException extends RuntimeException { diff --git a/src/main/java/fr/codecake/nbat/user/application/UserService.java b/src/main/java/fr/codecake/nbat/user/application/UserService.java new file mode 100644 index 0000000..e39a671 --- /dev/null +++ b/src/main/java/fr/codecake/nbat/user/application/UserService.java @@ -0,0 +1,163 @@ +package fr.codecake.nbat.user.application; + +import fr.codecake.nbat.infrastructure.config.SecurityUtils; +import fr.codecake.nbat.user.application.dto.ReadUserDTO; +import fr.codecake.nbat.user.domain.Authority; +import fr.codecake.nbat.user.domain.User; +import fr.codecake.nbat.user.mapper.UserMapper; +import fr.codecake.nbat.user.repository.AuthorityRepository; +import fr.codecake.nbat.user.repository.UserRepository; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.Instant; +import java.util.*; + +@Service +public class UserService { + + private static final String UPDATED_AT_KEY = "updated_at"; + private final UserRepository userRepository; + private final AuthorityRepository authorityRepository; + private final UserMapper userMapper; + + public UserService(UserRepository userRepository, + AuthorityRepository authorityRepository, + Auth0Service auth0Service, + UserMapper userMapper) { + this.userRepository = userRepository; + this.authorityRepository = authorityRepository; + this.userMapper = userMapper; + } + + /* + * Method to get the User from the database where his authorities are + * Using the email of the authenticated user + */ + @Transactional(readOnly = true) + public ReadUserDTO getAuthenticatedUserFromDatabaseUsingSecurityContextEmail() { + + // get Authenticated user email from security context + OAuth2User principal = (OAuth2User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + User user = mapOauth2AttributesToUser(principal.getAttributes()); + + // user the email to get the user from the database + return getByEmail(user.getEmail()).orElseThrow(); + } + + @Transactional(readOnly = true) + public Optional getByEmail(String email) { + Optional oneByEmail = userRepository.findOneByEmail(email); + return oneByEmail.map(userMapper::userToReadUserDTO); + } + + @Transactional + public void syncWithIdp(OAuth2User oAuth2User) { // Remove forceResync param + Map attributes = oAuth2User.getAttributes(); + User user = mapOauth2AttributesToUser(attributes); + Optional existingUser = userRepository.findOneByEmail(user.getEmail()); + + if (existingUser.isPresent()) { + User currentUser = existingUser.get(); + + // Ensure user has default role + if (currentUser.getAuthorities().isEmpty()) { + assignTenantRoleForNewUser(currentUser); + } + + // Update profile every login (simple!) + updateUser(user); + } else { + // New user + assignTenantRoleForNewUser(user); + } + } + + @Transactional + private void assignTenantRoleForNewUser(User user) { + try { + // Get ROLE_TENANT from database (or create if doesn't exist) + Authority tenantAuthority = authorityRepository.findByName(SecurityUtils.ROLE_TENANT) + .orElseGet(() -> { + System.out.println("⚠️ ROLE_TENANT not found in DB, creating it..."); + Authority auth = new Authority(); + auth.setName(SecurityUtils.ROLE_TENANT); + return authorityRepository.save(auth); + }); + + // Add role to user and save to database + user.getAuthorities().clear(); // Remove any existing roles + user.getAuthorities().add(tenantAuthority); + + // For NEW users, save first, then add role (to ensure user ID is generated) + if (user.getId() == null) { + System.out.println("💾 Saving NEW user to database..."); + user = userRepository.saveAndFlush(user); + } + + // Now update with roles + userRepository.saveAndFlush(user); + System.out.println("✅ User saved to database with ROLE_TENANT"); + System.out.println("🔑 User ID: " + user.getId() + ", Public ID: " + user.getPublicId()); + + } catch (Exception e) { + System.err.println("❌ Error assigning tenant role: " + e.getMessage()); + e.printStackTrace(); + throw e; + } + } + + @Transactional + private void updateUser(User user) { + Optional userToUpdateOpt = userRepository.findOneByEmail(user.getEmail()); + if (userToUpdateOpt.isPresent()) { + User userToUpdate = userToUpdateOpt.get(); + userToUpdate.setEmail(user.getEmail()); + userToUpdate.setFirstName(user.getFirstName()); + userToUpdate.setLastName(user.getLastName()); + userToUpdate.setImageUrl(user.getImageUrl()); + userRepository.saveAndFlush(userToUpdate); + } + } + + public Optional getByPublicId(UUID publicId) { + Optional oneByPublicId = userRepository.findOneByPublicId(publicId); + return oneByPublicId.map(userMapper::userToReadUserDTO); + } + + @SuppressWarnings("unchecked") + private User mapOauth2AttributesToUser(Map attributes) { + User user = new User(); + String sub = String.valueOf(attributes.get("sub")); + String username = null; + + if (attributes.get("preferred_username") != null) { + username = ((String) attributes.get("preferred_username")).toLowerCase(); + } + + if (attributes.get("given_name") != null) { + user.setFirstName(((String) attributes.get("given_name"))); + } else if ((attributes.get("nickname") != null)) { + user.setFirstName(((String) attributes.get("nickname"))); + } + + if (attributes.get("family_name") != null) { + user.setLastName(((String) attributes.get("family_name"))); + } + + if (attributes.get("email") != null) { + user.setEmail(((String) attributes.get("email"))); + } else if (sub.contains("|") && (username != null && username.contains("@"))) { + user.setEmail(username); + } else { + user.setEmail(sub); + } + + if (attributes.get("picture") != null) { + user.setImageUrl(((String) attributes.get("picture"))); + } + return user; + } +} diff --git a/src/main/java/fr/codecake/airbnbclone/user/application/dto/ReadUserDTO.java b/src/main/java/fr/codecake/nbat/user/application/dto/ReadUserDTO.java similarity index 85% rename from src/main/java/fr/codecake/airbnbclone/user/application/dto/ReadUserDTO.java rename to src/main/java/fr/codecake/nbat/user/application/dto/ReadUserDTO.java index 5198db3..804872b 100644 --- a/src/main/java/fr/codecake/airbnbclone/user/application/dto/ReadUserDTO.java +++ b/src/main/java/fr/codecake/nbat/user/application/dto/ReadUserDTO.java @@ -1,4 +1,4 @@ -package fr.codecake.airbnbclone.user.application.dto; +package fr.codecake.nbat.user.application.dto; import java.util.Set; import java.util.UUID; diff --git a/src/main/java/fr/codecake/airbnbclone/user/domain/Authority.java b/src/main/java/fr/codecake/nbat/user/domain/Authority.java similarity index 96% rename from src/main/java/fr/codecake/airbnbclone/user/domain/Authority.java rename to src/main/java/fr/codecake/nbat/user/domain/Authority.java index 881a7ca..5fe489d 100644 --- a/src/main/java/fr/codecake/airbnbclone/user/domain/Authority.java +++ b/src/main/java/fr/codecake/nbat/user/domain/Authority.java @@ -1,4 +1,4 @@ -package fr.codecake.airbnbclone.user.domain; +package fr.codecake.nbat.user.domain; import jakarta.persistence.Column; import jakarta.persistence.Entity; diff --git a/src/main/java/fr/codecake/airbnbclone/user/domain/User.java b/src/main/java/fr/codecake/nbat/user/domain/User.java similarity index 96% rename from src/main/java/fr/codecake/airbnbclone/user/domain/User.java rename to src/main/java/fr/codecake/nbat/user/domain/User.java index e35b297..d075d4f 100644 --- a/src/main/java/fr/codecake/airbnbclone/user/domain/User.java +++ b/src/main/java/fr/codecake/nbat/user/domain/User.java @@ -1,4 +1,4 @@ -package fr.codecake.airbnbclone.user.domain; +package fr.codecake.nbat.user.domain; import java.util.HashSet; import java.util.Objects; @@ -7,7 +7,7 @@ import org.hibernate.annotations.UuidGenerator; -import fr.codecake.airbnbclone.sharedkernel.domain.AbstractAuditingEntity; +import fr.codecake.nbat.sharedkernel.domain.AbstractAuditingEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; diff --git a/src/main/java/fr/codecake/airbnbclone/user/mapper/UserMapper.java b/src/main/java/fr/codecake/nbat/user/mapper/UserMapper.java similarity index 54% rename from src/main/java/fr/codecake/airbnbclone/user/mapper/UserMapper.java rename to src/main/java/fr/codecake/nbat/user/mapper/UserMapper.java index ffaf103..935680e 100644 --- a/src/main/java/fr/codecake/airbnbclone/user/mapper/UserMapper.java +++ b/src/main/java/fr/codecake/nbat/user/mapper/UserMapper.java @@ -1,10 +1,10 @@ -package fr.codecake.airbnbclone.user.mapper; +package fr.codecake.nbat.user.mapper; import org.mapstruct.Mapper; -import fr.codecake.airbnbclone.user.application.dto.ReadUserDTO; -import fr.codecake.airbnbclone.user.domain.Authority; -import fr.codecake.airbnbclone.user.domain.User; +import fr.codecake.nbat.user.application.dto.ReadUserDTO; +import fr.codecake.nbat.user.domain.Authority; +import fr.codecake.nbat.user.domain.User; @Mapper(componentModel = "spring") public interface UserMapper { diff --git a/src/main/java/fr/codecake/nbat/user/presentation/AuthResource.java b/src/main/java/fr/codecake/nbat/user/presentation/AuthResource.java new file mode 100644 index 0000000..680e278 --- /dev/null +++ b/src/main/java/fr/codecake/nbat/user/presentation/AuthResource.java @@ -0,0 +1,78 @@ +package fr.codecake.nbat.user.presentation; + +import fr.codecake.nbat.user.application.Auth0Service; +import fr.codecake.nbat.user.application.UserService; +import fr.codecake.nbat.user.application.dto.ReadUserDTO; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.web.bind.annotation.*; + +import java.text.MessageFormat; +import java.util.Map; + +@RestController +@RequestMapping("/api/auth") +@CrossOrigin(origins = { "http://localhost:4200", "http://localhost:4201" }, allowCredentials = "true") +public class AuthResource { + + private final UserService userService; + + private final ClientRegistration registration; + + private final Auth0Service auth0Service; + + public AuthResource(UserService userService, ClientRegistrationRepository registrationRepository, + Auth0Service auth0Service) { + this.userService = userService; + this.registration = registrationRepository.findByRegistrationId("okta"); + if (this.registration == null) { + throw new IllegalStateException( + "OAuth2 Client registration 'okta' not found. Check application.yml configuration."); + } + this.auth0Service = auth0Service; + } + + @GetMapping("/get-authenticated-user") + public ResponseEntity getAuthenticatedUser() { + ReadUserDTO connectedUser = userService.getAuthenticatedUserFromDatabaseUsingSecurityContextEmail(); + return new ResponseEntity<>(connectedUser, HttpStatus.OK); + } + + @PostMapping("/assign-landlord-role") + public ResponseEntity assignLandlordRole( + @AuthenticationPrincipal OAuth2User user) { + + // get current user loged in from db + ReadUserDTO userDto = userService.getAuthenticatedUserFromDatabaseUsingSecurityContextEmail(); + + // Check if already has landlord role + if (userDto.authorities().contains("ROLE_LANDLORD")) { + return ResponseEntity.badRequest().body(Map.of( + "message", "User already has landlord role")); + } + + // add landlord role + auth0Service.addLandlordRoleToUser(userDto); + + // return ok 200 + return ResponseEntity.ok().body(Map.of( + "message", "landlord role was assigned successfully", + "authorities", userDto.authorities())); + } + + @PostMapping("/logout") + public ResponseEntity> logout(HttpServletRequest request) { + String issuerUri = registration.getProviderDetails().getIssuerUri(); + String originUrl = request.getHeader(HttpHeaders.ORIGIN); + Object[] params = { issuerUri, registration.getClientId(), originUrl }; + String logoutUrl = MessageFormat.format("{0}v2/logout?client_id={1}&returnTo={2}", params); + request.getSession().invalidate(); + return ResponseEntity.ok().body(Map.of("logoutUrl", logoutUrl)); + } +} diff --git a/src/main/java/fr/codecake/nbat/user/repository/AuthorityRepository.java b/src/main/java/fr/codecake/nbat/user/repository/AuthorityRepository.java new file mode 100644 index 0000000..2a5b9ab --- /dev/null +++ b/src/main/java/fr/codecake/nbat/user/repository/AuthorityRepository.java @@ -0,0 +1,10 @@ +package fr.codecake.nbat.user.repository; + +import fr.codecake.nbat.user.domain.Authority; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface AuthorityRepository extends JpaRepository { + Optional findByName(String name); +} diff --git a/src/main/java/fr/codecake/airbnbclone/user/repository/UserRepository.java b/src/main/java/fr/codecake/nbat/user/repository/UserRepository.java similarity index 74% rename from src/main/java/fr/codecake/airbnbclone/user/repository/UserRepository.java rename to src/main/java/fr/codecake/nbat/user/repository/UserRepository.java index d096364..e70eb6d 100644 --- a/src/main/java/fr/codecake/airbnbclone/user/repository/UserRepository.java +++ b/src/main/java/fr/codecake/nbat/user/repository/UserRepository.java @@ -1,6 +1,6 @@ -package fr.codecake.airbnbclone.user.repository; +package fr.codecake.nbat.user.repository; -import fr.codecake.airbnbclone.user.domain.User; +import fr.codecake.nbat.user.domain.User; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml deleted file mode 100644 index 05a8de9..0000000 --- a/src/main/resources/application-dev.yml +++ /dev/null @@ -1,21 +0,0 @@ -# spring: -# liquibase: -# contexts: dev -# default-schema: public -# jpa: -# properties: -# hibernate: -# '[default_schema]': public -# datasource: -# url: jdbc:postgresql://localhost:5432/airbnb_db -# username: postgres -# password: 0000 -# application: -# auth0: -# role-landlord-id: "rol_rSyLRkZ1gsnawxY4" -# role-tenant-id: "rol_Ur6hM5pvWc3q1yn9" - -spring: - liquibase: - contexts: dev - \ No newline at end of file diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml deleted file mode 100644 index f3d1e9b..0000000 --- a/src/main/resources/application-prod.yml +++ /dev/null @@ -1,40 +0,0 @@ -# logging: -# level: -# ROOT: info -# '[org.hibernate.SQL]': INFO -# '[fr.codecake.airbnbcloneback]': INFO - -# spring: -# datasource: -# url: jdbc:postgresql://localhost:5432/airbnb_db -# username: codecake -# password: codecake -# jpa: -# properties: -# hibernate: -# '[default_schema]': public -# liquibase: -# contexts: prod -# default-schema: public - -logging: - level: - ROOT: info - "[org.hibernate.SQL]": INFO - "[mr.imane.nbatcloneback]": INFO - -spring: - docker: - compose: - enabled: false - datasource: - url: jdbc:postgresql://localhost:5432/nbat_db - username: postgres - password: "0000" - jpa: - properties: - hibernate: - "[default_schema]": nbat_schema - liquibase: - contexts: prod - default-schema: nbat_schema \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index fb97b33..ebfa06d 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,50 +1,44 @@ logging: level: ROOT: INFO - '[org.hibernate.SQL]': DEBUG - "[mr.imane.nbatcloneback]": DEBUG + "[org.hibernate.SQL]": DEBUG + "[fr.codecake.nbat]": DEBUG spring: - profiles: - active: prod application: name: nbat-clone-back + + docker: + compose: + enabled: true + lifecycle-management: start_only + datasource: - type: com.zaxxer.hikari.HikariDataSource - hikari: - poolName: Hikari - auto-commit: false - data: - jpa: - repositories: - bootstrap-mode: deferred + url: jdbc:postgresql://localhost:5432/nbat_db + username: postgres + password: 0000 + driver-class-name: org.postgresql.Driver + jpa: - open-in-view: false hibernate: - ddl-auto: validate + ddl-auto: update properties: hibernate: - "[default_schema]": nbat_schema - liquibase: - contexts: dev - change-log: classpath:db/changelog/master.xml - default-schema: nbat_schema - enabled: true - # drop-first: true + default_schema: nbat_schema + format_sql: true + show-sql: true + servlet: multipart: enabled: true max-file-size: 100MB max-request-size: 100MB + server: port: 8080 okta: - oauth2: - issuer: https://dev-1m8rc8rkauzkk7c6.us.auth0.com/ - client-id: WNUDfBrJg6C46KxbhEmd8nK70y8eBZEg - client-secret: Cgi8WarbeegjZfKDVpBPHbUZQnJAgXg8GAqSp0GG8Bhw-7a1a5EAQue15XqBxVDw -application: - auth0: - role-landlord-id: "rol_rSyLRkZ1gsnawxY4" - + oauth2: + issuer: https://dev-1m8rc8rkauzkk7c6.us.auth0.com/ + client-id: WNUDfBrJg6C46KxbhEmd8nK70y8eBZEg + client-secret: Cgi8WarbeegjZfKDVpBPHbUZQnJAgXg8GAqSp0GG8Bhw-7a1a5EAQue15XqBxVDw diff --git a/src/main/resources/db/.DS_Store b/src/main/resources/db/.DS_Store deleted file mode 100644 index ab2e070..0000000 Binary files a/src/main/resources/db/.DS_Store and /dev/null differ diff --git a/src/main/resources/db/changelog/.DS_Store b/src/main/resources/db/changelog/.DS_Store deleted file mode 100644 index d3efac8..0000000 Binary files a/src/main/resources/db/changelog/.DS_Store and /dev/null differ diff --git a/src/main/resources/db/changelog/00000000000000_create-schema.xml b/src/main/resources/db/changelog/00000000000000_create-schema.xml deleted file mode 100644 index 26fbcc2..0000000 --- a/src/main/resources/db/changelog/00000000000000_create-schema.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - CREATE SCHEMA IF NOT EXISTS nbat_db; - - - - \ No newline at end of file diff --git a/src/main/resources/db/changelog/00000000000001_user.xml b/src/main/resources/db/changelog/00000000000001_user.xml deleted file mode 100644 index 3eb9b4b..0000000 --- a/src/main/resources/db/changelog/00000000000001_user.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/db/changelog/00000000000002_listing.xml b/src/main/resources/db/changelog/00000000000002_listing.xml deleted file mode 100644 index a3b97da..0000000 --- a/src/main/resources/db/changelog/00000000000002_listing.xml +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/db/changelog/00000000000003_booking.xml b/src/main/resources/db/changelog/00000000000003_booking.xml deleted file mode 100644 index 4c35c77..0000000 --- a/src/main/resources/db/changelog/00000000000003_booking.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/db/changelog/data/authority.csv b/src/main/resources/db/changelog/data/authority.csv deleted file mode 100644 index c48fe73..0000000 --- a/src/main/resources/db/changelog/data/authority.csv +++ /dev/null @@ -1,4 +0,0 @@ -name -ROLE_ADMIN -ROLE_TENANT -ROLE_LANDLORD diff --git a/src/main/resources/db/changelog/master.xml b/src/main/resources/db/changelog/master.xml deleted file mode 100644 index a484226..0000000 --- a/src/main/resources/db/changelog/master.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/main/resources/liquibase.properties b/src/main/resources/liquibase.properties deleted file mode 100644 index a14040f..0000000 --- a/src/main/resources/liquibase.properties +++ /dev/null @@ -1,6 +0,0 @@ -changeLogFile=src/main/resources/db/changelog/master.xml -url=jdbc:postgresql://localhost:5432/nbat_db -username=postgres -password=0000 -driver=org.postgresql.Driver -defaultSchemaName=nbat_schema