feat: setup monorepo infra — Go API + Vue 3 + PostgreSQL + Dockerfile Dokploy
Monorepo com apps/api (Go 1.23 + chi + pgx) e apps/web (Vue 3 + Vite + TypeScript). Dockerfile multi-stage consolida tudo em imagem única (~25MB) para deploy no Dokploy. Migrations rodando na inicialização; Vue embedado no binário Go via //go:embed. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
@@ -0,0 +1,17 @@
|
|||||||
|
# Copie para .env e ajuste os valores antes de rodar localmente
|
||||||
|
# cp .env.example .env
|
||||||
|
|
||||||
|
# ── Banco de dados ──────────────────────────────────────────────────────────────
|
||||||
|
# Senha do PostgreSQL (usada pelo docker-compose e pela API)
|
||||||
|
POSTGRES_PASSWORD=financeiro
|
||||||
|
|
||||||
|
# DSN completo para a API (usado quando roda fora do docker-compose)
|
||||||
|
DATABASE_URL=postgres://financeiro:financeiro@localhost:5432/financeiro?sslmode=disable
|
||||||
|
|
||||||
|
# ── API ─────────────────────────────────────────────────────────────────────────
|
||||||
|
# Porta em que o servidor Go escuta
|
||||||
|
PORT=8080
|
||||||
|
|
||||||
|
# ── docker-compose ──────────────────────────────────────────────────────────────
|
||||||
|
# Porta exposta na máquina host para o container da app
|
||||||
|
APP_PORT=8080
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
# env
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Go
|
||||||
|
apps/api/vendor/
|
||||||
|
apps/api/static/dist/*
|
||||||
|
!apps/api/static/dist/.gitkeep
|
||||||
|
|
||||||
|
# Node
|
||||||
|
apps/web/node_modules/
|
||||||
|
apps/web/dist/
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
*.log
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
# ─── Stage 1: Build Vue frontend ───────────────────────────────────────────────
|
||||||
|
FROM node:22-alpine AS web-builder
|
||||||
|
WORKDIR /web
|
||||||
|
COPY apps/web/package*.json ./
|
||||||
|
RUN npm ci
|
||||||
|
COPY apps/web/ .
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# ─── Stage 2: Build Go binary (with embedded Vue assets) ────────────────────────
|
||||||
|
FROM golang:1.23-alpine AS api-builder
|
||||||
|
WORKDIR /api
|
||||||
|
COPY apps/api/go.mod ./
|
||||||
|
RUN go mod download
|
||||||
|
COPY apps/api/ .
|
||||||
|
# Inject the compiled frontend so //go:embed picks it up
|
||||||
|
COPY --from=web-builder /web/dist ./static/dist
|
||||||
|
RUN go mod tidy && CGO_ENABLED=0 GOOS=linux go build -o /bin/server ./cmd/server
|
||||||
|
|
||||||
|
# ─── Stage 3: Minimal runtime image ─────────────────────────────────────────────
|
||||||
|
FROM alpine:3.20
|
||||||
|
RUN apk add --no-cache ca-certificates tzdata
|
||||||
|
COPY --from=api-builder /bin/server /bin/server
|
||||||
|
EXPOSE 8080
|
||||||
|
ENTRYPOINT ["/bin/server"]
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
|
||||||
|
"financeiro-carvalho/internal/db"
|
||||||
|
"financeiro-carvalho/internal/handler"
|
||||||
|
"financeiro-carvalho/internal/migration"
|
||||||
|
"financeiro-carvalho/static"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
_ = godotenv.Load()
|
||||||
|
|
||||||
|
dsn := os.Getenv("DATABASE_URL")
|
||||||
|
if dsn == "" {
|
||||||
|
log.Fatal("DATABASE_URL is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
pool, err := db.Connect(ctx, dsn)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("db connect: %v", err)
|
||||||
|
}
|
||||||
|
defer pool.Close()
|
||||||
|
|
||||||
|
if err := migration.Run(ctx, pool); err != nil {
|
||||||
|
log.Fatalf("migration: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
port := os.Getenv("PORT")
|
||||||
|
if port == "" {
|
||||||
|
port = "8080"
|
||||||
|
}
|
||||||
|
|
||||||
|
r := chi.NewRouter()
|
||||||
|
r.Use(middleware.Logger)
|
||||||
|
r.Use(middleware.Recoverer)
|
||||||
|
|
||||||
|
r.Get("/health", handler.Health)
|
||||||
|
|
||||||
|
r.Route("/api", func(r chi.Router) {
|
||||||
|
// API routes added here as features are built
|
||||||
|
})
|
||||||
|
|
||||||
|
// Serve Vue SPA — non-API routes fall through to index.html
|
||||||
|
distFS, err := fs.Sub(static.FS, "dist")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("static embed sub: %v", err)
|
||||||
|
}
|
||||||
|
fileServer := http.FileServer(http.FS(distFS))
|
||||||
|
r.Get("/*", func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
path := req.URL.Path[1:]
|
||||||
|
if _, statErr := fs.Stat(distFS, path); statErr != nil {
|
||||||
|
index, readErr := fs.ReadFile(distFS, "index.html")
|
||||||
|
if readErr != nil {
|
||||||
|
http.NotFound(w, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
w.Write(index)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fileServer.ServeHTTP(w, req)
|
||||||
|
})
|
||||||
|
|
||||||
|
fmt.Printf("server listening on :%s\n", port)
|
||||||
|
if err := http.ListenAndServe(":"+port, r); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
module financeiro-carvalho
|
||||||
|
|
||||||
|
go 1.23
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/go-chi/chi/v5 v5.2.1
|
||||||
|
github.com/jackc/pgx/v5 v5.7.2
|
||||||
|
github.com/joho/godotenv v1.5.1
|
||||||
|
)
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||||
|
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
|
github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI=
|
||||||
|
github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||||
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
|
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||||
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Connect(ctx context.Context, dsn string) (*pgxpool.Pool, error) {
|
||||||
|
var pool *pgxpool.Pool
|
||||||
|
var err error
|
||||||
|
|
||||||
|
for i := range 10 {
|
||||||
|
pool, err = pgxpool.New(ctx, dsn)
|
||||||
|
if err == nil {
|
||||||
|
if pingErr := pool.Ping(ctx); pingErr == nil {
|
||||||
|
return pool, nil
|
||||||
|
}
|
||||||
|
pool.Close()
|
||||||
|
}
|
||||||
|
wait := time.Duration(i+1) * 500 * time.Millisecond
|
||||||
|
fmt.Printf("db not ready, retrying in %s...\n", wait)
|
||||||
|
time.Sleep(wait)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("could not connect to database after retries: %w", err)
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Health(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package migration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed sql/001_initial.sql
|
||||||
|
var initial string
|
||||||
|
|
||||||
|
func Run(ctx context.Context, pool *pgxpool.Pool) error {
|
||||||
|
if _, err := pool.Exec(ctx, initial); err != nil {
|
||||||
|
return fmt.Errorf("001_initial: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS schema_migrations (
|
||||||
|
version BIGINT PRIMARY KEY,
|
||||||
|
applied_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS categories (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
color VARCHAR(7) NOT NULL DEFAULT '#6B7280',
|
||||||
|
is_default BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS transactions (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
date DATE NOT NULL,
|
||||||
|
amount NUMERIC(12, 2) NOT NULL,
|
||||||
|
description TEXT NOT NULL,
|
||||||
|
type VARCHAR(10) NOT NULL CHECK (type IN ('income', 'expense')),
|
||||||
|
source VARCHAR(10) NOT NULL DEFAULT 'manual' CHECK (source IN ('manual', 'import')),
|
||||||
|
category_id INTEGER REFERENCES categories (id) ON DELETE SET NULL,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS recurring_expenses (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
expected_amount NUMERIC(12, 2) NOT NULL,
|
||||||
|
day_of_month INTEGER NOT NULL CHECK (day_of_month BETWEEN 1 AND 31),
|
||||||
|
category_id INTEGER REFERENCES categories (id) ON DELETE SET NULL,
|
||||||
|
active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS import_logs (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
filename VARCHAR(255) NOT NULL,
|
||||||
|
format VARCHAR(10) NOT NULL CHECK (format IN ('ofx', 'csv')),
|
||||||
|
imported_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
duplicate_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
error_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO categories (name, color, is_default)
|
||||||
|
VALUES
|
||||||
|
('Saúde / Insulina', '#EF4444', TRUE),
|
||||||
|
('Veleiro', '#3B82F6', TRUE),
|
||||||
|
('Jogos de Tabuleiro', '#8B5CF6', TRUE),
|
||||||
|
('Alimentação', '#F59E0B', TRUE),
|
||||||
|
('Lazer', '#10B981', TRUE),
|
||||||
|
('Freelance', '#6366F1', TRUE),
|
||||||
|
('Outros', '#6B7280', TRUE)
|
||||||
|
ON CONFLICT DO NOTHING;
|
||||||
|
|
||||||
|
INSERT INTO schema_migrations (version) VALUES (1) ON CONFLICT DO NOTHING;
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS schema_migrations (
|
||||||
|
version BIGINT PRIMARY KEY,
|
||||||
|
applied_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS categories (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
color VARCHAR(7) NOT NULL DEFAULT '#6B7280',
|
||||||
|
is_default BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS transactions (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
date DATE NOT NULL,
|
||||||
|
amount NUMERIC(12, 2) NOT NULL,
|
||||||
|
description TEXT NOT NULL,
|
||||||
|
type VARCHAR(10) NOT NULL CHECK (type IN ('income', 'expense')),
|
||||||
|
source VARCHAR(10) NOT NULL DEFAULT 'manual' CHECK (source IN ('manual', 'import')),
|
||||||
|
category_id INTEGER REFERENCES categories (id) ON DELETE SET NULL,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS recurring_expenses (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
expected_amount NUMERIC(12, 2) NOT NULL,
|
||||||
|
day_of_month INTEGER NOT NULL CHECK (day_of_month BETWEEN 1 AND 31),
|
||||||
|
category_id INTEGER REFERENCES categories (id) ON DELETE SET NULL,
|
||||||
|
active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS import_logs (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
filename VARCHAR(255) NOT NULL,
|
||||||
|
format VARCHAR(10) NOT NULL CHECK (format IN ('ofx', 'csv')),
|
||||||
|
imported_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
duplicate_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
error_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO categories (name, color, is_default)
|
||||||
|
VALUES
|
||||||
|
('Saúde / Insulina', '#EF4444', TRUE),
|
||||||
|
('Veleiro', '#3B82F6', TRUE),
|
||||||
|
('Jogos de Tabuleiro', '#8B5CF6', TRUE),
|
||||||
|
('Alimentação', '#F59E0B', TRUE),
|
||||||
|
('Lazer', '#10B981', TRUE),
|
||||||
|
('Freelance', '#6366F1', TRUE),
|
||||||
|
('Outros', '#6B7280', TRUE)
|
||||||
|
ON CONFLICT DO NOTHING;
|
||||||
|
|
||||||
|
INSERT INTO schema_migrations (version) VALUES (1) ON CONFLICT DO NOTHING;
|
||||||
Vendored
@@ -0,0 +1,9 @@
|
|||||||
|
package static
|
||||||
|
|
||||||
|
import "embed"
|
||||||
|
|
||||||
|
// FS holds the compiled Vue frontend. Populated by the Docker build:
|
||||||
|
// the web-builder stage outputs to apps/api/static/dist before go build runs.
|
||||||
|
//
|
||||||
|
//go:embed dist
|
||||||
|
var FS embed.FS
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="pt-BR">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Financeiro Carvalho</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Generated
+1610
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "financeiro-carvalho-web",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vue-tsc && vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"pinia": "^2.3.1",
|
||||||
|
"vue": "^3.5.13",
|
||||||
|
"vue-router": "^4.5.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
|
"typescript": "^5.7.2",
|
||||||
|
"vite": "^6.3.5",
|
||||||
|
"vue-tsc": "^2.2.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<template>
|
||||||
|
<RouterView />
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { createApp } from 'vue'
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
|
||||||
|
import App from './App.vue'
|
||||||
|
import router from './router'
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
app.use(createPinia())
|
||||||
|
app.use(router)
|
||||||
|
app.mount('#app')
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
import HomeView from '../views/HomeView.vue'
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'home',
|
||||||
|
component: HomeView,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<template>
|
||||||
|
<main style="font-family: sans-serif; padding: 2rem; text-align: center">
|
||||||
|
<h1>Financeiro Carvalho</h1>
|
||||||
|
<p>Sistema de controle financeiro pessoal</p>
|
||||||
|
</main>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"jsxImportSource": "vue",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src/**/*", "src/**/*.vue"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.node.json" },
|
||||||
|
{ "path": "./tsconfig.app.json" }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
"target": "ES2022",
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://localhost:8080',
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: financeiro
|
||||||
|
POSTGRES_USER: financeiro
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-financeiro}
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U financeiro"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
|
||||||
|
app:
|
||||||
|
build: .
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "${APP_PORT:-8080}:8080"
|
||||||
|
environment:
|
||||||
|
DATABASE_URL: postgres://financeiro:${POSTGRES_PASSWORD:-financeiro}@postgres:5432/financeiro?sslmode=disable
|
||||||
|
PORT: 8080
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
Reference in New Issue
Block a user