From 113ab9d84dcf1de4f642ed9976d54f525540e928 Mon Sep 17 00:00:00 2001 From: Jared Henderson Date: Fri, 25 Aug 2023 12:09:10 -0400 Subject: [PATCH] api: add filter version to Device model --- api/Sources/Api/Configure/migrations.swift | 1 + api/Sources/Api/Database/Column.swift | 2 ++ .../Migrations/014_DeviceFilterVersion.swift | 22 +++++++++++++++++++ api/Sources/Api/Models/Admin/Device.swift | 3 +++ api/Sources/Api/Models/Models+Duet.swift | 3 ++- api/Sources/Api/Models/Models+DuetSQL.swift | 3 +++ .../Api/PairQL/MacApp/Resolvers/CheckIn.swift | 12 +++++++--- .../CheckInResolverTests.swift | 5 ++++- duet/Sources/DuetSQL/Postgres.swift | 7 ++++++ duet/Tests/DuetSQLTests/SQLTests.swift | 2 +- duet/Tests/DuetSQLTests/Thing.swift | 7 ++++++ gertie/Sources/Gertie/Semver.swift | 2 ++ 12 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 api/Sources/Api/Database/Migrations/014_DeviceFilterVersion.swift diff --git a/api/Sources/Api/Configure/migrations.swift b/api/Sources/Api/Configure/migrations.swift index 4539a2f7..3a098619 100644 --- a/api/Sources/Api/Configure/migrations.swift +++ b/api/Sources/Api/Configure/migrations.swift @@ -17,5 +17,6 @@ extension Configure { app.migrations.add(DeviceRefactor()) app.migrations.add(AddReleaseNotes()) app.migrations.add(DeviceIdForeignKey()) + app.migrations.add(DeviceFilterVersion()) } } diff --git a/api/Sources/Api/Database/Column.swift b/api/Sources/Api/Database/Column.swift index b4a53a1e..f8c93e77 100644 --- a/api/Sources/Api/Database/Column.swift +++ b/api/Sources/Api/Database/Column.swift @@ -9,6 +9,7 @@ public enum ColumnType { case bigint case boolean case jsonb + case varchar(Int) case custom(String) case timestampWithTimezone @@ -25,6 +26,7 @@ public enum ColumnType { case .bigint: return "bigint" case .boolean: return "boolean" case .jsonb: return "jsonb" + case .varchar(let length): return "varchar(\(length))" case .timestampWithTimezone: return "timestamp with time zone" case .custom(let type): return type } diff --git a/api/Sources/Api/Database/Migrations/014_DeviceFilterVersion.swift b/api/Sources/Api/Database/Migrations/014_DeviceFilterVersion.swift new file mode 100644 index 00000000..f3036ad2 --- /dev/null +++ b/api/Sources/Api/Database/Migrations/014_DeviceFilterVersion.swift @@ -0,0 +1,22 @@ +import FluentSQL + +struct DeviceFilterVersion: GertieMigration { + func up(sql: SQLDatabase) async throws { + try await sql.addColumn( + Device.M14.filterVersion, + on: Device.M3.self, + type: .varchar(12), + nullable: true + ) + } + + func down(sql: SQLDatabase) async throws { + try await sql.dropColumn(Device.M14.filterVersion, on: Device.M3.self) + } +} + +extension Device { + enum M14 { + static let filterVersion = FieldKey("filter_version") + } +} diff --git a/api/Sources/Api/Models/Admin/Device.swift b/api/Sources/Api/Models/Admin/Device.swift index 0ad7d74c..650bdadc 100644 --- a/api/Sources/Api/Models/Admin/Device.swift +++ b/api/Sources/Api/Models/Admin/Device.swift @@ -8,6 +8,7 @@ final class Device: Codable { var modelIdentifier: String var serialNumber: String var appReleaseChannel: ReleaseChannel + var filterVersion: Semver? var createdAt = Date() var updatedAt = Date() @@ -19,6 +20,7 @@ final class Device: Codable { adminId: Admin.Id, customName: String? = nil, appReleaseChannel: ReleaseChannel = .stable, + filterVersion: Semver? = nil, modelIdentifier: String, serialNumber: String ) { @@ -28,6 +30,7 @@ final class Device: Codable { self.appReleaseChannel = appReleaseChannel self.modelIdentifier = modelIdentifier self.serialNumber = serialNumber + self.filterVersion = filterVersion } } diff --git a/api/Sources/Api/Models/Models+Duet.swift b/api/Sources/Api/Models/Models+Duet.swift index 0c039955..ee432f69 100644 --- a/api/Sources/Api/Models/Models+Duet.swift +++ b/api/Sources/Api/Models/Models+Duet.swift @@ -8,7 +8,7 @@ extension Model { try await Current.db.update(self) } - static func find(_ id: Tagged) async throws -> Self { + static func find(_ id: Tagged) async throws -> Self { try await Current.db.query(Self.self).byId(id).first() } @@ -175,6 +175,7 @@ extension Device { case modelIdentifier case serialNumber case appReleaseChannel + case filterVersion case createdAt case updatedAt } diff --git a/api/Sources/Api/Models/Models+DuetSQL.swift b/api/Sources/Api/Models/Models+DuetSQL.swift index 145f8a3b..2061fa5a 100644 --- a/api/Sources/Api/Models/Models+DuetSQL.swift +++ b/api/Sources/Api/Models/Models+DuetSQL.swift @@ -251,6 +251,8 @@ extension Device: Model { return .string(serialNumber) case .appReleaseChannel: return .enum(appReleaseChannel) + case .filterVersion: + return .varchar(filterVersion?.string) case .createdAt: return .date(createdAt) case .updatedAt: @@ -266,6 +268,7 @@ extension Device: Model { .modelIdentifier: .string(modelIdentifier), .serialNumber: .string(serialNumber), .appReleaseChannel: .enum(appReleaseChannel), + .filterVersion: .varchar(filterVersion?.string), .createdAt: .currentTimestamp, .updatedAt: .currentTimestamp, ] diff --git a/api/Sources/Api/PairQL/MacApp/Resolvers/CheckIn.swift b/api/Sources/Api/PairQL/MacApp/Resolvers/CheckIn.swift index efb6714e..a179d293 100644 --- a/api/Sources/Api/PairQL/MacApp/Resolvers/CheckIn.swift +++ b/api/Sources/Api/PairQL/MacApp/Resolvers/CheckIn.swift @@ -6,14 +6,20 @@ extension CheckIn: Resolver { async let v1 = RefreshRules.resolve(with: .init(appVersion: input.appVersion), in: context) async let admin = context.user.admin() async let userDevice = context.userDevice() - let channel = try await userDevice.adminDevice().appReleaseChannel + let adminDevice = try await userDevice.adminDevice() + let channel = adminDevice.appReleaseChannel + async let latestRelease = LatestAppVersion.resolve( with: .init(releaseChannel: channel, currentVersion: input.appVersion), in: .init(requestId: context.requestId, dashboardUrl: context.dashboardUrl) ) - // TODO: use `input.filterVersion` - // https://github.com/gertrude-app/project/issues/185 + if let filterVersionSemver = input.filterVersion, + let filterVersion = Semver(filterVersionSemver), + filterVersion != adminDevice.filterVersion { + adminDevice.filterVersion = filterVersion + try await adminDevice.save() + } return Output( adminAccountStatus: try await admin.accountStatus, diff --git a/api/Tests/ApiTests/MacappPairResolvers/CheckInResolverTests.swift b/api/Tests/ApiTests/MacappPairResolvers/CheckInResolverTests.swift index 7c063114..e317881f 100644 --- a/api/Tests/ApiTests/MacappPairResolvers/CheckInResolverTests.swift +++ b/api/Tests/ApiTests/MacappPairResolvers/CheckInResolverTests.swift @@ -16,7 +16,7 @@ final class CheckInResolverTests: ApiTestCase { }).withDevice() let output = try await CheckIn.resolve( - with: .init(appVersion: "1.0.0", filterVersion: nil), + with: .init(appVersion: "1.0.0", filterVersion: "3.3.3"), in: user.context ) expect(output.userData.name).toBe(user.name) @@ -24,6 +24,9 @@ final class CheckInResolverTests: ApiTestCase { expect(output.userData.screenshotsEnabled).toBeTrue() expect(output.userData.screenshotFrequency).toEqual(376) expect(output.userData.screenshotSize).toEqual(1081) + + let device = try await Device.find(user.adminDevice.id) + expect(device.filterVersion).toEqual("3.3.3") } func testCheckIn_OtherProps() async throws { diff --git a/duet/Sources/DuetSQL/Postgres.swift b/duet/Sources/DuetSQL/Postgres.swift index c4cf704d..2833244e 100644 --- a/duet/Sources/DuetSQL/Postgres.swift +++ b/duet/Sources/DuetSQL/Postgres.swift @@ -45,6 +45,7 @@ public enum Postgres { public enum Data { case id(UUIDIdentifiable) case string(String?) + case varchar(String?) case intArray([Int]?) case int(Int?) case int64(Int64?) @@ -64,6 +65,8 @@ public enum Postgres { return false case .string(let wrapped): return wrapped == nil + case .varchar(let wrapped): + return wrapped == nil case .intArray(let wrapped): return wrapped == nil case .int(let wrapped): @@ -91,6 +94,8 @@ public enum Postgres { switch self { case .string: return "text" + case .varchar: + return "varchar" case .int, .int64, .double, .float: return "numeric" case .intArray: @@ -118,6 +123,8 @@ public enum Postgres { return nullable(enumVal?.rawValue) case .string(let string): return nullable(string) + case .varchar(let string): + return nullable(string) case .int64(let int64): return nullable(int64) case .int(let int): diff --git a/duet/Tests/DuetSQLTests/SQLTests.swift b/duet/Tests/DuetSQLTests/SQLTests.swift index 0b47003a..998e656f 100644 --- a/duet/Tests/DuetSQLTests/SQLTests.swift +++ b/duet/Tests/DuetSQLTests/SQLTests.swift @@ -359,7 +359,7 @@ final class SqlTests: XCTestCase { let query = """ UPDATE "things" - SET "int" = $1, "updated_at" = $2, "bool" = $3, "optional_string" = $4, "custom_enum" = $5, "optional_custom_enum" = $6, "string" = $7, "optional_int" = $8; + SET "int" = $1, "updated_at" = $2, "bool" = $3, "optional_string" = $4, "custom_enum" = $5, "optional_custom_enum" = $6, "version" = $7, "string" = $8, "optional_int" = $9; """ expect(statement.query).toEqual(query) diff --git a/duet/Tests/DuetSQLTests/Thing.swift b/duet/Tests/DuetSQLTests/Thing.swift index eaa63631..897d81aa 100644 --- a/duet/Tests/DuetSQLTests/Thing.swift +++ b/duet/Tests/DuetSQLTests/Thing.swift @@ -9,6 +9,7 @@ final class Thing: Codable { var id: Id var string: String + var version: String var int: Int var bool: Bool var customEnum: CustomEnum @@ -22,6 +23,7 @@ final class Thing: Codable { init( id: Id = .init(), string: String = "foo", + version: String = "1.0.0", int: Int = 123, bool: Bool = true, customEnum: CustomEnum = .foo, @@ -34,6 +36,7 @@ final class Thing: Codable { ) { self.id = id self.string = string + self.version = version self.int = int self.bool = bool self.customEnum = customEnum @@ -57,6 +60,7 @@ extension Thing { [ .id: .id(self), .string: .string(string), + .version: .varchar(version), .int: .int(int), .bool: .bool(bool), .optionalInt: .int(optionalInt), @@ -73,6 +77,7 @@ extension Thing { enum CodingKeys: String, CodingKey, CaseIterable { case id case string + case version case int case bool case optionalInt @@ -95,6 +100,8 @@ extension Thing: Model { return .id(self) case .string: return .string(string) + case .version: + return .varchar(version) case .int: return .int(int) case .bool: diff --git a/gertie/Sources/Gertie/Semver.swift b/gertie/Sources/Gertie/Semver.swift index 05ac9ec0..3f7d1dbe 100644 --- a/gertie/Sources/Gertie/Semver.swift +++ b/gertie/Sources/Gertie/Semver.swift @@ -168,6 +168,8 @@ extension Semver: LosslessStringConvertible { } return result } + + public var string: String { description } } extension Semver: ExpressibleByStringLiteral {