Compare commits
2 commits
f02124cbe8
...
664f9c1777
Author | SHA1 | Date | |
---|---|---|---|
664f9c1777 | |||
e8f816e7ec |
|
@ -90,6 +90,7 @@ kotlin {
|
||||||
implementation "io.ktor:ktor-client-apache:$ktor_version"
|
implementation "io.ktor:ktor-client-apache:$ktor_version"
|
||||||
|
|
||||||
implementation 'org.xerial:sqlite-jdbc:3.25.2'
|
implementation 'org.xerial:sqlite-jdbc:3.25.2'
|
||||||
|
api 'mysql:mysql-connector-java:8.0.16'
|
||||||
implementation 'org.jetbrains.exposed:exposed:0.12.2'
|
implementation 'org.jetbrains.exposed:exposed:0.12.2'
|
||||||
|
|
||||||
implementation 'org.mindrot:jbcrypt:0.4'
|
implementation 'org.mindrot:jbcrypt:0.4'
|
||||||
|
|
|
@ -18,3 +18,9 @@ wiki_url = "https://wiki.kif.rocks/w/index.php?title=KIF470:Arbeitskreise&action
|
||||||
|
|
||||||
[twitter]
|
[twitter]
|
||||||
timeline = "https://twitter.com/kiforbiter?ref_src=twsrc%5Etfw"
|
timeline = "https://twitter.com/kiforbiter?ref_src=twsrc%5Etfw"
|
||||||
|
|
||||||
|
[database]
|
||||||
|
type = "sqlite"
|
||||||
|
url = ""
|
||||||
|
username = ""
|
||||||
|
password = ""
|
||||||
|
|
|
@ -69,6 +69,10 @@ fun checkConstraints(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (schedule.workGroup.interested > schedule.room.places) {
|
||||||
|
errors += ConstraintError("The work group has more interested then the room has places")
|
||||||
|
}
|
||||||
|
|
||||||
for (leader in schedule.workGroup.leader) {
|
for (leader in schedule.workGroup.leader) {
|
||||||
for (s in against) {
|
for (s in against) {
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
package de.kif.frontend
|
package de.kif.frontend
|
||||||
|
|
||||||
|
import de.kif.frontend.repository.RoomRepository
|
||||||
|
import de.kif.frontend.repository.ScheduleRepository
|
||||||
|
import de.kif.frontend.repository.WorkGroupRepository
|
||||||
import de.kif.frontend.views.board.initBoard
|
import de.kif.frontend.views.board.initBoard
|
||||||
import de.kif.frontend.views.calendar.initCalendar
|
import de.kif.frontend.views.calendar.initCalendar
|
||||||
import de.kif.frontend.views.initAnnouncement
|
import de.kif.frontend.views.initAnnouncement
|
||||||
|
@ -61,4 +64,35 @@ fun main() = init {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val url = window.location.pathname
|
||||||
|
if ("brett" in url || "wand" in url) {
|
||||||
|
ScheduleRepository.onCreate {
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
ScheduleRepository.onUpdate {
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
ScheduleRepository.onDelete {
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
RoomRepository.onCreate {
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
RoomRepository.onUpdate {
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
RoomRepository.onDelete {
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
WorkGroupRepository.onCreate {
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
WorkGroupRepository.onUpdate {
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
WorkGroupRepository.onDelete {
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,11 +26,12 @@ class Calendar(calendar: HTMLElement) : View(calendar) {
|
||||||
|
|
||||||
private val htmlBody = document.body ?: createHtmlView()
|
private val htmlBody = document.body ?: createHtmlView()
|
||||||
|
|
||||||
val day = (calendarTable.dataset["day"]?.toIntOrNull() ?: -1).also { println(it) }
|
val day = (calendarTable.dataset["day"]?.toIntOrNull() ?: -1)
|
||||||
val reloadOnFinish = (calendarTable.dataset["reload"]?.toBoolean() ?: false).also { println(it) }
|
val reloadOnFinish = (calendarTable.dataset["reload"]?.toBoolean() ?: false)
|
||||||
val referenceDate = (calendarTable.dataset["reference"]?.toLongOrNull() ?: -1L).also { println(it) }
|
val hideEmpty = (calendarTable.dataset["hide-empty"]?.toBoolean() ?: false)
|
||||||
val nowDate = (calendarTable.dataset["now"]?.toLongOrNull() ?: -1L).also { println(it) }
|
val referenceDate = (calendarTable.dataset["reference"]?.toLongOrNull() ?: -1L)
|
||||||
val timeDifference = (Date.now().toLong() - nowDate).also { println(it) }
|
val nowDate = (calendarTable.dataset["now"]?.toLongOrNull() ?: -1L)
|
||||||
|
val timeDifference = (Date.now().toLong() - nowDate)
|
||||||
|
|
||||||
fun scrollVerticalBy(pixel: Double, scrollBehavior: ScrollBehavior = ScrollBehavior.SMOOTH) {
|
fun scrollVerticalBy(pixel: Double, scrollBehavior: ScrollBehavior = ScrollBehavior.SMOOTH) {
|
||||||
scrollAllVerticalBy(pixel, scrollBehavior)
|
scrollAllVerticalBy(pixel, scrollBehavior)
|
||||||
|
|
|
@ -74,18 +74,12 @@ class CalendarBody(val calendar: Calendar, view: HTMLElement) : ViewCollection<C
|
||||||
min = (min / 60 - 1) * 60
|
min = (min / 60 - 1) * 60
|
||||||
max = (max / 60 + 2) * 60
|
max = (max / 60 + 2) * 60
|
||||||
|
|
||||||
if (min == minTime && max == maxTime) return
|
|
||||||
|
|
||||||
minTime = min
|
minTime = min
|
||||||
maxTime = max
|
maxTime = max
|
||||||
|
|
||||||
min = calendarBodies.map { it.minTime }.min() ?: min
|
min = calendarBodies.map { it.minTime }.min() ?: min
|
||||||
max = calendarBodies.map { it.maxTime }.max() ?: max
|
max = calendarBodies.map { it.maxTime }.max() ?: max
|
||||||
|
|
||||||
calendarBodies.filter { it != this }.forEach {
|
|
||||||
it.updateRows()
|
|
||||||
}
|
|
||||||
|
|
||||||
while (isNotEmpty() && min > first().time) {
|
while (isNotEmpty() && min > first().time) {
|
||||||
remove(first())
|
remove(first())
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import org.w3c.dom.HTMLElement
|
||||||
import org.w3c.dom.HTMLSpanElement
|
import org.w3c.dom.HTMLSpanElement
|
||||||
import org.w3c.dom.set
|
import org.w3c.dom.set
|
||||||
|
|
||||||
class CalendarRow(calendar: CalendarBody, view: HTMLElement) : ViewCollection<CalendarCell>(view) {
|
class CalendarRow(val calendar: CalendarBody, view: HTMLElement) : ViewCollection<CalendarCell>(view) {
|
||||||
val day = calendar.day
|
val day = calendar.day
|
||||||
|
|
||||||
val time = dataset["time"]?.toIntOrNull() ?: 0
|
val time = dataset["time"]?.toIntOrNull() ?: 0
|
||||||
|
@ -49,8 +49,6 @@ class CalendarRow(calendar: CalendarBody, view: HTMLElement) : ViewCollection<Ca
|
||||||
}
|
}
|
||||||
row.html.appendChild(rowHeader)
|
row.html.appendChild(rowHeader)
|
||||||
|
|
||||||
row.html
|
|
||||||
|
|
||||||
val rooms = RoomRepository.all()
|
val rooms = RoomRepository.all()
|
||||||
|
|
||||||
for (room in rooms) {
|
for (room in rooms) {
|
||||||
|
|
|
@ -169,6 +169,10 @@
|
||||||
background-color: var(--table-header-color) !important;
|
background-color: var(--table-header-color) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.calendar-cell[data-empty = "true"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.calendar-tools {
|
.calendar-tools {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -5rem;
|
top: -5rem;
|
||||||
|
|
|
@ -133,6 +133,20 @@ object Configuration {
|
||||||
val time by c(ResoSpec.time)
|
val time by c(ResoSpec.time)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private object DatabaseSpec : ConfigSpec("database") {
|
||||||
|
val type by required<String>()
|
||||||
|
val url by required<String>()
|
||||||
|
val username by required<String>()
|
||||||
|
val password by required<String>()
|
||||||
|
}
|
||||||
|
|
||||||
|
object Database {
|
||||||
|
val type by c(DatabaseSpec.type)
|
||||||
|
val url by c(DatabaseSpec.url)
|
||||||
|
val username by c(DatabaseSpec.username)
|
||||||
|
val password by c(DatabaseSpec.password)
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
var config = Config {
|
var config = Config {
|
||||||
addSpec(ServerSpec)
|
addSpec(ServerSpec)
|
||||||
|
@ -142,6 +156,7 @@ object Configuration {
|
||||||
addSpec(GeneralSpec)
|
addSpec(GeneralSpec)
|
||||||
addSpec(TwitterSpec)
|
addSpec(TwitterSpec)
|
||||||
addSpec(ResoSpec)
|
addSpec(ResoSpec)
|
||||||
|
addSpec(DatabaseSpec)
|
||||||
}.from.toml.resource("portal.toml")
|
}.from.toml.resource("portal.toml")
|
||||||
|
|
||||||
for (file in Files.list(Paths.get("."))) {
|
for (file in Files.list(Paths.get("."))) {
|
||||||
|
|
|
@ -26,14 +26,39 @@ object Connection {
|
||||||
)
|
)
|
||||||
|
|
||||||
fun init() {
|
fun init() {
|
||||||
val dbPath = Configuration.Path.databasePath.toString()
|
val type = Configuration.Database.type
|
||||||
Database.connect("jdbc:sqlite:$dbPath", "org.sqlite.JDBC")
|
val url = Configuration.Database.url
|
||||||
|
val username = Configuration.Database.username
|
||||||
|
val password = Configuration.Database.password
|
||||||
|
when (type) {
|
||||||
|
"mysql" -> {
|
||||||
|
Database.connect(
|
||||||
|
"jdbc:mysql://$url:3306/akplan?user=$username&password=$password&serverTimezone=UTC",
|
||||||
|
"com.mysql.cj.jdbc.Driver",
|
||||||
|
username,
|
||||||
|
password
|
||||||
|
)
|
||||||
|
}
|
||||||
|
"mariadb" -> {
|
||||||
|
Database.connect(
|
||||||
|
"jdbc:mariadb://$url:3306/akplan?user=$username&password=$password&serverTimezone=UTC",
|
||||||
|
"org.mariadb.jdbc.Driver",
|
||||||
|
username,
|
||||||
|
password
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
val dbPath = Configuration.Path.databasePath.toString()
|
||||||
|
Database.connect("jdbc:sqlite:$dbPath", "org.sqlite.JDBC")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TransactionManager.manager.defaultIsolationLevel = TRANSACTION_SERIALIZABLE
|
TransactionManager.manager.defaultIsolationLevel = TRANSACTION_SERIALIZABLE
|
||||||
|
|
||||||
try {
|
try {
|
||||||
create()
|
create()
|
||||||
} catch (e: ExposedSQLException) {
|
} catch (e: ExposedSQLException) {
|
||||||
logger.error { "Cannot initialize the database!" }
|
logger.error(e) { "Cannot initialize the database!" }
|
||||||
|
|
||||||
print("Do you want to recreate the database (you will lose all data)? [y/N]: ")
|
print("Do you want to recreate the database (you will lose all data)? [y/N]: ")
|
||||||
val result = readLine()
|
val result = readLine()
|
||||||
|
|
|
@ -29,7 +29,7 @@ object DbWorkGroup : Table() {
|
||||||
val accessible = bool("accessible")
|
val accessible = bool("accessible")
|
||||||
|
|
||||||
val language = enumeration("language", Language::class)
|
val language = enumeration("language", Language::class)
|
||||||
val leader = text("leader").default("[]")
|
val leader = text("leader")
|
||||||
|
|
||||||
val length = integer("length")
|
val length = integer("length")
|
||||||
val constraints = text("constraints")
|
val constraints = text("constraints")
|
||||||
|
@ -49,9 +49,9 @@ object DbRoom : Table() {
|
||||||
val whiteboard = bool("whiteboard")
|
val whiteboard = bool("whiteboard")
|
||||||
val blackboard = bool("blackboard")
|
val blackboard = bool("blackboard")
|
||||||
val accessible = bool("accessible")
|
val accessible = bool("accessible")
|
||||||
val pool = bool("pool").default(false)
|
val pool = bool("pool")
|
||||||
|
|
||||||
val blocked = text("blocked").default("[]")
|
val blocked = text("blocked")
|
||||||
|
|
||||||
val createdAt = long("createdAt")
|
val createdAt = long("createdAt")
|
||||||
val updatedAt = long("updatedAt")
|
val updatedAt = long("updatedAt")
|
||||||
|
@ -63,8 +63,8 @@ object DbSchedule : Table() {
|
||||||
val roomId = long("room_id").index()
|
val roomId = long("room_id").index()
|
||||||
val day = integer("day").index()
|
val day = integer("day").index()
|
||||||
val time = integer("time_slot")
|
val time = integer("time_slot")
|
||||||
val lockRoom = bool("lock_room").default(false)
|
val lockRoom = bool("lock_room")
|
||||||
val lockTime = bool("lock_time").default(false)
|
val lockTime = bool("lock_time")
|
||||||
|
|
||||||
val createdAt = long("createdAt")
|
val createdAt = long("createdAt")
|
||||||
val updatedAt = long("updatedAt")
|
val updatedAt = long("updatedAt")
|
||||||
|
|
|
@ -6,10 +6,12 @@ import de.kif.backend.prefix
|
||||||
import de.kif.backend.repository.*
|
import de.kif.backend.repository.*
|
||||||
import de.kif.backend.route.api.error
|
import de.kif.backend.route.api.error
|
||||||
import de.kif.backend.util.Backup
|
import de.kif.backend.util.Backup
|
||||||
|
import de.kif.backend.util.PushService
|
||||||
import de.kif.backend.util.WikiImporter
|
import de.kif.backend.util.WikiImporter
|
||||||
import de.kif.backend.view.respondMain
|
import de.kif.backend.view.respondMain
|
||||||
import de.kif.common.RepositoryType
|
import de.kif.common.RepositoryType
|
||||||
import de.kif.common.model.Permission
|
import de.kif.common.model.Permission
|
||||||
|
import de.kif.common.model.Post
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.http.ContentType
|
import io.ktor.http.ContentType
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
|
@ -51,6 +53,10 @@ fun Route.account() {
|
||||||
+"Sicherung"
|
+"Sicherung"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a(href = "$prefix/account/reload", classes = "form-btn") {
|
||||||
|
+"Alle Geräte neu laden"
|
||||||
|
}
|
||||||
|
|
||||||
if (user.checkPermission(Permission.SCHEDULE)) {
|
if (user.checkPermission(Permission.SCHEDULE)) {
|
||||||
a(href = "$prefix/account/import", classes = "form-btn") {
|
a(href = "$prefix/account/import", classes = "form-btn") {
|
||||||
+"Aus Wiki importieren"
|
+"Aus Wiki importieren"
|
||||||
|
@ -516,4 +522,8 @@ fun Route.account() {
|
||||||
call.error(HttpStatusCode.Unauthorized)
|
call.error(HttpStatusCode.Unauthorized)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
get("/account/reload") {
|
||||||
|
PushService.signature = Post.generateUrl()
|
||||||
|
call.respondRedirect("$prefix/account")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -175,7 +175,8 @@ fun Route.board() {
|
||||||
max,
|
max,
|
||||||
rooms,
|
rooms,
|
||||||
schedules,
|
schedules,
|
||||||
reloadAfterFinish = true
|
reloadAfterFinish = true,
|
||||||
|
hideEmptyRooms = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
div("board-twitter") {
|
div("board-twitter") {
|
||||||
|
|
|
@ -72,7 +72,8 @@ fun DIV.renderCalendar(
|
||||||
to: Int,
|
to: Int,
|
||||||
rooms: List<Room>,
|
rooms: List<Room>,
|
||||||
schedules: Map<Room, Map<Int, List<Schedule>>>,
|
schedules: Map<Room, Map<Int, List<Schedule>>>,
|
||||||
reloadAfterFinish: Boolean = false
|
reloadAfterFinish: Boolean = false,
|
||||||
|
hideEmptyRooms: Boolean = false
|
||||||
) {
|
) {
|
||||||
val gridLabelWidth = 60
|
val gridLabelWidth = 60
|
||||||
val minutesOfDay = to - from
|
val minutesOfDay = to - from
|
||||||
|
@ -91,6 +92,7 @@ fun DIV.renderCalendar(
|
||||||
attributes["data-reference"] = Configuration.Schedule.referenceDate.time.toString()
|
attributes["data-reference"] = Configuration.Schedule.referenceDate.time.toString()
|
||||||
attributes["data-now"] = now.timeInMillis.toString()
|
attributes["data-now"] = now.timeInMillis.toString()
|
||||||
attributes["data-reload"] = reloadAfterFinish.toString()
|
attributes["data-reload"] = reloadAfterFinish.toString()
|
||||||
|
attributes["data-hide-empty"] = hideEmptyRooms.toString()
|
||||||
|
|
||||||
div("calendar-table-box ${orientation.name.toLowerCase().replace("_", "-")}") {
|
div("calendar-table-box ${orientation.name.toLowerCase().replace("_", "-")}") {
|
||||||
div("calendar-header") {
|
div("calendar-header") {
|
||||||
|
@ -103,6 +105,7 @@ fun DIV.renderCalendar(
|
||||||
for (room in rooms) {
|
for (room in rooms) {
|
||||||
div("calendar-cell") {
|
div("calendar-cell") {
|
||||||
attributes["data-room"] = room.id.toString()
|
attributes["data-room"] = room.id.toString()
|
||||||
|
attributes["data-empty"] = (hideEmptyRooms && room !in schedules).toString()
|
||||||
|
|
||||||
span {
|
span {
|
||||||
+room.name
|
+room.name
|
||||||
|
@ -147,6 +150,7 @@ fun DIV.renderCalendar(
|
||||||
div("calendar-cell") {
|
div("calendar-cell") {
|
||||||
attributes["data-room"] = room.id.toString()
|
attributes["data-room"] = room.id.toString()
|
||||||
attributes["data-blocked"] = blocked.toString()
|
attributes["data-blocked"] = blocked.toString()
|
||||||
|
attributes["data-empty"] = (hideEmptyRooms && room !in schedules).toString()
|
||||||
|
|
||||||
title = room.name + " - " + timeString
|
title = room.name + " - " + timeString
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
package de.kif.backend.route
|
package de.kif.backend.route
|
||||||
|
|
||||||
import com.soywiz.klock.*
|
|
||||||
import com.soywiz.klock.locale.german
|
|
||||||
import de.kif.backend.Configuration
|
import de.kif.backend.Configuration
|
||||||
import de.kif.backend.repository.RoomRepository
|
import de.kif.backend.repository.RoomRepository
|
||||||
import de.kif.backend.repository.ScheduleRepository
|
import de.kif.backend.repository.ScheduleRepository
|
||||||
import de.kif.backend.view.respondMain
|
import de.kif.backend.view.respondMain
|
||||||
|
import de.kif.common.formatDate
|
||||||
|
import de.kif.common.formatDateWithoutYear
|
||||||
import de.kif.common.model.Room
|
import de.kif.common.model.Room
|
||||||
import de.kif.common.model.Schedule
|
import de.kif.common.model.Schedule
|
||||||
import io.ktor.routing.Route
|
import io.ktor.routing.Route
|
||||||
|
@ -63,6 +63,8 @@ fun Route.wall() {
|
||||||
wallStart + 2
|
wallStart + 2
|
||||||
).map { genWallData(it) }
|
).map { genWallData(it) }
|
||||||
|
|
||||||
|
val rooms = RoomRepository.all()
|
||||||
|
|
||||||
var min = days.mapNotNull { it.min }.min() ?: 12 * 60
|
var min = days.mapNotNull { it.min }.min() ?: 12 * 60
|
||||||
val max = days.mapNotNull { it.max }.max() ?: 12 * 60
|
val max = days.mapNotNull { it.max }.max() ?: 12 * 60
|
||||||
|
|
||||||
|
@ -70,18 +72,15 @@ fun Route.wall() {
|
||||||
min = max
|
min = max
|
||||||
}
|
}
|
||||||
|
|
||||||
val refDate = DateTime(Configuration.Schedule.referenceDate.time)
|
val refDate = Configuration.Schedule.referenceDate.time
|
||||||
|
|
||||||
respondMain(true, true) {
|
respondMain(true, true) {
|
||||||
content {
|
content {
|
||||||
div("wall") {
|
div("wall") {
|
||||||
for (day in days) {
|
for (day in days) {
|
||||||
|
|
||||||
val date = refDate + day.number.days
|
val date = refDate + (day.number * 1000 * 60 * 60 * 24)
|
||||||
val dateString = DateFormat("EEEE, d. MMMM")
|
val dateString = formatDateWithoutYear(date, Configuration.Schedule.offset)
|
||||||
.withLocale(KlockLocale.german)
|
|
||||||
.format(date)
|
|
||||||
|
|
||||||
|
|
||||||
div("wall-box") {
|
div("wall-box") {
|
||||||
div("wall-name") {
|
div("wall-name") {
|
||||||
|
@ -95,8 +94,9 @@ fun Route.wall() {
|
||||||
day.number,
|
day.number,
|
||||||
min,
|
min,
|
||||||
max,
|
max,
|
||||||
day.schedules.keys.toList().sortedBy { it.id },
|
rooms,
|
||||||
day.schedules
|
day.schedules,
|
||||||
|
hideEmptyRooms = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ import kotlin.concurrent.thread
|
||||||
object PushService {
|
object PushService {
|
||||||
|
|
||||||
internal var leastValidTimestamp = System.currentTimeMillis()
|
internal var leastValidTimestamp = System.currentTimeMillis()
|
||||||
val signature = Post.generateUrl()
|
var signature = Post.generateUrl()
|
||||||
|
|
||||||
private val messages: MutableList<Pair<Long, Message>> = mutableListOf()
|
private val messages: MutableList<Pair<Long, Message>> = mutableListOf()
|
||||||
|
|
||||||
|
|
|
@ -32,3 +32,9 @@ backup_interval = 3600000
|
||||||
|
|
||||||
[twitter]
|
[twitter]
|
||||||
timeline = ""
|
timeline = ""
|
||||||
|
|
||||||
|
[database]
|
||||||
|
type = "sqlite"
|
||||||
|
url = ""
|
||||||
|
username = ""
|
||||||
|
password = ""
|
||||||
|
|
Loading…
Reference in a new issue