feat: 최근 들은 콘텐츠 로컬 DB 추가

This commit is contained in:
Yu Sung
2025-07-28 22:34:34 +09:00
parent a73cafa08c
commit 70af4cb3dd
10 changed files with 306 additions and 67 deletions

View File

@@ -0,0 +1,24 @@
//
// PersistenceController.swift
// SodaLive
//
// Created by klaus on 7/28/25.
//
import CoreData
struct PersistenceController {
static let shared = PersistenceController()
let container: NSPersistentContainer
init() {
container = NSPersistentContainer(name: "DataModel")
container.loadPersistentStores { description, error in
if let error = error {
fatalError("Error loading Core Data stores: \(error)")
}
}
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
}
}

View File

@@ -0,0 +1,25 @@
//
// RecentContent+CoreDataClass.swift
// SodaLive
//
// Created by klaus on 7/28/25.
//
import Foundation
import CoreData
@objc(RecentContent)
public class RecentContent: NSManagedObject {
}
extension RecentContent {
@nonobjc public class func fetchRequest() -> NSFetchRequest<RecentContent> {
return NSFetchRequest<RecentContent>(entityName: "RecentContent")
}
@NSManaged public var contentId: Int64
@NSManaged public var coverImageUrl: String
@NSManaged public var title: String
@NSManaged public var creatorNickname: String
@NSManaged public var listenedAt: Date
}

View File

@@ -0,0 +1,123 @@
//
// RecentContentService.swift
// SodaLive
//
// Created by klaus on 7/28/25.
//
import CoreData
import Combine
class RecentContentService {
private let viewContext: NSManagedObjectContext
init(context: NSManagedObjectContext = PersistenceController.shared.container.viewContext) {
self.viewContext = context
}
func getRecentContents(limit: Int = 10) -> [RecentContent] {
let request = RecentContent.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(key: "listenedAt", ascending: false)]
request.fetchLimit = limit
do {
return try viewContext.fetch(request)
} catch {
print("Error fetching recent contents: \(error)")
return []
}
}
func insertRecentContent(contentId: Int64, coverImageUrl: String, title: String, creatorNickname: String) {
// Check if content already exists
if let existingContent = findContent(byId: contentId) {
// Update timestamp
existingContent.listenedAt = Date()
saveContext()
} else {
// Create new content
let newContent = RecentContent(context: viewContext)
newContent.contentId = contentId
newContent.coverImageUrl = coverImageUrl
newContent.title = title
newContent.creatorNickname = creatorNickname
newContent.listenedAt = Date()
saveContext()
}
// Keep only most recent 10 items
keepMostRecent(limit: 10)
}
func deleteByContentId(contentId: Int64) {
if let content = findContent(byId: contentId) {
viewContext.delete(content)
saveContext()
}
}
func getCount() -> Int {
let request = RecentContent.fetchRequest()
do {
return try viewContext.count(for: request)
} catch {
print("Error counting recent contents: \(error)")
return 0
}
}
func truncate() {
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = RecentContent.fetchRequest()
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
do {
try viewContext.execute(deleteRequest)
saveContext()
} catch {
print("Error truncating recent contents: \(error)")
}
}
private func keepMostRecent(limit: Int) {
let count = getCount()
if count <= limit { return }
let request = RecentContent.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(key: "listenedAt", ascending: false)]
do {
let allContents = try viewContext.fetch(request)
for i in limit..<allContents.count {
viewContext.delete(allContents[i])
}
saveContext()
} catch {
print("Error keeping most recent contents: \(error)")
}
}
private func findContent(byId contentId: Int64) -> RecentContent? {
let request = RecentContent.fetchRequest()
request.predicate = NSPredicate(format: "contentId == %lld", contentId)
request.fetchLimit = 1
do {
let results = try viewContext.fetch(request)
return results.first
} catch {
print("Error finding content by ID: \(error)")
return nil
}
}
private func saveContext() {
if viewContext.hasChanges {
do {
try viewContext.save()
} catch {
print("Error saving context: \(error)")
}
}
}
}

View File

@@ -0,0 +1,55 @@
//
// RecentContentViewModel.swift
// SodaLive
//
// Created by klaus on 7/28/25.
//
import SwiftUI
import Combine
class RecentContentViewModel: ObservableObject {
@Published var recentContents: [RecentContent] = []
@Published var contentCount: Int = 0
private let service: RecentContentService
private var cancellables = Set<AnyCancellable>()
init(service: RecentContentService = RecentContentService()) {
self.service = service
fetchRecentContents()
fetchContentCount()
}
func fetchRecentContents(limit: Int = 10) {
self.recentContents = service.getRecentContents(limit: limit)
self.contentCount = recentContents.count
}
func fetchContentCount() {
self.contentCount = service.getCount()
}
func insertRecentContent(contentId: Int64, coverImageUrl: String, title: String, creatorNickname: String) {
service.insertRecentContent(
contentId: contentId,
coverImageUrl: coverImageUrl,
title: title,
creatorNickname: creatorNickname
)
fetchRecentContents()
fetchContentCount()
}
func deleteByContentId(contentId: Int64) {
service.deleteByContentId(contentId: contentId)
fetchRecentContents()
fetchContentCount()
}
func truncate() {
service.truncate()
fetchRecentContents()
fetchContentCount()
}
}