Skip to content

Commit a9915cd

Browse files
authored
Merge pull request #348 from EAT-SSU/fix/#327
[#327] λ‹‰λ„€μž„ κ·œμΉ™ μ‹œμŠ€ν…œ κ°œμ„ 
2 parents 4120fbc + 2abf85e commit a9915cd

File tree

7 files changed

+392
-107
lines changed

7 files changed

+392
-107
lines changed

β€ŽEATSSU/App/Sources/Presentation/Auth/Enum/NIcknameTextFieldResultType.swiftβ€Ž

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,32 +13,76 @@ enum NicknameTextFieldResultType {
1313
/// common
1414
case textFieldEmpty
1515
/// nickname
16-
case nicknameTextFieldOver
1716
case nicknameTextFieldDuplicated
1817
case nicknameTextFieldDoubleCheck
1918
case nicknameTextFieldValid
20-
19+
case invalidLength
20+
case invalidStartOrEnd
21+
case consecutiveSpecialChars
22+
case onlyNumbers
23+
case invalidCharacters
24+
case bannedWord
25+
case whitespaceAtStartOrEnd
26+
case consecutiveWhitespace
27+
case emojiOrSpecialChar
28+
case adminRelatedWord
29+
case serviceNameWord
30+
case profanityWord
31+
2132
var hintMessage: String {
2233
switch self {
2334
case .textFieldEmpty:
24-
"ν•„μˆ˜ μž…λ ₯ μ‚¬ν•­μž…λ‹ˆλ‹€."
25-
case .nicknameTextFieldOver:
26-
"2~8μžλ‚΄λ‘œ μž…λ ₯ν•΄μ£Όμ„Έμš”."
35+
"ν•„μˆ˜ μž…λ ₯ μ‚¬ν•­μž…λ‹ˆλ‹€"
2736
case .nicknameTextFieldDoubleCheck:
2837
"쀑볡 확인을 μ§„ν–‰ν•΄μ£Όμ„Έμš”."
2938
case .nicknameTextFieldDuplicated:
3039
"이미 μ‚¬μš© 쀑인 λ‹‰λ„€μž„μ΄μ—μš”."
3140
case .nicknameTextFieldValid:
32-
"μ‚¬μš©κ°€λŠ₯ν•œ λ‹‰λ„€μž„μ΄μ—μš”."
41+
"μ‚¬μš©κ°€λŠ₯ν•œ λ‹‰λ„€μž„μ΄μ—μš”"
42+
case .invalidLength:
43+
"2~16κΈ€μžλ₯Ό μž…λ ₯ν•΄ μ£Όμ„Έμš”."
44+
case .invalidStartOrEnd:
45+
"특수문자둜 μ‹œμž‘/λλ‚˜λŠ” λ‹‰λ„€μž„μ€ μ‚¬μš©ν•  수 μ—†μ–΄μš”."
46+
case .consecutiveSpecialChars:
47+
"μ—°μ†λœ 특수문자(--, __)λŠ” μ‚¬μš©ν•  수 μ—†μ–΄μš”."
48+
case .onlyNumbers:
49+
"숫자만으둜 된 λ‹‰λ„€μž„μ€ μ‚¬μš©ν•  수 μ—†μ–΄μš”."
50+
case .invalidCharacters:
51+
"ν—ˆμš© 문자(ν•œκΈ€/영문/숫자)만 μ‚¬μš©ν•  수 μžˆμ–΄μš”."
52+
case .bannedWord:
53+
"μ‚¬μš©ν•  수 μ—†λŠ” 단어가 ν¬ν•¨λ˜μ–΄ μžˆμ–΄μš”."
54+
case .whitespaceAtStartOrEnd:
55+
"λ„μ–΄μ“°κΈ°λ‘œ μ‹œμž‘/λλ‚˜λŠ” λ‹‰λ„€μž„μ€ μ‚¬μš©ν•  수 μ—†μ–΄μš”."
56+
case .consecutiveWhitespace:
57+
"μ—°μ†λœ λ„μ–΄μ“°κΈ°λŠ” μ‚¬μš©ν•  수 μ—†μ–΄μš”."
58+
case .emojiOrSpecialChar:
59+
"이λͺ¨μ§€, νŠΉμˆ˜λ¬ΈμžλŠ” μ‚¬μš©ν•  수 μ—†μ–΄μš”."
60+
case .adminRelatedWord:
61+
"κ΄€λ¦¬μžλ‘œ ν˜Όλ™λ  수 μžˆλŠ” λ‹‰λ„€μž„μ€ μ‚¬μš©ν•  수 μ—†μ–΄μš”."
62+
case .serviceNameWord:
63+
"μ„œλΉ„μŠ€λͺ… 단독 λ‹‰λ„€μž„μ€ μ‚¬μš©ν•  수 μ—†μ–΄μš”."
64+
case .profanityWord:
65+
"μš•μ„€, 비속어 λ“±μ˜ ν‘œν˜„μ΄ ν¬ν•¨λœ λ‹‰λ„€μž„μ€ μ‚¬μš©ν•  수 μ—†μ–΄μš”."
3366
}
3467
}
3568

3669
var textColor: UIColor {
3770
switch self {
38-
case .textFieldEmpty, .nicknameTextFieldOver, .nicknameTextFieldDuplicated, .nicknameTextFieldDoubleCheck:
71+
case .nicknameTextFieldValid:
72+
EATSSUDesignAsset.Color.GrayScale.gray600.color
73+
default:
3974
.primary
75+
}
76+
}
77+
78+
var borderColor: UIColor {
79+
switch self {
4080
case .nicknameTextFieldValid:
41-
EATSSUDesignAsset.Color.info.color
81+
EATSSUDesignAsset.Color.Main.primary.color
82+
case .textFieldEmpty, .nicknameTextFieldDoubleCheck:
83+
EATSSUDesignAsset.Color.GrayScale.gray100.color
84+
default:
85+
.primary
4286
}
4387
}
4488
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
//
2+
// NicknameBannedWords.swift
3+
// EATSSU
4+
//
5+
// Created by ν™©μƒν™˜ on 10/25/25.
6+
//
7+
8+
import Foundation
9+
10+
struct NicknameBannedWords {
11+
12+
// MARK: - μ‹œμŠ€ν…œ/운영 κ΄€λ ¨ κΈˆμ§€μ–΄
13+
static let systemWords: [String] = [
14+
"admin", "κ΄€λ¦¬μž",
15+
"manager", "운영자",
16+
"system",
17+
]
18+
19+
// MARK: - μ„œλΉ„μŠ€λͺ…/λΈŒλžœλ“œλͺ…
20+
static let serviceWords: [String] = [
21+
"eatssu", "EATSSU", "μž‡μŠˆ", "EatSSU",
22+
"EAT-SSU", "읻슈", "잍슈", "μž‡μ“”", "μž‡μ“²", "μž‡μ”¨μœ ", "μž‡μŠˆμš°", "μž‡μŠˆμ›…",
23+
"eat-ssu", "eatsu", "e4tssu", "3at-ssu", "ea7-ssu", "e @t-ssu",
24+
"e.at.ssu", "e-a-t-s-s-u", "e_a_t_s_s_u", "e a t ssu",
25+
"eat_ssu", "eatssu_", "eatssu123", "e @tssu", "Δ“at-ssu",
26+
"3atssu", "eat$u", "eat5su", "eats$u", "eats-u", "E4T슈"
27+
]
28+
29+
// MARK: - μš•μ„€/비속어 (μž„μ‹œ κΈ°λ³Έ 리슀트)
30+
static let profanityWords: [String] = [
31+
"μ‹œλ°œ", "μ”¨λ°œ", "γ……γ…‚",
32+
"병신", "γ…‚γ……",
33+
"κ°œμƒˆλΌ", "γ„±γ……γ„²",
34+
"μ‘΄λ‚˜", "γ…ˆγ„΄",
35+
"fuck", "shit", "bitch"
36+
// ν•„μš”μ‹œ μΆ”κ°€
37+
]
38+
39+
// MARK: - 전체 κΈˆμ§€μ–΄ 리슀트
40+
static var allBannedWords: [String] {
41+
return systemWords + serviceWords + profanityWords
42+
}
43+
44+
// MARK: - κΈˆμ§€μ–΄ 포함 μ—¬λΆ€ 체크
45+
static func containsBannedWord(_ nickname: String) -> Bool {
46+
let lowercased = nickname.lowercased()
47+
48+
return allBannedWords.contains { bannedWord in
49+
lowercased.contains(bannedWord.lowercased())
50+
}
51+
}
52+
53+
// MARK: - νƒ€μž…λ³„ κΈˆμ§€μ–΄ 체크
54+
55+
/// κ΄€λ¦¬μž/운영자 κ΄€λ ¨ κΈˆμ§€μ–΄ 체크
56+
static func containsAdminWord(_ nickname: String) -> Bool {
57+
let lowercased = nickname.lowercased()
58+
59+
return systemWords.contains { word in
60+
lowercased.contains(word.lowercased())
61+
}
62+
}
63+
64+
/// μ„œλΉ„μŠ€λͺ…/λΈŒλžœλ“œλͺ… κΈˆμ§€μ–΄ 체크
65+
static func containsServiceName(_ nickname: String) -> Bool {
66+
let lowercased = nickname.lowercased()
67+
68+
return serviceWords.contains { word in
69+
lowercased.contains(word.lowercased())
70+
}
71+
}
72+
73+
/// μš•μ„€/비속어 체크
74+
static func containsProfanity(_ nickname: String) -> Bool {
75+
let lowercased = nickname.lowercased()
76+
77+
return profanityWords.contains { word in
78+
lowercased.contains(word.lowercased())
79+
}
80+
}
81+
}
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
//
2+
// NicknameValidator.swift
3+
// EATSSU
4+
//
5+
// Created by ν™©μƒν™˜ on 10/24/25.
6+
//
7+
8+
import Foundation
9+
10+
final class NicknameValidator {
11+
12+
// MARK: - Validation Methods
13+
14+
/// λ‹‰λ„€μž„ μœ νš¨μ„± 검사 (λͺ¨λ“  κ·œμΉ™ 체크)
15+
static func validate(_ nickname: String) -> NicknameTextFieldResultType? {
16+
// 빈 λ¬Έμžμ—΄ 체크
17+
if nickname.isEmpty {
18+
return .textFieldEmpty
19+
}
20+
21+
// 길이 체크 (2~16자)
22+
if !(2...16).contains(nickname.count) {
23+
return .invalidLength
24+
}
25+
26+
// 띄어쓰기 μ‹œμž‘/끝 체크
27+
if hasWhitespaceAtStartOrEnd(nickname) {
28+
return .whitespaceAtStartOrEnd
29+
}
30+
31+
// 연속 띄어쓰기 체크
32+
if hasConsecutiveWhitespace(nickname) {
33+
return .consecutiveWhitespace
34+
}
35+
36+
// 이λͺ¨μ§€ 및 ν—ˆμš©λ˜μ§€ μ•Šμ€ 특수문자 체크
37+
if containsEmojiOrInvalidSpecialChar(nickname) {
38+
return .emojiOrSpecialChar
39+
}
40+
41+
// ν—ˆμš©λ˜μ§€ μ•Šμ€ 문자 체크
42+
if !isAllowedCharacters(nickname) {
43+
return .invalidCharacters
44+
}
45+
46+
// μ‹œμž‘κ³Ό 끝이 ν•œκΈ€(μ΄ˆμ„±ν¬ν•¨), 영문, μˆ«μžμΈμ§€ 체크
47+
if !isValidStartAndEnd(nickname) {
48+
return .invalidStartOrEnd
49+
}
50+
51+
// 특수문자 연속 μ‚¬μš© 체크
52+
if hasConsecutiveSpecialChars(nickname) {
53+
return .consecutiveSpecialChars
54+
}
55+
56+
// 숫자둜만 κ΅¬μ„±λ˜μ—ˆλŠ”μ§€ 체크
57+
if isOnlyNumbers(nickname) {
58+
return .onlyNumbers
59+
}
60+
61+
// κΈˆμ§€μ–΄ 체크 (νƒ€μž…λ³„)
62+
if let bannedWordType = checkBannedWordType(nickname) {
63+
return bannedWordType
64+
}
65+
66+
// λͺ¨λ“  검사 톡과 - 쀑볡 확인 ν•„μš”
67+
return .nicknameTextFieldDoubleCheck
68+
}
69+
70+
// MARK: - Private Helper Methods
71+
72+
/// λ„μ–΄μ“°κΈ°λ‘œ μ‹œμž‘ν•˜κ±°λ‚˜ λλ‚˜λŠ”μ§€ 체크
73+
private static func hasWhitespaceAtStartOrEnd(_ nickname: String) -> Bool {
74+
return nickname.first == " " || nickname.last == " "
75+
}
76+
77+
/// μ—°μ†λœ 띄어쓰기가 μžˆλŠ”μ§€ 체크
78+
private static func hasConsecutiveWhitespace(_ nickname: String) -> Bool {
79+
return nickname.contains(" ")
80+
}
81+
82+
/// 이λͺ¨μ§€ λ˜λŠ” ν—ˆμš©λ˜μ§€ μ•Šμ€ 특수문자 포함 μ—¬λΆ€ 체크
83+
private static func containsEmojiOrInvalidSpecialChar(_ nickname: String) -> Bool {
84+
for scalar in nickname.unicodeScalars {
85+
// 이λͺ¨μ§€ 체크
86+
if scalar.properties.isEmoji {
87+
return true
88+
}
89+
90+
// ν—ˆμš©λœ 문자 집합에 μ—†λŠ” 특수문자 체크
91+
let char = Character(scalar)
92+
if char.isPunctuation || char.isSymbol {
93+
// - 와 _ λŠ” ν—ˆμš©
94+
if char != "-" && char != "_" {
95+
return true
96+
}
97+
}
98+
}
99+
return false
100+
}
101+
102+
/// ν—ˆμš©λœ 문자만 ν¬ν•¨λ˜μ–΄ μžˆλŠ”μ§€ 체크 (ν•œκΈ€(μ΄ˆμ„±ν¬ν•¨), 영문, 숫자, 띄어쓰기, -, _)
103+
private static func isAllowedCharacters(_ nickname: String) -> Bool {
104+
var allowed = CharacterSet()
105+
106+
// 영문 μ†Œλ¬Έμž
107+
allowed.formUnion(CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyz"))
108+
// 영문 λŒ€λ¬Έμž
109+
allowed.formUnion(CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZ"))
110+
// 숫자
111+
allowed.formUnion(CharacterSet.decimalDigits)
112+
// ν•œκΈ€ (μ™„μ„±λœ κΈ€μž)
113+
allowed.formUnion(CharacterSet(charactersIn: "κ°€".unicodeScalars.first!..."힣".unicodeScalars.first!))
114+
// ν•œκΈ€ 자음
115+
allowed.formUnion(CharacterSet(charactersIn: "γ„±".unicodeScalars.first!..."γ…Ž".unicodeScalars.first!))
116+
// ν•œκΈ€ λͺ¨μŒ
117+
allowed.formUnion(CharacterSet(charactersIn: "ㅏ".unicodeScalars.first!..."γ…£".unicodeScalars.first!))
118+
// 특수문자 (- 와 _ 만 ν—ˆμš©)
119+
allowed.formUnion(CharacterSet(charactersIn: "-_"))
120+
// 띄어쓰기 ν—ˆμš©
121+
allowed.formUnion(CharacterSet(charactersIn: " "))
122+
123+
return nickname.unicodeScalars.allSatisfy { allowed.contains($0) }
124+
}
125+
126+
/// μ‹œμž‘κ³Ό 끝이 ν•œκΈ€(μ΄ˆμ„±ν¬ν•¨), 영문, μˆ«μžμΈμ§€ 체크
127+
private static func isValidStartAndEnd(_ nickname: String) -> Bool {
128+
guard let first = nickname.first, let last = nickname.last else {
129+
return false
130+
}
131+
132+
var valid = CharacterSet()
133+
134+
// 영문 μ†Œλ¬Έμž
135+
valid.formUnion(CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyz"))
136+
// 영문 λŒ€λ¬Έμž
137+
valid.formUnion(CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZ"))
138+
// 숫자
139+
valid.formUnion(CharacterSet.decimalDigits)
140+
// ν•œκΈ€ (μ™„μ„±λœ κΈ€μž)
141+
valid.formUnion(CharacterSet(charactersIn: "κ°€".unicodeScalars.first!..."힣".unicodeScalars.first!))
142+
// ν•œκΈ€ 자음
143+
valid.formUnion(CharacterSet(charactersIn: "γ„±".unicodeScalars.first!..."γ…Ž".unicodeScalars.first!))
144+
// ν•œκΈ€ λͺ¨μŒ
145+
valid.formUnion(CharacterSet(charactersIn: "ㅏ".unicodeScalars.first!..."γ…£".unicodeScalars.first!))
146+
147+
let firstString = String(first)
148+
let lastString = String(last)
149+
150+
let isFirstValid = firstString.unicodeScalars.allSatisfy { valid.contains($0) }
151+
let isLastValid = lastString.unicodeScalars.allSatisfy { valid.contains($0) }
152+
153+
return isFirstValid && isLastValid
154+
}
155+
156+
/// 특수문자(-, _)κ°€ μ—°μ†μœΌλ‘œ μ‚¬μš©λ˜μ—ˆλŠ”μ§€ 체크
157+
private static func hasConsecutiveSpecialChars(_ nickname: String) -> Bool {
158+
let specialChars: [Character] = ["-", "_"]
159+
160+
for i in 0..<nickname.count - 1 {
161+
let currentIndex = nickname.index(nickname.startIndex, offsetBy: i)
162+
let nextIndex = nickname.index(nickname.startIndex, offsetBy: i + 1)
163+
164+
let currentChar = nickname[currentIndex]
165+
let nextChar = nickname[nextIndex]
166+
167+
if specialChars.contains(currentChar) && specialChars.contains(nextChar) {
168+
return true
169+
}
170+
}
171+
172+
return false
173+
}
174+
175+
/// 숫자둜만 κ΅¬μ„±λ˜μ–΄ μžˆλŠ”μ§€ 체크
176+
private static func isOnlyNumbers(_ nickname: String) -> Bool {
177+
return nickname.allSatisfy { $0.isNumber }
178+
}
179+
180+
/// κΈˆμ§€μ–΄ νƒ€μž…λ³„ 체크
181+
private static func checkBannedWordType(_ nickname: String) -> NicknameTextFieldResultType? {
182+
// κ΄€λ¦¬μž/운영자 κ΄€λ ¨ κΈˆμ§€μ–΄ 체크
183+
if NicknameBannedWords.containsAdminWord(nickname) {
184+
return .adminRelatedWord
185+
}
186+
187+
// μ„œλΉ„μŠ€λͺ…/λΈŒλžœλ“œλͺ… κΈˆμ§€μ–΄ 체크
188+
if NicknameBannedWords.containsServiceName(nickname) {
189+
return .serviceNameWord
190+
}
191+
192+
// μš•μ„€/비속어 체크
193+
if NicknameBannedWords.containsProfanity(nickname) {
194+
return .profanityWord
195+
}
196+
197+
return nil
198+
}
199+
}

0 commit comments

Comments
Β (0)