feature/admin #5
|
|
@ -16,15 +16,22 @@ repositories {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
// spring
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||||
|
|
||||||
// thymeleaf
|
// thymeleaf
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
||||||
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
|
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
|
||||||
|
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:3.3.0'
|
||||||
|
|
||||||
// jpa
|
//querydsl
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
implementation "com.querydsl:querydsl-jpa:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
|
||||||
|
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
|
||||||
|
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
|
||||||
|
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
|
||||||
|
|
||||||
// security
|
// security
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ services:
|
||||||
environment:
|
environment:
|
||||||
- TZ=Asia/Seoul
|
- TZ=Asia/Seoul
|
||||||
- MARIADB_ROOT_PASSWORD=root
|
- MARIADB_ROOT_PASSWORD=root
|
||||||
- MARIADB_DATABASE=login
|
- MARIADB_DATABASE=admin-system
|
||||||
- MARIADB_USER=admin
|
- MARIADB_USER=admin
|
||||||
- MARIADB_PASSWORD=1234
|
- MARIADB_PASSWORD=1234
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,59 @@
|
||||||
package com.bpgroup.poc.admin.app.login;
|
package com.bpgroup.poc.admin.app.login;
|
||||||
|
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@ToString
|
|
||||||
public class LoginResult {
|
public class LoginResult {
|
||||||
private Long id;
|
private Long id;
|
||||||
private String loginId;
|
private String loginId;
|
||||||
private String name;
|
private String name;
|
||||||
private String email;
|
private String email;
|
||||||
|
|
||||||
public static LoginResult of(Long id, String loginId, String name, String email) {
|
private Set<MenuInfo> menus = new HashSet<>();
|
||||||
|
|
||||||
|
public static LoginResult of(Long id, String loginId, String name, String email, Set<MenuInfo> menus) {
|
||||||
LoginResult loginResult = new LoginResult();
|
LoginResult loginResult = new LoginResult();
|
||||||
loginResult.id = id;
|
loginResult.id = id;
|
||||||
loginResult.loginId = loginId;
|
loginResult.loginId = loginId;
|
||||||
loginResult.name = name;
|
loginResult.name = name;
|
||||||
loginResult.email = email;
|
loginResult.email = email;
|
||||||
|
loginResult.menus = menus;
|
||||||
return loginResult;
|
return loginResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@ToString
|
||||||
|
@EqualsAndHashCode
|
||||||
|
public static class MenuInfo {
|
||||||
|
private String menuGroupUri;
|
||||||
|
private String menuGroupName;
|
||||||
|
private Integer menuGroupSortOrder;
|
||||||
|
private String menuUri;
|
||||||
|
private String menuName;
|
||||||
|
private Integer menuSortOrder;
|
||||||
|
|
||||||
|
public static MenuInfo of(
|
||||||
|
String menuGroupUri,
|
||||||
|
String menuGroupName,
|
||||||
|
Integer menuGroupSortOrder,
|
||||||
|
String menuUri,
|
||||||
|
String menuName,
|
||||||
|
Integer menuSortOrder
|
||||||
|
) {
|
||||||
|
MenuInfo menuInfo = new MenuInfo();
|
||||||
|
menuInfo.menuGroupUri = menuGroupUri;
|
||||||
|
menuInfo.menuGroupName = menuGroupName;
|
||||||
|
menuInfo.menuGroupSortOrder = menuGroupSortOrder;
|
||||||
|
menuInfo.menuUri = menuUri;
|
||||||
|
menuInfo.menuName = menuName;
|
||||||
|
menuInfo.menuSortOrder = menuSortOrder;
|
||||||
|
return menuInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,29 @@
|
||||||
package com.bpgroup.poc.admin.app.login;
|
package com.bpgroup.poc.admin.app.login;
|
||||||
|
|
||||||
import com.bpgroup.poc.admin.app.login.exception.AdministratorNotFoundException;
|
import com.bpgroup.poc.admin.app.login.exception.AdministratorNotFoundException;
|
||||||
|
import com.bpgroup.poc.admin.app.login.exception.DoNotHaveAnyMenuException;
|
||||||
import com.bpgroup.poc.admin.app.login.exception.InvalidPasswordException;
|
import com.bpgroup.poc.admin.app.login.exception.InvalidPasswordException;
|
||||||
import com.bpgroup.poc.admin.domain.admin.Administrator;
|
import com.bpgroup.poc.admin.domain.admin.entity.Administrator;
|
||||||
import com.bpgroup.poc.admin.domain.admin.AdministratorRepository;
|
import com.bpgroup.poc.admin.domain.admin.entity.AdministratorRepository;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@Transactional
|
||||||
public class LoginService {
|
public class LoginService {
|
||||||
|
|
||||||
private final AdministratorRepository loginRepository;
|
private final AdministratorRepository loginRepository;
|
||||||
private final PasswordEncoder passwordEncoder;
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
public LoginResult login(String loginId, String pwd) throws AdministratorNotFoundException, InvalidPasswordException {
|
public LoginResult login(String loginId, String pwd) throws AdministratorNotFoundException, InvalidPasswordException, DoNotHaveAnyMenuException {
|
||||||
|
|
||||||
Optional<Administrator> administrator = loginRepository.findByLoginId(loginId);
|
Optional<Administrator> administrator = loginRepository.findByLoginId(loginId);
|
||||||
if (administrator.isEmpty()) {
|
if (administrator.isEmpty()) {
|
||||||
|
|
@ -32,8 +38,31 @@ public class LoginService {
|
||||||
administrator.get().getId(),
|
administrator.get().getId(),
|
||||||
administrator.get().getLoginId(),
|
administrator.get().getLoginId(),
|
||||||
administrator.get().getName(),
|
administrator.get().getName(),
|
||||||
administrator.get().getLoginId()
|
administrator.get().getLoginId(),
|
||||||
|
getMenus(administrator.get())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static LinkedHashSet<LoginResult.MenuInfo> getMenus(Administrator administrator) throws DoNotHaveAnyMenuException {
|
||||||
|
try {
|
||||||
|
return administrator.getAdministratorRole().getRole().getRoleMenus().stream()
|
||||||
|
.map(roleMenu -> LoginResult.MenuInfo.of(
|
||||||
|
roleMenu.getMenu().getMenuGroup().getUri(),
|
||||||
|
roleMenu.getMenu().getMenuGroup().getName(),
|
||||||
|
roleMenu.getMenu().getMenuGroup().getSortOrder(),
|
||||||
|
roleMenu.getMenu().getUri(),
|
||||||
|
roleMenu.getMenu().getName(),
|
||||||
|
roleMenu.getMenu().getSortOrder()
|
||||||
|
))
|
||||||
|
.sorted(
|
||||||
|
Comparator
|
||||||
|
.comparingInt(LoginResult.MenuInfo::getMenuGroupSortOrder)
|
||||||
|
.thenComparingInt(LoginResult.MenuInfo::getMenuSortOrder)
|
||||||
|
)
|
||||||
|
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
throw new DoNotHaveAnyMenuException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.bpgroup.poc.admin.app.login.exception;
|
||||||
|
|
||||||
|
public class DoNotHaveAnyMenuException extends Exception {
|
||||||
|
public DoNotHaveAnyMenuException() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.bpgroup.poc.admin.config;
|
||||||
|
|
||||||
|
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||||
|
import jakarta.persistence.EntityManager;
|
||||||
|
import jakarta.persistence.PersistenceContext;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class QuerydslConfig {
|
||||||
|
|
||||||
|
@PersistenceContext
|
||||||
|
private EntityManager entityManager;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public JPAQueryFactory jpaQueryFactory() {
|
||||||
|
return new JPAQueryFactory(entityManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.bpgroup.poc.admin.domain;
|
||||||
|
|
||||||
|
public class DomainException extends RuntimeException {
|
||||||
|
public DomainException() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
package com.bpgroup.poc.admin.domain.admin;
|
|
||||||
|
|
||||||
import com.bpgroup.poc.admin.domain.BaseEntity;
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Entity
|
|
||||||
@Table(name = "administrator")
|
|
||||||
public class Administrator extends BaseEntity {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
@Column(name = "login_id")
|
|
||||||
private String loginId;
|
|
||||||
|
|
||||||
@Column(name = "password")
|
|
||||||
private String password;
|
|
||||||
|
|
||||||
@Column(name = "email")
|
|
||||||
private String email;
|
|
||||||
|
|
||||||
@Column(name = "name")
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
package com.bpgroup.poc.admin.domain.admin.entity;
|
||||||
|
|
||||||
|
import com.bpgroup.poc.admin.domain.BaseEntity;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Entity
|
||||||
|
@Table(name = "administrator")
|
||||||
|
public class Administrator extends BaseEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(name = "login_id", length = 100, nullable = false)
|
||||||
|
private String loginId;
|
||||||
|
|
||||||
|
@Column(name = "password", length = 255, nullable = false)
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
@Column(name = "email", length = 100, nullable = false)
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@Column(name = "name", length = 100, nullable = false)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@OneToOne(mappedBy = "administrator", fetch = FetchType.LAZY)
|
||||||
|
private AdministratorRole administratorRole;
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
public static Administrator of(String loginId, String password, String email, String name) {
|
||||||
|
Administrator administrator = new Administrator();
|
||||||
|
administrator.loginId = loginId;
|
||||||
|
administrator.password = password;
|
||||||
|
administrator.email = email;
|
||||||
|
administrator.name = name;
|
||||||
|
return administrator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update(Administrator administrator) {
|
||||||
|
this.password = administrator.password;
|
||||||
|
this.email = administrator.email;
|
||||||
|
this.name = administrator.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.bpgroup.poc.admin.domain.admin;
|
package com.bpgroup.poc.admin.domain.admin.entity;
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.bpgroup.poc.admin.domain.admin.entity;
|
||||||
|
|
||||||
|
import com.bpgroup.poc.admin.domain.BaseEntity;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Entity
|
||||||
|
@Table(name = "administrator_role")
|
||||||
|
public class AdministratorRole extends BaseEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
|
||||||
|
@JoinColumn(name = "administrator_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
|
||||||
|
private Administrator administrator;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
|
||||||
|
@JoinColumn(name = "role_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
|
||||||
|
private Role role;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
package com.bpgroup.poc.admin.domain.admin.entity;
|
||||||
|
|
||||||
|
import com.bpgroup.poc.admin.domain.BaseEntity;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Entity
|
||||||
|
@Table(name = "menu")
|
||||||
|
public class Menu extends BaseEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(name = "uri", length = 100, nullable = false)
|
||||||
|
private String uri;
|
||||||
|
|
||||||
|
@Column(name = "name", length = 100, nullable = false)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Column(name = "sort_order", nullable = false)
|
||||||
|
private Integer sortOrder;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
|
||||||
|
@JoinColumn(name = "menu_group_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
|
||||||
|
private MenuGroup menuGroup;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.bpgroup.poc.admin.domain.admin.entity;
|
||||||
|
|
||||||
|
import com.bpgroup.poc.admin.domain.BaseEntity;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Entity
|
||||||
|
@Table(name = "menu_group")
|
||||||
|
public class MenuGroup extends BaseEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(name = "uri", length = 100, nullable = false)
|
||||||
|
private String uri;
|
||||||
|
|
||||||
|
@Column(name = "name", length = 100, nullable = false)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Column(name = "sort_order", nullable = false)
|
||||||
|
private Integer sortOrder;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.bpgroup.poc.admin.domain.admin.entity;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
public interface MenuRepository extends JpaRepository<Menu, Long> {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package com.bpgroup.poc.admin.domain.admin.entity;
|
||||||
|
|
||||||
|
import com.bpgroup.poc.admin.domain.BaseEntity;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Entity
|
||||||
|
@Table(name = "role")
|
||||||
|
public class Role extends BaseEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(name = "name", length = 100, nullable = false)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Column(name = "description")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "role", fetch = FetchType.LAZY)
|
||||||
|
private Set<RoleMenu> roleMenus = new HashSet<>();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.bpgroup.poc.admin.domain.admin.entity;
|
||||||
|
|
||||||
|
import com.bpgroup.poc.admin.domain.BaseEntity;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Entity
|
||||||
|
@Table(name = "role_menu")
|
||||||
|
public class RoleMenu extends BaseEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
|
||||||
|
@JoinColumn(name = "role_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
|
||||||
|
private Role role;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
|
||||||
|
@JoinColumn(name = "menu_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
|
||||||
|
private Menu menu;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.bpgroup.poc.admin.domain.admin.entity;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
public interface RoleRepository extends JpaRepository<Role, Long> {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
package com.bpgroup.poc.admin.domain.admin.service;
|
||||||
|
|
||||||
|
import com.bpgroup.poc.admin.domain.admin.entity.Administrator;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@ToString
|
||||||
|
public class AdministratorCreateCommand {
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
private String loginId;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public static AdministratorCreateCommand of(String loginId, String password, String email, String name) {
|
||||||
|
AdministratorCreateCommand command = new AdministratorCreateCommand();
|
||||||
|
command.loginId = loginId;
|
||||||
|
command.password = password;
|
||||||
|
command.email = email;
|
||||||
|
command.name = name;
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Administrator toEntity() {
|
||||||
|
return Administrator.builder()
|
||||||
|
.loginId(loginId)
|
||||||
|
.password(password)
|
||||||
|
.email(email)
|
||||||
|
.name(name)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
package com.bpgroup.poc.admin.domain.admin.service;
|
||||||
|
|
||||||
|
import com.bpgroup.poc.admin.domain.admin.entity.Administrator;
|
||||||
|
import com.bpgroup.poc.admin.domain.admin.entity.AdministratorRepository;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Validated
|
||||||
|
@Transactional
|
||||||
|
public class AdministratorService {
|
||||||
|
|
||||||
|
private final AdministratorRepository administratorRepository;
|
||||||
|
|
||||||
|
public Long create(
|
||||||
|
@NotNull @Valid AdministratorCreateCommand command
|
||||||
|
) {
|
||||||
|
// 대소문자 구별이 필요한 경우 인증 부분과 유효성 검사 부분 모두 변경이 필요하다.
|
||||||
|
Optional<Administrator> administrator = administratorRepository.findByLoginId(command.getLoginId());
|
||||||
|
if (administrator.isPresent()) {
|
||||||
|
throw new DuplicationAdministratorException(command.getLoginId());
|
||||||
|
}
|
||||||
|
|
||||||
|
Administrator createAdministrator = administratorRepository.save(command.toEntity());
|
||||||
|
return createAdministrator.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update(
|
||||||
|
@NotNull @Valid AdministratorUpdateCommand command
|
||||||
|
) {
|
||||||
|
Optional<Administrator> administrator = administratorRepository.findById(command.getId());
|
||||||
|
if (administrator.isEmpty()) {
|
||||||
|
throw new NotFoundAdministratorException();
|
||||||
|
}
|
||||||
|
|
||||||
|
administrator.get().update(command.toEntity());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete(
|
||||||
|
@NotNull Long id
|
||||||
|
) {
|
||||||
|
administratorRepository.deleteById(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
package com.bpgroup.poc.admin.domain.admin.service;
|
||||||
|
|
||||||
|
import com.bpgroup.poc.admin.domain.admin.entity.Administrator;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@ToString
|
||||||
|
public class AdministratorUpdateCommand {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public static AdministratorUpdateCommand of(Long id, String password, String email, String name) {
|
||||||
|
AdministratorUpdateCommand command = new AdministratorUpdateCommand();
|
||||||
|
command.id = id;
|
||||||
|
command.password = password;
|
||||||
|
command.email = email;
|
||||||
|
command.name = name;
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Administrator toEntity() {
|
||||||
|
return Administrator.builder()
|
||||||
|
.password(password)
|
||||||
|
.email(email)
|
||||||
|
.name(name)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.bpgroup.poc.admin.domain.admin.service;
|
||||||
|
|
||||||
|
public class DuplicationAdministratorException extends RuntimeException {
|
||||||
|
public DuplicationAdministratorException(String loginId) {
|
||||||
|
super("이미 존재하는 아이디 입니다. : " + loginId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.bpgroup.poc.admin.domain.admin.service;
|
||||||
|
|
||||||
|
import com.bpgroup.poc.admin.domain.DomainException;
|
||||||
|
|
||||||
|
public class NotFoundAdministratorException extends DomainException {
|
||||||
|
public NotFoundAdministratorException() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
package com.bpgroup.poc.admin.security;
|
|
||||||
|
|
||||||
import jakarta.servlet.FilterChain;
|
|
||||||
import jakarta.servlet.ServletException;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import org.springframework.security.web.csrf.CsrfToken;
|
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class CsrfCookieFilter extends OncePerRequestFilter {
|
|
||||||
@Override
|
|
||||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
|
||||||
CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
|
|
||||||
if (csrfToken != null) {
|
|
||||||
response.setHeader(csrfToken.getHeaderName(), csrfToken.getToken());
|
|
||||||
}
|
|
||||||
|
|
||||||
filterChain.doFilter(request, response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,19 +2,18 @@ package com.bpgroup.poc.admin.security;
|
||||||
|
|
||||||
import com.bpgroup.poc.admin.common.FormatHelper;
|
import com.bpgroup.poc.admin.common.FormatHelper;
|
||||||
import com.bpgroup.poc.admin.security.authentication.AuthenticationFailException;
|
import com.bpgroup.poc.admin.security.authentication.AuthenticationFailException;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
|
||||||
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
|
|
||||||
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
|
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
|
|
||||||
private static final String LOGIN_PATH = "/login";
|
private static final String LOGIN_PATH = "/login";
|
||||||
|
|
@ -23,17 +22,6 @@ public class SecurityConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
|
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||||
// CSRF 설정
|
|
||||||
CsrfTokenRequestAttributeHandler csrfTokenRequestAttributeHandler = new CsrfTokenRequestAttributeHandler();
|
|
||||||
csrfTokenRequestAttributeHandler.setCsrfRequestAttributeName("_csrf");
|
|
||||||
|
|
||||||
http.csrf(t -> {
|
|
||||||
t.csrfTokenRequestHandler(csrfTokenRequestAttributeHandler)
|
|
||||||
.ignoringRequestMatchers("/common/modal/**")
|
|
||||||
.ignoringRequestMatchers(LOGIN_PATH, LOGOUT_PATH, ERROR_PATH) // CSRF 무시 URL 설정
|
|
||||||
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); // CSRF 토큰을 쿠키에 저장 및 사용 가능
|
|
||||||
}).addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class); // 로그인이 완료된 후 CSRF Filter 를 실행
|
|
||||||
|
|
||||||
// 인증 설정
|
// 인증 설정
|
||||||
http.authorizeHttpRequests(c -> c
|
http.authorizeHttpRequests(c -> c
|
||||||
.requestMatchers("/css/**", "/images/**", "/js/**").permitAll()
|
.requestMatchers("/css/**", "/images/**", "/js/**").permitAll()
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
package com.bpgroup.poc.admin.security.authentication;
|
package com.bpgroup.poc.admin.security.authentication;
|
||||||
|
|
||||||
import com.bpgroup.poc.admin.app.login.LoginResult;
|
import com.bpgroup.poc.admin.app.login.LoginResult;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import lombok.ToString;
|
||||||
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public class AuthenticationDetail {
|
public class AuthenticationDetail {
|
||||||
|
|
@ -11,12 +15,74 @@ public class AuthenticationDetail {
|
||||||
private String name;
|
private String name;
|
||||||
private String email;
|
private String email;
|
||||||
|
|
||||||
|
private MultiValueMap<MenuOneDepth, MenuTwoDepth> menus = new LinkedMultiValueMap<>();
|
||||||
|
|
||||||
public static AuthenticationDetail from(LoginResult result) {
|
public static AuthenticationDetail from(LoginResult result) {
|
||||||
AuthenticationDetail authenticationDetail = new AuthenticationDetail();
|
AuthenticationDetail authenticationDetail = new AuthenticationDetail();
|
||||||
authenticationDetail.id = result.getId();
|
authenticationDetail.id = result.getId();
|
||||||
authenticationDetail.loginId = result.getLoginId();
|
authenticationDetail.loginId = result.getLoginId();
|
||||||
authenticationDetail.name = result.getName();
|
authenticationDetail.name = result.getName();
|
||||||
authenticationDetail.email = result.getEmail();
|
authenticationDetail.email = result.getEmail();
|
||||||
|
|
||||||
|
result.getMenus().forEach(menu -> authenticationDetail.menus.add(
|
||||||
|
MenuOneDepth.of(
|
||||||
|
menu.getMenuGroupUri(),
|
||||||
|
menu.getMenuGroupName(),
|
||||||
|
menu.getMenuGroupSortOrder()
|
||||||
|
),
|
||||||
|
MenuTwoDepth.of(
|
||||||
|
menu.getMenuUri(),
|
||||||
|
menu.getMenuName(),
|
||||||
|
menu.getMenuSortOrder()
|
||||||
|
)
|
||||||
|
));
|
||||||
|
|
||||||
return authenticationDetail;
|
return authenticationDetail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EqualsAndHashCode annotattion이 없을 경우 MultiValueMap을 사용할 때 중복된 key를 찾지 못함
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@ToString
|
||||||
|
@EqualsAndHashCode
|
||||||
|
private static class MenuOneDepth {
|
||||||
|
private String uri;
|
||||||
|
private String name;
|
||||||
|
private Integer sortOrder;
|
||||||
|
|
||||||
|
public static MenuOneDepth of(
|
||||||
|
String uri,
|
||||||
|
String name,
|
||||||
|
Integer sortOrder
|
||||||
|
) {
|
||||||
|
MenuOneDepth menu = new MenuOneDepth();
|
||||||
|
menu.uri = uri;
|
||||||
|
menu.name = name;
|
||||||
|
menu.sortOrder = sortOrder;
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@ToString
|
||||||
|
private static class MenuTwoDepth {
|
||||||
|
private String uri;
|
||||||
|
private String name;
|
||||||
|
private Integer sortOrder;
|
||||||
|
|
||||||
|
public static MenuTwoDepth of(
|
||||||
|
String uri,
|
||||||
|
String name,
|
||||||
|
Integer sortOrder
|
||||||
|
) {
|
||||||
|
MenuTwoDepth menu = new MenuTwoDepth();
|
||||||
|
menu.uri = uri;
|
||||||
|
menu.name = name;
|
||||||
|
menu.sortOrder = sortOrder;
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.bpgroup.poc.admin.security.authentication;
|
package com.bpgroup.poc.admin.security.authentication;
|
||||||
|
|
||||||
import com.bpgroup.poc.admin.app.login.exception.AdministratorNotFoundException;
|
import com.bpgroup.poc.admin.app.login.exception.AdministratorNotFoundException;
|
||||||
|
import com.bpgroup.poc.admin.app.login.exception.DoNotHaveAnyMenuException;
|
||||||
import com.bpgroup.poc.admin.app.login.exception.InvalidPasswordException;
|
import com.bpgroup.poc.admin.app.login.exception.InvalidPasswordException;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
|
@ -8,11 +9,14 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
public enum AuthenticationFailReason {
|
public enum AuthenticationFailReason {
|
||||||
WRONG_LOGIN_ID,
|
WRONG_LOGIN_ID,
|
||||||
WRONG_PASSWORD,
|
WRONG_PASSWORD,
|
||||||
|
HAVE_NO_MENU,
|
||||||
INTERNAL_ERROR;
|
INTERNAL_ERROR;
|
||||||
|
|
||||||
public static AuthenticationFailReason from(Exception e) {
|
public static AuthenticationFailReason from(Exception e) {
|
||||||
if (e instanceof AdministratorNotFoundException || e instanceof InvalidPasswordException) {
|
if (e instanceof AdministratorNotFoundException || e instanceof InvalidPasswordException) {
|
||||||
return WRONG_LOGIN_ID;
|
return WRONG_LOGIN_ID;
|
||||||
|
} else if (e instanceof DoNotHaveAnyMenuException) {
|
||||||
|
return HAVE_NO_MENU;
|
||||||
} else {
|
} else {
|
||||||
return INTERNAL_ERROR;
|
return INTERNAL_ERROR;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package com.bpgroup.poc.admin.values;
|
||||||
|
|
||||||
|
public class AdministratorMenu {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package com.bpgroup.poc.admin.web.advice;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
|
|
||||||
|
@ControllerAdvice
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class PathInfoControllerAdvice {
|
||||||
|
|
||||||
|
@ModelAttribute("pathInfo")
|
||||||
|
public PathInfo pathInfo(HttpServletRequest request) {
|
||||||
|
return new PathInfo(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public static class PathInfo {
|
||||||
|
|
||||||
|
private final String currentPath;
|
||||||
|
|
||||||
|
public PathInfo(HttpServletRequest request) {
|
||||||
|
this.currentPath = request.getServletPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isActive(String path) {
|
||||||
|
return currentPath.startsWith(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String activeClass(String path) {
|
||||||
|
return isActive(path) ? "active" : "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.bpgroup.poc.admin.web.common;
|
||||||
|
|
||||||
|
public class CommonResponse {
|
||||||
|
|
||||||
|
protected String resultCode;
|
||||||
|
protected String resultMessage;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -4,12 +4,14 @@ import com.bpgroup.poc.admin.security.authentication.AuthenticationFailReason;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
|
@RequestMapping("/login")
|
||||||
public class LoginController {
|
public class LoginController {
|
||||||
|
|
||||||
@GetMapping("/login")
|
@GetMapping
|
||||||
public String loginPage(
|
public String loginPage(
|
||||||
@RequestParam(required = false) AuthenticationFailReason error,
|
@RequestParam(required = false) AuthenticationFailReason error,
|
||||||
Model model
|
Model model
|
||||||
|
|
@ -24,7 +26,8 @@ public class LoginController {
|
||||||
private String getMessage(AuthenticationFailReason error) {
|
private String getMessage(AuthenticationFailReason error) {
|
||||||
return switch (error) {
|
return switch (error) {
|
||||||
case WRONG_LOGIN_ID, WRONG_PASSWORD -> "아이디 또는 비밀번호가 일치하지 않습니다.";
|
case WRONG_LOGIN_ID, WRONG_PASSWORD -> "아이디 또는 비밀번호가 일치하지 않습니다.";
|
||||||
case INTERNAL_ERROR -> "서버에 오류가 발생했습니다.";
|
case HAVE_NO_MENU -> "등록된 메뉴가 없습니다.\n 메뉴 등록 후 이용해주세요.";
|
||||||
|
default -> "서버에 오류가 발생했습니다.";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.bpgroup.poc.admin.web.main.admin.management;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
@RequestMapping("/admin/management")
|
||||||
|
public class AdministratorManagementController {
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public String administratorManagementPage() {
|
||||||
|
return "main/admin/management/list";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
package com.bpgroup.poc.admin.web.main.admin.management;
|
||||||
|
|
||||||
|
import com.bpgroup.poc.admin.web.main.admin.management.reqres.AdministratorFind;
|
||||||
|
import com.querydsl.core.types.Projections;
|
||||||
|
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.bpgroup.poc.admin.domain.admin.entity.QAdministrator.administrator;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AdministratorManagementQueryRepository {
|
||||||
|
|
||||||
|
private final JPAQueryFactory queryFactory;
|
||||||
|
|
||||||
|
public List<AdministratorFind.Response> findAll() {
|
||||||
|
return queryFactory.select(Projections.fields(AdministratorFind.Response.class,
|
||||||
|
administrator.id,
|
||||||
|
administrator.loginId,
|
||||||
|
administrator.email,
|
||||||
|
administrator.name))
|
||||||
|
.from(administrator)
|
||||||
|
.fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
public AdministratorFind.Response findByLoginId(String loginId) {
|
||||||
|
return queryFactory.select(Projections.fields(AdministratorFind.Response.class,
|
||||||
|
administrator.id,
|
||||||
|
administrator.loginId,
|
||||||
|
administrator.email,
|
||||||
|
administrator.name))
|
||||||
|
.from(administrator)
|
||||||
|
.where(administrator.loginId.eq(loginId))
|
||||||
|
.fetchOne();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
package com.bpgroup.poc.admin.web.main.admin.management;
|
||||||
|
|
||||||
|
import com.bpgroup.poc.admin.web.main.admin.management.reqres.AdministratorCreate;
|
||||||
|
import com.bpgroup.poc.admin.web.main.admin.management.reqres.AdministratorDelete;
|
||||||
|
import com.bpgroup.poc.admin.web.main.admin.management.reqres.AdministratorFind;
|
||||||
|
import com.bpgroup.poc.admin.web.main.admin.management.reqres.AdministratorUpdate;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.validation.BindingResult;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RequestMapping("/admin/management")
|
||||||
|
public class AdministratorManagementRestController {
|
||||||
|
|
||||||
|
private final AdministratorManagementWebService administratorManagementWebService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전체 조회
|
||||||
|
* @return 응답
|
||||||
|
*/
|
||||||
|
@GetMapping("/list")
|
||||||
|
public ResponseEntity<?> getAdministrators() {
|
||||||
|
List<AdministratorFind.Response> response = administratorManagementWebService.findAll();
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 조회
|
||||||
|
* @param loginId 관리자 ID
|
||||||
|
* @return 응답
|
||||||
|
*/
|
||||||
|
@GetMapping("/{loginId}")
|
||||||
|
public ResponseEntity<?> getAdministrator(@PathVariable @NotBlank String loginId) {
|
||||||
|
AdministratorFind.Response response = administratorManagementWebService.find(loginId);
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 관리자 등록
|
||||||
|
* @param request 요청
|
||||||
|
* @param bindingResult Validation 결과
|
||||||
|
* @return 응답
|
||||||
|
*/
|
||||||
|
@PostMapping("/create")
|
||||||
|
public ResponseEntity<?> createAdministrator(
|
||||||
|
@RequestBody @Validated AdministratorCreate.Request request,
|
||||||
|
BindingResult bindingResult
|
||||||
|
) {
|
||||||
|
AdministratorCreate.Response response = administratorManagementWebService.create(request);
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/update")
|
||||||
|
public ResponseEntity<?> updateAdministrator(
|
||||||
|
@RequestBody @Validated AdministratorUpdate.Request request,
|
||||||
|
BindingResult bindingResult
|
||||||
|
) {
|
||||||
|
administratorManagementWebService.update(request);
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 관리자 삭제
|
||||||
|
* @param request 요청
|
||||||
|
* @param bindingResult Validation 결과
|
||||||
|
* @return 응답
|
||||||
|
*/
|
||||||
|
@PostMapping("/delete")
|
||||||
|
public ResponseEntity<?> deleteAdministrator(
|
||||||
|
@RequestBody @Validated AdministratorDelete.Request request,
|
||||||
|
BindingResult bindingResult
|
||||||
|
) {
|
||||||
|
administratorManagementWebService.delete(request);
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
package com.bpgroup.poc.admin.web.main.admin.management;
|
||||||
|
|
||||||
|
import com.bpgroup.poc.admin.domain.admin.service.AdministratorCreateCommand;
|
||||||
|
import com.bpgroup.poc.admin.domain.admin.service.AdministratorService;
|
||||||
|
import com.bpgroup.poc.admin.domain.admin.service.AdministratorUpdateCommand;
|
||||||
|
import com.bpgroup.poc.admin.web.main.admin.management.reqres.AdministratorCreate;
|
||||||
|
import com.bpgroup.poc.admin.web.main.admin.management.reqres.AdministratorDelete;
|
||||||
|
import com.bpgroup.poc.admin.web.main.admin.management.reqres.AdministratorFind;
|
||||||
|
import com.bpgroup.poc.admin.web.main.admin.management.reqres.AdministratorUpdate;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Transactional
|
||||||
|
public class AdministratorManagementWebService {
|
||||||
|
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
private final AdministratorService administratorService;
|
||||||
|
private final AdministratorManagementQueryRepository administratorManagementQueryRepository;
|
||||||
|
|
||||||
|
public AdministratorCreate.Response create(AdministratorCreate.Request request) {
|
||||||
|
Long id = administratorService.create(AdministratorCreateCommand.of(
|
||||||
|
request.getLoginId(),
|
||||||
|
passwordEncoder.encode(request.getPassword()),
|
||||||
|
request.getEmail(),
|
||||||
|
request.getName()
|
||||||
|
));
|
||||||
|
|
||||||
|
return AdministratorCreate.Response.builder()
|
||||||
|
.id(id)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public AdministratorFind.Response find(@NotBlank String loginId) {
|
||||||
|
return administratorManagementQueryRepository.findByLoginId(loginId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete(AdministratorDelete.Request request) {
|
||||||
|
administratorService.delete(request.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update(AdministratorUpdate.Request request) {
|
||||||
|
administratorService.update(AdministratorUpdateCommand.of(
|
||||||
|
request.getId(),
|
||||||
|
passwordEncoder.encode(request.getPassword()),
|
||||||
|
request.getEmail(),
|
||||||
|
request.getName()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<AdministratorFind.Response> findAll() {
|
||||||
|
return administratorManagementQueryRepository.findAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
package com.bpgroup.poc.admin.web.main.admin.management.reqres;
|
||||||
|
|
||||||
|
import com.bpgroup.poc.admin.web.common.CommonResponse;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
public class AdministratorCreate {
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class Request {
|
||||||
|
@NotBlank
|
||||||
|
private String loginId;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
private String name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@ToString
|
||||||
|
public static class Response extends CommonResponse {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
public static Response success(Long id) {
|
||||||
|
Response response = new Response();
|
||||||
|
response.resultCode = "0000";
|
||||||
|
response.resultMessage = "Success";
|
||||||
|
response.id = id;
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
public static Response fail() {
|
||||||
|
Response response = new Response();
|
||||||
|
response.resultCode = "9999";
|
||||||
|
response.resultMessage = "Fail";
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.bpgroup.poc.admin.web.main.admin.management.reqres;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
public class AdministratorDelete {
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class Request {
|
||||||
|
@NotNull
|
||||||
|
private Long id;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.bpgroup.poc.admin.web.main.admin.management.reqres;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
public class AdministratorFind {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@ToString
|
||||||
|
public static class Response {
|
||||||
|
private Long id;
|
||||||
|
private String loginId;
|
||||||
|
private String email;
|
||||||
|
private String name;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.bpgroup.poc.admin.web.main.admin.management.reqres;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
public class AdministratorUpdate {
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class Request {
|
||||||
|
@NotNull
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
private String name;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package com.bpgroup.poc.admin.web.main.admin.role;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
@RequestMapping("/admin/role")
|
||||||
|
public class RoleController {
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public String rolePage() {
|
||||||
|
return "main/admin/role/list";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ spring:
|
||||||
mode: always
|
mode: always
|
||||||
|
|
||||||
datasource:
|
datasource:
|
||||||
url: jdbc:mariadb://localhost:3307/login
|
url: jdbc:mariadb://localhost:3307/admin-system
|
||||||
username: admin
|
username: admin
|
||||||
password: 1234
|
password: 1234
|
||||||
driver-class-name: org.mariadb.jdbc.Driver
|
driver-class-name: org.mariadb.jdbc.Driver
|
||||||
|
|
@ -17,3 +17,6 @@ spring:
|
||||||
hibernate:
|
hibernate:
|
||||||
ddl-auto: create-drop
|
ddl-auto: create-drop
|
||||||
defer-datasource-initialization: true
|
defer-datasource-initialization: true
|
||||||
|
|
||||||
|
logging:
|
||||||
|
config: classpath:logback-local.xml
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
spring:
|
spring:
|
||||||
application:
|
application:
|
||||||
name: login
|
name: admin
|
||||||
|
|
||||||
profiles:
|
profiles:
|
||||||
default: local
|
default: local
|
||||||
|
|
@ -1,2 +1,34 @@
|
||||||
INSERT INTO `administrator` (`login_id`, `password`, `email`, `name`, `create_date`, `update_date`)
|
INSERT INTO `administrator` (`login_id`, `password`, `email`, `name`, `create_date`, `update_date`)
|
||||||
VALUES ('admin', '$2a$10$g6UOrQ/OS8o5r5CJk7C5juVFaItQ62U3EIn8zLPzkFplM3wVLvKZ2', 'admin@admin.com', '홍길동', CURDATE(), CURDATE());
|
VALUES ('admin', '$2a$10$g6UOrQ/OS8o5r5CJk7C5juVFaItQ62U3EIn8zLPzkFplM3wVLvKZ2', 'admin@admin.com', '홍길동', CURDATE(),
|
||||||
|
CURDATE());
|
||||||
|
|
||||||
|
INSERT INTO `role` (`name`, `description`, `create_date`, `update_date`)
|
||||||
|
VALUES ('SUPER_ADMIN', '최고 관리자', CURDATE(), CURDATE()),
|
||||||
|
('ADMIN', '관리자', CURDATE(), CURDATE()),
|
||||||
|
('CLIENT', '고객', CURDATE(), CURDATE());
|
||||||
|
|
||||||
|
INSERT INTO `administrator_role` (`administrator_id`, `role_id`, `create_date`, `update_date`)
|
||||||
|
VALUES ('1', '1', CURDATE(), CURDATE());
|
||||||
|
|
||||||
|
INSERT INTO `menu_group` (`uri`, `name`, `sort_order`, `create_date`, `update_date`)
|
||||||
|
VALUES ('/admin', '관리자 관리', 2, CURDATE(), CURDATE()),
|
||||||
|
('/settings', '설정', 3, CURDATE(), CURDATE()),
|
||||||
|
('/temp', '임시', 1, CURDATE(), CURDATE());
|
||||||
|
|
||||||
|
INSERT INTO `menu` (`uri`, `name`, `sort_order`, `menu_group_id`, `create_date`, `update_date`)
|
||||||
|
VALUES ('/admin/management', '관리자 관리', 1, 1, CURDATE(), CURDATE()),
|
||||||
|
('/admin/role', '권한 관리', 2, 1, CURDATE(), CURDATE())
|
||||||
|
;
|
||||||
|
|
||||||
|
INSERT INTO `menu` (`uri`, `name`, `sort_order`, `menu_group_id`, `create_date`, `update_date`)
|
||||||
|
VALUES ('/settings/code', '코드 관리', 1, 2, CURDATE(), CURDATE())
|
||||||
|
;
|
||||||
|
|
||||||
|
INSERT INTO `role_menu` (`role_id`, `menu_id`, `create_date`, `update_date`)
|
||||||
|
VALUES ('1', '1', CURDATE(), CURDATE()),
|
||||||
|
('1', '2', CURDATE(), CURDATE()),
|
||||||
|
('1', '3', CURDATE(), CURDATE())
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration>
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%5p|%d{yyyy-MM-dd HH:mm:ss.SSS}|%X{loginId}|%32X{sessionId}|%32X{transactionId}|%logger:%line|%msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<root level="DEBUG">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</root>
|
||||||
|
|
||||||
|
<logger name="org.springframework" level="INFO" />
|
||||||
|
|
||||||
|
<logger name="com.zaxxer.hikari" level="INFO" />
|
||||||
|
<logger name="org.mariadb.jdbc.client" level="INFO" />
|
||||||
|
<logger name="org.mariadb.jdbc.message" level="INFO" />
|
||||||
|
<logger name="jdbc" level="OFF" />
|
||||||
|
<logger name="log4jdbc" level="INFO" />
|
||||||
|
<logger name="org.hibernate" level="INFO" />
|
||||||
|
<logger name="org.thymeleaf" level="INFO" />
|
||||||
|
|
||||||
|
<logger name="org.hibernate.orm.jdbc" level="TRACE" />
|
||||||
|
</configuration>
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
const EventRouter = {
|
const EventRouter = {
|
||||||
_eventRouter: $({}),
|
_eventRouter: new EventTarget(),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 이벤트 발생
|
* 이벤트 발생
|
||||||
|
|
@ -8,7 +8,8 @@ const EventRouter = {
|
||||||
* @param {*=} data
|
* @param {*=} data
|
||||||
*/
|
*/
|
||||||
trigger(eventName, data) {
|
trigger(eventName, data) {
|
||||||
this._eventRouter.trigger(eventName, data);
|
const event = new CustomEvent(eventName, { detail: data });
|
||||||
|
this._eventRouter.dispatchEvent(event);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -23,15 +24,7 @@ const EventRouter = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._eventRouter.off(eventName);
|
this._eventRouter.removeEventListener(eventName, func);
|
||||||
this._eventRouter.on(eventName, func);
|
this._eventRouter.addEventListener(eventName, func);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* 현재 등록된 이벤트 조회 (디버깅용)
|
|
||||||
*/
|
|
||||||
logCurrentRegistered() {
|
|
||||||
const events = $._data(this._eventRouter[0], "events");
|
|
||||||
console.log(events);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
const originalFetch = window.fetch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* csrf token 없을 경우 추가하는 fetch interceptor
|
||||||
|
* @param url
|
||||||
|
* @param options
|
||||||
|
* @returns {Promise<Response>}
|
||||||
|
*/
|
||||||
|
window.fetch = function(url, options) {
|
||||||
|
// CSRF 토큰
|
||||||
|
const csrfToken = document.querySelector('meta[name="_csrf"]').getAttribute('content');
|
||||||
|
const csrfHeader = document.querySelector('meta[name="_csrf_header"]').getAttribute('content');
|
||||||
|
|
||||||
|
if (options.headers) {
|
||||||
|
options.headers[csrfHeader] = csrfToken;
|
||||||
|
} else {
|
||||||
|
options.headers = {
|
||||||
|
[csrfHeader]: csrfToken,
|
||||||
|
ContentType: 'application/json',
|
||||||
|
method: options.method ? options.method : 'POST'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 원래의 fetch 함수 호출
|
||||||
|
return originalFetch.apply(this, arguments);
|
||||||
|
};
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1,73 +1,73 @@
|
||||||
// mobile 100vh err issue 대응
|
// mobile 100vh err issue 대응
|
||||||
|
|
||||||
const setVh = () => {
|
const setVh = () => {
|
||||||
document.documentElement.style.setProperty('--vh', `${window.innerHeight}px`)
|
document.documentElement.style.setProperty('--vh', `${window.innerHeight}px`)
|
||||||
};
|
};
|
||||||
window.addEventListener('resize', setVh);
|
window.addEventListener('resize', setVh);
|
||||||
setVh();
|
setVh();
|
||||||
|
|
||||||
$(document).on('touchstart', function () {
|
document.addEventListener('touchstart', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// 콘텐츠영역 자동 높이 조절
|
// 콘텐츠영역 자동 높이 조절
|
||||||
$(document).ready(function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
// var headerHeight = $('header').outerHeight();
|
|
||||||
// $('.wrapper').css('padding-top', headerHeight + 'px');
|
|
||||||
});
|
|
||||||
|
|
||||||
$(document).ready(function () {
|
|
||||||
setupAccordion();
|
setupAccordion();
|
||||||
|
|
||||||
// navigation 모바일 처리
|
// navigation 모바일 처리
|
||||||
$('#toggleLink').click(function () {
|
document.querySelector('#toggleLink').addEventListener('click', function () {
|
||||||
toggleMenu('.menu_wrap');
|
toggleMenu('.menu_wrap');
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.closeButton').click(function () {
|
document.querySelector('.closeButton').addEventListener('click', function () {
|
||||||
closeMenu('.menu_wrap');
|
closeMenu('.menu_wrap');
|
||||||
});
|
});
|
||||||
|
|
||||||
//modal
|
document.querySelectorAll('.modal .close, .modal').forEach(function (element) {
|
||||||
$("#openModalBtn").click(function () {
|
element.addEventListener('click', function (e) {
|
||||||
openModal();
|
if (e.target !== this) return; // 모달 내부를 클릭한 경우에는 닫히지 않도록 처리
|
||||||
});
|
closeModal();
|
||||||
|
});
|
||||||
$(".modal .close, .modal").click(function (e) {
|
|
||||||
if (e.target !== this) return; // 모달 내부를 클릭한 경우에는 닫히지 않도록 처리
|
|
||||||
closeModal();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// 아코디언
|
// 아코디언
|
||||||
function setupAccordion() {
|
function setupAccordion() {
|
||||||
$('.nav_tit').on('click', function () {
|
document.querySelectorAll('.nav_tit').forEach(function (element) {
|
||||||
$(this).next('.nav_section').slideToggle(150);
|
element.addEventListener('click', function () {
|
||||||
$(this).toggleClass('active');
|
this.nextElementSibling.classList.toggle('active');
|
||||||
$('.nav_section').not($(this).next('.nav_section')).slideUp(150);
|
document.querySelectorAll('.nav_section').forEach(function (section) {
|
||||||
$('.nav_tit').not($(this)).removeClass('active');
|
if (section !== this.nextElementSibling) {
|
||||||
|
section.classList.remove('active');
|
||||||
|
} else {
|
||||||
|
section.classList.toggle('active');
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
document.querySelectorAll('.nav_tit').forEach(function (tit) {
|
||||||
|
if (tit !== this) {
|
||||||
|
tit.classList.remove('active');
|
||||||
|
} else {
|
||||||
|
tit.classList.toggle('active');
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// navigation 모바일 처리
|
// navigation 모바일 처리
|
||||||
function toggleMenu(selector) {
|
function toggleMenu(selector) {
|
||||||
$(selector).toggleClass('active');
|
document.querySelector(selector).classList.toggle('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeMenu(selector) {
|
function closeMenu(selector) {
|
||||||
$(selector).removeClass('active');
|
document.querySelector(selector).classList.remove('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//modal 열기 닫기
|
//modal 열기 닫기
|
||||||
function openModal() {
|
function openModal() {
|
||||||
$(".modal").fadeIn(100);
|
document.querySelector('.modal').style.display = 'block';
|
||||||
$("body").css("overflow", "hidden");
|
document.body.style.overflow = 'hidden';
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeModal() {
|
function closeModal() {
|
||||||
$(".modal").fadeOut(100);
|
document.querySelector('.modal').style.display = 'none';
|
||||||
$("body").css("overflow", "auto");
|
document.body.style.overflow = 'auto';
|
||||||
}
|
}
|
||||||
|
|
@ -7,12 +7,12 @@ const PageHelper = {
|
||||||
* @param confirmFunction
|
* @param confirmFunction
|
||||||
*/
|
*/
|
||||||
showSimpleConfirmModal(message, confirmFunction) {
|
showSimpleConfirmModal(message, confirmFunction) {
|
||||||
this.showConfirmModal({
|
this.showConfirmModal({
|
||||||
title: '확인',
|
title: '확인',
|
||||||
message: message,
|
message: message,
|
||||||
}, {
|
}, {
|
||||||
confirm: confirmFunction,
|
confirm: confirmFunction,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -52,7 +52,8 @@ const PageHelper = {
|
||||||
*/
|
*/
|
||||||
showErrorModal(message, cancelFunction) {
|
showErrorModal(message, cancelFunction) {
|
||||||
if (!cancelFunction) {
|
if (!cancelFunction) {
|
||||||
cancelFunction = () => {}
|
cancelFunction = () => {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.showAlertModal({
|
this.showAlertModal({
|
||||||
|
|
@ -71,7 +72,8 @@ const PageHelper = {
|
||||||
*/
|
*/
|
||||||
showFinishModal(message, cancelFunction) {
|
showFinishModal(message, cancelFunction) {
|
||||||
if (!cancelFunction) {
|
if (!cancelFunction) {
|
||||||
cancelFunction = () => {}
|
cancelFunction = () => {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.showAlertModal({
|
this.showAlertModal({
|
||||||
|
|
@ -117,7 +119,7 @@ const PageHelper = {
|
||||||
* @param {string} params.contentSelector
|
* @param {string} params.contentSelector
|
||||||
* @param {string} params.appendToSelector
|
* @param {string} params.appendToSelector
|
||||||
* @param {Object=} callbacks
|
* @param {Object=} callbacks
|
||||||
* @param {function(jQuery)=} callbacks.success - modal body의 jquery 객체가 파라미터로 오는 success 콜백 함수
|
* @param {function(Element)=} callbacks.success - 파라미터로 오는 success 콜백 함수
|
||||||
* @param {function(Object)=} callbacks.error - 에러 메세지가 파라미터로 오는 error 콜백 함수
|
* @param {function(Object)=} callbacks.error - 에러 메세지가 파라미터로 오는 error 콜백 함수
|
||||||
* @param {function()=} callbacks.complete
|
* @param {function()=} callbacks.complete
|
||||||
*/
|
*/
|
||||||
|
|
@ -132,11 +134,19 @@ const PageHelper = {
|
||||||
data: params.data,
|
data: params.data,
|
||||||
}, {
|
}, {
|
||||||
success: function (res) {
|
success: function (res) {
|
||||||
const e = $(res);
|
const p = new DOMParser();
|
||||||
const d = e.find(params.contentSelector);
|
const e = p.parseFromString(res, 'text/html');
|
||||||
d.appendTo(params.appendToSelector);
|
|
||||||
const s = e.find('script');
|
const d = e.querySelector(params.contentSelector);
|
||||||
s.appendTo('body');
|
document.body.appendChild(d);
|
||||||
|
|
||||||
|
const sDiv = document.createElement('script');
|
||||||
|
sDiv.setAttribute('data-page-modal', 'script');
|
||||||
|
|
||||||
|
const s = e.querySelector('script');
|
||||||
|
sDiv.textContent = s.textContent;
|
||||||
|
document.body.appendChild(sDiv);
|
||||||
|
|
||||||
if (callbacks.success) callbacks.success(d);
|
if (callbacks.success) callbacks.success(d);
|
||||||
},
|
},
|
||||||
error: function (error) {
|
error: function (error) {
|
||||||
|
|
@ -163,20 +173,21 @@ const PageHelper = {
|
||||||
callbacks = {};
|
callbacks = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
$.ajax({
|
fetch(params.url, {
|
||||||
url: params.url,
|
method: params.method,
|
||||||
type: params.method,
|
body: new URLSearchParams(params.data)
|
||||||
data: params.data,
|
})
|
||||||
success: function (res) {
|
.then(response => response.text())
|
||||||
if (callbacks.success) callbacks.success(res);
|
.then(data => {
|
||||||
},
|
if (callbacks.success) callbacks.success(data);
|
||||||
error: function (error) {
|
})
|
||||||
|
.catch(error => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
if (callbacks.error) callbacks.error(error);
|
if (callbacks.error) callbacks.error(error);
|
||||||
},
|
})
|
||||||
complete: function () {
|
.finally(() => {
|
||||||
if (callbacks.complete) callbacks.complete();
|
if (callbacks.complete) callbacks.complete();
|
||||||
}
|
});
|
||||||
});
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,81 +1,79 @@
|
||||||
const ReqHelper = {
|
const Reqhelper = {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* post json http request
|
||||||
* @param {jQuery} form
|
* @param url request url
|
||||||
* @param {Object=} callbacks
|
* @param data is not json stringify data
|
||||||
* @param {function(Object=)=} callbacks.success
|
* @param sFunc success function
|
||||||
* @param {function(Object)=} callbacks.error
|
* @param eFunc error function
|
||||||
* @param {function()=} callbacks.complete
|
* @param fFunc finally function
|
||||||
*/
|
*/
|
||||||
reqByForm(form, callbacks) {
|
reqPostJson: function (url, data, sFunc, eFunc, fFunc) {
|
||||||
if (!(form instanceof jQuery)) {
|
fetch(url, {
|
||||||
throw new Error('form is not jQuery instance');
|
method: 'POST',
|
||||||
}
|
body: JSON.stringify(data),
|
||||||
if (!callbacks) {
|
headers: {
|
||||||
callbacks = {};
|
'Content-Type': 'application/json'
|
||||||
}
|
|
||||||
|
|
||||||
const url = form.attr('action');
|
|
||||||
const method = form.attr('method');
|
|
||||||
const dataObj = form.serializeArray().reduce((obj, item) => {
|
|
||||||
obj[item.name] = item.value;
|
|
||||||
return obj;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: url,
|
|
||||||
type: method,
|
|
||||||
contentType: 'application/json',
|
|
||||||
data: JSON.stringify(dataObj),
|
|
||||||
success: function (res) {
|
|
||||||
if (!callbacks.success) return;
|
|
||||||
|
|
||||||
res.isSuccess = () => res.resultCode === "0000";
|
|
||||||
res.isFail = () => !res.isSuccess();
|
|
||||||
res.getMessage = () => res.resultMsg || '';
|
|
||||||
callbacks.success(res);
|
|
||||||
},
|
|
||||||
error: function (error) {
|
|
||||||
if (callbacks.error) callbacks.error(error);
|
|
||||||
},
|
|
||||||
complete: function () {
|
|
||||||
if (callbacks.complete) callbacks.complete();
|
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(response.statusText);
|
||||||
|
}
|
||||||
|
return response.text().then(text => text ? JSON.parse(text) : null);
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
if (sFunc) {
|
||||||
|
sFunc(data);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
if (eFunc) {
|
||||||
|
eFunc(error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
if (fFunc) {
|
||||||
|
fFunc();
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* get json http request
|
||||||
* @param url
|
* @param url request url
|
||||||
* @param sendData {Object=}
|
* @param sFunc success function
|
||||||
* @param {Object=} callbacks
|
* @param eFunc error function
|
||||||
* @param {function(Object=)=} callbacks.success
|
* @param fFunc finally function
|
||||||
* @param {function(Object)=} callbacks.error
|
|
||||||
* @param {function()=} callbacks.complete
|
|
||||||
*/
|
*/
|
||||||
reqByObj(url, sendData, callbacks) {
|
|
||||||
if (!url) {
|
|
||||||
throw new Error('url is empty');
|
|
||||||
}
|
|
||||||
if (!callbacks) {
|
|
||||||
callbacks = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
$.ajax({
|
reqGetJson: function (url, sFunc, eFunc, fFunc) {
|
||||||
url: url,
|
fetch(url, {
|
||||||
type: "post",
|
method: 'GET',
|
||||||
contentType: 'application/json',
|
headers: {
|
||||||
data: JSON.stringify(sendData),
|
'Content-Type': 'application/json'
|
||||||
success: function (res) {
|
|
||||||
if (callbacks.success) callbacks.success(res);
|
|
||||||
},
|
|
||||||
error: function (error) {
|
|
||||||
if (callbacks.error) callbacks.error(error);
|
|
||||||
},
|
|
||||||
complete: function () {
|
|
||||||
if (callbacks.complete) callbacks.complete();
|
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
},
|
.then(response => {
|
||||||
};
|
if (!response.ok) {
|
||||||
|
throw new Error(response.statusText);
|
||||||
|
}
|
||||||
|
return response.text().then(text => text ? JSON.parse(text) : null);
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
if (sFunc) {
|
||||||
|
sFunc(data);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
if (eFunc) {
|
||||||
|
eFunc(error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
if (fFunc) {
|
||||||
|
fFunc();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -4,17 +4,6 @@
|
||||||
<th:block th:fragment="applyCsrf">
|
<th:block th:fragment="applyCsrf">
|
||||||
<meta name="_csrf_header" th:content="${_csrf.headerName}"/>
|
<meta name="_csrf_header" th:content="${_csrf.headerName}"/>
|
||||||
<meta name="_csrf" th:content="${_csrf.token}"/>
|
<meta name="_csrf" th:content="${_csrf.token}"/>
|
||||||
<script th:inline="javascript">
|
|
||||||
// 모든 AJAX 요청에 자동으로 CSRF 토큰이 포함
|
|
||||||
const csrfToken = $('meta[name="_csrf"]').attr('content');
|
|
||||||
const csrfHeader = $('meta[name="_csrf_header"]').attr('content');
|
|
||||||
|
|
||||||
$.ajaxSetup({
|
|
||||||
beforeSend: function (xhr) {
|
|
||||||
xhr.setRequestHeader(csrfHeader, csrfToken);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</th:block>
|
</th:block>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -21,21 +21,25 @@
|
||||||
|
|
||||||
<script data-alert-modal="script">
|
<script data-alert-modal="script">
|
||||||
(function () {
|
(function () {
|
||||||
const modalBody = $('div[data-alert-modal="body"]');
|
const modalBody = document.querySelector('div[data-alert-modal="body"]');
|
||||||
|
|
||||||
modalBody.fadeIn(100);
|
modalBody.style.display = "block";
|
||||||
$("body").css("overflow", "hidden");
|
document.body.style.overflow = "hidden";
|
||||||
|
|
||||||
$('[data-alert-modal="cancelButton"]').on('click', function () {
|
document.querySelectorAll('[data-alert-modal="cancelButton"]').forEach(button => {
|
||||||
close();
|
button.addEventListener('click', function () {
|
||||||
EventRouter.trigger('clickAlertModalCancelButton');
|
close();
|
||||||
|
EventRouter.trigger('clickAlertModalCancelButton');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
modalBody.fadeOut(100);
|
modalBody.style.display = "none";
|
||||||
modalBody.remove();
|
modalBody.parentNode.removeChild(modalBody);
|
||||||
$("body").css("overflow", "auto");
|
document.body.style.overflow = "auto";
|
||||||
$('script[data-alert-modal="script"]').remove();
|
|
||||||
|
const modalScript = document.querySelector('script[data-page-modal="script"]');
|
||||||
|
modalScript.parentNode.removeChild(modalScript);
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,12 @@
|
||||||
|
|
||||||
<script data-confirm-modal="script">
|
<script data-confirm-modal="script">
|
||||||
(function () {
|
(function () {
|
||||||
const modalBody = $('div[data-confirm-modal="body"]');
|
const modalBody = document.querySelector('div[data-confirm-modal="body"]');
|
||||||
|
|
||||||
modalBody.fadeIn(100);
|
modalBody.style.display = "block";
|
||||||
$("body").css("overflow", "hidden");
|
document.body.style.overflow = "hidden";
|
||||||
|
|
||||||
$('button[data-confirm-modal="confirmButton"]').on('click', function () {
|
document.querySelector('button[data-confirm-modal="confirmButton"]').addEventListener('click', function () {
|
||||||
let prevented = false;
|
let prevented = false;
|
||||||
EventRouter.trigger('clickConfirmModalConfirmButton', {preventDefault: () => prevented = true});
|
EventRouter.trigger('clickConfirmModalConfirmButton', {preventDefault: () => prevented = true});
|
||||||
if (prevented) {
|
if (prevented) {
|
||||||
|
|
@ -37,21 +37,23 @@
|
||||||
hideAndRemove();
|
hideAndRemove();
|
||||||
});
|
});
|
||||||
|
|
||||||
$('[data-confirm-modal="cancelButton"]').on('click', function () {
|
document.querySelectorAll('[data-alert-modal="cancelButton"]').forEach(button => {
|
||||||
let prevented = false;
|
button.addEventListener('click', function () {
|
||||||
EventRouter.trigger('clickConfirmModalCancelButton', {preventDefault: () => prevented = true});
|
let prevented = false;
|
||||||
if (prevented) {
|
EventRouter.trigger('clickConfirmModalCancelButton', {preventDefault: () => prevented = true});
|
||||||
return;
|
if (prevented) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
hideAndRemove();
|
hideAndRemove();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function hideAndRemove() {
|
function hideAndRemove() {
|
||||||
modalBody.fadeOut(100);
|
modalBody.style.display = "none";
|
||||||
modalBody.remove();
|
modalBody.remove();
|
||||||
$("body").css("overflow", "auto");
|
document.body.style.overflow = "auto";
|
||||||
$('script[data-confirm-modal="script"]').remove();
|
document.querySelector('script[data-confirm-modal="script"]').remove();
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script data-page-modal="script">
|
<script data-page-modal="script">
|
||||||
$('div[data-page-modal="body"]').fadeIn(100);
|
document.querySelector('div[data-page-modal="body"]').style.display = "block";
|
||||||
$("body").css("overflow", "hidden");
|
document.body.style.overflow = "hidden";
|
||||||
|
|
||||||
$('[data-page-modal="cancelButton"]').on('click', cancelModal);
|
document.querySelectorAll('[data-page-modal="cancelButton"]').forEach(button => {
|
||||||
|
button.addEventListener('click', cancelModal);
|
||||||
|
});
|
||||||
|
|
||||||
function cancelModal() {
|
function cancelModal() {
|
||||||
closeModal();
|
closeModal();
|
||||||
|
|
@ -32,11 +34,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeModal() {
|
function closeModal() {
|
||||||
const modalBody = $('div[data-page-modal="body"]');
|
const modalBody = document.querySelector('div[data-page-modal="body"]');
|
||||||
modalBody.fadeOut(100);
|
modalBody.style.display = "none";
|
||||||
modalBody.remove();
|
modalBody.parentNode.removeChild(modalBody);
|
||||||
$("body").css("overflow", "auto");
|
document.body.style.overflow = "auto";
|
||||||
$('script[data-page-modal="script"]').remove();
|
|
||||||
|
const modalScript = document.querySelector('script[data-page-modal="script"]');
|
||||||
|
modalScript.parentNode.removeChild(modalScript);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ko" xmlns:th="http://www.thymeleaf.org"
|
||||||
|
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
|
||||||
|
<title>관리자 시스템</title>
|
||||||
|
<link rel="shortcut icon" th:href="@{/images/favicon.ico}">
|
||||||
|
<link rel="stylesheet" th:href="@{/css/style.css}"/>
|
||||||
|
<link rel="stylesheet" th:href="@{/css/sub.css}"/>
|
||||||
|
|
||||||
|
<script type="text/javascript" th:src="@{/js/motion.js}"></script>
|
||||||
|
<script type="text/javascript" th:src="@{/js/pagehelper.js}"></script>
|
||||||
|
<script type="text/javascript" th:src="@{/js/eventrouter.js}"></script>
|
||||||
|
<script type="text/javascript" th:src="@{/js/httpinterceptor.js}"></script>
|
||||||
|
<script type="text/javascript" th:src="@{/js/reqhelper.js}"></script>
|
||||||
|
|
||||||
|
<th:block th:replace="~{fragment/csrf/csrf :: applyCsrf}"></th:block>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<th:block th:replace="~{layout/gnb :: gnb}"></th:block>
|
||||||
|
</header>
|
||||||
|
<div class="wrapper flexbox">
|
||||||
|
<th:block th:replace="~{layout/lnb :: lnb}"></th:block>
|
||||||
|
<div class="container flexitem">
|
||||||
|
<th:block layout:fragment="contents"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<th:block layout:fragment="script">
|
||||||
|
</th:block>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ko"
|
||||||
|
xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<body>
|
||||||
|
<th:block th:fragment="lnb">
|
||||||
|
<nav class="menu_wrap flexitem">
|
||||||
|
<h2>메뉴</h2>
|
||||||
|
<button class="closeButton"><img th:src="@{/images/ico_close.svg}"></button>
|
||||||
|
<div th:if="${#authorization.expression('isAuthenticated()')}">
|
||||||
|
<div th:each="entry : ${#authentication.details.getMenus().entrySet()}">
|
||||||
|
<a class="nav_tit"
|
||||||
|
th:classappend="${pathInfo.activeClass(entry.key.getUri())}"
|
||||||
|
th:text="${entry.key.getName()}">
|
||||||
|
</a>
|
||||||
|
<ul class="nav_section">
|
||||||
|
<li th:each="menu : ${entry.value}">
|
||||||
|
<a th:with="path= ${menu.getUri()}" th:href="${path}"
|
||||||
|
th:classappend="${pathInfo.activeClass(path)}"><span th:text="${menu.getName()}"></span></a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</th:block>
|
||||||
|
</body>
|
||||||
|
|
@ -5,13 +5,14 @@
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
|
||||||
<title>시스템</title>
|
<title>시스템</title>
|
||||||
<script type="text/javascript" th:src="@{/js/jquery/jquery-3.7.1.min.js}"></script>
|
|
||||||
<link rel="shortcut icon" th:href="@{/images/favicon.ico}">
|
<link rel="shortcut icon" th:href="@{/images/favicon.ico}">
|
||||||
<link rel="stylesheet" th:href="@{/css/style.css}"/>
|
<link rel="stylesheet" th:href="@{/css/style.css}"/>
|
||||||
|
|
||||||
<script type="text/javascript" th:src="@{/js/pagehelper.js}"></script>
|
<script type="text/javascript" th:src="@{/js/pagehelper.js}"></script>
|
||||||
<script type="text/javascript" th:src="@{/js/reqhelper.js}"></script>
|
|
||||||
<script type="text/javascript" th:src="@{/js/eventrouter.js}"></script>
|
<script type="text/javascript" th:src="@{/js/eventrouter.js}"></script>
|
||||||
|
<script type="text/javascript" th:src="@{/js/httpinterceptor.js}"></script>
|
||||||
|
|
||||||
|
<th:block th:replace="~{fragment/csrf/csrf :: applyCsrf}"></th:block>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="login_wrap">
|
<div class="login_wrap">
|
||||||
|
|
@ -39,16 +40,16 @@
|
||||||
PageHelper.showErrorModal(errorMessage);
|
PageHelper.showErrorModal(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#loginButton').click(function () {
|
document.querySelector('#loginButton').addEventListener('click', function () {
|
||||||
|
|
||||||
if (!validate()) {
|
if (!validate()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$('#loginForm').submit();
|
document.querySelector('#loginForm').submit();
|
||||||
|
|
||||||
function validate() {
|
function validate() {
|
||||||
const loginId = $('input[name="loginId"]').val();
|
const loginId = document.querySelector('input[name="loginId"]').value;
|
||||||
const password = $('input[name="password"]').val();
|
const password = document.querySelector('input[name="password"]').value;
|
||||||
|
|
||||||
if (!loginId) {
|
if (!loginId) {
|
||||||
PageHelper.showErrorModal('아이디를 입력해주세요.');
|
PageHelper.showErrorModal('아이디를 입력해주세요.');
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,166 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ko" xmlns:th="http://www.thymeleaf.org"
|
||||||
|
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||||
|
layout:decorate="~{main/admin/management/root.html}">
|
||||||
|
<body>
|
||||||
|
<th:block layout:fragment="innerContents">
|
||||||
|
<div class="table_wrap mt-20">
|
||||||
|
<h4>관리자 등록</h4>
|
||||||
|
<div class="tb_wrapper">
|
||||||
|
<label for="iptCreateLoginId">아이디: </label><input type="text" id="iptCreateLoginId" name="loginId">
|
||||||
|
<label for="iptCreatePassword">비밀번호: </label><input type="password" id="iptCreatePassword" name="password">
|
||||||
|
<label for="iptCreateEmail">이메일: </label><input type="text" id="iptCreateEmail" name="email">
|
||||||
|
<label for="iptCreateNAme">이름: </label><input type="text" id="iptCreateNAme" name="name">
|
||||||
|
<button type="button" id="btnCreate" class="btn_confirm" data-bs-dismiss="modal">등록</button>
|
||||||
|
<button type="button" id="btnCheckDuplication" class="btn_confirm" data-bs-dismiss="modal">아이디 중복체크</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4>관리자 조회</h4>
|
||||||
|
<div class="tb_wrapper">
|
||||||
|
<label for="selAdministrator">관리자: </label>
|
||||||
|
<select id="selAdministrator" class="table_select_box">
|
||||||
|
<option value="">선택</option>
|
||||||
|
</select>
|
||||||
|
<button type="button" id="btnFindAll" class="btn_confirm" data-bs-dismiss="modal">조회</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4>관리자 수정</h4>
|
||||||
|
<div class="tb_wrapper">
|
||||||
|
<input type="hidden" id="iptUpdateId" name="id">
|
||||||
|
<label for="iptUpdateLoginId">아이디: </label><input type="text" id="iptUpdateLoginId" name="loginId" readonly>
|
||||||
|
<label for="iptUpdatePassword">비밀번호: </label><input type="password" id="iptUpdatePassword" name="password">
|
||||||
|
<label for="iptUpdateEmail">이메일: </label><input type="text" id="iptUpdateEmail" name="email">
|
||||||
|
<label for="iptUpdateName">이름: </label><input type="text" id="iptUpdateName" name="name">
|
||||||
|
<button type="button" id="btnUpdate" class="btn_confirm" data-bs-dismiss="modal">수정</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4>관리자 삭제</h4>
|
||||||
|
<div class="tb_wrapper">
|
||||||
|
<input type="hidden" id="iptDeleteId" name="id">
|
||||||
|
<label for="iptUpdateLoginId">아이디: </label><input type="text" id="iptDeleteLoginId" name="loginId" readonly>
|
||||||
|
<button type="button" id="btnDelete" class="btn_confirm" data-bs-dismiss="modal">삭제</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th:block>
|
||||||
|
|
||||||
|
<th:block layout:fragment="innerScript">
|
||||||
|
<script th:inline="javascript">
|
||||||
|
function clearInput() {
|
||||||
|
document.getElementById('iptCreateLoginId').value = '';
|
||||||
|
document.getElementById('iptCreatePassword').value = '';
|
||||||
|
document.getElementById('iptCreateEmail').value = '';
|
||||||
|
document.getElementById('iptCreateNAme').value = '';
|
||||||
|
document.getElementById('iptUpdateId').value = '';
|
||||||
|
document.getElementById('iptUpdateLoginId').value = '';
|
||||||
|
document.getElementById('iptUpdatePassword').value = '';
|
||||||
|
document.getElementById('iptUpdateEmail').value = '';
|
||||||
|
document.getElementById('iptUpdateName').value = '';
|
||||||
|
document.getElementById('iptDeleteId').value = '';
|
||||||
|
document.getElementById('iptDeleteLoginId').value = '';
|
||||||
|
document.getElementById('selAdministrator').innerHTML = '<option value="">선택</option>';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 아이디 중복 체크
|
||||||
|
*/
|
||||||
|
document.getElementById('btnCheckDuplication').addEventListener(('click'), function () {
|
||||||
|
const loginId = document.getElementById('iptCreateLoginId').value;
|
||||||
|
const requestUri = /*[[@{/admin/management}]]*/ '';
|
||||||
|
|
||||||
|
Reqhelper.reqGetJson(requestUri.concat('/', loginId), (res) => {
|
||||||
|
if (!res) {
|
||||||
|
PageHelper.showAlertModal({title: '아이디 중복', message: '사용 가능한 아이디입니다.'});
|
||||||
|
} else {
|
||||||
|
PageHelper.showAlertModal({title: '아이디 중복', message: '사용할 수 없는 아이디입니다.'});
|
||||||
|
}
|
||||||
|
}, () => {
|
||||||
|
PageHelper.showErrorModal('데이터 조회에 실패했습니다.');
|
||||||
|
});
|
||||||
|
})
|
||||||
|
;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 관리자 등록
|
||||||
|
*/
|
||||||
|
document.getElementById('btnCreate').addEventListener(('click'), function () {
|
||||||
|
let data = {
|
||||||
|
loginId: document.getElementById('iptCreateLoginId').value,
|
||||||
|
password: document.getElementById('iptCreatePassword').value,
|
||||||
|
email: document.getElementById('iptCreateEmail').value,
|
||||||
|
name: document.getElementById('iptCreateEmail').value
|
||||||
|
};
|
||||||
|
const requestUri = /*[[@{/admin/management/create}]]*/ '';
|
||||||
|
|
||||||
|
Reqhelper.reqPostJson(requestUri, data, () => {
|
||||||
|
PageHelper.showAlertModal({title: '등록 완료', message: '등록이 완료되었습니다.'});
|
||||||
|
clearInput();
|
||||||
|
|
||||||
|
}, () => {
|
||||||
|
PageHelper.showErrorModal('등록에 실패했습니다.');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전체 조회
|
||||||
|
*/
|
||||||
|
document.getElementById('btnFindAll').addEventListener(('click'), function () {
|
||||||
|
const requestUri = /*[[@{/admin/management/list}]]*/ '';
|
||||||
|
Reqhelper.reqGetJson(requestUri, (res) => {
|
||||||
|
const selAdministrator = document.getElementById('selAdministrator');
|
||||||
|
selAdministrator.innerHTML = '<option value="">선택</option>';
|
||||||
|
res.forEach(item => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = item.id;
|
||||||
|
option.text = item.loginId;
|
||||||
|
selAdministrator.appendChild(option);
|
||||||
|
});
|
||||||
|
}, () => {
|
||||||
|
PageHelper.showErrorModal('데이터 조회에 실패했습니다.');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('selAdministrator').addEventListener(('change'), function () {
|
||||||
|
document.getElementById('iptUpdateId').value = this.value;
|
||||||
|
document.getElementById('iptUpdateLoginId').value = this.options[this.selectedIndex].text;
|
||||||
|
document.getElementById('iptDeleteId').value = this.value;
|
||||||
|
document.getElementById('iptDeleteLoginId').value = this.options[this.selectedIndex].text;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 관리자 수정
|
||||||
|
*/
|
||||||
|
document.getElementById('btnUpdate').addEventListener(('click'), function () {
|
||||||
|
let data = {
|
||||||
|
id: document.getElementById('iptUpdateId').value,
|
||||||
|
password: document.getElementById('iptUpdatePassword').value,
|
||||||
|
email: document.getElementById('iptUpdateEmail').value,
|
||||||
|
name: document.getElementById('iptUpdateName').value
|
||||||
|
};
|
||||||
|
const requestUri = /*[[@{/admin/management/update}]]*/ '';
|
||||||
|
|
||||||
|
Reqhelper.reqPostJson(requestUri, data, () => {
|
||||||
|
PageHelper.showAlertModal({title: '수정 완료', message: '수정이 완료되었습니다.'});
|
||||||
|
}, () => {
|
||||||
|
PageHelper.showErrorModal('수정에 실패했습니다.');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('btnDelete').addEventListener(('click'), function () {
|
||||||
|
let data = {
|
||||||
|
id: document.getElementById('iptDeleteId').value
|
||||||
|
};
|
||||||
|
const requestUri = /*[[@{/admin/management/delete}]]*/ '';
|
||||||
|
|
||||||
|
Reqhelper.reqPostJson(requestUri, data, () => {
|
||||||
|
PageHelper.showAlertModal({title: '삭제 완료', message: '삭제가 완료되었습니다.'});
|
||||||
|
clearInput();
|
||||||
|
}, () => {
|
||||||
|
PageHelper.showErrorModal('삭제에 실패했습니다.');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</th:block>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
<!-- 메뉴 공통 영역-->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ko" xmlns:th="http://www.thymeleaf.org"
|
||||||
|
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||||
|
layout:decorate="~{layout/common.html}">
|
||||||
|
<body>
|
||||||
|
<th:block layout:fragment="contents">
|
||||||
|
<th:block layout:fragment="innerContents"></th:block>
|
||||||
|
</th:block>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<th:block layout:fragment="script">
|
||||||
|
<th:block layout:fragment="innerScript"></th:block>
|
||||||
|
</th:block>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ko" xmlns:th="http://www.thymeleaf.org"
|
||||||
|
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||||
|
layout:decorate="~{main/admin/role/root.html}">
|
||||||
|
<body>
|
||||||
|
<th:block layout:fragment="innerContents">
|
||||||
|
</th:block>
|
||||||
|
|
||||||
|
<th:block layout:fragment="innerScript">
|
||||||
|
<script th:inline="javascript">
|
||||||
|
</script>
|
||||||
|
</th:block>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
<!-- 메뉴 공통 영역-->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ko" xmlns:th="http://www.thymeleaf.org"
|
||||||
|
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||||
|
layout:decorate="~{layout/common.html}">
|
||||||
|
<body>
|
||||||
|
<th:block layout:fragment="contents">
|
||||||
|
<th:block layout:fragment="innerContents"></th:block>
|
||||||
|
</th:block>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<th:block layout:fragment="script">
|
||||||
|
<th:block layout:fragment="innerScript"></th:block>
|
||||||
|
</th:block>
|
||||||
|
</html>
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ko"
|
|
||||||
xmlns:th="http://www.thymeleaf.org"
|
|
||||||
xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity6">
|
|
||||||
<body>
|
|
||||||
<th:block th:fragment="lnb">
|
|
||||||
<nav class="menu_wrap flexitem">
|
|
||||||
<!--/*@thymesVar id="pathInfo" type="kr.co.bpsoft.settlement_system.backend.admin.web.advice.PathInfoControllerAdvice$PathInfo"*/-->
|
|
||||||
<h2>메뉴</h2>
|
|
||||||
<button class="closeButton"><img th:src="@{/images/ico_close.svg}"></button>
|
|
||||||
<th>
|
|
||||||
<a href="#"
|
|
||||||
class="nav_tit">
|
|
||||||
1 Depth
|
|
||||||
</a>
|
|
||||||
<ul class="nav_section">
|
|
||||||
<li>
|
|
||||||
<a>2 Depth</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</th>
|
|
||||||
</nav>
|
|
||||||
</th:block>
|
|
||||||
</body>
|
|
||||||
|
|
@ -1,37 +1,17 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="ko" xmlns:th="http://www.thymeleaf.org"
|
<html lang="ko" xmlns:th="http://www.thymeleaf.org"
|
||||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
|
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||||
<head>
|
layout:decorate="~{layout/common.html}">
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
|
|
||||||
<title>관리자 시스템</title>
|
|
||||||
<script type="text/javascript" th:src="@{/js/jquery/jquery-3.7.1.min.js}"></script>
|
|
||||||
<link rel="shortcut icon" th:href="@{/images/favicon.ico}">
|
|
||||||
<link rel="stylesheet" th:href="@{/css/style.css}"/>
|
|
||||||
<link rel="stylesheet" th:href="@{/css/sub.css}"/>
|
|
||||||
|
|
||||||
<script type="text/javascript" th:src="@{/js/motion.js}"></script>
|
|
||||||
<script type="text/javascript" th:src="@{/js/pagehelper.js}"></script>
|
|
||||||
<script type="text/javascript" th:src="@{/js/reqhelper.js}"></script>
|
|
||||||
<script type="text/javascript" th:src="@{/js/eventrouter.js}"></script>
|
|
||||||
|
|
||||||
<th:block th:replace="~{fragment/csrf/csrf :: applyCsrf}"></th:block>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<th:block layout:fragment="contents">
|
||||||
<th:block th:replace="~{main/gnb :: gnb}"></th:block>
|
<th:block layout:fragment="innerContents">
|
||||||
</header>
|
|
||||||
<div class="wrapper flexbox">
|
|
||||||
<th:block th:replace="~{main/lnb :: lnb}"></th:block>
|
|
||||||
<div class="container flexitem">
|
|
||||||
<th:block layout:fragment="contents"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<th:block layout:fragment="script">
|
|
||||||
|
|
||||||
|
</th:block>
|
||||||
</th:block>
|
</th:block>
|
||||||
</body>
|
</body>
|
||||||
|
<th:block layout:fragment="script">
|
||||||
|
<script th:inline="javascript">
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</th:block>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue