diff --git a/androidExportReader/.gitignore b/androidExportReader/.gitignore
new file mode 100644
index 0000000..c5e3419
--- /dev/null
+++ b/androidExportReader/.gitignore
@@ -0,0 +1,58 @@
+# Created by https://www.toptal.com/developers/gitignore/api/java,gradle
+# Edit at https://www.toptal.com/developers/gitignore?templates=java,gradle
+
+### Java ###
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+replay_pid*
+
+### Gradle ###
+.gradle
+**/build/
+!src/**/build/
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
+
+# Avoid ignore Gradle wrappper properties
+!gradle-wrapper.properties
+
+# Cache of project
+.gradletasknamecache
+
+# Eclipse Gradle plugin generated files
+# Eclipse Core
+.project
+# JDT-specific (Eclipse Java Development Tools)
+.classpath
+
+### Gradle Patch ###
+# Java heap dump
+*.hprof
+
+# End of https://www.toptal.com/developers/gitignore/api/java,gradle
+
diff --git a/androidExportReader/.idea/.gitignore b/androidExportReader/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/androidExportReader/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/androidExportReader/.idea/compiler.xml b/androidExportReader/.idea/compiler.xml
new file mode 100644
index 0000000..fcb19bf
--- /dev/null
+++ b/androidExportReader/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/androidExportReader/.idea/gradle.xml b/androidExportReader/.idea/gradle.xml
new file mode 100644
index 0000000..ba1ec5c
--- /dev/null
+++ b/androidExportReader/.idea/gradle.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/androidExportReader/.idea/inspectionProfiles/Project_Default.xml b/androidExportReader/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..daba1aa
--- /dev/null
+++ b/androidExportReader/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/androidExportReader/.idea/jarRepositories.xml b/androidExportReader/.idea/jarRepositories.xml
new file mode 100644
index 0000000..f5a0c5d
--- /dev/null
+++ b/androidExportReader/.idea/jarRepositories.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/androidExportReader/.idea/misc.xml b/androidExportReader/.idea/misc.xml
new file mode 100644
index 0000000..aa7273e
--- /dev/null
+++ b/androidExportReader/.idea/misc.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/androidExportReader/.idea/vcs.xml b/androidExportReader/.idea/vcs.xml
new file mode 100644
index 0000000..6c0b863
--- /dev/null
+++ b/androidExportReader/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/androidExportReader/build.gradle b/androidExportReader/build.gradle
new file mode 100644
index 0000000..e2ebf20
--- /dev/null
+++ b/androidExportReader/build.gradle
@@ -0,0 +1,47 @@
+
+buildscript {
+ repositories {
+ gradlePluginPortal()
+ }
+ dependencies {
+ classpath 'gradle.plugin.com.github.johnrengelman:shadow:7.1.2'
+ }
+}
+
+plugins {
+ id 'java'
+ id("com.github.johnrengelman.shadow") version "7.1.2"
+ id 'application'
+}
+
+group 'com.blackforestbytes'
+version '1.0-SNAPSHOT'
+
+repositories {
+ mavenCentral()
+ maven { url "https://jitpack.io" }
+}
+
+application {
+ mainClass = 'com.blackforestbytes.Main'
+}
+
+jar {
+ manifest {
+ attributes 'Main-Class': application.mainClass
+ }
+}
+
+tasks.jar {
+ manifest.attributes["Main-Class"] = application.mainClass
+}
+
+dependencies {
+ implementation 'com.github.RalleYTN:SimpleJSON:2.1.1'
+ testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
+ testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
+}
+
+test {
+ useJUnitPlatform()
+}
\ No newline at end of file
diff --git a/androidExportReader/gradle/wrapper/gradle-wrapper.jar b/androidExportReader/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..249e583
Binary files /dev/null and b/androidExportReader/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/androidExportReader/gradle/wrapper/gradle-wrapper.properties b/androidExportReader/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..ae04661
--- /dev/null
+++ b/androidExportReader/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/androidExportReader/gradlew b/androidExportReader/gradlew
new file mode 100755
index 0000000..a69d9cb
--- /dev/null
+++ b/androidExportReader/gradlew
@@ -0,0 +1,240 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+# 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/.
+#
+##############################################################################
+
+# 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
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${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
+
+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 ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+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
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ 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
+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
+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 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" )
+
+ 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
+ done
+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.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# 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' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/androidExportReader/gradlew.bat b/androidExportReader/gradlew.bat
new file mode 100644
index 0000000..53a6b23
--- /dev/null
+++ b/androidExportReader/gradlew.bat
@@ -0,0 +1,91 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/androidExportReader/settings.gradle b/androidExportReader/settings.gradle
new file mode 100644
index 0000000..11581b9
--- /dev/null
+++ b/androidExportReader/settings.gradle
@@ -0,0 +1,2 @@
+rootProject.name = 'androidExportReader'
+
diff --git a/androidExportReader/src/main/java/com/blackforestbytes/Main.java b/androidExportReader/src/main/java/com/blackforestbytes/Main.java
new file mode 100644
index 0000000..d536f80
--- /dev/null
+++ b/androidExportReader/src/main/java/com/blackforestbytes/Main.java
@@ -0,0 +1,104 @@
+package com.blackforestbytes;
+
+import de.ralleytn.simple.json.JSONArray;
+import de.ralleytn.simple.json.JSONFormatter;
+import de.ralleytn.simple.json.JSONObject;
+
+import java.io.ObjectInputStream;
+import java.net.URI;
+import java.nio.file.FileSystems;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class Main {
+ @SuppressWarnings("unchecked")
+ public static void main(String[] args) {
+ if (args.length != 1) {
+ System.err.println("call with ./androidExportConvert scn_export.dat");
+ return;
+ }
+
+ try {
+
+ var path = FileSystems.getDefault().getPath(args[0]).normalize().toAbsolutePath().toUri().toURL();
+
+ ObjectInputStream stream = new ObjectInputStream(path.openStream());
+
+ Map d1 = new HashMap<>((Map)stream.readObject());
+ Map d2 = new HashMap<>((Map)stream.readObject());
+ Map d3 = new HashMap<>((Map)stream.readObject());
+ Map d4 = new HashMap<>((Map)stream.readObject());
+
+ stream.close();
+
+ JSONObject root = new JSONObject();
+
+ var subConfig = new JSONObject();
+ var subIAB = new JSONArray();
+ var subCMessageList = new JSONArray();
+ var subAcks = new JSONArray();
+ var subQueryLog = new JSONArray();
+
+ for (Map.Entry entry : d1.entrySet())
+ {
+ if (entry.getValue() instanceof String) subConfig.put(entry.getKey(), (String)entry.getValue());
+ if (entry.getValue() instanceof Boolean) subConfig.put(entry.getKey(), (Boolean)entry.getValue());
+ if (entry.getValue() instanceof Float) subConfig.put(entry.getKey(), (Float)entry.getValue());
+ if (entry.getValue() instanceof Integer) subConfig.put(entry.getKey(), (Integer)entry.getValue());
+ if (entry.getValue() instanceof Long) subConfig.put(entry.getKey(), (Long)entry.getValue());
+ if (entry.getValue() instanceof Set>) subConfig.put(entry.getKey(), ((Set)entry.getValue()).toArray());
+ }
+
+ for (int i = 0; i < (Integer)d2.get("c"); i++) {
+ var obj = new JSONObject();
+ obj.put("key", d2.get("["+i+"]->key"));
+ obj.put("value", d2.get("["+i+"]->value"));
+ subIAB.add(obj);
+ }
+
+ for (int i = 0; i < (Integer)d3.get("message_count"); i++) {
+ if (d3.get("message["+i+"].scnid") == null)
+ throw new Exception("ONF");
+
+ var obj = new JSONObject();
+ obj.put("timestamp", d3.get("message["+i+"].timestamp"));
+ obj.put("title", d3.get("message["+i+"].title"));
+ obj.put("content", d3.get("message["+i+"].content"));
+ obj.put("priority", d3.get("message["+i+"].priority"));
+ obj.put("scnid", d3.get("message["+i+"].scnid"));
+ subCMessageList.add(obj);
+ }
+
+ subAcks.addAll(((Set)d3.get("acks")).stream().map(p -> Long.decode("0x"+p)).toList());
+
+ for (int i = 0; i < (Integer)d4.get("history_count"); i++) {
+ if (d4.get("message["+(i+1000)+"].Name") == null)
+ throw new Exception("ONF");
+
+ var obj = new JSONObject();
+ obj.put("Level", d4.get("message["+(i+1000)+"].Level"));
+ obj.put("Timestamp", d4.get("message["+(i+1000)+"].Timestamp"));
+ obj.put("Name", d4.get("message["+(i+1000)+"].Name"));
+ obj.put("URL", d4.get("message["+(i+1000)+"].URL"));
+ obj.put("Response", d4.get("message["+(i+1000)+"].Response"));
+ obj.put("ResponseCode", d4.get("message["+(i+1000)+"].ResponseCode"));
+ obj.put("ExceptionString", d4.get("message["+(i+1000)+"].ExceptionString"));
+ subQueryLog.add(obj);
+ }
+
+ root.put("config", subConfig);
+ root.put("iab", subIAB);
+ root.put("cmessagelist", subCMessageList);
+ root.put("acks", subAcks);
+ root.put("querylog", subQueryLog);
+
+ System.out.println(new JSONFormatter().format(root.toString()));
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/scnserver/.gitignore b/scnserver/.gitignore
index cf5b9d2..f9286da 100644
--- a/scnserver/.gitignore
+++ b/scnserver/.gitignore
@@ -5,6 +5,8 @@ _build
DOCKER_GIT_INFO
+scn_export.dat
+scn_export.json
##############
diff --git a/scnserver/.idea/sqldialects.xml b/scnserver/.idea/sqldialects.xml
index 64f2482..4a1c2d7 100644
--- a/scnserver/.idea/sqldialects.xml
+++ b/scnserver/.idea/sqldialects.xml
@@ -5,7 +5,7 @@
-
-
+
+
\ No newline at end of file
diff --git a/scnserver/Makefile b/scnserver/Makefile
index f13c20d..cfa3de5 100644
--- a/scnserver/Makefile
+++ b/scnserver/Makefile
@@ -71,4 +71,7 @@ fmt:
test:
go test ./test/...
+migrate:
+ CGO_ENABLED=1 go build -v -o _build/scn_migrate -tags "timetzdata sqlite_fts5 sqlite_foreign_keys" ./cmd/migrate
+ ./_build/scn_migrate
diff --git a/scnserver/README.md b/scnserver/README.md
index b2f0b38..bda006a 100644
--- a/scnserver/README.md
+++ b/scnserver/README.md
@@ -9,6 +9,8 @@
- finish tests (!)
- migration script for existing data
+ apply local deletion in (my) app
+ delete excessive dockerwatch messages (directly in db?)
- app-store link in HTML
@@ -43,6 +45,8 @@
(or add another /kuma endpoint)
-> https://webhook.site/
+ - endpoint to list all servernames of user (distinct select)
+
#### PERSONAL
- in my script: use `srvname` for sendername
diff --git a/scnserver/api/handler/website.go b/scnserver/api/handler/website.go
index d0e15d4..492bea0 100644
--- a/scnserver/api/handler/website.go
+++ b/scnserver/api/handler/website.go
@@ -7,6 +7,7 @@ import (
"errors"
"github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
+ "gogs.mikescher.com/BlackForestBytes/goext/rext"
"net/http"
"regexp"
"strings"
@@ -14,15 +15,15 @@ import (
type WebsiteHandler struct {
app *logic.Application
- rexTemplate *regexp.Regexp
- rexConfig *regexp.Regexp
+ rexTemplate rext.Regex
+ rexConfig rext.Regex
}
func NewWebsiteHandler(app *logic.Application) WebsiteHandler {
return WebsiteHandler{
app: app,
- rexTemplate: regexp.MustCompile("{{template\\|[A-Za-z0-9_\\-\\[\\].]+}}"),
- rexConfig: regexp.MustCompile("{{config\\|[A-Za-z0-9_\\-.]+}}"),
+ rexTemplate: rext.W(regexp.MustCompile("{{template\\|[A-Za-z0-9_\\-\\[\\].]+}}")),
+ rexConfig: rext.W(regexp.MustCompile("{{config\\|[A-Za-z0-9_\\-.]+}}")),
}
}
@@ -77,17 +78,19 @@ func (h WebsiteHandler) CSS(g *gin.Context) ginresp.HTTPResponse {
}
func (h WebsiteHandler) serveAsset(g *gin.Context, fn string, repl bool) ginresp.HTTPResponse {
- data, err := website.Assets.ReadFile(fn)
+ _data, err := website.Assets.ReadFile(fn)
if err != nil {
return ginresp.Status(http.StatusNotFound)
}
+ data := string(_data)
+
if repl {
failed := false
- data = h.rexTemplate.ReplaceAllFunc(data, func(match []byte) []byte {
+ data = h.rexTemplate.ReplaceAllFunc(data, func(match string) string {
prefix := len("{{template|")
suffix := len("}}")
- fnSub := string(match[prefix : len(match)-suffix])
+ fnSub := match[prefix : len(match)-suffix]
fnSub = strings.ReplaceAll(fnSub, "[theme]", h.getTheme(g))
@@ -96,23 +99,23 @@ func (h WebsiteHandler) serveAsset(g *gin.Context, fn string, repl bool) ginresp
log.Error().Str("templ", string(match)).Str("fnSub", fnSub).Str("source", fn).Msg("Failed to replace template")
failed = true
}
- return subdata
+ return string(subdata)
})
if failed {
return ginresp.InternalError(errors.New("template replacement failed"))
}
- data = h.rexConfig.ReplaceAllFunc(data, func(match []byte) []byte {
+ data = h.rexConfig.ReplaceAllFunc(data, func(match string) string {
prefix := len("{{config|")
suffix := len("}}")
cfgKey := match[prefix : len(match)-suffix]
- cval, ok := h.getReplConfig(string(cfgKey))
+ cval, ok := h.getReplConfig(cfgKey)
if !ok {
- log.Error().Str("templ", string(match)).Str("source", fn).Msg("Failed to replace config")
+ log.Error().Str("templ", match).Str("source", fn).Msg("Failed to replace config")
failed = true
}
- return []byte(cval)
+ return cval
})
if failed {
return ginresp.InternalError(errors.New("config replacement failed"))
@@ -138,7 +141,7 @@ func (h WebsiteHandler) serveAsset(g *gin.Context, fn string, repl bool) ginresp
mime = "image/svg+xml"
}
- return ginresp.Data(http.StatusOK, mime, data)
+ return ginresp.Data(http.StatusOK, mime, []byte(data))
}
func (h WebsiteHandler) getReplConfig(key string) (string, bool) {
diff --git a/scnserver/cmd/migrate/main.go b/scnserver/cmd/migrate/main.go
new file mode 100644
index 0000000..fcb2ab1
--- /dev/null
+++ b/scnserver/cmd/migrate/main.go
@@ -0,0 +1,871 @@
+package main
+
+import (
+ scn "blackforestbytes.com/simplecloudnotifier"
+ "blackforestbytes.com/simplecloudnotifier/logic"
+ "blackforestbytes.com/simplecloudnotifier/models"
+ "bufio"
+ "context"
+ "encoding/json"
+ "fmt"
+ _ "github.com/go-sql-driver/mysql"
+ "github.com/jmoiron/sqlx"
+ "gogs.mikescher.com/BlackForestBytes/goext/langext"
+ "gogs.mikescher.com/BlackForestBytes/goext/rext"
+ "gogs.mikescher.com/BlackForestBytes/goext/sq"
+ "os"
+ "regexp"
+ "strings"
+ "time"
+)
+
+type OldUser struct {
+ UserId int64 `db:"user_id"`
+ UserKey string `db:"user_key"`
+ FcmToken *string `db:"fcm_token"`
+ MessagesSent int64 `db:"messages_sent"`
+ TimestampCreated time.Time `db:"timestamp_created"`
+ TimestampAccessed *time.Time `db:"timestamp_accessed"`
+ QuotaToday int64 `db:"quota_today"`
+ QuotaDay *time.Time `db:"quota_day"`
+ IsPro bool `db:"is_pro"`
+ ProToken *string `db:"pro_token"`
+}
+
+type OldMessage struct {
+ ScnMessageId int64 `db:"scn_message_id"`
+ SenderUserId int64 `db:"sender_user_id"`
+ TimestampReal time.Time `db:"timestamp_real"`
+ Ack []uint8 `db:"ack"`
+ Title string `db:"title"`
+ Content *string `db:"content"`
+ Priority int64 `db:"priority"`
+ Sendtime int64 `db:"sendtime"`
+ FcmMessageId *string `db:"fcm_message_id"`
+ UsrMessageId *string `db:"usr_message_id"`
+}
+
+type SCNExport struct {
+ Messages []SCNExportMessage `json:"cmessagelist"`
+}
+
+type SCNExportMessage struct {
+ MessageID int64 `json:"scnid"`
+}
+
+func main() {
+ ctx := context.Background()
+
+ conf, _ := scn.GetConfig("local-host")
+ conf.DBMain.File = ".run-data/migrate_main.sqlite3"
+ conf.DBMain.EnableLogger = false
+
+ if _, err := os.Stat(".run-data/migrate_main.sqlite3"); err == nil {
+ err = os.Remove(".run-data/migrate_main.sqlite3")
+ if err != nil {
+ panic(err)
+ }
+ }
+ if _, err := os.Stat(".run-data/migrate_main.sqlite3-shm"); err == nil {
+ err = os.Remove(".run-data/migrate_main.sqlite3-shm")
+ if err != nil {
+ panic(err)
+ }
+ }
+ if _, err := os.Stat(".run-data/migrate_main.sqlite3-wal"); err == nil {
+ err = os.Remove(".run-data/migrate_main.sqlite3-wal")
+ if err != nil {
+ panic(err)
+ }
+ }
+
+ sqlite, err := logic.NewDBPool(conf)
+ if err != nil {
+ panic(err)
+ }
+
+ err = sqlite.Migrate(ctx)
+ if err != nil {
+ panic(err)
+ }
+
+ connstr := os.Getenv("SQL_CONN_STR")
+ if connstr == "" {
+ scanner := bufio.NewScanner(os.Stdin)
+
+ fmt.Print("Enter DB URL [127.0.0.1:3306]: ")
+ scanner.Scan()
+ host := scanner.Text()
+ if host == "" {
+ host = "127.0.0.1:3306"
+ }
+
+ fmt.Print("Enter DB Username [root]: ")
+ scanner.Scan()
+ username := scanner.Text()
+ if host == "" {
+ host = "root"
+ }
+
+ fmt.Print("Enter DB Password []: ")
+ scanner.Scan()
+ pass := scanner.Text()
+ if host == "" {
+ host = ""
+ }
+
+ connstr = fmt.Sprintf("%s:%s@tcp(%s)", username, pass, host)
+ }
+
+ _dbold, err := sqlx.Open("mysql", connstr+"/simple_cloud_notifier?parseTime=true")
+ if err != nil {
+ panic(err)
+ }
+ dbold := sq.NewDB(_dbold)
+
+ rowsUser, err := dbold.Query(ctx, "SELECT * FROM users", sq.PP{})
+ if err != nil {
+ panic(err)
+ }
+
+ var export SCNExport
+ exfn, err := os.ReadFile("scn_export.json")
+ err = json.Unmarshal(exfn, &export)
+ if err != nil {
+ panic(err)
+ }
+
+ appids := make(map[int64]int64)
+ for _, v := range export.Messages {
+ appids[v.MessageID] = v.MessageID
+ }
+
+ users := make([]OldUser, 0)
+ for rowsUser.Next() {
+ var u OldUser
+ err = rowsUser.StructScan(&u)
+ if err != nil {
+ panic(err)
+ }
+ users = append(users, u)
+ }
+
+ fmt.Printf("\n")
+
+ for _, v := range users {
+ fmt.Printf("========================================\n")
+ fmt.Printf(" MIGRATE USER %d\n", v.UserId)
+ fmt.Printf("========================================\n")
+ migrateUser(ctx, sqlite.Primary.DB(), dbold, v, appids)
+ fmt.Printf("========================================\n")
+ fmt.Printf("\n")
+ fmt.Printf("\n")
+ }
+
+ err = sqlite.Stop(context.Background())
+ if err != nil {
+ panic(err)
+ }
+}
+
+var rexTitleChannel = rext.W(regexp.MustCompile("^\\[(?P[A-Za-z\\-0-9_ ]+)] (?P(.|\\r|\\n)+)$"))
+
+var usedFCM = make(map[string]models.ClientID)
+
+func migrateUser(ctx context.Context, dbnew sq.DB, dbold sq.DB, user OldUser, appids map[int64]int64) {
+
+ rowsMessages, err := dbold.Query(ctx, "SELECT * FROM messages WHERE sender_user_id = :uid ORDER BY timestamp_real ASC", sq.PP{"uid": user.UserId})
+ if err != nil {
+ panic(err)
+ }
+
+ messages := make([]OldMessage, 0)
+ for rowsMessages.Next() {
+ var m OldMessage
+ err = rowsMessages.StructScan(&m)
+ if err != nil {
+ panic(err)
+ }
+ messages = append(messages, m)
+ }
+
+ fmt.Printf("Found %d messages\n", len(messages))
+
+ userid := models.NewUserID()
+
+ fmt.Printf("New UserID: %s\n", userid)
+
+ readKey := scn.RandomAuthKey()
+ sendKey := scn.RandomAuthKey()
+ adminKey := user.UserKey
+
+ protoken := user.ProToken
+ if protoken != nil {
+ protoken = langext.Ptr("ANDROID|v1|" + *protoken)
+ }
+
+ _, err = dbnew.Exec(ctx, "INSERT INTO users (user_id, username, read_key, send_key, admin_key, is_pro, pro_token, timestamp_created) VALUES (:uid, :un, :rk, :sk, :ak, :pro, :tok, :ts)", sq.PP{
+ "uid": userid,
+ "un": nil,
+ "rk": readKey,
+ "sk": sendKey,
+ "ak": adminKey,
+ "pro": langext.Conditional(user.IsPro, 1, 0),
+ "tok": protoken,
+ "ts": user.TimestampCreated.UnixMilli(),
+ })
+ if err != nil {
+ panic(err)
+ }
+
+ _, err = dbnew.Exec(ctx, "INSERT INTO compat_ids (old, new, type) VALUES (:old, :new, :typ)", sq.PP{
+ "old": user.UserId,
+ "new": userid,
+ "typ": "userid",
+ })
+ if err != nil {
+ panic(err)
+ }
+
+ var clientid *models.ClientID = nil
+
+ if user.FcmToken != nil && *user.FcmToken != "BLACKLISTED" {
+
+ if _, ok := usedFCM[*user.FcmToken]; ok {
+
+ fmt.Printf("Skip Creating Client (fcm token reuse)\n")
+
+ } else {
+ _clientid := models.NewClientID()
+
+ _, err = dbnew.Exec(ctx, "INSERT INTO clients (client_id, user_id, type, fcm_token, timestamp_created, agent_model, agent_version) VALUES (:cid, :uid, :typ, :fcm, :ts, :am, :av)", sq.PP{
+ "cid": _clientid,
+ "uid": userid,
+ "typ": "ANDROID",
+ "fcm": *user.FcmToken,
+ "ts": user.TimestampCreated.UnixMilli(),
+ "am": "[migrated]",
+ "av": "[migrated]",
+ })
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Printf("Created Client %s\n", _clientid)
+
+ clientid = &_clientid
+
+ usedFCM[*user.FcmToken] = _clientid
+ }
+ }
+
+ mainChannelID := models.NewChannelID()
+ _, err = dbnew.Exec(ctx, "INSERT INTO channels (channel_id, owner_user_id, display_name, internal_name, description_name, subscribe_key, send_key, timestamp_created) VALUES (:cid, :ouid, :dnam, :inam, :hnam, :subkey, :sendkey, :ts)", sq.PP{
+ "cid": mainChannelID,
+ "ouid": userid,
+ "dnam": "main",
+ "inam": "main",
+ "hnam": nil,
+ "subkey": scn.RandomAuthKey(),
+ "sendkey": scn.RandomAuthKey(),
+ "ts": user.TimestampCreated.UnixMilli(),
+ })
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Printf("Created (Main) Channel [%s]: %s\n", "main", mainChannelID)
+
+ _, err = dbnew.Exec(ctx, "INSERT INTO subscriptions (subscription_id, subscriber_user_id, channel_owner_user_id, channel_internal_name, channel_id, timestamp_created, confirmed) VALUES (:sid, :suid, :ouid, :cnam, :cid, :ts, :conf)", sq.PP{
+ "sid": models.NewSubscriptionID(),
+ "suid": user.UserId,
+ "ouid": user.UserId,
+ "cnam": "main",
+ "cid": mainChannelID,
+ "ts": user.TimestampCreated.UnixMilli(),
+ "conf": true,
+ })
+ if err != nil {
+ panic(err)
+ }
+
+ channelMap := make(map[string]models.ChannelID)
+
+ lastTitle := ""
+ lastChannel := models.NewChannelID()
+ lastContent := langext.Ptr("")
+ lastSendername := langext.Ptr("")
+ lastTimestamp := time.Time{}
+
+ for _, oldmessage := range messages {
+
+ messageid := models.NewMessageID()
+
+ title := oldmessage.Title
+
+ channelInternalName := "main"
+ channelID := mainChannelID
+
+ if oldmessage.UsrMessageId != nil && strings.TrimSpace(*oldmessage.UsrMessageId) == "" {
+ oldmessage.UsrMessageId = nil
+ }
+
+ if match, ok := rexTitleChannel.MatchFirst(title); ok {
+
+ chanNameTitle := match.GroupByName("channel").Value()
+
+ if strings.HasPrefix(chanNameTitle, "VBOARD ERROR") {
+ chanNameTitle = "VBOARD-ERROR"
+ }
+
+ if chanNameTitle != "status" {
+ title = match.GroupByName("title").Value()
+
+ dummyApp := logic.Application{}
+
+ dispName := dummyApp.NormalizeChannelDisplayName(chanNameTitle)
+ intName := dummyApp.NormalizeChannelInternalName(chanNameTitle)
+
+ if v, ok := channelMap[intName]; ok {
+ channelID = v
+ channelInternalName = intName
+ } else {
+
+ channelID = models.NewChannelID()
+ channelInternalName = intName
+
+ _, err = dbnew.Exec(ctx, "INSERT INTO channels (channel_id, owner_user_id, display_name, internal_name, description_name, subscribe_key, send_key, timestamp_created) VALUES (:cid, :ouid, :dnam, :inam, :hnam, :subkey, :sendkey, :ts)", sq.PP{
+ "cid": channelID,
+ "ouid": userid,
+ "dnam": dispName,
+ "inam": intName,
+ "hnam": nil,
+ "subkey": scn.RandomAuthKey(),
+ "sendkey": scn.RandomAuthKey(),
+ "ts": oldmessage.TimestampReal.UnixMilli(),
+ })
+ if err != nil {
+ panic(err)
+ }
+
+ _, err = dbnew.Exec(ctx, "INSERT INTO subscriptions (subscription_id, subscriber_user_id, channel_owner_user_id, channel_internal_name, channel_id, timestamp_created, confirmed) VALUES (:sid, :suid, :ouid, :cnam, :cid, :ts, :conf)", sq.PP{
+ "sid": models.NewSubscriptionID(),
+ "suid": user.UserId,
+ "ouid": user.UserId,
+ "cnam": intName,
+ "cid": channelID,
+ "ts": oldmessage.TimestampReal.UnixMilli(),
+ "conf": true,
+ })
+ if err != nil {
+ panic(err)
+ }
+
+ channelMap[intName] = channelID
+
+ fmt.Printf("Auto Created Channel [%s]: %s\n", dispName, channelID)
+
+ }
+ }
+ }
+
+ sendername := determineSenderName(user, oldmessage, title, oldmessage.Content, channelInternalName)
+
+ if lastTitle == title && channelID == lastChannel &&
+ langext.PtrEquals(lastContent, oldmessage.Content) &&
+ langext.PtrEquals(lastSendername, sendername) && oldmessage.TimestampReal.Sub(lastTimestamp) < 5*time.Second {
+
+ lastTitle = title
+ lastChannel = channelID
+ lastContent = oldmessage.Content
+ lastSendername = sendername
+ lastTimestamp = oldmessage.TimestampReal
+
+ fmt.Printf("Skip message [%d] \"%s\" (fast-duplicate)\n", oldmessage.ScnMessageId, oldmessage.Title)
+
+ continue
+ }
+
+ var sendTimeMillis *int64 = nil
+ if oldmessage.Sendtime > 0 && (oldmessage.Sendtime*1000) != oldmessage.TimestampReal.UnixMilli() {
+ sendTimeMillis = langext.Ptr(oldmessage.Sendtime * 1000)
+ }
+
+ if user.UserId == 56 && oldmessage.ScnMessageId >= 15729 {
+ if _, ok := appids[oldmessage.ScnMessageId]; !ok {
+
+ lastTitle = title
+ lastChannel = channelID
+ lastContent = oldmessage.Content
+ lastSendername = sendername
+ lastTimestamp = oldmessage.TimestampReal
+
+ fmt.Printf("Skip message [%d] \"%s\" (locally deleted in app)\n", oldmessage.ScnMessageId, oldmessage.Title)
+ continue
+ }
+ }
+
+ pp := sq.PP{
+ "mid": messageid,
+ "suid": userid,
+ "ouid": user.UserId,
+ "cnam": channelInternalName,
+ "cid": channelID,
+ "tsr": oldmessage.TimestampReal.UnixMilli(),
+ "tsc": sendTimeMillis,
+ "tit": title,
+ "cnt": oldmessage.Content,
+ "prio": oldmessage.Priority,
+ "umid": oldmessage.UsrMessageId,
+ "ip": "",
+ "snam": sendername,
+ }
+ _, err = dbnew.Exec(ctx, "INSERT INTO messages (message_id, sender_user_id, owner_user_id, channel_internal_name, channel_id, timestamp_real, timestamp_client, title, content, priority, usr_message_id, sender_ip, sender_name) VALUES (:mid, :suid, :ouid, :cnam, :cid, :tsr, :tsc, :tit, :cnt, :prio, :umid, :ip, :snam)", pp)
+ if err != nil {
+ jv, _ := json.MarshalIndent(pp, "", " ")
+ fmt.Printf("%s", string(jv))
+ panic(err)
+ }
+
+ _, err = dbnew.Exec(ctx, "INSERT INTO compat_ids (old, new, type) VALUES (:old, :new, :typ)", sq.PP{
+ "old": oldmessage.ScnMessageId,
+ "new": messageid,
+ "typ": "messageid",
+ })
+ if err != nil {
+ panic(err)
+ }
+
+ if len(oldmessage.Ack) == 1 && oldmessage.Ack[0] == 1 {
+
+ if clientid != nil {
+ _, err = dbnew.Exec(ctx, "INSERT INTO deliveries (delivery_id, message_id, receiver_user_id, receiver_client_id, timestamp_created, timestamp_finalized, status, fcm_message_id, next_delivery) VALUES (:did, :mid, :ruid, :rcid, :tsc, :tsf, :stat, :fcm, :next)", sq.PP{
+ "did": models.NewDeliveryID(),
+ "mid": messageid,
+ "ruid": user.UserId,
+ "rcid": *clientid,
+ "tsc": oldmessage.TimestampReal.UnixMilli(),
+ "tsf": oldmessage.TimestampReal.UnixMilli(),
+ "stat": models.DeliveryStatusSuccess,
+ "fcm": *user.FcmToken,
+ "next": nil,
+ })
+ if err != nil {
+ panic(err)
+ }
+ }
+
+ } else if len(oldmessage.Ack) == 1 && oldmessage.Ack[0] == 0 {
+
+ if clientid != nil {
+ _, err = dbnew.Exec(ctx, "INSERT INTO deliveries (delivery_id, message_id, receiver_user_id, receiver_client_id, timestamp_created, timestamp_finalized, status, fcm_message_id, next_delivery) VALUES (:did, :mid, :ruid, :rcid, :tsc, :tsf, :stat, :fcm, :next)", sq.PP{
+ "did": models.NewDeliveryID(),
+ "mid": messageid,
+ "ruid": user.UserId,
+ "rcid": *clientid,
+ "tsc": oldmessage.TimestampReal.UnixMilli(),
+ "tsf": oldmessage.TimestampReal.UnixMilli(),
+ "stat": models.DeliveryStatusFailed,
+ "fcm": *user.FcmToken,
+ "next": nil,
+ })
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Printf("Create failed-delivery for message %d (no ack)\n", oldmessage.ScnMessageId)
+ }
+
+ } else {
+ panic("cannot parse ack")
+ }
+
+ lastTitle = title
+ lastChannel = channelID
+ lastContent = oldmessage.Content
+ lastSendername = sendername
+ lastTimestamp = oldmessage.TimestampReal
+ }
+
+}
+
+func determineSenderName(user OldUser, oldmessage OldMessage, title string, content *string, channame string) *string {
+ if user.UserId != 56 {
+ return nil
+ }
+
+ if channame == "t-ctrl" {
+ return langext.Ptr("sbox")
+ }
+
+ if channame == "torr" {
+ return langext.Ptr("sbox")
+ }
+
+ if channame == "yt-dl" {
+ return langext.Ptr("mscom")
+ }
+
+ if channame == "ncc-upload" {
+ return langext.Ptr("mscom")
+ }
+
+ if channame == "cron" {
+ if strings.Contains(title, "error on bfb") {
+ return langext.Ptr("bfb")
+ }
+ if strings.Contains(title, "error on mscom") {
+ return langext.Ptr("mscom")
+ }
+ if strings.Contains(title, "error on niflheim-3") {
+ return langext.Ptr("niflheim-3")
+ }
+
+ if strings.Contains(*content, "on mscom") {
+ return langext.Ptr("mscom")
+ }
+ if strings.Contains(*content, "on bfb") {
+ return langext.Ptr("bfb")
+ }
+ if strings.Contains(*content, "gogitmirror_cron") {
+ return langext.Ptr("mscom")
+ }
+ if strings.Contains(*content, "comic_downloader") {
+ return langext.Ptr("mscom")
+ }
+ }
+
+ if channame == "sshguard" {
+ if strings.Contains(*content, "logged in to mscom") {
+ return langext.Ptr("mscom")
+ }
+ if strings.Contains(*content, "logged in to bfb") {
+ return langext.Ptr("bfb")
+ }
+ if strings.Contains(*content, "logged in to statussrv") {
+ return langext.Ptr("statussrv")
+ }
+ }
+
+ if channame == "docker-watch" {
+ if strings.Contains(title, "on plantafelstaging") {
+ return langext.Ptr("plantafelstaging")
+ }
+ if strings.Contains(title, "@ mscom") {
+ return langext.Ptr("mscom")
+ }
+ if strings.Contains(title, "@ bfb") {
+ return langext.Ptr("bfb")
+ }
+ if strings.Contains(*content, "registry.blackforestbytes.com/scn_server:latest") {
+ return langext.Ptr("bfb")
+ }
+ if strings.Contains(*content, "archivebox/archivebox:latest") {
+ return langext.Ptr("mscom")
+ }
+ if strings.Contains(*content, "antoniomika/sish:latest") {
+ return langext.Ptr("mscom")
+ }
+ if strings.Contains(*content, "binwiederhier/ntfy:latest") {
+ return langext.Ptr("mscom")
+ }
+ if strings.Contains(*content, "registry.blackforestbytes.com/kgserver:latest") {
+ return langext.Ptr("mscom")
+ }
+ if strings.Contains(*content, "registry.blackforestbytes.com/mikescher/kgserver:latest") {
+ return langext.Ptr("mscom")
+ }
+ if strings.Contains(*content, "jenkins/jenkins:lts") {
+ return langext.Ptr("mscom")
+ }
+ if strings.Contains(*content, "mikescher/youtube-dl-viewer:latest") {
+ return langext.Ptr("mscom")
+ }
+ if strings.Contains(*content, "etherpad/etherpad:latest") {
+ return langext.Ptr("mscom")
+ }
+ if strings.Contains(*content, "teamcity_agent") {
+ return langext.Ptr("bfb")
+ }
+ if strings.Contains(*content, "teamcity_server") {
+ return langext.Ptr("bfb")
+ }
+ if strings.Contains(*content, "registry.blackforestbytes.com/inoshop/") {
+ return langext.Ptr("inoshop")
+ }
+ if strings.Contains(*content, "inopart_mongo_") {
+ return langext.Ptr("inoshop")
+ }
+ if strings.Contains(*content, "Image: wkk_") {
+ return langext.Ptr("wkk")
+ }
+ if strings.Contains(*content, "registry.blackforestbytes.com/holz100") {
+ return langext.Ptr("bfb")
+ }
+ if strings.Contains(*content, "registry.blackforestbytes.com/bewirto") {
+ return langext.Ptr("bfb-testserver")
+ }
+ if strings.Contains(*content, "registry.blackforestbytes.com/bfb-website") {
+ return langext.Ptr("bfb")
+ }
+ if strings.Contains(*content, "registry.blackforestbytes.com/bfb/website") {
+ return langext.Ptr("bfb")
+ }
+ if strings.Contains(*content, "registry.blackforestbytes.com/psycho/backend") {
+ return langext.Ptr("bfb-testserver")
+ }
+ if strings.Contains(*content, "registry.blackforestbytes.com/vereinsboard") {
+ return langext.Ptr("bfb")
+ }
+ if strings.Contains(*content, "registry.blackforestbytes.com/isiproject") {
+ return langext.Ptr("bfb")
+ }
+ if strings.Contains(*content, "registry.blackforestbytes.com/ar-app-supportchat-server") {
+ return langext.Ptr("bfb")
+ }
+ if strings.Contains(*content, "registry.blackforestbytes.com/planitec/ar-app-supportchat-server") {
+ return langext.Ptr("bfb")
+ }
+ if strings.Contains(*content, "docker_registry") {
+ return langext.Ptr("bfb")
+ }
+ if strings.Contains(*content, "registry.blackforestbytes.com/balu") && strings.Contains(*content, "prod") {
+ return langext.Ptr("lbxprod")
+ }
+ if strings.Contains(*content, "registry.blackforestbytes.com/balu") && strings.Contains(*content, "dev") {
+ return langext.Ptr("lbxdev")
+ }
+ if strings.Contains(*content, "Server: bfb-testserver") {
+ return langext.Ptr("bfb-testserver")
+ }
+ if strings.Contains(*content, "wptest_") {
+ return langext.Ptr("bfb")
+ }
+ if strings.Contains(title, "balu-db") {
+ return langext.Ptr("lbprod")
+ }
+ }
+
+ if channame == "certbot" {
+ if strings.Contains(title, "Update cert_badennet_main") {
+ return langext.Ptr("bfb-testserver")
+ }
+ if strings.Contains(title, "Update cert_badennet_main") {
+ return langext.Ptr("bfb-testserver")
+ }
+ if strings.Contains(title, "Update cert_bfbugs_main") {
+ return langext.Ptr("bfb-testserver")
+ }
+ if strings.Contains(title, "Update bfbugs_0001") {
+ return langext.Ptr("bfb-testserver")
+ }
+ if strings.Contains(title, "Update inoshop_bfb") {
+ return langext.Ptr("inoshop")
+ }
+ if strings.Contains(title, "Update cert_bfb_0001") {
+ return langext.Ptr("mscom")
+ }
+ if strings.Contains(title, "Update cert_bugkultur_0001") {
+ return langext.Ptr("mscom")
+ }
+ if strings.Contains(title, "Update cert_public_0001") {
+ return langext.Ptr("mscom")
+ }
+ if strings.Contains(title, "Update cert_korbers_0001") {
+ return langext.Ptr("mscom")
+ }
+ if strings.Contains(title, "Update cert_wkk_staging_external") {
+ return langext.Ptr("wkk")
+ }
+ if strings.Contains(title, "Update cert_wkk_production_external") {
+ return langext.Ptr("wkk")
+ }
+ if strings.Contains(title, "Update cert_wkk_develop_external") {
+ return langext.Ptr("wkk")
+ }
+ if strings.Contains(title, "Update cert_wkk_internal") {
+ return langext.Ptr("wkk")
+ }
+ if strings.Contains(title, "Update bfb_de_wildcard") {
+ return langext.Ptr("bfb")
+ }
+ if strings.Contains(title, "Update cannonconquest") {
+ return langext.Ptr("bfb")
+ }
+ if strings.Contains(title, "Update isiproject_wildcard") {
+ return langext.Ptr("bfb")
+ }
+ if strings.Contains(title, "Update vereinsboard_demo") {
+ return langext.Ptr("bfb")
+ }
+ if strings.Contains(title, "Update vereinsboard_wildcard") {
+ return langext.Ptr("bfb")
+ }
+ if strings.Contains(title, "Update cert_bewirto_main") {
+ return langext.Ptr("bfb-testserver")
+ }
+ if strings.Contains(title, "Update cert_badennet_main") {
+ return langext.Ptr("bfb-testserver")
+ }
+ if strings.Contains(title, "Update cert_mampfkultur_main") {
+ return langext.Ptr("bfb-testserver")
+ }
+ if strings.Contains(title, "Update cert_psycho_main") {
+ return langext.Ptr("bfb-testserver")
+ }
+ if strings.Contains(*content, "DNS:*.blackforestbytes.com") {
+ return langext.Ptr("bfb")
+ }
+ if strings.Contains(*content, "DNS:*.mikescher.com") {
+ return langext.Ptr("mscom")
+ }
+ if strings.Contains(title, "plantafel-digital.de") {
+ return langext.Ptr("plan-web-prod")
+ }
+ if strings.Contains(title, "plantafeldev.de") {
+ return langext.Ptr("plantafeldev")
+ }
+ if strings.Contains(title, "plantafelstaging.de") {
+ return langext.Ptr("plantafeldev")
+ }
+ if strings.Contains(*content, "DNS:*.plantafeldev.de") {
+ return langext.Ptr("plantafeldev")
+ }
+ if strings.Contains(*content, "plantafel-digital.de") {
+ return langext.Ptr("plan-web-prod")
+ }
+ if strings.Contains(*content, "plantafeldev.de") {
+ return langext.Ptr("plantafeldev")
+ }
+ if strings.Contains(*content, "plantafelstaging.de") {
+ return langext.Ptr("plantafeldev")
+ }
+ }
+
+ if channame == "space-warning" {
+ if title == "bfb" {
+ return langext.Ptr("bfb")
+ }
+ if title == "mscom" {
+ return langext.Ptr("mscom")
+ }
+ if title == "plan-web-prod" {
+ return langext.Ptr("plan-web-prod")
+ }
+ if title == "statussrv" {
+ return langext.Ptr("statussrv")
+ }
+ }
+
+ if channame == "srv-backup" {
+ if strings.Contains(*content, "Server: bfb-testserver") {
+ return langext.Ptr("bfb-testserver")
+ }
+ if strings.Contains(*content, "Server: bfb") {
+ return langext.Ptr("bfb")
+ }
+ if strings.Contains(*content, "Server: mscom") {
+ return langext.Ptr("mscom")
+ }
+ if strings.Contains(*content, "Server: statussrv") {
+ return langext.Ptr("statussrv")
+ }
+ }
+
+ if title == "[status] Updating uptime-kuma image" {
+ return langext.Ptr("statussrv")
+ }
+
+ if channame == "omv-backup" {
+ return langext.Ptr("omv")
+ }
+
+ if channame == "omv-rcheck" {
+ return langext.Ptr("omv")
+ }
+
+ if channame == "tfin" {
+ return langext.Ptr("sbox")
+ }
+
+ if channame == "vboard-error" {
+ return langext.Ptr("bfb")
+ }
+
+ if channame == "vboard" {
+ return langext.Ptr("bfb")
+ }
+
+ if channame == "cubox" {
+ return langext.Ptr("cubox")
+ }
+
+ if channame == "sys" {
+ if strings.Contains(title, "h2896063") {
+ return langext.Ptr("mscom")
+ }
+ if strings.Contains(title, "h2516246") {
+ return langext.Ptr("mscom")
+ }
+ if strings.Contains(title, "h2770024") {
+ return langext.Ptr("bfb")
+ }
+ if strings.Contains(title, "Reboot plan-web-prod") {
+ return langext.Ptr("plan-web-prod")
+ }
+ if strings.Contains(title, "Reboot mikescher.com") {
+ return langext.Ptr("mscom")
+ }
+ if strings.Contains(title, "Reboot blackforestbytes.com") {
+ return langext.Ptr("bfb")
+ }
+ if strings.Contains(title, "Reboot plan-web-dev") {
+ return langext.Ptr("plan-web-dev")
+ }
+ if strings.Contains(title, "Reboot plan-web-staging") {
+ return langext.Ptr("plan-web-staging")
+ }
+ if strings.Contains(title, "Reboot virmach-01") {
+ return langext.Ptr("statussrv")
+ }
+ if strings.Contains(title, "Reboot wkk-1") {
+ return langext.Ptr("wkk")
+ }
+ if strings.Contains(title, "Reboot lbxprod") {
+ return langext.Ptr("lbxprod")
+ }
+ }
+
+ if channame == "yt-tvc" {
+ return langext.Ptr("mscom")
+ }
+
+ if channame == "gdapi" {
+ return langext.Ptr("bfb")
+ }
+
+ if channame == "ttrss" {
+ return langext.Ptr("mscom")
+ }
+
+ if title == "NCC Upload failed" || title == "NCC Upload successful" {
+ return langext.Ptr("mscom")
+ }
+
+ if oldmessage.ScnMessageId == 7975 {
+ return langext.Ptr("mscom")
+ }
+
+ if strings.Contains(title, "bfbackup job") {
+ return langext.Ptr("bfbackup")
+ }
+
+ if strings.Contains(title, "Repo migration of /volume1") {
+ return langext.Ptr("bfbackup")
+ }
+
+ //fmt.Printf("Failed to determine sender of [%d] '%s' '%s'\n", oldmessage.ScnMessageId, oldmessage.Title, langext.Coalesce(oldmessage.Content, ""))
+ fmt.Printf("Failed to determine sender of [%d] '%s'\n", oldmessage.ScnMessageId, oldmessage.Title)
+
+ return nil
+}
diff --git a/scnserver/config.go b/scnserver/config.go
index c8e9c37..1f6d2ef 100644
--- a/scnserver/config.go
+++ b/scnserver/config.go
@@ -55,6 +55,7 @@ type DBConfig struct {
CheckForeignKeys bool `env:"CHECKFOREIGNKEYS"`
SingleConn bool `env:"SINGLECONNECTION"`
BusyTimeout time.Duration `env:"BUSYTIMEOUT"`
+ EnableLogger bool `env:"ENABLELOGGER"`
}
var Conf Config
@@ -78,6 +79,7 @@ var configLocHost = func() Config {
ConnMaxLifetime: 60 * time.Minute,
ConnMaxIdleTime: 60 * time.Minute,
BusyTimeout: 100 * time.Millisecond,
+ EnableLogger: true,
},
DBRequests: DBConfig{
File: ".run-data/loc_requests.sqlite3",
@@ -90,6 +92,7 @@ var configLocHost = func() Config {
ConnMaxLifetime: 60 * time.Minute,
ConnMaxIdleTime: 60 * time.Minute,
BusyTimeout: 500 * time.Millisecond,
+ EnableLogger: true,
},
DBLogs: DBConfig{
File: ".run-data/loc_logs.sqlite3",
@@ -102,6 +105,7 @@ var configLocHost = func() Config {
ConnMaxLifetime: 60 * time.Minute,
ConnMaxIdleTime: 60 * time.Minute,
BusyTimeout: 500 * time.Millisecond,
+ EnableLogger: true,
},
RequestTimeout: 16 * time.Second,
RequestMaxRetry: 8,
@@ -147,6 +151,7 @@ var configLocDocker = func() Config {
ConnMaxLifetime: 60 * time.Minute,
ConnMaxIdleTime: 60 * time.Minute,
BusyTimeout: 100 * time.Millisecond,
+ EnableLogger: true,
},
DBRequests: DBConfig{
File: "/data/docker_scn_requests.sqlite3",
@@ -159,6 +164,7 @@ var configLocDocker = func() Config {
ConnMaxLifetime: 60 * time.Minute,
ConnMaxIdleTime: 60 * time.Minute,
BusyTimeout: 500 * time.Millisecond,
+ EnableLogger: true,
},
DBLogs: DBConfig{
File: "/data/docker_scn_logs.sqlite3",
@@ -171,6 +177,7 @@ var configLocDocker = func() Config {
ConnMaxLifetime: 60 * time.Minute,
ConnMaxIdleTime: 60 * time.Minute,
BusyTimeout: 500 * time.Millisecond,
+ EnableLogger: true,
},
RequestTimeout: 16 * time.Second,
RequestMaxRetry: 8,
@@ -215,6 +222,7 @@ var configDev = func() Config {
ConnMaxLifetime: 60 * time.Minute,
ConnMaxIdleTime: 60 * time.Minute,
BusyTimeout: 100 * time.Millisecond,
+ EnableLogger: true,
},
DBRequests: DBConfig{
File: "/data/scn_requests.sqlite3",
@@ -227,6 +235,7 @@ var configDev = func() Config {
ConnMaxLifetime: 60 * time.Minute,
ConnMaxIdleTime: 60 * time.Minute,
BusyTimeout: 500 * time.Millisecond,
+ EnableLogger: true,
},
DBLogs: DBConfig{
File: "/data/scn_logs.sqlite3",
@@ -239,6 +248,7 @@ var configDev = func() Config {
ConnMaxLifetime: 60 * time.Minute,
ConnMaxIdleTime: 60 * time.Minute,
BusyTimeout: 500 * time.Millisecond,
+ EnableLogger: true,
},
RequestTimeout: 16 * time.Second,
RequestMaxRetry: 8,
@@ -283,6 +293,7 @@ var configStag = func() Config {
ConnMaxLifetime: 60 * time.Minute,
ConnMaxIdleTime: 60 * time.Minute,
BusyTimeout: 100 * time.Millisecond,
+ EnableLogger: true,
},
DBRequests: DBConfig{
File: "/data/scn_requests.sqlite3",
@@ -295,6 +306,7 @@ var configStag = func() Config {
ConnMaxLifetime: 60 * time.Minute,
ConnMaxIdleTime: 60 * time.Minute,
BusyTimeout: 500 * time.Millisecond,
+ EnableLogger: true,
},
DBLogs: DBConfig{
File: "/data/scn_logs.sqlite3",
@@ -307,6 +319,7 @@ var configStag = func() Config {
ConnMaxLifetime: 60 * time.Minute,
ConnMaxIdleTime: 60 * time.Minute,
BusyTimeout: 500 * time.Millisecond,
+ EnableLogger: true,
},
RequestTimeout: 16 * time.Second,
RequestMaxRetry: 8,
@@ -351,6 +364,7 @@ var configProd = func() Config {
ConnMaxLifetime: 60 * time.Minute,
ConnMaxIdleTime: 60 * time.Minute,
BusyTimeout: 100 * time.Millisecond,
+ EnableLogger: true,
},
DBRequests: DBConfig{
File: "/data/scn_requests.sqlite3",
@@ -363,6 +377,7 @@ var configProd = func() Config {
ConnMaxLifetime: 60 * time.Minute,
ConnMaxIdleTime: 60 * time.Minute,
BusyTimeout: 500 * time.Millisecond,
+ EnableLogger: true,
},
DBLogs: DBConfig{
File: "/data/scn_logs.sqlite3",
@@ -375,6 +390,7 @@ var configProd = func() Config {
ConnMaxLifetime: 60 * time.Minute,
ConnMaxIdleTime: 60 * time.Minute,
BusyTimeout: 500 * time.Millisecond,
+ EnableLogger: true,
},
RequestTimeout: 16 * time.Second,
RequestMaxRetry: 8,
diff --git a/scnserver/db/database.go b/scnserver/db/database.go
index d619b63..6053078 100644
--- a/scnserver/db/database.go
+++ b/scnserver/db/database.go
@@ -6,6 +6,8 @@ import (
)
type DatabaseImpl interface {
+ DB() sq.DB
+
Migrate(ctx context.Context) error
Ping(ctx context.Context) error
BeginTx(ctx context.Context) (sq.Tx, error)
diff --git a/scnserver/db/dbtools/logger.go b/scnserver/db/dbtools/logger.go
index bc0cf04..4fae157 100644
--- a/scnserver/db/dbtools/logger.go
+++ b/scnserver/db/dbtools/logger.go
@@ -81,7 +81,7 @@ func (l DBLogger) PostExec(txID *uint16, sqlOriginal string, sqlReal string, par
}
func fmtSQLPrint(sql string) string {
- if strings.Contains(sql, ";") {
+ if strings.Contains(sql, ";") && len(sql) > 1024 {
return "(...multi...)"
}
diff --git a/scnserver/db/dbtools/preprocessor.go b/scnserver/db/dbtools/preprocessor.go
index 0d5fa8f..32625c3 100644
--- a/scnserver/db/dbtools/preprocessor.go
+++ b/scnserver/db/dbtools/preprocessor.go
@@ -6,6 +6,7 @@ import (
"fmt"
"github.com/rs/zerolog/log"
"gogs.mikescher.com/BlackForestBytes/goext/langext"
+ "gogs.mikescher.com/BlackForestBytes/goext/rext"
"gogs.mikescher.com/BlackForestBytes/goext/sq"
"regexp"
"strings"
@@ -37,7 +38,7 @@ type DBPreprocessor struct {
cacheQuery map[string]string
}
-var regexAlias = regexp.MustCompile("([A-Za-z_\\-0-9]+)\\s+AS\\s+([A-Za-z_\\-0-9]+)")
+var regexAlias = rext.W(regexp.MustCompile("([A-Za-z_\\-0-9]+)\\s+AS\\s+([A-Za-z_\\-0-9]+)"))
func NewDBPreprocessor(db sq.DB) (*DBPreprocessor, error) {
@@ -146,8 +147,8 @@ func (pp *DBPreprocessor) PreQuery(ctx context.Context, txID *uint16, sql *strin
newsel := make([]string, 0)
aliasMap := make(map[string]string)
- for _, v := range regexAlias.FindAllStringSubmatch(sqlOriginal, idxFrom+len(" FROM")) {
- aliasMap[strings.TrimSpace(v[2])] = strings.TrimSpace(v[1])
+ for _, v := range regexAlias.MatchAll(sqlOriginal) {
+ aliasMap[strings.TrimSpace(v.GroupByIndex(1).Value())] = strings.TrimSpace(v.GroupByIndex(2).Value())
}
for _, expr := range split {
diff --git a/scnserver/db/impl/logs/database.go b/scnserver/db/impl/logs/database.go
index 7941c46..6b6d749 100644
--- a/scnserver/db/impl/logs/database.go
+++ b/scnserver/db/impl/logs/database.go
@@ -42,7 +42,9 @@ func NewLogsDatabase(cfg server.Config) (*Database, error) {
qqdb := sq.NewDB(xdb)
- qqdb.AddListener(dbtools.DBLogger{})
+ if conf.EnableLogger {
+ qqdb.AddListener(dbtools.DBLogger{})
+ }
pp, err := dbtools.NewDBPreprocessor(qqdb)
if err != nil {
@@ -56,6 +58,10 @@ func NewLogsDatabase(cfg server.Config) (*Database, error) {
return scndb, nil
}
+func (db *Database) DB() sq.DB {
+ return db.db
+}
+
func (db *Database) Migrate(ctx context.Context) error {
ctx, cancel := context.WithTimeout(context.Background(), 24*time.Second)
defer cancel()
diff --git a/scnserver/db/impl/primary/channels.go b/scnserver/db/impl/primary/channels.go
index 395d648..38dc24a 100644
--- a/scnserver/db/impl/primary/channels.go
+++ b/scnserver/db/impl/primary/channels.go
@@ -91,11 +91,12 @@ func (db *Database) CreateChannel(ctx TxContext, userid models.UserID, dispName
channelid := models.NewChannelID()
- _, err = tx.Exec(ctx, "INSERT INTO channels (channel_id, owner_user_id, display_name, internal_name, subscribe_key, send_key, timestamp_created) VALUES (:cid, :ouid, :dnam, :inam, :subkey, :sendkey, :ts)", sq.PP{
+ _, err = tx.Exec(ctx, "INSERT INTO channels (channel_id, owner_user_id, display_name, internal_name, description_name, subscribe_key, send_key, timestamp_created) VALUES (:cid, :ouid, :dnam, :inam, :hnam, :subkey, :sendkey, :ts)", sq.PP{
"cid": channelid,
"ouid": userid,
"dnam": dispName,
"inam": intName,
+ "hnam": nil,
"subkey": subscribeKey,
"sendkey": sendKey,
"ts": time2DB(now),
diff --git a/scnserver/db/impl/primary/database.go b/scnserver/db/impl/primary/database.go
index 8b1908f..1f2259f 100644
--- a/scnserver/db/impl/primary/database.go
+++ b/scnserver/db/impl/primary/database.go
@@ -42,7 +42,9 @@ func NewPrimaryDatabase(cfg server.Config) (*Database, error) {
qqdb := sq.NewDB(xdb)
- qqdb.AddListener(dbtools.DBLogger{})
+ if conf.EnableLogger {
+ qqdb.AddListener(dbtools.DBLogger{})
+ }
pp, err := dbtools.NewDBPreprocessor(qqdb)
if err != nil {
@@ -56,6 +58,10 @@ func NewPrimaryDatabase(cfg server.Config) (*Database, error) {
return scndb, nil
}
+func (db *Database) DB() sq.DB {
+ return db.db
+}
+
func (db *Database) Migrate(ctx context.Context) error {
ctx, cancel := context.WithTimeout(context.Background(), 24*time.Second)
defer cancel()
diff --git a/scnserver/db/impl/requests/database.go b/scnserver/db/impl/requests/database.go
index 0e0aaba..1361336 100644
--- a/scnserver/db/impl/requests/database.go
+++ b/scnserver/db/impl/requests/database.go
@@ -42,7 +42,9 @@ func NewRequestsDatabase(cfg server.Config) (*Database, error) {
qqdb := sq.NewDB(xdb)
- qqdb.AddListener(dbtools.DBLogger{})
+ if conf.EnableLogger {
+ qqdb.AddListener(dbtools.DBLogger{})
+ }
pp, err := dbtools.NewDBPreprocessor(qqdb)
if err != nil {
@@ -56,6 +58,10 @@ func NewRequestsDatabase(cfg server.Config) (*Database, error) {
return scndb, nil
}
+func (db *Database) DB() sq.DB {
+ return db.db
+}
+
func (db *Database) Migrate(ctx context.Context) error {
ctx, cancel := context.WithTimeout(context.Background(), 24*time.Second)
defer cancel()
diff --git a/scnserver/go.mod b/scnserver/go.mod
index a3b33c2..53b45e5 100644
--- a/scnserver/go.mod
+++ b/scnserver/go.mod
@@ -4,10 +4,12 @@ go 1.19
require (
github.com/gin-gonic/gin v1.8.1
+ github.com/go-playground/validator/v10 v10.10.0
+ github.com/go-sql-driver/mysql v1.6.0
github.com/jmoiron/sqlx v1.3.5
github.com/mattn/go-sqlite3 v1.14.16
github.com/rs/zerolog v1.28.0
- gogs.mikescher.com/BlackForestBytes/goext v0.0.56
+ gogs.mikescher.com/BlackForestBytes/goext v0.0.59
gopkg.in/loremipsum.v1 v1.1.0
)
@@ -15,7 +17,6 @@ require (
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
- github.com/go-playground/validator/v10 v10.10.0 // indirect
github.com/goccy/go-json v0.9.7 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/json-iterator/go v1.1.12 // indirect
diff --git a/scnserver/go.sum b/scnserver/go.sum
index 4f09b74..6457e10 100644
--- a/scnserver/go.sum
+++ b/scnserver/go.sum
@@ -73,14 +73,14 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
-gogs.mikescher.com/BlackForestBytes/goext v0.0.49 h1:Ro62ZyJW22elAJKT0XlY94LzAv0dVuiI2m0/Hp1xLgk=
-gogs.mikescher.com/BlackForestBytes/goext v0.0.49/go.mod h1:ZEXyKUr8t0EKdPN1FYdk0klY7N8OwXxipGE9lWgpVE8=
-gogs.mikescher.com/BlackForestBytes/goext v0.0.50 h1:WuhfxFVyywR7J4+hSTTW/wE87aFbGk7q22TGYusPg0s=
-gogs.mikescher.com/BlackForestBytes/goext v0.0.50/go.mod h1:ZEXyKUr8t0EKdPN1FYdk0klY7N8OwXxipGE9lWgpVE8=
-gogs.mikescher.com/BlackForestBytes/goext v0.0.55 h1:mzX/s+EBhnaRbiz3+6iwDJyJFS0F+jkbssiLDr9eJYY=
-gogs.mikescher.com/BlackForestBytes/goext v0.0.55/go.mod h1:ZEXyKUr8t0EKdPN1FYdk0klY7N8OwXxipGE9lWgpVE8=
gogs.mikescher.com/BlackForestBytes/goext v0.0.56 h1:nl+2mP3BmkeB3kT6zFNXqYkOLc3JnFF3m8QwhxZJf2A=
gogs.mikescher.com/BlackForestBytes/goext v0.0.56/go.mod h1:ZEXyKUr8t0EKdPN1FYdk0klY7N8OwXxipGE9lWgpVE8=
+gogs.mikescher.com/BlackForestBytes/goext v0.0.57 h1:R5M0Y+4kS6v5GtsXcHlDBYbcfenj1nOmAaNj4XQUous=
+gogs.mikescher.com/BlackForestBytes/goext v0.0.57/go.mod h1:ZEXyKUr8t0EKdPN1FYdk0klY7N8OwXxipGE9lWgpVE8=
+gogs.mikescher.com/BlackForestBytes/goext v0.0.58 h1:W53yfHhpFQS13zgtzCjfJQ42WG0OORa+kQWKrp+W73Q=
+gogs.mikescher.com/BlackForestBytes/goext v0.0.58/go.mod h1:ZEXyKUr8t0EKdPN1FYdk0klY7N8OwXxipGE9lWgpVE8=
+gogs.mikescher.com/BlackForestBytes/goext v0.0.59 h1:3bHSjqgty9yp0EIyqwGAb06ZS7bLvm806zRj6j+WOEE=
+gogs.mikescher.com/BlackForestBytes/goext v0.0.59/go.mod h1:ZEXyKUr8t0EKdPN1FYdk0klY7N8OwXxipGE9lWgpVE8=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
diff --git a/scnserver/logic/application.go b/scnserver/logic/application.go
index d9eaa6b..943dbc7 100644
--- a/scnserver/logic/application.go
+++ b/scnserver/logic/application.go
@@ -12,8 +12,8 @@ import (
"github.com/gin-gonic/gin/binding"
"github.com/rs/zerolog/log"
"gogs.mikescher.com/BlackForestBytes/goext/langext"
+ "gogs.mikescher.com/BlackForestBytes/goext/rext"
"gogs.mikescher.com/BlackForestBytes/goext/syncext"
- "math/rand"
"net"
"net/http"
"os"
@@ -24,9 +24,9 @@ import (
"time"
)
-var rexWhitespaceStart = regexp.MustCompile("^\\s+")
-
-var rexWhitespaceEnd = regexp.MustCompile("\\s+$")
+var rexWhitespaceStart = rext.W(regexp.MustCompile("^\\s+"))
+var rexWhitespaceEnd = rext.W(regexp.MustCompile("\\s+$"))
+var rexNormalizeUsername = rext.W(regexp.MustCompile("[^[:alnum:]\\-_ ]"))
type Application struct {
Config scn.Config
@@ -154,12 +154,7 @@ func (app *Application) Run() {
}
func (app *Application) GenerateRandomAuthKey() string {
- charset := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
- k := ""
- for i := 0; i < 64; i++ {
- k += string(charset[rand.Int()%len(charset)])
- }
- return k
+ return scn.RandomAuthKey()
}
func (app *Application) QuotaMax(ispro bool) int {
@@ -171,6 +166,10 @@ func (app *Application) QuotaMax(ispro bool) int {
}
func (app *Application) VerifyProToken(ctx *AppContext, token string) (bool, error) {
+ if strings.HasPrefix(token, "ANDROID|v1|") {
+ subToken := token[len("ANDROID|v2|"):]
+ return app.VerifyAndroidProToken(ctx, subToken)
+ }
if strings.HasPrefix(token, "ANDROID|v2|") {
subToken := token[len("ANDROID|v2|"):]
return app.VerifyAndroidProToken(ctx, subToken)
@@ -319,8 +318,8 @@ func (app *Application) GetOrCreateChannel(ctx *AppContext, userid models.UserID
func (app *Application) NormalizeChannelDisplayName(v string) string {
v = strings.TrimSpace(v)
- v = rexWhitespaceStart.ReplaceAllString(v, "")
- v = rexWhitespaceEnd.ReplaceAllString(v, "")
+ v = rexWhitespaceStart.RemoveAll(v)
+ v = rexWhitespaceEnd.RemoveAll(v)
return v
}
@@ -328,17 +327,15 @@ func (app *Application) NormalizeChannelDisplayName(v string) string {
func (app *Application) NormalizeChannelInternalName(v string) string {
v = strings.TrimSpace(v)
v = strings.ToLower(v)
- v = rexWhitespaceStart.ReplaceAllString(v, "")
- v = rexWhitespaceEnd.ReplaceAllString(v, "")
+ v = rexWhitespaceStart.RemoveAll(v)
+ v = rexWhitespaceEnd.RemoveAll(v)
return v
}
func (app *Application) NormalizeUsername(v string) string {
- rex := regexp.MustCompile("[^[:alnum:]\\-_ ]")
-
v = strings.TrimSpace(v)
- v = rex.ReplaceAllString(v, "")
+ v = rexNormalizeUsername.RemoveAll(v)
return v
}
diff --git a/scnserver/models/ids.go b/scnserver/models/ids.go
index 8a19e37..85c0d0d 100644
--- a/scnserver/models/ids.go
+++ b/scnserver/models/ids.go
@@ -7,6 +7,7 @@ import (
"github.com/go-playground/validator/v10"
"github.com/rs/zerolog/log"
"gogs.mikescher.com/BlackForestBytes/goext/langext"
+ "gogs.mikescher.com/BlackForestBytes/goext/rext"
"math/big"
"reflect"
"regexp"
@@ -19,7 +20,7 @@ type EntityID interface {
Prefix() string
Raw() string
CheckString() string
- Regex() *regexp.Regexp
+ Regex() rext.Regex
}
const idlen = 24
@@ -51,8 +52,8 @@ var (
regexRequestID = generateRegex(prefixRequestID)
)
-func generateRegex(prefix string) *regexp.Regexp {
- return regexp.MustCompile(fmt.Sprintf("^%s[%s]{%d}[%s]{%d}$", prefix, idCharset, idlen-len(prefix)-checklen, idCharset, checklen))
+func generateRegex(prefix string) rext.Regex {
+ return rext.W(regexp.MustCompile(fmt.Sprintf("^%s[%s]{%d}[%s]{%d}$", prefix, idCharset, idlen-len(prefix)-checklen, idCharset, checklen)))
}
func generateCharsetMap() []int {
@@ -179,7 +180,7 @@ func (id UserID) CheckString() string {
return getCheckString(prefixUserID, string(id))
}
-func (id UserID) Regex() *regexp.Regexp {
+func (id UserID) Regex() rext.Regex {
return regexUserID
}
@@ -211,7 +212,7 @@ func (id ChannelID) CheckString() string {
return getCheckString(prefixChannelID, string(id))
}
-func (id ChannelID) Regex() *regexp.Regexp {
+func (id ChannelID) Regex() rext.Regex {
return regexChannelID
}
@@ -243,7 +244,7 @@ func (id DeliveryID) CheckString() string {
return getCheckString(prefixDeliveryID, string(id))
}
-func (id DeliveryID) Regex() *regexp.Regexp {
+func (id DeliveryID) Regex() rext.Regex {
return regexDeliveryID
}
@@ -275,7 +276,7 @@ func (id MessageID) CheckString() string {
return getCheckString(prefixMessageID, string(id))
}
-func (id MessageID) Regex() *regexp.Regexp {
+func (id MessageID) Regex() rext.Regex {
return regexMessageID
}
@@ -307,7 +308,7 @@ func (id SubscriptionID) CheckString() string {
return getCheckString(prefixSubscriptionID, string(id))
}
-func (id SubscriptionID) Regex() *regexp.Regexp {
+func (id SubscriptionID) Regex() rext.Regex {
return regexSubscriptionID
}
@@ -339,7 +340,7 @@ func (id ClientID) CheckString() string {
return getCheckString(prefixClientID, string(id))
}
-func (id ClientID) Regex() *regexp.Regexp {
+func (id ClientID) Regex() rext.Regex {
return regexClientID
}
@@ -371,6 +372,6 @@ func (id RequestID) CheckString() string {
return getCheckString(prefixRequestID, string(id))
}
-func (id RequestID) Regex() *regexp.Regexp {
+func (id RequestID) Regex() rext.Regex {
return regexRequestID
}
diff --git a/scnserver/util.go b/scnserver/util.go
index 9d75dbc..03c994e 100644
--- a/scnserver/util.go
+++ b/scnserver/util.go
@@ -2,6 +2,7 @@ package server
import (
"gogs.mikescher.com/BlackForestBytes/goext/timeext"
+ "math/rand"
"time"
)
@@ -12,3 +13,12 @@ func QuotaDayString() string {
func NextDeliveryTimestamp(now time.Time) time.Time {
return now.Add(5 * time.Second)
}
+
+func RandomAuthKey() string {
+ charset := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+ k := ""
+ for i := 0; i < 64; i++ {
+ k += string(charset[rand.Int()%len(charset)])
+ }
+ return k
+}