Compare commits
27 Commits
rowing
...
cfdb5b4d50
| Author | SHA1 | Date | |
|---|---|---|---|
| cfdb5b4d50 | |||
| 37580cdc42 | |||
| 711236b776 | |||
| 75454c2ed8 | |||
| 78c824c4c8 | |||
| ba3b933068 | |||
| 14c430bbad | |||
| 26c7422e34 | |||
| 470b1c79d8 | |||
| d849b606ec | |||
| 46a9da4c90 | |||
| 398a610cb2 | |||
| b506bae515 | |||
| 11ad0b5a83 | |||
| d7393e1419 | |||
| 0d32333c0c | |||
| 050a38a76f | |||
| bc43e9ed02 | |||
| 75b8b02825 | |||
| 5c69a1d0a7 | |||
| aa915e1071 | |||
| 7dc3f49273 | |||
| 21d3997a16 | |||
| c56ba217dd | |||
| 91804f1fe7 | |||
| 7e74ce5a2a | |||
| e92ac49140 |
19
.gitea/workflows/deploy.yaml
Normal file
19
.gitea/workflows/deploy.yaml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
name: Deploy with Docker Compose
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: self-hosted
|
||||||
|
steps:
|
||||||
|
- name: Pull changes
|
||||||
|
working-directory: /home/adamf/deploy/web_server
|
||||||
|
run: git pull gitea main
|
||||||
|
|
||||||
|
- name: Run docker compose up
|
||||||
|
working-directory: /home/adamf/deploy/web_server
|
||||||
|
env:
|
||||||
|
DOCKER_API_VERSION: "1.41"
|
||||||
|
run: docker compose up -d --build --remove-orphans
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -3,6 +3,9 @@ certbot/www
|
|||||||
backend/token/
|
backend/token/
|
||||||
.env
|
.env
|
||||||
|
|
||||||
|
gitea/data/*
|
||||||
|
gitea-runner/data/*
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
|
|||||||
@@ -7,13 +7,12 @@ require (
|
|||||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||||
github.com/zmb3/spotify/v2 v2.4.3
|
github.com/zmb3/spotify/v2 v2.4.3
|
||||||
golang.org/x/crypto v0.43.0
|
golang.org/x/crypto v0.43.0
|
||||||
golang.org/x/oauth2 v0.30.0
|
golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5
|
||||||
gorm.io/driver/postgres v1.6.0
|
gorm.io/driver/postgres v1.6.0
|
||||||
gorm.io/gorm v1.31.1
|
gorm.io/gorm v1.31.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/anthropics/anthropic-sdk-go v1.26.0 // indirect
|
|
||||||
github.com/bytedance/sonic v1.14.0 // indirect
|
github.com/bytedance/sonic v1.14.0 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||||
@@ -24,7 +23,7 @@ require (
|
|||||||
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.2 // indirect
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
github.com/goccy/go-yaml v1.18.0 // indirect
|
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||||
github.com/golang/protobuf v1.5.4 // indirect
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
github.com/gorilla/websocket v1.5.3 // indirect
|
github.com/gorilla/websocket v1.5.3 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
@@ -41,11 +40,6 @@ require (
|
|||||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
github.com/quic-go/qpack v0.5.1 // indirect
|
github.com/quic-go/qpack v0.5.1 // indirect
|
||||||
github.com/quic-go/quic-go v0.54.0 // indirect
|
github.com/quic-go/quic-go v0.54.0 // indirect
|
||||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd // indirect
|
|
||||||
github.com/tidwall/gjson v1.18.0 // indirect
|
|
||||||
github.com/tidwall/match v1.1.1 // indirect
|
|
||||||
github.com/tidwall/pretty v1.2.1 // indirect
|
|
||||||
github.com/tidwall/sjson v1.2.5 // indirect
|
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.3.0 // indirect
|
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||||
go.uber.org/mock v0.5.0 // indirect
|
go.uber.org/mock v0.5.0 // indirect
|
||||||
|
|||||||
@@ -33,8 +33,6 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
|
|||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/anthropics/anthropic-sdk-go v1.26.0 h1:oUTzFaUpAevfuELAP1sjL6CQJ9HHAfT7CoSYSac11PY=
|
|
||||||
github.com/anthropics/anthropic-sdk-go v1.26.0/go.mod h1:qUKmaW+uuPB64iy1l+4kOSvaLqPXnHTTBKH6RVZ7q5Q=
|
|
||||||
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
||||||
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
|
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
|
||||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||||
@@ -104,8 +102,6 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
|
|||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
@@ -176,8 +172,6 @@ github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1
|
|||||||
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
|
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
|
||||||
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc=
|
|
||||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
@@ -189,16 +183,6 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
|||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
|
||||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
|
||||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
|
||||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
|
||||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
|
||||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
|
||||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
|
||||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
|
||||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
|
||||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||||
@@ -303,8 +287,6 @@ golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4Iltr
|
|||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5 h1:Ati8dO7+U7mxpkPSxBZQEvzHVUYB/MqCklCN8ig5w/o=
|
golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5 h1:Ati8dO7+U7mxpkPSxBZQEvzHVUYB/MqCklCN8ig5w/o=
|
||||||
golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
|
||||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
|||||||
@@ -1,154 +0,0 @@
|
|||||||
package handlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/rwcarlsen/goexif/exif"
|
|
||||||
|
|
||||||
"adam-french.co.uk/backend/models"
|
|
||||||
"github.com/anthropics/anthropic-sdk-go"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ExtractedRowingData struct {
|
|
||||||
TimeMinutes float64 `json:"timeMinutes"`
|
|
||||||
TimeSeconds float64 `json:"timeSeconds"`
|
|
||||||
Distance float64 `json:"distance"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (store *Store) GetRowing(ctx *gin.Context) {
|
|
||||||
var rowing []models.Rowing
|
|
||||||
if err := store.DB.Order("Created_At DESC").Find(&rowing).Error; err != nil {
|
|
||||||
ctx.JSON(http.StatusInternalServerError, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx.JSON(http.StatusOK, rowing)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (store *Store) CreateRowing(ctx *gin.Context) {
|
|
||||||
file, err := ctx.FormFile("image")
|
|
||||||
if err != nil {
|
|
||||||
ctx.JSON(http.StatusBadRequest, gin.H{"error": "image is required"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := file.Open()
|
|
||||||
if err != nil {
|
|
||||||
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "failed to open image"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
// Get the date taken from the EXIF data
|
|
||||||
x, err := exif.Decode(f)
|
|
||||||
if err != nil {
|
|
||||||
ctx.JSON(http.StatusBadRequest, gin.H{"error": "no EXIF data found"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
dateTaken, err := x.DateTime()
|
|
||||||
if err != nil {
|
|
||||||
ctx.JSON(http.StatusBadRequest, gin.H{"error": "no date found in EXIF data"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Seek back to start since exif.Decode advanced the cursor
|
|
||||||
if _, err := f.Seek(0, io.SeekStart); err != nil {
|
|
||||||
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "failed to seek image"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := io.ReadAll(f)
|
|
||||||
if err != nil {
|
|
||||||
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "failed to read image"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
allowedMediaTypes := map[string]bool{
|
|
||||||
"image/jpeg": true,
|
|
||||||
"image/png": true,
|
|
||||||
"image/gif": true,
|
|
||||||
"image/webp": true,
|
|
||||||
}
|
|
||||||
mediaType := file.Header.Get("Content-Type")
|
|
||||||
if !allowedMediaTypes[mediaType] {
|
|
||||||
ctx.JSON(http.StatusBadRequest, gin.H{"error": "unsupported image type"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
encoded := base64.StdEncoding.EncodeToString(data)
|
|
||||||
|
|
||||||
// Build the message with an image + text prompt
|
|
||||||
message, err := store.ClaudeClient.Messages.New(context.Background(), anthropic.MessageNewParams{
|
|
||||||
Model: anthropic.ModelClaudeHaiku4_5,
|
|
||||||
MaxTokens: 256,
|
|
||||||
Messages: []anthropic.MessageParam{
|
|
||||||
{
|
|
||||||
Role: "user",
|
|
||||||
Content: []anthropic.ContentBlockParamUnion{
|
|
||||||
// Image block
|
|
||||||
anthropic.NewImageBlock(anthropic.Base64ImageSourceParam{
|
|
||||||
Type: "base64",
|
|
||||||
MediaType: anthropic.Base64ImageSourceMediaType(mediaType),
|
|
||||||
Data: encoded,
|
|
||||||
}),
|
|
||||||
// Text prompt requesting exactly 2 variables
|
|
||||||
anthropic.NewTextBlock(
|
|
||||||
`Look at this rowing machine display. Extract the total elapsed time and total distance.
|
|
||||||
|
|
||||||
Return ONLY a JSON object with these exact keys and numeric values:
|
|
||||||
- "timeMinutes": total minutes (e.g. 2:30 = 2)
|
|
||||||
- "timeSeconds": total seconds (e.g. 2:30 = 30)
|
|
||||||
- "distance": distance in meters as a number (e.g. 5000)
|
|
||||||
|
|
||||||
No text, no markdown, no explanation. Just the JSON object.`),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "failed to analyze image"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(message.Content) == 0 {
|
|
||||||
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "empty response from Claude"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
extractedData := ExtractedRowingData{}
|
|
||||||
err = json.Unmarshal([]byte(message.Content[0].Text), &extractedData)
|
|
||||||
if err != nil {
|
|
||||||
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "failed to parse JSON response"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if extractedData.Distance == 0 {
|
|
||||||
ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid distance in image"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
totalSeconds := extractedData.TimeMinutes*60 + extractedData.TimeSeconds
|
|
||||||
totalDuration := time.Duration(totalSeconds * float64(time.Second))
|
|
||||||
per500m := time.Duration(totalSeconds / extractedData.Distance * 500 * float64(time.Second))
|
|
||||||
|
|
||||||
rowing := models.Rowing{
|
|
||||||
Date: dateTaken,
|
|
||||||
Time: totalDuration,
|
|
||||||
TimePer500m: per500m,
|
|
||||||
Distance: extractedData.Distance,
|
|
||||||
Calories: extractedData.Distance / 7500.0 * 500.0,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := store.DB.Create(&rowing).Error; err != nil {
|
|
||||||
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save rowing"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.JSON(http.StatusCreated, rowing)
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"adam-french.co.uk/backend/services"
|
"adam-french.co.uk/backend/services"
|
||||||
"github.com/anthropics/anthropic-sdk-go"
|
|
||||||
"github.com/zmb3/spotify/v2"
|
"github.com/zmb3/spotify/v2"
|
||||||
spotifyauth "github.com/zmb3/spotify/v2/auth"
|
spotifyauth "github.com/zmb3/spotify/v2/auth"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@@ -14,7 +13,6 @@ type Store struct {
|
|||||||
DB *gorm.DB
|
DB *gorm.DB
|
||||||
SpotifyAuth *spotifyauth.Authenticator
|
SpotifyAuth *spotifyauth.Authenticator
|
||||||
SpotifyClient *spotify.Client
|
SpotifyClient *spotify.Client
|
||||||
ClaudeClient *anthropic.Client
|
|
||||||
Auth *services.Auth
|
Auth *services.Auth
|
||||||
Notes *services.Notes
|
Notes *services.Notes
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
logsDir := "/backend/logs"
|
logsDir := "/backend/logs"
|
||||||
logFile, err := os.OpenFile(logsDir+"/go.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
logFile, err := os.OpenFile(logsDir+"/go.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -40,18 +39,12 @@ func main() {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SPOTIFY
|
|
||||||
spotifyAuthState := os.Getenv("SPOTIFY_AUTH_STATE")
|
spotifyAuthState := os.Getenv("SPOTIFY_AUTH_STATE")
|
||||||
spotifyRedirectURL := os.Getenv("SPOTIFY_REDIRECT_URI")
|
spotifyRedirectURL := os.Getenv("SPOTIFY_REDIRECT_URI")
|
||||||
spotifyClientID := os.Getenv("SPOTIFY_CLIENT_ID")
|
spotifyClientID := os.Getenv("SPOTIFY_CLIENT_ID")
|
||||||
spotifyClientSecret := os.Getenv("SPOTIFY_CLIENT_SECRET")
|
spotifyClientSecret := os.Getenv("SPOTIFY_CLIENT_SECRET")
|
||||||
spotifyConfig := services.SpotifyConfig{AuthState: spotifyAuthState, RedirectURL: spotifyRedirectURL, ClientID: spotifyClientID, ClientSecret: spotifyClientSecret}
|
spotifyConfig := services.SpotifyConfig{AuthState: spotifyAuthState, RedirectURL: spotifyRedirectURL, ClientID: spotifyClientID, ClientSecret: spotifyClientSecret}
|
||||||
spotifyAuth, spotifyClient := services.InitSpotifyAuth(&spotifyConfig)
|
spotifyAuth, client := services.InitSpotifyAuth(&spotifyConfig)
|
||||||
|
|
||||||
// CLAUDE
|
|
||||||
claudeAPIKey := os.Getenv("CLAUDE_API_KEY")
|
|
||||||
claudeConfig := services.ClaudeConfig{APIKey: claudeAPIKey}
|
|
||||||
claudeClient := services.InitClaude(&claudeConfig)
|
|
||||||
|
|
||||||
authSecret := os.Getenv("BACKEND_SECRET")
|
authSecret := os.Getenv("BACKEND_SECRET")
|
||||||
domainName := os.Getenv("DOMAIN")
|
domainName := os.Getenv("DOMAIN")
|
||||||
@@ -65,7 +58,7 @@ func main() {
|
|||||||
notesConfig := services.NotesConfig{Dir: notesDir}
|
notesConfig := services.NotesConfig{Dir: notesDir}
|
||||||
notes := services.InitNotes(¬esConfig)
|
notes := services.InitNotes(¬esConfig)
|
||||||
|
|
||||||
store := handlers.Store{DB: db, SpotifyAuth: spotifyAuth, SpotifyClient: spotifyClient, ClaudeClient: claudeClient, Auth: auth, Notes: notes}
|
store := handlers.Store{DB: db, SpotifyAuth: spotifyAuth, SpotifyClient: client, Auth: auth, Notes: notes}
|
||||||
|
|
||||||
protected := r.Group("/", store.AuthMiddlewear)
|
protected := r.Group("/", store.AuthMiddlewear)
|
||||||
|
|
||||||
@@ -73,10 +66,6 @@ func main() {
|
|||||||
r.GET("/favorites", store.GetFavorites)
|
r.GET("/favorites", store.GetFavorites)
|
||||||
protected.POST("/favorites", store.CreateFavorite)
|
protected.POST("/favorites", store.CreateFavorite)
|
||||||
|
|
||||||
// ROWING
|
|
||||||
r.GET("/rowing", store.GetRowing)
|
|
||||||
protected.POST("/rowing", store.CreateRowing)
|
|
||||||
|
|
||||||
// ACTIVITIES
|
// ACTIVITIES
|
||||||
r.GET("/activity", store.GetActivity)
|
r.GET("/activity", store.GetActivity)
|
||||||
protected.POST("/activity", store.CreateActivity)
|
protected.POST("/activity", store.CreateActivity)
|
||||||
|
|||||||
@@ -55,14 +55,3 @@ type Favorite struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Link *string `json:"link"`
|
Link *string `json:"link"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Rowing struct {
|
|
||||||
ID uint `gorm:"primarykey" json:"id"`
|
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"deletedAt"`
|
|
||||||
Date time.Time `json:"date"`
|
|
||||||
Time time.Duration `json:"time"`
|
|
||||||
TimePer500m time.Duration `json:"timePer500m"`
|
|
||||||
Distance float64 `json:"distance"`
|
|
||||||
Calories float64 `json:"calories"`
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
package services
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/anthropics/anthropic-sdk-go"
|
|
||||||
"github.com/anthropics/anthropic-sdk-go/option"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ClaudeConfig struct {
|
|
||||||
APIKey string
|
|
||||||
}
|
|
||||||
|
|
||||||
func InitClaude(config *ClaudeConfig) *anthropic.Client {
|
|
||||||
client := anthropic.NewClient(option.WithAPIKey(config.APIKey))
|
|
||||||
return &client
|
|
||||||
}
|
|
||||||
@@ -36,7 +36,6 @@ func migrateDatabase(db *gorm.DB) error {
|
|||||||
&models.Post{},
|
&models.Post{},
|
||||||
&models.Activity{},
|
&models.Activity{},
|
||||||
&models.Favorite{},
|
&models.Favorite{},
|
||||||
&models.Rowing{},
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -12,10 +12,11 @@ services:
|
|||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
container_name: nginx
|
container_name: nginx
|
||||||
env_file: ./.env
|
env_file: ./.env
|
||||||
restart: unless-stopped
|
restart: always
|
||||||
depends_on:
|
depends_on:
|
||||||
- backend
|
- backend
|
||||||
- icecast2
|
- icecast2
|
||||||
|
- gitea
|
||||||
networks:
|
networks:
|
||||||
- app-network
|
- app-network
|
||||||
ports:
|
ports:
|
||||||
@@ -33,6 +34,8 @@ services:
|
|||||||
- ./certbot/conf:/etc/letsencrypt
|
- ./certbot/conf:/etc/letsencrypt
|
||||||
- ./certbot/www:/var/www/certbot
|
- ./certbot/www:/var/www/certbot
|
||||||
entrypoint: ["/entrypoint.sh"]
|
entrypoint: ["/entrypoint.sh"]
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
networks:
|
networks:
|
||||||
- app-network
|
- app-network
|
||||||
|
|
||||||
@@ -41,7 +44,7 @@ services:
|
|||||||
context: ./backend
|
context: ./backend
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
container_name: "${BACKEND_HOST}"
|
container_name: "${BACKEND_HOST}"
|
||||||
restart: unless-stopped
|
restart: always
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
networks:
|
networks:
|
||||||
@@ -56,13 +59,15 @@ services:
|
|||||||
db:
|
db:
|
||||||
image: postgres:16
|
image: postgres:16
|
||||||
container_name: "${POSTGRES_HOST}"
|
container_name: "${POSTGRES_HOST}"
|
||||||
restart: unless-stopped
|
restart: always
|
||||||
env_file:
|
env_file:
|
||||||
- ./.env
|
- ./.env
|
||||||
networks:
|
networks:
|
||||||
- app-network
|
- app-network
|
||||||
volumes:
|
volumes:
|
||||||
- dbdata:/var/lib/postgresql/data
|
- dbdata:/var/lib/postgresql/data
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
|
||||||
icecast2:
|
icecast2:
|
||||||
build:
|
build:
|
||||||
@@ -76,3 +81,43 @@ services:
|
|||||||
- ./.env
|
- ./.env
|
||||||
ports:
|
ports:
|
||||||
- "${ICECAST_PORT}:${ICECAST_PORT}"
|
- "${ICECAST_PORT}:${ICECAST_PORT}"
|
||||||
|
|
||||||
|
gitea-runner:
|
||||||
|
image: gitea/act_runner:latest
|
||||||
|
container_name: "${GITEA_RUNNER_HOST}"
|
||||||
|
environment:
|
||||||
|
GITEA_RUNNER_NAME: ${GITEA_RUNNER_NAME}
|
||||||
|
CONFIG_FILE: /config.yaml
|
||||||
|
GITEA_RUNNER_REGISTRATION_TOKEN: ${GITEA_RUNNER_REGISTRATION_TOKEN}
|
||||||
|
GITEA_INSTANCE_URL: "http://${GITEA_HOST}:3000"
|
||||||
|
GITEA_RUNNER_LABELS: "self-hosted:host"
|
||||||
|
volumes:
|
||||||
|
- ./gitea-runner/config.yaml:/config.yaml
|
||||||
|
- ./gitea-runner/data:/data
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
|
||||||
|
gitea:
|
||||||
|
image: docker.gitea.com/gitea:1.25.4-rootless
|
||||||
|
container_name: "${GITEA_HOST}"
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
environment:
|
||||||
|
- GITEA__database__DB_TYPE=postgres
|
||||||
|
- GITEA__database__HOST=${POSTGRES_HOST}
|
||||||
|
- GITEA__database__NAME=${POSTGRES_GITEA_DB}
|
||||||
|
- GITEA__database__USER=${POSTGRES_USER}
|
||||||
|
- GITEA__database__PASSWD=${POSTGRES_PASSWORD}
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- ./gitea/data:/var/lib/gitea
|
||||||
|
- ./gitea/config:/etc/gitea
|
||||||
|
- /etc/timezone:/etc/timezone:ro
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
- "2222:2222"
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
|||||||
110
gitea-runner/config.yaml
Normal file
110
gitea-runner/config.yaml
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
# Example configuration file, it's safe to copy this as the default config file without any modification.
|
||||||
|
|
||||||
|
# You don't have to copy this file to your instance,
|
||||||
|
# just run `./act_runner generate-config > config.yaml` to generate a config file.
|
||||||
|
|
||||||
|
log:
|
||||||
|
# The level of logging, can be trace, debug, info, warn, error, fatal
|
||||||
|
level: info
|
||||||
|
|
||||||
|
runner:
|
||||||
|
# Where to store the registration result.
|
||||||
|
file: .runner
|
||||||
|
# Execute how many tasks concurrently at the same time.
|
||||||
|
capacity: 1
|
||||||
|
# Extra environment variables to run jobs.
|
||||||
|
envs:
|
||||||
|
A_TEST_ENV_NAME_1: a_test_env_value_1
|
||||||
|
A_TEST_ENV_NAME_2: a_test_env_value_2
|
||||||
|
# Extra environment variables to run jobs from a file.
|
||||||
|
# It will be ignored if it's empty or the file doesn't exist.
|
||||||
|
env_file: .env
|
||||||
|
# The timeout for a job to be finished.
|
||||||
|
# Please note that the Gitea instance also has a timeout (3h by default) for the job.
|
||||||
|
# So the job could be stopped by the Gitea instance if it's timeout is shorter than this.
|
||||||
|
timeout: 3h
|
||||||
|
# The timeout for the runner to wait for running jobs to finish when shutting down.
|
||||||
|
# Any running jobs that haven't finished after this timeout will be cancelled.
|
||||||
|
shutdown_timeout: 0s
|
||||||
|
# Whether skip verifying the TLS certificate of the Gitea instance.
|
||||||
|
insecure: false
|
||||||
|
# The timeout for fetching the job from the Gitea instance.
|
||||||
|
fetch_timeout: 5s
|
||||||
|
# The interval for fetching the job from the Gitea instance.
|
||||||
|
fetch_interval: 2s
|
||||||
|
# The github_mirror of a runner is used to specify the mirror address of the github that pulls the action repository.
|
||||||
|
# It works when something like `uses: actions/checkout@v4` is used and DEFAULT_ACTIONS_URL is set to github,
|
||||||
|
# and github_mirror is not empty. In this case,
|
||||||
|
# it replaces https://github.com with the value here, which is useful for some special network environments.
|
||||||
|
github_mirror: ''
|
||||||
|
# The labels of a runner are used to determine which jobs the runner can run, and how to run them.
|
||||||
|
# Like: "macos-arm64:host" or "ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest"
|
||||||
|
# Find more images provided by Gitea at https://gitea.com/docker.gitea.com/runner-images .
|
||||||
|
# If it's empty when registering, it will ask for inputting labels.
|
||||||
|
# If it's empty when execute `daemon`, will use labels in `.runner` file.
|
||||||
|
labels:
|
||||||
|
- "ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest"
|
||||||
|
- "ubuntu-22.04:docker://docker.gitea.com/runner-images:ubuntu-22.04"
|
||||||
|
- "ubuntu-20.04:docker://docker.gitea.com/runner-images:ubuntu-20.04"
|
||||||
|
|
||||||
|
cache:
|
||||||
|
# Enable cache server to use actions/cache.
|
||||||
|
enabled: true
|
||||||
|
# The directory to store the cache data.
|
||||||
|
# If it's empty, the cache data will be stored in $HOME/.cache/actcache.
|
||||||
|
dir: ""
|
||||||
|
# The host of the cache server.
|
||||||
|
# It's not for the address to listen, but the address to connect from job containers.
|
||||||
|
# So 0.0.0.0 is a bad choice, leave it empty to detect automatically.
|
||||||
|
host: ""
|
||||||
|
# The port of the cache server.
|
||||||
|
# 0 means to use a random available port.
|
||||||
|
port: 0
|
||||||
|
# The external cache server URL. Valid only when enable is true.
|
||||||
|
# If it's specified, act_runner will use this URL as the ACTIONS_CACHE_URL rather than start a server by itself.
|
||||||
|
# The URL should generally end with "/".
|
||||||
|
external_server: ""
|
||||||
|
|
||||||
|
container:
|
||||||
|
# Specifies the network to which the container will connect.
|
||||||
|
# Could be host, bridge or the name of a custom network.
|
||||||
|
# If it's empty, act_runner will create a network automatically.
|
||||||
|
network: ""
|
||||||
|
# Whether to use privileged mode or not when launching task containers (privileged mode is required for Docker-in-Docker).
|
||||||
|
privileged: false
|
||||||
|
# And other options to be used when the container is started (eg, --add-host=my.gitea.url:host-gateway).
|
||||||
|
options:
|
||||||
|
# The parent directory of a job's working directory.
|
||||||
|
# NOTE: There is no need to add the first '/' of the path as act_runner will add it automatically.
|
||||||
|
# If the path starts with '/', the '/' will be trimmed.
|
||||||
|
# For example, if the parent directory is /path/to/my/dir, workdir_parent should be path/to/my/dir
|
||||||
|
# If it's empty, /workspace will be used.
|
||||||
|
workdir_parent:
|
||||||
|
# Volumes (including bind mounts) can be mounted to containers. Glob syntax is supported, see https://github.com/gobwas/glob
|
||||||
|
# You can specify multiple volumes. If the sequence is empty, no volumes can be mounted.
|
||||||
|
# For example, if you only allow containers to mount the `data` volume and all the json files in `/src`, you should change the config to:
|
||||||
|
# valid_volumes:
|
||||||
|
# - data
|
||||||
|
# - /src/*.json
|
||||||
|
# If you want to allow any volume, please use the following configuration:
|
||||||
|
# valid_volumes:
|
||||||
|
# - '**'
|
||||||
|
valid_volumes: []
|
||||||
|
# overrides the docker client host with the specified one.
|
||||||
|
# If it's empty, act_runner will find an available docker host automatically.
|
||||||
|
# If it's "-", act_runner will find an available docker host automatically, but the docker host won't be mounted to the job containers and service containers.
|
||||||
|
# If it's not empty or "-", the specified docker host will be used. An error will be returned if it doesn't work.
|
||||||
|
docker_host: ""
|
||||||
|
# Pull docker image(s) even if already present
|
||||||
|
force_pull: true
|
||||||
|
# Rebuild docker image(s) even if already present
|
||||||
|
force_rebuild: false
|
||||||
|
# Always require a reachable docker daemon, even if not required by act_runner
|
||||||
|
require_docker: false
|
||||||
|
# Timeout to wait for the docker daemon to be reachable, if docker is required by require_docker or act_runner
|
||||||
|
docker_timeout: 0s
|
||||||
|
|
||||||
|
host:
|
||||||
|
# The parent directory of a job's working directory.
|
||||||
|
# If it's empty, $HOME/.cache/act/ will be used.
|
||||||
|
workdir_parent:
|
||||||
98
gitea/config/app.ini
Normal file
98
gitea/config/app.ini
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
APP_NAME = Gitea: Git with a cup of tea
|
||||||
|
RUN_USER = git
|
||||||
|
RUN_MODE = prod
|
||||||
|
WORK_PATH = /var/lib/gitea
|
||||||
|
|
||||||
|
[repository]
|
||||||
|
ROOT = /var/lib/gitea/git/repositories
|
||||||
|
|
||||||
|
[repository.local]
|
||||||
|
LOCAL_COPY_PATH = /tmp/gitea/local-repo
|
||||||
|
|
||||||
|
[repository.upload]
|
||||||
|
TEMP_PATH = /tmp/gitea/uploads
|
||||||
|
|
||||||
|
[server]
|
||||||
|
APP_DATA_PATH = /var/lib/gitea
|
||||||
|
SSH_DOMAIN = adam-french.co.uk
|
||||||
|
HTTP_PORT = 3000
|
||||||
|
ROOT_URL = https://adam-french.co.uk/gitea/
|
||||||
|
DISABLE_SSH = false
|
||||||
|
; In rootless gitea container only internal ssh server is supported
|
||||||
|
START_SSH_SERVER = true
|
||||||
|
SSH_PORT = 2222
|
||||||
|
SSH_LISTEN_PORT = 2222
|
||||||
|
BUILTIN_SSH_SERVER_USER = git
|
||||||
|
LFS_START_SERVER = true
|
||||||
|
DOMAIN = stppi.local
|
||||||
|
LFS_JWT_SECRET = XHIJprS_aMv0tizioZpUD38GGqTtNMFXMz1R6LuPvjU
|
||||||
|
OFFLINE_MODE = true
|
||||||
|
|
||||||
|
[database]
|
||||||
|
PATH = /var/lib/gitea/data/gitea.db
|
||||||
|
DB_TYPE = postgres
|
||||||
|
HOST = db
|
||||||
|
NAME = gitea
|
||||||
|
USER = postgres
|
||||||
|
PASSWD = password
|
||||||
|
SCHEMA =
|
||||||
|
SSL_MODE = disable
|
||||||
|
LOG_SQL = false
|
||||||
|
|
||||||
|
[session]
|
||||||
|
PROVIDER_CONFIG = /var/lib/gitea/data/sessions
|
||||||
|
PROVIDER = file
|
||||||
|
|
||||||
|
[picture]
|
||||||
|
AVATAR_UPLOAD_PATH = /var/lib/gitea/data/avatars
|
||||||
|
REPOSITORY_AVATAR_UPLOAD_PATH = /var/lib/gitea/data/repo-avatars
|
||||||
|
|
||||||
|
[attachment]
|
||||||
|
PATH = /var/lib/gitea/data/attachments
|
||||||
|
|
||||||
|
[log]
|
||||||
|
ROOT_PATH = /var/lib/gitea/data/log
|
||||||
|
MODE = console
|
||||||
|
LEVEL = info
|
||||||
|
|
||||||
|
[security]
|
||||||
|
INSTALL_LOCK = true
|
||||||
|
SECRET_KEY =
|
||||||
|
REVERSE_PROXY_LIMIT = 1
|
||||||
|
REVERSE_PROXY_TRUSTED_PROXIES = *
|
||||||
|
INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE3NzEyNDMyMTd9.yHsgFcEwDNWmZebftpe8tpWRFa5aR5tkpQuVYybeVaY
|
||||||
|
PASSWORD_HASH_ALGO = pbkdf2
|
||||||
|
|
||||||
|
[service]
|
||||||
|
DISABLE_REGISTRATION = true
|
||||||
|
REQUIRE_SIGNIN_VIEW = false
|
||||||
|
REGISTER_EMAIL_CONFIRM = false
|
||||||
|
ENABLE_NOTIFY_MAIL = false
|
||||||
|
ALLOW_ONLY_EXTERNAL_REGISTRATION = false
|
||||||
|
ENABLE_CAPTCHA = false
|
||||||
|
DEFAULT_KEEP_EMAIL_PRIVATE = false
|
||||||
|
DEFAULT_ALLOW_CREATE_ORGANIZATION = true
|
||||||
|
DEFAULT_ENABLE_TIMETRACKING = true
|
||||||
|
NO_REPLY_ADDRESS = noreply.localhost
|
||||||
|
|
||||||
|
[lfs]
|
||||||
|
PATH = /var/lib/gitea/git/lfs
|
||||||
|
|
||||||
|
[mailer]
|
||||||
|
ENABLED = false
|
||||||
|
|
||||||
|
[openid]
|
||||||
|
ENABLE_OPENID_SIGNIN = true
|
||||||
|
ENABLE_OPENID_SIGNUP = true
|
||||||
|
|
||||||
|
[cron.update_checker]
|
||||||
|
ENABLED = false
|
||||||
|
|
||||||
|
[repository.pull-request]
|
||||||
|
DEFAULT_MERGE_STYLE = merge
|
||||||
|
|
||||||
|
[repository.signing]
|
||||||
|
DEFAULT_TRUST_MODEL = committer
|
||||||
|
|
||||||
|
[oauth2]
|
||||||
|
JWT_SECRET = pYiwW8xxGi23gysl2pa-02Cf567Z5ERvR6DDFGIn2iQ
|
||||||
@@ -4,7 +4,7 @@ set -e
|
|||||||
# Check if certificate exists
|
# Check if certificate exists
|
||||||
if [ -f "/etc/letsencrypt/live/$DOMAIN/fullchain.pem" ] && [ -f "/etc/letsencrypt/live/$DOMAIN/privkey.pem" ]; then
|
if [ -f "/etc/letsencrypt/live/$DOMAIN/fullchain.pem" ] && [ -f "/etc/letsencrypt/live/$DOMAIN/privkey.pem" ]; then
|
||||||
echo "Certificates found. Using production nginx config."
|
echo "Certificates found. Using production nginx config."
|
||||||
envsubst '${DOMAIN} ${BACKEND_HOST} ${BACKEND_PORT} ${BACKEND_ENDPOINT} ${ICECAST_HOST} ${ICECAST_PORT}' \
|
envsubst '${DOMAIN} ${BACKEND_HOST} ${BACKEND_PORT} ${BACKEND_ENDPOINT} ${ICECAST_HOST} ${ICECAST_PORT} ${GITEA_HOST} ${GITEA_PORT}' \
|
||||||
< /etc/nginx/nginx.conf.template \
|
< /etc/nginx/nginx.conf.template \
|
||||||
> /etc/nginx/nginx.conf
|
> /etc/nginx/nginx.conf
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -98,6 +98,18 @@ http {
|
|||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location /gitea {
|
||||||
|
return 301 /gitea/;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /gitea/ {
|
||||||
|
proxy_pass http://$GITEA_HOST:$GITEA_PORT/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
69
nginx/vue/package-lock.json
generated
69
nginx/vue/package-lock.json
generated
@@ -10,12 +10,14 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mdit/plugin-katex": "^0.24.1",
|
"@mdit/plugin-katex": "^0.24.1",
|
||||||
"@tailwindcss/vite": "^4.1.18",
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
|
"@vueuse/core": "^14.2.1",
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
"katex": "^0.16.27",
|
"katex": "^0.16.27",
|
||||||
"markdown-it": "^14.1.0",
|
"markdown-it": "^14.1.0",
|
||||||
"markdown-it-wikilinks": "^1.4.0",
|
"markdown-it-wikilinks": "^1.4.0",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"tailwindcss": "^4.1.18",
|
"tailwindcss": "^4.1.18",
|
||||||
|
"typescript": "^5.9.3",
|
||||||
"vue": "^3.5.22",
|
"vue": "^3.5.22",
|
||||||
"vue-router": "^4.6.3"
|
"vue-router": "^4.6.3"
|
||||||
},
|
},
|
||||||
@@ -1631,6 +1633,12 @@
|
|||||||
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
|
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/web-bluetooth": {
|
||||||
|
"version": "0.0.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
|
||||||
|
"integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@vitejs/plugin-vue": {
|
"node_modules/@vitejs/plugin-vue": {
|
||||||
"version": "6.0.3",
|
"version": "6.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.3.tgz",
|
||||||
@@ -1916,6 +1924,44 @@
|
|||||||
"integrity": "sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==",
|
"integrity": "sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@vueuse/core": {
|
||||||
|
"version": "14.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.2.1.tgz",
|
||||||
|
"integrity": "sha512-3vwDzV+GDUNpdegRY6kzpLm4Igptq+GA0QkJ3W61Iv27YWwW/ufSlOfgQIpN6FZRMG0mkaz4gglJRtq5SeJyIQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/web-bluetooth": "^0.0.21",
|
||||||
|
"@vueuse/metadata": "14.2.1",
|
||||||
|
"@vueuse/shared": "14.2.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vueuse/metadata": {
|
||||||
|
"version": "14.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.2.1.tgz",
|
||||||
|
"integrity": "sha512-1ButlVtj5Sb/HDtIy1HFr1VqCP4G6Ypqt5MAo0lCgjokrk2mvQKsK2uuy0vqu/Ks+sHfuHo0B9Y9jn9xKdjZsw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vueuse/shared": {
|
||||||
|
"version": "14.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.2.1.tgz",
|
||||||
|
"integrity": "sha512-shTJncjV9JTI4oVNyF1FQonetYAiTBd+Qj7cY89SWbXSkx7gyhrgtEdF2ZAVWS1S3SHlaROO6F2IesJxQEkZBw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ansis": {
|
"node_modules/ansis": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz",
|
||||||
@@ -1939,13 +1985,13 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "1.13.2",
|
"version": "1.13.5",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",
|
||||||
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
|
"integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.15.6",
|
"follow-redirects": "^1.15.11",
|
||||||
"form-data": "^4.0.4",
|
"form-data": "^4.0.5",
|
||||||
"proxy-from-env": "^1.1.0"
|
"proxy-from-env": "^1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3382,6 +3428,19 @@
|
|||||||
"utf8-byte-length": "^1.0.1"
|
"utf8-byte-length": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"version": "5.9.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/uc.micro": {
|
"node_modules/uc.micro": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
|
||||||
|
|||||||
@@ -14,12 +14,14 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mdit/plugin-katex": "^0.24.1",
|
"@mdit/plugin-katex": "^0.24.1",
|
||||||
"@tailwindcss/vite": "^4.1.18",
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
|
"@vueuse/core": "^14.2.1",
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
"katex": "^0.16.27",
|
"katex": "^0.16.27",
|
||||||
"markdown-it": "^14.1.0",
|
"markdown-it": "^14.1.0",
|
||||||
"markdown-it-wikilinks": "^1.4.0",
|
"markdown-it-wikilinks": "^1.4.0",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"tailwindcss": "^4.1.18",
|
"tailwindcss": "^4.1.18",
|
||||||
|
"typescript": "^5.9.3",
|
||||||
"vue": "^3.5.22",
|
"vue": "^3.5.22",
|
||||||
"vue-router": "^4.6.3"
|
"vue-router": "^4.6.3"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -279,8 +279,8 @@ td {
|
|||||||
background-position: 0 0;
|
background-position: 0 0;
|
||||||
|
|
||||||
mask-image: linear-gradient(
|
mask-image: linear-gradient(
|
||||||
30deg,
|
-180deg,
|
||||||
rgba(1, 1, 1, 1) 0%,
|
rgba(1, 1, 1, 1) 0%,
|
||||||
rgba(1, 1, 1, 0.9) 100%
|
rgba(1, 1, 1, 0.92) 100%
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,14 +49,10 @@ const faces_string = faces.join(" ");
|
|||||||
<RouterLink class="bdr-2 bg-bg_primary" to="/" v-if="!inHome">
|
<RouterLink class="bdr-2 bg-bg_primary" to="/" v-if="!inHome">
|
||||||
<a>HOME</a>
|
<a>HOME</a>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<RouterLink
|
<RouterLink class="bdr-2 bg-bg_primary" v-if="parentPath" :to="parentPath">
|
||||||
class="bdr-2 bg-bg_primary"
|
|
||||||
v-if="parentPath"
|
|
||||||
:to="parentPath"
|
|
||||||
>
|
|
||||||
<a>UP</a>
|
<a>UP</a>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<Headline class="border flex-1">
|
<Headline class="border flex-1 max-w-full">
|
||||||
<code class="whitespace-pre">{{ faces_string }}</code>
|
<code class="whitespace-pre">{{ faces_string }}</code>
|
||||||
</Headline>
|
</Headline>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, useTemplateRef, onUnmounted } from "vue";
|
import { onMounted, useTemplateRef, onUnmounted } from "vue";
|
||||||
|
|
||||||
const container = useTemplateRef("container");
|
const container = useTemplateRef("container");
|
||||||
const item1 = useTemplateRef("item1");
|
const item1 = useTemplateRef("item1");
|
||||||
const item2 = useTemplateRef("item2");
|
|
||||||
|
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
|
|
||||||
@@ -14,7 +13,6 @@ const speed = 0.5; // pixels per frame
|
|||||||
function animate() {
|
function animate() {
|
||||||
const ctnr = container.value;
|
const ctnr = container.value;
|
||||||
const it1 = item1.value;
|
const it1 = item1.value;
|
||||||
const it2 = item2.value;
|
|
||||||
|
|
||||||
const width = Math.max(ctnr.offsetWidth, it1.scrollWidth);
|
const width = Math.max(ctnr.offsetWidth, it1.scrollWidth);
|
||||||
|
|
||||||
@@ -24,8 +22,7 @@ function animate() {
|
|||||||
offset += width;
|
offset += width;
|
||||||
}
|
}
|
||||||
|
|
||||||
it1.style.transform = `translateX(${offset}px)`;
|
ctnr.style.transform = `translateX(${offset}px)`;
|
||||||
it2.style.transform = `translateX(${width + offset}px)`;
|
|
||||||
|
|
||||||
rafId = requestAnimationFrame(animate);
|
rafId = requestAnimationFrame(animate);
|
||||||
}
|
}
|
||||||
@@ -40,40 +37,32 @@ onUnmounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="marquee">
|
<div class="root">
|
||||||
<div class="container" ref="container">
|
<div class="container" ref="container">
|
||||||
<div class="item" ref="item1"><slot /></div>
|
<div ref="item1">
|
||||||
<div class="item item2" ref="item2"><slot /></div>
|
<slot />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.marquee {
|
.root {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
width: 100%;
|
|
||||||
height: fit-content;
|
|
||||||
position: relative;
|
|
||||||
will-change: transform;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item {
|
|
||||||
height: fit-content;
|
|
||||||
top: 0px;
|
|
||||||
padding-right: 3em;
|
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
white-space: nowrap;
|
height: fit-content;
|
||||||
}
|
display: grid;
|
||||||
|
grid-auto-flow: column;
|
||||||
.item1 {
|
grid-auto-columns: max-content;
|
||||||
left: 0px;
|
/* Each column fits its content */
|
||||||
}
|
overflow-x: visible;
|
||||||
|
will-change: transform;
|
||||||
.item2 {
|
gap: 10em;
|
||||||
position: absolute;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
42
nginx/vue/src/views/home/BadApple.vue
Normal file
42
nginx/vue/src/views/home/BadApple.vue
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useTemplateRef, ref, onMounted, onUnmounted } from 'vue';
|
||||||
|
|
||||||
|
const display = useTemplateRef('display')
|
||||||
|
const displayText = ref("");
|
||||||
|
|
||||||
|
const charHeight: number = 14;
|
||||||
|
const charWidth: number = charHeight * 0.6;
|
||||||
|
let n: number;
|
||||||
|
let m: number;
|
||||||
|
|
||||||
|
function setup() {
|
||||||
|
display.value.style.fontSize = `${charHeight}px`;
|
||||||
|
display.value.style.lineHeight = `${charHeight}px`;
|
||||||
|
fillDisplay()
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillDisplay() {
|
||||||
|
// M rows N columns
|
||||||
|
m = Math.floor(display.value.offsetHeight / charHeight);
|
||||||
|
n = Math.floor(display.value.offsetWidth / charWidth);
|
||||||
|
const row = ' '.repeat(n);
|
||||||
|
displayText.value = (row + '\n').repeat(m - 1) + row
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
displayText.value = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
setup()
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
close()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<pre class="overflow-scroll w-full h-full bg-black text-white m-0 p-0" id="container" ref="display">{{ displayText
|
||||||
|
}}</pre>
|
||||||
|
</template>
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import Timer from "@/components/util/Timer.vue";
|
import Timer from "@/components/util/Timer.vue";
|
||||||
import Elle from "@/components/elle/Elle.vue";
|
|
||||||
import Time from "@/components/util/Time.vue";
|
import Time from "@/components/util/Time.vue";
|
||||||
|
import Elle from "@/components/elle/Elle.vue";
|
||||||
import Chat from "@/components/util/Chat.vue";
|
import Chat from "@/components/util/Chat.vue";
|
||||||
import MusicPlayer from "@/components/util/MusicPlayer.vue";
|
import MusicPlayer from "@/components/util/MusicPlayer.vue";
|
||||||
|
|
||||||
import Intro from "./Intro.vue";
|
import Intro from "./Intro.vue";
|
||||||
|
import Intro2 from "./Intro2.vue";
|
||||||
|
import BadApple from "./BadApple.vue";
|
||||||
import Stamps from "./Stamps.vue";
|
import Stamps from "./Stamps.vue";
|
||||||
import Listening from "./Listening.vue";
|
import Listening from "./Listening.vue";
|
||||||
import Links from "./Links.vue";
|
import Links from "./Links.vue";
|
||||||
@@ -14,15 +16,15 @@ import Collage from "./Collage.vue";
|
|||||||
import Favorites from "./Favorites.vue";
|
import Favorites from "./Favorites.vue";
|
||||||
import Gym from "./Gym.vue";
|
import Gym from "./Gym.vue";
|
||||||
import Consumption from "./Consumption.vue";
|
import Consumption from "./Consumption.vue";
|
||||||
|
|
||||||
import UtenaFrame from "@/components/borders/UtenaFrame.vue";
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main class="halftone justify-center flex flex-row w-full h-full">
|
<main class="halftone justify-center flex flex-row w-full h-full">
|
||||||
<div class="h-fit flex flex-row">
|
<div class="h-fit flex flex-row">
|
||||||
<div class="a4page-portrait homeGrid relative bdr-1">
|
<div class="a4page-portrait homeGrid relative bdr-1">
|
||||||
<Intro class="intro" />
|
<!-- <Intro class="intro" /> -->
|
||||||
|
<!-- <Intro2 class="intro" /> -->
|
||||||
|
<BadApple class="intro" />
|
||||||
<Listening class="listening" />
|
<Listening class="listening" />
|
||||||
<Stamps class="stamps" />
|
<Stamps class="stamps" />
|
||||||
<Feed class="feed" />
|
<Feed class="feed" />
|
||||||
@@ -32,21 +34,16 @@ import UtenaFrame from "@/components/borders/UtenaFrame.vue";
|
|||||||
<Favorites class="favorites" />
|
<Favorites class="favorites" />
|
||||||
<Gym class="gym" />
|
<Gym class="gym" />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class="sidebar border-quaternary place-content-between flex-1 flex flex-col m-10 w-60 ">
|
||||||
class="sidebar border-quaternary place-content-between flex-1 flex flex-col m-10 w-60 border-2"
|
|
||||||
>
|
|
||||||
<div class="flex flex-col flex-1">
|
<div class="flex flex-col flex-1">
|
||||||
<Time class="bg-bg_primary border-primary border-b" />
|
<Time class="bg-bg_primary border-primary border" />
|
||||||
<Timer class="border-primary border-b bg-bg_primary" />
|
<Timer class="border-primary border bg-bg_primary" />
|
||||||
<!-- <Elle class="flex-1" /> -->
|
<!-- <Elle class="flex-1" /> -->
|
||||||
<!-- <Chat class="bdr-2 bg-bg_primary" /> -->
|
<!-- <Chat class="bdr-2 bg-bg_primary" /> -->
|
||||||
<!-- <MusicPlayer /> -->
|
<!-- <MusicPlayer /> -->
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<img
|
<img src="/img/memes/fire-woman.gif" class="border-tertiary border" />
|
||||||
src="/img/memes/fire-woman.gif"
|
|
||||||
class="border-tertiary border"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -54,7 +51,7 @@ import UtenaFrame from "@/components/borders/UtenaFrame.vue";
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.homeGrid > * {
|
.homeGrid>* {
|
||||||
border: 2px solid var(--quaternary);
|
border: 2px solid var(--quaternary);
|
||||||
border-color: var(--quaternary);
|
border-color: var(--quaternary);
|
||||||
background-color: var(--bg_primary);
|
background-color: var(--bg_primary);
|
||||||
@@ -76,6 +73,7 @@ import UtenaFrame from "@/components/borders/UtenaFrame.vue";
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1200px) {
|
@media (max-width: 1200px) {
|
||||||
|
|
||||||
.tr,
|
.tr,
|
||||||
.br,
|
.br,
|
||||||
.sidebar {
|
.sidebar {
|
||||||
@@ -117,6 +115,7 @@ import UtenaFrame from "@/components/borders/UtenaFrame.vue";
|
|||||||
grid-column: span 4;
|
grid-column: span 4;
|
||||||
grid-row: span 2;
|
grid-row: span 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gym {
|
.gym {
|
||||||
grid-column: span 3;
|
grid-column: span 3;
|
||||||
grid-row: span 2;
|
grid-row: span 2;
|
||||||
|
|||||||
@@ -4,34 +4,27 @@ import Paragraph from "@/components/text/Paragraph.vue";
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div class="flex-1 border-box flex flex-col p-1 text-left items-start justify-start">
|
||||||
class="flex-1 border-box flex flex-col p-1 text-left items-start justify-start"
|
<Header>Yo</Header>
|
||||||
>
|
<!-- <Header>Intro</Header> -->
|
||||||
<Header>Intro</Header>
|
<!-- <Paragraph> -->
|
||||||
<Paragraph>
|
<!-- Hi, I'm Adam, thank you for visiting my website. -->
|
||||||
Hi, I'm Adam, thank you for visiting my website. I'm currently a 20
|
<!-- </Paragraph> -->
|
||||||
something graduate looking for work. I like to game, listen to lots
|
<!-- <Header>Getting around</Header> -->
|
||||||
of music and occasionally watch anime.
|
<!-- <Paragraph> -->
|
||||||
</Paragraph>
|
<!-- Pages available can be traversed through links below. I am hoping to -->
|
||||||
<Header>Getting around</Header>
|
<!-- add some shrines, code-walkthoughs, live chat and page transitions -->
|
||||||
<Paragraph>
|
<!-- at a later date. -->
|
||||||
Pages available can be traversed through links below. I am hoping to
|
<!-- </Paragraph> -->
|
||||||
add some shrines, code-walkthoughs, live chat and page transitions
|
<!-- <Header>Contact</Header> -->
|
||||||
at a later date.
|
<!-- <Paragraph> -->
|
||||||
</Paragraph>
|
<!-- Please email me <a href="mailto:adam.a.french@outlook.com">here</a>, -->
|
||||||
<Header>Contact</Header>
|
<!-- or contact me though any of the social medias linked. -->
|
||||||
<Paragraph>
|
<!-- </Paragraph> -->
|
||||||
Please email me <a href="mailto:adam.a.french@outlook.com">here</a>,
|
<!-- <Header>A Quote</Header> -->
|
||||||
or contact me though any of the social medias linked.
|
<!-- <Paragraph> -->
|
||||||
</Paragraph>
|
<!-- One crossed wire, one wayward pinch of potassium chlorate, one -->
|
||||||
<Header>A Quote</Header>
|
<!-- errant twitch, and KA-BLOOIE! -->
|
||||||
<!-- <p>
|
<!-- </Paragraph> -->
|
||||||
What makes me a good demoman? If I were a bad demoman, I wouldn't be
|
|
||||||
sittin' here discussin' it with you, now would I?!
|
|
||||||
</p> -->
|
|
||||||
<Paragraph>
|
|
||||||
One crossed wire, one wayward pinch of potassium chlorate, one
|
|
||||||
errant twitch, and KA-BLOOIE!
|
|
||||||
</Paragraph>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
82
nginx/vue/src/views/home/Intro2.vue
Normal file
82
nginx/vue/src/views/home/Intro2.vue
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { rand } from '@vueuse/core'
|
||||||
|
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
|
||||||
|
|
||||||
|
interface Item {
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
dx: number
|
||||||
|
dy: number
|
||||||
|
content: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = ref<HTMLDivElement | null>(null)
|
||||||
|
const itemEls = ref<HTMLDivElement[]>([])
|
||||||
|
|
||||||
|
const phrases = [
|
||||||
|
'Welcome to my website',
|
||||||
|
'Thank you for visiting',
|
||||||
|
"I'd love to know your recommendations",
|
||||||
|
"Message me on discord or steam",
|
||||||
|
"I like anime, all kinds of music and sci fic",
|
||||||
|
"Try to stay away from instagram",
|
||||||
|
"Always watching too much youtube",
|
||||||
|
]
|
||||||
|
|
||||||
|
const items = ref<Item[]>(
|
||||||
|
phrases.map((text, i) => ({
|
||||||
|
x: i * 20,
|
||||||
|
y: i * 20,
|
||||||
|
dx: rand(0, 100) / 100,
|
||||||
|
dy: 0.5,
|
||||||
|
content: text,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
|
let rafId = 0
|
||||||
|
|
||||||
|
function animate() {
|
||||||
|
const c = container.value
|
||||||
|
if (!c) return
|
||||||
|
|
||||||
|
const cw = c.clientWidth
|
||||||
|
const ch = c.clientHeight
|
||||||
|
|
||||||
|
items.value.forEach((item, i) => {
|
||||||
|
const el = itemEls.value[i]
|
||||||
|
if (!el) return
|
||||||
|
|
||||||
|
const ew = el.offsetWidth
|
||||||
|
const eh = el.offsetHeight
|
||||||
|
|
||||||
|
item.x += item.dx
|
||||||
|
item.y += item.dy
|
||||||
|
|
||||||
|
if (item.x < 0 || item.x > cw - ew) item.dx *= -1
|
||||||
|
if (item.y < 0 || item.y > ch - eh) item.dy *= -1
|
||||||
|
})
|
||||||
|
|
||||||
|
rafId = requestAnimationFrame(animate)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await nextTick()
|
||||||
|
rafId = requestAnimationFrame(animate)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
cancelAnimationFrame(rafId)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="container" class="w-full h-full relative overflow-hidden">
|
||||||
|
<div v-for="(item, i) in items" :key="i" ref="itemEls" class=" absolute w-fit h-fit" :style="{
|
||||||
|
transform: `translate(${item.x}px, ${item.y}px)`
|
||||||
|
}">
|
||||||
|
<h1>
|
||||||
|
{{ item.content }}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import RouterTable from "@/components/util/RouterTable.vue";
|
import RouterTable from "@/components/util/RouterTable.vue";
|
||||||
import LinkTable from "@/components/util/LinkTable.vue";
|
import LinkTable from "@/components/util/LinkTable.vue";
|
||||||
import Markdown from "@/components/util/Markdown.vue";
|
|
||||||
|
|
||||||
const site_links = [
|
const site_links = [
|
||||||
{ name: "CV", link: "/cv" },
|
{ name: "CV", link: "/cv" },
|
||||||
|
|||||||
Reference in New Issue
Block a user