diff --git a/.gitignore b/.gitignore index e4a226798..594f1c5e0 100644 --- a/.gitignore +++ b/.gitignore @@ -54,6 +54,7 @@ dump.rdb **/resources/application-local.yml **/resources/application-test.yml **/resources/application-qa.yml +**/resources/application-seed.yml **/resources/dongnae_firebase_key.json Dockerfile diff --git a/app-main/src/main/java/net/causw/app/main/domain/user/account/repository/user/UserRepository.java b/app-main/src/main/java/net/causw/app/main/domain/user/account/repository/user/UserRepository.java index 491025687..946740172 100644 --- a/app-main/src/main/java/net/causw/app/main/domain/user/account/repository/user/UserRepository.java +++ b/app-main/src/main/java/net/causw/app/main/domain/user/account/repository/user/UserRepository.java @@ -84,4 +84,6 @@ Optional findByPhoneNumber( Boolean existsByStudentId(String studentId); Optional findByEmailAndName(String email, String name); + + boolean existsBy(); } \ No newline at end of file diff --git a/app-main/src/main/java/net/causw/app/main/shared/seed/UserSeedRunner.java b/app-main/src/main/java/net/causw/app/main/shared/seed/UserSeedRunner.java new file mode 100644 index 000000000..5dd60bf10 --- /dev/null +++ b/app-main/src/main/java/net/causw/app/main/shared/seed/UserSeedRunner.java @@ -0,0 +1,22 @@ +package net.causw.app.main.shared.seed; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +@Component +@Profile("seed") +public class UserSeedRunner implements CommandLineRunner { + + private final UserSeeder userSeeder; + + public UserSeedRunner(UserSeeder userSeeder) { + this.userSeeder = userSeeder; + } + + @Override + public void run(String... args) { + int count = args.length > 0 ? Integer.parseInt(args[0]) : 10_000; + userSeeder.seed(count); + } +} diff --git a/app-main/src/main/java/net/causw/app/main/shared/seed/UserSeeder.java b/app-main/src/main/java/net/causw/app/main/shared/seed/UserSeeder.java new file mode 100644 index 000000000..d69db140a --- /dev/null +++ b/app-main/src/main/java/net/causw/app/main/shared/seed/UserSeeder.java @@ -0,0 +1,154 @@ +package net.causw.app.main.shared.seed; + +import java.util.List; +import java.util.Set; +import java.util.UUID; + +import org.springframework.context.annotation.Profile; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import net.causw.app.main.api.dto.user.UserCreateRequestDto; +import net.causw.app.main.domain.asset.file.entity.UuidFile; +import net.causw.app.main.domain.asset.file.entity.joinEntity.UserAcademicRecordApplicationAttachImage; +import net.causw.app.main.domain.asset.file.entity.joinEntity.UserProfileImage; +import net.causw.app.main.domain.asset.file.enums.FilePath; +import net.causw.app.main.domain.user.academic.entity.userAcademicRecord.UserAcademicRecordApplication; +import net.causw.app.main.domain.user.academic.enums.userAcademicRecord.AcademicRecordRequestStatus; +import net.causw.app.main.domain.user.academic.enums.userAcademicRecord.AcademicStatus; +import net.causw.app.main.domain.user.account.entity.user.User; +import net.causw.app.main.domain.user.account.entity.user.UserAdmissionLog; +import net.causw.app.main.domain.user.account.enums.user.Department; +import net.causw.app.main.domain.user.account.enums.user.Role; +import net.causw.app.main.domain.user.account.enums.user.UserAdmissionLogAction; +import net.causw.app.main.domain.user.account.enums.user.UserState; +import net.causw.app.main.domain.user.account.repository.user.UserRepository; + +import jakarta.persistence.EntityManager; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Component +@Profile("seed") +@RequiredArgsConstructor +@Slf4j +public class UserSeeder { + + private final EntityManager em; + private final PasswordEncoder passwordEncoder; + private final UserRepository userRepository; + + @Transactional + public void seed(int count) { + + boolean exist = userRepository.existsBy(); + + if (exist) { + log.warn("🚫 Seed skipped: users already exist"); + return; + } + // getOrCreateAdmin(); + process(count); + } + + private void process(int count) { + int batchSize = 500; + + String encodedPassword = passwordEncoder.encode("password00!!"); + for (int i = 1; i <= count; i++) { + + User user = createActiveCommonUser(i, encodedPassword); + + UserAdmissionLog log = createUserAdmissionLog(user); + createEnrolledAcademicRecord(user, 2026); + + // batch flush + if (i % batchSize == 0) { + em.flush(); + em.clear(); + System.out.println("Seeded users: " + i); + } + } + + em.flush(); + em.clear(); + } + + private UserAdmissionLog createUserAdmissionLog(User user) { + UserAdmissionLog log = UserAdmissionLog.of( + user.getEmail(), + user.getName(), + "admin@seed.test", + "μ‹œλ“œ κ΄€λ¦¬μž", + UserAdmissionLogAction.ACCEPT, + List.of(), + "μ‹œλ“œ 데이터 - κ°€μž… 승인 μ™„λ£Œ", + null); + em.persist(log); + + return log; + } + + private User createActiveCommonUser(int i, String encodedPassword) { + UserCreateRequestDto dto = UserCreateRequestDto.builder() + .email("seed" + i + "@cau.ac.kr") + .name("μ‹œλ“œμœ μ €" + i) + .password("password00!!") // μ •κ·œμ‹ 톡과 + .studentId("2020" + String.format("%04d", i)) + .admissionYear(2020) + .nickname("seed_" + i) + .major("μ†Œν”„νŠΈμ›¨μ–΄ν•™λΆ€") + .department(Department.SCHOOL_OF_SW) + .phoneNumber(String.format("010-%04d-%04d", 2000 + i / 10000, i % 10000)) + .build(); + + User user = User.from(dto, encodedPassword); + user.setRoles(Set.of(Role.COMMON)); + user.setState(UserState.ACTIVE); + em.persist(user); + + // UUID File (κ°€μ§œ 파일) + UuidFile file = UuidFile.of(UUID.randomUUID().toString(), "seed/profile/" + i + ".png", + "https://cdn.seed.test/profile/" + i + ".png", "profile_" + i + ".png", "png", FilePath.ETC); + em.persist(file); + // User File 관계 ν…Œμ΄λΈ” + UserProfileImage profile = UserProfileImage.of(user, file); + em.persist(profile); + + return user; + } + + private void createEnrolledAcademicRecord( + User user, + int completedSemester) { + // 1. User μƒνƒœ ν™•μ • + user.setAcademicStatus(AcademicStatus.ENROLLED); + user.setCurrentCompletedSemester(completedSemester); + user.setState(UserState.ACTIVE); + + // 2. 학적 증빙 μ‹ μ²­μ„œ (이미 ACCEPT 된 μƒνƒœ) + UserAcademicRecordApplication application = UserAcademicRecordApplication.create( + user, + AcademicRecordRequestStatus.ACCEPT, + AcademicStatus.ENROLLED, + completedSemester, + "μ‹œλ“œ 데이터 - μž¬ν•™ 인증 μ™„λ£Œ"); + + em.persist(application); + + // 3. 첨뢀 이미지 1개 + UuidFile recordFile = UuidFile.of( + UUID.randomUUID().toString(), + "seed/academic-record/" + user.getId() + ".png", + "https://cdn.seed.test/academic-record/" + user.getId() + ".png", + "academic_record.png", + "png", + FilePath.ETC); + em.persist(recordFile); + + UserAcademicRecordApplicationAttachImage attach = UserAcademicRecordApplicationAttachImage.of(application, + recordFile); + em.persist(attach); + } +} \ No newline at end of file