Wire up svelte frontend with ktor backend

This commit is contained in:
Mikołaj Pich 2023-10-29 12:59:42 +01:00
parent 0c02cdbf3f
commit 9b0178f3cb
8 changed files with 46 additions and 95 deletions

View file

@ -4,16 +4,15 @@
import { onMount } from "svelte"; import { onMount } from "svelte";
import Table, { Pagination, Row, Search, Sort } from "./Table.svelte"; import Table, { Pagination, Row, Search, Sort } from "./Table.svelte";
import { getData } from "./server.js"; import { getData } from "./server.js";
import { sortNumber, sortString } from "./sorting.js";
let rows = []; let rows = [];
let page = 0; //first page let page = 0; //first page
let pageIndex = 0; //first row let pageIndex = 0; //first row
let pageSize = 3; //optional, 10 by default let pageSize = 10; //optional, 10 by default
let loading = true; let loading = true;
let rowsCount = 0; let rowsCount = 0;
let text; let text = "";
let sorting; let sorting;
onMount(async () => { onMount(async () => {
@ -57,24 +56,24 @@
<tr> <tr>
<th> <th>
Name Name
<Sort key="name" on:sort={onSort} /> <Sort key="schoolName" on:sort={onSort} />
</th> </th>
<th> <th>
Lastname Short
<Sort key="lastName" on:sort={onSort} /> <Sort key="schoolShort" on:sort={onSort} />
</th> </th>
<th> <th>
Age Address
<Sort key="age" on:sort={onSort} /> <Sort key="schoolAddress" on:sort={onSort} />
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{#each rows2 as row, index (row)} {#each rows2 as row, index (row)}
<Row {index} on:click={() => onCellClick(row)}> <Row {index} on:click={() => onCellClick(row)}>
<td data-label="Name">{row.name}</td> <td data-label="Name">{row.schoolName}</td>
<td data-label="Lastname">{row.lastName}</td> <td data-label="Short">{row.schoolShort}</td>
<td data-label="Age">{row.age}</td> <td data-label="Address">{row.schoolAddress}</td>
</Row> </Row>
{/each} {/each}
</tbody> </tbody>

View file

@ -27,7 +27,7 @@
export let pageIndex = 0; export let pageIndex = 0;
export let pageSize = 10; export let pageSize = 10;
export let responsive = true; export let responsive = true;
export let rows; export let rows = [];
export let serverSide = false; export let serverSide = false;
export let labels = { export let labels = {
empty: "No records available", empty: "No records available",
@ -38,7 +38,7 @@
let buttons = [-2, -1, 0, 1, 2]; let buttons = [-2, -1, 0, 1, 2];
let pageCount = 0; let pageCount = 0;
$: filteredRows = rows; $: filteredRows = rows || [];
$: visibleRows = filteredRows.slice(pageIndex, pageIndex + pageSize); $: visibleRows = filteredRows.slice(pageIndex, pageIndex + pageSize);
setContext("state", { setContext("state", {

View file

@ -1,64 +1,5 @@
import { sortNumber, sortString } from "./sorting.js";
function generateData() {
const rand = Math.floor(Math.random() * 1000);
return [
{ name: "a-" + rand.toString(), lastName: "o", age: 12 },
{ name: "b", lastName: "n", age: 1 },
{ name: "c", lastName: "m", age: 13 },
{ name: "d", lastName: "l", age: 21 },
{ name: "e", lastName: "k", age: 2 },
{ name: "f", lastName: "j", age: 4 }
];
}
export function getAll(text) {
return new Promise((resolve, reject) => {
setTimeout(function() {
resolve(generateData());
}, 500);
});
}
export function getData(page, pageSize, text, sorting) { export function getData(page, pageSize, text, sorting) {
let originalData = generateData(); let options = sorting || { dir: null, key: null };
return fetch(`/log/list?page=${page}&pageSize=${pageSize}&text=${text}&sortBy=${options.key}&order=${options.dir}`)
if (sorting) { .then((res) => res.json());
if (sorting.key === "age") {
originalData = sortNumber(originalData, sorting.dir, sorting.key);
} else {
originalData = sortString(originalData, sorting.dir, sorting.key);
}
}
return new Promise((resolve, reject) => {
setTimeout(function() {
let rowsCount = originalData.length;
const originalRows = originalData;
let rows = [];
if (text && text.length > 0) {
for (let i in originalRows) {
for (let j in originalRows[i]) {
if (
originalRows[i][j]
.toString()
.toLowerCase()
.indexOf(text) > -1
) {
rows.push(originalRows[i]);
break;
}
}
}
rowsCount = rows.length;
} else {
rows = originalRows;
}
resolve({ rows: rows.slice(0, pageSize), rowsCount: rowsCount - 1 });
}, 500);
});
} }

View file

@ -1,13 +0,0 @@
export function sortString(rows, dir, key) {
return rows.sort((a, b) =>
dir === "asc"
? ("" + a[key]).localeCompare(b[key])
: ("" + b[key]).localeCompare(a[key])
);
}
export function sortNumber(rows, dir, key) {
return rows.sort((a, b) =>
dir === "asc" ? a[key] - b[key] : b[key] - a[key]
);
}

View file

@ -39,6 +39,7 @@ dependencies {
implementation("io.ktor:ktor-server-content-negotiation-jvm:$ktor_version") implementation("io.ktor:ktor-server-content-negotiation-jvm:$ktor_version")
implementation("io.ktor:ktor-serialization-kotlinx-json-jvm:$ktor_version") implementation("io.ktor:ktor-serialization-kotlinx-json-jvm:$ktor_version")
implementation("io.ktor:ktor-server-netty-jvm:$ktor_version") implementation("io.ktor:ktor-server-netty-jvm:$ktor_version")
implementation("io.ktor:ktor-server-cors:$ktor_version")
implementation("ch.qos.logback:logback-classic:$logback_version") implementation("ch.qos.logback:logback-classic:$logback_version")
implementation("io.ktor:ktor-server-auth:$ktor_version") implementation("io.ktor:ktor-server-auth:$ktor_version")
implementation("com.google.apis:google-api-services-playintegrity:v1-rev20230910-2.0.0") implementation("com.google.apis:google-api-services-playintegrity:v1-rev20230910-2.0.0")

View file

@ -5,10 +5,7 @@ import io.github.wulkanowy.schools.model.LoginEvent
import io.github.wulkanowy.schools.model.LoginEvents import io.github.wulkanowy.schools.model.LoginEvents
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jetbrains.exposed.sql.ResultRow import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.insertIgnore
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
import java.time.Instant import java.time.Instant
@ -25,8 +22,15 @@ class LoginEventDao {
uuid = row[LoginEvents.uuid], uuid = row[LoginEvents.uuid],
) )
suspend fun allLoginEvents(): List<LoginEvent> = dbQuery { suspend fun allLoginEvents(page: Long, pageSize: Int): List<LoginEvent> = dbQuery {
LoginEvents.selectAll().map(::resultRowToLoginEvent) LoginEvents
.selectAll()
.limit(pageSize, page * pageSize)
.map(::resultRowToLoginEvent)
}
suspend fun getLoginEventsCount(): Long = dbQuery {
LoginEvents.selectAll().count()
} }
suspend fun addLoginEvent(event: LoginEvent) = withContext(Dispatchers.IO) { suspend fun addLoginEvent(event: LoginEvent) = withContext(Dispatchers.IO) {

View file

@ -0,0 +1,9 @@
package io.github.wulkanowy.schools.model
import kotlinx.serialization.Serializable
@Serializable
data class ListResponse(
val rows: List<LoginEvent>,
val rowsCount: Long,
)

View file

@ -2,6 +2,7 @@ package io.github.wulkanowy.schools.plugins
import io.github.wulkanowy.schools.dao.LoginEventDao import io.github.wulkanowy.schools.dao.LoginEventDao
import io.github.wulkanowy.schools.integrity.* import io.github.wulkanowy.schools.integrity.*
import io.github.wulkanowy.schools.model.ListResponse
import io.github.wulkanowy.schools.model.LoginEvent import io.github.wulkanowy.schools.model.LoginEvent
import io.ktor.http.* import io.ktor.http.*
import io.ktor.server.application.* import io.ktor.server.application.*
@ -38,7 +39,16 @@ fun Application.configureRouting() {
} }
} }
get("/log/list") { get("/log/list") {
call.respond(loginEventDao.allLoginEvents()) val params = call.request.queryParameters
call.respond(
ListResponse(
rows = loginEventDao.allLoginEvents(
page = params["page"]?.toLongOrNull() ?: 0,
pageSize = params["pageSize"]?.toIntOrNull() ?: 10,
),
rowsCount = loginEventDao.getLoginEventsCount(),
)
)
} }
singlePageApplication { singlePageApplication {
useResources = true useResources = true