Initial Commit

This commit is contained in:
digimint 2024-06-19 20:55:25 -05:00
commit d190beb7bf
Signed by: digimint
GPG key ID: 8DF1C6FD85ABF748
13 changed files with 382 additions and 0 deletions

32
.gitignore vendored Normal file
View file

@ -0,0 +1,32 @@
# macOS
.DS_Store
# sbt specific
dist/*
target/
lib_managed/
src_managed/
project/boot/
project/plugins/project/
project/local-plugins.sbt
.history
.ensime
.ensime_cache/
.sbt-scripted/
local.sbt
# Bloop
.bsp
# VS Code
.vscode/
# Metals
.bloop/
.metals/
metals.sbt
# IDEA
.idea
.idea_modules
/.worksheet/

8
README.md Normal file
View file

@ -0,0 +1,8 @@
## sbt project compiled with Scala 3
### Usage
This is a normal sbt project. You can compile code with `sbt compile`, run it with `sbt run`, and `sbt console` will start a Scala 3 REPL.
For more information on the sbt-dotty plugin, see the
[scala3-example-project](https://github.com/scala/scala3-example-project/blob/main/README.md).

22
build.sbt Normal file
View file

@ -0,0 +1,22 @@
val scala3Version = "3.4.1"
lazy val root = project
.in(file("."))
.settings(
name := "Unit-CA5 Control Program",
version := "0.1.0-SNAPSHOT",
scalaVersion := scala3Version,
libraryDependencies ++= Seq(
"org.scalameta" %% "munit" % "0.7.29" % Test, // Base
"org.scala-lang" %% "toolkit" % "0.1.7",
"ch.qos.logback" % "logback-classic" % "1.5.6", // Logging
"com.typesafe.slick" %% "slick" % "3.5.1", // Database
"com.typesafe" % "config" % "1.4.3",
"com.typesafe.slick" %% "slick-hikaricp" % "3.5.1",
"org.postgresql" % "postgresql" % "42.2.5"
)
)

View file

@ -0,0 +1,26 @@
# Use postgres/example user/password credentials
version: '3.9'
services:
db:
image: postgres
restart: always
# set shared memory limit when using docker-compose
shm_size: 128mb
# or set shared memory limit when deploy via swarm stack
#volumes:
# - type: tmpfs
# target: /dev/shm
# tmpfs:
# size: 134217728 # 128*2^20 bytes = 128Mb
environment:
POSTGRES_PASSWORD: Xo65TR6yzUJe2i4t9pQD6AfNGe23Y8ww8CtW7aADfrLLivSsuYpB36oyXXBLH443
ports:
- 5432:5432
adminer:
image: adminer
restart: always
ports:
- 8080:8080

1
project/build.properties Normal file
View file

@ -0,0 +1 @@
sbt.version=1.9.9

2
src/main/resources/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
# Prevent unintentional commit of secrets
application.conf

View file

@ -0,0 +1,17 @@
db_conf = {
connectionPool = "HikariCP"
dataSourceClass = "org.postgresql.ds.PGSimpleDataSource"
properties = {
serverName = ""
portNumber = ""
databaseName = ""
user = ""
password = ""
}
numThreads = 1
}
twitch_conf = {
client_id = ""
client_secret = ""
}

86
src/main/scala/Main.scala Normal file
View file

@ -0,0 +1,86 @@
package unit_ca5
import slick.jdbc.PostgresProfile.api._
import scala.concurrent.Await
import scala.concurrent.duration.Duration
import com.typesafe.config.ConfigFactory
import com.typesafe.config.Config
import java.time.Instant
@main def hello(): Unit =
println("Hello world!")
println(msg)
val conf: Config = ConfigFactory.load("application.conf")
val test_key: String = conf.getString("test")
println(s"Test config key: ${test_key}")
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")
// 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 user = foreignKey("USER_FK", userId, authUsers)(_.userId)
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")
def * = (userId, name)
val authUsers = TableQuery[AuthUsers]
object DBConnection{
val db = Database.forConfig("db_conf")
val setup = DBIO.seq(
(authUsers.schema ++ userTokens.schema).createIfNotExists
)
def doSetup =
db.run(setup)
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)
)
)
db.run(testInserts)
// val userInserts: DBIO[Option[Int]] = authUsers ++= Seq (
// ("a", "digimint"),
// ("b", "PancakeDragoness")
// )
// val tokensInserts: DBIO[Option[Int]] = userTokens ++= Seq (
// ()
// )
}

View file

@ -0,0 +1,48 @@
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

View file

@ -0,0 +1,22 @@
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 * = ???

View file

@ -0,0 +1,84 @@
package unit_ca5.twitch
enum TokenScope:
// API Scopes
case AnalyticsReadExtensions
case AnalyticsReadGames
case BitsRead
case ChannelManageAds
case ChannelReadAds
case ChannelManageBroadcast
case ChannelReadCharity
case ChannelEditCommercial
case ChannelReadEditors
case ChannelManageExtensions
case ChannelReadGoals
case ChannelReadGuestStar
case ChannelManageGuestStar
case ChannelReadHypeTrain
case ChannelManageModerators
case ChannelReadPolls
case ChannelManagePolls
case ChannelReadPredictions
case ChannelManagePredictions
case ChannelManageRaids
case ChannelReadRedemptions
case ChannelManageRedemptions
case ChannelManageSchedule
case ChannelReadStreamKey
case ChannelReadSubscriptions
case ChannelManageVideos
case ChannelReadVIPs
case ChannelManageVIPs
case ClipsEdit
case ModerationRead
case ModeratorManageAnnouncement
case ModeratorManageAutomod
case ModeratorReadAutomodSettings
case ModeratorManageAutomodSettings
case ModeratorManageBannedUsers
case ModeratorReadBlockedTerms
case ModeratorManageBlockedTerms
case ModeratorManageChatMessages
case ModeratorReadChatters
case ModeratorReadFollowers
case ModeratorReadGuestStar
case ModeratorManageGuestStar
case ModeratorReadShieldMode
case ModeratorManageShieldMode
case ModeratorReadShoutouts
case ModeratorManageShoutouts
case ModeratorReadUnbanRequests
case ModeratorManageUnbanRequests
case UserEdit
case UserEditFollows
case UserReadBlockedUsers
case UserManageBlockedUsers
case UserReadBroadcast
case UserManageChatColor
case UserReadEmail
case UserReadEmotes
case UserReadFollows
case UserReadModeratedChannels
case UserReadSubscriptions
case UserManageWhispers
// Chat and PubSub scopes
case ChannelBot
case ChannelModerate
case ChatEdit
case ChatRead
case UserBot
case UserReadChat
case UserWriteChat
case WhispersRead
case WhispersEdit

View file

@ -0,0 +1,25 @@
package unit_ca5.twitch
import java.time.Instant
import unit_ca5.twitch.TokenScope
type TwitchUID = String
type AccessToken = String
type RefreshToken = String
case class UserAuthenticationCredential(
userId: TwitchUID,
accessToken: AccessToken,
refreshToken: RefreshToken,
expires: Instant,
scopes: List[TokenScope]
):
def is_expired(now: Instant): Boolean =
now.isAfter(expires)
def supports(scope: TokenScope): Boolean =
scopes.contains(scope)
def supportsAll(scopeList: List[TokenScope]): Boolean =

View file

@ -0,0 +1,9 @@
// For more information on writing tests, see
// https://scalameta.org/munit/docs/getting-started.html
class MySuite extends munit.FunSuite {
test("example test that succeeds") {
val obtained = 42
val expected = 42
assertEquals(obtained, expected)
}
}