diff --git a/sep490-commons/api/src/main/java/green_buildings/commons/api/enums/UserLocale.java b/sep490-commons/api/src/main/java/green_buildings/commons/api/enums/UserLocale.java new file mode 100644 index 00000000..4e05eb6e --- /dev/null +++ b/sep490-commons/api/src/main/java/green_buildings/commons/api/enums/UserLocale.java @@ -0,0 +1,25 @@ +package green_buildings.commons.api.enums; + +import green_buildings.commons.api.BaseEnum; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +@Getter +@RequiredArgsConstructor(access = AccessLevel.PACKAGE) +public enum UserLocale implements BaseEnum { + VI("vi-VN"), + EN("en-US"), + ZH("zh-CN"); + + private final String code; + + public static UserLocale fromCode(String code) { + return Arrays.stream(values()) + .filter(userLanguage -> userLanguage.getCode().equals(code)) + .findFirst() + .orElseThrow(); + } +} diff --git a/sep490-commons/springfw-impl/src/main/java/commons/springfw/impl/securities/JwtAuthenticationConverter.java b/sep490-commons/springfw-impl/src/main/java/commons/springfw/impl/securities/JwtAuthenticationConverter.java index a812dbf4..e3a701b7 100644 --- a/sep490-commons/springfw-impl/src/main/java/commons/springfw/impl/securities/JwtAuthenticationConverter.java +++ b/sep490-commons/springfw-impl/src/main/java/commons/springfw/impl/securities/JwtAuthenticationConverter.java @@ -69,6 +69,7 @@ public JwtAuthenticationTokenDecorator convert(Jwt source) { email, enterpriseId, StringUtils.EMPTY, + StringUtils.EMPTY, // can't initialize language from JWT List.copyOf(authorities), List.copyOf(buildingPermissions) ) diff --git a/sep490-commons/springfw-impl/src/main/java/commons/springfw/impl/securities/UserContextData.java b/sep490-commons/springfw-impl/src/main/java/commons/springfw/impl/securities/UserContextData.java index b50c2528..5fa7ca7f 100644 --- a/sep490-commons/springfw-impl/src/main/java/commons/springfw/impl/securities/UserContextData.java +++ b/sep490-commons/springfw-impl/src/main/java/commons/springfw/impl/securities/UserContextData.java @@ -15,6 +15,7 @@ public class UserContextData implements UserDetails { private final String username; private final UUID enterpriseId; private final String password; + private final String language; private final List authorities; - private final List permissions; + private final transient List permissions; } diff --git a/sep490-frontend/src/app/app.component.ts b/sep490-frontend/src/app/app.component.ts index 4183405f..7d427c5f 100644 --- a/sep490-frontend/src/app/app.component.ts +++ b/sep490-frontend/src/app/app.component.ts @@ -1,6 +1,8 @@ import {Component, OnDestroy, OnInit} from '@angular/core'; import {TranslateService} from '@ngx-translate/core'; +import {Subject, filter, takeUntil} from 'rxjs'; import {ThemeService} from './modules/core/services/theme.service'; +import {UserService} from './services/user.service'; @Component({ selector: 'app-root', @@ -8,22 +10,26 @@ import {ThemeService} from './modules/core/services/theme.service'; styleUrl: './app.component.scss' }) export class AppComponent implements OnInit, OnDestroy { + protected readonly destroy$: Subject = new Subject(); private systemThemeMediaQuery?: MediaQueryList; constructor( private readonly themeService: ThemeService, - private readonly translate: TranslateService - ) { - this.themeService.initTheme(); - - // this language will be used as a fallback when a translation isn't found in the current language - this.translate.setDefaultLang('vi'); - - // the lang to use, if the lang isn't available, it will use the current loader to get them - this.translate.use('vi'); - } + private readonly translate: TranslateService, + private readonly userService: UserService + ) {} ngOnInit(): void { + this.userService.userConfigs + .pipe( + takeUntil(this.destroy$), + filter(userConfigs => !!userConfigs) + ) + .subscribe(userConfigs => { + this.translate.use(userConfigs.language.split('-')[0]); + this.themeService.setTheme(userConfigs.theme); + this.themeService.initTheme(); + }); // Listen for system theme changes this.systemThemeMediaQuery = window.matchMedia( this.themeService.SYSTEM_COLOR_SCHEME_QUERY @@ -35,6 +41,8 @@ export class AppComponent implements OnInit, OnDestroy { } ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); this.systemThemeMediaQuery?.removeEventListener( 'change', this.handleThemeChange diff --git a/sep490-frontend/src/app/app.module.ts b/sep490-frontend/src/app/app.module.ts index f514fd83..660819b7 100644 --- a/sep490-frontend/src/app/app.module.ts +++ b/sep490-frontend/src/app/app.module.ts @@ -28,7 +28,6 @@ import {SidebarComponent} from './components/sidebar/sidebar.component'; import {UnauthorizedComponent} from './components/unauthorized/unauthorized.component'; import {CoreModule} from './modules/core/core.module'; import {HttpErrorHandlerInterceptor} from './modules/core/services/http-error-handler.interceptor'; -import {LanguageInterceptor} from './modules/shared/interceptor/language.interceptor'; import {SharedModule} from './modules/shared/shared.module'; enum OidcScopes { @@ -86,7 +85,6 @@ export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader { autoUserInfo: true, renewUserInfoAfterTokenRenew: true, logLevel: environment.production ? LogLevel.Warn : LogLevel.Debug, - historyCleanupOff: true, secureRoutes: [environment.idpApiUrl, environment.enterpriseUrl] } }), @@ -110,7 +108,6 @@ export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader { deps: [OidcSecurityService], multi: true }, - {provide: HTTP_INTERCEPTORS, useClass: LanguageInterceptor, multi: true}, {provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true}, { provide: HTTP_INTERCEPTORS, diff --git a/sep490-frontend/src/app/components/header/header.component.ts b/sep490-frontend/src/app/components/header/header.component.ts index 8be1d33f..0959a7fa 100644 --- a/sep490-frontend/src/app/components/header/header.component.ts +++ b/sep490-frontend/src/app/components/header/header.component.ts @@ -1,14 +1,16 @@ import {Component, OnInit} from '@angular/core'; import {TranslateService} from '@ngx-translate/core'; -import {Observable} from 'rxjs'; +import {Observable, map, takeUntil} from 'rxjs'; import {ApplicationService} from '../../modules/core/services/application.service'; import {ThemeService} from '../../modules/core/services/theme.service'; import {SubscriptionAwareComponent} from '../../modules/core/subscription-aware.component'; +import {UserLanguage} from '../../modules/shared/enums/user-language.enum'; +import {UserService} from '../../services/user.service'; interface Language { display: string; mobile: string; - key: string; + key: UserLanguage; } @Component({ @@ -27,6 +29,7 @@ export class HeaderComponent constructor( protected readonly applicationService: ApplicationService, + private readonly userService: UserService, private readonly themeService: ThemeService, private readonly translate: TranslateService ) { @@ -36,15 +39,28 @@ export class HeaderComponent ngOnInit(): void { this.languages = [ - {display: 'Tiếng Việt', mobile: 'VI', key: 'vi'}, - {display: 'English', mobile: 'EN', key: 'en'}, - {display: '中文(简体)', mobile: 'ZH', key: 'zh'} + {display: 'Tiếng Việt', mobile: 'VI', key: UserLanguage.VI}, + {display: 'English', mobile: 'EN', key: UserLanguage.EN}, + {display: '中文(简体)', mobile: 'ZH', key: UserLanguage.ZH} ]; - this.selectedLanguage = this.languages[0]; + this.translate.onLangChange + .pipe( + takeUntil(this.destroy$), + map(event => event.lang) + ) + .subscribe(lang => { + this.selectedLanguage = this.languages?.find( + language => language.key.split('-')[0] === lang + ); + }); } protected changeLanguage(language: Language): void { - this.translate.use(language.key); + this.translate.use(language.key.split('-')[0]); + this.userService + .changeLanguage(language.key) + .pipe(takeUntil(this.destroy$)) + .subscribe(); } protected login(): void { diff --git a/sep490-frontend/src/app/modules/authorization/authorization.module.ts b/sep490-frontend/src/app/modules/authorization/authorization.module.ts index bddd0849..4a9b414e 100644 --- a/sep490-frontend/src/app/modules/authorization/authorization.module.ts +++ b/sep490-frontend/src/app/modules/authorization/authorization.module.ts @@ -5,7 +5,7 @@ import {AuthorizationRoutingModule} from './authorization-routing.module'; import {AuthorizationComponent} from './authorization.component'; import {EnterpriseUserDetailsComponent} from './components/create-user/enterprise-user-details.component'; import {UsersComponent} from './components/users/users.component'; -import {UserService} from './services/user.service'; +import {EnterpriseUserService} from './services/enterprise-user.service'; @NgModule({ declarations: [ @@ -14,6 +14,6 @@ import {UserService} from './services/user.service'; UsersComponent ], imports: [SharedModule, AuthorizationRoutingModule], - providers: [UserService] + providers: [EnterpriseUserService] }) export class AuthorizationModule {} diff --git a/sep490-frontend/src/app/modules/authorization/components/create-user/enterprise-user-details.component.ts b/sep490-frontend/src/app/modules/authorization/components/create-user/enterprise-user-details.component.ts index 1b301054..bd61c1ba 100644 --- a/sep490-frontend/src/app/modules/authorization/components/create-user/enterprise-user-details.component.ts +++ b/sep490-frontend/src/app/modules/authorization/components/create-user/enterprise-user-details.component.ts @@ -25,7 +25,7 @@ import { BuildingPermission, EnterpriseUserDetails } from '../../models/enterprise-user'; -import {UserService} from '../../services/user.service'; +import {EnterpriseUserService} from '../../services/enterprise-user.service'; import {MultiSelectChangeEvent} from 'primeng/multiselect'; import {SelectChangeEvent} from 'primeng/select'; @@ -125,7 +125,7 @@ export class EnterpriseUserDetailsComponent extends AbstractFormComponent; systemPreferredColorTheme: ThemeType; userPreferredColorTheme: ThemeType; - constructor(private readonly config: PrimeNG) { + constructor( + private readonly config: PrimeNG, + private readonly userService: UserService + ) { const themeOptions = { prefix: 'p', darkModeSelector: 'system', @@ -73,7 +76,7 @@ export class ThemeService { initTheme(): void { if (this.isThemeConfigured()) { this.config.theme.set(this.userPreferredColorTheme); - if (localStorage.getItem(this.LOCAL_STORAGE_KEY) === this.DARK_MODE) { + if (localStorage.getItem(this.LOCAL_STORAGE_KEY) === Theme[Theme.DARK]) { document.querySelector('html')?.classList.add(this.TOKEN); } return; @@ -87,7 +90,7 @@ export class ThemeService { isDarkMode(): Observable { if (this.isThemeConfigured()) { return of( - localStorage.getItem(this.LOCAL_STORAGE_KEY) === this.DARK_MODE + localStorage.getItem(this.LOCAL_STORAGE_KEY) === Theme[Theme.DARK] ); } return this.systemPreferredColorThemeChanged; @@ -96,13 +99,23 @@ export class ThemeService { toggleLightDark(): void { this.config.theme.set(this.userPreferredColorTheme); if (document.querySelector('html')?.classList.contains(this.TOKEN)) { - localStorage.setItem(this.LOCAL_STORAGE_KEY, this.LIGHT_MODE); + localStorage.setItem(this.LOCAL_STORAGE_KEY, Theme[Theme.LIGHT]); + this.userService.changeTheme(Theme[Theme.LIGHT]).subscribe(); } else { - localStorage.setItem(this.LOCAL_STORAGE_KEY, this.DARK_MODE); + localStorage.setItem(this.LOCAL_STORAGE_KEY, Theme[Theme.DARK]); + this.userService.changeTheme(Theme[Theme.DARK]).subscribe(); } document.querySelector('html')?.classList.toggle(this.TOKEN); } + setTheme(theme: keyof typeof Theme): void { + if (theme === Theme[Theme.SYSTEM]) { + localStorage.removeItem(this.LOCAL_STORAGE_KEY); + return; + } + localStorage.setItem(this.LOCAL_STORAGE_KEY, theme); + } + private isThemeConfigured(): boolean { return !!localStorage.getItem(this.LOCAL_STORAGE_KEY); } diff --git a/sep490-frontend/src/app/modules/shared/enums/user-language.enum.ts b/sep490-frontend/src/app/modules/shared/enums/user-language.enum.ts new file mode 100644 index 00000000..c2d05bf3 --- /dev/null +++ b/sep490-frontend/src/app/modules/shared/enums/user-language.enum.ts @@ -0,0 +1,5 @@ +export enum UserLanguage { + VI = 'vi-VN', + EN = 'en-US', + ZH = 'zh-CN' +} diff --git a/sep490-frontend/src/app/modules/shared/interceptor/language.interceptor.ts b/sep490-frontend/src/app/modules/shared/interceptor/language.interceptor.ts deleted file mode 100644 index 2a61460b..00000000 --- a/sep490-frontend/src/app/modules/shared/interceptor/language.interceptor.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {Injectable} from '@angular/core'; -import { - HttpEvent, - HttpHandler, - HttpInterceptor, - HttpRequest -} from '@angular/common/http'; -import {TranslateService} from '@ngx-translate/core'; -import {Observable} from 'rxjs'; - -@Injectable({providedIn: 'root'}) -export class LanguageInterceptor implements HttpInterceptor { - constructor(private readonly languageService: TranslateService) {} - - intercept( - req: HttpRequest, - next: HttpHandler - ): Observable> { - const language = this.languageService.currentLang ?? 'vi'; - const modifiedReq = req.clone({ - setHeaders: {'Accept-Language': language} - }); - return next.handle(modifiedReq); - } -} diff --git a/sep490-frontend/src/app/modules/shared/models/user-configs.ts b/sep490-frontend/src/app/modules/shared/models/user-configs.ts new file mode 100644 index 00000000..c3faa51e --- /dev/null +++ b/sep490-frontend/src/app/modules/shared/models/user-configs.ts @@ -0,0 +1,12 @@ +import {UserLanguage} from '../enums/user-language.enum'; + +export interface UserConfigs { + language: UserLanguage; + theme: keyof typeof Theme; +} + +export enum Theme { + DARK, + LIGHT, + SYSTEM +} diff --git a/sep490-frontend/src/app/services/user.service.ts b/sep490-frontend/src/app/services/user.service.ts new file mode 100644 index 00000000..26229e36 --- /dev/null +++ b/sep490-frontend/src/app/services/user.service.ts @@ -0,0 +1,33 @@ +import {HttpClient} from '@angular/common/http'; +import {Injectable} from '@angular/core'; +import {Observable} from 'rxjs'; +import {AppRoutingConstants} from '../app-routing.constant'; +import {UserLanguage} from '../modules/shared/enums/user-language.enum'; +import {UserConfigs} from '../modules/shared/models/user-configs'; + +@Injectable({ + providedIn: 'root' +}) +export class UserService { + constructor(private readonly httpClient: HttpClient) {} + + get userConfigs(): Observable { + return this.httpClient.get( + `${AppRoutingConstants.IDP_API_URL}/user/configs` + ); + } + + changeLanguage(language: UserLanguage): Observable { + return this.httpClient.put( + `${AppRoutingConstants.IDP_API_URL}/user/language/${language}`, + {} + ); + } + + changeTheme(theme: string): Observable { + return this.httpClient.put( + `${AppRoutingConstants.IDP_API_URL}/user/theme/${theme}`, + {} + ); + } +} diff --git a/sep490-idp/src/main/java/green_buildings/idp/dto/UserConfigs.java b/sep490-idp/src/main/java/green_buildings/idp/dto/UserConfigs.java new file mode 100644 index 00000000..7cf13354 --- /dev/null +++ b/sep490-idp/src/main/java/green_buildings/idp/dto/UserConfigs.java @@ -0,0 +1,10 @@ +package green_buildings.idp.dto; + +import lombok.Builder; + +@Builder +public record UserConfigs( + String language, + String theme +) { +} diff --git a/sep490-idp/src/main/java/green_buildings/idp/entity/UserEntity.java b/sep490-idp/src/main/java/green_buildings/idp/entity/UserEntity.java index bf7c503a..571af3ed 100644 --- a/sep490-idp/src/main/java/green_buildings/idp/entity/UserEntity.java +++ b/sep490-idp/src/main/java/green_buildings/idp/entity/UserEntity.java @@ -80,6 +80,12 @@ public class UserEntity extends AbstractAuditableEntity { @Column(name = "last_name", length = 100) private String lastName; + @Column(name = "locale", length = 5) + private String locale; + + @Column(name = "theme", length = 16) + private String theme; + @Column(name = "deleted") private boolean deleted; diff --git a/sep490-idp/src/main/java/green_buildings/idp/rest/UserResource.java b/sep490-idp/src/main/java/green_buildings/idp/rest/UserResource.java new file mode 100644 index 00000000..648f8512 --- /dev/null +++ b/sep490-idp/src/main/java/green_buildings/idp/rest/UserResource.java @@ -0,0 +1,71 @@ +package green_buildings.idp.rest; + +import commons.springfw.impl.securities.UserContextData; +import green_buildings.commons.api.enums.UserLocale; +import green_buildings.commons.api.security.UserRole; +import green_buildings.idp.dto.UserConfigs; +import green_buildings.idp.service.UserService; +import jakarta.annotation.security.RolesAllowed; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Arrays; + +@RestController +@RequestMapping("/api/user") +@RequiredArgsConstructor +@RolesAllowed({UserRole.RoleNameConstant.ENTERPRISE_OWNER, + UserRole.RoleNameConstant.ENTERPRISE_EMPLOYEE, + UserRole.RoleNameConstant.SYSTEM_ADMIN}) +public class UserResource { + + private final UserService userService; + + @GetMapping("/configs") + public ResponseEntity getLanguage(@AuthenticationPrincipal UserContextData userContextData) { + var userEntity = userService.findByEmail(userContextData.getUsername()).orElseThrow(); + var userLocaleResponse = UserConfigs + .builder() + .language(UserLocale.fromCode(userEntity.getLocale()).getCode()) + .theme(userEntity.getTheme()) + .build(); + return ResponseEntity.ok(userLocaleResponse); + } + + @PutMapping("/theme/{theme}") + public ResponseEntity changeTheme(@PathVariable("theme") String theme, + @AuthenticationPrincipal UserContextData userContextData) { + var userEntity = userService.findByEmail(userContextData.getUsername()).orElseThrow(); + + userEntity.setTheme(theme); + + userService.update(userEntity); + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); + } + + @PutMapping("/language/{language}") + public ResponseEntity changeLanguage(@PathVariable("language") String locale, + @AuthenticationPrincipal UserContextData userContextData) { + if (Arrays.stream(UserLocale.values()) + .map(UserLocale::getCode) + .noneMatch(code -> code.equals(locale)) + ) { + return ResponseEntity.badRequest().build(); + } + + var userEntity = userService.findByEmail(userContextData.getUsername()).orElseThrow(); + + userEntity.setLocale(locale); + + userService.update(userEntity); + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); + } + +} diff --git a/sep490-idp/src/main/java/green_buildings/idp/security/MvcUserContextData.java b/sep490-idp/src/main/java/green_buildings/idp/security/MvcUserContextData.java index abd6dac8..e074b274 100644 --- a/sep490-idp/src/main/java/green_buildings/idp/security/MvcUserContextData.java +++ b/sep490-idp/src/main/java/green_buildings/idp/security/MvcUserContextData.java @@ -19,6 +19,7 @@ public MvcUserContextData(@NotNull UserEntity userEntity, super(userEntity.getEmail(), userEntity.getEnterprise().getId(), userEntity.getPassword(), + userEntity.getLocale(), List.copyOf(authorities), List.copyOf(permissions)); this.userEntity = userEntity; diff --git a/sep490-idp/src/main/java/green_buildings/idp/service/UserService.java b/sep490-idp/src/main/java/green_buildings/idp/service/UserService.java index 7041f41c..67399de5 100644 --- a/sep490-idp/src/main/java/green_buildings/idp/service/UserService.java +++ b/sep490-idp/src/main/java/green_buildings/idp/service/UserService.java @@ -2,13 +2,14 @@ import green_buildings.commons.api.dto.SearchCriteriaDTO; import green_buildings.commons.api.exceptions.BusinessErrorResponse; -import org.springframework.data.domain.Page; -import org.springframework.ui.Model; import green_buildings.idp.dto.SignupDTO; import green_buildings.idp.dto.SignupResult; import green_buildings.idp.dto.UserCriteriaDTO; import green_buildings.idp.entity.UserEntity; +import org.springframework.data.domain.Page; +import org.springframework.ui.Model; +import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -26,4 +27,8 @@ public interface UserService { void createOrUpdateEnterpriseUser(UserEntity user); UserEntity getEnterpriseUserDetail(UUID id); + + Optional findByEmail(String email); + + void update(UserEntity user); } diff --git a/sep490-idp/src/main/java/green_buildings/idp/service/impl/LoginService.java b/sep490-idp/src/main/java/green_buildings/idp/service/impl/LoginService.java index d9c4a694..e52f2541 100644 --- a/sep490-idp/src/main/java/green_buildings/idp/service/impl/LoginService.java +++ b/sep490-idp/src/main/java/green_buildings/idp/service/impl/LoginService.java @@ -1,32 +1,33 @@ package green_buildings.idp.service.impl; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; import green_buildings.commons.api.dto.auth.BuildingPermissionDTO; import green_buildings.idp.entity.UserEntity; import green_buildings.idp.repository.BuildingPermissionRepository; -import green_buildings.idp.security.PasskeyAuthenticationToken; import green_buildings.idp.security.MvcUserContextData; +import green_buildings.idp.security.PasskeyAuthenticationToken; import green_buildings.idp.utils.IdpSecurityUtils; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; @Component @RequiredArgsConstructor @Transactional(rollbackFor = Throwable.class) public class LoginService { - + private final HttpServletRequest request; private final HttpServletResponse response; private final BuildingPermissionRepository buildingPermissionRepository; - + public void login(UserEntity user) { var permissions = buildingPermissionRepository.findAllByUserId(user.getId()); var buildingPermissions = permissions.stream() .map(e -> new BuildingPermissionDTO(e.getBuilding(), e.getRole())) .toList(); - var auth = new PasskeyAuthenticationToken(new MvcUserContextData(user, IdpSecurityUtils.getAuthoritiesFromUserRole(user), buildingPermissions)); + var userContextData = new MvcUserContextData(user, IdpSecurityUtils.getAuthoritiesFromUserRole(user), buildingPermissions); + var auth = new PasskeyAuthenticationToken(userContextData); IdpSecurityUtils.storeAuthenticationToContext(auth, request, response); } } diff --git a/sep490-idp/src/main/java/green_buildings/idp/service/impl/UserInfoService.java b/sep490-idp/src/main/java/green_buildings/idp/service/impl/UserInfoService.java index 688b8b89..82f6004a 100644 --- a/sep490-idp/src/main/java/green_buildings/idp/service/impl/UserInfoService.java +++ b/sep490-idp/src/main/java/green_buildings/idp/service/impl/UserInfoService.java @@ -1,13 +1,13 @@ package green_buildings.idp.service.impl; -import lombok.RequiredArgsConstructor; -import org.springframework.security.oauth2.core.oidc.OidcUserInfo; -import org.springframework.stereotype.Service; import green_buildings.commons.api.dto.auth.BuildingPermissionDTO; import green_buildings.idp.entity.UserEnterpriseEntity; import green_buildings.idp.entity.UserEntity; import green_buildings.idp.repository.BuildingPermissionRepository; import green_buildings.idp.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.oauth2.core.oidc.OidcUserInfo; +import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.Map; @@ -53,9 +53,9 @@ public Map getCustomClaimsForJwtAuthenticationToken(String email )) .toList(); claims.put("enterpriseId", Optional.ofNullable(user.getEnterprise()) - .map(UserEnterpriseEntity::getEnterprise) - .map(UUID::toString) - .orElse(null)); + .map(UserEnterpriseEntity::getEnterprise) + .map(UUID::toString) + .orElse(null)); claims.put("permissions", buildingPermissions); return claims; } diff --git a/sep490-idp/src/main/java/green_buildings/idp/service/impl/UserServiceImpl.java b/sep490-idp/src/main/java/green_buildings/idp/service/impl/UserServiceImpl.java index 00d3410e..d1a11d04 100644 --- a/sep490-idp/src/main/java/green_buildings/idp/service/impl/UserServiceImpl.java +++ b/sep490-idp/src/main/java/green_buildings/idp/service/impl/UserServiceImpl.java @@ -39,6 +39,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -229,4 +230,14 @@ public UserEntity getEnterpriseUserDetail(UUID id) { return userRepo.findByIdWithBuildingPermissions(id).orElseThrow(); } + @Override + public Optional findByEmail(String email) { + return userRepo.findByEmail(email); + } + + @Override + public void update(UserEntity user) { + userRepo.save(user); + } + } diff --git a/sep490-idp/src/main/resources/db/migration/V0.0.1.8__UserConfigs.sql b/sep490-idp/src/main/resources/db/migration/V0.0.1.8__UserConfigs.sql new file mode 100644 index 00000000..9a3e6fa3 --- /dev/null +++ b/sep490-idp/src/main/resources/db/migration/V0.0.1.8__UserConfigs.sql @@ -0,0 +1,3 @@ +ALTER TABLE users + ADD COLUMN locale VARCHAR(5) NOT NULL DEFAULT 'vi-VN', + ADD COLUMN theme VARCHAR(16) NOT NULL DEFAULT 'system'; \ No newline at end of file