Skip to content

Commit 37c12b4

Browse files
authored
Merge pull request #119 from /issues/116
API Tokens repository
2 parents f197cf4 + c9348be commit 37c12b4

File tree

9 files changed

+188
-0
lines changed

9 files changed

+188
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package me.madhead.tyzenhaus.entity.api.token
2+
3+
import java.time.Instant
4+
import java.util.UUID
5+
6+
/**
7+
* Scope of API tokens for Mini Apps.
8+
*
9+
* Available scopes:
10+
* - HISTORY: Allows to read group's transaction history.
11+
*/
12+
enum class Scope {
13+
HISTORY,
14+
}
15+
16+
/**
17+
* Security token used to access API from the Mini Apps.
18+
*/
19+
data class APIToken(
20+
val token: UUID,
21+
val groupId: Long,
22+
val scope: Scope,
23+
val validUntil: Instant,
24+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package me.madhead.tyzenhaus.repository.postgresql.api.token
2+
3+
import java.sql.ResultSet
4+
import java.util.UUID
5+
import me.madhead.tyzenhaus.entity.api.token.APIToken
6+
import me.madhead.tyzenhaus.entity.api.token.Scope
7+
8+
internal fun ResultSet.toAPIToken(): APIToken? {
9+
return if (this.next()) {
10+
APIToken(
11+
token = this.getObject("token", UUID::class.java),
12+
groupId = this.getLong("group_id"),
13+
scope = Scope.valueOf(this.getString("scope")),
14+
validUntil = this.getTimestamp("valid_until").toInstant(),
15+
)
16+
} else {
17+
null
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package me.madhead.tyzenhaus.repository.postgresql.api.token
2+
3+
import java.sql.Timestamp
4+
import java.util.UUID
5+
import javax.sql.DataSource
6+
import me.madhead.tyzenhaus.entity.api.token.APIToken
7+
import me.madhead.tyzenhaus.entity.balance.Balance
8+
import me.madhead.tyzenhaus.repository.postgresql.PostgreSqlRepository
9+
import org.apache.logging.log4j.LogManager
10+
11+
/**
12+
* PostgreSQL repository for [balances][Balance].
13+
*/
14+
class APITokenRepository(dataSource: DataSource)
15+
: me.madhead.tyzenhaus.repository.APITokenRepository, PostgreSqlRepository(dataSource) {
16+
companion object {
17+
private val logger = LogManager.getLogger(APITokenRepository::class.java)!!
18+
}
19+
20+
override fun get(id: UUID): APIToken? {
21+
logger.debug("get {}", id)
22+
23+
dataSource.connection.use { connection ->
24+
connection
25+
.prepareStatement("""SELECT * FROM "api_token" WHERE "token" = ?;""")
26+
.use { preparedStatement ->
27+
preparedStatement.setObject(@Suppress("MagicNumber") 1, id)
28+
preparedStatement.executeQuery().use { resultSet ->
29+
return@get resultSet.toAPIToken()
30+
}
31+
}
32+
}
33+
}
34+
35+
@Suppress("DuplicatedCode")
36+
override fun save(entity: APIToken) {
37+
logger.debug("save {}", entity)
38+
39+
dataSource
40+
.connection
41+
.use { connection ->
42+
connection
43+
.prepareStatement("""
44+
INSERT INTO "api_token" ("token", "group_id", "scope", "valid_until")
45+
VALUES (?, ?, ?, ?)
46+
""".trimIndent())
47+
.use { preparedStatement ->
48+
preparedStatement.setObject(@Suppress("MagicNumber") 1, entity.token)
49+
preparedStatement.setLong(@Suppress("MagicNumber") 2, entity.groupId)
50+
preparedStatement.setString(@Suppress("MagicNumber") 3, entity.scope.name)
51+
preparedStatement.setTimestamp(@Suppress("MagicNumber") 4, Timestamp.from(entity.validUntil))
52+
preparedStatement.executeUpdate()
53+
}
54+
}
55+
}
56+
}

repository/postgresql/src/main/liquibase/changelog.yml

+11
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,14 @@ databaseChangeLog:
6565
- sqlFile:
6666
path: ../sql/transaction-remove-title-column.sql
6767
relativeToChangelogFile: true
68+
- changeSet:
69+
id: 7
70+
author: madhead
71+
changes:
72+
- sqlFile:
73+
path: ../sql/api-token.init.sql
74+
relativeToChangelogFile: true
75+
rollback:
76+
- sqlFile:
77+
path: ../sql/api-token.deinit.sql
78+
relativeToChangelogFile: true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DROP TABLE api_token;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CREATE TABLE api_token
2+
(
3+
token UUID PRIMARY KEY,
4+
group_id BIGINT NOT NULL,
5+
scope VARCHAR(128) NOT NULL,
6+
valid_until TIMESTAMP WITH TIME ZONE NOT NULL
7+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package me.madhead.tyzenhaus.repository.postgresql.api.token
2+
3+
import java.net.URI
4+
import java.time.Instant
5+
import java.util.UUID
6+
import me.madhead.tyzenhaus.entity.api.token.APIToken
7+
import me.madhead.tyzenhaus.entity.api.token.Scope
8+
import org.junit.jupiter.api.Assertions.assertEquals
9+
import org.junit.jupiter.api.Assertions.assertNull
10+
import org.junit.jupiter.api.BeforeAll
11+
import org.junit.jupiter.api.Tag
12+
import org.junit.jupiter.api.Test
13+
import org.junit.jupiter.api.TestInstance
14+
import org.postgresql.ds.PGSimpleDataSource
15+
16+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
17+
@Tag("db")
18+
class APITokenRepositoryTest {
19+
private lateinit var apiTokenRepository: APITokenRepository
20+
21+
@BeforeAll
22+
fun setUp() {
23+
val databaseUri = URI(System.getenv("DATABASE_URL")!!)
24+
25+
apiTokenRepository = APITokenRepository(
26+
PGSimpleDataSource().apply {
27+
setUrl("jdbc:postgresql://${databaseUri.host}:${databaseUri.port}${databaseUri.path}")
28+
user = databaseUri.userInfo.split(":")[0]
29+
password = databaseUri.userInfo.split(":")[1]
30+
}
31+
)
32+
}
33+
34+
@Test
35+
fun get() {
36+
assertEquals(
37+
APIToken(UUID.fromString("00000000-0000-0000-0000-000000000000"), 1, Scope.HISTORY, Instant.ofEpochMilli(253400572800000)),
38+
apiTokenRepository.get(UUID.fromString("00000000-0000-0000-0000-000000000000"))
39+
)
40+
}
41+
42+
@Test
43+
fun getNonExisting() {
44+
assertNull(apiTokenRepository.get(UUID.fromString("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF")))
45+
}
46+
47+
@Test
48+
fun save() {
49+
apiTokenRepository.save(
50+
APIToken(UUID.fromString("00000000-0000-0000-0000-000000000001"), 2, Scope.HISTORY, Instant.ofEpochMilli(808174800000)),
51+
)
52+
53+
assertEquals(
54+
APIToken(UUID.fromString("00000000-0000-0000-0000-000000000001"), 2, Scope.HISTORY, Instant.ofEpochMilli(808174800000)),
55+
apiTokenRepository.get(UUID.fromString("00000000-0000-0000-0000-000000000001"))
56+
)
57+
}
58+
}

repository/postgresql/src/test/sql/seed.sql

+3
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,6 @@ VALUES (6, 1, '{
5656
"groupId": 6,
5757
"balance": {}
5858
}'::JSONB);
59+
60+
INSERT INTO "api_token" ("token", "group_id", "scope", "valid_until")
61+
VALUES ('00000000-0000-0000-0000-000000000000', 1, 'HISTORY', '9999-12-12 00:00:00');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package me.madhead.tyzenhaus.repository
2+
3+
import java.util.UUID
4+
import me.madhead.tyzenhaus.entity.api.token.APIToken
5+
6+
/**
7+
* API tokens repository.
8+
*/
9+
interface APITokenRepository : Repository<UUID, APIToken>

0 commit comments

Comments
 (0)