diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..a9d1727 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,311 @@ +{ + "pins" : [ + { + "identity" : "async-http-client", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swift-server/async-http-client.git", + "state" : { + "revision" : "fc510a39cff61b849bf5cdff17eb2bd6d0777b49", + "version" : "1.11.5" + } + }, + { + "identity" : "async-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/async-kit.git", + "state" : { + "revision" : "c3329e444bafbb12d1d312af9191be95348a8175", + "version" : "1.13.0" + } + }, + { + "identity" : "bson", + "kind" : "remoteSourceControl", + "location" : "https://github.com/OpenKitten/BSON.git", + "state" : { + "revision" : "40956d97c36aa166b8baea5add897864e1568a78", + "version" : "7.0.29" + } + }, + { + "identity" : "console-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/console-kit.git", + "state" : { + "revision" : "a7e67a1719933318b5ab7eaaed355cde020465b1", + "version" : "4.5.0" + } + }, + { + "identity" : "fluent", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/fluent.git", + "state" : { + "revision" : "26c446002f03c5ab34b20d86873014ef3d92d0da", + "version" : "4.5.0" + } + }, + { + "identity" : "fluent-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/fluent-kit.git", + "state" : { + "revision" : "38670d2eefcba27530272946d627ac8d4e45f017", + "version" : "1.35.1" + } + }, + { + "identity" : "fluent-mongo-driver", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/fluent-mongo-driver.git", + "state" : { + "revision" : "e96cf416e4a224cf9f104c930abaa8b69fb4d8cd", + "version" : "1.1.2" + } + }, + { + "identity" : "jwt", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/jwt.git", + "state" : { + "revision" : "506d238a707c0e7c1d2cf690863902eaf3bc4e94", + "version" : "4.2.1" + } + }, + { + "identity" : "jwt-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/jwt-kit.git", + "state" : { + "revision" : "87ce13a1df913ba4d51cf00606df7ef24d455571", + "version" : "4.7.0" + } + }, + { + "identity" : "mongokitten", + "kind" : "remoteSourceControl", + "location" : "https://github.com/OpenKitten/MongoKitten.git", + "state" : { + "revision" : "f2d245e50143e9d940566dd2d20914c040f34b9f", + "version" : "6.7.6" + } + }, + { + "identity" : "multipart-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/multipart-kit.git", + "state" : { + "revision" : "0d55c35e788451ee27222783c7d363cb88092fab", + "version" : "4.5.2" + } + }, + { + "identity" : "niodns", + "kind" : "remoteSourceControl", + "location" : "https://github.com/OpenKitten/NioDNS.git", + "state" : { + "revision" : "5d1c701127f1d399cfb27c38aeb40bfde40df004", + "version" : "2.1.1" + } + }, + { + "identity" : "routing-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/routing-kit.git", + "state" : { + "revision" : "ffac7b3a127ce1e85fb232f1a6271164628809ad", + "version" : "4.6.0" + } + }, + { + "identity" : "sql-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/sql-kit.git", + "state" : { + "revision" : "3c5413a229bc2abc962dab17ea66d25e448ad344", + "version" : "3.21.0" + } + }, + { + "identity" : "swift-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-algorithms.git", + "state" : { + "revision" : "b14b7f4c528c942f121c8b860b9410b2bf57825e", + "version" : "1.0.0" + } + }, + { + "identity" : "swift-atomics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-atomics.git", + "state" : { + "revision" : "919eb1d83e02121cdb434c7bfc1f0c66ef17febe", + "version" : "1.0.2" + } + }, + { + "identity" : "swift-backtrace", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swift-server/swift-backtrace.git", + "state" : { + "revision" : "f25620d5d05e2f1ba27154b40cafea2b67566956", + "version" : "1.3.3" + } + }, + { + "identity" : "swift-case-paths", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-case-paths", + "state" : { + "revision" : "7346701ea29da0a85d4403cf3d7a589a58ae3dee", + "version" : "0.9.2" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "f504716c27d2e5d4144fa4794b12129301d17729", + "version" : "1.0.3" + } + }, + { + "identity" : "swift-crypto", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-crypto.git", + "state" : { + "revision" : "d9825fa541df64b1a7b182178d61b9a82730d01f", + "version" : "2.1.0" + } + }, + { + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log.git", + "state" : { + "revision" : "6fe203dc33195667ce1759bf0182975e4653ba1c", + "version" : "1.4.4" + } + }, + { + "identity" : "swift-metrics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-metrics.git", + "state" : { + "revision" : "53be78637ecd165d1ddedc4e20de69b8f43ec3b7", + "version" : "2.3.2" + } + }, + { + "identity" : "swift-nio", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio.git", + "state" : { + "revision" : "b4e0a274f7f34210e97e2f2c50ab02a10b549250", + "version" : "2.41.1" + } + }, + { + "identity" : "swift-nio-extras", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-extras.git", + "state" : { + "revision" : "6c84d247754ad77487a6f0694273b89b83efd056", + "version" : "1.14.0" + } + }, + { + "identity" : "swift-nio-http2", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-http2.git", + "state" : { + "revision" : "f9ab1c94c80d568efd762d2a638f25162691d766", + "version" : "1.22.1" + } + }, + { + "identity" : "swift-nio-ssl", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-ssl.git", + "state" : { + "revision" : "ba7c0d7f82affc518147ea61d240330bf7f7ea9b", + "version" : "2.22.1" + } + }, + { + "identity" : "swift-nio-transport-services", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-transport-services.git", + "state" : { + "revision" : "4e02d9cf35cabfb538c96613272fb027dd0c8692", + "version" : "1.13.1" + } + }, + { + "identity" : "swift-numerics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-numerics", + "state" : { + "revision" : "0a5bc04095a675662cf24757cc0640aa2204253b", + "version" : "1.0.2" + } + }, + { + "identity" : "swift-parsing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-parsing.git", + "state" : { + "revision" : "bc92e84968990b41640214b636667f35b6e5d44c", + "version" : "0.10.0" + } + }, + { + "identity" : "swift-url-routing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-url-routing.git", + "state" : { + "revision" : "80e8a0257ccdd639e31f709954ceca6b690fdc67", + "version" : "0.3.1" + } + }, + { + "identity" : "vapor", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/vapor.git", + "state" : { + "revision" : "dda0de537e7906414dccd551e77095be1e34e3da", + "version" : "4.65.2" + } + }, + { + "identity" : "vapor-routing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/vapor-routing.git", + "state" : { + "revision" : "f07b4d7618bf48b450ed11c9f85b74ba8b9bae6c", + "version" : "0.1.1" + } + }, + { + "identity" : "websocket-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/websocket-kit.git", + "state" : { + "revision" : "2d9d2188a08eef4a869d368daab21b3c08510991", + "version" : "2.6.1" + } + }, + { + "identity" : "xctest-dynamic-overlay", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", + "state" : { + "revision" : "30314f1ece684dd60679d598a9b89107557b67d9", + "version" : "0.4.1" + } + } + ], + "version" : 2 +} diff --git a/Package.swift b/Package.swift index 343bb36..1ed874f 100644 --- a/Package.swift +++ b/Package.swift @@ -5,6 +5,10 @@ import PackageDescription let package = Package( name: "NWSharedModels", + platforms: [ + .iOS(.v13), + .macOS(.v12) + ], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. .library( @@ -12,15 +16,64 @@ let package = Package( targets: ["NWSharedModels"]), ], dependencies: [ - // Dependencies declare other packages that this package depends on. - // .package(url: /* package url */, from: "1.0.0"), + .package(url: "https://github.com/vapor/vapor.git", from: "4.62.1"), + .package(url: "https://github.com/vapor/fluent.git", from: "4.4.0"), + .package(url: "https://github.com/vapor/fluent-mongo-driver.git", from: "1.1.2"), + .package(url: "https://github.com/vapor/jwt.git", from: "4.2.1"), + + // Route + .package(url: "https://github.com/pointfreeco/vapor-routing.git", from: "0.1.1"), +// .package(url: "https://github.com/pointfreeco/swift-parsing.git", from: "0.9.2"), + .package(url: "https://github.com/pointfreeco/swift-url-routing.git", from: "0.3.0"), + .package(url: "https://github.com/OpenKitten/BSON.git", from: "7.0.0") ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. // Targets can depend on other targets in this package, and on products in packages this package depends on. .target( name: "NWSharedModels", - dependencies: []), + dependencies: [ + .product( + name: "Vapor", + package: "vapor", + condition: .when(platforms: [.macOS, .linux]) + ), + .product( + name: "Fluent", + package: "fluent", + condition: .when(platforms: [.macOS, .linux]) + ), + .product( + name: "FluentMongoDriver", + package: "fluent-mongo-driver", + condition: .when(platforms: [.macOS, .linux]) + ), + .product( + name: "VaporRouting", + package: "vapor-routing", + condition: .when(platforms: [.macOS, .linux]) + ), + .product( + name: "JWT", + package: "jwt", + condition: .when(platforms: [.macOS, .linux]) + ), + + // IOS + .product( + name: "URLRouting", + package: "swift-url-routing", + condition: .when(platforms: [.iOS]) + ), + + .product( + name: "BSON", + package: "BSON" + ) + + ] + + ), .testTarget( name: "NWSharedModelsTests", dependencies: ["NWSharedModels"]), diff --git a/Sources/NWSharedModels/Auth/AuthMocks.swift b/Sources/NWSharedModels/Auth/AuthMocks.swift new file mode 100644 index 0000000..2f25d39 --- /dev/null +++ b/Sources/NWSharedModels/Auth/AuthMocks.swift @@ -0,0 +1,16 @@ +extension VerifySMSInOutput { + static public var draff: VerifySMSInOutput = .init( + phoneNumber: "+79218821217", + attemptId: "165541EC-692E-440A-9CF8-565776E9DC99", + code: "336699", + isLoggedIn: false + ) +} + +extension LoginResponseP { + static public var draff: LoginResponseP = .init(status: "online", user: .init(fullName: "Saroar", email: "", role: .superAdmin, language: .english), access: .draff) +} + +extension RefreshTokenResponse { + static public var draff: RefreshTokenResponse = .init(accessToken: "", refreshToken: "") +} diff --git a/Sources/NWSharedModels/Auth/NewUserInOutPut.swift b/Sources/NWSharedModels/Auth/NewUserInOutPut.swift new file mode 100644 index 0000000..0839de0 --- /dev/null +++ b/Sources/NWSharedModels/Auth/NewUserInOutPut.swift @@ -0,0 +1,28 @@ +// +// File.swift +// +// +// Created by Saroar Khandoker on 18.07.2022. +// + +#if os(macOS) || os(Linux) +import Vapor + +extension NewUserInOutPut: Content {} + +#endif + +import BSON + +public struct NewUserInOutPut: Codable { + public init(id: ObjectId?, firstName: String?, lastName: String?, phoneNumber: String) { + self.id = id + self.firstName = firstName + self.lastName = lastName + self.phoneNumber = phoneNumber + } + + public let id: ObjectId? + public let firstName, lastName: String? + public let phoneNumber: String +} diff --git a/Sources/NWSharedModels/Auth/RefreshToken.swift b/Sources/NWSharedModels/Auth/RefreshToken.swift new file mode 100644 index 0000000..7f74fb1 --- /dev/null +++ b/Sources/NWSharedModels/Auth/RefreshToken.swift @@ -0,0 +1,31 @@ +// +// RefreshToken.swift +// +// +// Created by Alif on 7/6/20. +// + +#if os(macOS) || os(Linux) +import Vapor +import JWT +import BSON + +//public struct RefreshToken: JWTPayload { +// public var id: ObjectId? +// public var iat: Int +// public var exp: Int +// +// public init(user: User, expiration: Int = 31536000) { // Expiration 1 year +// let now = Date().timeIntervalSince1970 +// self.id = user.id +// self.iat = Int(now) +// self.exp = Int(now) + expiration +// } +// +// public func verify(using signer: JWTSigner) throws { +// let expiration = Date(timeIntervalSince1970: TimeInterval(self.exp)) +// try ExpirationClaim(value: expiration).verifyNotExpired() +// } +//} + +#endif diff --git a/Sources/NWSharedModels/Auth/RefreshTokenInput.swift b/Sources/NWSharedModels/Auth/RefreshTokenInput.swift new file mode 100644 index 0000000..ab3d0df --- /dev/null +++ b/Sources/NWSharedModels/Auth/RefreshTokenInput.swift @@ -0,0 +1,19 @@ +// +// RefreshTokenInput +// +// +// Created by Alif on 7/6/20. +// + +#if os(macOS) || os(Linux) +import Vapor + +extension RefreshTokenInput: Content {} + +#endif + +public struct RefreshTokenInput: Codable { + public var refreshToken: String +} + +extension RefreshTokenInput: Equatable {} diff --git a/Sources/NWSharedModels/Auth/SMSVerificationAttempt.swift b/Sources/NWSharedModels/Auth/SMSVerificationAttempt.swift new file mode 100644 index 0000000..65dfdcd --- /dev/null +++ b/Sources/NWSharedModels/Auth/SMSVerificationAttempt.swift @@ -0,0 +1,30 @@ +// +// SMSVerification.swift +// +// +// Created by Alif on 7/6/20. +// + +#if os(macOS) || os(Linux) +import Vapor +import Fluent +import BSON + +public final class SMSVerificationAttempt: Model, Content { + static public let schema = "sms_verification_attempts" + + @ID(custom: "id") public var id: ObjectId? + @Field(key: "expiresAt") public var expiresAt: Date? + @Field(key: "phoneNumber") public var phoneNumber: String + @Field(key: "code") public var code: String + + public init() { } + + public init(code: String, expiresAt: Date?, phoneNumber: String) { + self.code = code + self.expiresAt = expiresAt + self.phoneNumber = phoneNumber + } +} + +#endif diff --git a/Sources/NWSharedModels/Auth/UserResponse.swift b/Sources/NWSharedModels/Auth/UserResponse.swift new file mode 100644 index 0000000..f512dc2 --- /dev/null +++ b/Sources/NWSharedModels/Auth/UserResponse.swift @@ -0,0 +1,57 @@ +// +// UserResponse.swift +// +// +// Created by Alif on 7/6/20. +// + +#if os(macOS) || os(Linux) +import Vapor + +extension RefreshTokenResponse: Content {} +extension LoginResponseP: Content {} +extension UserSuccessResponse: Content {} + +#endif + +public struct UserSuccessResponse: Codable { + let user: UserGetObject + + public init(user: UserGetObject) { + self.user = user + } +} + +public struct RefreshTokenResponse: Codable { + public var accessToken: String + public var refreshToken: String + + public init(accessToken: String, refreshToken: String) { + self.accessToken = accessToken + self.refreshToken = refreshToken + } +} + + +// MARK: - Login Response for mobile auth + +public struct LoginResponseP: Codable, Equatable { + public let status: String + public let user: UserGetObject? + public let access: RefreshTokenResponse? + + public init( + status: String, + user: UserGetObject? = nil, + access: RefreshTokenResponse? = nil + ) { + self.status = status + self.user = user + self.access = access + } + + public static func == (lhs: Self, rhs: Self) -> Bool { + lhs.user == rhs.user + && lhs.access?.accessToken == rhs.access?.accessToken + } +} diff --git a/Sources/NWSharedModels/Auth/VerifySMSInOutPut.swift b/Sources/NWSharedModels/Auth/VerifySMSInOutPut.swift new file mode 100644 index 0000000..5e970ad --- /dev/null +++ b/Sources/NWSharedModels/Auth/VerifySMSInOutPut.swift @@ -0,0 +1,68 @@ +// +// VerifySMSInput.swift +// +// +// Created by Alif on 7/6/20. +// + +#if os(macOS) || os(Linux) +import Vapor + +extension VerifySMSInOutput: Content {} +extension LoginInput: Content {} +extension SendUserVerificationResponse: Content {} +extension UserVerificationPayload: Content {} +#endif + +import BSON + +public struct VerifySMSInOutput: Codable, Equatable { + public var phoneNumber: String + public var attemptId: String? + public var code: String? + public var isLoggedIn: Bool? = false + + public init( + phoneNumber: String, + attemptId: String? = nil, + code: String? = nil, + isLoggedIn: Bool? = false + ) { + self.phoneNumber = phoneNumber + self.attemptId = attemptId + self.code = code + self.isLoggedIn = isLoggedIn + } + +} + +// this belove code have to remove we have already alternative struct this +public struct LoginInput: Codable { + public init(phoneNumber: String) { + self.phoneNumber = phoneNumber + } + + public let phoneNumber: String +} + +public struct SendUserVerificationResponse: Codable { + public init(phoneNumber: String, attemptId: ObjectId) { + self.phoneNumber = phoneNumber + self.attemptId = attemptId + } + + public let phoneNumber: String + public let attemptId: ObjectId +} + +public struct UserVerificationPayload: Codable { + public init(attemptId: ObjectId, phoneNumber: String, code: String) { + self.attemptId = attemptId + self.phoneNumber = phoneNumber + self.code = code + } + + public let attemptId: ObjectId + public let phoneNumber: String + public let code: String +} diff --git a/Sources/NWSharedModels/Authentication/AccessToken/AccessTokenRequest.swift b/Sources/NWSharedModels/Authentication/AccessToken/AccessTokenRequest.swift new file mode 100644 index 0000000..0f16be7 --- /dev/null +++ b/Sources/NWSharedModels/Authentication/AccessToken/AccessTokenRequest.swift @@ -0,0 +1,8 @@ + +#if os(macOS) || os(Linux) +import Vapor + +public struct AccessTokenRequest: Content { + public let refreshToken: String +} +#endif diff --git a/Sources/NWSharedModels/Authentication/AccessToken/AccessTokenResponse.swift b/Sources/NWSharedModels/Authentication/AccessToken/AccessTokenResponse.swift new file mode 100644 index 0000000..fd2356f --- /dev/null +++ b/Sources/NWSharedModels/Authentication/AccessToken/AccessTokenResponse.swift @@ -0,0 +1,14 @@ + +#if os(macOS) || os(Linux) +import Vapor + +public struct AccessTokenResponse: Content { + public init(refreshToken: String, accessToken: String) { + self.refreshToken = refreshToken + self.accessToken = accessToken + } + + public let refreshToken: String + public let accessToken: String +} +#endif diff --git a/Sources/NWSharedModels/Authentication/EmailVerification/SendEmailVerificationRequest.swift b/Sources/NWSharedModels/Authentication/EmailVerification/SendEmailVerificationRequest.swift new file mode 100644 index 0000000..e1c89ba --- /dev/null +++ b/Sources/NWSharedModels/Authentication/EmailVerification/SendEmailVerificationRequest.swift @@ -0,0 +1,8 @@ + +#if os(macOS) || os(Linux) +import Vapor + +public struct SendEmailVerificationRequest: Content { + public let email: String +} +#endif diff --git a/Sources/NWSharedModels/Authentication/Login/LoginRequest.swift b/Sources/NWSharedModels/Authentication/Login/LoginRequest.swift new file mode 100644 index 0000000..2795c5e --- /dev/null +++ b/Sources/NWSharedModels/Authentication/Login/LoginRequest.swift @@ -0,0 +1,15 @@ +#if os(macOS) || os(Linux) +import Vapor + +public struct LoginRequest: Content { + public let email: String + public let password: String +} + +extension LoginRequest: Validatable { + public static func validations(_ validations: inout Validations) { + validations.add("email", as: String.self, is: .email) + validations.add("passwordHash", as: String.self, is: !.empty) + } +} +#endif diff --git a/Sources/NWSharedModels/Authentication/Login/LoginResponse.swift b/Sources/NWSharedModels/Authentication/Login/LoginResponse.swift new file mode 100644 index 0000000..7ece8f2 --- /dev/null +++ b/Sources/NWSharedModels/Authentication/Login/LoginResponse.swift @@ -0,0 +1,15 @@ +#if os(macOS) || os(Linux) +import Vapor + +public struct LoginResponse: Content { + public init(user: UserGetObject, accessToken: String, refreshToken: String) { + self.user = user + self.accessToken = accessToken + self.refreshToken = refreshToken + } + + public let user: UserGetObject + public let accessToken: String + public let refreshToken: String +} +#endif diff --git a/Sources/NWSharedModels/Authentication/Register/RegisterRequest.swift b/Sources/NWSharedModels/Authentication/Register/RegisterRequest.swift new file mode 100644 index 0000000..c0d83fe --- /dev/null +++ b/Sources/NWSharedModels/Authentication/Register/RegisterRequest.swift @@ -0,0 +1,34 @@ +#if os(macOS) || os(Linux) +import Vapor + +public struct RegisterRequest: Content { + public let fullName: String + public let email: String + public let role: UserRole + public let language: UserLanguage + public let passwordHash: String + public let confirmPassword: String +} + +extension RegisterRequest: Validatable { + public static func validations(_ validations: inout Validations) { + validations.add("fullName", as: String.self, is: .count(3...)) + validations.add("email", as: String.self, is: .email) + validations.add("passwordHash", as: String.self, is: .count(8...)) + validations.add("role", as: String.self, is: .case(of: UserRole.self )) + validations.add("language", as: String.self, is: .case(of: UserLanguage.self)) + } +} + +extension UserModel { + public convenience init(from register: RegisterRequest, hash: String) throws { + self.init( + fullName: register.fullName, + language: register.language, + role: register.role, + email: register.email, + passwordHash: hash + ) + } +} +#endif diff --git a/Sources/NWSharedModels/Authentication/ResetPassword/RecoverAccountRequest.swift b/Sources/NWSharedModels/Authentication/ResetPassword/RecoverAccountRequest.swift new file mode 100644 index 0000000..9fc91de --- /dev/null +++ b/Sources/NWSharedModels/Authentication/ResetPassword/RecoverAccountRequest.swift @@ -0,0 +1,17 @@ +#if os(macOS) || os(Linux) +import Vapor + +public struct RecoverAccountRequest: Content { + public let password: String + public let confirmPassword: String + public let token: String +} + +extension RecoverAccountRequest: Validatable { + public static func validations(_ validations: inout Validations) { + validations.add("password", as: String.self, is: .count(8...)) + validations.add("confirmPassword", as: String.self, is: !.empty) + validations.add("token", as: String.self, is: !.empty) + } +} +#endif diff --git a/Sources/NWSharedModels/Authentication/ResetPassword/ResetPasswordRequest.swift b/Sources/NWSharedModels/Authentication/ResetPassword/ResetPasswordRequest.swift new file mode 100644 index 0000000..46aea3a --- /dev/null +++ b/Sources/NWSharedModels/Authentication/ResetPassword/ResetPasswordRequest.swift @@ -0,0 +1,13 @@ +#if os(macOS) || os(Linux) +import Vapor + +public struct ResetPasswordRequest: Content { + public let email: String +} + +extension ResetPasswordRequest: Validatable { + public static func validations(_ validations: inout Validations) { + validations.add("email", as: String.self, is: .email) + } +} +#endif diff --git a/Sources/NWSharedModels/Backend/Constants.swift b/Sources/NWSharedModels/Backend/Constants.swift new file mode 100644 index 0000000..015b720 --- /dev/null +++ b/Sources/NWSharedModels/Backend/Constants.swift @@ -0,0 +1,17 @@ +// +// Constants.swift +// +// +// Created by Saroar Khandoker on 20.01.2022. +// + +public struct Constants { + /// How long should access tokens live for. Default: 1 year (in seconds) + public static let ACCESS_TOKEN_LIFETIME: Double = 60 * 60 * 24 * 365 + /// How long should refresh tokens live for: Default: 7 days (in seconds) + public static let REFRESH_TOKEN_LIFETIME: Double = 60 * 60 * 24 * 7 + /// How long should the email tokens live for: Default 24 hours (in seconds) + public static let EMAIL_TOKEN_LIFETIME: Double = 60 * 60 * 24 + /// Lifetime of reset password tokens: Default 1 hour (seconds) + public static let RESET_PASSWORD_TOKEN_LIFETIME: Double = 60 * 60 +} diff --git a/Sources/NWSharedModels/Backend/EmailToken.swift b/Sources/NWSharedModels/Backend/EmailToken.swift new file mode 100644 index 0000000..71dbe3f --- /dev/null +++ b/Sources/NWSharedModels/Backend/EmailToken.swift @@ -0,0 +1,35 @@ +// +// EmailToken.swift +// +// +// Created by Saroar Khandoker on 20.01.2022. +// + +#if os(macOS) || os(Linux) +import Vapor +import Fluent +import MongoKitten + +public final class EmailToken: Model { + public static let schema = "userEmailTokens" + + @ID(custom: "id") public var id: ObjectId? + @Parent(key: "userId") public var user: UserModel + @Field(key: "token") public var token: String + @Field(key: "expiresAt") public var expiresAt: Date + + public init() {} + + public init( + id: ObjectId? = nil, + userID: ObjectId, + token: String, + expiresAt: Date = Date().addingTimeInterval(Constants.EMAIL_TOKEN_LIFETIME) + ) { + self.id = id + self.$user.id = userID + self.token = token + self.expiresAt = expiresAt + } +} +#endif diff --git a/Sources/NWSharedModels/Backend/PasswordToken.swift b/Sources/NWSharedModels/Backend/PasswordToken.swift new file mode 100644 index 0000000..e390d53 --- /dev/null +++ b/Sources/NWSharedModels/Backend/PasswordToken.swift @@ -0,0 +1,32 @@ +// +// PasswordToken.swift +// +// +// Created by Saroar Khandoker on 20.01.2022. +// +#if os(macOS) || os(Linux) +import Vapor +import Fluent +import MongoKitten + +public final class PasswordToken: Model { + public static var schema: String = "userPasswordTokens" + + @ID(custom: "id") public var id: ObjectId? + @Parent(key: "userId") public var user: UserModel + + @Field(key: "token") public var token: String + + @Field(key: "expiresAt") public var expiresAt: Date + + public init() {} + + public init(id: ObjectId? = nil, userID: ObjectId, token: String, expiresAt: Date = Date().addingTimeInterval(Constants.RESET_PASSWORD_TOKEN_LIFETIME)) { + self.id = id + self.$user.id = userID + self.token = token + self.expiresAt = expiresAt + } + +} +#endif diff --git a/Sources/NWSharedModels/Backend/PropertyNames.swift b/Sources/NWSharedModels/Backend/PropertyNames.swift new file mode 100644 index 0000000..bfbbb18 --- /dev/null +++ b/Sources/NWSharedModels/Backend/PropertyNames.swift @@ -0,0 +1,9 @@ +public protocol PropertyNames { + func propertyNames() -> [String] +} + +extension PropertyNames { + public func propertyNames() -> [String] { + return Mirror(reflecting: self).children.compactMap { $0.label } + } +} diff --git a/Sources/NWSharedModels/Backend/RefreshTokenModel.swift b/Sources/NWSharedModels/Backend/RefreshTokenModel.swift new file mode 100644 index 0000000..c3d0fdf --- /dev/null +++ b/Sources/NWSharedModels/Backend/RefreshTokenModel.swift @@ -0,0 +1,35 @@ +// +// RefreshToken.swift +// +// +// Created by Saroar Khandoker on 20.01.2022. +// + +#if os(macOS) || os(Linux) +import Vapor +import Fluent +import MongoKitten + +public final class RefreshToken: Model { + public static let schema = "userRefreshTokens" + + @ID(custom: "id") public var id: ObjectId? + @Field(key: "token") public var token: String + @Parent(key: "userId") public var user: UserModel + + @Field(key: "expiresAt") public var expiresAt: Date + @Field(key: "issuedAt") public var issuedAt: Date + + public init() {} + + public init(id: ObjectId? = nil, token: String, + userID: ObjectId, expiresAt: Date = Date().addingTimeInterval(Constants.REFRESH_TOKEN_LIFETIME), issuedAt: Date = Date()) { + self.id = id + self.token = token + self.$user.id = userID + self.expiresAt = expiresAt + self.issuedAt = issuedAt + } +} +#endif + diff --git a/Sources/NWSharedModels/JSONDecoder+iso.swift b/Sources/NWSharedModels/JSONDecoder+iso.swift new file mode 100644 index 0000000..068b98a --- /dev/null +++ b/Sources/NWSharedModels/JSONDecoder+iso.swift @@ -0,0 +1,9 @@ +//import Foundation +// +//extension JSONDecoder { +// public static let iso8601: JSONDecoder = { +// let decoder = JSONDecoder() +// decoder.dateDecodingStrategy = .iso8601 +// return decoder +// }() +//} diff --git a/Sources/NWSharedModels/JWT/Playload.swift b/Sources/NWSharedModels/JWT/Playload.swift new file mode 100644 index 0000000..2260482 --- /dev/null +++ b/Sources/NWSharedModels/JWT/Playload.swift @@ -0,0 +1,48 @@ +// +// Payload.swift +// +// +// Created by Saroar Khandoker on 20.01.2022. +// + +//#if os(macOS) || os(Linux) +//import Vapor +//import JWT +//import JWTKit +//import MongoKitten +// +//public struct Payload: JWTPayload, Authenticatable { +// // User-releated stuff +// public var user: UserModel +// // JWT stuff +// public var exp: ExpirationClaim +// +// public func verify(using signer: JWTSigner) throws { +// try self.exp.verifyNotExpired() +// } +// +// public init(with user: UserModel) throws { +// self.user = user +// self.exp = ExpirationClaim(value: Date().addingTimeInterval(Constants.ACCESS_TOKEN_LIFETIME)) +// } +//} +// +//extension UserModel { +// public convenience init(from payload: Payload) { +// self.init( +// id: payload.user.id, +// fullName: payload.user.fullName, +// language: payload.user.language, +// role: payload.user.role, +// email: payload.user.email, +// passwordHash: "__" +// ) +// } +//} +// +////extension Application { +//// var jwt: JWTSigner { +//// return JWTSigner.hs256(key: Array("mysupersecretsecurityKey".utf8)) +//// } +////} +//#endif diff --git a/Sources/NWSharedModels/LanguageCode.swift b/Sources/NWSharedModels/LanguageCode.swift new file mode 100644 index 0000000..0686985 --- /dev/null +++ b/Sources/NWSharedModels/LanguageCode.swift @@ -0,0 +1,42 @@ +// +// LanguageCode.swift +// +// +// Created by Saroar Khandoker on 20.12.2021. +// + +import Foundation + +public struct LanguageCode: Codable, Hashable, Equatable { + public var name: String + public var nativeName: String + public var code: String + + public var description: String { + return "\(countryFlag(countryCode: code.uppercased())) \(nativeName.capitalized)" + } + + func countryFlag(countryCode: String) -> String { + let base = 127397 + var tempScalarView = String.UnicodeScalarView() + for i in countryCode.utf16 { + if let scalar = UnicodeScalar(base + Int(i)) { + tempScalarView.append(scalar) + } + } + return String(tempScalarView) + } +} + +extension LanguageCode { + public static let bangla: LanguageCode = .init(name: "Bangla", nativeName: "বাংলা", code: "bd") + public static let english: LanguageCode = .init(name: "English", nativeName: "English", code: "us") + + public static let empty: LanguageCode = .init(name: "", nativeName: "", code: "") + + public static let list: [LanguageCode] = [ + .init(name: "Bangla", nativeName: "বাংলা", code: "bd"), + .init(name: "Russian", nativeName: "русский язык", code: "ru"), + .init(name: "English", nativeName: "English", code: "us") + ] +} diff --git a/Sources/NWSharedModels/NWError.swift b/Sources/NWSharedModels/NWError.swift new file mode 100644 index 0000000..128e340 --- /dev/null +++ b/Sources/NWSharedModels/NWError.swift @@ -0,0 +1,49 @@ +import Foundation + + +public struct NWError: Error, Equatable { + + public static func == (lhs: Self, rhs: Self) -> Bool { + return lhs.description == rhs.description + } + + public var description: String + public let reason: Error? + + public static var nonHTTPResponse: Self { + .init(description: "Non-HTTP response received", reason: nil) + } + + public static var missingTokenFromIOS: Self { + .init(description: "JWT token are missing on ios app", reason: nil) + } + + public static func requestFailed(_ statusCode: Int) -> Self { + return .init(description: "Request Failed HTTP with error - \(statusCode)", reason: nil) + } + + public static func serverError(_ statusCode: Int) -> Self { + return .init(description: "Server Error - \(statusCode)", reason: nil) + } + + public static func networkError(_ error: Error?) -> Self { + return .init(description: "Failed to load the request: \(String(describing: error))", reason: error) + } + + public static func authError(_ statusCode: Int) -> Self { + return .init(description: "Authentication Token is expired: \(statusCode)", reason: nil) + } + + public static func decodingError(_ decError: DecodingError) -> Self { + return .init(description: "Failed to process response: \(decError)", reason: decError) + } + + public static func unhandledResponse(_ statusCode: Int) -> Self { + return .init(description: "Unhandled HTTP Response Status code: \(statusCode)", reason: nil) + } + + public static func custom(_ status: String, _ error: Error?) -> Self { + return .init(description: "\(status)", reason: error) + } +} + diff --git a/Sources/NWSharedModels/SiteRoute.swift b/Sources/NWSharedModels/SiteRoute.swift new file mode 100644 index 0000000..bca2370 --- /dev/null +++ b/Sources/NWSharedModels/SiteRoute.swift @@ -0,0 +1,127 @@ + +import URLRouting + +public enum SiteRoute: Equatable { + case authEngine(AuthEngineRoute) + case word(WordsRoute) + case terms + case privacy +} + +public enum WordsRoute: Equatable { + case list(query: Language) +} + +public let wordsRouter = OneOf { + Parse(.memberwise(Language.init)) { + Query { + Field("from") + Field("to") + } + } +} + +public let siteRouter = OneOf { + + Route(.case(SiteRoute.authEngine)) { + Path { "v1" } + authEngineRoute + } + + Route(.case(SiteRoute.terms)) { + Path { "terms" } + } + + Route(.case(SiteRoute.privacy)) { + Path { "privacy" } + } +} + +import URLRouting + +public enum AuthEngineRoute: Equatable { + case authentication(AuthenticationRoute) + case users(UsersRoute) +} + +public let authEngineRoute = OneOf { + Route(.case(AuthEngineRoute.authentication)) { + Path { "auth" } + authenticationRouter + } + + Route(.case(AuthEngineRoute.users)) { + Path { "users" } + usersRouter + } + +} + +public enum AuthenticationRoute: Equatable { + case login(input: VerifySMSInOutput) + case verifySms(input: VerifySMSInOutput) + case refreshToken(input: RefreshTokenInput) +} + +public let authenticationRouter = OneOf { + Route(.case(AuthenticationRoute.login)) { + Path { "login" } + Method.post + Body(.json(VerifySMSInOutput.self)) + } + + Route(.case(AuthenticationRoute.verifySms)) { + Path { "verify_sms" } + Method.post + Body(.json(VerifySMSInOutput.self)) + } + + Route(.case(AuthenticationRoute.refreshToken)) { + Path { "refresh_token" } + Method.post + Body(.json(RefreshTokenInput.self)) + } +} + +public enum UsersRoute: Equatable { + case user(id: String, route: UserRoute) + case update(input: UserGetObject) +} + +public let usersRouter = OneOf { + Route(.case(UsersRoute.user)) { + Path { Parse(.string) } + userRouter + } + + Route(.case(UsersRoute.update)) { + Method.put + Body(.json(UserGetObject.self)) + } +} + +public enum UserRoute: Equatable { + case find + case deleteSoft + case restore + case delete +} + +public let userRouter = OneOf { + Route(.case(UserRoute.find)) + + Route(.case(UserRoute.deleteSoft)) { + Method.delete + Path { "soft" } + } + + Route(.case(UserRoute.restore)) { + Method.put + Path { "restore" } + } + + Route(.case(UserRoute.delete)) { + Method.delete + } + +} diff --git a/Sources/NWSharedModels/SwiftUI+Extension.swift b/Sources/NWSharedModels/SwiftUI+Extension.swift new file mode 100644 index 0000000..0cc868a --- /dev/null +++ b/Sources/NWSharedModels/SwiftUI+Extension.swift @@ -0,0 +1,19 @@ +// +// SwiftUI+Extension.swift +// +// +// Created by Saroar Khandoker on 08.12.2021. +// + +import SwiftUI + +//extension View { +// @ViewBuilder +// public func stackNavigationViewStyle() -> some View { +// if #available(iOS 15.0, *) { +// self.navigationViewStyle(.stack) +// } else { +// self.navigationViewStyle(StackNavigationViewStyle()) +// } +// } +//} diff --git a/Sources/NWSharedModels/URLResponse+extension.swift b/Sources/NWSharedModels/URLResponse+extension.swift new file mode 100644 index 0000000..90d48b8 --- /dev/null +++ b/Sources/NWSharedModels/URLResponse+extension.swift @@ -0,0 +1,10 @@ +import Foundation + +extension URLResponse { + public func isResponseOK() -> Bool { + if let httpResponse = self as? HTTPURLResponse { + return (200...299).contains(httpResponse.statusCode) + } + return false + } +} diff --git a/Sources/NWSharedModels/User/File.swift b/Sources/NWSharedModels/User/File.swift new file mode 100644 index 0000000..2d9a49b --- /dev/null +++ b/Sources/NWSharedModels/User/File.swift @@ -0,0 +1,74 @@ + +import BSON + +public enum UserRole: String, Codable, CaseIterable { + case basic, superAdmin, englishAdmin, russianAdmin, banglaAdmin +} + +public enum UserLanguage: String, Codable, CaseIterable { + case russian, english, bangla +} + +public struct UserRoleAndLanguage: Encodable { + public init() {} + + public let roles = UserRole.allCases + public let languages = UserLanguage.allCases +} + +public struct UserCreateObject: Codable { + public var fullName: String + public var language: UserLanguage + public var role: UserRole = .basic + + public var email: String + public var passwordHash: String +} + +public struct UserGetObject: Codable { + + public let id: ObjectId? + public let fullName: String + public let email: String + public let role: UserRole + public let language: UserLanguage + + public init( + id: ObjectId? = nil, fullName: String, + email: String, role: UserRole, + language: UserLanguage + ) { + self.id = id + self.fullName = fullName + self.email = email + self.role = role + self.language = language + } +} + +extension UserGetObject: Equatable {} + +public struct UserGetPublicObject: Codable { + + public let id: ObjectId? + public let fullName: String + public let role: UserRole + public let language: UserLanguage + + public init( + id: ObjectId? = nil, fullName: String, + role: UserRole, + language: UserLanguage + ) { + self.id = id + self.fullName = fullName + self.role = role + self.language = language + } +} + +extension UserGetPublicObject: Equatable {} + +extension UserGetPublicObject { + public static let demo: Self = .init(fullName: "", role: .banglaAdmin, language: .english) +} diff --git a/Sources/NWSharedModels/User/User.swift b/Sources/NWSharedModels/User/User.swift new file mode 100644 index 0000000..06825c7 --- /dev/null +++ b/Sources/NWSharedModels/User/User.swift @@ -0,0 +1,132 @@ +// +// User.swift +// +// +// Created by Saroar Khandoker on 04.01.2022. +// + +#if os(macOS) || os(Linux) +import Vapor +import Fluent +import FluentMongoDriver + +public final class UserModel: Model { + + public init() {} + + public static let schema = "users" + + @ID(custom: "id") public var id: ObjectId? + @Field(key: "fullName") public var fullName: String + @Field(key: "language") public var language: UserLanguage + @Enum(key: "role") public var role: UserRole + @Field(key: "isEmailVerified") public var isEmailVerified: Bool + + @Field(key: "email") public var email: String + @Field(key: "passwordHash") public var passwordHash: String + + @Children(for: \.$user) public var words: [WordModel] + + @Timestamp(key: "createdAt", on: .create) public var createdAt: Date? + @Timestamp(key: "updatedAt", on: .update) public var updatedAt: Date? + + public init( + id: ObjectId? = nil, + fullName: String, + language: UserLanguage, role: UserRole = .basic, + isEmailVerified: Bool = false, + email: String, passwordHash: String + ) { + self.fullName = fullName + self.language = language + self.role = role + self.isEmailVerified = isEmailVerified + self.email = email + self.passwordHash = passwordHash + } + + public final class Public: Content { + public init( + id: ObjectId? = nil, fullName: String, + language: UserLanguage, role: UserRole + ) { + self.id = id + self.fullName = fullName + self.language = language + self.role = role + } + + public var id: ObjectId? + public var fullName: String + public var language: UserLanguage + public var role: UserRole + } + +} + +extension UserModel { + public func create(_ input: UserCreateObject) { + fullName = input.fullName + language = input.language + role = input.role + email = input.email + passwordHash = input.passwordHash + } +} + + +extension UserModel { + public func mapGet() -> UserGetObject { + return .init(id: id, fullName: fullName, email: email, role: role, language: language) + } + + public func mapGetPublic() -> UserGetPublicObject { + return .init(id: id, fullName: fullName, role: role, language: language) + } +} + +extension UserCreateObject: Content {} + +extension UserModel: Authenticatable {} + +extension UserModel { + public func convertToPublic() -> UserModel.Public { + return UserModel.Public.init(id: id, fullName: fullName, language: language, role: role) + } +} + +extension EventLoopFuture where Value: UserModel { + public func convertToPublic() -> EventLoopFuture { + return self.map { user in + return user.convertToPublic() + } + } +} + +extension Collection where Element: UserModel { + public func convertToPublic() -> [UserModel.Public] { + return self.map { $0.convertToPublic() } + } +} + +extension EventLoopFuture where Value == Array { + public func convertToPublic() -> EventLoopFuture<[UserModel.Public]> { + return self.map { $0.convertToPublic() } + } +} + +extension UserModel { + public func requireID() throws -> IDValue { + guard let id = self.id else { + throw FluentError.idRequired + } + return id + } +} + +extension UserGetObject: Content { + public init(from user: UserModel) { + self.init(id: user.id, fullName: user.fullName, email: user.email, role: user.role, language: user.language) + } +} +#endif diff --git a/Sources/NWSharedModels/Word/Word.swift b/Sources/NWSharedModels/Word/Word.swift new file mode 100644 index 0000000..fdade7f --- /dev/null +++ b/Sources/NWSharedModels/Word/Word.swift @@ -0,0 +1,148 @@ +// +// Word.swift +// +// +// Created by Saroar Khandoker on 29.11.2021. +// + +#if os(macOS) || os(Linux) +import Vapor +import Fluent +import FluentMongoDriver + +public final class WordModel: Model, Content, PropertyNames { + + public static var schema = "words" + + public init() {} + + public init( + id: ObjectId? = nil, + icon: String? = nil, + englishWord: String, englishDefinition: String, + englishImageLink: String? = nil, englishVideoLink: String? = nil, + russianWord: String? = nil, russianDefinition: String? = nil, + russianImageLink: String? = nil, russianVideoLink: String? = nil, + banglaWord: String? = nil, banglaDefinition: String? = nil, + banglaImageLink: String? = nil, banglaVideoLink: String? = nil, + isReadFromNotification: Bool = false, isReadFromView: Bool = false, + level: WordLevel = .beginner, userId: ObjectId + ) { + self.id = id + self.icon = icon + self.englishWord = englishWord + self.englishDefinition = englishDefinition + self.englishImageLink = englishImageLink + self.englishVideoLink = englishVideoLink + + self.russianWord = russianWord + self.russianDefinition = russianDefinition + self.russianImageLink = russianImageLink + self.russianVideoLink = russianVideoLink + + self.banglaWord = banglaWord + self.banglaDefinition = banglaDefinition + self.banglaImageLink = banglaImageLink + self.banglaVideoLink = banglaVideoLink + + self.isReadFromNotification = isReadFromNotification + self.isReadFromView = isReadFromView + self.level = level + + $user.id = userId + } + + @ID(custom: "id") public var id: ObjectId? + + @OptionalField(key: "icon") public var icon: String? + @Field(key: "englishWord") public var englishWord: String + @Field(key: "englishDefinition") public var englishDefinition: String + @OptionalField(key: "englishImageLink") public var englishImageLink: String? + @OptionalField(key: "englishVideoLink") public var englishVideoLink: String? + + @OptionalField(key: "russianWord") public var russianWord: String? + @OptionalField(key: "russianDefinition") public var russianDefinition: String? + @OptionalField(key: "russianImageLink") public var russianImageLink: String? + @OptionalField(key: "russianVideoLink") public var russianVideoLink: String? + + @OptionalField(key: "banglaWord") public var banglaWord: String? + @OptionalField(key: "banglaDefinition") public var banglaDefinition: String? + @OptionalField(key: "banglaImageLink") public var banglaImageLink: String? + @OptionalField(key: "banglaVideoLink") public var banglaVideoLink: String? + + @Field(key: "isReadFromNotification") public var isReadFromNotification: Bool + @Field(key: "isReadFromView") public var isReadFromView: Bool + @Field(key: "level") public var level: WordLevel + @Parent(key: "userId") public var user: UserModel + + @Timestamp(key: "createdAt", on: .create) public var createdAt: Date? + @Timestamp(key: "updatedAt", on: .update) public var updatedAt: Date? + +} + +extension WordModel { + + public func mapGet() -> WordGetObject { + .init( + _id: id!, icon: icon, englishWord: englishWord, englishDefinition: englishDefinition, + englishImageLink: englishImageLink, englishVideoLink: englishVideoLink, + russianWord: russianWord, russianDefinition: russianDefinition, + russianImageLink: russianImageLink, russianVideoLink: russianVideoLink, + banglaWord: banglaWord, banglaDefinition: banglaDefinition, + banglaImageLink: banglaImageLink, banglaVideoLink: banglaVideoLink, + isReadFromNotification: isReadFromNotification, + isReadFromView: isReadFromView, user: user.mapGetPublic(), level: level, + createdAt: createdAt, updatedAt: updatedAt + ) + } + + public func create(_ input: WordCreateObject) { + icon = input.icon + englishWord = input.englishWord + englishDefinition = input.englishDefinition + englishImageLink = input.englishImageLink + englishVideoLink = input.englishVideoLink + + russianWord = input.russianWord + russianDefinition = input.russianDefinition + russianImageLink = input.russianImageLink + russianVideoLink = input.russianVideoLink + + banglaWord = input.banglaWord + banglaDefinition = input.banglaDefinition + banglaImageLink = input.banglaImageLink + banglaVideoLink = input.banglaVideoLink + + isReadFromNotification = input.isReadFromNotification + isReadFromView = input.isReadFromView + level = input.level + } + + public func update(_ input: WordUpdateObject) async throws { + icon = input.icon + englishWord = input.englishWord + englishDefinition = input.englishDefinition + englishImageLink = input.englishImageLink + englishVideoLink = input.englishVideoLink + + russianWord = input.russianWord + russianDefinition = input.russianDefinition + russianImageLink = input.russianImageLink + russianVideoLink = input.russianVideoLink + + banglaWord = input.banglaWord + banglaDefinition = input.banglaDefinition + banglaImageLink = input.banglaImageLink + banglaVideoLink = input.banglaVideoLink + + isReadFromNotification = input.isReadFromNotification + isReadFromView = input.isReadFromView + level = input.level + + } +} +extension Language: Content {} +extension WordCreateObject: Content {} +extension WordGetObject: Content {} +extension WordGetObjectWithUser: Content {} +#endif diff --git a/Sources/NWSharedModels/Word/WordExtra.swift b/Sources/NWSharedModels/Word/WordExtra.swift new file mode 100644 index 0000000..0fc2135 --- /dev/null +++ b/Sources/NWSharedModels/Word/WordExtra.swift @@ -0,0 +1,376 @@ +import BSON +import Foundation + +public struct Language: Codable { + public init(from: String? = nil, to: String? = nil) { + self.from = from + self.to = to + } + + public var from: String? + public var to: String? +} + +extension Language: Equatable {} + +extension WordGetObject { + public var englishWordTitle: String { + return "\(icon ?? "") " + englishWord + } + + public var banglaWordTitle: String { + return "\(icon ?? "")\(banglaWord ?? "")" + } + + public var russianWordTitle: String { + return "\(icon ?? "")\(russianWord ?? "")" + } +} + +public enum WordLevel: String, Equatable, Codable, CaseIterable { + case beginner = "Beginner" + case intermediate = "Intermediate" + case advanced = "Advanced" +} + +public let allCases = WordLevel.allCases + +public struct WordGetObjectWithUser: Codable { + public let _id: ObjectId + public let icon: String? + public let englishWord: String? + public let englishDefinition: String? + public let englishImageLink: String? + public let englishVideoLink: String? + + public let russianWord: String? + public let russianDefinition: String? + public let russianImageLink: String? + public let russianVideoLink: String? + + public let banglaWord: String? + public let banglaDefinition: String? + public let banglaImageLink: String? + public let banglaVideoLink: String? + + public let isReadFromNotification: Bool + public let isReadFromView: Bool + + public let level: WordLevel + + public var createdAt: Date? + public var updatedAt: Date? +} + +public struct WordGetObject: Codable { + + public var _id: ObjectId + public var icon: String? + public let englishWord: String + public let englishDefinition: String + public let englishImageLink: String? + public let englishVideoLink: String? + + public let russianWord: String? + public let russianDefinition: String? + public let russianImageLink: String? + public let russianVideoLink: String? + + public let banglaWord: String? + public let banglaDefinition: String? + public let banglaImageLink: String? + public let banglaVideoLink: String? + + public let isReadFromNotification: Bool + public let isReadFromView: Bool + public let user: UserGetPublicObject + + public let level: WordLevel + + public var createdAt: Date? + public var updatedAt: Date? +} + +public struct WordCreateObject: Codable { + public var icon: String? + public var englishWord: String + public var englishDefinition: String + public var englishImageLink: String? = nil + public var englishVideoLink: String? = nil + + public var russianWord: String? = nil + public var russianDefinition: String? = nil + public var russianImageLink: String? = nil + public var russianVideoLink: String? = nil + + public var banglaWord: String? = nil + public var banglaDefinition: String? = nil + public var banglaImageLink: String? = nil + public var banglaVideoLink: String? = nil + + public var isReadFromNotification: Bool = false + public var isReadFromView: Bool = false + public var userId: ObjectId + + public var level: WordLevel = .beginner +} + +public struct WordUpdateObject: Codable { + public var icon: String? + public var englishWord: String + public var englishDefinition: String + public var englishImageLink: String? = nil + public var englishVideoLink: String? = nil + + public var russianWord: String? = nil + public var russianDefinition: String? = nil + public var russianImageLink: String? = nil + public var russianVideoLink: String? = nil + + public var banglaWord: String? = nil + public var banglaDefinition: String? = nil + public var banglaImageLink: String? = nil + public var banglaVideoLink: String? = nil + + public var isReadFromNotification: Bool = false + public var isReadFromView: Bool = false + + public var level: WordLevel = .beginner +} + +public struct WordEditObject: Codable { + public var title: String = "Word Edit" + public let word: WordGetObject + public var editing: Bool = true + public var wordLevelAllCases = WordLevel.allCases +} + +extension Word { + public var englishTitle: String { + if let iconR = icon { + return iconR + " " + englishWord + } + + return englishWord + } + + public var russianTitle: String? { + if let icon = icon, let russianWord = russianWord { + return icon + " " + russianWord + } + + return russianWord + } + + public var banglaTitle: String? { + if let icon = icon, let banglaWord = banglaWord { + return icon + " " + banglaWord + } + + return banglaWord + } + + public func buildNotificationTitle(from: String, to: String) -> String { + var result = "" + + if from == "english" || to == "english" { + result += englishTitle + } + + if from == "russian" || to == "russian" { + result += russianWord != nil ? " -> \(russianWord ?? "")" : "" + } + + if from == "bangla" || to == "bangla" { + result += banglaWord != nil ? " -> \(banglaWord ?? "")" : "" + } + + return result + } + + public func buildNotificationDefinition(from: String, to: String) -> String { + var result = "" + + if from == "english" || to == "english" { + result += englishDefinition + } + + if from == "russian" || to == "russian" { + result += russianDefinition != nil ? " -> \(russianDefinition ?? "")" : "" + } + + if from == "bangla" || to == "bangla" { + result += banglaDefinition != nil ? " -> \(banglaDefinition ?? "")" : "" + } + + return result + } +} + +public struct Word: Equatable, Identifiable, Codable { + + public var id: String + public let icon: String? + public let englishWord: String + public let englishDefinition: String + public let englishImageLink: String? + public let englishVideoLink: String? + + public var russianWord: String? + public var russianDefinition: String? + public var russianImageLink: String? + public var russianVideoLink: String? + + public var banglaWord: String? + public var banglaDefinition: String? + public var banglaImageLink: String? + public var banglaVideoLink: String? + + public var isReadFromNotification: Bool + public var isReadFromView: Bool + + public var level: WordLevel + public var user: UserGetPublicObject? + + public var createdAt: Date? + public var updatedAt: Date? + + enum CodingKeys: String, CodingKey { + case id = "_id" + case icon, englishWord, englishDefinition, englishImageLink, englishVideoLink + case russianWord, russianDefinition, russianImageLink, russianVideoLink + case banglaWord, banglaDefinition, banglaImageLink, banglaVideoLink + case isReadFromView, level, isReadFromNotification, user + case createdAt, updatedAt + } + + public init( + id: String, icon: String? = nil, englishWord: String, englishDefinition: String, englishImageLink: String? = nil, englishVideoLink: String? = nil, + russianWord: String? = nil, russianDefinition: String? = nil, russianImageLink: String? = nil, russianVideoLink: String? = nil, + banglaWord: String? = nil, banglaDefinition: String? = nil, banglaImageLink: String? = nil, banglaVideoLink: String? = nil, + isReadFromNotification: Bool = false, + isReadFromView: Bool = false, + level: WordLevel = .beginner, + user: UserGetPublicObject? = nil, + createdAt: Date? = nil, + updatedAt: Date? = nil + + ) { + self.id = id + self.icon = icon + self.englishWord = englishWord + self.englishDefinition = englishDefinition + self.englishImageLink = englishImageLink + self.englishVideoLink = englishVideoLink + self.russianWord = russianWord + self.russianDefinition = russianDefinition + self.russianImageLink = russianImageLink + self.russianVideoLink = russianVideoLink + self.banglaWord = banglaWord + self.banglaDefinition = banglaDefinition + self.banglaImageLink = banglaImageLink + self.banglaVideoLink = banglaVideoLink + self.isReadFromNotification = isReadFromNotification + self.isReadFromView = isReadFromView + self.level = level + self.user = user + self.createdAt = createdAt + self.updatedAt = updatedAt + } + + public init(_ word: Word) { + self.id = word.id + self.icon = word.icon + self.englishWord = word.englishWord + self.englishDefinition = word.englishDefinition + self.englishImageLink = word.englishImageLink + self.englishVideoLink = word.englishVideoLink + self.russianWord = word.russianWord + self.russianDefinition = word.russianDefinition + self.russianImageLink = word.russianImageLink + self.russianVideoLink = word.russianVideoLink + self.banglaWord = word.banglaWord + self.banglaDefinition = word.banglaDefinition + self.banglaImageLink = word.banglaImageLink + self.banglaVideoLink = word.banglaVideoLink + self.isReadFromNotification = word.isReadFromNotification + self.isReadFromView = word.isReadFromView + self.level = word.level + self.user = word.user + self.createdAt = word.createdAt + self.updatedAt = word.updatedAt + } +} + +extension Word: Hashable { + public static func == (lhs: Word, rhs: Word) -> Bool { + return lhs.id == rhs.id && lhs.id == rhs.id + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(id) + } +} + +extension Word { + public static let demo: Self = .init(id: "", englishWord: "", englishDefinition: "") +} + +public struct DayWords: Codable, Equatable, Identifiable { + public var id: String { return "\(dayNumber)" } + public var dayNumber: Int + public var words: [Word] = [] + + public init(dayNumber: Int, words: [Word] = []) { + self.dayNumber = dayNumber + self.words = words + } +} + +extension DayWords { + public static let happyPath: DayWords = .init(dayNumber: 117, words: Word.mockDatas) +} + +public struct User: Codable, Equatable { + public let fullName, language, id, role: String +} + +extension User { + public static var demo: User = .init(fullName: "Saroar", language: "ru", id: "624c31898addf0419b877915", role: "superAdmin") +} + +extension Word { + public static let mockEmpty: Word = .init(id: "", englishWord: "", englishDefinition: "") + public static let mockDatas: [Word] = [ + Word( + id: "D6168009-CEA2-45FC-874B-1426F7FB1005", icon: "🍏", englishWord: "Apple", englishDefinition: "AppleAppleAppleAppleAppleApple", englishImageLink: nil, englishVideoLink: nil, + + russianWord: "Яблока", russianDefinition: "ЯблокаЯблокаЯблокаЯблокаЯблокаЯблока", russianImageLink: nil, russianVideoLink: nil, + + banglaWord: "অ্যাপল", banglaDefinition: "অ্যাপলঅ্যাপলঅ্যাপলঅ্যাপল", banglaImageLink: nil, banglaVideoLink: nil, + + isReadFromNotification: false, isReadFromView: false, level: .beginner, user: nil, createdAt: nil, updatedAt: nil + ), + + Word( + id: "610800E5-A59C-44F5-ACC3-6809F39B42D2", icon: "🧰", englishWord: "Able", englishDefinition: "AbleAbleAbleAbleAbleAble", englishImageLink: nil, englishVideoLink: nil, + + russianWord: "Способный", russianDefinition: "СпособныйСпособныйСпособныйСпособныйСпособный", russianImageLink: nil, russianVideoLink: nil, + + banglaWord: "সক্ষম", banglaDefinition: "সক্ষমসক্ষমসক্ষমসক্ষমসক্ষমসক্ষম", banglaImageLink: nil, banglaVideoLink: nil, + + isReadFromNotification: false, isReadFromView: false, level: .beginner, user: nil, createdAt: nil, updatedAt: nil + ), + + Word( + id: "FC6F24EF-0DF7-4551-97AA-64E0340860D5", icon: "💨", englishWord: "Air", englishDefinition: "AirAirAirAirAirAir", englishImageLink: nil, englishVideoLink: nil, + + russianWord: "Воздух", russianDefinition: "ВоздухВоздухВоздухВоздух", russianImageLink: nil, russianVideoLink: nil, + + banglaWord: "এয়ার", banglaDefinition: "এয়ারএয়ারএয়ারএয়ারএয়ার", banglaImageLink: nil, banglaVideoLink: nil, + + isReadFromNotification: false, isReadFromView: false, level: .beginner, user: nil, createdAt: nil, updatedAt: nil + ) + ] +} + diff --git a/Sources/NWSharedModels/Word/WordReminder.swift b/Sources/NWSharedModels/Word/WordReminder.swift new file mode 100644 index 0000000..25256e9 --- /dev/null +++ b/Sources/NWSharedModels/Word/WordReminder.swift @@ -0,0 +1,38 @@ +// +// WordReminder.swift +// +// +// Created by Кхандокер Сароар on 26.05.2022. +// + +import Foundation + +public struct WordReminder: Equatable, Identifiable, Hashable, Codable { + + public static func == (lhs: WordReminder, rhs: WordReminder) -> Bool { + return lhs.hour == rhs.hour && lhs.date == rhs.date + } + + public var id = 0 + public var hour: Int = 0 + public var date: Date = Calendar.current + .date(bySettingHour: 8, minute: 00, second: 0, of: Date())! + + public init(id: Int = 0, hour: Int = 0, date: Date = Calendar.current + .date(bySettingHour: 8, minute: 00, second: 0, of: Date())!) { + self.id = id + self.hour = hour + self.date = date + } +} + +extension WordReminder { + static let list: [WordReminder] = (8..<14).enumerated().map { + .init( + id: $0 + 1, + hour: $1, + date: Calendar.current + .date(bySettingHour: $1, minute: 00, second: 0, of: Date())! + ) + } +}