Skip to content

Commit bd30ba6

Browse files
committed
Kotlin mentions processor
1 parent 68154fd commit bd30ba6

File tree

34 files changed

+813
-7
lines changed

34 files changed

+813
-7
lines changed

.deploy/lambda/layers/libGL.zip

3.73 MB
Binary file not shown.
4 MB
Binary file not shown.

.deploy/lambda/lib/JProfByBotStack.ts

+19
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,27 @@ export class JProfByBotStack extends cdk.Stack {
2020
partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
2121
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
2222
});
23+
const kotlinMentionsTable = new dynamodb.Table(this, 'jprof-by-bot-table-kotlin-mentions', {
24+
tableName: 'jprof-by-bot-table-kotlin-mentions',
25+
partitionKey: { name: 'chat', type: dynamodb.AttributeType.NUMBER },
26+
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
27+
});
28+
const layerLibGL = new lambda.LayerVersion(this, 'jprof-by-bot-lambda-layer-libGL', {
29+
code: lambda.Code.fromAsset('layers/libGL.zip'),
30+
compatibleRuntimes: [lambda.Runtime.JAVA_11],
31+
});
32+
const layerLibfontconfig = new lambda.LayerVersion(this, 'jprof-by-bot-lambda-layer-libfontconfig', {
33+
code: lambda.Code.fromAsset('layers/libfontconfig.zip'),
34+
compatibleRuntimes: [lambda.Runtime.JAVA_11],
35+
});
2336

2437
const lambdaWebhook = new lambda.Function(this, 'jprof-by-bot-lambda-webhook', {
2538
functionName: 'jprof-by-bot-lambda-webhook',
2639
runtime: lambda.Runtime.JAVA_11,
40+
layers: [
41+
layerLibGL,
42+
layerLibfontconfig,
43+
],
2744
timeout: Duration.seconds(30),
2845
memorySize: 1024,
2946
code: lambda.Code.fromAsset('../../runners/lambda/build/libs/jprof_by_bot-runners-lambda-all.jar'),
@@ -32,13 +49,15 @@ export class JProfByBotStack extends cdk.Stack {
3249
'LOG_THRESHOLD': 'DEBUG',
3350
'TABLE_VOTES': votesTable.tableName,
3451
'TABLE_YOUTUBE_CHANNELS_WHITELIST': youtubeChannelsWhitelistTable.tableName,
52+
'TABLE_KOTLIN_MENTIONS': kotlinMentionsTable.tableName,
3553
'TOKEN_TELEGRAM_BOT': props.telegramToken,
3654
'TOKEN_YOUTUBE_API': props.youtubeToken,
3755
},
3856
});
3957

4058
votesTable.grantReadWriteData(lambdaWebhook);
4159
youtubeChannelsWhitelistTable.grantReadData(lambdaWebhook);
60+
kotlinMentionsTable.grantReadWriteData(lambdaWebhook);
4261

4362
const api = new apigateway.RestApi(this, 'jprof-by-bot-api', {
4463
restApiName: 'jprof-by-bot-api',

.github/workflows/default.yml

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ jobs:
5656
read-only: ${{ github.ref != 'refs/heads/master' }}
5757
- run: votes/dynamodb/src/test/resources/seed.sh
5858
- run: youtube/dynamodb/src/test/resources/seed.sh
59+
- run: kotlin/dynamodb/src/test/resources/seed.sh
5960
- run: ./gradlew clean dbTest
6061
- uses: actions/upload-artifact@v2
6162
if: always()

build.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ plugins {
1010
subprojects {
1111
repositories {
1212
mavenCentral()
13+
maven("https://packages.jetbrains.team/maven/p/skija/maven")
1314
}
1415

1516
tasks {

gradle/libs.versions.toml

+7
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ tgbotapi = "0.33.3"
1414

1515
jsoup = "1.13.1"
1616

17+
skija = "0.91.4"
18+
1719
google-api-services-youtube = "v3-rev20210410-1.31.0"
1820

1921
log4j = "2.14.1"
@@ -40,6 +42,11 @@ tgbotapi-extensions-utils = { group = "dev.inmo", name = "tgbotapi.extensions.ut
4042

4143
jsoup = { group = "org.jsoup", name = "jsoup", version.ref = "jsoup" }
4244

45+
skija-linux = { group = "org.jetbrains.skija", name = "skija-linux", version.ref = "skija" }
46+
skija-macos-arm64 = { group = "org.jetbrains.skija", name = "skija-macos-arm64", version.ref = "skija" }
47+
skija-macos-x64 = { group = "org.jetbrains.skija", name = "skija-macos-x64", version.ref = "skija" }
48+
skija-windows = { group = "org.jetbrains.skija", name = "skija-windows", version.ref = "skija" }
49+
4350
google-api-services-youtube = { group = "com.google.apis", name = "google-api-services-youtube", version.ref = "google-api-services-youtube" }
4451

4552
log4j-api = { group = "org.apache.logging.log4j", name = "log4j-api", version.ref = "log4j" }

kotlin/build.gradle.kts

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
plugins {
2+
kotlin("jvm")
3+
}
4+
5+
dependencies {
6+
api(project.projects.core)
7+
api(libs.tgbotapi.core)
8+
implementation(libs.log4j.api)
9+
implementation(libs.tgbotapi.extensions.api)
10+
implementation(libs.tgbotapi.extensions.utils)
11+
// implementation(libs.skija.windows)
12+
implementation(libs.skija.linux)
13+
14+
testImplementation(libs.junit.jupiter.api)
15+
testImplementation(libs.junit.jupiter.params)
16+
testImplementation(libs.mockk)
17+
testRuntimeOnly(libs.junit.jupiter.engine)
18+
testRuntimeOnly(libs.log4j.core)
19+
}

kotlin/dynamodb/build.gradle.kts

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
plugins {
2+
kotlin("jvm")
3+
}
4+
5+
dependencies {
6+
api(project.projects.kotlin)
7+
api(libs.dynamodb)
8+
implementation(project.projects.utils.dynamodb)
9+
implementation(libs.kotlinx.coroutines.jdk8)
10+
11+
testImplementation(libs.junit.jupiter.api)
12+
testImplementation(libs.junit.jupiter.params)
13+
testImplementation(libs.aws.junit5.dynamo.v2)
14+
testImplementation(project.projects.utils.awsJunit5)
15+
testRuntimeOnly(libs.junit.jupiter.engine)
16+
}
17+
18+
tasks {
19+
val dbTest by registering(Test::class) {
20+
group = LifecycleBasePlugin.VERIFICATION_GROUP
21+
description = "Runs the DB tests."
22+
shouldRunAfter("test")
23+
outputs.upToDateWhen { false }
24+
useJUnitPlatform {
25+
includeTags("db")
26+
}
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package by.jprof.telegram.bot.kotlin.dynamodb.dao
2+
3+
import by.jprof.telegram.bot.kotlin.dao.KotlinMentionsDAO
4+
import by.jprof.telegram.bot.kotlin.model.KotlinMentions
5+
import by.jprof.telegram.bot.kotlin.model.UserStatistics
6+
import by.jprof.telegram.bot.utils.dynamodb.toAttributeValue
7+
import by.jprof.telegram.bot.utils.dynamodb.toInstant
8+
import by.jprof.telegram.bot.utils.dynamodb.toLong
9+
import kotlinx.coroutines.Dispatchers
10+
import kotlinx.coroutines.future.await
11+
import kotlinx.coroutines.withContext
12+
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient
13+
import software.amazon.awssdk.services.dynamodb.model.AttributeValue
14+
15+
class KotlinMentionsDAO(
16+
private val dynamoDb: DynamoDbAsyncClient,
17+
private val table: String
18+
) : KotlinMentionsDAO {
19+
override suspend fun save(kotlinMentions: KotlinMentions) {
20+
withContext(Dispatchers.IO) {
21+
dynamoDb.putItem {
22+
it.tableName(table)
23+
it.item(kotlinMentions.toAttributes())
24+
}.await()
25+
}
26+
}
27+
28+
override suspend fun get(chat: Long): KotlinMentions? {
29+
return withContext(Dispatchers.IO) {
30+
dynamoDb.getItem {
31+
it.tableName(table)
32+
it.key(mapOf("chat" to chat.toAttributeValue()))
33+
}.await()?.item()?.takeUnless { it.isEmpty() }?.toKotlinMentions()
34+
}
35+
}
36+
}
37+
38+
fun UserStatistics.toAttributes(): Map<String, AttributeValue> = mapOf(
39+
"count" to this.count.toAttributeValue(),
40+
"lastMention" to this.lastMention.toAttributeValue(),
41+
)
42+
43+
fun KotlinMentions.toAttributes(): Map<String, AttributeValue> = mapOf(
44+
"chat" to this.chat.toAttributeValue(),
45+
"lastMention" to this.lastMention.toAttributeValue(),
46+
"usersStatistics" to this.usersStatistics
47+
.mapKeys { it.key.toString() }
48+
.mapValues { AttributeValue.builder().m(it.value.toAttributes()).build() }
49+
.toAttributeValue()
50+
)
51+
52+
fun Map<String, AttributeValue>.toUserStatistics(): UserStatistics = UserStatistics(
53+
count = this["count"].toLong("count"),
54+
lastMention = this["lastMention"].toInstant("lastMention"),
55+
)
56+
57+
fun Map<String, AttributeValue>.toKotlinMentions(): KotlinMentions = KotlinMentions(
58+
chat = this["chat"].toLong("chat"),
59+
lastMention = this["lastMention"].toInstant("lastMention"),
60+
usersStatistics = this["usersStatistics"]?.m()?.let {
61+
it
62+
.mapValues { (_, value) -> value.m().toUserStatistics() }
63+
.mapKeys { (key, _) -> key.toLong() }
64+
} ?: emptyMap()
65+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package by.jprof.telegram.bot.kotlin.dynamodb.dao
2+
3+
import by.jprof.telegram.bot.kotlin.model.KotlinMentions
4+
import by.jprof.telegram.bot.kotlin.model.UserStatistics
5+
import by.jprof.telegram.bot.utils.aws_junit5.Endpoint
6+
import kotlinx.coroutines.runBlocking
7+
import me.madhead.aws_junit5.common.AWSClient
8+
import me.madhead.aws_junit5.dynamo.v2.DynamoDB
9+
import org.junit.jupiter.api.*
10+
import org.junit.jupiter.api.extension.ExtendWith
11+
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient
12+
import java.time.Instant
13+
14+
@Tag("db")
15+
@ExtendWith(DynamoDB::class)
16+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
17+
internal class KotlinMentionsDAOTest {
18+
@AWSClient(endpoint = Endpoint::class)
19+
private lateinit var dynamoDB: DynamoDbAsyncClient
20+
private lateinit var sut: KotlinMentionsDAO
21+
22+
@BeforeAll
23+
internal fun setup() {
24+
sut = KotlinMentionsDAO(dynamoDB, "kotlin-mentions")
25+
}
26+
27+
@Test
28+
fun save() = runBlocking {
29+
sut.save(kotlinMentions)
30+
}
31+
32+
@Test
33+
fun get() = runBlocking {
34+
Assertions.assertEquals(kotlinMentions, sut.get(42))
35+
}
36+
37+
@Test
38+
fun getUnexisting() = runBlocking {
39+
Assertions.assertNull(sut.get(-42))
40+
}
41+
42+
private val kotlinMentions
43+
get() = KotlinMentions(
44+
chat = 42L,
45+
lastMention = Instant.ofEpochMilli(1227376823000),
46+
usersStatistics = mapOf(
47+
1L to UserStatistics(2, Instant.ofEpochMilli(1227376823000)),
48+
2L to UserStatistics(1, Instant.ofEpochMilli(1214996810000)),
49+
)
50+
)
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package by.jprof.telegram.bot.kotlin.dynamodb.dao
2+
3+
import by.jprof.telegram.bot.kotlin.model.KotlinMentions
4+
import by.jprof.telegram.bot.kotlin.model.UserStatistics
5+
import org.junit.jupiter.api.Assertions
6+
import org.junit.jupiter.api.Test
7+
import software.amazon.awssdk.services.dynamodb.model.AttributeValue
8+
import java.time.Instant
9+
10+
internal class KotlinMentionsTest {
11+
@Test
12+
fun toAttributes() {
13+
Assertions.assertEquals(
14+
attributes,
15+
kotlinMentions.toAttributes(),
16+
)
17+
}
18+
19+
@Test
20+
fun tousersStatistics() {
21+
Assertions.assertEquals(
22+
kotlinMentions,
23+
attributes.toKotlinMentions(),
24+
)
25+
}
26+
27+
private val kotlinMentions
28+
get() = KotlinMentions(
29+
chat = 42L,
30+
lastMention = Instant.ofEpochMilli(1227376823000),
31+
usersStatistics = mapOf(
32+
1L to UserStatistics(2, Instant.ofEpochMilli(1227376823000)),
33+
2L to UserStatistics(1, Instant.ofEpochMilli(1214996810000)),
34+
)
35+
)
36+
37+
private val attributes
38+
get() = mapOf(
39+
"chat" to AttributeValue.builder().n("42").build(),
40+
"lastMention" to AttributeValue.builder().n("1227376823000").build(),
41+
"usersStatistics" to AttributeValue.builder().m(
42+
mapOf(
43+
"1" to AttributeValue.builder().m(
44+
mapOf(
45+
"count" to AttributeValue.builder().n("2").build(),
46+
"lastMention" to AttributeValue.builder().n("1227376823000").build(),
47+
)
48+
).build(),
49+
"2" to AttributeValue.builder().m(
50+
mapOf(
51+
"count" to AttributeValue.builder().n("1").build(),
52+
"lastMention" to AttributeValue.builder().n("1214996810000").build(),
53+
)
54+
).build(),
55+
)
56+
).build(),
57+
)
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package by.jprof.telegram.bot.kotlin.dynamodb.dao
2+
3+
import by.jprof.telegram.bot.kotlin.model.UserStatistics
4+
import org.junit.jupiter.api.Assertions
5+
import org.junit.jupiter.api.Test
6+
import software.amazon.awssdk.services.dynamodb.model.AttributeValue
7+
import java.time.Instant
8+
9+
internal class UserStatisticsTest {
10+
@Test
11+
fun toAttributes() {
12+
Assertions.assertEquals(
13+
attributes,
14+
userStatistics.toAttributes()
15+
)
16+
}
17+
18+
@Test
19+
fun toUserStatistics() {
20+
Assertions.assertEquals(
21+
userStatistics,
22+
attributes.toUserStatistics()
23+
)
24+
}
25+
26+
private val userStatistics
27+
get() = UserStatistics(
28+
count = 42,
29+
lastMention = Instant.ofEpochMilli(1227376823000)
30+
)
31+
32+
private val attributes
33+
get() = mapOf(
34+
"count" to AttributeValue.builder().n("42").build(),
35+
"lastMention" to AttributeValue.builder().n("1227376823000").build(),
36+
)
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"kotlin-mentions": [
3+
{
4+
"PutRequest": {
5+
"Item": {
6+
"chat": {
7+
"N": "42"
8+
},
9+
"lastMention": {
10+
"N": "1227376823000"
11+
},
12+
"usersStatistics": {
13+
"M": {
14+
"1": {
15+
"M": {
16+
"count": {
17+
"N": "2"
18+
},
19+
"lastMention": {
20+
"N": "1227376823000"
21+
}
22+
}
23+
},
24+
"2": {
25+
"M": {
26+
"count": {
27+
"N": "1"
28+
},
29+
"lastMention": {
30+
"N": "1214996810000"
31+
}
32+
}
33+
}
34+
}
35+
}
36+
}
37+
}
38+
}
39+
]
40+
}

0 commit comments

Comments
 (0)