11package com .example .solidconnection .siteuser .service ;
22
33import static com .example .solidconnection .common .exception .ErrorCode .SCHOOL_EMAIL_ALREADY_VERIFIED ;
4+ import static com .example .solidconnection .common .exception .ErrorCode .SCHOOL_EMAIL_ALREADY_USED ;
45import static com .example .solidconnection .common .exception .ErrorCode .SCHOOL_EMAIL_CONFIRM_CODE_DIFFERENT ;
56import static com .example .solidconnection .common .exception .ErrorCode .SCHOOL_EMAIL_CONFIRM_REQUEST_NOT_FOUND ;
67import static com .example .solidconnection .common .exception .ErrorCode .SCHOOL_EMAIL_DOMAIN_NOT_SUPPORTED ;
1718import com .example .solidconnection .university .repository .HomeUniversityRepository ;
1819import com .fasterxml .jackson .core .JsonProcessingException ;
1920import com .fasterxml .jackson .databind .ObjectMapper ;
21+ import java .util .Locale ;
2022import java .util .concurrent .ThreadLocalRandom ;
2123import java .util .concurrent .TimeUnit ;
2224import lombok .RequiredArgsConstructor ;
25+ import org .springframework .dao .DataIntegrityViolationException ;
2326import org .springframework .data .redis .core .RedisTemplate ;
2427import org .springframework .stereotype .Service ;
2528import org .springframework .transaction .annotation .Transactional ;
@@ -46,15 +49,17 @@ public void requestSchoolEmailVerification(long siteUserId, String schoolEmail)
4649 throw new CustomException (SCHOOL_EMAIL_ALREADY_VERIFIED );
4750 }
4851
49- String domain = extractEmailDomain (schoolEmail );
52+ String normalizedSchoolEmail = normalizeSchoolEmail (schoolEmail );
53+ String domain = extractEmailDomain (normalizedSchoolEmail );
5054 HomeUniversity homeUniversity = homeUniversityRepository .findByEmailDomain (domain )
5155 .orElseThrow (() -> new CustomException (SCHOOL_EMAIL_DOMAIN_NOT_SUPPORTED ));
56+ validateVerifiedSchoolEmailNotDuplicated (normalizedSchoolEmail );
5257
5358 String code = generateVerificationCode ();
54- saveVerificationInfo (siteUserId , new SchoolVerificationInfo (schoolEmail , homeUniversity .getId (), code ));
59+ saveVerificationInfo (siteUserId , new SchoolVerificationInfo (normalizedSchoolEmail , homeUniversity .getId (), code ));
5560
5661 try {
57- mailService .sendVerificationEmail (schoolEmail , code );
62+ mailService .sendVerificationEmail (normalizedSchoolEmail , code );
5863 } catch (Exception e ) {
5964 redisTemplate .delete (KEY_PREFIX + siteUserId );
6065 throw e ;
@@ -72,10 +77,21 @@ public void confirmSchoolEmail(long siteUserId, String code) {
7277 throw new CustomException (SCHOOL_EMAIL_CONFIRM_CODE_DIFFERENT );
7378 }
7479
75- siteUser .verifySchool (info .getHomeUniversityId ());
80+ String verifiedSchoolEmail = normalizeSchoolEmail (info .getSchoolEmail ());
81+ validateVerifiedSchoolEmailNotDuplicated (verifiedSchoolEmail );
82+ siteUser .verifySchool (info .getHomeUniversityId (), verifiedSchoolEmail );
83+ flushVerifiedSchoolEmail ();
7684 redisTemplate .delete (KEY_PREFIX + siteUserId );
7785 }
7886
87+ private void flushVerifiedSchoolEmail () {
88+ try {
89+ siteUserRepository .flush ();
90+ } catch (DataIntegrityViolationException e ) {
91+ throw new CustomException (SCHOOL_EMAIL_ALREADY_USED );
92+ }
93+ }
94+
7995 private void saveVerificationInfo (long siteUserId , SchoolVerificationInfo info ) {
8096 try {
8197 redisTemplate .opsForValue ().set (
@@ -95,15 +111,57 @@ private SchoolVerificationInfo getVerificationInfo(long siteUserId) {
95111 throw new CustomException (SCHOOL_EMAIL_CONFIRM_REQUEST_NOT_FOUND );
96112 }
97113 try {
98- return objectMapper .readValue (jsonInfo , SchoolVerificationInfo .class );
114+ SchoolVerificationInfo info = objectMapper .readValue (jsonInfo , SchoolVerificationInfo .class );
115+ validateVerificationInfo (siteUserId , info );
116+ return info ;
99117 } catch (JsonProcessingException e ) {
100- redisTemplate .delete (KEY_PREFIX + siteUserId );
101- throw new CustomException (SCHOOL_EMAIL_VERIFICATION_INFO_CORRUPTED );
118+ throw corruptedVerificationInfoException (siteUserId );
119+ }
120+ }
121+
122+ private void validateVerificationInfo (long siteUserId , SchoolVerificationInfo info ) {
123+ if (info == null
124+ || isBlank (info .getSchoolEmail ())
125+ || !hasEmailDomain (info .getSchoolEmail ())
126+ || info .getHomeUniversityId () == null
127+ || isBlank (info .getCode ())
128+ || !homeUniversityRepository .existsById (info .getHomeUniversityId ())) {
129+ throw corruptedVerificationInfoException (siteUserId );
130+ }
131+ }
132+
133+ private boolean isBlank (String value ) {
134+ return value == null || value .isBlank ();
135+ }
136+
137+ private CustomException corruptedVerificationInfoException (long siteUserId ) {
138+ redisTemplate .delete (KEY_PREFIX + siteUserId );
139+ return new CustomException (SCHOOL_EMAIL_VERIFICATION_INFO_CORRUPTED );
140+ }
141+
142+ private void validateVerifiedSchoolEmailNotDuplicated (String verifiedSchoolEmail ) {
143+ if (siteUserRepository .existsByVerifiedSchoolEmail (verifiedSchoolEmail )) {
144+ throw new CustomException (SCHOOL_EMAIL_ALREADY_USED );
102145 }
103146 }
104147
148+ private String normalizeSchoolEmail (String schoolEmail ) {
149+ return schoolEmail .trim ().toLowerCase (Locale .ROOT );
150+ }
151+
105152 private String extractEmailDomain (String email ) {
106- return email .substring (email .indexOf ('@' ) + 1 ).toLowerCase ();
153+ if (!hasEmailDomain (email )) {
154+ throw new CustomException (SCHOOL_EMAIL_DOMAIN_NOT_SUPPORTED );
155+ }
156+ return email .substring (email .indexOf ('@' ) + 1 );
157+ }
158+
159+ private boolean hasEmailDomain (String email ) {
160+ if (email == null ) {
161+ return false ;
162+ }
163+ int atIndex = email .indexOf ('@' );
164+ return atIndex > 0 && atIndex < email .length () - 1 ;
107165 }
108166
109167 private String generateVerificationCode () {
0 commit comments