Skip to content

Commit 0e7c625

Browse files
authored
Merge pull request #10 from JavaBy/feature/quizoji
Quizojis
2 parents 0ea6788 + df6c835 commit 0e7c625

File tree

54 files changed

+2775
-46
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+2775
-46
lines changed

.deploy/lambda/lib/JProfByBotStack.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,18 @@ export class JProfByBotStack extends cdk.Stack {
2525
partitionKey: { name: 'chat', type: dynamodb.AttributeType.NUMBER },
2626
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
2727
});
28+
const dialogStatesTable = new dynamodb.Table(this, 'jprof-by-bot-table-dialog-states', {
29+
tableName: 'jprof-by-bot-table-dialog-states',
30+
partitionKey: { name: 'userId', type: dynamodb.AttributeType.NUMBER },
31+
sortKey: { name: 'chatId', type: dynamodb.AttributeType.NUMBER },
32+
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
33+
});
34+
const quizojisTable = new dynamodb.Table(this, 'jprof-by-bot-table-quizojis', {
35+
tableName: 'jprof-by-bot-table-quizojis',
36+
partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
37+
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
38+
});
39+
2840
const layerLibGL = new lambda.LayerVersion(this, 'jprof-by-bot-lambda-layer-libGL', {
2941
code: lambda.Code.fromAsset('layers/libGL.zip'),
3042
compatibleRuntimes: [lambda.Runtime.JAVA_11],
@@ -50,6 +62,8 @@ export class JProfByBotStack extends cdk.Stack {
5062
'TABLE_VOTES': votesTable.tableName,
5163
'TABLE_YOUTUBE_CHANNELS_WHITELIST': youtubeChannelsWhitelistTable.tableName,
5264
'TABLE_KOTLIN_MENTIONS': kotlinMentionsTable.tableName,
65+
'TABLE_DIALOG_STATES': dialogStatesTable.tableName,
66+
'TABLE_QUIZOJIS': quizojisTable.tableName,
5367
'TOKEN_TELEGRAM_BOT': props.telegramToken,
5468
'TOKEN_YOUTUBE_API': props.youtubeToken,
5569
},
@@ -58,6 +72,8 @@ export class JProfByBotStack extends cdk.Stack {
5872
votesTable.grantReadWriteData(lambdaWebhook);
5973
youtubeChannelsWhitelistTable.grantReadData(lambdaWebhook);
6074
kotlinMentionsTable.grantReadWriteData(lambdaWebhook);
75+
dialogStatesTable.grantReadWriteData(lambdaWebhook);
76+
quizojisTable.grantReadWriteData(lambdaWebhook);
6177

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

.github/workflows/default.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ jobs:
5757
- run: votes/dynamodb/src/test/resources/seed.sh
5858
- run: youtube/dynamodb/src/test/resources/seed.sh
5959
- run: kotlin/dynamodb/src/test/resources/seed.sh
60+
- run: dialogs/dynamodb/src/test/resources/seed.sh
61+
- run: quizoji/dynamodb/src/test/resources/seed.sh
6062
- run: ./gradlew clean dbTest
6163
- uses: actions/upload-artifact@v2
6264
if: always()

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
44

55
plugins {
66
kotlin("jvm").version("1.4.32").apply(false)
7+
kotlin("plugin.serialization").version("1.4.32").apply(false)
78
id("com.github.johnrengelman.shadow").version("6.1.0").apply(false)
89
}
910

dialogs/build.gradle.kts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
plugins {
2+
kotlin("jvm")
3+
kotlin("plugin.serialization")
4+
}
5+
6+
dependencies {
7+
api(libs.tgbotapi.core)
8+
implementation(libs.kotlinx.serialization.core)
9+
}

dialogs/dynamodb/build.gradle.kts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
plugins {
2+
kotlin("jvm")
3+
}
4+
5+
dependencies {
6+
api(project.projects.dialogs)
7+
api(libs.dynamodb)
8+
implementation(project.projects.utils.dynamodb)
9+
implementation(project.projects.utils.tgbotapiSerialization)
10+
implementation(libs.kotlinx.coroutines.jdk8)
11+
implementation(libs.kotlinx.serialization.json)
12+
13+
testImplementation(libs.junit.jupiter.api)
14+
testImplementation(libs.junit.jupiter.params)
15+
testImplementation(libs.aws.junit5.dynamo.v2)
16+
testImplementation(project.projects.utils.awsJunit5)
17+
testRuntimeOnly(libs.junit.jupiter.engine)
18+
}
19+
20+
tasks {
21+
val dbTest by registering(Test::class) {
22+
group = LifecycleBasePlugin.VERIFICATION_GROUP
23+
description = "Runs the DB tests."
24+
shouldRunAfter("test")
25+
outputs.upToDateWhen { false }
26+
useJUnitPlatform {
27+
includeTags("db")
28+
}
29+
}
30+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package by.jprof.telegram.bot.dialogs.dynamodb.dao
2+
3+
import by.jprof.telegram.bot.dialogs.dao.DialogStateDAO
4+
import by.jprof.telegram.bot.dialogs.model.DialogState
5+
import by.jprof.telegram.bot.utils.dynamodb.toAttributeValue
6+
import by.jprof.telegram.bot.utils.dynamodb.toString
7+
import by.jprof.telegram.bot.utils.tgbotapi_serialization.TgBotAPI
8+
import kotlinx.coroutines.Dispatchers
9+
import kotlinx.coroutines.future.await
10+
import kotlinx.coroutines.withContext
11+
import kotlinx.serialization.decodeFromString
12+
import kotlinx.serialization.encodeToString
13+
import kotlinx.serialization.json.Json
14+
import kotlinx.serialization.modules.plus
15+
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient
16+
import software.amazon.awssdk.services.dynamodb.model.AttributeValue
17+
18+
class DialogStateDAO(
19+
private val dynamoDb: DynamoDbAsyncClient,
20+
private val table: String
21+
) : DialogStateDAO {
22+
override suspend fun get(chatId: Long, userId: Long): DialogState? {
23+
return withContext(Dispatchers.IO) {
24+
dynamoDb.getItem {
25+
it.tableName(table)
26+
it.key(
27+
mapOf(
28+
"userId" to userId.toAttributeValue(),
29+
"chatId" to chatId.toAttributeValue(),
30+
)
31+
)
32+
}.await()?.item()?.takeUnless { it.isEmpty() }?.toDialogState()
33+
}
34+
}
35+
36+
override suspend fun save(dialogState: DialogState) {
37+
withContext(Dispatchers.IO) {
38+
dynamoDb.putItem {
39+
it.tableName(table)
40+
it.item(dialogState.toAttributes())
41+
}.await()
42+
}
43+
}
44+
45+
override suspend fun delete(chatId: Long, userId: Long) {
46+
withContext(Dispatchers.IO) {
47+
dynamoDb.deleteItem {
48+
it.tableName(table)
49+
it.key(
50+
mapOf(
51+
"userId" to userId.toAttributeValue(),
52+
"chatId" to chatId.toAttributeValue(),
53+
)
54+
)
55+
}
56+
}
57+
}
58+
}
59+
60+
private val json = Json { serializersModule = DialogState.serializers + TgBotAPI.module }
61+
62+
fun Map<String, AttributeValue>.toDialogState(): DialogState = json.decodeFromString(this["value"].toString("value"))
63+
64+
fun DialogState.toAttributes(): Map<String, AttributeValue> = mapOf(
65+
"userId" to this.userId.toAttributeValue(),
66+
"chatId" to this.chatId.toAttributeValue(),
67+
"value" to json.encodeToString(this).toAttributeValue(),
68+
)
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package by.jprof.telegram.bot.dialogs.dynamodb.dao
2+
3+
import by.jprof.telegram.bot.dialogs.model.quizoji.WaitingForQuestion
4+
import by.jprof.telegram.bot.utils.aws_junit5.Endpoint
5+
import kotlinx.coroutines.delay
6+
import kotlinx.coroutines.runBlocking
7+
import kotlinx.coroutines.withTimeout
8+
import me.madhead.aws_junit5.common.AWSClient
9+
import me.madhead.aws_junit5.dynamo.v2.DynamoDB
10+
import org.junit.jupiter.api.*
11+
import org.junit.jupiter.api.extension.ExtendWith
12+
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient
13+
14+
@Tag("db")
15+
@ExtendWith(DynamoDB::class)
16+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
17+
internal class DialogStateDAOTest {
18+
@AWSClient(endpoint = Endpoint::class)
19+
private lateinit var dynamoDB: DynamoDbAsyncClient
20+
private lateinit var sut: DialogStateDAO
21+
22+
@BeforeAll
23+
internal fun setup() {
24+
sut = DialogStateDAO(dynamoDB, "dialog-states")
25+
}
26+
27+
@Test
28+
fun get() = runBlocking {
29+
Assertions.assertEquals(
30+
WaitingForQuestion(
31+
chatId = 1,
32+
userId = 1,
33+
),
34+
sut.get(1, 1)
35+
)
36+
}
37+
38+
@Test
39+
fun save() = runBlocking {
40+
sut.save(
41+
WaitingForQuestion(
42+
chatId = 1,
43+
userId = 2,
44+
)
45+
)
46+
47+
Assertions.assertEquals(
48+
WaitingForQuestion(
49+
chatId = 1,
50+
userId = 2,
51+
),
52+
sut.get(1, 2)
53+
)
54+
}
55+
56+
@Test
57+
fun delete() = runBlocking {
58+
sut.save(
59+
WaitingForQuestion(
60+
chatId = 1,
61+
userId = 3,
62+
)
63+
)
64+
sut.delete(1, 3)
65+
66+
withTimeout(3_000) {
67+
while (null != sut.get(1, 3)) {
68+
delay(100)
69+
}
70+
}
71+
}
72+
73+
@Test
74+
fun getUnexisting() = runBlocking {
75+
Assertions.assertNull(sut.get(0, 0))
76+
}
77+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package by.jprof.telegram.bot.dialogs.dynamodb.dao
2+
3+
import by.jprof.telegram.bot.dialogs.model.quizoji.WaitingForOptions
4+
import by.jprof.telegram.bot.dialogs.model.quizoji.WaitingForQuestion
5+
import dev.inmo.tgbotapi.types.message.content.TextContent
6+
import org.junit.jupiter.api.Assertions
7+
import org.junit.jupiter.api.Test
8+
import software.amazon.awssdk.services.dynamodb.model.AttributeValue
9+
10+
internal class DialogStateTest {
11+
@Test
12+
fun quizojiWaitingForQuestionToAttributes() {
13+
Assertions.assertEquals(
14+
mapOf(
15+
"userId" to AttributeValue.builder().n("2").build(),
16+
"chatId" to AttributeValue.builder().n("1").build(),
17+
"value" to AttributeValue
18+
.builder()
19+
.s("{\"type\":\"WaitingForQuestion\",\"chatId\":1,\"userId\":2}")
20+
.build(),
21+
),
22+
WaitingForQuestion(1, 2).toAttributes()
23+
)
24+
}
25+
26+
@Test
27+
fun quizojiAttributesToWaitingForQuestion() {
28+
Assertions.assertEquals(
29+
WaitingForQuestion(1, 2),
30+
mapOf(
31+
"userId" to AttributeValue.builder().n("2").build(),
32+
"chatId" to AttributeValue.builder().n("1").build(),
33+
"value" to AttributeValue
34+
.builder()
35+
.s("{\"type\":\"WaitingForQuestion\",\"chatId\":1,\"userId\":2}")
36+
.build(),
37+
).toDialogState()
38+
)
39+
}
40+
41+
@Test
42+
fun quizojiWaitingForOptionsToAttributes() {
43+
Assertions.assertEquals(
44+
mapOf(
45+
"userId" to AttributeValue.builder().n("2").build(),
46+
"chatId" to AttributeValue.builder().n("1").build(),
47+
"value" to AttributeValue
48+
.builder()
49+
.s("{\"type\":\"WaitingForOptions\",\"chatId\":1,\"userId\":2,\"question\":{\"type\":\"TextContent\",\"text\":\"Question\"}}")
50+
.build(),
51+
),
52+
WaitingForOptions(1, 2, TextContent("Question")).toAttributes()
53+
)
54+
}
55+
56+
@Test
57+
fun quizojiAttributesToWaitingForOptions() {
58+
Assertions.assertEquals(
59+
WaitingForOptions(1, 2, TextContent("Question")),
60+
mapOf(
61+
"userId" to AttributeValue.builder().n("2").build(),
62+
"chatId" to AttributeValue.builder().n("1").build(),
63+
"value" to AttributeValue
64+
.builder()
65+
.s("{\"type\":\"WaitingForOptions\",\"chatId\":1,\"userId\":2,\"question\":{\"type\":\"TextContent\",\"text\":\"Question\"}}")
66+
.build(),
67+
).toDialogState()
68+
)
69+
}
70+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"dialog-states": [
3+
{
4+
"PutRequest": {
5+
"Item": {
6+
"userId": {
7+
"N": "1"
8+
},
9+
"chatId": {
10+
"N": "1"
11+
},
12+
"value": {
13+
"S": "{\n \"userId\": 1,\n \"chatId\": 1,\n \"type\": \"WaitingForQuestion\"\n}\n"
14+
}
15+
}
16+
}
17+
}
18+
]
19+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"TableName": "dialog-states",
3+
"AttributeDefinitions": [
4+
{
5+
"AttributeName": "chatId",
6+
"AttributeType": "N"
7+
},
8+
{
9+
"AttributeName": "userId",
10+
"AttributeType": "N"
11+
}
12+
],
13+
"KeySchema": [
14+
{
15+
"AttributeName": "userId",
16+
"KeyType": "HASH"
17+
},
18+
{
19+
"AttributeName": "chatId",
20+
"KeyType": "RANGE"
21+
}
22+
],
23+
"ProvisionedThroughput": {
24+
"ReadCapacityUnits": 1,
25+
"WriteCapacityUnits": 1
26+
}
27+
}

0 commit comments

Comments
 (0)