Compare commits
7 Commits
190cded4e5
...
v1.0.1
Author | SHA1 | Date | |
---|---|---|---|
1ba4f865de | |||
acd9fda529 | |||
f63e04f7b0 | |||
ff2f37f718 | |||
134e601d57 | |||
842c541c13 | |||
41cee8af1d |
123
.gitea/workflows/release.yml
Normal file
123
.gitea/workflows/release.yml
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
# Git(tea) Actions workflow: Build and publish standalone binaries **plus** bundled `static/` assets
|
||||||
|
# ────────────────────────────────────────────────────────────────────
|
||||||
|
# ✧ Builds the Go‑based 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 Bootstrap‑Assets & 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 Tag‑Push (vX.Y.Z) – als Release‑Assets.
|
||||||
|
#
|
||||||
|
# Secrets/variables:
|
||||||
|
# GITEA_TOKEN – optional, falls default token keine Release‑Rechte 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 Tag‑Pushes
|
||||||
|
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
|
3
go.mod
3
go.mod
@@ -10,8 +10,9 @@ require (
|
|||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // 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/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/libc v1.65.10 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
modernc.org/memory v1.11.0 // indirect
|
modernc.org/memory v1.11.0 // indirect
|
||||||
|
30
go.sum
30
go.sum
@@ -1,5 +1,7 @@
|
|||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
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/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 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
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=
|
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/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 h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
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 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
||||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
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 h1:ZwEk8+jhW7qBjHIT+wd0d9VjitRyQef9BnzlzGwMODc=
|
||||||
modernc.org/libc v1.65.10/go.mod h1:StFvYpx7i/mXtBAfVOjaU0PWZOvIRoZSgXhrwXzr8Po=
|
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 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
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 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
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 h1:+4OrfPQ8pxHKuWG4md1JpR/EYAh3Md7TdejuuzE7EUI=
|
||||||
modernc.org/sqlite v1.38.0/go.mod h1:1Bj+yES4SVvBZ4cBOpVZ6QgesMCKpJZDq0nxYzOpmNE=
|
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
264
main.go
@@ -1,7 +1,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
@@ -12,8 +14,10 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
_ "modernc.org/sqlite" // statt github.com/mattn/go-sqlite3
|
_ "modernc.org/sqlite" // statt github.com/mattn/go-sqlite3
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -36,14 +40,15 @@ func Enabled(k string, def bool) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
username = GetENV("KT_USERNAME", "root")
|
username = GetENV("KT_USERNAME", "root")
|
||||||
password = GetENV("KT_PASSWORD", "root")
|
password = GetENV("KT_PASSWORD", "root")
|
||||||
membername = GetENV("KT_MEMBER", "demo")
|
membername = GetENV("KT_MEMBER", "demo")
|
||||||
productive = Enabled("KT_PRODUCTIVE", false)
|
productive = Enabled("KT_PRODUCTIVE", false)
|
||||||
hasimpressum = Enabled("KT_HASIMPRESSUM", false)
|
hasimpressum = Enabled("KT_HASIMPRESSUM", false)
|
||||||
impressum = GetENV("KT_IMPRESSUM", "")
|
impressum = GetENV("KT_IMPRESSUM", "")
|
||||||
orte = []string{}
|
hashedPassword = ""
|
||||||
schiffe = []string{
|
orte = []string{}
|
||||||
|
schiffe = []string{
|
||||||
"", "100i", "125a", "135c", "Arrow", "Aurora CL", "Aurora ES", "Aurora LN", "Aurora LX", "Aurora MR",
|
"", "100i", "125a", "135c", "Arrow", "Aurora CL", "Aurora ES", "Aurora LN", "Aurora LX", "Aurora MR",
|
||||||
"Avenger Stalker", "Avenger Titan", "Avenger Titan Renegade", "Avenger Warlock",
|
"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",
|
"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 {
|
func isAuthenticated(r *http.Request) bool {
|
||||||
cookie, err := r.Cookie("session")
|
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() {
|
func main() {
|
||||||
@@ -219,6 +254,8 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hashedPassword = hashPassword(password)
|
||||||
|
|
||||||
var pois []POI
|
var pois []POI
|
||||||
if err := json.Unmarshal(data, &pois); err != nil {
|
if err := json.Unmarshal(data, &pois); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -245,34 +282,86 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
|
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 {
|
if r.Method == http.MethodPost {
|
||||||
r.ParseForm()
|
r.ParseForm()
|
||||||
user := r.FormValue("username")
|
user := r.FormValue("username")
|
||||||
pass := r.FormValue("password")
|
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{
|
http.SetCookie(w, &http.Cookie{
|
||||||
Name: "session",
|
Name: "session",
|
||||||
Value: "authenticated",
|
Value: token,
|
||||||
Path: "/",
|
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)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
return
|
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)
|
http.Error(w, "Login fehlgeschlagen", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GET: Login-Formular
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
w.Write([]byte(loginForm))
|
w.Write([]byte(loginForm))
|
||||||
})
|
})
|
||||||
|
|
||||||
http.HandleFunc("/logout", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/logout", func(w http.ResponseWriter, r *http.Request) {
|
||||||
http.SetCookie(w, &http.Cookie{
|
cookie, err := r.Cookie("session")
|
||||||
Name: "session",
|
if err == nil {
|
||||||
Value: "",
|
token := cookie.Value
|
||||||
Path: "/",
|
// Token aus dem serverseitigen Store löschen
|
||||||
MaxAge: -1,
|
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)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -285,6 +374,11 @@ func main() {
|
|||||||
if id != "" {
|
if id != "" {
|
||||||
db.Exec("DELETE FROM eintraege WHERE id = ?", 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)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -298,10 +392,16 @@ func main() {
|
|||||||
|
|
||||||
// Auto-Increment-Zähler zurücksetzen
|
// Auto-Increment-Zähler zurücksetzen
|
||||||
db.Exec("DELETE FROM sqlite_sequence WHERE name='eintraege'")
|
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.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
})
|
})
|
||||||
|
|
||||||
http.HandleFunc("/markaspaid", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/markaspaid", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Println("Method", "/markaspaid", r.Method)
|
||||||
if !isAuthenticated(r) {
|
if !isAuthenticated(r) {
|
||||||
http.Error(w, "Nicht autorisiert", http.StatusUnauthorized)
|
http.Error(w, "Nicht autorisiert", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
@@ -311,10 +411,16 @@ func main() {
|
|||||||
if id != "" {
|
if id != "" {
|
||||||
db.Exec("UPDATE eintraege SET bezahlt = 1 WHERE id = ?", 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.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
})
|
})
|
||||||
|
|
||||||
http.HandleFunc("/unmarkaspaid", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/unmarkaspaid", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Println("Method", "/unmarkaspaid", r.Method)
|
||||||
if !isAuthenticated(r) {
|
if !isAuthenticated(r) {
|
||||||
http.Error(w, "Nicht autorisiert", http.StatusUnauthorized)
|
http.Error(w, "Nicht autorisiert", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
@@ -324,10 +430,16 @@ func main() {
|
|||||||
if id != "" {
|
if id != "" {
|
||||||
db.Exec("UPDATE eintraege SET bezahlt = 0 WHERE id = ?", 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.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
})
|
})
|
||||||
|
|
||||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Println("Method", "/", r.Method)
|
||||||
if r.Method == http.MethodPost {
|
if r.Method == http.MethodPost {
|
||||||
if !isAuthenticated(r) {
|
if !isAuthenticated(r) {
|
||||||
http.Error(w, "Nicht autorisiert", http.StatusUnauthorized)
|
http.Error(w, "Nicht autorisiert", http.StatusUnauthorized)
|
||||||
@@ -346,15 +458,47 @@ func main() {
|
|||||||
wareStr := strings.Join(ware, ", ")
|
wareStr := strings.Join(ware, ", ")
|
||||||
zeitaufwand, _ := strconv.ParseFloat(r.FormValue("zeitaufwand"), 64)
|
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)
|
_, 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 {
|
if err != nil {
|
||||||
http.Error(w, "Fehler beim Einfügen", http.StatusInternalServerError)
|
http.Error(w, "Fehler beim Einfügen", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cacheMutex.Lock()
|
||||||
|
cache.LastComputed = time.Time{} // auf null zurücksetzen
|
||||||
|
cacheMutex.Unlock()
|
||||||
|
|
||||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
return
|
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`)
|
rows, err := db.Query(`SELECT id, anfangsbestand, endbestand, prozentwert, abgabe, bezahlt, created_at, startort, zielort, schiff, ware, zeitaufwand FROM eintraege`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Fehler beim Abrufen", http.StatusInternalServerError)
|
http.Error(w, "Fehler beim Abrufen", http.StatusInternalServerError)
|
||||||
@@ -487,7 +631,28 @@ func main() {
|
|||||||
abteilungen[i].WertOffen = (abteilungen[i].Anteil / 100) * offeneSumme
|
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
|
Entries []Entry
|
||||||
Summe float64
|
Summe float64
|
||||||
OffeneSumme float64
|
OffeneSumme float64
|
||||||
@@ -513,7 +678,7 @@ func main() {
|
|||||||
Orte: orte,
|
Orte: orte,
|
||||||
Schiffe: schiffe,
|
Schiffe: schiffe,
|
||||||
Waren: waren,
|
Waren: waren,
|
||||||
})
|
})*/
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -521,6 +686,63 @@ func main() {
|
|||||||
http.ListenAndServe(":8080", nil)
|
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) {
|
func createTable(db *sql.DB) {
|
||||||
_, err := db.Exec(`
|
_, err := db.Exec(`
|
||||||
CREATE TABLE IF NOT EXISTS eintraege (
|
CREATE TABLE IF NOT EXISTS eintraege (
|
||||||
|
Reference in New Issue
Block a user