Compare commits

...

7 Commits

Author SHA1 Message Date
1ba4f865de .gitea/workflows/release.yml hinzugefügt
All checks were successful
release-tag / release-image (push) Successful in 2m13s
build-binaries / build (, amd64, linux) (push) Successful in 11m24s
build-binaries / build (, arm, 7, linux) (push) Successful in 10m42s
build-binaries / build (, arm64, linux) (push) Successful in 10m41s
build-binaries / build (.exe, amd64, windows) (push) Successful in 10m46s
build-binaries / release (push) Successful in 38s
2025-08-06 17:36:56 +00:00
acd9fda529 Bugfix: Discord-Webhook Anzeige von UEC-Profit ist immer 0
All checks were successful
release-tag / release-image (push) Successful in 2m6s
2025-08-02 19:11:46 +02:00
f63e04f7b0 Discord-Push hinzugefügt
All checks were successful
release-tag / release-image (push) Successful in 2m11s
2025-08-02 19:02:25 +02:00
ff2f37f718 Bug behoben: Wenn Einträge bearbeitet werden, wird man visuell abgemeldet. Das stimmt jedoch nicht, und mit eine F5 ist man wieder angemeldet.
All checks were successful
release-tag / release-image (push) Successful in 2m30s
2025-08-01 21:00:10 +02:00
134e601d57 Debug-Schritte entfernt
All checks were successful
release-tag / release-image (push) Successful in 2m13s
2025-07-31 23:13:52 +02:00
842c541c13 Hotfix - Login-Bug
Some checks failed
release-tag / release-image (push) Has been cancelled
2025-07-31 23:12:11 +02:00
41cee8af1d Berechnungs-Cache eingebaut, um Anzeige zu beschleunigen. Standard-Wert für Cache ist 6 Stunden und ist In-Memory
All checks were successful
release-tag / release-image (push) Successful in 2m18s
2025-07-31 22:32:19 +02:00
5 changed files with 396 additions and 24 deletions

View File

@@ -0,0 +1,123 @@
# Git(tea) Actions workflow: Build and publish standalone binaries **plus** bundled `static/` assets
# ────────────────────────────────────────────────────────────────────
# ✧ Builds the Gobased WoL server for four targets **and** packt das Verzeichnis
# `static` zusammen mit der Binary, sodass es relativ zur ausführbaren Datei
# liegt (wichtig für die eingebauten BootstrapAssets & favicon).
#
# • linux/amd64 → wol-server-linux-amd64.tar.gz
# • linux/arm64 → wol-server-linux-arm64.tar.gz
# • linux/arm/v7 → wol-server-linux-armv7.tar.gz
# • windows/amd64 → wol-server-windows-amd64.zip
#
# ✧ Artefakte landen im Workflow und bei TagPush (vX.Y.Z) als ReleaseAssets.
#
# Secrets/variables:
# GITEA_TOKEN optional, falls default token keine ReleaseRechte hat.
# ────────────────────────────────────────────────────────────────────
name: build-binaries
on:
push:
branches: [ "main" ]
tags: [ "v*" ]
jobs:
build:
runs-on: ubuntu-fast
strategy:
matrix:
include:
- goos: linux
goarch: amd64
ext: ""
- goos: linux
goarch: arm64
ext: ""
- goos: linux
goarch: arm
goarm: "7"
ext: ""
- goos: windows
goarch: amd64
ext: ".exe"
env:
GO_VERSION: "1.24"
BINARY_NAME: trading-server
steps:
- name: Checkout source
uses: actions/checkout@v3
- name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v4
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- name: Build ${{ matrix.goos }}/${{ matrix.goarch }}${{ matrix.goarm && format('/v{0}', matrix.goarm) || '' }}
shell: bash
run: |
set -e
mkdir -p dist/package
if [ -n "${{ matrix.goarm }}" ]; then export GOARM=${{ matrix.goarm }}; fi
CGO_ENABLED=0 GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} go build -trimpath -ldflags "-s -w" \
-o "dist/package/${BINARY_NAME}${{ matrix.ext }}" .
# Assets: statisches Verzeichnis beilegen
cp -r static dist/package/
- name: Package archive with static assets
shell: bash
run: |
set -e
cd dist
if [ "${{ matrix.goos }}" == "windows" ]; then
ZIP_NAME="${BINARY_NAME}-windows-amd64.zip"
(cd package && zip -r "../$ZIP_NAME" .)
else
ARCH_SUFFIX="${{ matrix.goarch }}"
if [ "${{ matrix.goarch }}" == "arm" ]; then ARCH_SUFFIX="armv${{ matrix.goarm }}"; fi
TAR_NAME="${BINARY_NAME}-${{ matrix.goos }}-${ARCH_SUFFIX}.tar.gz"
tar -czf "$TAR_NAME" -C package .
fi
- name: Upload workflow artifact
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.goarm && format('v{0}', matrix.goarm) || '' }}
path: dist/*.tar.gz
if-no-files-found: ignore
- uses: actions/upload-artifact@v3
with:
name: windows-amd64
path: dist/*.zip
if-no-files-found: ignore
# Release Schritt für TagPushes
release:
if: startsWith(github.ref, 'refs/tags/')
needs: build
runs-on: ubuntu-fast
permissions:
contents: write
steps:
- name: Download artifacts
uses: actions/download-artifact@v3
with:
path: ./dist
- name: Create / Update release
uses: softprops/action-gh-release@v2
env:
GITHUB_TOKEN: ${{ secrets.GITEA_TOKEN || github.token }}
with:
name: "Release ${{ github.ref_name }}"
tag_name: ${{ github.ref_name }}
draft: false
prerelease: false
files: |
dist/**/${BINARY_NAME}-*.tar.gz
dist/**/${BINARY_NAME}-*.zip

BIN
data.db

Binary file not shown.

3
go.mod
View File

@@ -10,8 +10,9 @@ require (
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
golang.org/x/crypto v0.40.0
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/sys v0.34.0 // indirect
modernc.org/libc v1.65.10 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect

30
go.sum
View File

@@ -1,5 +1,7 @@
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
@@ -8,16 +10,40 @@ github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdh
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s=
modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
modernc.org/fileutil v1.3.3 h1:3qaU+7f7xxTUmvU1pJTZiDLAIoJVdUSSauJNHg9yXoA=
modernc.org/fileutil v1.3.3/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/libc v1.65.10 h1:ZwEk8+jhW7qBjHIT+wd0d9VjitRyQef9BnzlzGwMODc=
modernc.org/libc v1.65.10/go.mod h1:StFvYpx7i/mXtBAfVOjaU0PWZOvIRoZSgXhrwXzr8Po=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.38.0 h1:+4OrfPQ8pxHKuWG4md1JpR/EYAh3Md7TdejuuzE7EUI=
modernc.org/sqlite v1.38.0/go.mod h1:1Bj+yES4SVvBZ4cBOpVZ6QgesMCKpJZDq0nxYzOpmNE=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=

264
main.go
View File

@@ -1,7 +1,9 @@
package main
import (
"crypto/rand"
"database/sql"
"encoding/hex"
"encoding/json"
"fmt"
"html/template"
@@ -12,8 +14,10 @@ import (
"sort"
"strconv"
"strings"
"sync"
"time"
"golang.org/x/crypto/bcrypt"
_ "modernc.org/sqlite" // statt github.com/mattn/go-sqlite3
)
@@ -36,14 +40,15 @@ func Enabled(k string, def bool) bool {
}
var (
username = GetENV("KT_USERNAME", "root")
password = GetENV("KT_PASSWORD", "root")
membername = GetENV("KT_MEMBER", "demo")
productive = Enabled("KT_PRODUCTIVE", false)
hasimpressum = Enabled("KT_HASIMPRESSUM", false)
impressum = GetENV("KT_IMPRESSUM", "")
orte = []string{}
schiffe = []string{
username = GetENV("KT_USERNAME", "root")
password = GetENV("KT_PASSWORD", "root")
membername = GetENV("KT_MEMBER", "demo")
productive = Enabled("KT_PRODUCTIVE", false)
hasimpressum = Enabled("KT_HASIMPRESSUM", false)
impressum = GetENV("KT_IMPRESSUM", "")
hashedPassword = ""
orte = []string{}
schiffe = []string{
"", "100i", "125a", "135c", "Arrow", "Aurora CL", "Aurora ES", "Aurora LN", "Aurora LX", "Aurora MR",
"Avenger Stalker", "Avenger Titan", "Avenger Titan Renegade", "Avenger Warlock",
"Blade", "Buccaneer", "C1 Spirit", "C2 Hercules Starlifter", "M2 Hercules Starlifter", "A2 Hercules Starlifter", "C8 Pisces", "C8R Pisces Rescue",
@@ -189,7 +194,37 @@ func reverse(s string) string {
func isAuthenticated(r *http.Request) bool {
cookie, err := r.Cookie("session")
return err == nil && cookie.Value == "authenticated"
if err != nil {
return false
}
// Prüfen, ob der Token im sessionStore existiert
_, ok := sessionStore[cookie.Value]
return ok
}
var sessionStore = make(map[string]string) // token → username
var loginAttempts = make(map[string]int)
var loginLastAttempt = make(map[string]time.Time)
var loginBlockedUntil = make(map[string]time.Time)
var loginMutex sync.Mutex
func hashPassword(pw string) string {
hash, _ := bcrypt.GenerateFromPassword([]byte(pw), bcrypt.DefaultCost)
return string(hash)
}
func checkPasswordHash(pw, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(pw))
return err == nil
}
func generateSessionToken() string {
b := make([]byte, 32)
_, err := rand.Read(b)
if err != nil {
return "" // handle error besser im echten Code
}
return hex.EncodeToString(b)
}
func main() {
@@ -219,6 +254,8 @@ func main() {
}
}
hashedPassword = hashPassword(password)
var pois []POI
if err := json.Unmarshal(data, &pois); err != nil {
panic(err)
@@ -245,34 +282,86 @@ func main() {
}
http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
ip := strings.Split(r.RemoteAddr, ":")[0]
loginMutex.Lock()
blockUntil, blocked := loginBlockedUntil[ip]
if blocked && time.Now().Before(blockUntil) {
loginMutex.Unlock()
http.Error(w, "Zu viele Fehlversuche. Bitte versuch es später erneut.", http.StatusTooManyRequests)
return
}
loginMutex.Unlock()
if r.Method == http.MethodPost {
r.ParseForm()
user := r.FormValue("username")
pass := r.FormValue("password")
if user == username && pass == password {
if user == username && checkPasswordHash(pass, hashedPassword) {
token := generateSessionToken()
// Speichere Session
sessionStore[token] = user
// Cookie setzen
http.SetCookie(w, &http.Cookie{
Name: "session",
Value: "authenticated",
Path: "/",
Name: "session",
Value: token,
Path: "/",
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteLaxMode,
})
// Erfolgreich -> Versuche zurücksetzen
loginMutex.Lock()
delete(loginAttempts, ip)
delete(loginLastAttempt, ip)
delete(loginBlockedUntil, ip)
loginMutex.Unlock()
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
// Fehlversuch behandeln
loginMutex.Lock()
loginAttempts[ip]++
loginLastAttempt[ip] = time.Now()
if loginAttempts[ip] >= 5 {
loginBlockedUntil[ip] = time.Now().Add(10 * time.Minute)
}
loginMutex.Unlock()
http.Error(w, "Login fehlgeschlagen", http.StatusUnauthorized)
return
}
// GET: Login-Formular
w.Header().Set("Content-Type", "text/html")
w.Write([]byte(loginForm))
})
http.HandleFunc("/logout", func(w http.ResponseWriter, r *http.Request) {
http.SetCookie(w, &http.Cookie{
Name: "session",
Value: "",
Path: "/",
MaxAge: -1,
})
cookie, err := r.Cookie("session")
if err == nil {
token := cookie.Value
// Token aus dem serverseitigen Store löschen
delete(sessionStore, token)
// Cookie ungültig machen
http.SetCookie(w, &http.Cookie{
Name: "session",
Value: "",
Path: "/",
MaxAge: -1,
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteLaxMode,
})
}
http.Redirect(w, r, "/", http.StatusSeeOther)
})
@@ -285,6 +374,11 @@ func main() {
if id != "" {
db.Exec("DELETE FROM eintraege WHERE id = ?", id)
}
cacheMutex.Lock()
cache.LastComputed = time.Time{} // auf null zurücksetzen
cacheMutex.Unlock()
http.Redirect(w, r, "/", http.StatusSeeOther)
})
@@ -298,10 +392,16 @@ func main() {
// Auto-Increment-Zähler zurücksetzen
db.Exec("DELETE FROM sqlite_sequence WHERE name='eintraege'")
cacheMutex.Lock()
cache.LastComputed = time.Time{} // auf null zurücksetzen
cacheMutex.Unlock()
http.Redirect(w, r, "/", http.StatusSeeOther)
})
http.HandleFunc("/markaspaid", func(w http.ResponseWriter, r *http.Request) {
fmt.Println("Method", "/markaspaid", r.Method)
if !isAuthenticated(r) {
http.Error(w, "Nicht autorisiert", http.StatusUnauthorized)
return
@@ -311,10 +411,16 @@ func main() {
if id != "" {
db.Exec("UPDATE eintraege SET bezahlt = 1 WHERE id = ?", id)
}
cacheMutex.Lock()
cache.LastComputed = time.Time{} // auf null zurücksetzen
cacheMutex.Unlock()
http.Redirect(w, r, "/", http.StatusSeeOther)
})
http.HandleFunc("/unmarkaspaid", func(w http.ResponseWriter, r *http.Request) {
fmt.Println("Method", "/unmarkaspaid", r.Method)
if !isAuthenticated(r) {
http.Error(w, "Nicht autorisiert", http.StatusUnauthorized)
return
@@ -324,10 +430,16 @@ func main() {
if id != "" {
db.Exec("UPDATE eintraege SET bezahlt = 0 WHERE id = ?", id)
}
cacheMutex.Lock()
cache.LastComputed = time.Time{} // auf null zurücksetzen
cacheMutex.Unlock()
http.Redirect(w, r, "/", http.StatusSeeOther)
})
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Println("Method", "/", r.Method)
if r.Method == http.MethodPost {
if !isAuthenticated(r) {
http.Error(w, "Nicht autorisiert", http.StatusUnauthorized)
@@ -346,15 +458,47 @@ func main() {
wareStr := strings.Join(ware, ", ")
zeitaufwand, _ := strconv.ParseFloat(r.FormValue("zeitaufwand"), 64)
e := Entry{
Anfangsbestand: anfang,
Endbestand: ende,
Gesamtwert: diff,
Prozentwert: prozent,
Abgabe: abgabe,
Startort: startort,
Zielort: zielort,
Schiff: schiff,
Ware: wareStr,
Zeitaufwand: zeitaufwand,
}
go sendDiscordWebhook(e)
_, err := db.Exec(`INSERT INTO eintraege (anfangsbestand, endbestand, prozentwert, abgabe, created_at, startort, zielort, schiff, ware, zeitaufwand) VALUES (?, ?, ?, ?, datetime('now'), ?, ?, ?, ?, ?)`, anfang, ende, prozent, abgabe, startort, zielort, schiff, wareStr, zeitaufwand)
if err != nil {
http.Error(w, "Fehler beim Einfügen", http.StatusInternalServerError)
return
}
cacheMutex.Lock()
cache.LastComputed = time.Time{} // auf null zurücksetzen
cacheMutex.Unlock()
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
cacheMutex.RLock()
validCache := time.Since(cache.LastComputed) < 6*time.Hour
cachedData := cache.Data
cacheMutex.RUnlock()
fmt.Println("validCache:", validCache)
if validCache {
cachedData.LoggedIn = isAuthenticated(r)
tmpl.Execute(w, cachedData)
return
}
rows, err := db.Query(`SELECT id, anfangsbestand, endbestand, prozentwert, abgabe, bezahlt, created_at, startort, zielort, schiff, ware, zeitaufwand FROM eintraege`)
if err != nil {
http.Error(w, "Fehler beim Abrufen", http.StatusInternalServerError)
@@ -487,7 +631,28 @@ func main() {
abteilungen[i].WertOffen = (abteilungen[i].Anteil / 100) * offeneSumme
}
tmpl.Execute(w, struct {
computed := TemplateData{
Entries: eintraege,
Summe: summe,
OffeneSumme: offeneSumme,
Abteilungen: abteilungen,
Monatsstatistik: monatsStat,
Member: membername,
HasImpressum: hasimpressum,
Impressum: impressum,
Orte: orte,
Schiffe: schiffe,
Waren: waren,
}
cacheMutex.Lock()
cache.Data = computed
cache.LastComputed = time.Now()
cacheMutex.Unlock()
computed.LoggedIn = isAuthenticated(r)
tmpl.Execute(w, computed)
/*tmpl.Execute(w, struct {
Entries []Entry
Summe float64
OffeneSumme float64
@@ -513,7 +678,7 @@ func main() {
Orte: orte,
Schiffe: schiffe,
Waren: waren,
})
})*/
})
@@ -521,6 +686,63 @@ func main() {
http.ListenAndServe(":8080", nil)
}
type TemplateData struct {
Entries []Entry
Summe float64
OffeneSumme float64
Abteilungen []Abteilung
Monatsstatistik []Monatsstatistik
LoggedIn bool
Member string
HasImpressum bool
Impressum string
Orte []string
Schiffe []string
Waren []string
}
type CachedData struct {
Data TemplateData // das Struct, das du an tmpl.Execute übergibst
LastComputed time.Time
}
var cache CachedData
var cacheMutex sync.RWMutex
var discordWebhook = GetENV("DISCORD_WEBHOOK_URL", "")
func sendDiscordWebhook(entry Entry) {
if discordWebhook == "" {
return
}
message := fmt.Sprintf(
"📦 **Neuer Abgabe-Eintrag**\n"+
"**UEC:** %s → %s (%s UEC Profit)\n"+
"**Abgabe:** %s UEC (%s%%)\n"+
"**Route:** %s → %s mit %s\n"+
"**Ware:** %s\n"+
"**Dauer:** %.0f Minuten",
formatNumber(entry.Anfangsbestand),
formatNumber(entry.Endbestand),
formatNumber(entry.Gesamtwert),
formatNumber(entry.Abgabe),
formatNumber(entry.Prozentwert),
entry.Startort,
entry.Zielort,
entry.Schiff,
entry.Ware,
entry.Zeitaufwand,
)
payload := map[string]string{
"content": message,
}
jsonData, _ := json.Marshal(payload)
http.Post(discordWebhook, "application/json", strings.NewReader(string(jsonData)))
}
func createTable(db *sql.DB) {
_, err := db.Exec(`
CREATE TABLE IF NOT EXISTS eintraege (