From 2fb31ec034bbf3e16f9aadbfbe64b8a1092eac6c Mon Sep 17 00:00:00 2001 From: "genaro.arvizu" Date: Fri, 6 Oct 2023 07:59:19 -0500 Subject: [PATCH 01/15] FirebaseServiceAccountKey configuration added --- Sources/Ferno/FernoConfiguration.swift | 10 ++ Sources/Ferno/FirebaseServiceAccountKey.swift | 104 ++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 Sources/Ferno/FirebaseServiceAccountKey.swift diff --git a/Sources/Ferno/FernoConfiguration.swift b/Sources/Ferno/FernoConfiguration.swift index 79bbcb1..1889039 100644 --- a/Sources/Ferno/FernoConfiguration.swift +++ b/Sources/Ferno/FernoConfiguration.swift @@ -30,4 +30,14 @@ public struct FernoConfiguration { self.tokenExpriationDate = tokenExpriationDate self.logger = logger } + + public init(jsonConfiguration: FirebaseServiceAccountKey, + tokenExpriationDate: Date? = nil, + logger: Logger = .init(label: "codes.vapor.ferno")) { + self.basePath = jsonConfiguration.basePath + self.email = jsonConfiguration.clientEmail + self.privateKey = jsonConfiguration.privateKey + self.tokenExpriationDate = tokenExpriationDate + self.logger = logger + } } diff --git a/Sources/Ferno/FirebaseServiceAccountKey.swift b/Sources/Ferno/FirebaseServiceAccountKey.swift new file mode 100644 index 0000000..f1edd05 --- /dev/null +++ b/Sources/Ferno/FirebaseServiceAccountKey.swift @@ -0,0 +1,104 @@ +import Vapor + +public struct FirebaseServiceAccountKey: Content, Sendable { + public let type: String + public let projectId: String + public let clientEmail: String + public let clientId: String + public let basePath: String + internal let privateKeyId: String + internal let privateKey: String + internal let authUri: String + internal let tokenUri: String + internal let authProviderX509CertUrl: String + internal let clientX509CertUrl: String + internal let universeDomain: String + + enum CodingKeys: String, CodingKey { + case type + case projectId = "project_id" + case privateKeyId = "private_key_id" + case privateKey = "private_key" + case clientEmail = "client_email" + case clientId = "client_id" + case authUri = "auth_uri" + case tokenUri = "token_uri" + case authProviderX509CertUrl = "auth_provider_x509_cert_url" + case clientX509CertUrl = "client_x509_cert_url" + case universeDomain = "universe_domain" + } + + public init(type: String, + projectId: String, + privateKeyId: String, + privateKey: String, + clientEmail: String, + clientId: String, + authUri: String, + tokenUri: String, + authProviderX509CertUrl: String, + clientX509CertUrl: String, + universeDomain: String) { + self.type = type + self.projectId = projectId + self.privateKeyId = privateKeyId + self.privateKey = privateKey + self.clientEmail = clientEmail + self.clientId = clientId + self.authUri = authUri + self.tokenUri = tokenUri + self.authProviderX509CertUrl = authProviderX509CertUrl + self.clientX509CertUrl = clientX509CertUrl + self.universeDomain = universeDomain + self.basePath = "https://\(clientId).firebaseio.com" + } + + public init(json: Data) throws { + let configuration = try JSONDecoder().decode(FirebaseServiceAccountKey.self, from: json) + self.type = configuration.type + self.projectId = configuration.projectId + self.privateKeyId = configuration.privateKeyId + self.privateKey = configuration.privateKey + self.clientEmail = configuration.clientEmail + self.clientId = configuration.clientId + self.authUri = configuration.authUri + self.tokenUri = configuration.tokenUri + self.authProviderX509CertUrl = configuration.authProviderX509CertUrl + self.clientX509CertUrl = configuration.clientX509CertUrl + self.universeDomain = configuration.universeDomain + self.basePath = "https://\(clientId).firebaseio.com" + } + + public init(json: ByteBuffer) throws { + let configuration = try JSONDecoder().decode(FirebaseServiceAccountKey.self, from: json) + self.type = configuration.type + self.projectId = configuration.projectId + self.privateKeyId = configuration.privateKeyId + self.privateKey = configuration.privateKey + self.clientEmail = configuration.clientEmail + self.clientId = configuration.clientId + self.authUri = configuration.authUri + self.tokenUri = configuration.tokenUri + self.authProviderX509CertUrl = configuration.authProviderX509CertUrl + self.clientX509CertUrl = configuration.clientX509CertUrl + self.universeDomain = configuration.universeDomain + self.basePath = "https://\(clientId).firebaseio.com" + } + + public init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer = try decoder.container(keyedBy: FirebaseServiceAccountKey.CodingKeys.self) + + self.type = try container.decode(String.self, forKey: FirebaseServiceAccountKey.CodingKeys.type) + self.projectId = try container.decode(String.self, forKey: FirebaseServiceAccountKey.CodingKeys.projectId) + self.privateKeyId = try container.decode(String.self, forKey: FirebaseServiceAccountKey.CodingKeys.privateKeyId) + self.privateKey = try container.decode(String.self, forKey: FirebaseServiceAccountKey.CodingKeys.privateKey) + self.clientEmail = try container.decode(String.self, forKey: FirebaseServiceAccountKey.CodingKeys.clientEmail) + self.clientId = try container.decode(String.self, forKey: FirebaseServiceAccountKey.CodingKeys.clientId) + self.authUri = try container.decode(String.self, forKey: FirebaseServiceAccountKey.CodingKeys.authUri) + self.tokenUri = try container.decode(String.self, forKey: FirebaseServiceAccountKey.CodingKeys.tokenUri) + self.authProviderX509CertUrl = try container.decode(String.self, forKey: FirebaseServiceAccountKey.CodingKeys.authProviderX509CertUrl) + self.clientX509CertUrl = try container.decode(String.self, forKey: FirebaseServiceAccountKey.CodingKeys.clientX509CertUrl) + self.universeDomain = try container.decode(String.self, forKey: FirebaseServiceAccountKey.CodingKeys.universeDomain) + self.basePath = "https://\(clientId).firebaseio.com" + } +} From 21ccd889d67f284ac83f64638673e7880c8bc60e Mon Sep 17 00:00:00 2001 From: genaro_arvizu Date: Fri, 6 Oct 2023 16:27:21 -0500 Subject: [PATCH 02/15] Swiftlint warnings fixed --- Sources/Ferno/Application+Ferno.swift | 40 ++++++------- Sources/Ferno/FernoClient.swift | 39 +++++++----- Sources/Ferno/FernoDriver.swift | 2 +- Sources/Ferno/FernoPath.swift | 2 +- Sources/Ferno/FernoQuery.swift | 2 +- Sources/Ferno/FirebaseServiceAccountKey.swift | 2 +- Sources/Ferno/JWTPayload.swift | 9 ++- Tests/FernoTests/FernoTests.swift | 60 +++++++++++-------- 8 files changed, 86 insertions(+), 70 deletions(-) diff --git a/Sources/Ferno/Application+Ferno.swift b/Sources/Ferno/Application+Ferno.swift index 8f33ef5..d0ed67d 100644 --- a/Sources/Ferno/Application+Ferno.swift +++ b/Sources/Ferno/Application+Ferno.swift @@ -4,21 +4,21 @@ import JWT extension Application { /// The `Ferno` object public var ferno: Ferno { .init(application: self) } - + public struct Ferno { - + struct Key: StorageKey { typealias Value = Storage } - + /// The provider of the `Ferno` configuration public struct Provider { - let run: (Application) -> () + let run: (Application) -> Void - public init(_ run: @escaping (Application) -> ()) { + public init(_ run: @escaping (Application) -> Void) { self.run = run } - + public static func `default`(_ configuration: FernoConfiguration) -> Self { .init { app in app.ferno.use(configuration) @@ -26,7 +26,7 @@ extension Application { } } } - + final class Storage { public var configuration: FernoConfiguration public var driver: FernoDriver? @@ -35,7 +35,7 @@ extension Application { self.configuration = config } } - + struct Lifecycle: LifecycleHandler { func shutdown(_ application: Application) { if let driver = application.ferno.storage.driver { @@ -43,9 +43,9 @@ extension Application { } } } - + public let application: Application - + /// The `FernoConfiguration` object public var configuration: FernoConfiguration { get { self.storage.configuration } @@ -59,38 +59,38 @@ extension Application { } return driver } - + var storage: Storage { guard let storage = self.application.storage[Key.self] else { fatalError("No Ferno configuration found. Configure with app.ferno.use(...)") } return storage } - + var client: FernoClient { driver.makeClient(with: configuration) } - + func initialize() { self.application.lifecycle.use(Lifecycle()) } - + public func use(_ provider: Provider) { provider.run(self.application) } - + public func use(_ config: FernoConfiguration) { self.application.storage[Key.self] = .init(config: config) self.initialize() } - + public func use(custom driver: FernoDriver) { self.storage.driver = driver } - + } } extension Application.Ferno { - + /// Deletes everything public func delete(_ path: String...) async throws -> Bool { try await self.delete(path) @@ -120,9 +120,8 @@ extension Application.Ferno { } } - extension Application.Ferno { - + /// Deletes everything public func delete(_ path: [String]) async throws -> Bool { try await self.client.delete(method: .DELETE, path: path) @@ -139,7 +138,6 @@ extension Application.Ferno { ) } - /// Overwrites everything at that location with the data public func overwrite(_ path: [String], body: T) async throws -> T { try await self.client.send( diff --git a/Sources/Ferno/FernoClient.swift b/Sources/Ferno/FernoClient.swift index 0929e8d..b9e4c73 100644 --- a/Sources/Ferno/FernoClient.swift +++ b/Sources/Ferno/FernoClient.swift @@ -3,14 +3,25 @@ import JWT import NIOConcurrencyHelpers struct OAuthBody: Content { - var grant_type: String + var grantType: String var assertion: String + + private enum CodingKeys: String, CodingKey { + case grantType = "grant_type" + case assertion + } } struct OAuthResponse: Content { - var access_token: String - var token_type: String - var expires_in: Int + var accessToken: String + var tokenType: String + var expiresIn: Int + + private enum CodingKeys: String, CodingKey { + case accessToken = "access_token" + case tokenType = "token_type" + case expiresIn = "expires_in" + } } public protocol FernoClient { @@ -18,7 +29,7 @@ public protocol FernoClient { method: HTTPMethod, path: [String] ) async throws -> Bool - + func send( method: HTTPMethod, path: [String], @@ -26,7 +37,7 @@ public protocol FernoClient { body: T, headers: HTTPHeaders ) async throws -> F - + func sendMany( method: HTTPMethod, path: [String], @@ -115,12 +126,12 @@ extension FernoAPIClient { exp: .init(value: expirationDate), iat: .init(value: currentDate) ) - + return try privateSigner.sign(payload) } private func getAccessToken() async throws -> String { - + if let cachedToken = lock.withLock({ if let accessToken = configuration.accessToken, let tokenExpriationDate = configuration.tokenExpriationDate, @@ -136,10 +147,10 @@ extension FernoAPIClient { configuration.logger.debug("Going to get accessToken") let jwt = try createJWT() configuration.logger.debug("JWT created") - + var headers = HTTPHeaders() headers.add(name: .contentType, value: "application/x-www-form-urlencoded") - let oauthBody = OAuthBody(grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer", assertion: jwt) + let oauthBody = OAuthBody(grantType: "urn:ietf:params:oauth:grant-type:jwt-bearer", assertion: jwt) var req = ClientRequest( method: .POST, url: URI(string: "https://www.googleapis.com/oauth2/v4/token"), @@ -147,13 +158,13 @@ extension FernoAPIClient { body: nil ) try req.content.encode(oauthBody, as: .urlEncodedForm) - + let res = try await client.send(req).content.decode(OAuthResponse.self) lock.withLockVoid { - self.configuration.accessToken = res.access_token - self.configuration.tokenExpriationDate = Date().addingTimeInterval(TimeInterval(res.expires_in)) + self.configuration.accessToken = res.accessToken + self.configuration.tokenExpriationDate = Date().addingTimeInterval(TimeInterval(res.expiresIn)) } self.configuration.logger.debug("Access token received") - return res.access_token + return res.accessToken } } diff --git a/Sources/Ferno/FernoDriver.swift b/Sources/Ferno/FernoDriver.swift index 2a95871..623f758 100644 --- a/Sources/Ferno/FernoDriver.swift +++ b/Sources/Ferno/FernoDriver.swift @@ -11,7 +11,7 @@ import Vapor public protocol FernoDriver { /// Makes the ferno client func makeClient(with config: FernoConfiguration) -> FernoClient - + /// Shuts down the driver func shutdown() } diff --git a/Sources/Ferno/FernoPath.swift b/Sources/Ferno/FernoPath.swift index 1088e81..5ce6216 100644 --- a/Sources/Ferno/FernoPath.swift +++ b/Sources/Ferno/FernoPath.swift @@ -7,7 +7,7 @@ import Foundation -//Firebase Path Enum +// Firebase Path Enum public enum FernoPath { case child(String) case json diff --git a/Sources/Ferno/FernoQuery.swift b/Sources/Ferno/FernoQuery.swift index 904c831..82368ae 100644 --- a/Sources/Ferno/FernoQuery.swift +++ b/Sources/Ferno/FernoQuery.swift @@ -7,7 +7,7 @@ import Foundation -//FernoQuery Enum +// FernoQuery Enum public enum FernoQuery { case shallow(Bool) diff --git a/Sources/Ferno/FirebaseServiceAccountKey.swift b/Sources/Ferno/FirebaseServiceAccountKey.swift index f1edd05..56a94a0 100644 --- a/Sources/Ferno/FirebaseServiceAccountKey.swift +++ b/Sources/Ferno/FirebaseServiceAccountKey.swift @@ -28,7 +28,7 @@ public struct FirebaseServiceAccountKey: Content, Sendable { case universeDomain = "universe_domain" } - public init(type: String, + public init(type: String, projectId: String, privateKeyId: String, privateKey: String, diff --git a/Sources/Ferno/JWTPayload.swift b/Sources/Ferno/JWTPayload.swift index ebcf1b8..697719a 100644 --- a/Sources/Ferno/JWTPayload.swift +++ b/Sources/Ferno/JWTPayload.swift @@ -9,14 +9,13 @@ import Vapor import JWT struct Payload: JWTPayload { - - func verify(using signer: JWTSigner) throws { - try exp.verifyNotExpired() - } - var iss: IssuerClaim var scope: String var aud: String var exp: ExpirationClaim var iat: IssuedAtClaim + + func verify(using signer: JWTSigner) throws { + try exp.verifyNotExpired() + } } diff --git a/Tests/FernoTests/FernoTests.swift b/Tests/FernoTests/FernoTests.swift index b0f57fc..fa4187a 100644 --- a/Tests/FernoTests/FernoTests.swift +++ b/Tests/FernoTests/FernoTests.swift @@ -28,12 +28,12 @@ struct Student: Content { final class FirebaseTests: XCTestCase { - //GET a student + // GET a student func testGetStudent() async throws { try await launch { app in - - //Create 3 new students - let austin = Student(name: "Austin", major: "Computer Science", school: "Cornell University", age: 21, willGraduate: true) + // Create 3 new students + let austin = Student(name: "Austin", major: "Computer Science", + school: "Cornell University", age: 21, willGraduate: true) let child = try await app.ferno.create(["Student-get"], body: austin) let student: Student = try await app.ferno.retrieve(["Student-get", child.name], queryItems: []) @@ -47,26 +47,30 @@ final class FirebaseTests: XCTestCase { } } - //GET students + // GET students func testGetStudents() async throws { try await launch { app in - - //Create 3 new students - let austin = Student(name: "Austin", major: "Computer Science", school: "Cornell University", age: 21, willGraduate: true) - let ashley = Student(name: "Ashley", major: "Biology", school: "Siena & Cornell University", age: 20, willGraduate: true) - let billy = Student(name: "Billy", major: "Business", school: "Mira Costa Community", age: 22, willGraduate: false) - - let _ = try await app.ferno.create(["Students-get"], body: austin) - let _ = try await app.ferno.create(["Students-get"], body: ashley) - let _ = try await app.ferno.create(["Students-get"], body: billy) - + // Create 3 new students + let austin = Student(name: "Austin", major: "Computer Science", + school: "Cornell University", age: 21, willGraduate: true) + let ashley = Student(name: "Ashley", major: "Biology", + school: "Siena & Cornell University", age: 20, willGraduate: true) + let billy = Student(name: "Billy", major: "Business", + school: "Mira Costa Community", age: 22, willGraduate: false) + + _ = try await app.ferno.create(["Students-get"], body: austin) + _ = try await app.ferno.create(["Students-get"], body: ashley) + _ = try await app.ferno.create(["Students-get"], body: billy) let names = ["Austin", "Ashley", "Billy"] let ages = ["Austin": 21, "Ashley": 20, "Billy": 22] let majors = ["Austin": "Computer Science", "Ashley": "Biology", "Billy": "Business"] - let schools = ["Austin": "Cornell University", "Ashley": "Siena & Cornell University", "Billy": "Mira Costa Community"] - let willGradaute = ["Austin": true, "Ashley": true, "Billy": false] - + let schools = ["Austin": "Cornell University", + "Ashley": "Siena & Cornell University", + "Billy": "Mira Costa Community"] + let willGradaute = ["Austin": true, + "Ashley": true, + "Billy": false] let students: [Student] = try await app.ferno.retrieveMany("Students-get", queryItems: []).map { $0.value } @@ -88,10 +92,11 @@ final class FirebaseTests: XCTestCase { } } - //POST Student + // POST Student func testCreateStudent() async throws { try await launch { app in - let student = Student(name: "Matt", major: "Computer Science", school: "Cornell University", age: 20, willGraduate: true) + let student = Student(name: "Matt", major: "Computer Science", + school: "Cornell University", age: 20, willGraduate: true) let child = try await app.ferno.create(body: student) XCTAssertNotNil(child.name) @@ -101,10 +106,11 @@ final class FirebaseTests: XCTestCase { } } - //DELETE student + // DELETE student func testDeleteStudent() async throws { try await launch { app in - let timothy = Student(name: "Timothy", major: "Agriculture", school: "Mira Costa Community", age: 24, willGraduate: false) + let timothy = Student(name: "Timothy", major: "Agriculture", + school: "Mira Costa Community", age: 24, willGraduate: false) let child = try await app.ferno.create("Students-delete", body: timothy) @@ -113,10 +119,11 @@ final class FirebaseTests: XCTestCase { } } - //PATCH update student + // PATCH update student func testUpdateStudent() async throws { try await launch { app in - let austin = Student(name: "Austin", major: "Computer Science", school: "Cornell Univeristy", age: 21, willGraduate: true) + let austin = Student(name: "Austin", major: "Computer Science", + school: "Cornell Univeristy", age: 21, willGraduate: true) let child = try await app.ferno.create(["Students-patch"], body: austin) let updateStudentInfo = UpdateStudentInfo(major: "Cooking") @@ -129,10 +136,11 @@ final class FirebaseTests: XCTestCase { } } - //PUT overwrite student + // PUT overwrite student func testOverwriteStudent() async throws { try await launch { app in - let austin = Student(name: "Austin", major: "Computer Science", school: "Cornell Univeristy", age: 21, willGraduate: true) + let austin = Student(name: "Austin", major: "Computer Science", + school: "Cornell Univeristy", age: 21, willGraduate: true) let child = try await app.ferno.create(["Students-put"], body: austin) let teacher = Teacher(name: "Ms. Jennifer", teachesGrade: "12th", age: 29) From c900256387c580e53d5a1a79ff82a09db84e58c7 Mon Sep 17 00:00:00 2001 From: genaro_arvizu Date: Fri, 6 Oct 2023 19:11:24 -0500 Subject: [PATCH 03/15] Changing clientId for projectId to build basePath --- Sources/Ferno/FirebaseServiceAccountKey.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/Ferno/FirebaseServiceAccountKey.swift b/Sources/Ferno/FirebaseServiceAccountKey.swift index 56a94a0..04f5637 100644 --- a/Sources/Ferno/FirebaseServiceAccountKey.swift +++ b/Sources/Ferno/FirebaseServiceAccountKey.swift @@ -50,7 +50,7 @@ public struct FirebaseServiceAccountKey: Content, Sendable { self.authProviderX509CertUrl = authProviderX509CertUrl self.clientX509CertUrl = clientX509CertUrl self.universeDomain = universeDomain - self.basePath = "https://\(clientId).firebaseio.com" + self.basePath = "https://\(projectId).firebaseio.com" } public init(json: Data) throws { @@ -66,7 +66,7 @@ public struct FirebaseServiceAccountKey: Content, Sendable { self.authProviderX509CertUrl = configuration.authProviderX509CertUrl self.clientX509CertUrl = configuration.clientX509CertUrl self.universeDomain = configuration.universeDomain - self.basePath = "https://\(clientId).firebaseio.com" + self.basePath = "https://\(projectId).firebaseio.com" } public init(json: ByteBuffer) throws { @@ -82,7 +82,7 @@ public struct FirebaseServiceAccountKey: Content, Sendable { self.authProviderX509CertUrl = configuration.authProviderX509CertUrl self.clientX509CertUrl = configuration.clientX509CertUrl self.universeDomain = configuration.universeDomain - self.basePath = "https://\(clientId).firebaseio.com" + self.basePath = "https://\(projectId).firebaseio.com" } public init(from decoder: Decoder) throws { @@ -99,6 +99,6 @@ public struct FirebaseServiceAccountKey: Content, Sendable { self.authProviderX509CertUrl = try container.decode(String.self, forKey: FirebaseServiceAccountKey.CodingKeys.authProviderX509CertUrl) self.clientX509CertUrl = try container.decode(String.self, forKey: FirebaseServiceAccountKey.CodingKeys.clientX509CertUrl) self.universeDomain = try container.decode(String.self, forKey: FirebaseServiceAccountKey.CodingKeys.universeDomain) - self.basePath = "https://\(clientId).firebaseio.com" + self.basePath = "https://\(projectId).firebaseio.com" } } From 4d4d8bfb42f7a2d7212c6b1b07931620da77b915 Mon Sep 17 00:00:00 2001 From: genaro_arvizu Date: Fri, 6 Oct 2023 22:20:28 -0500 Subject: [PATCH 04/15] ServiceAccountKey configuration added --- Sources/Ferno/Application+Ferno.swift | 15 ++++-- Sources/Ferno/FernoClient.swift | 4 +- Sources/Ferno/FernoConfiguration.swift | 21 ++++----- Sources/Ferno/FernoDriver.swift | 10 +++- ...tKey.swift => FirebaseConfiguration.swift} | 46 ++++++++++--------- 5 files changed, 55 insertions(+), 41 deletions(-) rename Sources/Ferno/{FirebaseServiceAccountKey.swift => FirebaseConfiguration.swift} (73%) diff --git a/Sources/Ferno/Application+Ferno.swift b/Sources/Ferno/Application+Ferno.swift index d0ed67d..01d6669 100644 --- a/Sources/Ferno/Application+Ferno.swift +++ b/Sources/Ferno/Application+Ferno.swift @@ -25,13 +25,20 @@ extension Application { app.ferno.use(custom: DefaultFernoDriver(client: app.client)) } } + + public static func serviceAccountKey(_ configuration: FirebaseConfiguration) -> Self { + .init { app in + app.ferno.use(configuration) + app.ferno.use(custom: ServiceAccountKeyFernoDriver(client: app.client)) + } + } } final class Storage { - public var configuration: FernoConfiguration + public var configuration: Configuration public var driver: FernoDriver? - public init(config: FernoConfiguration) { + public init(config: Configuration) { self.configuration = config } } @@ -47,7 +54,7 @@ extension Application { public let application: Application /// The `FernoConfiguration` object - public var configuration: FernoConfiguration { + public var configuration: any Configuration { get { self.storage.configuration } nonmutating set { self.storage.configuration = newValue } } @@ -77,7 +84,7 @@ extension Application { provider.run(self.application) } - public func use(_ config: FernoConfiguration) { + public func use(_ config: Configuration) { self.application.storage[Key.self] = .init(config: config) self.initialize() } diff --git a/Sources/Ferno/FernoClient.swift b/Sources/Ferno/FernoClient.swift index b9e4c73..864dede 100644 --- a/Sources/Ferno/FernoClient.swift +++ b/Sources/Ferno/FernoClient.swift @@ -50,10 +50,10 @@ public protocol FernoClient { final class FernoAPIClient: FernoClient { private let decoder = JSONDecoder() private let client: Client - private(set) public var configuration: FernoConfiguration + private(set) public var configuration: Configuration private let lock = NIOLock() - public init(configuration: FernoConfiguration, client: Client) { + public init(configuration: Configuration, client: Client) { self.configuration = configuration self.client = client } diff --git a/Sources/Ferno/FernoConfiguration.swift b/Sources/Ferno/FernoConfiguration.swift index 1889039..cec3657 100644 --- a/Sources/Ferno/FernoConfiguration.swift +++ b/Sources/Ferno/FernoConfiguration.swift @@ -7,7 +7,16 @@ import Vapor -public struct FernoConfiguration { +public protocol Configuration { + var logger: Logger { get } + var basePath: String { get } + var email: String { get } + var privateKey: String { get } + var accessToken: String? { get set } + var tokenExpriationDate: Date? { get set } +} + +public struct FernoConfiguration: Configuration { public let logger: Logger public let basePath: String public let email: String @@ -30,14 +39,4 @@ public struct FernoConfiguration { self.tokenExpriationDate = tokenExpriationDate self.logger = logger } - - public init(jsonConfiguration: FirebaseServiceAccountKey, - tokenExpriationDate: Date? = nil, - logger: Logger = .init(label: "codes.vapor.ferno")) { - self.basePath = jsonConfiguration.basePath - self.email = jsonConfiguration.clientEmail - self.privateKey = jsonConfiguration.privateKey - self.tokenExpriationDate = tokenExpriationDate - self.logger = logger - } } diff --git a/Sources/Ferno/FernoDriver.swift b/Sources/Ferno/FernoDriver.swift index 623f758..f10e80c 100644 --- a/Sources/Ferno/FernoDriver.swift +++ b/Sources/Ferno/FernoDriver.swift @@ -10,7 +10,7 @@ import Vapor /// A new driver for Ferno public protocol FernoDriver { /// Makes the ferno client - func makeClient(with config: FernoConfiguration) -> FernoClient + func makeClient(with config: Configuration) -> FernoClient /// Shuts down the driver func shutdown() @@ -18,6 +18,12 @@ public protocol FernoDriver { struct DefaultFernoDriver: FernoDriver { var client: Client - func makeClient(with config: FernoConfiguration) -> FernoClient { FernoAPIClient(configuration: config, client: client) } + func makeClient(with config: Configuration) -> FernoClient { FernoAPIClient(configuration: config, client: client) } + func shutdown() {} +} + +struct ServiceAccountKeyFernoDriver: FernoDriver { + var client: Client + func makeClient(with config: Configuration) -> FernoClient { FernoAPIClient(configuration: config, client: client) } func shutdown() {} } diff --git a/Sources/Ferno/FirebaseServiceAccountKey.swift b/Sources/Ferno/FirebaseConfiguration.swift similarity index 73% rename from Sources/Ferno/FirebaseServiceAccountKey.swift rename to Sources/Ferno/FirebaseConfiguration.swift index 04f5637..935d762 100644 --- a/Sources/Ferno/FirebaseServiceAccountKey.swift +++ b/Sources/Ferno/FirebaseConfiguration.swift @@ -1,13 +1,16 @@ import Vapor -public struct FirebaseServiceAccountKey: Content, Sendable { +public struct FirebaseConfiguration: Configuration, Content, Sendable { public let type: String public let projectId: String - public let clientEmail: String + public let email: String public let clientId: String public let basePath: String + public var accessToken: String? + public var tokenExpriationDate: Date? + public var logger: Logger = .init(label: "codes.vapor.ferno") + public let privateKey: String internal let privateKeyId: String - internal let privateKey: String internal let authUri: String internal let tokenUri: String internal let authProviderX509CertUrl: String @@ -19,7 +22,7 @@ public struct FirebaseServiceAccountKey: Content, Sendable { case projectId = "project_id" case privateKeyId = "private_key_id" case privateKey = "private_key" - case clientEmail = "client_email" + case email = "client_email" case clientId = "client_id" case authUri = "auth_uri" case tokenUri = "token_uri" @@ -43,7 +46,7 @@ public struct FirebaseServiceAccountKey: Content, Sendable { self.projectId = projectId self.privateKeyId = privateKeyId self.privateKey = privateKey - self.clientEmail = clientEmail + self.email = clientEmail self.clientId = clientId self.authUri = authUri self.tokenUri = tokenUri @@ -54,12 +57,12 @@ public struct FirebaseServiceAccountKey: Content, Sendable { } public init(json: Data) throws { - let configuration = try JSONDecoder().decode(FirebaseServiceAccountKey.self, from: json) + let configuration = try JSONDecoder().decode(FirebaseConfiguration.self, from: json) self.type = configuration.type self.projectId = configuration.projectId self.privateKeyId = configuration.privateKeyId self.privateKey = configuration.privateKey - self.clientEmail = configuration.clientEmail + self.email = configuration.email self.clientId = configuration.clientId self.authUri = configuration.authUri self.tokenUri = configuration.tokenUri @@ -70,12 +73,12 @@ public struct FirebaseServiceAccountKey: Content, Sendable { } public init(json: ByteBuffer) throws { - let configuration = try JSONDecoder().decode(FirebaseServiceAccountKey.self, from: json) + let configuration = try JSONDecoder().decode(FirebaseConfiguration.self, from: json) self.type = configuration.type self.projectId = configuration.projectId self.privateKeyId = configuration.privateKeyId self.privateKey = configuration.privateKey - self.clientEmail = configuration.clientEmail + self.email = configuration.email self.clientId = configuration.clientId self.authUri = configuration.authUri self.tokenUri = configuration.tokenUri @@ -86,19 +89,18 @@ public struct FirebaseServiceAccountKey: Content, Sendable { } public init(from decoder: Decoder) throws { - let container: KeyedDecodingContainer = try decoder.container(keyedBy: FirebaseServiceAccountKey.CodingKeys.self) - - self.type = try container.decode(String.self, forKey: FirebaseServiceAccountKey.CodingKeys.type) - self.projectId = try container.decode(String.self, forKey: FirebaseServiceAccountKey.CodingKeys.projectId) - self.privateKeyId = try container.decode(String.self, forKey: FirebaseServiceAccountKey.CodingKeys.privateKeyId) - self.privateKey = try container.decode(String.self, forKey: FirebaseServiceAccountKey.CodingKeys.privateKey) - self.clientEmail = try container.decode(String.self, forKey: FirebaseServiceAccountKey.CodingKeys.clientEmail) - self.clientId = try container.decode(String.self, forKey: FirebaseServiceAccountKey.CodingKeys.clientId) - self.authUri = try container.decode(String.self, forKey: FirebaseServiceAccountKey.CodingKeys.authUri) - self.tokenUri = try container.decode(String.self, forKey: FirebaseServiceAccountKey.CodingKeys.tokenUri) - self.authProviderX509CertUrl = try container.decode(String.self, forKey: FirebaseServiceAccountKey.CodingKeys.authProviderX509CertUrl) - self.clientX509CertUrl = try container.decode(String.self, forKey: FirebaseServiceAccountKey.CodingKeys.clientX509CertUrl) - self.universeDomain = try container.decode(String.self, forKey: FirebaseServiceAccountKey.CodingKeys.universeDomain) + let container: KeyedDecodingContainer = try decoder.container(keyedBy: FirebaseConfiguration.CodingKeys.self) + self.type = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.type) + self.projectId = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.projectId) + self.privateKeyId = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.privateKeyId) + self.privateKey = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.privateKey) + self.email = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.email) + self.clientId = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.clientId) + self.authUri = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.authUri) + self.tokenUri = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.tokenUri) + self.authProviderX509CertUrl = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.authProviderX509CertUrl) + self.clientX509CertUrl = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.clientX509CertUrl) + self.universeDomain = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.universeDomain) self.basePath = "https://\(projectId).firebaseio.com" } } From bd51baff0927acab7e4c51f54ecb2ff23b770cca Mon Sep 17 00:00:00 2001 From: genaro_arvizu Date: Sat, 7 Oct 2023 13:45:19 -0500 Subject: [PATCH 05/15] Aud URL updated according to the google documentation --- Sources/Ferno/FernoClient.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Ferno/FernoClient.swift b/Sources/Ferno/FernoClient.swift index 864dede..5fca01a 100644 --- a/Sources/Ferno/FernoClient.swift +++ b/Sources/Ferno/FernoClient.swift @@ -122,7 +122,7 @@ extension FernoAPIClient { "https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/firebase.database" ].joined(separator: " "), - aud: "https://www.googleapis.com/oauth2/v4/token", + aud: "https://oauth2.googleapis.com/token", exp: .init(value: expirationDate), iat: .init(value: currentDate) ) From 245153c4f9f663f9d245524abf3b38eeaa82082b Mon Sep 17 00:00:00 2001 From: genaro_arvizu Date: Sat, 7 Oct 2023 22:24:03 -0500 Subject: [PATCH 06/15] Get access token URI updated --- Sources/Ferno/FernoClient.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Ferno/FernoClient.swift b/Sources/Ferno/FernoClient.swift index 5fca01a..6862fe1 100644 --- a/Sources/Ferno/FernoClient.swift +++ b/Sources/Ferno/FernoClient.swift @@ -153,7 +153,7 @@ extension FernoAPIClient { let oauthBody = OAuthBody(grantType: "urn:ietf:params:oauth:grant-type:jwt-bearer", assertion: jwt) var req = ClientRequest( method: .POST, - url: URI(string: "https://www.googleapis.com/oauth2/v4/token"), + url: URI(string: "https://oauth2.googleapis.com/token"), headers: headers, body: nil ) From 3110359916f2c6b8665633c2f12c6a264777f785 Mon Sep 17 00:00:00 2001 From: genaro_arvizu Date: Sat, 7 Oct 2023 23:04:10 -0500 Subject: [PATCH 07/15] FernoConfiguration renamed --- Sources/Ferno/Application+Ferno.swift | 8 +-- Sources/Ferno/FernoClient.swift | 8 +-- Sources/Ferno/FernoConfiguration.swift | 42 ----------- Sources/Ferno/FernoDefaultConfiguration.swift | 70 +++++++++++++++++++ Sources/Ferno/FernoDriver.swift | 6 +- Sources/Ferno/FirebaseConfiguration.swift | 60 ++++++++-------- 6 files changed, 113 insertions(+), 81 deletions(-) delete mode 100644 Sources/Ferno/FernoConfiguration.swift create mode 100644 Sources/Ferno/FernoDefaultConfiguration.swift diff --git a/Sources/Ferno/Application+Ferno.swift b/Sources/Ferno/Application+Ferno.swift index 01d6669..4604c31 100644 --- a/Sources/Ferno/Application+Ferno.swift +++ b/Sources/Ferno/Application+Ferno.swift @@ -35,10 +35,10 @@ extension Application { } final class Storage { - public var configuration: Configuration + public var configuration: FernoConfigurationProvider public var driver: FernoDriver? - public init(config: Configuration) { + public init(config: FernoConfigurationProvider) { self.configuration = config } } @@ -54,7 +54,7 @@ extension Application { public let application: Application /// The `FernoConfiguration` object - public var configuration: any Configuration { + public var configuration: any FernoConfigurationProvider { get { self.storage.configuration } nonmutating set { self.storage.configuration = newValue } } @@ -84,7 +84,7 @@ extension Application { provider.run(self.application) } - public func use(_ config: Configuration) { + public func use(_ config: FernoConfigurationProvider) { self.application.storage[Key.self] = .init(config: config) self.initialize() } diff --git a/Sources/Ferno/FernoClient.swift b/Sources/Ferno/FernoClient.swift index 6862fe1..4b3cb5c 100644 --- a/Sources/Ferno/FernoClient.swift +++ b/Sources/Ferno/FernoClient.swift @@ -50,10 +50,10 @@ public protocol FernoClient { final class FernoAPIClient: FernoClient { private let decoder = JSONDecoder() private let client: Client - private(set) public var configuration: Configuration + private(set) public var configuration: FernoConfigurationProvider private let lock = NIOLock() - public init(configuration: Configuration, client: Client) { + public init(configuration: FernoConfigurationProvider, client: Client) { self.configuration = configuration self.client = client } @@ -122,7 +122,7 @@ extension FernoAPIClient { "https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/firebase.database" ].joined(separator: " "), - aud: "https://oauth2.googleapis.com/token", + aud: configuration.tokenURI, exp: .init(value: expirationDate), iat: .init(value: currentDate) ) @@ -153,7 +153,7 @@ extension FernoAPIClient { let oauthBody = OAuthBody(grantType: "urn:ietf:params:oauth:grant-type:jwt-bearer", assertion: jwt) var req = ClientRequest( method: .POST, - url: URI(string: "https://oauth2.googleapis.com/token"), + url: URI(string: configuration.tokenURI), headers: headers, body: nil ) diff --git a/Sources/Ferno/FernoConfiguration.swift b/Sources/Ferno/FernoConfiguration.swift deleted file mode 100644 index cec3657..0000000 --- a/Sources/Ferno/FernoConfiguration.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// FernoConfiguration.swift -// Ferno -// -// Created by Maxim Krouk on 6/12/20. -// - -import Vapor - -public protocol Configuration { - var logger: Logger { get } - var basePath: String { get } - var email: String { get } - var privateKey: String { get } - var accessToken: String? { get set } - var tokenExpriationDate: Date? { get set } -} - -public struct FernoConfiguration: Configuration { - public let logger: Logger - public let basePath: String - public let email: String - public let privateKey: String - public var accessToken: String? - public var tokenExpriationDate: Date? - - public init( - basePath: String, - email: String, - privateKey: String, - accessToken: String? = nil, - tokenExpriationDate: Date? = nil, - logger: Logger = .init(label: "codes.vapor.ferno") - ) { - self.basePath = basePath - self.email = email - self.privateKey = privateKey - self.accessToken = accessToken - self.tokenExpriationDate = tokenExpriationDate - self.logger = logger - } -} diff --git a/Sources/Ferno/FernoDefaultConfiguration.swift b/Sources/Ferno/FernoDefaultConfiguration.swift new file mode 100644 index 0000000..2aaa825 --- /dev/null +++ b/Sources/Ferno/FernoDefaultConfiguration.swift @@ -0,0 +1,70 @@ +// +// FernoConfiguration.swift +// Ferno +// +// Created by Maxim Krouk on 6/12/20. +// + +import Vapor + +public protocol FernoConfigurationProvider { + var logger: Logger { get } + var basePath: String { get } + var email: String { get } + var privateKey: String { get } + var accessToken: String? { get set } + var tokenExpriationDate: Date? { get set } + var tokenURI: String { get } +} + +@available(*, deprecated, renamed: "FernoDefaultConfiguration") +public struct FernoConfiguration: FernoConfigurationProvider { + public let logger: Logger + public let basePath: String + public let email: String + public let privateKey: String + public var accessToken: String? + public var tokenExpriationDate: Date? + public let tokenURI: String = "https://oauth2.googleapis.com/token" + public init( + basePath: String, + email: String, + privateKey: String, + accessToken: String? = nil, + tokenExpriationDate: Date? = nil, + logger: Logger = .init(label: "codes.vapor.ferno") + ) { + self.basePath = basePath + self.email = email + self.privateKey = privateKey + self.accessToken = accessToken + self.tokenExpriationDate = tokenExpriationDate + self.logger = logger + } +} + +public struct FernoDefaultConfiguration: FernoConfigurationProvider { + public let logger: Logger + public let basePath: String + public let email: String + public let privateKey: String + public var accessToken: String? + public var tokenExpriationDate: Date? + public let tokenURI: String = "https://oauth2.googleapis.com/token" + + public init( + basePath: String, + email: String, + privateKey: String, + accessToken: String? = nil, + tokenExpriationDate: Date? = nil, + logger: Logger = .init(label: "codes.vapor.ferno") + ) { + self.basePath = basePath + self.email = email + self.privateKey = privateKey + self.accessToken = accessToken + self.tokenExpriationDate = tokenExpriationDate + self.logger = logger + } +} diff --git a/Sources/Ferno/FernoDriver.swift b/Sources/Ferno/FernoDriver.swift index f10e80c..12ad29b 100644 --- a/Sources/Ferno/FernoDriver.swift +++ b/Sources/Ferno/FernoDriver.swift @@ -10,7 +10,7 @@ import Vapor /// A new driver for Ferno public protocol FernoDriver { /// Makes the ferno client - func makeClient(with config: Configuration) -> FernoClient + func makeClient(with config: FernoConfigurationProvider) -> FernoClient /// Shuts down the driver func shutdown() @@ -18,12 +18,12 @@ public protocol FernoDriver { struct DefaultFernoDriver: FernoDriver { var client: Client - func makeClient(with config: Configuration) -> FernoClient { FernoAPIClient(configuration: config, client: client) } + func makeClient(with config: FernoConfigurationProvider) -> FernoClient { FernoAPIClient(configuration: config, client: client) } func shutdown() {} } struct ServiceAccountKeyFernoDriver: FernoDriver { var client: Client - func makeClient(with config: Configuration) -> FernoClient { FernoAPIClient(configuration: config, client: client) } + func makeClient(with config: FernoConfigurationProvider) -> FernoClient { FernoAPIClient(configuration: config, client: client) } func shutdown() {} } diff --git a/Sources/Ferno/FirebaseConfiguration.swift b/Sources/Ferno/FirebaseConfiguration.swift index 935d762..8899309 100644 --- a/Sources/Ferno/FirebaseConfiguration.swift +++ b/Sources/Ferno/FirebaseConfiguration.swift @@ -1,6 +1,6 @@ import Vapor -public struct FirebaseConfiguration: Configuration, Content, Sendable { +public struct FirebaseConfiguration: FernoConfigurationProvider, Content { public let type: String public let projectId: String public let email: String @@ -10,11 +10,11 @@ public struct FirebaseConfiguration: Configuration, Content, Sendable { public var tokenExpriationDate: Date? public var logger: Logger = .init(label: "codes.vapor.ferno") public let privateKey: String + public let tokenURI: String internal let privateKeyId: String - internal let authUri: String - internal let tokenUri: String - internal let authProviderX509CertUrl: String - internal let clientX509CertUrl: String + internal let authURI: String + internal let authProviderX509CertURL: String + internal let clientX509CertURL: String internal let universeDomain: String enum CodingKeys: String, CodingKey { @@ -24,10 +24,10 @@ public struct FirebaseConfiguration: Configuration, Content, Sendable { case privateKey = "private_key" case email = "client_email" case clientId = "client_id" - case authUri = "auth_uri" - case tokenUri = "token_uri" - case authProviderX509CertUrl = "auth_provider_x509_cert_url" - case clientX509CertUrl = "client_x509_cert_url" + case authURI = "auth_uri" + case tokenURI = "token_uri" + case authProviderX509CertURL = "auth_provider_x509_cert_url" + case clientX509CertURL = "client_x509_cert_url" case universeDomain = "universe_domain" } @@ -41,22 +41,24 @@ public struct FirebaseConfiguration: Configuration, Content, Sendable { tokenUri: String, authProviderX509CertUrl: String, clientX509CertUrl: String, - universeDomain: String) { + universeDomain: String, + logger: Logger = .init(label: "codes.vapor.ferno")) { self.type = type self.projectId = projectId self.privateKeyId = privateKeyId self.privateKey = privateKey self.email = clientEmail self.clientId = clientId - self.authUri = authUri - self.tokenUri = tokenUri - self.authProviderX509CertUrl = authProviderX509CertUrl - self.clientX509CertUrl = clientX509CertUrl + self.authURI = authUri + self.tokenURI = tokenUri + self.authProviderX509CertURL = authProviderX509CertUrl + self.clientX509CertURL = clientX509CertUrl self.universeDomain = universeDomain + self.logger = logger self.basePath = "https://\(projectId).firebaseio.com" } - public init(json: Data) throws { + public init(json: Data, logger: Logger = .init(label: "codes.vapor.ferno")) throws { let configuration = try JSONDecoder().decode(FirebaseConfiguration.self, from: json) self.type = configuration.type self.projectId = configuration.projectId @@ -64,15 +66,16 @@ public struct FirebaseConfiguration: Configuration, Content, Sendable { self.privateKey = configuration.privateKey self.email = configuration.email self.clientId = configuration.clientId - self.authUri = configuration.authUri - self.tokenUri = configuration.tokenUri - self.authProviderX509CertUrl = configuration.authProviderX509CertUrl - self.clientX509CertUrl = configuration.clientX509CertUrl + self.authURI = configuration.authURI + self.tokenURI = configuration.tokenURI + self.authProviderX509CertURL = configuration.authProviderX509CertURL + self.clientX509CertURL = configuration.clientX509CertURL self.universeDomain = configuration.universeDomain + self.logger = logger self.basePath = "https://\(projectId).firebaseio.com" } - public init(json: ByteBuffer) throws { + public init(json: ByteBuffer, logger: Logger = .init(label: "codes.vapor.ferno")) throws { let configuration = try JSONDecoder().decode(FirebaseConfiguration.self, from: json) self.type = configuration.type self.projectId = configuration.projectId @@ -80,11 +83,12 @@ public struct FirebaseConfiguration: Configuration, Content, Sendable { self.privateKey = configuration.privateKey self.email = configuration.email self.clientId = configuration.clientId - self.authUri = configuration.authUri - self.tokenUri = configuration.tokenUri - self.authProviderX509CertUrl = configuration.authProviderX509CertUrl - self.clientX509CertUrl = configuration.clientX509CertUrl + self.authURI = configuration.authURI + self.tokenURI = configuration.tokenURI + self.authProviderX509CertURL = configuration.authProviderX509CertURL + self.clientX509CertURL = configuration.clientX509CertURL self.universeDomain = configuration.universeDomain + self.logger = logger self.basePath = "https://\(projectId).firebaseio.com" } @@ -96,10 +100,10 @@ public struct FirebaseConfiguration: Configuration, Content, Sendable { self.privateKey = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.privateKey) self.email = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.email) self.clientId = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.clientId) - self.authUri = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.authUri) - self.tokenUri = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.tokenUri) - self.authProviderX509CertUrl = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.authProviderX509CertUrl) - self.clientX509CertUrl = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.clientX509CertUrl) + self.authURI = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.authURI) + self.tokenURI = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.tokenURI) + self.authProviderX509CertURL = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.authProviderX509CertURL) + self.clientX509CertURL = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.clientX509CertURL) self.universeDomain = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.universeDomain) self.basePath = "https://\(projectId).firebaseio.com" } From 164400eb5d415bb74025849ade52b76c468c5c59 Mon Sep 17 00:00:00 2001 From: genaro_arvizu Date: Sun, 8 Oct 2023 11:46:50 -0500 Subject: [PATCH 08/15] New ferno clases renamed --- Sources/Ferno/Application+Ferno.swift | 15 +++++++--- Sources/Ferno/FernoDriver.swift | 4 +-- ...FernoServiceAccountKeyConfiguration.swift} | 30 +++++++++---------- 3 files changed, 28 insertions(+), 21 deletions(-) rename Sources/Ferno/{FirebaseConfiguration.swift => FernoServiceAccountKeyConfiguration.swift} (77%) diff --git a/Sources/Ferno/Application+Ferno.swift b/Sources/Ferno/Application+Ferno.swift index 4604c31..7c96707 100644 --- a/Sources/Ferno/Application+Ferno.swift +++ b/Sources/Ferno/Application+Ferno.swift @@ -18,18 +18,25 @@ extension Application { public init(_ run: @escaping (Application) -> Void) { self.run = run } - + @available(*, deprecated) public static func `default`(_ configuration: FernoConfiguration) -> Self { .init { app in app.ferno.use(configuration) - app.ferno.use(custom: DefaultFernoDriver(client: app.client)) + app.ferno.use(custom: FernoDefaultDriver(client: app.client)) + } + } + + public static func `default`(_ configuration: FernoDefaultConfiguration) -> Self { + .init { app in + app.ferno.use(configuration) + app.ferno.use(custom: FernoDefaultDriver(client: app.client)) } } - public static func serviceAccountKey(_ configuration: FirebaseConfiguration) -> Self { + public static func serviceAccountKey(_ configuration: FernoServiceAccountKeyConfiguration) -> Self { .init { app in app.ferno.use(configuration) - app.ferno.use(custom: ServiceAccountKeyFernoDriver(client: app.client)) + app.ferno.use(custom: FernoServiceAccountKeyDriver(client: app.client)) } } } diff --git a/Sources/Ferno/FernoDriver.swift b/Sources/Ferno/FernoDriver.swift index 12ad29b..9e96646 100644 --- a/Sources/Ferno/FernoDriver.swift +++ b/Sources/Ferno/FernoDriver.swift @@ -16,13 +16,13 @@ public protocol FernoDriver { func shutdown() } -struct DefaultFernoDriver: FernoDriver { +struct FernoDefaultDriver: FernoDriver { var client: Client func makeClient(with config: FernoConfigurationProvider) -> FernoClient { FernoAPIClient(configuration: config, client: client) } func shutdown() {} } -struct ServiceAccountKeyFernoDriver: FernoDriver { +struct FernoServiceAccountKeyDriver: FernoDriver { var client: Client func makeClient(with config: FernoConfigurationProvider) -> FernoClient { FernoAPIClient(configuration: config, client: client) } func shutdown() {} diff --git a/Sources/Ferno/FirebaseConfiguration.swift b/Sources/Ferno/FernoServiceAccountKeyConfiguration.swift similarity index 77% rename from Sources/Ferno/FirebaseConfiguration.swift rename to Sources/Ferno/FernoServiceAccountKeyConfiguration.swift index 8899309..cd06e45 100644 --- a/Sources/Ferno/FirebaseConfiguration.swift +++ b/Sources/Ferno/FernoServiceAccountKeyConfiguration.swift @@ -1,6 +1,6 @@ import Vapor -public struct FirebaseConfiguration: FernoConfigurationProvider, Content { +public struct FernoServiceAccountKeyConfiguration: FernoConfigurationProvider, Content { public let type: String public let projectId: String public let email: String @@ -59,7 +59,7 @@ public struct FirebaseConfiguration: FernoConfigurationProvider, Content { } public init(json: Data, logger: Logger = .init(label: "codes.vapor.ferno")) throws { - let configuration = try JSONDecoder().decode(FirebaseConfiguration.self, from: json) + let configuration = try JSONDecoder().decode(FernoServiceAccountKeyConfiguration.self, from: json) self.type = configuration.type self.projectId = configuration.projectId self.privateKeyId = configuration.privateKeyId @@ -76,7 +76,7 @@ public struct FirebaseConfiguration: FernoConfigurationProvider, Content { } public init(json: ByteBuffer, logger: Logger = .init(label: "codes.vapor.ferno")) throws { - let configuration = try JSONDecoder().decode(FirebaseConfiguration.self, from: json) + let configuration = try JSONDecoder().decode(FernoServiceAccountKeyConfiguration.self, from: json) self.type = configuration.type self.projectId = configuration.projectId self.privateKeyId = configuration.privateKeyId @@ -93,18 +93,18 @@ public struct FirebaseConfiguration: FernoConfigurationProvider, Content { } public init(from decoder: Decoder) throws { - let container: KeyedDecodingContainer = try decoder.container(keyedBy: FirebaseConfiguration.CodingKeys.self) - self.type = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.type) - self.projectId = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.projectId) - self.privateKeyId = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.privateKeyId) - self.privateKey = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.privateKey) - self.email = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.email) - self.clientId = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.clientId) - self.authURI = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.authURI) - self.tokenURI = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.tokenURI) - self.authProviderX509CertURL = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.authProviderX509CertURL) - self.clientX509CertURL = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.clientX509CertURL) - self.universeDomain = try container.decode(String.self, forKey: FirebaseConfiguration.CodingKeys.universeDomain) + let container: KeyedDecodingContainer = try decoder.container(keyedBy: FernoServiceAccountKeyConfiguration.CodingKeys.self) + self.type = try container.decode(String.self, forKey: FernoServiceAccountKeyConfiguration.CodingKeys.type) + self.projectId = try container.decode(String.self, forKey: FernoServiceAccountKeyConfiguration.CodingKeys.projectId) + self.privateKeyId = try container.decode(String.self, forKey: FernoServiceAccountKeyConfiguration.CodingKeys.privateKeyId) + self.privateKey = try container.decode(String.self, forKey: FernoServiceAccountKeyConfiguration.CodingKeys.privateKey) + self.email = try container.decode(String.self, forKey: FernoServiceAccountKeyConfiguration.CodingKeys.email) + self.clientId = try container.decode(String.self, forKey: FernoServiceAccountKeyConfiguration.CodingKeys.clientId) + self.authURI = try container.decode(String.self, forKey: FernoServiceAccountKeyConfiguration.CodingKeys.authURI) + self.tokenURI = try container.decode(String.self, forKey: FernoServiceAccountKeyConfiguration.CodingKeys.tokenURI) + self.authProviderX509CertURL = try container.decode(String.self, forKey: FernoServiceAccountKeyConfiguration.CodingKeys.authProviderX509CertURL) + self.clientX509CertURL = try container.decode(String.self, forKey: FernoServiceAccountKeyConfiguration.CodingKeys.clientX509CertURL) + self.universeDomain = try container.decode(String.self, forKey: FernoServiceAccountKeyConfiguration.CodingKeys.universeDomain) self.basePath = "https://\(projectId).firebaseio.com" } } From ed7135f5dd2300c8fcf1d0cfbc64c6b6640fe6e4 Mon Sep 17 00:00:00 2001 From: genaro_arvizu Date: Sun, 8 Oct 2023 12:21:15 -0500 Subject: [PATCH 09/15] FernoServiceAccountKeyConfiguration path initializer --- .../FernoServiceAccountKeyConfiguration.swift | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Sources/Ferno/FernoServiceAccountKeyConfiguration.swift b/Sources/Ferno/FernoServiceAccountKeyConfiguration.swift index cd06e45..f7a0f6f 100644 --- a/Sources/Ferno/FernoServiceAccountKeyConfiguration.swift +++ b/Sources/Ferno/FernoServiceAccountKeyConfiguration.swift @@ -91,7 +91,26 @@ public struct FernoServiceAccountKeyConfiguration: FernoConfigurationProvider, C self.logger = logger self.basePath = "https://\(projectId).firebaseio.com" } - + + /// The `ServiceAccountKey.json` path in URL format + public init(path: URL, logger: Logger = .init(label: "codes.vapor.ferno")) throws { + let data = try Data(contentsOf: path) + let configuration = try JSONDecoder().decode(FernoServiceAccountKeyConfiguration.self, from: data) + self.type = configuration.type + self.projectId = configuration.projectId + self.privateKeyId = configuration.privateKeyId + self.privateKey = configuration.privateKey + self.email = configuration.email + self.clientId = configuration.clientId + self.authURI = configuration.authURI + self.tokenURI = configuration.tokenURI + self.authProviderX509CertURL = configuration.authProviderX509CertURL + self.clientX509CertURL = configuration.clientX509CertURL + self.universeDomain = configuration.universeDomain + self.logger = logger + self.basePath = "https://\(projectId).firebaseio.com" + } + public init(from decoder: Decoder) throws { let container: KeyedDecodingContainer = try decoder.container(keyedBy: FernoServiceAccountKeyConfiguration.CodingKeys.self) self.type = try container.decode(String.self, forKey: FernoServiceAccountKeyConfiguration.CodingKeys.type) From f37ebf265ed3a6bf2455bd414b7ee08ec04254ff Mon Sep 17 00:00:00 2001 From: genaro_arvizu Date: Fri, 13 Oct 2023 14:12:37 -0500 Subject: [PATCH 10/15] Cleaning code --- Sources/Ferno/Application+Ferno.swift | 2 +- ...ft => FernoServiceJsonConfiguration.swift} | 32 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) rename Sources/Ferno/{FernoServiceAccountKeyConfiguration.swift => FernoServiceJsonConfiguration.swift} (83%) diff --git a/Sources/Ferno/Application+Ferno.swift b/Sources/Ferno/Application+Ferno.swift index 7c96707..95c33e5 100644 --- a/Sources/Ferno/Application+Ferno.swift +++ b/Sources/Ferno/Application+Ferno.swift @@ -33,7 +33,7 @@ extension Application { } } - public static func serviceAccountKey(_ configuration: FernoServiceAccountKeyConfiguration) -> Self { + public static func serviceAccountKey(_ configuration: FernoServiceJsonConfiguration) -> Self { .init { app in app.ferno.use(configuration) app.ferno.use(custom: FernoServiceAccountKeyDriver(client: app.client)) diff --git a/Sources/Ferno/FernoServiceAccountKeyConfiguration.swift b/Sources/Ferno/FernoServiceJsonConfiguration.swift similarity index 83% rename from Sources/Ferno/FernoServiceAccountKeyConfiguration.swift rename to Sources/Ferno/FernoServiceJsonConfiguration.swift index f7a0f6f..8e4c835 100644 --- a/Sources/Ferno/FernoServiceAccountKeyConfiguration.swift +++ b/Sources/Ferno/FernoServiceJsonConfiguration.swift @@ -1,6 +1,6 @@ import Vapor -public struct FernoServiceAccountKeyConfiguration: FernoConfigurationProvider, Content { +public struct FernoServiceJsonConfiguration: FernoConfigurationProvider, Content { public let type: String public let projectId: String public let email: String @@ -59,7 +59,7 @@ public struct FernoServiceAccountKeyConfiguration: FernoConfigurationProvider, C } public init(json: Data, logger: Logger = .init(label: "codes.vapor.ferno")) throws { - let configuration = try JSONDecoder().decode(FernoServiceAccountKeyConfiguration.self, from: json) + let configuration = try JSONDecoder().decode(FernoServiceJsonConfiguration.self, from: json) self.type = configuration.type self.projectId = configuration.projectId self.privateKeyId = configuration.privateKeyId @@ -76,7 +76,7 @@ public struct FernoServiceAccountKeyConfiguration: FernoConfigurationProvider, C } public init(json: ByteBuffer, logger: Logger = .init(label: "codes.vapor.ferno")) throws { - let configuration = try JSONDecoder().decode(FernoServiceAccountKeyConfiguration.self, from: json) + let configuration = try JSONDecoder().decode(FernoServiceJsonConfiguration.self, from: json) self.type = configuration.type self.projectId = configuration.projectId self.privateKeyId = configuration.privateKeyId @@ -95,7 +95,7 @@ public struct FernoServiceAccountKeyConfiguration: FernoConfigurationProvider, C /// The `ServiceAccountKey.json` path in URL format public init(path: URL, logger: Logger = .init(label: "codes.vapor.ferno")) throws { let data = try Data(contentsOf: path) - let configuration = try JSONDecoder().decode(FernoServiceAccountKeyConfiguration.self, from: data) + let configuration = try JSONDecoder().decode(FernoServiceJsonConfiguration.self, from: data) self.type = configuration.type self.projectId = configuration.projectId self.privateKeyId = configuration.privateKeyId @@ -112,18 +112,18 @@ public struct FernoServiceAccountKeyConfiguration: FernoConfigurationProvider, C } public init(from decoder: Decoder) throws { - let container: KeyedDecodingContainer = try decoder.container(keyedBy: FernoServiceAccountKeyConfiguration.CodingKeys.self) - self.type = try container.decode(String.self, forKey: FernoServiceAccountKeyConfiguration.CodingKeys.type) - self.projectId = try container.decode(String.self, forKey: FernoServiceAccountKeyConfiguration.CodingKeys.projectId) - self.privateKeyId = try container.decode(String.self, forKey: FernoServiceAccountKeyConfiguration.CodingKeys.privateKeyId) - self.privateKey = try container.decode(String.self, forKey: FernoServiceAccountKeyConfiguration.CodingKeys.privateKey) - self.email = try container.decode(String.self, forKey: FernoServiceAccountKeyConfiguration.CodingKeys.email) - self.clientId = try container.decode(String.self, forKey: FernoServiceAccountKeyConfiguration.CodingKeys.clientId) - self.authURI = try container.decode(String.self, forKey: FernoServiceAccountKeyConfiguration.CodingKeys.authURI) - self.tokenURI = try container.decode(String.self, forKey: FernoServiceAccountKeyConfiguration.CodingKeys.tokenURI) - self.authProviderX509CertURL = try container.decode(String.self, forKey: FernoServiceAccountKeyConfiguration.CodingKeys.authProviderX509CertURL) - self.clientX509CertURL = try container.decode(String.self, forKey: FernoServiceAccountKeyConfiguration.CodingKeys.clientX509CertURL) - self.universeDomain = try container.decode(String.self, forKey: FernoServiceAccountKeyConfiguration.CodingKeys.universeDomain) + let container: KeyedDecodingContainer = try decoder.container(keyedBy: FernoServiceJsonConfiguration.CodingKeys.self) + self.type = try container.decode(String.self, forKey: FernoServiceJsonConfiguration.CodingKeys.type) + self.projectId = try container.decode(String.self, forKey: FernoServiceJsonConfiguration.CodingKeys.projectId) + self.privateKeyId = try container.decode(String.self, forKey: FernoServiceJsonConfiguration.CodingKeys.privateKeyId) + self.privateKey = try container.decode(String.self, forKey: FernoServiceJsonConfiguration.CodingKeys.privateKey) + self.email = try container.decode(String.self, forKey: FernoServiceJsonConfiguration.CodingKeys.email) + self.clientId = try container.decode(String.self, forKey: FernoServiceJsonConfiguration.CodingKeys.clientId) + self.authURI = try container.decode(String.self, forKey: FernoServiceJsonConfiguration.CodingKeys.authURI) + self.tokenURI = try container.decode(String.self, forKey: FernoServiceJsonConfiguration.CodingKeys.tokenURI) + self.authProviderX509CertURL = try container.decode(String.self, forKey: FernoServiceJsonConfiguration.CodingKeys.authProviderX509CertURL) + self.clientX509CertURL = try container.decode(String.self, forKey: FernoServiceJsonConfiguration.CodingKeys.clientX509CertURL) + self.universeDomain = try container.decode(String.self, forKey: FernoServiceJsonConfiguration.CodingKeys.universeDomain) self.basePath = "https://\(projectId).firebaseio.com" } } From 7d139cbe7bbb87c69015abac957489c2f1a6e1df Mon Sep 17 00:00:00 2001 From: Genaro Arvizu <30416292+chibombo@users.noreply.github.com> Date: Fri, 13 Oct 2023 16:11:37 -0500 Subject: [PATCH 11/15] Create swift.yml --- .github/workflows/swift.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/swift.yml diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml new file mode 100644 index 0000000..bbc9e38 --- /dev/null +++ b/.github/workflows/swift.yml @@ -0,0 +1,16 @@ +name: test + +on: + pull_request: { types: [opened, reopened, synchronize, ready_for_review] } + push: { branches: [ master ] } + +jobs: + build: + runs-on: ubuntu-latest + container: swift:5.9-jammy + steps: + - uses: actions/checkout@v4 + - name: Build + run: swift build -v + - name: Run tests + run: swift test -v From abb8dc6ccd835aabd5eb5b4dcded0e25bf74d6d4 Mon Sep 17 00:00:00 2001 From: genaro_arvizu Date: Sat, 14 Oct 2023 11:01:17 -0500 Subject: [PATCH 12/15] Adding some unit tests --- .../Ferno/FernoServiceJsonConfiguration.swift | 2 +- Tests/FernoTests/Application+Testing.swift | 14 +- ...rnoTests.swift => FernoDefaultTests.swift} | 32 +---- Tests/FernoTests/FernoServiceJsonTests.swift | 129 ++++++++++++++++++ .../Models/FirebaseAccountKey.swift | 10 ++ Tests/FernoTests/Models/Student.swift | 9 ++ Tests/FernoTests/Models/Teacher.swift | 7 + .../FernoTests/Models/UpdateStudentInfo.swift | 5 + 8 files changed, 181 insertions(+), 27 deletions(-) rename Tests/FernoTests/{FernoTests.swift => FernoDefaultTests.swift} (91%) create mode 100644 Tests/FernoTests/FernoServiceJsonTests.swift create mode 100644 Tests/FernoTests/Models/FirebaseAccountKey.swift create mode 100644 Tests/FernoTests/Models/Student.swift create mode 100644 Tests/FernoTests/Models/Teacher.swift create mode 100644 Tests/FernoTests/Models/UpdateStudentInfo.swift diff --git a/Sources/Ferno/FernoServiceJsonConfiguration.swift b/Sources/Ferno/FernoServiceJsonConfiguration.swift index 177e7cf..9edf3a0 100644 --- a/Sources/Ferno/FernoServiceJsonConfiguration.swift +++ b/Sources/Ferno/FernoServiceJsonConfiguration.swift @@ -1,6 +1,6 @@ import Vapor -public struct FernoServiceJsonConfiguration: FernoConfigurationProvider, Content { +public struct FernoServiceJsonConfiguration: FernoConfigurationProvider, Decodable, Sendable { public let type: String public let projectId: String public let email: String diff --git a/Tests/FernoTests/Application+Testing.swift b/Tests/FernoTests/Application+Testing.swift index 8f56516..ae261c5 100644 --- a/Tests/FernoTests/Application+Testing.swift +++ b/Tests/FernoTests/Application+Testing.swift @@ -8,7 +8,7 @@ import Ferno import XCTVapor -func launch(_ test: (Application) async throws -> Void) async throws { +func defaultLaunch(_ test: (Application) async throws -> Void) async throws { let app = Application(.testing) defer { app.shutdown() } app.ferno.use( @@ -22,3 +22,15 @@ func launch(_ test: (Application) async throws -> Void) async throws { ) try await test(app) } + +func serviceJsonLaunch(_ test: (Application) async throws -> Void) async throws { + let app = Application(.testing) + guard let json = FirebaseAccountKey.json.data(using: .utf8) else { return } + + let configuration = try FernoServiceJsonConfiguration(json: json) + defer { app.shutdown() } + app.ferno.use( + .serviceAccountKey(configuration) + ) + try await test(app) +} diff --git a/Tests/FernoTests/FernoTests.swift b/Tests/FernoTests/FernoDefaultTests.swift similarity index 91% rename from Tests/FernoTests/FernoTests.swift rename to Tests/FernoTests/FernoDefaultTests.swift index fa4187a..13cb880 100644 --- a/Tests/FernoTests/FernoTests.swift +++ b/Tests/FernoTests/FernoDefaultTests.swift @@ -8,29 +8,11 @@ import XCTVapor import Ferno -struct Teacher: Content { - var name: String - var teachesGrade: String - var age: Int -} - -struct UpdateStudentInfo: Content { - var major: String -} - -struct Student: Content { - var name: String - var major: String - var school: String - var age: Int - var willGraduate: Bool -} - -final class FirebaseTests: XCTestCase { +final class FernoDefaultTests: XCTestCase { // GET a student func testGetStudent() async throws { - try await launch { app in + try await defaultLaunch { app in // Create 3 new students let austin = Student(name: "Austin", major: "Computer Science", school: "Cornell University", age: 21, willGraduate: true) @@ -49,7 +31,7 @@ final class FirebaseTests: XCTestCase { // GET students func testGetStudents() async throws { - try await launch { app in + try await defaultLaunch { app in // Create 3 new students let austin = Student(name: "Austin", major: "Computer Science", school: "Cornell University", age: 21, willGraduate: true) @@ -94,7 +76,7 @@ final class FirebaseTests: XCTestCase { // POST Student func testCreateStudent() async throws { - try await launch { app in + try await defaultLaunch { app in let student = Student(name: "Matt", major: "Computer Science", school: "Cornell University", age: 20, willGraduate: true) let child = try await app.ferno.create(body: student) @@ -108,7 +90,7 @@ final class FirebaseTests: XCTestCase { // DELETE student func testDeleteStudent() async throws { - try await launch { app in + try await defaultLaunch { app in let timothy = Student(name: "Timothy", major: "Agriculture", school: "Mira Costa Community", age: 24, willGraduate: false) @@ -121,7 +103,7 @@ final class FirebaseTests: XCTestCase { // PATCH update student func testUpdateStudent() async throws { - try await launch { app in + try await defaultLaunch { app in let austin = Student(name: "Austin", major: "Computer Science", school: "Cornell Univeristy", age: 21, willGraduate: true) let child = try await app.ferno.create(["Students-patch"], body: austin) @@ -138,7 +120,7 @@ final class FirebaseTests: XCTestCase { // PUT overwrite student func testOverwriteStudent() async throws { - try await launch { app in + try await defaultLaunch { app in let austin = Student(name: "Austin", major: "Computer Science", school: "Cornell Univeristy", age: 21, willGraduate: true) let child = try await app.ferno.create(["Students-put"], body: austin) diff --git a/Tests/FernoTests/FernoServiceJsonTests.swift b/Tests/FernoTests/FernoServiceJsonTests.swift new file mode 100644 index 0000000..e47adae --- /dev/null +++ b/Tests/FernoTests/FernoServiceJsonTests.swift @@ -0,0 +1,129 @@ +import XCTVapor +import Ferno + +class FernoServiceJsonTests: XCTestCase { + // GET a student + func testGetStudent() async throws { + try await serviceJsonLaunch { app in + // Create 3 new students + let austin = Student(name: "Austin", major: "Computer Science", + school: "Cornell University", age: 21, willGraduate: true) + let child = try await app.ferno.create(["Student-get"], body: austin) + + let student: Student = try await app.ferno.retrieve(["Student-get", child.name], queryItems: []) + + XCTAssert(student.name == "Austin") + XCTAssert(student.major == "Computer Science") + + let success = try await app.ferno.delete(["Student-get", child.name]) + + XCTAssertTrue(success) + } + } + + // GET students + func testGetStudents() async throws { + try await serviceJsonLaunch { app in + // Create 3 new students + let austin = Student(name: "Austin", major: "Computer Science", + school: "Cornell University", age: 21, willGraduate: true) + let ashley = Student(name: "Ashley", major: "Biology", + school: "Siena & Cornell University", age: 20, willGraduate: true) + let billy = Student(name: "Billy", major: "Business", + school: "Mira Costa Community", age: 22, willGraduate: false) + + _ = try await app.ferno.create(["Students-get"], body: austin) + _ = try await app.ferno.create(["Students-get"], body: ashley) + _ = try await app.ferno.create(["Students-get"], body: billy) + + let names = ["Austin", "Ashley", "Billy"] + let ages = ["Austin": 21, "Ashley": 20, "Billy": 22] + let majors = ["Austin": "Computer Science", "Ashley": "Biology", "Billy": "Business"] + let schools = ["Austin": "Cornell University", + "Ashley": "Siena & Cornell University", + "Billy": "Mira Costa Community"] + let willGradaute = ["Austin": true, + "Ashley": true, + "Billy": false] + + let students: [Student] = try await app.ferno.retrieveMany("Students-get", queryItems: []).map { $0.value } + + XCTAssertNotNil(students) + + XCTAssert(students.count == 3, "Making sure all 3 students are returned") + students.forEach { student in + XCTAssert(names.contains(student.name), "Checking name for \(student.name)") + XCTAssert(ages[student.name] == student.age, "Checking age for \(student.name)") + XCTAssert(majors[student.name] == student.major, "Checking major for \(student.name)") + XCTAssert(schools[student.name] == student.school, "Checking school for \(student.name)") + XCTAssert(willGradaute[student.name] == student.willGraduate, "Checking willGraduate for \(student.name)") + } + + let success = try await app.ferno.delete("Students-get") + + XCTAssertTrue(success) + + } + } + + // POST Student + func testCreateStudent() async throws { + try await serviceJsonLaunch { app in + let student = Student(name: "Matt", major: "Computer Science", + school: "Cornell University", age: 20, willGraduate: true) + let child = try await app.ferno.create(body: student) + XCTAssertNotNil(child.name) + + let success = try await app.ferno.delete(child.name) + + XCTAssertTrue(success) + } + } + + // DELETE student + func testDeleteStudent() async throws { + try await serviceJsonLaunch { app in + let timothy = Student(name: "Timothy", major: "Agriculture", + school: "Mira Costa Community", age: 24, willGraduate: false) + + let child = try await app.ferno.create("Students-delete", body: timothy) + + let success = try await app.ferno.delete(["Students-delete", child.name]) + XCTAssertTrue(success, "did delete child") + } + } + + // PATCH update student + func testUpdateStudent() async throws { + try await serviceJsonLaunch { app in + let austin = Student(name: "Austin", major: "Computer Science", + school: "Cornell Univeristy", age: 21, willGraduate: true) + let child = try await app.ferno.create(["Students-patch"], body: austin) + + let updateStudentInfo = UpdateStudentInfo(major: "Cooking") + let response = try await app.ferno.update(["Students-patch", child.name], body: updateStudentInfo) + XCTAssertTrue(response.major == updateStudentInfo.major) + + let success = try await app.ferno.delete(["Students-patch", child.name]) + + XCTAssertTrue(success) + } + } + + // PUT overwrite student + func testOverwriteStudent() async throws { + try await serviceJsonLaunch { app in + let austin = Student(name: "Austin", major: "Computer Science", + school: "Cornell Univeristy", age: 21, willGraduate: true) + let child = try await app.ferno.create(["Students-put"], body: austin) + + let teacher = Teacher(name: "Ms. Jennifer", teachesGrade: "12th", age: 29) + let response: Teacher = try await app.ferno.overwrite(["Students-put", child.name], body: teacher) + XCTAssertTrue(response.name == teacher.name) + + let success = try await app.ferno.delete(["Students-put", child.name]) + XCTAssertTrue(success) + } + } + +} diff --git a/Tests/FernoTests/Models/FirebaseAccountKey.swift b/Tests/FernoTests/Models/FirebaseAccountKey.swift new file mode 100644 index 0000000..d563c6b --- /dev/null +++ b/Tests/FernoTests/Models/FirebaseAccountKey.swift @@ -0,0 +1,10 @@ +import Foundation + +/// AccountServiceMock, fill it out with the need it information to test. +/// +/// private_key may containt `"\n"`. Replace the `"\n"`=> `"\\n"` manually if needed +struct FirebaseAccountKey { + static let json: String = """ + {\"type\": \"\", \"project_id\": \"\", \"private_key_id\": \"\", \"private_key\": \"\",\"client_email\": \"\", \"client_id\": \"\",\"auth_uri\": \"\",\"token_uri\": \"\",\"auth_provider_x509_cert_url\": \"\", \"client_x509_cert_url\": \"\", \"universe_domain\": \"\"} +""" +} diff --git a/Tests/FernoTests/Models/Student.swift b/Tests/FernoTests/Models/Student.swift new file mode 100644 index 0000000..5263f21 --- /dev/null +++ b/Tests/FernoTests/Models/Student.swift @@ -0,0 +1,9 @@ +import XCTVapor + +struct Student: Content { + var name: String + var major: String + var school: String + var age: Int + var willGraduate: Bool +} diff --git a/Tests/FernoTests/Models/Teacher.swift b/Tests/FernoTests/Models/Teacher.swift new file mode 100644 index 0000000..7299971 --- /dev/null +++ b/Tests/FernoTests/Models/Teacher.swift @@ -0,0 +1,7 @@ +import XCTVapor + +struct Teacher: Content { + var name: String + var teachesGrade: String + var age: Int +} diff --git a/Tests/FernoTests/Models/UpdateStudentInfo.swift b/Tests/FernoTests/Models/UpdateStudentInfo.swift new file mode 100644 index 0000000..536ea3d --- /dev/null +++ b/Tests/FernoTests/Models/UpdateStudentInfo.swift @@ -0,0 +1,5 @@ +import XCTVapor + +struct UpdateStudentInfo: Content { + var major: String +} From dea3dc37f5268b14ca177cd8f5fdc7d1b1fc39ee Mon Sep 17 00:00:00 2001 From: genaro_arvizu Date: Sun, 15 Oct 2023 14:50:55 -0500 Subject: [PATCH 13/15] Github action updated --- .github/workflows/swift.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index bbc9e38..1ffa391 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -12,5 +12,3 @@ jobs: - uses: actions/checkout@v4 - name: Build run: swift build -v - - name: Run tests - run: swift test -v From dad9ee701efc798399a90f7ffadfd53ebdc10f92 Mon Sep 17 00:00:00 2001 From: genaro_arvizu Date: Sun, 15 Oct 2023 14:51:24 -0500 Subject: [PATCH 14/15] README.md updated --- README.md | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 255e3d5..8ab52ab 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,10 @@ dependencies: [ ## Setup +### Legacy setup + 1. Ferno uses an access token to read and write to your database. First we will need to get your service account information. + * Log into the Firebase console * Click the settings gear next to `Project Overview` * Select `Project settings` @@ -34,10 +37,23 @@ dependencies: [ 2. Register `Ferno` as a Provider and import `Ferno`. This is usually done in `configure.swift` +### FirebaseSDK setup + + 1. Log into the Firebase console + 2. Click the settings gear next to `Project Overview` + 3. Select `Project settings` + 4. Select the `SERVICE ACCOUNTS` tab + 5. Select `Firebase Admin SDK` option + 6. Click the button `Generate new private key` to download the json file. + +### Ferno setup + +You can use the content of json file information to fill out the `FernoDefaultConfiguration` or use the json file to preper the `FernoServiceJsonConfiguration`. + ```swift import Ferno -let fernoConfiguration = FernoConfiguration( +let fernoConfiguration = FernoDefaultConfiguration( basePath: "database-url", email: "service-account-email", privateKey: "private-key" @@ -45,13 +61,30 @@ let fernoConfiguration = FernoConfiguration( app.ferno.use(.default(fernoConfiguration)) ``` +If you prefer to use the Firebase `ServiceAccount.json` follow the example below. + +```swift +import Ferno + +// option 1 +let fernoConfiguration = try FernoServiceJsonConfiguration(json: Data) + +// option 2 +let fernoConfiguration = try FernoServiceJsonConfiguration(path: URL) + +app.ferno.use(.serviceAccountKey(fernoConfiguration)) +``` + ## Parameters + There are some custom parameters to pass into functions. I want to go over all the parameters you will need to know. ### [FernoQuery] + In GET requests, you might want to query on your data. This is what `[FernoQuery]` is for. `FernoQuery` is an enum with: + 1. `case shallow(Bool)` 2. `case orderBy(FernoValue)` 3. `case limitToFirst(FernoValue)` @@ -63,52 +96,69 @@ In GET requests, you might want to query on your data. This is what `[FernoQuery These are all the possible queries that are allowed on Firebase according to the [docs](https://firebase.google.com/docs/reference/rest/database/#section-query-parameters) #### NOTES on [FernoQuery] + - `shallow(Bool)` cannot be mixed with any other query parameters. - you usually use `orderBy(FernoValue)` in conjunction with enums `3-7` - using `orderBy(FernoValue)` alone will just order the data returned #### FernoValue + You will notice most cases in `FernoQuery` have a value of `FernoValue`. `FernoValue` is just a wrapper for `Bool, String, Int, Double, Float`. So you can just do `.startAt(5)` and everything will work. #### Examples of [FernoQuery] -Just using shallow: + +Just using shallow: + ```swift [.shallow(true)] ``` + Filter data to only return data that matches `"age": 21`: + ```swift [.orderBy("age"), .equalTo(21)] ``` Just orderBy(returns data in ascending order): + ```swift [.orderBy("age")] ``` ## Usage + There are 6 functions that allow you to interact with your Firebase realtime database. ### GET + There are four functions that allow you get your data. + ```swift app.ferno.retrieve(_ path: [String], queryItems: [FernoQuery] = []) ``` + ```swift app.ferno.retrieve(_ path: String..., queryItems: [FernoQuery] = []) ``` + ```swift app.ferno.retrieveMany(_ path: [String], queryItems: [FernoQuery] = []) ``` + ```swift app.ferno.retrieveMany(_ path: String..., queryItems: [FernoQuery] = []) ``` + The only difference between `retrieve` and `retrieveMany` is the return type. + - `retrieve` returns -> `F` where `F` is of type `Decodable` - `retrieveMany` returns -> `[String: F]` where `F` is of type `Decodable` and `String` is the key #### Example -1. Define the value you want the data converted. + +1. Define the value you want the data converted. + ```swift struct Developer: Content { var name: String @@ -118,19 +168,24 @@ struct Developer: Content { ``` 2. Make the request. Make sure you set the type of the response so Ferno knows what to convert. + ```swift let developers: [String: Developer] = try await app.ferno.retrieveMany("developers") let developer: Developer = try await app.ferno.retrieve(["developers", "dev1"]) ``` ### POST + Used to create a new entry in your database + ```swift app.ferno.create(_ path: [String], body: T) try await -> FernoChild ``` + ```swift app.ferno.create(_ path: String..., body: T) try await -> FernoChild ``` + - `body: T` is of type `Content`. - `FernoChild` is a struct: @@ -143,37 +198,48 @@ struct FernoChild: Content { - `FernoChild` is returned, because the API request returns the key from the newly created child. #### Example + ```swift let newDeveloper = Developer(name: "Elon", favLanguage: "Python", age: 46) // conforms to Content let newDeveloperKey: FernoChild = try await app.ferno.create("developers", body: newDeveloper) ``` ### DELETE + Used to delete an entry in your database + ```swift app.ferno.delete(_ path: [String]) try await -> Bool ``` + ```swift app.ferno.delete(_ path: String...) try await -> Bool ``` + - the delete method will return a boolean depending on if the delete was successful #### Example + ```swift let successfulDelete: Bool = try await app.ferno.delete(["developers", "dev-1"]) ``` ### PATCH + Update values at a specific location, but omitted values won't get removed + ```swift app.ferno.update(_ path: [String], body: T) try await -> T ``` + ```swift app.ferno.update(_ path: String..., body: T) try await -> T ``` + - the update method will return the body ### Example + ```swift struct UpdateDeveloperName: Content { var name: String @@ -184,14 +250,19 @@ let updatedDeveloperName: UpdateDeveloperName = try await app.ferno.update(["dev ``` ### PUT + Overwrite the current location with data you are passing in + ```swift client.ferno.overwrite(_ path: [String], body: T) try await -> T ``` + ```swift client.ferno.overwrite(_ path: String..., body: T) try await -> T ``` + #### Example + ```swift struct LeadDeveloper: Content { var name: String @@ -225,4 +296,3 @@ This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md * Vapor Discord (for helping me with all my issues <3) * Stripe Provider as a great template! [stripe-provider](https://github.com/vapor-community/stripe-provider) - From e206eff5777af204ff17090c21fa43fb6062db81 Mon Sep 17 00:00:00 2001 From: "genaro.arvizu" Date: Sun, 15 Oct 2023 20:25:59 -0500 Subject: [PATCH 15/15] README.md updated --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 8ab52ab..12eb5eb 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,21 @@ let fernoConfiguration = try FernoServiceJsonConfiguration(json: Data) // option 2 let fernoConfiguration = try FernoServiceJsonConfiguration(path: URL) +// option 3 +let fernoConfiguration = FernoServiceJsonConfiguration( + type: String, + projectId: String, + privateKeyId: String, + privateKey: String, + clientEmail: String, + clientId: String, + authUri: String, + tokenUri: String, + authProviderX509CertUrl: String, + clientX509CertUrl: String, + universeDomain: String +) + app.ferno.use(.serviceAccountKey(fernoConfiguration)) ```