Skip to content

Commit eb02aa9

Browse files
committed
(chore): Add SCIDUserHelper and expand user types
1 parent 43f75b8 commit eb02aa9

File tree

5 files changed

+162
-10
lines changed

5 files changed

+162
-10
lines changed

ScribbleLab.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ScribbleLab/Misc/SCIDUserHelper.swift

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
//
2+
// SCIDUserHelper.swift
3+
// ScribbleLab
4+
//
5+
// Copyright (c) 2024 ScribbleLabApp LLC. - All rights reserved.
6+
//
7+
// Redistribution and use in source and binary forms, with or without
8+
// modification, are permitted provided that the following conditions are met:
9+
//
10+
// 1. Redistributions of source code must retain the above copyright notice, this
11+
// list of conditions and the following disclaimer.
12+
//
13+
// 2. Redistributions in binary form must reproduce the above copyright notice,
14+
// this list of conditions and the following disclaimer in the documentation
15+
// and/or other materials provided with the distribution.
16+
//
17+
// 3. Neither the name of the copyright holder nor the names of its
18+
// contributors may be used to endorse or promote products derived from
19+
// this software without specific prior written permission.
20+
//
21+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22+
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23+
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24+
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25+
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26+
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27+
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28+
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29+
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
32+
import Foundation
33+
import ScribbleFoundation
34+
35+
internal class SCIDUserHelper {
36+
37+
/// Calculates the age based on the birth date.
38+
///
39+
/// - Parameter birthDate: The birth date of the user.
40+
/// - Returns: The age of the user in years, or `nil` if the birth date is not valid.
41+
static func calculateAge(birthDate: Date?) -> Int? {
42+
guard let birthDate = birthDate else { return nil }
43+
44+
let calendar = Calendar.current
45+
let components = calendar.dateComponents([.year], from: birthDate, to: Date())
46+
47+
return components.year
48+
}
49+
50+
/// Calculates the remaining duration until the trial period ends.
51+
///
52+
/// - Parameter trialEndDate: The end date of the trial period.
53+
/// - Returns: A string representing the remaining duration (e.g., "2 days", "1 month"), or
54+
/// `nil` if the trial end date is not valid.
55+
static func trialDurationUntilEnd(trialEndDate: Date?) -> String? {
56+
guard let trialEndDate = trialEndDate else { return nil }
57+
58+
let calendar = Calendar.current
59+
let currentDate = Date()
60+
61+
if trialEndDate < currentDate {
62+
return "Trial period has ended"
63+
}
64+
65+
let components = calendar.dateComponents(
66+
[.day, .hour, .minute],
67+
from: currentDate,
68+
to: trialEndDate
69+
)
70+
71+
var durationStr = ""
72+
73+
if let days = components.day, days > 0 {
74+
durationStr += "\(days) day\(days > 1 ? "s" : "")"
75+
}
76+
77+
if let hours = components.hour, hours > 0 {
78+
durationStr += (durationStr.isEmpty ? "" : ", ") + "\(hours) hour\(hours > 1 ? "s" : "")"
79+
}
80+
81+
if let minutes = components.minute, minutes > 0 {
82+
durationStr += (durationStr.isEmpty ? "" : ", ") + "\(minutes) minute\(minutes > 1 ? "s" : "")"
83+
}
84+
85+
return durationStr.isEmpty ? "Trial Period ends soon" : durationStr
86+
}
87+
}

ScribbleLab/Misc/User.swift

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,27 @@ struct SLUser: Identifiable, Hashable, Codable {
6565
/// The user’s email address.
6666
///
6767
/// This is a unique, immutable property for each user, used for authentication and notifications.
68-
let email: String
68+
var email: String
69+
70+
/// The user’s birthdate, if provided.
71+
///
72+
/// This may be used for age-restricted content or features in the application.
73+
var birthDate: Date?
74+
75+
/// The user’s role within the application.
76+
///
77+
/// This defines access levels or permissions and can be extended as necessary.
78+
var usrRole: SCIDUserRole
79+
80+
/// The date when the user’s trial period ends, if applicable.
81+
///
82+
/// - Note: If the user has a premium subscription, this value may be `nil`.
83+
var trialPeriodEndDate: Date?
84+
85+
/// A Boolean indicating whether the user has a premium subscription.
86+
///
87+
/// Premium users may have access to additional features within the application.
88+
var hasPremium: Bool
6989

7090
/// A computed property that indicates whether this `SLUser` instance represents the current authenticated user.
7191
///
@@ -77,3 +97,14 @@ struct SLUser: Identifiable, Hashable, Codable {
7797
return currentUid == id
7898
}
7999
}
100+
101+
/// Enumeration of user roles in the application.
102+
///
103+
/// - intern: A user with intern-level access.
104+
/// - developer: A user with developer-level access.
105+
/// - none: A default role with basic access.
106+
enum SCIDUserRole: String, Codable {
107+
case intern
108+
case developer
109+
case none
110+
}

ScribbleLab/Services/SCIDAuthentication/SCIDAuthService.swift

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,19 +169,28 @@ class SCIDAuthService: Authenticatable {
169169
/// - uid: The unique identifier for the user.
170170
/// - username: The username associated with the user.
171171
/// - email: The email address associated with the user.
172+
/// - usrRole: The role of the user (intern, developer, etc.).
173+
/// - hasPremium: A Boolean indicating whether the user has a premium account.
172174
///
173175
/// - Note: This function sets the current user with the provided details and uploads it to Firestore.
174176
/// It is a private function and should be called from within the `SLAuthService`.
175177
///
176178
/// > WARNING
177-
/// > This function is called internally by the `SLAuthService` and should not typically be accessed directly.
178-
///
179+
/// > This function is called internally by the `SCIDAuthService` and should not typically be accessed directly.
179180
func uploadUserData(
180181
uid: String,
181182
username: String,
182-
email: String
183+
email: String,
184+
usrRole: SCIDUserRole = .none,
185+
hasPremium: Bool = false
183186
) async {
184-
let user = SLUser(id: uid, username: username, email: email)
187+
let user = SLUser(
188+
id: uid,
189+
username: username,
190+
email: email,
191+
usrRole: usrRole,
192+
hasPremium: hasPremium
193+
)
185194
self.currentUser = user
186195

187196
do {

ScribbleLab/Services/User/SCIDUserService.swift

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,34 +50,59 @@ struct SCIDUserService {
5050

5151
let profileImageUrl = data["profileImageUrl"] as? String
5252
let fullname = data["fullname"] as? String
53+
let birthDate = (data["birthDate"] as? Timestamp)?.dateValue()
54+
let usrRoleString = data["usrRole"] as? String
55+
let usrRole = SCIDUserRole(rawValue: usrRoleString ?? "") ?? .none
56+
let trialPeriodEndDate = (data["trialPeriodEndDate"] as? Timestamp)?.dateValue()
57+
let hasPremium = data["hasPremium"] as? Bool ?? false
5358

5459
return SLUser(
5560
id: id,
5661
username: username,
5762
profileImageUrl: profileImageUrl,
5863
fullname: fullname,
59-
email: email
64+
email: email,
65+
birthDate: birthDate,
66+
usrRole: usrRole,
67+
trialPeriodEndDate: trialPeriodEndDate,
68+
hasPremium: hasPremium
6069
)
6170
}
6271

6372
static func fetchAllUsers() async throws -> [SLUser] {
6473
let snapshot = try await Firestore.firestore().collection("users").getDocuments()
6574

66-
return snapshot.documents.compactMap { document in
75+
return try snapshot.documents.compactMap { document in
6776
let data = document.data()
6877
guard let id = data["id"] as? String,
6978
let username = data["username"] as? String,
70-
let email = data["email"] as? String else { return nil }
79+
let email = data["email"] as? String else {
80+
81+
throw NSError(
82+
domain: "SCIDUserService",
83+
code: -202,
84+
userInfo: [NSLocalizedDescriptionKey: "Missing required user fields - SLUSR-E202"]
85+
)
86+
}
7187

7288
let profileImageUrl = data["profileImageUrl"] as? String
7389
let fullname = data["fullname"] as? String
90+
let birthDate = (data["birthDate"] as? Timestamp)?.dateValue()
91+
let usrRoleString = data["usrRole"] as? String
92+
let usrRole = SCIDUserRole(rawValue: usrRoleString ?? "") ?? .none
93+
let trialPeriodEndDate = (data["trialPeriodEndDate"] as? Timestamp)?.dateValue()
94+
let hasPremium = data["hasPremium"] as? Bool ?? false
7495

7596
return SLUser(
7697
id: id,
7798
username: username,
7899
profileImageUrl: profileImageUrl,
79100
fullname: fullname,
80-
email: email
101+
email: email,
102+
birthDate: birthDate,
103+
usrRole: usrRole,
104+
trialPeriodEndDate: trialPeriodEndDate,
105+
hasPremium: hasPremium
81106
)
82107
}
83108
}

0 commit comments

Comments
 (0)