Files
polskalokalnie/backend/src/main/java/pl/polskalokalnie/auth/AuthService.java
T
pascal 93e67bd27e Zmiana nazwy na Polska Lokalnie, przyklejony naglowek i uklad stron konta
Nazwa i adres:
- marka "Mieszko" -> "Polska Lokalnie" w calym interfejsie (27 miejsc),
  w tekstach zaleznych dodane slowo "serwis", zeby brzmialy poprawnie po polsku
- domena mieszko.pl -> polskalokalnie.pl (adresy kontaktowe, profil, udostepnianie)
- haslo pod logo: "mieszkania za darmo" -> "ogloszenia za darmo"
- backend: teksty powiadomien, dane demo i domyslna nazwa admina
- nazwa wyswietlana istniejacego admina zaktualizowana w bazie;
  login admin@mieszko.pl celowo bez zmian

Naglowek:
- logo, nawigacja i panel zostaja widoczne przy przewijaniu (position: sticky,
  warstwa 65 - nad listami rozwijanymi, pod lightboxem zdjec)
- sticky sidebary z top: 18-20px wyrownane do 96px, zeby nie wjezdzaly pod naglowek

Uklad stron konta:
- /ulubione, /wiadomosci i /powiadomienia otwieraja sie jak /konto/wyszukiwania -
  wspolny layout AccountShell zamiast powielania ukladu w kazdej stronie
- /konto/porownanie mialo wlasny uklad (sidebar 300px, inny gap i padding),
  przez co menu przeskakiwalo - wyrownane do .account-layout wraz z punktem
  zwijania (1200px zamiast 1500px)
- narzedzia publiczne (/wycena, /historia-cen, /ranking-dzielnic, /negocjacje)
  bez menu konta - dzialaja jak /kupno czy /wynajem

Naprawy regresji po zamianie przyciskow na linki:
- ikona ulubionych na kartach /kupno wracala do przeplywu (nadpisany
  position: absolute) - teraz podnoszona jest tylko warstwa
- menu uzytkownika i "Zobacz wszystkie" w powiadomieniach: reguly CSS
  celowaly tylko w <button>, przez co pozycje rozjezdzaly sie na dwie linie

Testy: 47 (bylo 41), w tym zabezpieczenie przed powrotem przeskoku menu
i przed rozjechaniem pozycji menu uzytkownika.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 01:21:20 +02:00

190 lines
8.4 KiB
Java

package pl.polskalokalnie.auth;
import java.time.LocalDate;
import java.time.format.DateTimeParseException;
import java.util.Locale;
import org.springframework.http.HttpStatus;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import pl.polskalokalnie.lead.LeadSyncService;
import pl.polskalokalnie.moderation.TextModerationService;
import pl.polskalokalnie.auth.dto.AuthResponse;
import pl.polskalokalnie.auth.dto.LoginRequest;
import pl.polskalokalnie.auth.dto.RegisterRequest;
import pl.polskalokalnie.auth.dto.SocialLoginRequest;
import pl.polskalokalnie.auth.dto.UpdateProfileRequest;
import pl.polskalokalnie.auth.dto.UserResponse;
import pl.polskalokalnie.user.AccountType;
import pl.polskalokalnie.user.AppUser;
import pl.polskalokalnie.user.AuthProvider;
import pl.polskalokalnie.user.BlockedEmailRepository;
import pl.polskalokalnie.user.ContactPreference;
import pl.polskalokalnie.user.PreferredLanguage;
import pl.polskalokalnie.user.Role;
import pl.polskalokalnie.user.UserRepository;
@Service
public class AuthService {
private final UserRepository userRepository;
private final BlockedEmailRepository blockedEmailRepository;
private final PasswordEncoder passwordEncoder;
private final JwtService jwtService;
private final TextModerationService textModerationService;
private final LeadSyncService leadSyncService;
public AuthService(
UserRepository userRepository,
BlockedEmailRepository blockedEmailRepository,
PasswordEncoder passwordEncoder,
JwtService jwtService,
TextModerationService textModerationService,
LeadSyncService leadSyncService
) {
this.userRepository = userRepository;
this.blockedEmailRepository = blockedEmailRepository;
this.passwordEncoder = passwordEncoder;
this.jwtService = jwtService;
this.textModerationService = textModerationService;
this.leadSyncService = leadSyncService;
}
public AuthResponse register(RegisterRequest request) {
String email = normalizeEmail(request.email());
if (blockedEmailRepository.existsByEmailIgnoreCase(email)) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Ten adres e-mail został zablokowany i nie można go już użyć do rejestracji");
}
if (userRepository.existsByEmailIgnoreCase(email)) {
throw new ResponseStatusException(HttpStatus.CONFLICT, "Konto z tym adresem e-mail już istnieje");
}
textModerationService.validateOrThrow(request.fullName(), request.phone(), request.nip());
AppUser user = new AppUser();
user.setEmail(email);
user.setFullName(request.fullName().trim());
user.setPasswordHash(passwordEncoder.encode(request.password()));
user.setRole(Role.USER);
user.setProvider(AuthProvider.LOCAL);
user.setAccountType(request.accountType() != null ? request.accountType() : AccountType.PERSONAL);
user.setPhone(request.phone() != null && !request.phone().isBlank() ? request.phone().trim() : null);
user.setNip(request.nip() != null && !request.nip().isBlank() ? request.nip().trim() : null);
// Konta zalozone samodzielnie czekaja na weryfikacje danych przez administratora.
user.setVerified(false);
AppUser saved = userRepository.save(user);
leadSyncService.syncUser(saved);
return buildAuthResponse(saved);
}
public AuthResponse login(LoginRequest request) {
AppUser user = userRepository.findByEmailIgnoreCase(normalizeEmail(request.email()))
.orElseThrow(() -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Nieprawidłowy e-mail lub hasło"));
if (user.getPasswordHash() == null
|| !passwordEncoder.matches(request.password(), user.getPasswordHash())) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Nieprawidłowy e-mail lub hasło");
}
if (user.isBlocked()) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Konto zostało zablokowane");
}
return buildAuthResponse(user);
}
/**
* Symulowane logowanie spoleczne: znajduje lub tworzy konto powiazane z dostawca.
* Nie ma tu prawdziwego OAuth - dane przychodza z frontendu jako demo.
*/
public AuthResponse socialLogin(SocialLoginRequest request) {
AuthProvider provider = request.provider();
String email = normalizeEmail(
request.email() != null && !request.email().isBlank()
? request.email()
: defaultSocialEmail(provider));
String name = request.fullName() != null && !request.fullName().isBlank()
? request.fullName().trim()
: defaultSocialName(provider);
textModerationService.validateOrThrow(name);
AppUser user = userRepository.findByEmailIgnoreCase(email).orElseGet(() -> {
if (blockedEmailRepository.existsByEmailIgnoreCase(email)) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Ten adres e-mail został zablokowany i nie można go już użyć do rejestracji");
}
AppUser created = new AppUser();
created.setEmail(email);
created.setFullName(name);
created.setRole(Role.USER);
created.setProvider(provider);
// Logowanie spoleczne nie wymaga rekopisania danych, wiec konto jest od razu zweryfikowane.
created.setVerified(true);
return userRepository.save(created);
});
if (user.isBlocked()) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Konto zostało zablokowane");
}
leadSyncService.syncUser(user);
return buildAuthResponse(user);
}
public UserResponse currentUser(String email) {
return userRepository.findByEmailIgnoreCase(email)
.map(UserResponse::from)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Sesja wygasła"));
}
public UserResponse updateProfile(String email, UpdateProfileRequest request) {
AppUser user = userRepository.findByEmailIgnoreCase(email)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Sesja wygasła"));
textModerationService.validateOrThrow(request.fullName(), request.phone(), request.birthDate(), request.address());
user.setFullName(request.fullName().trim());
user.setPhone(request.phone() != null && !request.phone().isBlank() ? request.phone().trim() : null);
user.setAddress(request.address() != null && !request.address().isBlank() ? request.address().trim() : null);
user.setContactPreference(request.contactPreference() != null ? request.contactPreference() : ContactPreference.EMAIL_AND_PHONE);
user.setPreferredLanguage(request.preferredLanguage() != null ? request.preferredLanguage() : PreferredLanguage.PL);
if (request.birthDate() != null && !request.birthDate().isBlank()) {
try {
user.setBirthDate(LocalDate.parse(request.birthDate().trim()));
} catch (DateTimeParseException ex) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Nieprawidłowy format daty urodzenia");
}
} else {
user.setBirthDate(null);
}
AppUser saved = userRepository.save(user);
leadSyncService.syncUser(saved);
return UserResponse.from(saved);
}
private AuthResponse buildAuthResponse(AppUser user) {
return new AuthResponse(jwtService.generateToken(user), UserResponse.from(user));
}
private String normalizeEmail(String email) {
return email.trim().toLowerCase(Locale.ROOT);
}
private String defaultSocialEmail(AuthProvider provider) {
return switch (provider) {
case GOOGLE -> "demo.google@gmail.com";
case FACEBOOK -> "demo.facebook@facebook.com";
case LOCAL -> "demo.local@polskalokalnie.pl";
};
}
private String defaultSocialName(AuthProvider provider) {
return switch (provider) {
case GOOGLE -> "Użytkownik Google";
case FACEBOOK -> "Użytkownik Facebook";
case LOCAL -> "Użytkownik";
};
}
}