From cada6302982b733f7ef9092c6a89d7d4ebb4bf37 Mon Sep 17 00:00:00 2001 From: Andrew Glaze Date: Thu, 15 May 2025 22:14:00 -0400 Subject: [PATCH] impl Account Table --- .../Controllers/InfodeskController.swift | 1 - .../Controllers/OpenApi/AuthController.swift | 160 ++++++++++++++++++ .../Controllers/OpenApi/UtilController.swift | 14 ++ .../Controllers/OpenApiController.swift | 14 ++ Sources/stella/DTOs/TodoDTO.swift | 17 -- Sources/stella/Migrations/CreateAccount.swift | 21 +++ Sources/stella/Migrations/CreateTodo.swift | 14 -- Sources/stella/Models/Account.swift | 50 ++++++ Sources/stella/Models/Todo.swift | 29 ---- Sources/stella/configure.swift | 2 +- Sources/stella/entrypoint.swift | 8 +- Sources/stella/{routes => }/routes.swift | 6 +- Sources/stella/routes/openapi.swift | 140 --------------- Sources/stella/util.swift | 3 + 14 files changed, 268 insertions(+), 211 deletions(-) create mode 100644 Sources/stella/Controllers/OpenApi/AuthController.swift create mode 100644 Sources/stella/Controllers/OpenApi/UtilController.swift create mode 100644 Sources/stella/Controllers/OpenApiController.swift delete mode 100644 Sources/stella/DTOs/TodoDTO.swift create mode 100644 Sources/stella/Migrations/CreateAccount.swift delete mode 100644 Sources/stella/Migrations/CreateTodo.swift create mode 100644 Sources/stella/Models/Account.swift delete mode 100644 Sources/stella/Models/Todo.swift rename Sources/stella/{routes => }/routes.swift (74%) delete mode 100644 Sources/stella/routes/openapi.swift create mode 100644 Sources/stella/util.swift diff --git a/Sources/stella/Controllers/InfodeskController.swift b/Sources/stella/Controllers/InfodeskController.swift index 2129ff9..f541c13 100644 --- a/Sources/stella/Controllers/InfodeskController.swift +++ b/Sources/stella/Controllers/InfodeskController.swift @@ -1,4 +1,3 @@ -import Fluent import Vapor struct InfodeskController: RouteCollection { diff --git a/Sources/stella/Controllers/OpenApi/AuthController.swift b/Sources/stella/Controllers/OpenApi/AuthController.swift new file mode 100644 index 0000000..8be0eae --- /dev/null +++ b/Sources/stella/Controllers/OpenApi/AuthController.swift @@ -0,0 +1,160 @@ +import Vapor +import Fluent + +struct AuthController: RouteCollection { + func boot(routes: any RoutesBuilder) throws { + routes.post("v4", "device", "accessToken", "create", use: self.createAccessToken) + routes.post("v3", "agreement", "getForLogin", use: self.loginAgreement) + routes.post("v4", "auth", "loginDevice", use: self.loginDevice) + } + + @Sendable + func createAccessToken(req: Request) async throws -> AccessTokenRes { + AccessTokenRes( + accessToken: "fwPla7fQ8ty9+DZT/lD//uWZD4uD6C4lD6gGIIZTLKRTQ52/SLCRmk/370jcWGs+e+1iSoZtL7lj8ov9B0/jHmijH4nsHPQT6pchaQM1M9mtwYNQq0BWhVr9hF0jjCK/a5LIVd1kBac/Gemv29WKEDKSrUS9HxxUigoPRwtOy8m+oDj9FmDJZ+rzqWCc0QjES4Ky0fTpXZ7ESoguDzNmRtW3FYr+OFexw8wBPlwiC4w=", + expiryTime: Int(Date.init(timeIntervalSinceNow: .init(0)).timeIntervalSince1970) + ) + } + + @Sendable + func loginAgreement(req: Request) async throws -> String { + let body = try req.content.decode(LoginAgreementReq.self, as: .json) + + return #""" + { + "adAgreementStatus": "n", + "agreement": { + "E001": "n", + "E002": "n", + "E006": "n", + "N002": "n", + "N003": "n", + "timestamp": "\#(Int(Date().timeIntervalSince1970) * 1000)" + }, + "agreementPopup": "n", + "appId": "\#(body.appId)", + "appName": "World Flipper (NA)", + "context": "login", + "country": "\#(body.country)", + "firstAgreement": "n", + "idpCode": "\#(body.idpCode)", + "idpId": "6076008646", + "informationSecurityCountry": "kr", + "kakaoSyncAgreementGetSet": "n", + "kakaoSyncStatus": "off", + "kakaogameSdkVer": "3.0", + "lang": "\#(body.lang)", + "partnerId": 825, + "partnerName": "주식회사 카카오게임즈", + "plusFriendStatusInfo": null, + "policyApplyTime": 1630854000000 + } + """# + } + + @Sendable + func loginDevice(req: Request) async throws -> Response { + let body = try req.content.decode(LoginDeviceReq.self, as: .json) + + let idpAlias = generateIdpAlias(appId: body.appId, deviceId: body.deviceId, serialNo: body.serialNo) + let idpId = body.whiteKey + var account: Account? = nil + + if let rawAccountId = req.headers["playerId"].first, let accountId = Int(rawAccountId) { + if let existingAccount = try await Account.query(on: req.db).filter(\.$id == accountId).first() { + account = existingAccount + } + } else if let existingAccount = try await Account.query(on: req.db).filter(\.$idpId == idpId).first() { + account = existingAccount + } else { + let account = Account(appId: body.appId, idpAlias: idpAlias, idpCode: "zd3", idpId: idpId, status: "normal") + try await account.create(on: req.db) + } + + guard let account = account else { + return Response(status: .badRequest, body: "{\"error\": \"Bad Request\", \"message\": \"Invalid playerId provided.\"}") + } + return Response(status: .notImplemented) + } +} + +struct AccessTokenRes: Content { + let accessToken: String + let expiryTime: Int +} + +struct LoginAgreementReq: Content { + let deviceId: String + let os: String + let country: String + let lang: String + let appId: String + let idpCode: String + let serialNo: String + let idpId: String +} + +struct LoginDeviceReq: Content { + let lang: String + let clientTime: Int + let deviceId: String + let serialNo: String + let country: String + let whiteKey: String + let market: String + let appSecret: String + let deviceAppKey: String + let sdkVer: String + let appVer: String + let os: String + let loginType: String + let accessToken: String + let resume: Bool + let osVer: String + let appId: String + let deviceModel: String + let network: String + let isIosAppOnMac: Bool + let adid: String + let timezoneOffset: Int + let fields: [String] + let telecom: String +} + +struct LoginDeviceRes: Content { + let zatExpiryTime: Int + let zrtExpiryTime: Int + let firstLogin: Bool + let externalToken: String + let zat: String + let zrt: String + let player: Player +} + +struct Player: Content { + let idpId: String + let appId: String + let lang: String + let playerId: String + let agreement: AgreementResponse + let pushOption: PushOptionResponse + let lastLoginTime: Int + let regTime: Int + let idpAlias: String + let firstLoginTime: Int + let status: String +} + +struct AgreementResponse: Content { + let E001: String + let E002: String + let E006: String + let N002: String + let N003: String + let timestamp: String +} + +struct PushOptionResponse: Content { + let night: String + let player: String +} diff --git a/Sources/stella/Controllers/OpenApi/UtilController.swift b/Sources/stella/Controllers/OpenApi/UtilController.swift new file mode 100644 index 0000000..1a151e8 --- /dev/null +++ b/Sources/stella/Controllers/OpenApi/UtilController.swift @@ -0,0 +1,14 @@ +import Vapor + +struct UtilController: RouteCollection { + func boot(routes: any RoutesBuilder) throws { + let group = routes.grouped("v3", "util") + + group.post("country", "get", use: self.getCountry) + } + + @Sendable + func getCountry(req: Request) async throws -> String { + "{\"country\": \"us\"}" + } +} diff --git a/Sources/stella/Controllers/OpenApiController.swift b/Sources/stella/Controllers/OpenApiController.swift new file mode 100644 index 0000000..fda6b06 --- /dev/null +++ b/Sources/stella/Controllers/OpenApiController.swift @@ -0,0 +1,14 @@ +import Vapor + +struct OpenApiController: RouteCollection { + func boot(routes: any RoutesBuilder) throws { + let group = routes.grouped("openapi", "service") + try group.register(collection: UtilController()) + try group.register(collection: AuthController()) + + group.post("v3", "log", "writeSdkBasicLog") { req in + req.logger.log(level: .debug, .init(stringLiteral: req.body.string ?? "")) + return Response() + } + } +} diff --git a/Sources/stella/DTOs/TodoDTO.swift b/Sources/stella/DTOs/TodoDTO.swift deleted file mode 100644 index c35ba33..0000000 --- a/Sources/stella/DTOs/TodoDTO.swift +++ /dev/null @@ -1,17 +0,0 @@ -import Fluent -import Vapor - -struct TodoDTO: Content { - var id: UUID? - var title: String? - - func toModel() -> Todo { - let model = Todo() - - model.id = self.id - if let title = self.title { - model.title = title - } - return model - } -} diff --git a/Sources/stella/Migrations/CreateAccount.swift b/Sources/stella/Migrations/CreateAccount.swift new file mode 100644 index 0000000..e76459e --- /dev/null +++ b/Sources/stella/Migrations/CreateAccount.swift @@ -0,0 +1,21 @@ +import Fluent + +struct CreateAccount: AsyncMigration { + func prepare(on database: any Database) async throws { + try await database.schema("accounts") + .field("id", .int, .identifier(auto: true)) + .field("app_id", .string, .required) + .field("first_login_time", .date, .required) + .field("idp_alias", .string, .required) + .field("idp_code", .string, .required) + .field("idp_id", .string, .required) + .field("reg_time", .date, .required) + .field("last_login_time", .date, .required) + .field("status", .string, .required) + .create() + } + + func revert(on database: any Database) async throws { + try await database.schema("accounts").delete() + } +} diff --git a/Sources/stella/Migrations/CreateTodo.swift b/Sources/stella/Migrations/CreateTodo.swift deleted file mode 100644 index 7466919..0000000 --- a/Sources/stella/Migrations/CreateTodo.swift +++ /dev/null @@ -1,14 +0,0 @@ -import Fluent - -struct CreateTodo: AsyncMigration { - func prepare(on database: any Database) async throws { - try await database.schema("todos") - .id() - .field("title", .string, .required) - .create() - } - - func revert(on database: any Database) async throws { - try await database.schema("todos").delete() - } -} diff --git a/Sources/stella/Models/Account.swift b/Sources/stella/Models/Account.swift new file mode 100644 index 0000000..86cbb8c --- /dev/null +++ b/Sources/stella/Models/Account.swift @@ -0,0 +1,50 @@ +import Fluent +import struct Foundation.UUID + +/// Property wrappers interact poorly with `Sendable` checking, causing a warning for the `@ID` property +/// It is recommended you write your model with sendability checking on and then suppress the warning +/// afterwards with `@unchecked Sendable`. +final class Account: Model, @unchecked Sendable { + static let schema = "accounts" + + @ID(custom: "id", generatedBy: .database) + var id: Int? + + @Field(key: "app_id") + var appId: String + + @Field(key: "first_login_time") + var firstLogin: Date + + @Field(key: "reg_time") + var regDate: Date + + @Field(key: "last_login_time") + var lastLogin: Date + + @Field(key: "idp_alias") + var idpAlias: String + + @Field(key: "idp_code") + var idpCode: String + + @Field(key: "idp_id") + var idpId: String + + @Field(key: "status") + var status: String + + init() { } + + init(id: Int? = nil, appId: String, idpAlias: String, idpCode: String, idpId: String, status: String) { + self.id = id + self.appId = appId + self.firstLogin = Date.now + self.idpAlias = idpAlias + self.idpCode = idpCode + self.idpId = idpId + self.regDate = Date.now + self.lastLogin = Date.now + self.status = status + } +} diff --git a/Sources/stella/Models/Todo.swift b/Sources/stella/Models/Todo.swift deleted file mode 100644 index de1837d..0000000 --- a/Sources/stella/Models/Todo.swift +++ /dev/null @@ -1,29 +0,0 @@ -import Fluent -import struct Foundation.UUID - -/// Property wrappers interact poorly with `Sendable` checking, causing a warning for the `@ID` property -/// It is recommended you write your model with sendability checking on and then suppress the warning -/// afterwards with `@unchecked Sendable`. -final class Todo: Model, @unchecked Sendable { - static let schema = "todos" - - @ID(key: .id) - var id: UUID? - - @Field(key: "title") - var title: String - - init() { } - - init(id: UUID? = nil, title: String) { - self.id = id - self.title = title - } - - func toDTO() -> TodoDTO { - .init( - id: self.id, - title: self.$title.value - ) - } -} diff --git a/Sources/stella/configure.swift b/Sources/stella/configure.swift index 4da269c..97c1364 100644 --- a/Sources/stella/configure.swift +++ b/Sources/stella/configure.swift @@ -10,7 +10,7 @@ public func configure(_ app: Application) async throws { app.databases.use(DatabaseConfigurationFactory.sqlite(.file("db.sqlite")), as: .sqlite) - app.migrations.add(CreateTodo()) + app.migrations.add(CreateAccount()) app.http.server.configuration.hostname = "0.0.0.0" app.http.server.configuration.port = 8000 diff --git a/Sources/stella/entrypoint.swift b/Sources/stella/entrypoint.swift index ad283b4..54568f9 100644 --- a/Sources/stella/entrypoint.swift +++ b/Sources/stella/entrypoint.swift @@ -8,16 +8,16 @@ enum Entrypoint { static func main() async throws { var env = try Environment.detect() try LoggingSystem.bootstrap(from: &env) - + let app = try await Application.make(env) // This attempts to install NIO as the Swift Concurrency global executor. // You can enable it if you'd like to reduce the amount of context switching between NIO and Swift Concurrency. // Note: this has caused issues with some libraries that use `.wait()` and cleanly shutting down. // If enabled, you should be careful about calling async functions before this point as it can cause assertion failures. - // let executorTakeoverSuccess = NIOSingletons.unsafeTryInstallSingletonPosixEventLoopGroupAsConcurrencyGlobalExecutor() - // app.logger.debug("Tried to install SwiftNIO's EventLoopGroup as Swift's global concurrency executor", metadata: ["success": .stringConvertible(executorTakeoverSuccess)]) - + let executorTakeoverSuccess = NIOSingletons.unsafeTryInstallSingletonPosixEventLoopGroupAsConcurrencyGlobalExecutor() + app.logger.debug("Tried to install SwiftNIO's EventLoopGroup as Swift's global concurrency executor", metadata: ["success": .stringConvertible(executorTakeoverSuccess)]) + do { try await configure(app) try await app.execute() diff --git a/Sources/stella/routes/routes.swift b/Sources/stella/routes.swift similarity index 74% rename from Sources/stella/routes/routes.swift rename to Sources/stella/routes.swift index 0ab5799..2f6b01c 100644 --- a/Sources/stella/routes/routes.swift +++ b/Sources/stella/routes.swift @@ -10,10 +10,6 @@ func routes(_ app: Application) throws { "Hello, world!" } - let openApi = OpenApi() - openApi.registerRoutes(app) - try app.register(collection: InfodeskController()) - - print(app.routes.all) + try app.register(collection: OpenApiController()) } diff --git a/Sources/stella/routes/openapi.swift b/Sources/stella/routes/openapi.swift deleted file mode 100644 index 4edef9f..0000000 --- a/Sources/stella/routes/openapi.swift +++ /dev/null @@ -1,140 +0,0 @@ -import Vapor - -struct OpenApi { - func registerRoutes(_ app: Application) { - let group = app.grouped("openapi", "service") - - group.post("v3", "util", "country", "get") { req async -> String in - "{\"country\": \"us\"}" - } - - group.post("v4", "device", "accessToken", "create") { req async -> AccessTokenResponse in - AccessTokenResponse(accessToken: "fwPla7fQ8ty9+DZT/lD//uWZD4uD6C4lD6gGIIZTLKRTQ52/SLCRmk/370jcWGs+e+1iSoZtL7lj8ov9B0/jHmijH4nsHPQT6pchaQM1M9mtwYNQq0BWhVr9hF0jjCK/a5LIVd1kBac/Gemv29WKEDKSrUS9HxxUigoPRwtOy8m+oDj9FmDJZ+rzqWCc0QjES4Ky0fTpXZ7ESoguDzNmRtW3FYr+OFexw8wBPlwiC4w=", expiryTime: Int(Date.init(timeIntervalSinceNow: .init(0)).timeIntervalSince1970)) - } - - group.post("v3", "agreement", "getForLogin") { req async throws -> String in - let body = try req.content.decode(LoginAgreementRequest.self, as: .json) - - return #""" - { - "adAgreementStatus": "n", - "agreement": { - "E001": "n", - "E002": "n", - "E006": "n", - "N002": "n", - "N003": "n", - "timestamp": "\#(Int(Date().timeIntervalSince1970) * 1000)" - }, - "agreementPopup": "n", - "appId": "\#(body.appId)", - "appName": "World Flipper (NA)", - "context": "login", - "country": "\#(body.country)", - "firstAgreement": "n", - "idpCode": "\#(body.idpCode)", - "idpId": "6076008646", - "informationSecurityCountry": "kr", - "kakaoSyncAgreementGetSet": "n", - "kakaoSyncStatus": "off", - "kakaogameSdkVer": "3.0", - "lang": "\#(body.lang)", - "partnerId": 825, - "partnerName": "주식회사 카카오게임즈", - "plusFriendStatusInfo": null, - "policyApplyTime": 1630854000000 - } - """# - } - - group.post("v4", "auth", "loginDevice") { req async throws -> Response in - let body = try req.content.decode(LoginDeviceRequest.self, as: .json) - dump(body) - if let rawAccountId = req.headers["playerId"].first { - - } - return Response(status: .notImplemented) - } - } -} - -struct AccessTokenResponse: Content { - let accessToken: String - let expiryTime: Int -} - -struct LoginAgreementRequest: Content { - let deviceId: String - let os: String - let country: String - let lang: String - let appId: String - let idpCode: String - let serialNo: String - let idpId: String -} - -struct LoginDeviceRequest: Content { - let lang: String - let clientTime: Int - let deviceId: String - let serialNo: String - let country: String - let whiteKey: String - let market: String - let appSecret: String - let deviceAppKey: String - let sdkVer: String - let appVer: String - let os: String - let loginType: String - let accessToken: String - let resume: Bool - let osVer: String - let appId: String - let deviceModel: String - let network: String - let isIosAppOnMac: Bool - let adid: String - let timezoneOffset: Int - let fields: [String] - let telecom: String -} - -struct LoginDeviceResponse: Content { - let zatExpiryTime: Int - let zrtExpiryTime: Int - let firstLogin: Bool - let externalToken: String - let zat: String - let zrt: String - let player: Player -} - -struct Player: Content { - let idpId: String - let appId: String - let lang: String - let playerId: String - let agreement: AgreementResponse - let pushOption: PushOptionResponse - let lastLoginTime: Int - let regTime: Int - let idpAlias: String - let firstLoginTime: Int - let status: String -} - -struct AgreementResponse: Content { - let E001: String - let E002: String - let E006: String - let N002: String - let N003: String - let timestamp: String -} - -struct PushOptionResponse: Content { - let night: String - let player: String -} diff --git a/Sources/stella/util.swift b/Sources/stella/util.swift new file mode 100644 index 0000000..c58f48d --- /dev/null +++ b/Sources/stella/util.swift @@ -0,0 +1,3 @@ +func generateIdpAlias(appId: String, deviceId: String, serialNo: String) -> String { + return "\(appId):\(deviceId):\(serialNo)" +}