test: first build
Android development APK / apk (push) Has been cancelled
Checks / go (push) Failing after 6s
Container images / publish (., deploy/pocketbase/Dockerfile, evoliohealth-pocketbase) (push) Has been cancelled
Container images / publish (., server/Dockerfile, evoliohealth-server) (push) Has been cancelled
Checks / compose (push) Has been cancelled
Checks / flutter (push) Has been cancelled
Android development APK / apk (push) Has been cancelled
Checks / go (push) Failing after 6s
Container images / publish (., deploy/pocketbase/Dockerfile, evoliohealth-pocketbase) (push) Has been cancelled
Container images / publish (., server/Dockerfile, evoliohealth-server) (push) Has been cancelled
Checks / compose (push) Has been cancelled
Checks / flutter (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
.git
|
||||
.gitea
|
||||
.env
|
||||
.env.*
|
||||
**/.dart_tool
|
||||
**/build
|
||||
**/coverage
|
||||
runtime
|
||||
data
|
||||
config
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
EVOLIOHEALTH_REGISTRY=git.zaynet.fr/tony
|
||||
EVOLIOHEALTH_TAG=dev
|
||||
|
||||
APP_ENV=dev
|
||||
APP_URL=http://localhost:8080
|
||||
TZ=Europe/Paris
|
||||
PUID=1000
|
||||
PGID=1000
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
name: Android development APK
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- companion/**
|
||||
- .gitea/workflows/android.yaml
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
apk:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Install Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: 3.44.6
|
||||
channel: stable
|
||||
cache: true
|
||||
- name: Generate the development Android platform shell
|
||||
working-directory: companion
|
||||
run: flutter create --platforms=android --org fr.zaynet.evoliohealth --project-name evoliohealth_companion .
|
||||
- name: Build development APK
|
||||
working-directory: companion
|
||||
run: flutter build apk --debug
|
||||
- name: Publish development APK artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: evoliohealth-companion-dev
|
||||
path: companion/build/app/outputs/flutter-apk/app-debug.apk
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
name: Checks
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
go:
|
||||
runs-on: ubuntu-latest
|
||||
container: golang:1.26.5-alpine3.24
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Install native test dependencies
|
||||
run: apk add --no-cache build-base
|
||||
- name: Format check
|
||||
working-directory: server
|
||||
run: test -z "$(gofmt -l .)"
|
||||
- name: Vet
|
||||
working-directory: server
|
||||
run: go vet ./...
|
||||
- name: Test
|
||||
working-directory: server
|
||||
run: go test -race ./...
|
||||
|
||||
flutter:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Install Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: 3.44.6
|
||||
channel: stable
|
||||
cache: true
|
||||
- name: Check Web formatting
|
||||
working-directory: web
|
||||
run: dart format --output=none --set-exit-if-changed .
|
||||
- name: Test Web
|
||||
working-directory: web
|
||||
run: flutter test
|
||||
- name: Analyze Web
|
||||
working-directory: web
|
||||
run: flutter analyze
|
||||
- name: Check Companion formatting
|
||||
working-directory: companion
|
||||
run: dart format --output=none --set-exit-if-changed .
|
||||
- name: Test Companion
|
||||
working-directory: companion
|
||||
run: flutter test
|
||||
- name: Analyze Companion
|
||||
working-directory: companion
|
||||
run: flutter analyze
|
||||
|
||||
compose:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Validate Compose
|
||||
env:
|
||||
APP_URL: http://localhost:8080
|
||||
run: docker compose config --quiet
|
||||
@@ -0,0 +1,63 @@
|
||||
name: Container images
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
tags: ['v*']
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
env:
|
||||
REGISTRY: git.zaynet.fr
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- image: evoliohealth-server
|
||||
context: .
|
||||
file: server/Dockerfile
|
||||
- image: evoliohealth-pocketbase
|
||||
context: .
|
||||
file: deploy/pocketbase/Dockerfile
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Log in to Gitea registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ gitea.actor }}
|
||||
password: ${{ secrets.GITEA_TOKEN }}
|
||||
- name: Generate image metadata
|
||||
id: metadata
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/tony/${{ matrix.image }}
|
||||
tags: |
|
||||
type=raw,value=dev,enable=${{ gitea.ref == 'refs/heads/main' }}
|
||||
type=sha,prefix=sha-
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
- name: Build and publish image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ${{ matrix.context }}
|
||||
file: ${{ matrix.file }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.metadata.outputs.tags }}
|
||||
labels: ${{ steps.metadata.outputs.labels }}
|
||||
provenance: mode=max
|
||||
sbom: true
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
# Environment and secrets
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
*.key
|
||||
*.pem
|
||||
*.p12
|
||||
*.jks
|
||||
key.properties
|
||||
|
||||
# Go
|
||||
server/bin/
|
||||
server/coverage.out
|
||||
|
||||
# Flutter and Dart
|
||||
**/.dart_tool/
|
||||
**/.flutter-plugins
|
||||
**/.flutter-plugins-dependencies
|
||||
**/.packages
|
||||
**/build/
|
||||
**/coverage/
|
||||
|
||||
# Editors and operating systems
|
||||
.idea/
|
||||
.vscode/
|
||||
*.iml
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Runtime data
|
||||
runtime/
|
||||
data/
|
||||
config/
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to EvolioHealth are documented in this file.
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
- Initial Go server entry point with liveness, readiness, security headers, and graceful shutdown.
|
||||
- Minimal French/English Flutter Web and Android Companion applications.
|
||||
- Multi-stage server image bundling Flutter Web into the Go runtime.
|
||||
- Dedicated private PocketBase image with verified release checksum.
|
||||
- Hardened single-file Compose topology with no published PocketBase port.
|
||||
- Gitea Actions checks, multi-architecture container publishing, and development APK workflows.
|
||||
- Gitea registry defaults for `git.zaynet.fr/tony`.
|
||||
|
||||
@@ -20,4 +20,42 @@ This package is the initial product and architecture source of truth for impleme
|
||||
|
||||
## Status
|
||||
|
||||
This specification defines the intended first production architecture and staged delivery plan. Future exercise and nutrition catalogs are anticipated, but their complete user workflows are not required for the first usable release.
|
||||
The repository foundation is now present. It contains minimal Go, Flutter Web,
|
||||
and Flutter Android entry points, container builds, a hardened Compose topology,
|
||||
and Gitea Actions workflows. Business functionality remains staged according to
|
||||
[the development plan](docs/development-plan.md).
|
||||
|
||||
## Repository layout
|
||||
|
||||
```text
|
||||
server/ Go API and bundled Flutter Web server
|
||||
web/ Flutter Web application
|
||||
companion/ Flutter Android Companion
|
||||
deploy/pocketbase/ Private PocketBase container
|
||||
.gitea/workflows/ Checks, container publishing, and development APK
|
||||
compose.yaml Single supported deployment definition
|
||||
docs/ Authoritative specifications and decisions
|
||||
```
|
||||
|
||||
## Development deployment
|
||||
|
||||
Copy `.env.example` to `.env`, set `APP_URL`, and start the stack with the
|
||||
standard Docker Compose interface. Images default to:
|
||||
|
||||
```text
|
||||
git.zaynet.fr/tony/evoliohealth-server:dev
|
||||
git.zaynet.fr/tony/evoliohealth-pocketbase:dev
|
||||
```
|
||||
|
||||
PocketBase has no host port and is connected only to the internal backend
|
||||
network. The public application listens on host port `8080` for local
|
||||
development and is the only service a reverse proxy may target.
|
||||
|
||||
## Automated builds
|
||||
|
||||
Gitea Actions requires an online runner carrying the `ubuntu-latest` label.
|
||||
Repository Actions token permissions must allow `contents: read` and
|
||||
`packages: write`. A push to `main` publishes multi-architecture development
|
||||
images for `linux/amd64` and `linux/arm64`. The Android workflow produces a
|
||||
debug APK retained as a short-lived workflow artifact; release signing is not
|
||||
configured yet.
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const EvolioHealthCompanion());
|
||||
}
|
||||
|
||||
class EvolioHealthCompanion extends StatelessWidget {
|
||||
const EvolioHealthCompanion({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
localizationsDelegates: GlobalMaterialLocalizations.delegates,
|
||||
supportedLocales: const [Locale('en'), Locale('fr')],
|
||||
title: 'EvolioHealth Companion',
|
||||
theme: ThemeData(colorSchemeSeed: const Color(0xFF006C51)),
|
||||
home: const Scaffold(body: Center(child: Text('EvolioHealth Companion'))),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
name: evoliohealth_companion
|
||||
description: EvolioHealth Companion for Android.
|
||||
publish_to: none
|
||||
version: 0.1.0+1
|
||||
|
||||
environment:
|
||||
sdk: '>=3.12.0 <4.0.0'
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^6.0.0
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
@@ -0,0 +1,9 @@
|
||||
import 'package:evoliohealth_companion/main.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('renders the product name', (tester) async {
|
||||
await tester.pumpWidget(const EvolioHealthCompanion());
|
||||
expect(find.text('EvolioHealth Companion'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
name: evoliohealth
|
||||
|
||||
services:
|
||||
app:
|
||||
image: "${EVOLIOHEALTH_REGISTRY:-git.zaynet.fr/tony}/evoliohealth-server:${EVOLIOHEALTH_TAG:-dev}"
|
||||
user: "${PUID:-1000}:${PGID:-1000}"
|
||||
environment:
|
||||
APP_ENV: "${APP_ENV:-prod}"
|
||||
APP_URL: "${APP_URL:?APP_URL must be set}"
|
||||
POCKETBASE_URL: "http://pocketbase:8090"
|
||||
TZ: "${TZ:-UTC}"
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- evoliohealth_config:/config
|
||||
- evoliohealth_data:/data
|
||||
networks:
|
||||
- frontend
|
||||
- backend
|
||||
depends_on:
|
||||
pocketbase:
|
||||
condition: service_healthy
|
||||
read_only: true
|
||||
tmpfs:
|
||||
- /tmp:size=64m,mode=1777
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
- ALL
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:8080/health/live"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
restart: unless-stopped
|
||||
|
||||
pocketbase:
|
||||
image: "${EVOLIOHEALTH_REGISTRY:-git.zaynet.fr/tony}/evoliohealth-pocketbase:${EVOLIOHEALTH_TAG:-dev}"
|
||||
user: "${PUID:-1000}:${PGID:-1000}"
|
||||
environment:
|
||||
TZ: "${TZ:-UTC}"
|
||||
expose:
|
||||
- "8090"
|
||||
volumes:
|
||||
- evoliohealth_pocketbase:/data/pocketbase
|
||||
networks:
|
||||
- backend
|
||||
read_only: true
|
||||
tmpfs:
|
||||
- /tmp:size=64m,mode=1777
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
- ALL
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:8090/api/health"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 10s
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
frontend:
|
||||
backend:
|
||||
internal: true
|
||||
|
||||
volumes:
|
||||
evoliohealth_config:
|
||||
evoliohealth_data:
|
||||
evoliohealth_pocketbase:
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
ARG POCKETBASE_VERSION=0.38.2
|
||||
ARG ALPINE_VERSION=3.24.1
|
||||
|
||||
FROM alpine:${ALPINE_VERSION} AS downloader
|
||||
ARG POCKETBASE_VERSION
|
||||
ARG TARGETARCH
|
||||
RUN apk add --no-cache ca-certificates unzip wget \
|
||||
&& wget -q "https://github.com/pocketbase/pocketbase/releases/download/v${POCKETBASE_VERSION}/pocketbase_${POCKETBASE_VERSION}_linux_${TARGETARCH}.zip" -O /tmp/pocketbase.zip \
|
||||
&& wget -q "https://github.com/pocketbase/pocketbase/releases/download/v${POCKETBASE_VERSION}/checksums.txt" -O /tmp/checksums.txt \
|
||||
&& cd /tmp \
|
||||
&& grep "pocketbase_${POCKETBASE_VERSION}_linux_${TARGETARCH}.zip" checksums.txt | sha256sum -c - \
|
||||
&& unzip /tmp/pocketbase.zip -d /out
|
||||
|
||||
FROM alpine:${ALPINE_VERSION}
|
||||
RUN apk add --no-cache ca-certificates tzdata \
|
||||
&& addgroup -g 1000 pocketbase \
|
||||
&& adduser -D -H -u 1000 -G pocketbase pocketbase \
|
||||
&& install -d -o pocketbase -g pocketbase /data/pocketbase /tmp
|
||||
COPY --from=downloader /out/pocketbase /usr/local/bin/pocketbase
|
||||
EXPOSE 8090
|
||||
ENTRYPOINT ["/usr/local/bin/pocketbase", "serve", "--http=0.0.0.0:8090", "--dir=/data/pocketbase"]
|
||||
|
||||
@@ -4,6 +4,12 @@
|
||||
|
||||
Ship one `compose.yaml`. Operators launch it with their normal manager; no Compose profiles or mandatory override files. `APP_ENV=dev|prod` selects application behavior.
|
||||
|
||||
The checked-in Compose file selects published images with
|
||||
`EVOLIOHEALTH_REGISTRY` and `EVOLIOHEALTH_TAG`. These variables select immutable
|
||||
deployment artifacts and are not application configuration. Development
|
||||
defaults to `git.zaynet.fr/tony` and `dev`; production deployments SHOULD select
|
||||
an immutable semantic-version tag.
|
||||
|
||||
Conceptual topology:
|
||||
|
||||
```yaml
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
ARG FLUTTER_VERSION=3.44.6
|
||||
ARG GO_VERSION=1.26.5
|
||||
ARG ALPINE_VERSION=3.24.1
|
||||
|
||||
FROM ghcr.io/cirruslabs/flutter:${FLUTTER_VERSION} AS web-builder
|
||||
WORKDIR /src/web
|
||||
COPY web/pubspec.yaml web/analysis_options.yaml ./
|
||||
RUN flutter pub get
|
||||
COPY web/ ./
|
||||
RUN flutter build web --release
|
||||
|
||||
FROM golang:${GO_VERSION}-alpine3.24 AS go-builder
|
||||
WORKDIR /src/server
|
||||
COPY server/go.mod ./
|
||||
RUN go mod download
|
||||
COPY server/ ./
|
||||
COPY --from=web-builder /src/web/build/web ./cmd/evoliohealth/webdist
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} \
|
||||
go build -trimpath -ldflags="-s -w" -o /out/evoliohealth ./cmd/evoliohealth
|
||||
|
||||
FROM alpine:${ALPINE_VERSION}
|
||||
RUN apk add --no-cache ca-certificates tzdata \
|
||||
&& addgroup -g 1000 evoliohealth \
|
||||
&& adduser -D -H -u 1000 -G evoliohealth evoliohealth \
|
||||
&& install -d -o evoliohealth -g evoliohealth /config /data /tmp
|
||||
COPY --from=go-builder /out/evoliohealth /usr/local/bin/evoliohealth
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT ["/usr/local/bin/evoliohealth"]
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// webAssets contains the Flutter Web release copied by the container build.
|
||||
//go:embed webdist
|
||||
var webAssets embed.FS
|
||||
|
||||
type application struct {
|
||||
backendAddress string
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
func main() {
|
||||
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
|
||||
app := application{
|
||||
backendAddress: envOrDefault("POCKETBASE_URL", "http://pocketbase:8090"),
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
assets, err := fs.Sub(webAssets, "webdist")
|
||||
if err != nil {
|
||||
logger.Error("web assets are unavailable")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("GET /health/live", app.live)
|
||||
mux.HandleFunc("GET /health/ready", app.ready)
|
||||
mux.Handle("/", http.FileServer(http.FS(assets)))
|
||||
|
||||
server := &http.Server{
|
||||
Addr: ":8080",
|
||||
Handler: securityHeaders(mux),
|
||||
ReadHeaderTimeout: 5 * time.Second,
|
||||
ReadTimeout: 15 * time.Second,
|
||||
WriteTimeout: 30 * time.Second,
|
||||
IdleTimeout: 60 * time.Second,
|
||||
MaxHeaderBytes: 1 << 20,
|
||||
}
|
||||
|
||||
stop, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
defer cancel()
|
||||
|
||||
go func() {
|
||||
logger.Info("server started", "address", server.Addr)
|
||||
if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
|
||||
logger.Error("server stopped unexpectedly", "error", err)
|
||||
cancel()
|
||||
}
|
||||
}()
|
||||
|
||||
<-stop.Done()
|
||||
shutdownContext, shutdownCancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer shutdownCancel()
|
||||
if err := server.Shutdown(shutdownContext); err != nil {
|
||||
logger.Error("graceful shutdown failed", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
logger.Info("server stopped")
|
||||
}
|
||||
|
||||
func (app application) live(response http.ResponseWriter, _ *http.Request) {
|
||||
response.Header().Set("Content-Type", "application/json")
|
||||
response.WriteHeader(http.StatusOK)
|
||||
_, _ = response.Write([]byte(`{"status":"ok"}`))
|
||||
}
|
||||
|
||||
func (app application) ready(response http.ResponseWriter, request *http.Request) {
|
||||
address := strings.TrimPrefix(strings.TrimPrefix(app.backendAddress, "http://"), "https://")
|
||||
if !strings.Contains(address, ":") {
|
||||
address += ":8090"
|
||||
}
|
||||
|
||||
connection, err := (&net.Dialer{Timeout: 2 * time.Second}).DialContext(request.Context(), "tcp", address)
|
||||
if err != nil {
|
||||
http.Error(response, `{"status":"unavailable"}`, http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
_ = connection.Close()
|
||||
|
||||
response.Header().Set("Content-Type", "application/json")
|
||||
response.WriteHeader(http.StatusOK)
|
||||
_, _ = response.Write([]byte(`{"status":"ready"}`))
|
||||
}
|
||||
|
||||
func securityHeaders(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
||||
response.Header().Set("Content-Security-Policy", "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'")
|
||||
response.Header().Set("Referrer-Policy", "no-referrer")
|
||||
response.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
response.Header().Set("X-Frame-Options", "DENY")
|
||||
next.ServeHTTP(response, request)
|
||||
})
|
||||
}
|
||||
|
||||
func envOrDefault(name, fallback string) string {
|
||||
if value := os.Getenv(name); value != "" {
|
||||
return value
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func (app application) String() string {
|
||||
return fmt.Sprintf("application(%s)", app.backendAddress)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLiveDoesNotDiscloseInternals(t *testing.T) {
|
||||
app := application{}
|
||||
request := httptest.NewRequest(http.MethodGet, "/health/live", nil)
|
||||
response := httptest.NewRecorder()
|
||||
|
||||
app.live(response, request)
|
||||
|
||||
if response.Code != http.StatusOK {
|
||||
t.Fatalf("expected status %d, got %d", http.StatusOK, response.Code)
|
||||
}
|
||||
if response.Body.String() != `{"status":"ok"}` {
|
||||
t.Fatalf("unexpected response: %s", response.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecurityHeaders(t *testing.T) {
|
||||
handler := securityHeaders(http.HandlerFunc(func(response http.ResponseWriter, _ *http.Request) {
|
||||
response.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
request := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
response := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(response, request)
|
||||
|
||||
for _, header := range []string{"Content-Security-Policy", "Referrer-Policy", "X-Content-Type-Options", "X-Frame-Options"} {
|
||||
if response.Header().Get(header) == "" {
|
||||
t.Errorf("expected %s header", header)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<!doctype html><html lang="en"><body>EvolioHealth build placeholder</body></html>
|
||||
@@ -0,0 +1,4 @@
|
||||
module git.zaynet.fr/tony/EvolioHealth/server
|
||||
|
||||
go 1.26.0
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const EvolioHealthWeb());
|
||||
}
|
||||
|
||||
class EvolioHealthWeb extends StatelessWidget {
|
||||
const EvolioHealthWeb({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
localizationsDelegates: GlobalMaterialLocalizations.delegates,
|
||||
supportedLocales: const [Locale('en'), Locale('fr')],
|
||||
title: 'EvolioHealth',
|
||||
theme: ThemeData(colorSchemeSeed: const Color(0xFF006C51)),
|
||||
home: const Scaffold(body: Center(child: Text('EvolioHealth Server'))),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
name: evoliohealth_web
|
||||
description: EvolioHealth Web application.
|
||||
publish_to: none
|
||||
version: 0.1.0+1
|
||||
|
||||
environment:
|
||||
sdk: '>=3.12.0 <4.0.0'
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^6.0.0
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
@@ -0,0 +1,9 @@
|
||||
import 'package:evoliohealth_web/main.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('renders the product name', (tester) async {
|
||||
await tester.pumpWidget(const EvolioHealthWeb());
|
||||
expect(find.text('EvolioHealth Server'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<base href="$FLUTTER_BASE_HREF">
|
||||
<meta charset="UTF-8">
|
||||
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||
<meta name="description" content="Private, self-hosted health tracking.">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>EvolioHealth</title>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
</head>
|
||||
<body>
|
||||
<script src="flutter_bootstrap.js" async></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "EvolioHealth",
|
||||
"short_name": "EvolioHealth",
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#006c51",
|
||||
"description": "Private, self-hosted health tracking.",
|
||||
"orientation": "portrait-primary",
|
||||
"prefer_related_applications": false,
|
||||
"icons": []
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user