Initial Commit
This commit is contained in:
commit
d190beb7bf
13 changed files with 382 additions and 0 deletions
32
.gitignore
vendored
Normal file
32
.gitignore
vendored
Normal 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
8
README.md
Normal 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
22
build.sbt
Normal 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"
|
||||
)
|
||||
)
|
||||
26
deploy/docker/docker-compose.yml
Normal file
26
deploy/docker/docker-compose.yml
Normal 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
1
project/build.properties
Normal file
|
|
@ -0,0 +1 @@
|
|||
sbt.version=1.9.9
|
||||
2
src/main/resources/.gitignore
vendored
Normal file
2
src/main/resources/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# Prevent unintentional commit of secrets
|
||||
application.conf
|
||||
17
src/main/resources/application.conf.skel
Normal file
17
src/main/resources/application.conf.skel
Normal 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
86
src/main/scala/Main.scala
Normal 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 (
|
||||
// ()
|
||||
// )
|
||||
}
|
||||
48
src/main/scala/db/schema/ScopesVersion.scala
Normal file
48
src/main/scala/db/schema/ScopesVersion.scala
Normal 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
|
||||
|
||||
|
||||
22
src/main/scala/db/schema/UserToken.scala
Normal file
22
src/main/scala/db/schema/UserToken.scala
Normal 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 * = ???
|
||||
84
src/main/scala/twitch/api/TokenScope.scala
Normal file
84
src/main/scala/twitch/api/TokenScope.scala
Normal 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
|
||||
25
src/main/scala/twitch/api/UserAuthenticationCredential.scala
Normal file
25
src/main/scala/twitch/api/UserAuthenticationCredential.scala
Normal 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 =
|
||||
|
||||
9
src/test/scala/MySuite.scala
Normal file
9
src/test/scala/MySuite.scala
Normal 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)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue