93e67bd27e
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>
190 lines
8.4 KiB
Java
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";
|
|
};
|
|
}
|
|
}
|