35 Commits

Author SHA1 Message Date
cd64ec5c18 fixed getRoomName() 2022-04-30 20:10:55 +02:00
3556b74065 getSeatsLeft() re-implemented 2022-04-30 19:54:09 +02:00
6c43cb0352 Places available renamed to seats left 2022-04-30 00:44:46 +02:00
316723a9ac Finished cancelTravel use case 2022-04-28 00:15:16 +02:00
cc1de1d2d2 cancelTravel: WIP 2022-04-23 23:41:09 +02:00
eb45ea10d6 cancelTravel: WIP 2022-04-21 00:58:25 +02:00
89b6c90b65 Usecase.updateRoomNameInfo() 2022-04-18 23:04:30 +02:00
c0a149cadd MatrixApiClient.getAvailablePlaces() 2022-04-18 16:58:39 +02:00
c4e9bd7841 MatrixApiClient.client 2022-04-18 14:03:36 +02:00
e900c834dc MatrixApiClient.sendErrorTextMessage() 2022-04-18 03:40:59 +02:00
1bc59e8539 leaveTravel() 2022-04-18 01:45:11 +02:00
a4c84879d1 joinTravel() finished 2022-04-18 00:34:59 +02:00
52781d2862 WIP: joinTravel 2022-04-17 20:53:59 +02:00
6650603814 doc: moved docker instructions to main README 2022-04-17 15:47:13 +02:00
11122d350e TravelEventContentSerializerMappings simplified 2022-04-17 15:25:56 +02:00
86629b97dc Updated trixnity to 2.0.0-RC2, runtime errors present 2022-04-17 01:55:46 +02:00
2e084a1fdb Send room creation events to main room 2022-04-17 00:58:45 +02:00
caf551c1be Element config 2022-04-16 21:35:12 +02:00
e37e764c2a improvements in docs and null pointer fix 2022-04-16 20:46:30 +02:00
9f8d59b7a9 Powerlevels 2022-03-21 23:29:17 +01:00
0494a06e9c Use system timezone 2022-03-18 23:53:02 +01:00
ea2349759c Create travel 2022-03-18 00:02:32 +01:00
869d37c5d1 refactor 2022-03-16 00:23:52 +01:00
a2b50621f3 Refactor 2022-03-14 23:25:33 +01:00
d0f502c92f Upgraded to Trixniti 1.2.0-RC1 2022-03-13 16:33:24 +01:00
950b9cc542 Config file reworked 2022-03-11 00:08:50 +01:00
aa16be7722 Working Exposed and yaml config 2022-03-09 00:16:30 +01:00
59e4bcf36e Exposed WIP 2022-03-08 00:33:39 +01:00
77f3ab0ed9 Begin of command parser 2022-03-07 00:18:52 +01:00
6ee9887055 Exposed 2022-03-06 00:32:47 +01:00
8597d14408 Create public room 2022-03-02 00:19:26 +01:00
ea53b09bf3 learning api 2022-02-27 00:21:35 +01:00
e4e38d07fd Moved services each to own class 2022-02-25 01:17:20 +01:00
ead4876347 Synapse Dockerfile 2022-02-24 22:59:13 +01:00
a9a854cba0 Deleted Spring Boot, migrated to Trixnity 2022-02-08 19:33:01 +01:00
44 changed files with 1478 additions and 404 deletions

6
.gitignore vendored
View File

@@ -6,8 +6,10 @@ build/
!**/src/test/**/build/
# App service DB
matrix.mv.db
matrix.trace.db
okupamicoche.mv.db
# Synapse data
docker/synapse/data
### STS ###
.apt_generated

View File

@@ -1,3 +1,29 @@
# Okupa mi coche - App Service
Matrix Application Service for Okupa mi coche
Matrix Application Service for Okupa mi coche
## Setup
1. Install Docker in local machine
2. Add following line to /etc/hosts
```
127.0.0.1 okupamicoche-synapse
```
3. Go to `docker/synapse` and run `docker-compose up`
## Run
1. Start synapse: `docker restart okupamicoche-synapse`
2. Run app service: `./gradlew run`
## Launch Element with dev profile
1. Copy `docker/element/config.json` to `~/.config/Element-omc-dev/config.json`.
2. Launch new Element instance with `element-desktop --profile omc-dev`.
3. Send `/devtools` to any Element room. Enable `Developer mode` and `Show hidden events in timeline`.
Step 1 and 3 are only required in first run.
- Homeserver: `http://okupamicoche-synapse:8008`

View File

@@ -1,40 +1,61 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.springframework.boot") version "2.4.13"
id("io.spring.dependency-management") version "1.0.11.RELEASE"
kotlin("jvm") version "1.4.32"
kotlin("plugin.spring") version "1.4.32"
application
kotlin("jvm") version "1.6.20"
kotlin("plugin.serialization") version "1.6.20"
}
group = "eu.fosil"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_11
version = "0.0.1"
repositories {
mavenCentral()
mavenCentral()
maven { url = uri("https://maven.pkg.jetbrains.space/public/p/ktor/eap") }
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
implementation ("net.folivo:matrix-spring-boot-bot:0.4.8")
// implementation ("net.folivo:matrix-spring-boot-bot:0.5.2")
runtimeOnly("com.h2database:h2")
runtimeOnly("io.r2dbc:r2dbc-h2")
developmentOnly("org.springframework.boot:spring-boot-devtools")
testImplementation("org.springframework.boot:spring-boot-starter-test")
// Ktor
val ktorVersion: String by project
implementation("io.ktor:ktor-server-netty:$ktorVersion")
implementation("io.ktor:ktor-client-cio:$ktorVersion")
// Trixnity
implementation("net.folivo:trixnity-applicationservice:2.0.0-RC5")
// Exposed
val exposedVersion: String by project
dependencies {
implementation("org.jetbrains.exposed:exposed-core:$exposedVersion")
implementation("org.jetbrains.exposed:exposed-dao:$exposedVersion")
implementation("org.jetbrains.exposed:exposed-jdbc:$exposedVersion")
}
// H2 database
implementation("com.h2database:h2:2.1.212")
// Jackson (YAML parser)
val jacksonVersion: String by project
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion")
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:$jacksonVersion")
// Logger
implementation("io.github.microutils:kotlin-logging-jvm:2.1.21")
implementation("ch.qos.logback:logback-classic:1.2.11")
// JUnit
testImplementation("org.jetbrains.kotlin:kotlin-test-junit:1.6.10")
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "11"
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "11"
}
}
tasks.withType<Test> {
useJUnitPlatform()
tasks.test {
useJUnitPlatform()
}
application {
mainClass.set("eu.fosil.okupamicoche.MainKt")
}

View File

@@ -1,32 +0,0 @@
# Okupa mi coche - Docker containers for the backend
Guide for setting up a local development environment for the backend.
## Setup
1. Install Docker in local machine
2. Add following line to /etc/hosts
```
127.0.0.1 okupamicoche-synapse
```
5. Generate data folder for Synapse
```
cd docker/synape
docker run -it --rm \
--mount type=volume,src=synapse-data,dst=/data \
-e SYNAPSE_SERVER_NAME=okupamicoche-synapse \
-e SYNAPSE_REPORT_STATS=no \
matrixdotorg/synapse:latest generate
```
6. Run dockerized Synapse
```
docker run --name okupamicoche-synapse -p 8008:8008 --mount type=volume,src=synapse-data,dst=/data \
-e SYNAPSE_CONFIG_PATH=/homeserver.yaml \
-v $(pwd)/homeserver.yaml:/homeserver.yaml -v $(pwd)/okupamicoche-appservice.yaml:/okupamicoche-appservice.yaml \
matrixdotorg/synapse:latest
```
## Run
`docker start okupamicoche-synapse`
## Inspect container
`docker exec -t -i okupamicoche-synapse /bin/bash`

View File

@@ -0,0 +1,46 @@
{
"brand": "Okupamicoche",
"branding": {
"authFooterLinks": null,
"authHeaderLogoUrl": "welcome/images/logo.svg",
"welcomeBackgroundUrl": null
},
"bug_report_endpoint_url": "https://element.io/bugreports/submit",
"default_server_config": {
"m.homeserver": {
"base_url": "http://okupamicoche-synapse:8008",
"server_name": "okupamicoche"
},
"m.identity_server": {
"base_url": ""
}
},
"default_theme": "light",
"disable_custom_urls": true,
"disable_guests": true,
"embeddedPages": {
"homeUrl": ""
},
"enable_presence_by_hs_url": "\n",
"integrations_jitsi_widget_url": "https://scalar.vector.im/api/widgets/jitsi.html",
"integrations_rest_url": "https://scalar.vector.im/api",
"integrations_ui_url": "https://scalar.vector.im/",
"integrations_widgets_urls": [
"https://scalar.vector.im/api"
],
"jitsi": {
"preferredDomain": "meet.fosil.eu"
},
"permalinkPrefix": "https://matrix.to",
"roomDirectory": {
"servers": [
"matrix.org"
]
},
"settingDefaults": {
"custom_themes": []
},
"showLabsSettings": true,
"welcomeUserId": null,
"dangerously_allow_unsafe_and_insecure_passwords": true
}

View File

@@ -1,27 +1,27 @@
server_name: "okupamicoche-synapse"
pid_file: /data/homeserver.pid
public_baseurl: http://okupamicoche-synapse:8008/
listeners:
- port: 8008
tls: false
type: http
x_forwarded: true
resources:
- names: [ client, federation ]
- names: [client, federation]
compress: false
database:
name: sqlite3
args:
database: /data/homeserver.db
enable_registration: true
log_config: "/data/okupamicoche-synapse.log.config"
log_config: "/config/localhost.log.yaml"
media_store_path: "/data/media_store"
registration_shared_secret: "Y_XNuno*Dh,T2IpHA;i,bWF^fg&x.*t=iEz*@:y5REBMhgCA63"
report_stats: false
signing_key_path: "/config/localhost.signing.key"
macaroon_secret_key: "6VvBQj_TedGcDDB_z,-qXV1W3:.CXrRG6AWF&4p:~iGNguy&_h"
form_secret: "FM,2TSq++sZ@Tl0atcQP"
signing_key_path: "/data/okupamicoche-synapse.signing.key"
trusted_key_servers:
- server_name: "matrix.org"
report_stats: false
suppress_key_server_warning: true
enable_registration: true
app_service_config_files:
- /okupamicoche-appservice.yaml
- /config/okupamicoche-appservice.yaml
enable_registration_without_verification: true

View File

@@ -0,0 +1,33 @@
version: 1
formatters:
precise:
format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s'
filters:
context:
(): synapse.util.logcontext.LoggingContextFilter
request: ""
handlers:
console:
class: logging.StreamHandler
formatter: precise
filters: [context]
loggers:
synapse:
level: INFO
synapse.storage.SQL:
# beware: increasing this to DEBUG will make synapse log sensitive
# information such as access tokens.
level: WARNING
rest_auth_provider:
level: INFO
root:
level: WARNING
handlers: [console]

View File

@@ -0,0 +1 @@
ed25519 a_EgjW U0b5hmg9zXoLxAZFVDLTvtggKw+vkZQepCgjL8ZYRfI

View File

@@ -1,12 +1,11 @@
id: "Okupa mi coche"
url: "http://172.17.0.1:8080"
#url: "http://172.17.0.1:8081"
as_token: "30c05ae90a248a4188e620216fa72e349803310ec83e2a77b34fe90be6081f46"
hs_token: "312df522183efd404ec1cd22d2ffa4bbc76a8c1ccf541dd692eef281356bb74e"
sender_localpart: "okupamicoche"
namespaces:
users: [ ]
aliases:
- regex: "#viaje_.*"
exclusive: false
- regex: "#viaje.*"
exclusive: true
rooms: [ ]

View File

@@ -0,0 +1,19 @@
version: "3.3"
services:
synapse:
image: "matrixdotorg/synapse:latest"
container_name: "okupamicoche-synapse"
volumes:
- "./data:/data"
- "./config:/config"
environment:
VIRTUAL_HOST: "localhost"
VIRTUAL_PORT: 8008
SYNAPSE_SERVER_NAME: "okupamicoche-synapse"
SYNAPSE_REPORT_STATS: "no"
SYNAPSE_CONFIG_DIR: "/config"
GUI: 1002
UID: 1002
ports:
- "8008:8008"

4
gradle.properties Normal file
View File

@@ -0,0 +1,4 @@
kotlin.code.style=official
exposedVersion=0.37.3
ktorVersion=2.0.0
jacksonVersion=2.13.2

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

257
gradlew vendored
View File

@@ -1,7 +1,7 @@
#!/bin/sh
#!/usr/bin/env sh
#
# Copyright © 2015-2021 the original authors.
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -17,101 +17,67 @@
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
MAX_FD="maximum"
warn () {
echo "$*"
} >&2
}
die () {
echo
echo "$*"
echo
exit 1
} >&2
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MSYS* | MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@@ -121,9 +87,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD=$JAVA_HOME/bin/java
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -132,7 +98,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
@@ -140,95 +106,80 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=$( cygpath --unix "$JAVACMD" )
JAVACMD=`cygpath --unix "$JAVACMD"`
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

View File

@@ -1,4 +1 @@
rootProject.name = "okupamicoche"
// Include matrix-spring-boot-sdk for local development of the sdk
//includeBuild("../matrix-spring-boot-sdk")
rootProject.name = "eu.fosil.okupamicoche"

View File

@@ -1,11 +0,0 @@
package eu.fosil
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class OkupaMiCocheAppServiceApplication
fun main(args: Array<String>) {
runApplication<OkupaMiCocheAppServiceApplication>(*args)
}

View File

@@ -1,79 +0,0 @@
//package eu.fosil
//
//import kotlinx.coroutines.flow.collect
//import kotlinx.coroutines.flow.filter
//import net.folivo.spring.matrix.bot.event.MatrixMessageHandler
//import net.folivo.spring.matrix.bot.event.MessageContext
//import net.folivo.spring.matrix.bot.user.MatrixUserService
//import net.folivo.spring.matrix.bot.util.BotServiceHelper
//import net.folivo.trixnity.client.rest.MatrixClient
//import net.folivo.trixnity.core.model.events.m.room.MessageEventContent
//import org.slf4j.LoggerFactory
//import org.springframework.stereotype.Component
//
//@Component
//class PingHandler(
// private val matrixClient: MatrixClient,
// private val helper: BotServiceHelper,
// private val userService: MatrixUserService
//) : MatrixMessageHandler {
// companion object {
// private val LOG = LoggerFactory.getLogger(this::class.java)
// }
//
// override suspend fun handleMessage(content: MessageEventContent, context: MessageContext) {
// println("EOEO")
// LOG.debug(content.body)
// LOG.debug(context.roomId.full)
// if (content is MessageEventContent.TextMessageEventContent) {
// if (content.body.contains("ping")) {
// userService.getUsersByRoom(context.roomId)
// .filter { it.isManaged }
// .collect { member ->
// val messageId = context.answer("pong", asUserId = member.id)
// LOG.info("pong (messageid: $messageId)")
// }
// }
// }
// }
//}
package eu.fosil
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.filter
import net.folivo.matrix.bot.event.MatrixMessageHandler
import net.folivo.matrix.bot.event.MessageContext
import net.folivo.matrix.bot.user.MatrixUserService
import net.folivo.matrix.bot.util.BotServiceHelper
import net.folivo.matrix.core.model.events.m.room.message.MessageEvent
import net.folivo.matrix.restclient.MatrixClient
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component
@Component
class PingHandler(
private val matrixClient: MatrixClient,
private val helper: BotServiceHelper,
private val userService: MatrixUserService
) : MatrixMessageHandler {
companion object {
private val LOG = LoggerFactory.getLogger(this::class.java)
}
override suspend fun handleMessage(content: MessageEvent.MessageEventContent, context: MessageContext) {
println("handle message content=${content.body}")
userService.getUsersByRoom(context.roomId).collect { member ->
println(member.id)
println(member.isManaged)
}
if (content.body.contains("ping")) {
userService.getUsersByRoom(context.roomId).filter { it.isManaged }
.collect { member ->
val messageId = context.answer("pong", asUserId = member.id)
LOG.info("pong (messageid: $messageId)")
}
}
}
}

View File

@@ -0,0 +1,29 @@
package eu.fosil.okupamicoche
import eu.fosil.okupamicoche.config.ConfigReader
import eu.fosil.okupamicoche.matrix.MatrixApiClient
import eu.fosil.okupamicoche.matrix.createAppService
import eu.fosil.okupamicoche.matrix.event.TravelEventContentSerializerMappings
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import net.folivo.trixnity.applicationserviceapi.server.matrixApplicationServiceApiServer
suspend fun main() {
// Load config from file
val config = ConfigReader.load()
requireNotNull(config)
// Init matrix client
MatrixApiClient.init(config)
// Start Ktor server
embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
matrixApplicationServiceApiServer(
hsToken = config.tokens.homeserver,
applicationServiceApiServerHandler = createAppService(MatrixApiClient.client),
customMappings = TravelEventContentSerializerMappings
)
}.start(wait = true)
}

View File

@@ -0,0 +1,83 @@
package eu.fosil.okupamicoche.cli
import eu.fosil.okupamicoche.matrix.MatrixApiClient
import eu.fosil.okupamicoche.model.TravelOptions
import eu.fosil.okupamicoche.usecase.Usecase
import eu.fosil.okupamicoche.usecase.travel.cancelTravel
import eu.fosil.okupamicoche.usecase.travel.createTravel
import eu.fosil.okupamicoche.usecase.travel.joinTravel
import eu.fosil.okupamicoche.usecase.travel.leaveTravel
import mu.KotlinLogging
import net.folivo.trixnity.core.model.RoomId
import net.folivo.trixnity.core.model.UserId
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
private val logger = KotlinLogging.logger {}
object CommandParser {
fun isCommand(body: String) = body.matches("![a-zA-Z]+( .*)?".toRegex())
suspend fun parse(
user: UserId,
room: RoomId,
body: String
) {
val words = Regex("[^!\\s]+").findAll(body)
val command = words.first().value
val args = words.drop(1).toList().map { sequence -> sequence.value }
handleCommand(user, room, command, args)
}
private suspend fun handleCommand(
user: UserId,
room: RoomId,
command: String,
args: List<String>
) {
logger.info("command=$command args=$args")
try {
when (command) {
"create" -> handleCreate(user, room, args)
"join" -> Usecase.joinTravel(room, user, args)
"leave" -> Usecase.leaveTravel(room, user)
"cancel" -> Usecase.cancelTravel(room, user)
// TODO help
}
} catch (e: Exception) {
logger.error { "Exception captured: $e" }
e.printStackTrace()
MatrixApiClient.sendErrorTextMessage(
room,
"Error executing command: $e",
"Error executing command: <code>$e<code>"
)
}
}
private suspend fun handleCreate(
userId: UserId,
roomId: RoomId,
args: List<String>
) {
// TODO check arguments and show help on error
val date = args[2]
val time = args[3]
val formatter = DateTimeFormatter.ofPattern("yyyy/M/d H:mm").withZone(ZoneId.systemDefault())
val unixTime = Instant.from(formatter.parse("$date $time")).toEpochMilli()
val travelOptions = TravelOptions(
from = args[0],
to = args[1],
time = unixTime,
seats = args[4].toInt(),
description = args.subList(5, args.size).joinToString(" ")
)
Usecase.createTravel(travelOptions, userId, roomId)
}
}

View File

@@ -0,0 +1,18 @@
package eu.fosil.okupamicoche.config
data class Config(
val homeserver: HomeserverConfig,
val tokens: TokensConfig,
val mainRoom: String
)
data class HomeserverConfig(
val host: String,
val secure: Boolean,
val port: Int
)
data class TokensConfig(
val appService: String,
val homeserver: String
)

View File

@@ -0,0 +1,40 @@
package eu.fosil.okupamicoche.config
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException
import mu.KotlinLogging
import java.nio.file.Files
import kotlin.io.path.Path
private val logger = KotlinLogging.logger {}
object ConfigReader {
var config: Config? = null
fun load(): Config? {
val path = this.javaClass.getResource("/config.yaml")?.path
val mapper = ObjectMapper(YAMLFactory())
mapper.registerModule(KotlinModule())
if (path == null) {
logger.error { "Error getting config path" }
return null
}
config = try {
Files.newBufferedReader(Path(path)).use {
mapper.readValue(it, Config::class.java)
}
} catch (exception: MissingKotlinParameterException) {
logger.error("Could not read YAML file!")
logger.error(exception.message)
null
}
return config
}
}

View File

@@ -0,0 +1,39 @@
package eu.fosil.okupamicoche.db
import eu.fosil.okupamicoche.model.Travel
import org.jetbrains.exposed.dao.IntEntity
import org.jetbrains.exposed.dao.IntEntityClass
import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.dao.id.IntIdTable
object TravelsTable : IntIdTable() {
val roomId = varchar("room_id", 255)
val driver = varchar("driver", 255)
val from = varchar("from", 255)
val to = varchar("to", 255)
val time = long("time")
val seats = integer("seats")
val description = varchar("description", 1023)
}
class TravelEntity(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<TravelEntity>(TravelsTable) {
fun new(travel: Travel) = TravelEntity.new {
this.roomId = travel.room.full
this.driver = travel.driver.full
this.from = travel.options.from
this.to = travel.options.to
this.time = travel.options.time
this.seats = travel.options.seats
this.description = travel.options.description
}
}
var roomId by TravelsTable.roomId
var driver by TravelsTable.driver
var from by TravelsTable.from
var to by TravelsTable.to
var time by TravelsTable.time
var seats by TravelsTable.seats
var description by TravelsTable.description
}

View File

@@ -0,0 +1,110 @@
package eu.fosil.okupamicoche.matrix
import eu.fosil.okupamicoche.config.Config
import eu.fosil.okupamicoche.matrix.event.TravelEventContentSerializerMappings
import io.ktor.http.*
import mu.KotlinLogging
import net.folivo.trixnity.clientserverapi.client.MatrixClientServerApiClient
import net.folivo.trixnity.clientserverapi.model.rooms.DirectoryVisibility
import net.folivo.trixnity.core.model.RoomAliasId
import net.folivo.trixnity.core.model.RoomId
import net.folivo.trixnity.core.model.UserId
import net.folivo.trixnity.core.model.events.m.room.NameEventContent
import net.folivo.trixnity.core.model.events.m.room.RoomMessageEventContent
import net.folivo.trixnity.core.serialization.createEventContentSerializerMappings
import net.folivo.trixnity.core.serialization.createMatrixJson
private val logger = KotlinLogging.logger {}
object MatrixApiClient {
var appServiceUserId: UserId? = null
var mainRoomId: RoomId? = null
var client = MatrixClientServerApiClient()
private var config: Config? = null
suspend fun init(config: Config) {
this.config = config
client = MatrixClientServerApiClient(
baseUrl = Url(
(if (config.homeserver.secure) "https" else "http") +
"://${config.homeserver.host}:${config.homeserver.port}"
),
json = createMatrixJson(createEventContentSerializerMappings(TravelEventContentSerializerMappings)),
eventContentSerializerMappings = createEventContentSerializerMappings(TravelEventContentSerializerMappings)
).apply { accessToken.value = config.tokens.appService }
appServiceUserId = client.authentication.whoAmI().getOrThrow().userId
mainRoomId = createMainRoomIfNeeded(config)
}
suspend fun sendErrorTextMessage(
roomId: RoomId,
body: String,
formattedBody: String? = null
) {
val formattedBodyWithWarningIcon = "⚠️ " + (formattedBody ?: body)
client.rooms.sendMessageEvent(
roomId,
RoomMessageEventContent.TextMessageEventContent(
body = body,
format = "org.matrix.custom.html",
formattedBody = formattedBodyWithWarningIcon
)
)
}
suspend fun isRoomAliasAvailable(roomAliasId: RoomAliasId): Boolean {
val roomId = client.rooms.getRoomAlias(roomAliasId).getOrNull()
return roomId == null
}
suspend fun setRoomName(roomId: RoomId, name: String?) {
name?.let {
client.rooms.sendStateEvent(
roomId,
NameEventContent(it)
)
}
}
suspend fun removeRoomAliases(
roomId: RoomId
) {
MatrixApiClient.client.rooms.getRoomAliases(roomId).getOrNull()?.forEach { alias ->
MatrixApiClient.client.rooms.deleteRoomAlias(alias)
}
}
private suspend fun createMainRoomIfNeeded(config: Config): RoomId? {
val mainRoomId: RoomId?
val roomMainAlias = "#${config.mainRoom}:${config.homeserver.host}"
val roomAliasRes = client.rooms.getRoomAlias(RoomAliasId(roomMainAlias)).getOrNull()
if (roomAliasRes == null) {
logger.info("Creating $roomMainAlias public room")
mainRoomId = client.rooms.createRoom(
visibility = DirectoryVisibility.PUBLIC,
roomAliasId = RoomAliasId(roomMainAlias)
).getOrNull()
} else {
mainRoomId = roomAliasRes.roomId
val joinedToMainRoom = (client.rooms.getJoinedRooms().getOrNull()?.firstOrNull { roomId ->
roomId == mainRoomId
}) !== null
logger.info("alreadyJoinedToMainRoom=$joinedToMainRoom")
if (!joinedToMainRoom) {
client.rooms.joinRoom(mainRoomId)
}
val roomStateEvents = client.rooms.getState(mainRoomId).getOrNull()
roomStateEvents?.forEach { stateEvent ->
logger.debug("stateEvent=$stateEvent")
}
}
return mainRoomId
}
}

View File

@@ -0,0 +1,64 @@
package eu.fosil.okupamicoche.matrix
import eu.fosil.okupamicoche.cli.CommandParser
import eu.fosil.okupamicoche.db.TravelsTable
import eu.fosil.okupamicoche.matrix.event.message.TravelMessageEventContent
import eu.fosil.okupamicoche.matrix.services.EventTnxService
import eu.fosil.okupamicoche.matrix.services.RoomService
import eu.fosil.okupamicoche.matrix.services.UserService
import mu.KotlinLogging
import net.folivo.trixnity.applicationserviceapi.server.ApplicationServiceApiServerHandler
import net.folivo.trixnity.appservice.DefaultApplicationServiceApiServerHandler
import net.folivo.trixnity.clientserverapi.client.MatrixClientServerApiClient
import net.folivo.trixnity.core.model.events.Event
import net.folivo.trixnity.core.model.events.m.room.CreateEventContent
import net.folivo.trixnity.core.model.events.m.room.RoomMessageEventContent
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.transactions.transaction
val db = Database.connect("jdbc:h2:./okupamicoche", driver = "org.h2.Driver")
private val logger = KotlinLogging.logger {}
fun createAppService(matrixClientServerApiClient: MatrixClientServerApiClient): ApplicationServiceApiServerHandler {
transaction(db) {
SchemaUtils.create(TravelsTable)
logger.info { "Travels table created" }
}
return DefaultApplicationServiceApiServerHandler(
EventTnxService(),
UserService(matrixClientServerApiClient),
RoomService(matrixClientServerApiClient)
).apply {
subscribeAllEvents {
logger.debug("All events: $it")
}
subscribe(CreateEventContent::class) {
if (it is Event.RoomEvent) {
logger.info("${it.content.creator} created room ${it.roomId}")
}
}
subscribe(TravelMessageEventContent::class) { event ->
logger.info("NEW TRAVEL CREATED EVENT!!")
logger.info("$event")
}
subscribe(RoomMessageEventContent.TextMessageEventContent::class) { event ->
require(event is Event.MessageEvent)
val roomId = event.roomId
val body = event.content.body
logger.info("${event.sender} sent \"${event.content.body}\" on $roomId")
logger.info("$event")
if (CommandParser.isCommand(body)) {
CommandParser.parse(
user = event.sender,
roomId,
body
)
}
}
}
}

View File

@@ -0,0 +1,25 @@
package eu.fosil.okupamicoche.matrix.event
import eu.fosil.okupamicoche.matrix.event.message.JOIN_MESSAGE_EVENT_TYPE
import eu.fosil.okupamicoche.matrix.event.message.TRAVEL_MESSAGE_EVENT_TYPE
import eu.fosil.okupamicoche.matrix.event.message.TravelJoinMessageEventContent
import eu.fosil.okupamicoche.matrix.event.message.TravelMessageEventContent
import eu.fosil.okupamicoche.matrix.event.state.MEMBERSHIP_STATE_EVENT_TYPE
import eu.fosil.okupamicoche.matrix.event.state.TRAVEL_STATE_EVENT_TYPE
import eu.fosil.okupamicoche.matrix.event.state.TravelMembershipStateEventContent
import eu.fosil.okupamicoche.matrix.event.state.TravelStateEventContent
import net.folivo.trixnity.core.model.events.MessageEventContent
import net.folivo.trixnity.core.model.events.StateEventContent
import net.folivo.trixnity.core.serialization.events.BaseEventContentSerializerMappings
import net.folivo.trixnity.core.serialization.events.EventContentSerializerMapping
object TravelEventContentSerializerMappings : BaseEventContentSerializerMappings() {
override val message: Set<EventContentSerializerMapping<out MessageEventContent>> = setOf(
EventContentSerializerMapping.of<TravelMessageEventContent>(TRAVEL_MESSAGE_EVENT_TYPE),
EventContentSerializerMapping.of<TravelJoinMessageEventContent>(JOIN_MESSAGE_EVENT_TYPE)
)
override val state: Set<EventContentSerializerMapping<out StateEventContent>> = setOf(
EventContentSerializerMapping.of<TravelStateEventContent>(TRAVEL_STATE_EVENT_TYPE),
EventContentSerializerMapping.of<TravelMembershipStateEventContent>(MEMBERSHIP_STATE_EVENT_TYPE)
)
}

View File

@@ -0,0 +1,25 @@
package eu.fosil.okupamicoche.matrix.event.message
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.folivo.trixnity.core.model.RoomId
import net.folivo.trixnity.core.model.UserId
import net.folivo.trixnity.core.model.events.MessageEventContent
import net.folivo.trixnity.core.model.events.RelatesTo
const val JOIN_MESSAGE_EVENT_TYPE = "eu.fosil.travel.join"
@Serializable
data class TravelJoinMessageEventContent(
@SerialName("room")
val room: String,
@SerialName("user")
val user: String,
@SerialName("m.relates_to")
override val relatesTo: RelatesTo.Reference? = null
) : MessageEventContent {
constructor(roomId: RoomId, userId: UserId) : this(
roomId.full,
userId.full
)
}

View File

@@ -0,0 +1,46 @@
package eu.fosil.okupamicoche.matrix.event.message
import eu.fosil.okupamicoche.model.Travel
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.folivo.trixnity.core.model.events.MessageEventContent
import net.folivo.trixnity.core.model.events.RelatesTo
import net.folivo.trixnity.core.model.events.StateEventContent
const val TRAVEL_MESSAGE_EVENT_TYPE = "eu.fosil.travel"
@Serializable
data class TravelMessageEventContent(
@SerialName("room")
val room: String,
@SerialName("driver")
val driver: String,
@SerialName("from")
val from: String,
@SerialName("to")
val to: String,
@SerialName("time")
val time: Long,
@SerialName("seats")
val seats: Int,
@SerialName("description")
val description: String,
@SerialName("duplicateNum")
val duplicateNum: Int?,
@SerialName("canceled")
val canceled: Boolean,
@SerialName("m.relates_to")
override val relatesTo: RelatesTo.Reference? = null
) : MessageEventContent, StateEventContent {
constructor(travel: Travel) : this(
travel.room.full,
travel.driver.full,
travel.options.from,
travel.options.to,
travel.options.time,
travel.options.seats,
travel.options.description,
travel.duplicateNum,
travel.canceled
)
}

View File

@@ -0,0 +1,25 @@
package eu.fosil.okupamicoche.matrix.event.state
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.folivo.trixnity.core.model.UserId
import net.folivo.trixnity.core.model.events.StateEventContent
import net.folivo.trixnity.core.model.events.m.room.Membership
const val MEMBERSHIP_STATE_EVENT_TYPE = "eu.fosil.travel.member"
@Serializable
data class TravelMembershipStateEventContent(
@SerialName("user")
val user: String,
@SerialName("membership")
val membership: String,
@SerialName("seats")
val seats: Int
) : StateEventContent {
constructor(userId: UserId, membership: Membership, seats: Int = 1) : this(
userId.full,
membership.value,
seats
)
}

View File

@@ -0,0 +1,45 @@
package eu.fosil.okupamicoche.matrix.event.state
import eu.fosil.okupamicoche.model.TravelOptions
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.folivo.trixnity.core.model.UserId
import net.folivo.trixnity.core.model.events.StateEventContent
const val TRAVEL_STATE_EVENT_TYPE = "eu.fosil.travel"
@Serializable
data class TravelStateEventContent(
@SerialName("driver")
val driver: String,
@SerialName("from")
val from: String,
@SerialName("to")
val to: String,
@SerialName("time")
val time: Long,
@SerialName("seats")
val seats: Int,
@SerialName("description")
val description: String,
@SerialName("duplicateNum")
val duplicateNum: Int?,
@SerialName("canceled")
val canceled: Boolean
) : StateEventContent {
constructor(
travelOptions: TravelOptions,
driver: UserId,
duplicateNum: Int? = null,
canceled: Boolean = false
) : this(
driver.full,
travelOptions.from,
travelOptions.to,
travelOptions.time,
travelOptions.seats,
travelOptions.description,
duplicateNum,
canceled
)
}

View File

@@ -0,0 +1,18 @@
package eu.fosil.okupamicoche.matrix.services
import mu.KotlinLogging
import net.folivo.trixnity.appservice.ApplicationServiceEventTxnService
private val logger = KotlinLogging.logger {}
class EventTnxService : ApplicationServiceEventTxnService {
override suspend fun eventTnxProcessingState(txnId: String): ApplicationServiceEventTxnService.EventTnxProcessingState {
logger.debug("eventTnxProcessingState tnxId=$txnId")
return ApplicationServiceEventTxnService.EventTnxProcessingState.NOT_PROCESSED
// return AppserviceEventTnxService.EventTnxProcessingState.PROCESSED
}
override suspend fun onEventTnxProcessed(txnId: String) {
logger.debug("onEventTnxProcessed tnxId=$txnId")
}
}

View File

@@ -0,0 +1,30 @@
package eu.fosil.okupamicoche.matrix.services
import mu.KotlinLogging
import net.folivo.trixnity.appservice.ApplicationServiceRoomService
import net.folivo.trixnity.appservice.CreateRoomParameter
import net.folivo.trixnity.clientserverapi.client.MatrixClientServerApiClient
import net.folivo.trixnity.clientserverapi.model.rooms.DirectoryVisibility
import net.folivo.trixnity.core.model.RoomAliasId
import net.folivo.trixnity.core.model.RoomId
private val logger = KotlinLogging.logger {}
class RoomService(override val matrixClientServerApiClient: MatrixClientServerApiClient) :
ApplicationServiceRoomService {
override suspend fun getCreateRoomParameter(roomAlias: RoomAliasId): CreateRoomParameter {
logger.info("getCreateRoomParameter")
return CreateRoomParameter(DirectoryVisibility.PUBLIC)
}
override suspend fun onCreatedRoom(roomAlias: RoomAliasId, roomId: RoomId) {
logger.info("onCreatedRoom")
}
override suspend fun roomExistingState(roomAlias: RoomAliasId): ApplicationServiceRoomService.RoomExistingState {
logger.info("roomExistingState")
return ApplicationServiceRoomService.RoomExistingState.DOES_NOT_EXISTS
}
}

View File

@@ -0,0 +1,25 @@
package eu.fosil.okupamicoche.matrix.services
import net.folivo.trixnity.appservice.ApplicationServiceUserService
import net.folivo.trixnity.appservice.RegisterUserParameter
import net.folivo.trixnity.clientserverapi.client.MatrixClientServerApiClient
import net.folivo.trixnity.core.model.UserId
class UserService(override val matrixClientServerApiClient: MatrixClientServerApiClient) :
ApplicationServiceUserService {
override suspend fun getRegisterUserParameter(userId: UserId): RegisterUserParameter {
println("getRegisterUserParameter")
return RegisterUserParameter("user parameter")
}
override suspend fun onRegisteredUser(userId: UserId) {
println("onRegisteredUser")
}
override suspend fun userExistingState(userId: UserId): ApplicationServiceUserService.UserExistingState {
println("userExistingState")
return ApplicationServiceUserService.UserExistingState.CAN_BE_CREATED
}
}

View File

@@ -0,0 +1,49 @@
package eu.fosil.okupamicoche.model
import net.folivo.trixnity.core.model.RoomId
import net.folivo.trixnity.core.model.UserId
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
class Travel(
var room: RoomId,
var driver: UserId,
var options: TravelOptions,
var travelers: List<Pair<UserId, Int>> = listOf(),
var duplicateNum: Int? = null,
var canceled: Boolean = false
)
class TravelOptions(
var from: String,
var to: String,
var time: Long,
var seats: Int,
var description: String
) {
fun getRoomAliasPrefix(): String {
val instant = Instant.ofEpochMilli(time)
val date = DateTimeFormatter.ofPattern("yyyy/MM/dd").withZone(ZoneId.systemDefault()).format(instant)
val time =
DateTimeFormatter.ofPattern("H:mm").withZone(ZoneId.systemDefault()).format(instant).replace(':', 'H')
return "#viaje_${from}-${to}_${date}_${time}"
}
fun getRoomNamePrefix(): String {
val instant = Instant.ofEpochMilli(time)
val date = DateTimeFormatter.ofPattern("yyyy/MM/dd").withZone(ZoneId.systemDefault()).format(instant)
val time = DateTimeFormatter.ofPattern("H:mm").withZone(ZoneId.systemDefault()).format(instant)
return "Travel ${from}-${to} $date $time"
}
fun getRoomName(duplicateNum: Int?, travelers: List<Pair<UserId, Int>>? = null): String {
val attemptSuffix = if ((duplicateNum ?: 0) > 0) " ($duplicateNum)" else ""
val usedSeats = travelers?.sumOf { it.second } ?: 0
val availableSeats = seats - usedSeats
return getRoomNamePrefix() + " | $availableSeats seats available" + attemptSuffix
}
}

View File

@@ -0,0 +1,79 @@
package eu.fosil.okupamicoche.model
import eu.fosil.okupamicoche.matrix.MatrixApiClient
import eu.fosil.okupamicoche.matrix.event.state.TravelMembershipStateEventContent
import eu.fosil.okupamicoche.matrix.event.state.TravelStateEventContent
import net.folivo.trixnity.clientserverapi.client.getStateEvent
import net.folivo.trixnity.core.model.RoomId
import net.folivo.trixnity.core.model.UserId
import net.folivo.trixnity.core.model.events.m.room.Membership
object TravelRepository {
suspend fun getDisplayName(userId: UserId) =
MatrixApiClient.client.users.getDisplayName(userId).getOrNull() ?: userId.localpart
suspend fun isTravel(roomId: RoomId) =
MatrixApiClient.client.rooms.getStateEvent<TravelStateEventContent>(
roomId
).getOrNull() !== null
suspend fun getTravel(roomId: RoomId): Travel? {
val travelCreatedstateEvent = MatrixApiClient.client.rooms.getStateEvent<TravelStateEventContent>(
roomId
).getOrNull() ?: return null
return Travel(
room = roomId,
driver = UserId(travelCreatedstateEvent.driver),
options = TravelOptions(
from = travelCreatedstateEvent.from,
to = travelCreatedstateEvent.to,
time = travelCreatedstateEvent.time,
seats = travelCreatedstateEvent.seats,
description = travelCreatedstateEvent.description
),
travelers = getTravelers(roomId) ?: listOf(),
duplicateNum = travelCreatedstateEvent.duplicateNum
)
}
suspend fun isDriver(roomId: RoomId, userId: UserId) =
MatrixApiClient.client.rooms.getStateEvent<TravelStateEventContent>(
roomId
).getOrNull()?.driver == userId.full
suspend fun getTravelMembership(roomId: RoomId, userId: UserId) =
MatrixApiClient.client.rooms.getStateEvent<TravelMembershipStateEventContent>(
roomId,
userId.full.substring(1) // Synapse throws 403 if stateKey starts with '@'
).getOrNull()?.membership
suspend fun getTravelers(roomId: RoomId) = MatrixApiClient.client.rooms.getState(roomId).getOrNull()?.filter {
val content = it.content
(content is TravelMembershipStateEventContent) && (content.membership == Membership.JOIN.value)
}?.map {
Pair(
UserId("@${it.stateKey}"),
(it.content as TravelMembershipStateEventContent).seats
)
}
private suspend fun getUsedSeats(roomId: RoomId) = MatrixApiClient.client.rooms.getState(roomId).getOrNull()?.map {
it.content
}?.filterIsInstance(TravelMembershipStateEventContent::class.java)?.filter {
it.membership == Membership.JOIN.value
}?.sumOf { it.seats } ?: 0
suspend fun getSeatsLeft(roomId: RoomId): Int? {
// Get travel total seats (not counting driver)
val roomState = MatrixApiClient.client.rooms.getStateEvent<TravelStateEventContent>(roomId).getOrNull()
?: return null
// Get used seats
val usedSeats = getUsedSeats(roomId)
return roomState.seats - usedSeats
}
}

View File

@@ -0,0 +1,4 @@
package eu.fosil.okupamicoche.usecase
object Usecase {
}

View File

@@ -0,0 +1,91 @@
package eu.fosil.okupamicoche.usecase.travel
import eu.fosil.okupamicoche.matrix.MatrixApiClient
import eu.fosil.okupamicoche.matrix.event.message.TravelMessageEventContent
import eu.fosil.okupamicoche.matrix.event.state.TravelStateEventContent
import eu.fosil.okupamicoche.model.TravelRepository
import eu.fosil.okupamicoche.usecase.Usecase
import net.folivo.trixnity.clientserverapi.model.rooms.DirectoryVisibility
import net.folivo.trixnity.core.model.RoomId
import net.folivo.trixnity.core.model.UserId
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
suspend fun Usecase.cancelTravel(
roomId: RoomId,
driverId: UserId
) {
val isTravel = TravelRepository.isTravel(roomId)
val isDriver = TravelRepository.isDriver(roomId, driverId)
if (!isTravel) {
MatrixApiClient.sendErrorTextMessage(roomId, "There is no travel.")
} else if (!isDriver) {
MatrixApiClient.sendErrorTextMessage(
roomId,
"Only the driver can cancel the travel."
)
} else {
// Kick all users in room except app service user
MatrixApiClient.client.rooms.getJoinedMembers(roomId).getOrNull()?.joined?.filter { userMap ->
userMap.key != MatrixApiClient.appServiceUserId
}?.forEach { userMap ->
MatrixApiClient.client.rooms.kickUser(roomId, userMap.key, "Travel canceled")
}
// Make room private
MatrixApiClient.client.rooms.setDirectoryVisibility(roomId, DirectoryVisibility.PRIVATE)
// Change name
val travel = TravelRepository.getTravel(roomId)
val roomName = if (travel == null) "Travel CANCELED" else travel.options.getRoomNamePrefix() + " CANCELED"
MatrixApiClient.setRoomName(roomId, roomName)
// Remove alias
MatrixApiClient.removeRoomAliases(roomId)
// Leave the room
MatrixApiClient.client.rooms.leaveRoom(roomId, "Travel canceled")
// Send messages to main room
sendCancelMessageEvents(roomId)
}
}
private suspend fun sendCancelMessageEvents(
roomId: RoomId
) {
val mainRoomId = requireNotNull(MatrixApiClient.mainRoomId)
val travel = requireNotNull(TravelRepository.getTravel(roomId))
val instant = Instant.ofEpochMilli(travel.options.time)
val date = DateTimeFormatter.ofPattern("yyyy/MM/dd").withZone(ZoneId.systemDefault()).format(instant)
val time = DateTimeFormatter.ofPattern("H:mm").withZone(ZoneId.systemDefault()).format(instant)
// Remove state event from travel room
MatrixApiClient.client.rooms.sendStateEvent(
roomId,
TravelStateEventContent(
travelOptions = travel.options,
driver = travel.driver,
duplicateNum = travel.duplicateNum,
canceled = true
),
""
)
// Send text message to main room
val body = "TRAVEL CANCELED: ${travel.options.from}-${travel.options.to} on $date $time."
MatrixApiClient.sendErrorTextMessage(mainRoomId, body)
// Send text message to travel room
MatrixApiClient.sendErrorTextMessage(roomId, body)
// Send travel canceled message event to main room
travel.canceled = true
MatrixApiClient.client.rooms.sendMessageEvent(
mainRoomId,
TravelMessageEventContent(travel)
)
}

View File

@@ -0,0 +1,127 @@
package eu.fosil.okupamicoche.usecase.travel
import eu.fosil.okupamicoche.db.TravelEntity
import eu.fosil.okupamicoche.matrix.MatrixApiClient
import eu.fosil.okupamicoche.matrix.db
import eu.fosil.okupamicoche.matrix.event.message.TravelMessageEventContent
import eu.fosil.okupamicoche.matrix.event.state.TRAVEL_STATE_EVENT_TYPE
import eu.fosil.okupamicoche.matrix.event.state.TravelStateEventContent
import eu.fosil.okupamicoche.model.Travel
import eu.fosil.okupamicoche.model.TravelOptions
import eu.fosil.okupamicoche.model.TravelRepository
import eu.fosil.okupamicoche.usecase.Usecase
import net.folivo.trixnity.clientserverapi.model.rooms.DirectoryVisibility
import net.folivo.trixnity.core.model.RoomId
import net.folivo.trixnity.core.model.UserId
import net.folivo.trixnity.core.model.events.Event
import net.folivo.trixnity.core.model.events.m.room.PowerLevelsEventContent
import net.folivo.trixnity.core.model.events.m.room.RoomMessageEventContent
import org.jetbrains.exposed.sql.transactions.transaction
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
suspend fun Usecase.createTravel(
travelOptions: TravelOptions,
driver: UserId,
eventSentToRoomId: RoomId
) {
val newRoomId = createRoom(travelOptions, driver)
val travel = Travel(
newRoomId,
driver,
travelOptions
)
sendCreateMessageEvents(travel, eventSentToRoomId)
transaction(db) {
TravelEntity.new(travel)
}
}
private suspend fun createRoom(travelOptions: TravelOptions, driver: UserId): RoomId {
val appServiceUserId = requireNotNull(MatrixApiClient.appServiceUserId)
val newRoomNameInfo = Usecase.getRoomNameInfoFromTravelOptions(travelOptions)
val initialState = Event.InitialStateEvent(
TravelStateEventContent(
travelOptions,
driver,
newRoomNameInfo.duplicateNum
),
""
)
val powerLevels = PowerLevelsEventContent(
events = mapOf(Pair(TRAVEL_STATE_EVENT_TYPE, 100)),
users = mapOf(
Pair(appServiceUserId, 100),
Pair(driver, 50)
)
)
return MatrixApiClient.client.rooms.createRoom(
visibility = DirectoryVisibility.PUBLIC,
roomAliasId = newRoomNameInfo.aliasId,
name = newRoomNameInfo.name,
topic = travelOptions.description,
invite = setOf(driver),
roomVersion = "9",
initialState = listOf(initialState),
powerLevelContentOverride = powerLevels
).getOrThrow()
// NOT IMPLEMENTED IN ELEMENT YET
// https://github.com/vector-im/element-android/issues/3875
// https://github.com/vector-im/element-web/issues/18655
// Send joining rule state event
// MatrixApiClient.client.rooms.sendStateEvent(
// roomId,
// JoinRulesEventContent(JoinRulesEventContent.JoinRule.Knock)
// )
}
private suspend fun sendCreateMessageEvents(
travel: Travel,
eventSentToRoomId: RoomId
) {
val mainRoomId = requireNotNull(MatrixApiClient.mainRoomId)
val instant = Instant.ofEpochMilli(travel.options.time)
val date = DateTimeFormatter.ofPattern("yyyy/MM/dd").withZone(ZoneId.systemDefault()).format(instant)
val time = DateTimeFormatter.ofPattern("H:mm").withZone(ZoneId.systemDefault()).format(instant)
// Send text message to main room
val displayName = TravelRepository.getDisplayName(travel.driver)
val body = "$displayName created a new travel! ${travel.options.from}-${travel.options.to}" +
" on $date $time with ${travel.options.seats} free seats."
val formattedBody =
"\uD83D\uDE97 $displayName created a new travel!<br><br>${travel.options.from}-${travel.options.to}" +
" on $date $time with ${travel.options.seats} free seats."
MatrixApiClient.client.rooms.sendMessageEvent(
mainRoomId,
RoomMessageEventContent.TextMessageEventContent(
body = body,
format = "org.matrix.custom.html",
formattedBody = formattedBody
)
)
// Send new travel message event to main room
MatrixApiClient.client.rooms.sendMessageEvent(
mainRoomId,
TravelMessageEventContent(travel)
)
// Send text message to room where create command was sent (if it is not main room)
if (eventSentToRoomId != mainRoomId) {
MatrixApiClient.client.rooms.sendMessageEvent(
eventSentToRoomId,
RoomMessageEventContent.TextMessageEventContent(
body = "Travel created!",
format = "org.matrix.custom.html",
formattedBody = "\uD83D\uDE97 Travel created!"
)
)
}
}

View File

@@ -0,0 +1,55 @@
package eu.fosil.okupamicoche.usecase.travel
import eu.fosil.okupamicoche.config.ConfigReader
import eu.fosil.okupamicoche.matrix.MatrixApiClient.isRoomAliasAvailable
import eu.fosil.okupamicoche.model.TravelOptions
import eu.fosil.okupamicoche.model.TravelRepository
import eu.fosil.okupamicoche.usecase.Usecase
import net.folivo.trixnity.core.model.RoomAliasId
import net.folivo.trixnity.core.model.RoomId
data class RoomNameInfo(
val aliasId: RoomAliasId,
val name: String,
val duplicateNum: Int?
)
suspend fun Usecase.getRoomNameInfoFromTravelOptions(travelOptions: TravelOptions): RoomNameInfo {
val roomAliasPrefix = travelOptions.getRoomAliasPrefix()
val duplicateNum = calculateDuplicateNum(roomAliasPrefix)
return RoomNameInfo(
getRoomAlias(roomAliasPrefix, duplicateNum),
travelOptions.getRoomName(duplicateNum),
duplicateNum
)
}
suspend fun Usecase.getRoomNameInfoFromRoomId(roomId: RoomId): RoomNameInfo? {
val travel = TravelRepository.getTravel(roomId) ?: return null
val roomAliasPrefix = travel.options.getRoomAliasPrefix()
val duplicateNum = travel.duplicateNum
val travelers = TravelRepository.getTravelers(roomId)
return RoomNameInfo(
getRoomAlias(roomAliasPrefix, duplicateNum),
travel.options.getRoomName(duplicateNum, travelers),
duplicateNum
)
}
private suspend fun calculateDuplicateNum(roomAliasPrefix: String): Int? {
var duplicateNum = 0
// Look for available room alias
while (!isRoomAliasAvailable(getRoomAlias(roomAliasPrefix, duplicateNum)))
duplicateNum++
return if (duplicateNum == 0) null else duplicateNum
}
private fun getRoomAlias(roomAliasPrefix: String, duplicateNum: Int?): RoomAliasId {
val config = requireNotNull(ConfigReader.config)
val attemptPart = if ((duplicateNum ?: 0) > 0) "_$duplicateNum" else ""
return RoomAliasId("${roomAliasPrefix}$attemptPart:${config.homeserver.host}")
}

View File

@@ -0,0 +1,70 @@
package eu.fosil.okupamicoche.usecase.travel
import eu.fosil.okupamicoche.matrix.MatrixApiClient
import eu.fosil.okupamicoche.matrix.event.state.TravelMembershipStateEventContent
import eu.fosil.okupamicoche.model.TravelRepository
import eu.fosil.okupamicoche.usecase.Usecase
import net.folivo.trixnity.core.model.RoomId
import net.folivo.trixnity.core.model.UserId
import net.folivo.trixnity.core.model.events.m.room.Membership
import net.folivo.trixnity.core.model.events.m.room.RoomMessageEventContent
suspend fun Usecase.joinTravel(
roomId: RoomId,
userId: UserId,
args: List<String>
) {
val isTravel = TravelRepository.isTravel(roomId)
val isDriver = TravelRepository.isDriver(roomId, userId)
val membershipState = TravelRepository.getTravelMembership(roomId, userId)
val seatsLeft = TravelRepository.getSeatsLeft(roomId) ?: 0
// Seats asked by traveler, 1 if no specified
val seats = args.firstOrNull()?.toIntOrNull() ?: 1
if (!isTravel) {
MatrixApiClient.sendErrorTextMessage(roomId, "There is no travel.")
} else if (isDriver) {
MatrixApiClient.sendErrorTextMessage(roomId, "You are the driver, you don't need to join.")
} else if (membershipState == Membership.JOIN.value) {
MatrixApiClient.sendErrorTextMessage(roomId, "You are already joined.")
} else if (seatsLeft == 0) {
MatrixApiClient.sendErrorTextMessage(roomId, "Travel is full, no seats left.")
} else if (seatsLeft < seats) {
MatrixApiClient.sendErrorTextMessage(roomId, "Not enough seats left, only $seatsLeft available.")
} else {
sendJoinedMessageEvents(roomId, userId, seats)
val roomNameInfo = Usecase.getRoomNameInfoFromRoomId(roomId)
MatrixApiClient.setRoomName(roomId, roomNameInfo?.name)
}
}
private suspend fun sendJoinedMessageEvents(
roomId: RoomId,
userId: UserId,
seats: Int
) {
// Send membership change state event to travel room
MatrixApiClient.client.rooms.sendStateEvent(
roomId,
TravelMembershipStateEventContent(userId, Membership.JOIN, seats),
stateKey = userId.full.substring(1) // Synapse throws 403 if stateKey starts with '@'
).getOrThrow()
// Send text message to travel room
val displayName = TravelRepository.getDisplayName(userId)
val seatsLeft = TravelRepository.getSeatsLeft(roomId)
val messageBody = "$displayName joined the travel!" +
(if (seats == 1) null else " They are using $seats seats.") +
" Seats left: $seatsLeft"
MatrixApiClient.client.rooms.sendMessageEvent(
roomId,
RoomMessageEventContent.TextMessageEventContent(messageBody)
)
// THIS EVENT IS SENT BY CLIENT, NO APP SERVICE
// Send new travel message event to main room
// matrixApiClient.rooms.sendMessageEvent(
// roomId,
// JoinTravelMessageEventContent(roomId, userId)
// )
}

View File

@@ -0,0 +1,58 @@
package eu.fosil.okupamicoche.usecase.travel
import eu.fosil.okupamicoche.matrix.MatrixApiClient
import eu.fosil.okupamicoche.matrix.event.state.TravelMembershipStateEventContent
import eu.fosil.okupamicoche.model.TravelRepository
import eu.fosil.okupamicoche.usecase.Usecase
import net.folivo.trixnity.core.model.RoomId
import net.folivo.trixnity.core.model.UserId
import net.folivo.trixnity.core.model.events.m.room.Membership
import net.folivo.trixnity.core.model.events.m.room.RoomMessageEventContent
suspend fun Usecase.leaveTravel(
roomId: RoomId,
userId: UserId
) {
val isTravel = TravelRepository.isTravel(roomId)
val isDriver = TravelRepository.isDriver(roomId, userId)
val travelMembershipState = TravelRepository.getTravelMembership(roomId, userId)
if (!isTravel) {
MatrixApiClient.sendErrorTextMessage(roomId, "There is no travel.")
} else if (isDriver) {
MatrixApiClient.sendErrorTextMessage(
roomId,
"You are the driver, you cannot leave. You can cancel the travel sending '!cancel' if you want.",
"You are the driver, you cannot leave. You can cancel the travel sending <code>!cancel</code> if you want."
)
} else if (travelMembershipState != Membership.JOIN.value) {
MatrixApiClient.sendErrorTextMessage(roomId, "You are not joined.")
} else if (travelMembershipState == Membership.LEAVE.value) {
MatrixApiClient.sendErrorTextMessage(roomId, "You already left.")
} else {
sendLeftMessageEvents(roomId, userId)
val roomNameInfo = Usecase.getRoomNameInfoFromRoomId(roomId)
MatrixApiClient.setRoomName(roomId, roomNameInfo?.name)
}
}
private suspend fun sendLeftMessageEvents(
roomId: RoomId,
userId: UserId
) {
// Send membership change state event to travel room
MatrixApiClient.client.rooms.sendStateEvent(
roomId,
TravelMembershipStateEventContent(userId, Membership.LEAVE),
stateKey = userId.full.substring(1) // Synapse throws 403 if stateKey starts with '@'
).getOrThrow()
// Send text message to travel room
val displayName = TravelRepository.getDisplayName(userId)
val availableSeats = TravelRepository.getSeatsLeft(roomId)
val messageBody = "$displayName left the travel. Seats available: $availableSeats"
MatrixApiClient.client.rooms.sendMessageEvent(
roomId,
RoomMessageEventContent.TextMessageEventContent(messageBody)
)
}

View File

@@ -1,70 +0,0 @@
#server:
# port: 8080
# port: 8081
#spring:
# datasource:
# driver-class-name: org.h2.Driver
# url: jdbc:h2:file:./okupamicoche;DB_CLOSE_ON_EXIT=FALSE;AUTO_RECONNECT=TRUE
# jpa:
# database-platform: org.hibernate.dialect.H2Dialect
# hibernate:
# ddl-auto: update
# jackson:
# serialization:
# write-dates-as-timestamps: false
logging:
level:
org:
springframework: INFO
eu.fosil.okupamicoche: DEBUG
matrix:
bot:
# The domain-part of matrix-ids.E.g. example.org when your userIds look like @unicorn:example.org
serverName: okupamicoche-synapse
# The localpart (username) of the user associated with the application service
# or just the username of your bot.
username: okupamicoche
# (optional) Display name for the bot user.
displayname: Okupa mi coche
# (optional) The mode you want to use to create a bot. Default is CLIENT. The other is APPSERVICE.
mode: APPSERVICE
# (optional) Configure how users managed by your bot do automatically join rooms.
# ENABLED allows automatic joins to every invited room.
# DISABLED disables this feature.
# Default is RESTRICTED, which means, that only automatic joins to serverName are allowed.
autoJoin: ENABLED
# (optional) Configure if ALL membership changes should be tracked/saved with help of MatrixAppserviceRoomService
# or only membership changes of users, which are MANAGED by the bridge. Default is ALL (no tracking/saving).
trackMembership: ALL
# Connection setting to the database for migration purpose only (only jdbc drivers ar supported)
migration:
url: jdbc:h2:file:./matrix
username: sa
password:
# Connection settings to the database (only r2dbc drivers are supported)
database:
url: r2dbc:h2:file:///./matrix
username: sa
password:
client:
homeServer:
# The hostname of your Homeserver.
hostname: okupamicoche-synapse
# (optional) The port of your Homeserver. Default is 443.
port: 8008
# (optional) Use http or https. Default is true (so uses https).
secure: false
# The token to authenticate against the Homeserver.
token: "30c05ae90a248a4188e620216fa72e349803310ec83e2a77b34fe90be6081f46"
appservice:
# A unique token for Homeservers to use to authenticate requests to application services.
hsToken: "312df522183efd404ec1cd22d2ffa4bbc76a8c1ccf541dd692eef281356bb74e"
# A list of users, aliases and rooms namespaces that the application service controls.
namespaces:
users: [ ]
aliases:
- localpartRegex: "viaje_.*"
rooms: [ ]

View File

@@ -0,0 +1,8 @@
homeserver:
host: okupamicoche-synapse
secure: false
port: 8008
tokens:
appService: "30c05ae90a248a4188e620216fa72e349803310ec83e2a77b34fe90be6081f46"
homeserver: "312df522183efd404ec1cd22d2ffa4bbc76a8c1ccf541dd692eef281356bb74e"
mainRoom: "viajes"

View File

@@ -0,0 +1,13 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%-5level %logger{36} - %msg%n</pattern>
<!-- <pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>-->
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
<logger name="org.eclipse.jetty" level="INFO"/>
<logger name="io.netty" level="INFO"/>
</configuration>

View File

@@ -1,13 +0,0 @@
package eu.fosil.okupamicoche
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
@SpringBootTest
class OkupaMiCocheAppServiceApplicationTests {
@Test
fun contextLoads() {
}
}

View File

@@ -0,0 +1,4 @@
package eu.fosil.okupamicoche
class ApplicationTest {
}