Skip to content

Commit 2fb2977

Browse files
authored
#288] Todo에서 다른 Todo를 참조할 수 있도록 구현한다 (#314)
* feat: 신규 사용자일 때 todo의 auto_increment 용 플래그를 추가 * style: 로그 개선 * feat: Todo 업데이트 시 한 트랜잭션 내에서 auto increment와 업데이트를 같이 하도록 추가 * feat: Todo 관련 모델에 number 추가 * ui: number을 보여줘야 하는 ui에 '#번호' 형태로 구성 * feat: todo refs resolve 로직 추가 * ui: todo refs 렌더링 결과를 상세와 프리뷰에 반영 * ui: 마크다운 렌더러에서 탭하면 시트로 해당 todo를 띄울수 있도록 추가 * feat: todo refs 조회 구조를 reference item 기반으로 변경 * ui: todo refs를 전용 markdown 컴포넌트로 렌더링 * ui: '- refs # ' 사용법 추가 * ui: '- refs # ' 사용법 추가 * style: 로그 원복
1 parent bcaec8f commit 2fb2977

31 files changed

Lines changed: 594 additions & 44 deletions

DevLog/App/Assembler/DomainAssembler.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ private extension DomainAssembler {
5555
FetchTodoByIdUseCaseImpl(container.resolve(TodoRepository.self))
5656
}
5757

58+
container.register(FetchReferenceItemsUseCase.self) {
59+
FetchReferenceItemsUseCaseImpl(container.resolve(TodoRepository.self))
60+
}
61+
5862
container.register(FetchTodosUseCase.self) {
5963
FetchTodosUseCaseImpl(container.resolve(TodoRepository.self))
6064
}

DevLog/App/RootView.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ struct RootView: View {
4242
case .todoDetail(let todoId):
4343
NavigationStack {
4444
TodoDetailView(viewModel: TodoDetailViewModel(
45-
fetchUseCase: container.resolve(FetchTodoByIdUseCase.self),
45+
fetchTodoUseCase: container.resolve(FetchTodoByIdUseCase.self),
46+
fetchReferenceItemsUseCase: container.resolve(FetchReferenceItemsUseCase.self),
4647
upsertUseCase: container.resolve(UpsertTodoUseCase.self),
4748
todoId: todoId,
4849
showEditButton: false

DevLog/Data/DTO/TodoDTO.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ struct TodoRequest: Encodable {
1212
let isPinned: Bool
1313
let isCompleted: Bool
1414
let isChecked: Bool
15+
let number: Int?
1516
let title: String
1617
let content: String
1718
let createdAt: Date
@@ -28,6 +29,7 @@ struct TodoResponse {
2829
let isPinned: Bool
2930
let isCompleted: Bool
3031
let isChecked: Bool
32+
let number: Int
3133
let title: String
3234
let content: String
3335
let createdAt: Date

DevLog/Data/Mapper/TodoMapping.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ extension TodoRequest {
1212
isPinned: entity.isPinned,
1313
isCompleted: entity.isCompleted,
1414
isChecked: entity.isChecked,
15+
number: entity.number,
1516
title: entity.title,
1617
content: entity.content,
1718
createdAt: entity.createdAt,
@@ -35,6 +36,7 @@ extension TodoResponse {
3536
isPinned: self.isPinned,
3637
isCompleted: self.isCompleted,
3738
isChecked: self.isChecked,
39+
number: self.number,
3840
title: self.title,
3941
content: self.content,
4042
createdAt: self.createdAt,

DevLog/Data/Repository/TodoRepositoryImpl.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ final class TodoRepositoryImpl: TodoRepository {
2424
let response = try await todoService.fetchTodo(todoId: todoId)
2525
return try response.toDomain()
2626
}
27+
28+
func fetchReferenceItems(_ numbers: [Int]) async throws -> [Int: TodoReferenceItem] {
29+
try await todoService.fetchReferenceItems(numbers)
30+
}
2731

2832
func upsertTodo(_ todo: Todo) async throws {
2933
let request = TodoRequest.fromDomain(todo)

DevLog/Domain/Entity/Todo.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ struct Todo: Identifiable, Hashable {
1212
var isPinned: Bool // 해당 할 일이 상단에 고정되어 있는지 여부
1313
var isCompleted: Bool // 해당 할 일의 완료 여부
1414
var isChecked: Bool // 해당 할 일의 체크 여부
15+
var number: Int? // 사용자에게 노출되는 Todo 번호
1516
var title: String // 할 일의 제목
1617
var content: String // 할 일의 설명
1718
var createdAt: Date // 할 일 생성 날짜
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//
2+
// TodoReferenceItem.swift
3+
// DevLog
4+
//
5+
// Created by opfic on 3/25/26.
6+
//
7+
8+
import Foundation
9+
10+
struct TodoReferenceItem: Identifiable, Equatable {
11+
let id: String
12+
let title: String
13+
let kind: TodoKind
14+
}

DevLog/Domain/Extension/Array.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,20 @@ extension Array: @retroactive RawRepresentable where Element: Codable {
2828
return result
2929
}
3030
}
31+
32+
extension Array {
33+
func chunked(maxCount: Int) -> [[Element]] {
34+
guard 0 < maxCount else { return [] }
35+
36+
var chunks = [[Element]]()
37+
var startIndex = 0
38+
39+
while startIndex < count {
40+
let endIndex = Swift.min(startIndex + maxCount, count)
41+
chunks.append(Array(self[startIndex..<endIndex]))
42+
startIndex = endIndex
43+
}
44+
45+
return chunks
46+
}
47+
}

DevLog/Domain/Extension/String.swift

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,37 @@
77

88
import Foundation
99

10-
// UpperCamelCase → snake_case 변환
1110
extension String {
12-
var toSnakeCase: String {
13-
unicodeScalars.reduce("") {
14-
if CharacterSet.uppercaseLetters.contains($1) {
15-
return ($0.isEmpty ? "" : $0 + "_") + String($1).lowercased()
11+
private static let todoReferencePattern = #"^([ \t]*)-[ \t]+refs[ \t]+#(\d+)[ \t]*$"#
12+
13+
var todoReferenceNumbers: [Int] {
14+
guard
15+
let expression = try? NSRegularExpression(
16+
pattern: Self.todoReferencePattern,
17+
options: [.anchorsMatchLines]
18+
)
19+
else {
20+
return []
21+
}
22+
23+
let range = NSRange(startIndex..., in: self)
24+
let matches = expression.matches(in: self, options: [], range: range)
25+
var numbers = [Int]()
26+
var seen = Set<Int>()
27+
28+
for match in matches {
29+
guard
30+
let numberRange = Range(match.range(at: 2), in: self),
31+
let number = Int(self[numberRange]),
32+
!seen.contains(number)
33+
else {
34+
continue
1635
}
17-
return $0 + String($1)
36+
37+
seen.insert(number)
38+
numbers.append(number)
1839
}
19-
}
20-
21-
var upperCamelCase: String {
22-
guard let first = self.first else { return "" }
23-
return first.uppercased() + self.dropFirst()
40+
41+
return numbers
2442
}
2543
}

DevLog/Domain/Protocol/TodoRepository.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import Foundation
1010
protocol TodoRepository {
1111
func fetchTodos(_ query: TodoQuery, cursor: TodoCursor?) async throws -> TodoPage
1212
func fetchTodo(_ todoId: String) async throws -> Todo
13+
func fetchReferenceItems(_ numbers: [Int]) async throws -> [Int: TodoReferenceItem]
1314
func upsertTodo(_ todo: Todo) async throws
1415
func deleteTodo(_ todoId: String) async throws
1516
func undoDeleteTodo(_ todoId: String) async throws

0 commit comments

Comments
 (0)