Compare commits
2 Commits
ac171f7846
...
rowing
| Author | SHA1 | Date | |
|---|---|---|---|
| 095cd72946 | |||
| 1d4beca336 |
@@ -1,19 +0,0 @@
|
|||||||
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,9 +3,6 @@ certbot/www
|
|||||||
backend/token/
|
backend/token/
|
||||||
.env
|
.env
|
||||||
|
|
||||||
gitea/data/*
|
|
||||||
gitea-runner/data/*
|
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
|
|||||||
@@ -7,12 +7,13 @@ 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.0.0-20210810183815-faf39c7919d5
|
golang.org/x/oauth2 v0.30.0
|
||||||
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
|
||||||
@@ -23,7 +24,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.2 // indirect
|
github.com/golang/protobuf v1.5.4 // 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
|
||||||
@@ -40,6 +41,11 @@ 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,6 +33,8 @@ 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=
|
||||||
@@ -102,6 +104,8 @@ 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=
|
||||||
@@ -172,6 +176,8 @@ 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=
|
||||||
@@ -183,6 +189,16 @@ 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=
|
||||||
@@ -287,6 +303,8 @@ 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=
|
||||||
|
|||||||
154
backend/handlers/handle_rowing.go
Normal file
154
backend/handlers/handle_rowing.go
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
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,6 +4,7 @@ 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"
|
||||||
@@ -13,6 +14,7 @@ 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,6 +14,7 @@ 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 {
|
||||||
@@ -39,12 +40,18 @@ 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, client := services.InitSpotifyAuth(&spotifyConfig)
|
spotifyAuth, spotifyClient := 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")
|
||||||
@@ -58,7 +65,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: client, Auth: auth, Notes: notes}
|
store := handlers.Store{DB: db, SpotifyAuth: spotifyAuth, SpotifyClient: spotifyClient, ClaudeClient: claudeClient, Auth: auth, Notes: notes}
|
||||||
|
|
||||||
protected := r.Group("/", store.AuthMiddlewear)
|
protected := r.Group("/", store.AuthMiddlewear)
|
||||||
|
|
||||||
@@ -66,6 +73,10 @@ 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,3 +55,14 @@ 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"`
|
||||||
|
}
|
||||||
|
|||||||
15
backend/services/claude.go
Normal file
15
backend/services/claude.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
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,6 +36,7 @@ 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,11 +12,10 @@ services:
|
|||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
container_name: nginx
|
container_name: nginx
|
||||||
env_file: ./.env
|
env_file: ./.env
|
||||||
restart: always
|
restart: unless-stopped
|
||||||
depends_on:
|
depends_on:
|
||||||
- backend
|
- backend
|
||||||
- icecast2
|
- icecast2
|
||||||
- gitea
|
|
||||||
networks:
|
networks:
|
||||||
- app-network
|
- app-network
|
||||||
ports:
|
ports:
|
||||||
@@ -34,8 +33,6 @@ 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
|
||||||
|
|
||||||
@@ -44,7 +41,7 @@ services:
|
|||||||
context: ./backend
|
context: ./backend
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
container_name: "${BACKEND_HOST}"
|
container_name: "${BACKEND_HOST}"
|
||||||
restart: always
|
restart: unless-stopped
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
networks:
|
networks:
|
||||||
@@ -59,15 +56,13 @@ services:
|
|||||||
db:
|
db:
|
||||||
image: postgres:16
|
image: postgres:16
|
||||||
container_name: "${POSTGRES_HOST}"
|
container_name: "${POSTGRES_HOST}"
|
||||||
restart: always
|
restart: unless-stopped
|
||||||
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:
|
||||||
@@ -81,43 +76,3 @@ 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
|
|
||||||
|
|||||||
@@ -1,110 +0,0 @@
|
|||||||
# 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:
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
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} ${GITEA_HOST} ${GITEA_PORT}' \
|
envsubst '${DOMAIN} ${BACKEND_HOST} ${BACKEND_PORT} ${BACKEND_ENDPOINT} ${ICECAST_HOST} ${ICECAST_PORT}' \
|
||||||
< /etc/nginx/nginx.conf.template \
|
< /etc/nginx/nginx.conf.template \
|
||||||
> /etc/nginx/nginx.conf
|
> /etc/nginx/nginx.conf
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -98,18 +98,6 @@ 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,14 +10,12 @@
|
|||||||
"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"
|
||||||
},
|
},
|
||||||
@@ -1633,12 +1631,6 @@
|
|||||||
"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",
|
||||||
@@ -1924,44 +1916,6 @@
|
|||||||
"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",
|
||||||
@@ -1985,13 +1939,13 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "1.13.5",
|
"version": "1.13.2",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
|
||||||
"integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",
|
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.15.11",
|
"follow-redirects": "^1.15.6",
|
||||||
"form-data": "^4.0.5",
|
"form-data": "^4.0.4",
|
||||||
"proxy-from-env": "^1.1.0"
|
"proxy-from-env": "^1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3428,19 +3382,6 @@
|
|||||||
"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,14 +14,12 @@
|
|||||||
"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(
|
||||||
-180deg,
|
30deg,
|
||||||
rgba(1, 1, 1, 1) 0%,
|
rgba(1, 1, 1, 1) 0%,
|
||||||
rgba(1, 1, 1, 0.92) 100%
|
rgba(1, 1, 1, 0.9) 100%
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,10 +49,14 @@ 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 class="bdr-2 bg-bg_primary" v-if="parentPath" :to="parentPath">
|
<RouterLink
|
||||||
|
class="bdr-2 bg-bg_primary"
|
||||||
|
v-if="parentPath"
|
||||||
|
:to="parentPath"
|
||||||
|
>
|
||||||
<a>UP</a>
|
<a>UP</a>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<Headline class="border flex-1 max-w-full">
|
<Headline class="border flex-1">
|
||||||
<code class="whitespace-pre">{{ faces_string }}</code>
|
<code class="whitespace-pre">{{ faces_string }}</code>
|
||||||
</Headline>
|
</Headline>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted, useTemplateRef, onUnmounted } from "vue";
|
import { ref, 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;
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@ 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);
|
||||||
|
|
||||||
@@ -22,7 +24,8 @@ function animate() {
|
|||||||
offset += width;
|
offset += width;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctnr.style.transform = `translateX(${offset}px)`;
|
it1.style.transform = `translateX(${offset}px)`;
|
||||||
|
it2.style.transform = `translateX(${width + offset}px)`;
|
||||||
|
|
||||||
rafId = requestAnimationFrame(animate);
|
rafId = requestAnimationFrame(animate);
|
||||||
}
|
}
|
||||||
@@ -37,32 +40,40 @@ onUnmounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="root">
|
<div class="marquee">
|
||||||
<div class="container" ref="container">
|
<div class="container" ref="container">
|
||||||
<div ref="item1">
|
<div class="item" ref="item1"><slot /></div>
|
||||||
<slot />
|
<div class="item item2" ref="item2"><slot /></div>
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.root {
|
.marquee {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
width: fit-content;
|
width: 100%;
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
display: grid;
|
position: relative;
|
||||||
grid-auto-flow: column;
|
|
||||||
grid-auto-columns: max-content;
|
|
||||||
/* Each column fits its content */
|
|
||||||
overflow-x: visible;
|
|
||||||
will-change: transform;
|
will-change: transform;
|
||||||
gap: 10em;
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
height: fit-content;
|
||||||
|
top: 0px;
|
||||||
|
padding-right: 3em;
|
||||||
|
width: fit-content;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item1 {
|
||||||
|
left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item2 {
|
||||||
|
position: absolute;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
<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,13 +1,11 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import Timer from "@/components/util/Timer.vue";
|
import Timer from "@/components/util/Timer.vue";
|
||||||
import Time from "@/components/util/Time.vue";
|
|
||||||
import Elle from "@/components/elle/Elle.vue";
|
import Elle from "@/components/elle/Elle.vue";
|
||||||
|
import Time from "@/components/util/Time.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";
|
||||||
@@ -16,6 +14,8 @@ 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>
|
||||||
@@ -23,8 +23,6 @@ import Consumption from "./Consumption.vue";
|
|||||||
<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" />
|
||||||
@@ -35,11 +33,11 @@ import Consumption from "./Consumption.vue";
|
|||||||
<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" />
|
<Time class="bg-bg_primary border-primary border-b" />
|
||||||
<Timer class="border-primary border bg-bg_primary" />
|
<Timer class="border-primary border-b 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 /> -->
|
||||||
@@ -119,7 +117,6 @@ import Consumption from "./Consumption.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,27 +4,34 @@ import Paragraph from "@/components/text/Paragraph.vue";
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex-1 border-box flex flex-col p-1 text-left items-start justify-start">
|
<div
|
||||||
<Header>Yo</Header>
|
class="flex-1 border-box flex flex-col p-1 text-left items-start justify-start"
|
||||||
<!-- <Header>Intro</Header> -->
|
>
|
||||||
<!-- <Paragraph> -->
|
<Header>Intro</Header>
|
||||||
<!-- Hi, I'm Adam, thank you for visiting my website. -->
|
<Paragraph>
|
||||||
<!-- </Paragraph> -->
|
Hi, I'm Adam, thank you for visiting my website. I'm currently a 20
|
||||||
<!-- <Header>Getting around</Header> -->
|
something graduate looking for work. I like to game, listen to lots
|
||||||
<!-- <Paragraph> -->
|
of music and occasionally watch anime.
|
||||||
<!-- Pages available can be traversed through links below. I am hoping to -->
|
</Paragraph>
|
||||||
<!-- add some shrines, code-walkthoughs, live chat and page transitions -->
|
<Header>Getting around</Header>
|
||||||
<!-- at a later date. -->
|
<Paragraph>
|
||||||
<!-- </Paragraph> -->
|
Pages available can be traversed through links below. I am hoping to
|
||||||
<!-- <Header>Contact</Header> -->
|
add some shrines, code-walkthoughs, live chat and page transitions
|
||||||
<!-- <Paragraph> -->
|
at a later date.
|
||||||
<!-- Please email me <a href="mailto:adam.a.french@outlook.com">here</a>, -->
|
</Paragraph>
|
||||||
<!-- or contact me though any of the social medias linked. -->
|
<Header>Contact</Header>
|
||||||
<!-- </Paragraph> -->
|
<Paragraph>
|
||||||
<!-- <Header>A Quote</Header> -->
|
Please email me <a href="mailto:adam.a.french@outlook.com">here</a>,
|
||||||
<!-- <Paragraph> -->
|
or contact me though any of the social medias linked.
|
||||||
<!-- One crossed wire, one wayward pinch of potassium chlorate, one -->
|
</Paragraph>
|
||||||
<!-- errant twitch, and KA-BLOOIE! -->
|
<Header>A Quote</Header>
|
||||||
<!-- </Paragraph> -->
|
<!-- <p>
|
||||||
|
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>
|
||||||
|
|||||||
@@ -1,82 +0,0 @@
|
|||||||
<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,6 +1,7 @@
|
|||||||
<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