Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,9 @@ docker

.idea

.codemie
.codemie

.vscode

data/ml_models/
phase1_datasets/
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Arrays;
import java.util.List;

@Configuration
public class SecurityConfig {

Expand All @@ -36,19 +33,42 @@ public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(freeResourceUrls).permitAll()
.anyRequest().authenticated())
.cors(corsConfigurer -> corsConfigurer.configurationSource(corsConfigurationSource()))
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
.build();
}

/**
* CORS configuration for API Gateway.
* Frontend makes requests to the gateway, which proxies to internal services.
* Swagger UI on the gateway also needs to be allowed.
* Services do NOT need their own CORS config - this is the single point of CORS handling.
*/
@Bean
CorsConfigurationSource corsConfigurationSource() {
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(List.of("*"));

// Allow frontend origins
configuration.setAllowedOriginPatterns(java.util.List.of(
"http://localhost:4200", // Angular dev
"http://127.0.0.1:4200"
));

// Allow all HTTP methods
configuration.setAllowedMethods(java.util.List.of("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"));

// Allow all headers (including Authorization for JWT)
configuration.setAllowedHeaders(java.util.List.of("*"));

// CRITICAL: Allow credentials (JWT tokens in Authorization header)
configuration.setAllowCredentials(true);

// Cache preflight for 1 hour
configuration.setMaxAge(3600L);

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
Expand Down
28 changes: 2 additions & 26 deletions book-bazaar/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions book-bazaar/src/app/components/book-details/book-details.css
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,29 @@
outline: none;
}

/* Scrollable similar books container */
.similar-books-scroll {
scrollbar-width: thin;
scrollbar-color: rgba(156, 163, 175, 0.5) transparent;
}

.similar-books-scroll::-webkit-scrollbar {
width: 6px;
}

.similar-books-scroll::-webkit-scrollbar-track {
background: transparent;
}

.similar-books-scroll::-webkit-scrollbar-thumb {
background: rgba(156, 163, 175, 0.5);
border-radius: 3px;
}

.similar-books-scroll::-webkit-scrollbar-thumb:hover {
background: rgba(107, 114, 128, 0.7);
}

/* Тінь для книги */
.book-cover-container {
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.2), 0 8px 10px -6px rgba(0, 0, 0, 0.2);
Expand Down
62 changes: 55 additions & 7 deletions book-bazaar/src/app/components/book-details/book-details.html
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,56 @@ <h3 class="text-xl font-bold text-book-dark-gray mb-4 font-serif">Book Details</

<mat-divider class="mb-16"></mat-divider>

<div class="grid grid-cols-1 lg:grid-cols-12 gap-12 pt-10" id="reviews">
<div class="flex gap-12 pt-10" id="reviews">

<div class="lg:col-span-4 space-y-8">
<!-- Similar Books Sidebar - Fixed Width -->
<div class="w-72 shrink-0">
<div class="sticky top-24">
<h2 class="text-2xl font-bold text-gray-900 mb-6 font-serif">Similar Books</h2>

@if (isLoadingSimilar()) {
<div class="flex justify-center py-8">
<mat-spinner diameter="40"></mat-spinner>
</div>
} @else if (similarBooks().length === 0) {
<p class="text-gray-500 text-center py-8">No similar books found.</p>
} @else {
<div class="space-y-4 max-h-[calc(100vh-280px)] overflow-y-auto pr-2 similar-books-scroll">
@for (book of similarBooks(); track book.id) {
<div class="group"
[matTooltip]="getSimilarityReason(book)"
matTooltipPosition="above"
class="cursor-pointer rounded-lg overflow-hidden hover:shadow-md transition-shadow">
<div [routerLink]="['/book-details', book.id]"
class="h-48 rounded-lg overflow-hidden mb-3 bg-gray-100 hover:scale-105 transition-transform">
<img [src]="book.imageUrl || 'assets/default-book-cover.jpg'"
[alt]="book.title"
class="w-full h-full object-cover">
</div>
<h4 class="font-semibold text-sm text-gray-900 line-clamp-2 group-hover:text-primary transition-colors">
{{ book.title }}
</h4>
<p class="text-xs text-gray-600 truncate">{{ book.author?.name }}</p>
<div class="mt-2 flex items-center justify-between">
<span class="text-xs font-medium text-yellow-500 flex items-center gap-1">
<mat-icon class="!w-4 !h-4 !text-[16px]">star</mat-icon>
{{ book.averageRating?.toFixed(1) }}
</span>
<span class="text-xs text-gray-500">({{ book.totalReviews }})</span>
</div>
</div>
}
</div>
}
</div>
</div>

<!-- Main Content Area - Full Width -->
<div class="flex-1">
<!-- Reviews and Metrics Grid -->
<div class="grid grid-cols-1 md:grid-cols-12 gap-12">
<!-- Community Reviews Stats -->
<div class="md:col-span-5 space-y-8">
<div>
<h3 class="text-2xl font-bold text-book-dark-gray font-serif mb-6">Community Reviews</h3>

Expand Down Expand Up @@ -185,11 +232,11 @@ <h4 class="text-xl font-bold text-gray-900 mb-2 font-serif">
{{ currentUserReview() ? 'Edit Review' : 'Write a Review' }}
</button>
</div>
</div>
</div>

<div class="lg:col-span-8">

<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-8 gap-4 border-b border-gray-100 pb-4">
<!-- Reviews List -->
<div class="md:col-span-7">
<!-- Reviews Section -->
<h3 class="text-xl font-bold text-gray-900 flex items-center gap-2">
Reviews
@if(selectedRatingFilter()) {
Expand Down Expand Up @@ -229,7 +276,6 @@ <h3 class="text-xl font-bold text-gray-900 flex items-center gap-2">
</button>
</mat-menu>
</div>
</div>

@if (reviewsLoading()) {
<div class="flex justify-center py-20">
Expand Down Expand Up @@ -303,5 +349,7 @@ <h3 class="text-xl font-bold text-gray-900 flex items-center gap-2">

</div>
</div>
</div>
</div>
</div>
}
Loading
Loading