From 36afeaad421fc13f75140626353af4fff5f9035f Mon Sep 17 00:00:00 2001 From: digimint Date: Mon, 24 Jun 2024 03:41:00 -0500 Subject: [PATCH] Basic database structure and partial implementation for UACs and tokens --- src/main/scala/Main.scala | 173 +++++++++++------- src/main/scala/db/DatabaseLayer.scala | 41 +++++ .../scala/db/internal/NotFoundException.scala | 3 + .../scala/db/modules/DatabaseModule.scala | 7 + .../db/modules/executor/ExecutorModule.scala | 16 ++ src/main/scala/db/modules/uac/UACAPI.scala | 129 +++++++++++++ src/main/scala/db/modules/uac/UACModule.scala | 18 ++ .../scala/db/modules/uac/UACQueries.scala | 13 ++ src/main/scala/db/modules/uac/UACSchema.scala | 88 +++++++++ src/main/scala/db/schema/ScopesVersion.scala | 65 ------- src/main/scala/db/schema/UserToken.scala | 39 ---- src/main/scala/twitch/api/TokenScope.scala | 12 +- .../api/UserAuthenticationCredential.scala | 42 +++-- 13 files changed, 454 insertions(+), 192 deletions(-) create mode 100644 src/main/scala/db/DatabaseLayer.scala create mode 100644 src/main/scala/db/internal/NotFoundException.scala create mode 100644 src/main/scala/db/modules/DatabaseModule.scala create mode 100644 src/main/scala/db/modules/executor/ExecutorModule.scala create mode 100644 src/main/scala/db/modules/uac/UACAPI.scala create mode 100644 src/main/scala/db/modules/uac/UACModule.scala create mode 100644 src/main/scala/db/modules/uac/UACQueries.scala create mode 100644 src/main/scala/db/modules/uac/UACSchema.scala delete mode 100644 src/main/scala/db/schema/ScopesVersion.scala delete mode 100644 src/main/scala/db/schema/UserToken.scala diff --git a/src/main/scala/Main.scala b/src/main/scala/Main.scala index 92e6fb0..23cb31f 100644 --- a/src/main/scala/Main.scala +++ b/src/main/scala/Main.scala @@ -1,30 +1,37 @@ /* - UNIT_CA5 - Stream management bot - Copyright (C) 2024 digimint + UNIT_CA5 - Stream management bot + Copyright (C) 2024 digimint - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ -package unit_ca5 -import slick.jdbc.PostgresProfile.api._ +import db.DatabaseLayer + +import twitch.api.UserAuthenticationCredential + +import slick.jdbc.PostgresProfile +import slick.jdbc.H2Profile import scala.concurrent.Await import scala.concurrent.duration.Duration import com.typesafe.config.ConfigFactory import com.typesafe.config.Config import java.time.Instant +import twitch.api.TokenScope -@main def hello(): Unit = +import scala.concurrent.ExecutionContext.Implicits.global + +@main def main(): Unit = println("Hello world!") println(msg) @@ -33,71 +40,107 @@ import java.time.Instant println(s"Test config key: ${test_key}") - Await.result(DBConnection.doSetup, Duration.Inf) - println("Database initialized") + println("Initializing database") + val dbLayer = Await.result( + DatabaseLayer.setup( + PostgresProfile, + "db_conf" + ), + Duration.Inf + ) - Await.result(DBConnection.doTestInserts, Duration.Inf) - println("Test values inserted") + println("Performing some test inserts...") + + val query = dbLayer.uac.api.create( + UserAuthenticationCredential( + "digimint", + "asdfasdfasdfasdf", + "asdfasdfasdfasdf", + Instant.MAX, + List( + TokenScope.AnalyticsReadExtensions, + TokenScope.BitsRead, + TokenScope.ChannelManagePolls + ) + ) + ) + + Await.result(dbLayer.exec.execute(query), Duration.Inf) + + println("Querying result...") + + val q2 = dbLayer.uac.api.retrieve("digimint") + + val res = Await.result(dbLayer.exec.execute(q2), Duration.Inf) + + println(res) + + + // Await.result(DBConnection.doSetup, Duration.Inf) + // println("Database initialized") + + // Await.result(DBConnection.doTestInserts, Duration.Inf) + // println("Test values inserted") def msg = "I was compiled by Scala 3. :)" -class UserTokens(tag: Tag) extends Table[(Int, String, String, String, Instant, Int)](tag, "USER_TOKENS"): - def id = column[Int]("TKN_ID", O.PrimaryKey, O.AutoInc) - def userId = column[String]("USER_ID") - def accessToken = column[String]("ACCESS_TKN") - def refreshToken = column[String]("REFRESH_TKN") - def expires = column[Instant]("EXPIRES") +// class UserTokens(tag: Tag) extends Table[(Int, String, String, String, Instant, Int)](tag, "USER_TOKENS"): +// def id = column[Int]("TKN_ID", O.PrimaryKey, O.AutoInc) +// def userId = column[String]("USER_ID") +// def accessToken = column[String]("ACCESS_TKN") +// def refreshToken = column[String]("REFRESH_TKN") +// def expires = column[Instant]("EXPIRES") - // Compact representation of the scopes granted to this token. If a version - // update changes the set of required scopes, the "current" SCOPES_VERSION - // is incremented. This then gets mapped to a set of actual scopes, which - // other functions can check. - def scopes_version = column[Int]("SCOPES_VERSION") +// // Compact representation of the scopes granted to this token. If a version +// // update changes the set of required scopes, the "current" SCOPES_VERSION +// // is incremented. This then gets mapped to a set of actual scopes, which +// // other functions can check. +// def scopes_version = column[Int]("SCOPES_VERSION") - def * = (id, userId, accessToken, refreshToken, expires, scopes_version) +// def * = (id, userId, accessToken, refreshToken, expires, scopes_version) - def user = foreignKey("USER_FK", userId, authUsers)(_.userId) +// def user = foreignKey("USER_FK", userId, authUsers)(_.userId) -val userTokens = TableQuery[UserTokens] +// val userTokens = TableQuery[UserTokens] -class AuthUsers(tag: Tag) extends Table[(String, String)](tag, "USERS"): - def userId = column[String]("USER_ID", O.PrimaryKey) - def name = column[String]("TWITCH_USERNAME") +// class AuthUsers(tag: Tag) extends Table[(String, String)](tag, "USERS"): +// def userId = column[String]("USER_ID", O.PrimaryKey) +// def name = column[String]("TWITCH_USERNAME") - def * = (userId, name) +// def * = (userId, name) -val authUsers = TableQuery[AuthUsers] +// val authUsers = TableQuery[AuthUsers] -object DBConnection{ - val db = Database.forConfig("db_conf") +// object DBConnection{ +// val db = Database.forConfig("db_conf") - val setup = DBIO.seq( - (authUsers.schema ++ userTokens.schema).createIfNotExists - ) +// val setup = DBIO.seq( +// (authUsers.schema ++ userTokens.schema).createIfNotExists +// ) - def doSetup = - db.run(setup) +// def doSetup = +// db.run(setup) - def doTestInserts = - val testInserts = DBIO.seq( - authUsers ++= Seq( - ("a", "digimint"), - ("b", "PancakeDragoness") - ), +// def doTestInserts = +// val testInserts = DBIO.seq( +// authUsers ++= Seq( +// ("a", "digimint"), +// ("b", "PancakeDragoness") +// ), - userTokens ++= Seq( - (1, "a", "coolAccessToken", "coolRefreshToken", Instant.now, 1), - (2, "b", "hotAccessToken", "hotRefreshToken", Instant.now, 1) - ) - ) +// userTokens ++= Seq( +// (1, "a", "coolAccessToken", "coolRefreshToken", Instant.now, 1), +// (2, "b", "hotAccessToken", "hotRefreshToken", Instant.now, 1) +// ) +// ) - db.run(testInserts) - // val userInserts: DBIO[Option[Int]] = authUsers ++= Seq ( - // ("a", "digimint"), - // ("b", "PancakeDragoness") - // ) +// db.run(testInserts) +// // val userInserts: DBIO[Option[Int]] = authUsers ++= Seq ( +// // ("a", "digimint"), +// // ("b", "PancakeDragoness") +// // ) - // val tokensInserts: DBIO[Option[Int]] = userTokens ++= Seq ( - // () - // ) -} \ No newline at end of file +// // val tokensInserts: DBIO[Option[Int]] = userTokens ++= Seq ( +// // () +// // ) +// } \ No newline at end of file diff --git a/src/main/scala/db/DatabaseLayer.scala b/src/main/scala/db/DatabaseLayer.scala new file mode 100644 index 0000000..d304f84 --- /dev/null +++ b/src/main/scala/db/DatabaseLayer.scala @@ -0,0 +1,41 @@ +package db + +import modules.uac.UACModule +import modules.executor.ExecutorModule + +import scala.concurrent.Future +import slick.jdbc.JdbcProfile +import scala.concurrent.ExecutionContext + +class DatabaseLayer( + val uac: UACModule, + val exec: ExecutorModule +) + + +object DatabaseLayer: + def setup( + profile: JdbcProfile, + config_loc: String = "db_conf" + )(implicit ec: ExecutionContext): Future[DatabaseLayer] = + import profile.api._ + val database = Database.forConfig(config_loc) + + // Construct and initialize modules + val uacModule = UACModule(profile, database) + val uacSetupActions = uacModule.setup + + val executorModule = ExecutorModule(profile, database) + val executorSetupOptions = executorModule.setup + + // Run initialization actions + val initActions = DBIO.seq( + uacSetupActions, + executorSetupOptions + ) + + database.run(initActions).map(_ => + DatabaseLayer(uacModule, executorModule) + ) + + diff --git a/src/main/scala/db/internal/NotFoundException.scala b/src/main/scala/db/internal/NotFoundException.scala new file mode 100644 index 0000000..34d95e5 --- /dev/null +++ b/src/main/scala/db/internal/NotFoundException.scala @@ -0,0 +1,3 @@ +package db.internal + +class NotFoundException extends Throwable diff --git a/src/main/scala/db/modules/DatabaseModule.scala b/src/main/scala/db/modules/DatabaseModule.scala new file mode 100644 index 0000000..fd3da75 --- /dev/null +++ b/src/main/scala/db/modules/DatabaseModule.scala @@ -0,0 +1,7 @@ +package db.modules + +import slick.dbio.DBIO +import slick.jdbc.JdbcProfile + +trait DatabaseModule: + def setup: DBIO[Unit] diff --git a/src/main/scala/db/modules/executor/ExecutorModule.scala b/src/main/scala/db/modules/executor/ExecutorModule.scala new file mode 100644 index 0000000..31caf4e --- /dev/null +++ b/src/main/scala/db/modules/executor/ExecutorModule.scala @@ -0,0 +1,16 @@ +package db.modules.executor + +import slick.jdbc.JdbcProfile +import db.modules.DatabaseModule +import scala.concurrent.Future + +class ExecutorModule( + profile: JdbcProfile, + database: profile.backend.Database +) extends DatabaseModule: + import profile.api._ + + def execute[A](query: DBIO[A]): Future[A] = + database.run(query) + + def setup: DBIO[Unit] = DBIO.successful(None) \ No newline at end of file diff --git a/src/main/scala/db/modules/uac/UACAPI.scala b/src/main/scala/db/modules/uac/UACAPI.scala new file mode 100644 index 0000000..fe791b0 --- /dev/null +++ b/src/main/scala/db/modules/uac/UACAPI.scala @@ -0,0 +1,129 @@ +package db.modules.uac + +import db.internal.NotFoundException +import db.modules.uac.{UACSchema, UACQueries} +import twitch.api.{TwitchUID, UserAuthenticationCredential, TokenScope} + +import scala.concurrent.Future +import slick.jdbc.JdbcProfile +import scala.concurrent.ExecutionContext + +class UACAPI( + val profile: JdbcProfile, + val queries: UACQueries, + val database: profile.backend.Database +): + import profile.api._ + + def setup: DBIO[Unit] = + DBIO.seq( + (queries.UACBase.schema ++ queries.tokenScopeBase.schema).createIfNotExists + ) + + def create(cred: UserAuthenticationCredential)(implicit ec: ExecutionContext): DBIO[Option[Int]] = + queries.UACBase.returning(queries.UACBase.map(_.id)).+=( + queries.schema.RawUAC( + 0, + cred.userId, + cred.accessToken, + cred.refreshToken, + cred.expires + ) + ).flatMap(uacId => + queries.tokenScopeBase.++=( + cred.scopes.map(scope => + queries.schema.UACTokenScope( + uacId, + scope.uid + ) + ) + ) + ) + + def retrieve(user: TwitchUID)(implicit ec: ExecutionContext): DBIO[UserAuthenticationCredential] = + // Construct query for base UAC data + queries.UACBase.filter( + _.userId === user + ).take(1) + .result.headOption // Take only the first UAC that matches (there should never be more than one) + .flatMap(res => // With the result of that query: + res match + case None => slick.dbio.FailureAction(NotFoundException()) // Don't attempt to retrieve scopes if there's no matching UAC + case Some(uac) => + // Construct a query to retrieve relevant scopes + queries.tokenScopeBase.filter( scope => + scope.tokenId === uac.id + ).result.map(res => + val convertedScopes = res.map(scope => + // Convert UIDs to TokenScope enums + TokenScope.fromUid(scope.scopeId) + ).filter(mappedScope => + // Filter any UIDs that didn't match TokenScopes. + mappedScope match + case None => false + case _ => true + ).map(convertedScope => + // Strip the Options. + convertedScope.get + ) + + UserAuthenticationCredential( + uac.userId, + uac.accessToken, + uac.refreshToken, + uac.expires, + convertedScopes.toList + ) + ) + ) + + // val action = database.run(query).flatMap( res => + // res match + // case None => Future{None} + // case Some(uac) => + // // Construct query for + // val scopesQuery = queries.tokenScopeBase.filter( scope => + // scope.tokenId === uac.id + // ).result.map(res => + // res.map(scope => + // // Convert UIDs to TokenScope enums + // TokenScope.fromUid(scope.scopeId) + // ).filter(mappedScope => + // // Filter any UIDs that didn't match TokenScopes. + // mappedScope match + // case None => false + // case _ => true + // ).map(convertedScope => + // // Strip the Options. + // convertedScope.get + // ) + // ) + + // database.run(scopesQuery).map(res => + // UserAuthenticationCredential( + // uac.userId, + // uac.accessToken, + // uac.refreshToken, + // uac.expires, + // res.toList + // ) + // ) + // ) + + // .map((res) => + // val queryResult: Option[queries.schema.RawUAC] = + // if res.length > 0 then Some(res[0]) else None + + // if queryResult == None then + // throw Exception + + // val scopesQuery = queries.tokenScopeBase.filter( + // _.tokenId === queryResult.accessToken + // ).take(1).result + // ) + + // action + + def delete(user: TwitchUID): Future[Unit] = ??? + + \ No newline at end of file diff --git a/src/main/scala/db/modules/uac/UACModule.scala b/src/main/scala/db/modules/uac/UACModule.scala new file mode 100644 index 0000000..d354fad --- /dev/null +++ b/src/main/scala/db/modules/uac/UACModule.scala @@ -0,0 +1,18 @@ +package db.modules.uac + +import db.modules.DatabaseModule +import db.modules.uac.{UACAPI, UACQueries, UACSchema} + +import slick.jdbc.JdbcProfile +import slick.dbio.DBIO + +class UACModule( + val profile: JdbcProfile, + val database: profile.backend.Database +) extends DatabaseModule: + val schema = UACSchema(profile) + val queries = UACQueries(profile, schema) + val api = UACAPI(profile, queries, database) + + def setup: DBIO[Unit] = + api.setup diff --git a/src/main/scala/db/modules/uac/UACQueries.scala b/src/main/scala/db/modules/uac/UACQueries.scala new file mode 100644 index 0000000..49daac5 --- /dev/null +++ b/src/main/scala/db/modules/uac/UACQueries.scala @@ -0,0 +1,13 @@ +package db.modules.uac + +import db.modules.uac.UACSchema + +import slick.jdbc.JdbcProfile + +class UACQueries( + val profile: JdbcProfile, + val schema: UACSchema +): + import profile.api._ + val UACBase = schema._uacTable + val tokenScopeBase = schema._uacTokenScopeTable \ No newline at end of file diff --git a/src/main/scala/db/modules/uac/UACSchema.scala b/src/main/scala/db/modules/uac/UACSchema.scala new file mode 100644 index 0000000..7c76b1c --- /dev/null +++ b/src/main/scala/db/modules/uac/UACSchema.scala @@ -0,0 +1,88 @@ +/* + UNIT_CA5 - Stream management bot + Copyright (C) 2024 digimint + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + */ +package db.modules.uac + +import twitch.api.TwitchUID +import twitch.api.AccessToken +import twitch.api.RefreshToken + +import java.time.Instant +import slick.jdbc.JdbcProfile + +class UACSchema(val profile: JdbcProfile): + import profile.api._ + case class RawUAC( + id: Long = 0, + userId: TwitchUID, + accessToken: AccessToken, + refreshToken: RefreshToken, + expires: Instant + ) + + case class UACTokenScope( + tokenId: Long, + scopeId: Long + ) + + class UACTable(tag: Tag) extends Table[RawUAC](tag, "USER_TOKENS"): + def id = column[Long]("TKN_ID", O.PrimaryKey, O.AutoInc) + def userId = column[TwitchUID]("USER_ID") + def accessToken = column[AccessToken]("ACCESS_TKN") + def refreshToken = column[RefreshToken]("REFRESH_TKN") + def expires = column[Instant]("EXPIRES") + + def * = (id, userId, accessToken, refreshToken, expires).mapTo[RawUAC] + + val _uacTable = TableQuery[UACTable] + + + class UACTokenScopeTable(tag: Tag) extends Table[UACTokenScope](tag, "USER_TOKEN_SCOPES"): + def tokenId = column[Long]("TKN_ID") + def scopeId = column[Long]("SCOPE") + + def token = foreignKey( + "TKN", + tokenId, + _uacTable + )( + _.id, + onDelete=ForeignKeyAction.Cascade + ) + + def * = (tokenId, scopeId).mapTo[UACTokenScope] + + val _uacTokenScopeTable = TableQuery[UACTokenScopeTable] + // object api: + // def setup: DBIO[Unit] = + // DBIO.seq( + // (queries.UACBase.schema ++ queries.tokenScopeBase.schema).createIfNotExists + // ) + + + + // val queryScopesForCredential = + // tokenScopesQuery.join(rawUACs).on(_.tokenId === _.id) + // .groupBy( + // (token, cred) => cred.userId + // ) + // .map( + // (userId, group) => userId -> group.map( + // (scope, user) => scope.scopeId + // ) + // ) + \ No newline at end of file diff --git a/src/main/scala/db/schema/ScopesVersion.scala b/src/main/scala/db/schema/ScopesVersion.scala deleted file mode 100644 index 0e48f4b..0000000 --- a/src/main/scala/db/schema/ScopesVersion.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - UNIT_CA5 - Stream management bot - Copyright (C) 2024 digimint - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - */ -package unit_ca5.db.schema - -import unit_ca5.twitch.TokenScope - -trait ScopesVersion: - val scopes: List[TokenScope] - val version: Int - - def toScopes(): List[TokenScope] = scopes - - def toInt(): Int = version - - def matches(version: Int): Boolean = - this.version == version - - def matches(scopes: List[TokenScope]): Boolean = - this.scopes == scopes - - -object ScopesVersion: - val allScopesVersions: List[ScopesVersion] = List( - scopesVersion1 - ) - - // Construct from a list of scopes - def apply(scopes: List[TokenScope]): Option[ScopesVersion] = - allScopesVersions.find( - (candidate: ScopesVersion) => - candidate.matches(scopes) - ) - - // Construct from a version number - def apply(version: Int): Option[ScopesVersion] = - allScopesVersions.find( - (candidate: ScopesVersion) => - candidate.matches(version) - ) - -val scopesVersion1: ScopesVersion = - new ScopesVersion: - val scopes = List( - TokenScope.UserReadChat, - TokenScope.BitsRead, - TokenScope.ChannelManageRaids - ) - val version = 1 - - \ No newline at end of file diff --git a/src/main/scala/db/schema/UserToken.scala b/src/main/scala/db/schema/UserToken.scala deleted file mode 100644 index 68e63a4..0000000 --- a/src/main/scala/db/schema/UserToken.scala +++ /dev/null @@ -1,39 +0,0 @@ -/* - UNIT_CA5 - Stream management bot - Copyright (C) 2024 digimint - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - */ -package unit_ca5.db.schema - -import slick.jdbc.PostgresProfile.api._ - -import unit_ca5.twitch.UserAuthenticationCredential -import unit_ca5.twitch.TokenScope -import java.time.Instant - -class UserAuthenticationCredentialTable(tag: Tag) extends Table[UserAuthenticationCredential](tag, "USER_TOKENS"): - def id = column[Int]("TKN_ID", O.PrimaryKey, O.AutoInc) - def userId = column[String]("USER_ID") - def accessToken = column[String]("ACCESS_TKN") - def refreshToken = column[String]("REFRESH_TKN") - def expires = column[Instant]("EXPIRES") - - // Compact representation of the scopes granted to this token. If a version - // update changes the set of required scopes, the "current" SCOPES_VERSION - // is incremented. This then gets mapped to a set of actual scopes, which - // other functions can check. - def scopes_version = column[Int]("SCOPES_VERSION") - - def * = ??? \ No newline at end of file diff --git a/src/main/scala/twitch/api/TokenScope.scala b/src/main/scala/twitch/api/TokenScope.scala index 983d114..5836521 100644 --- a/src/main/scala/twitch/api/TokenScope.scala +++ b/src/main/scala/twitch/api/TokenScope.scala @@ -15,7 +15,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package unit_ca5.twitch +package twitch.api /** * Enumeration of all scopes currently supported by the Twitch API @@ -24,7 +24,7 @@ package unit_ca5.twitch * tokens with scopes in the database. The ordering of scopes within this enum * is unimportant, as long as each scope's UID is unique and does not change. */ -enum TokenScope(val uid: Int): +enum TokenScope(val uid: Long): // API Scopes case AnalyticsReadExtensions extends TokenScope(0x0000) case AnalyticsReadGames extends TokenScope(0x0001) @@ -105,4 +105,10 @@ enum TokenScope(val uid: Int): case UserWriteChat extends TokenScope(0x7022) case WhispersRead extends TokenScope(0x7030) - case WhispersEdit extends TokenScope(0x7031) \ No newline at end of file + case WhispersEdit extends TokenScope(0x7031) + +object TokenScope: + def fromUid(uid: Long): Option[TokenScope] = + TokenScope.values.foldLeft(None)( + (z, thisScope) => if(thisScope.uid == uid) Some(thisScope) else z + ) diff --git a/src/main/scala/twitch/api/UserAuthenticationCredential.scala b/src/main/scala/twitch/api/UserAuthenticationCredential.scala index af944bb..b193e4b 100644 --- a/src/main/scala/twitch/api/UserAuthenticationCredential.scala +++ b/src/main/scala/twitch/api/UserAuthenticationCredential.scala @@ -1,36 +1,36 @@ /* - UNIT_CA5 - Stream management bot - Copyright (C) 2024 digimint + UNIT_CA5 - Stream management bot + Copyright (C) 2024 digimint - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ -package unit_ca5.twitch +package twitch.api import java.time.Instant -import unit_ca5.twitch.TokenScope +import twitch.api.TokenScope type TwitchUID = String type AccessToken = String type RefreshToken = String case class UserAuthenticationCredential( - userId: TwitchUID, - accessToken: AccessToken, - refreshToken: RefreshToken, - expires: Instant, - scopes: List[TokenScope] + userId : TwitchUID, + accessToken : AccessToken, + refreshToken : RefreshToken, + expires : Instant, + scopes : List[TokenScope] ): def is_expired(now: Instant): Boolean = now.isAfter(expires) @@ -39,4 +39,6 @@ case class UserAuthenticationCredential( scopes.contains(scope) def supportsAll(scopeList: List[TokenScope]): Boolean = - \ No newline at end of file + scopeList.forall( + scopes.contains + ) \ No newline at end of file