Merge branch 'main' of github.com:rede5/gohorsejobs into dev
This commit is contained in:
commit
2a66e2888a
8 changed files with 203 additions and 76 deletions
|
|
@ -67,6 +67,25 @@ jobs:
|
||||||
|
|
||||||
# Injeta variáveis (Lembre-se de mudar DATABASE_URL para sslmode=disable no Forgejo!)
|
# Injeta variáveis (Lembre-se de mudar DATABASE_URL para sslmode=disable no Forgejo!)
|
||||||
kubectl delete secret backend-secrets -n gohorsejobsdev --ignore-not-found
|
kubectl delete secret backend-secrets -n gohorsejobsdev --ignore-not-found
|
||||||
|
|
||||||
|
# Prepare RSA key file if available (prefer secrets over vars)
|
||||||
|
if [ -n "${{ secrets.RSA_PRIVATE_KEY_BASE64 }}" ]; then
|
||||||
|
echo "Decoding RSA_PRIVATE_KEY_BASE64 from secrets"
|
||||||
|
printf '%b' "${{ secrets.RSA_PRIVATE_KEY_BASE64 }}" > /tmp/rsa_key.pem || true
|
||||||
|
# if it's base64-encoded PEM, decode it
|
||||||
|
if base64 -d /tmp/rsa_key.pem >/dev/null 2>&1; then
|
||||||
|
base64 -d /tmp/rsa_key.pem > /tmp/rsa_key_decoded.pem && mv /tmp/rsa_key_decoded.pem /tmp/rsa_key.pem || true
|
||||||
|
fi
|
||||||
|
elif [ -n "${{ vars.RSA_PRIVATE_KEY_BASE64 }}" ]; then
|
||||||
|
echo "Decoding RSA_PRIVATE_KEY_BASE64 from vars"
|
||||||
|
printf '%b' "${{ vars.RSA_PRIVATE_KEY_BASE64 }}" > /tmp/rsa_key.pem || true
|
||||||
|
if base64 -d /tmp/rsa_key.pem >/dev/null 2>&1; then
|
||||||
|
base64 -d /tmp/rsa_key.pem > /tmp/rsa_key_decoded.pem && mv /tmp/rsa_key_decoded.pem /tmp/rsa_key.pem || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create secret: if rsa file exists, create secret from file (robust); otherwise fallback to from-literal
|
||||||
|
if [ -f /tmp/rsa_key.pem ]; then
|
||||||
kubectl create secret generic backend-secrets -n gohorsejobsdev \
|
kubectl create secret generic backend-secrets -n gohorsejobsdev \
|
||||||
--from-literal=MTU="${{ vars.MTU }}" \
|
--from-literal=MTU="${{ vars.MTU }}" \
|
||||||
--from-literal=DATABASE_URL="${{ vars.DATABASE_URL }}" \
|
--from-literal=DATABASE_URL="${{ vars.DATABASE_URL }}" \
|
||||||
|
|
@ -84,7 +103,31 @@ jobs:
|
||||||
--from-literal=AWS_REGION="${{ vars.AWS_REGION }}" \
|
--from-literal=AWS_REGION="${{ vars.AWS_REGION }}" \
|
||||||
--from-literal=AWS_ENDPOINT="${{ vars.AWS_ENDPOINT }}" \
|
--from-literal=AWS_ENDPOINT="${{ vars.AWS_ENDPOINT }}" \
|
||||||
--from-literal=AWS_ACCESS_KEY_ID="${{ vars.AWS_ACCESS_KEY_ID }}" \
|
--from-literal=AWS_ACCESS_KEY_ID="${{ vars.AWS_ACCESS_KEY_ID }}" \
|
||||||
--from-literal=AWS_SECRET_ACCESS_KEY="${{ vars.AWS_SECRET_ACCESS_KEY }}"
|
--from-literal=AWS_SECRET_ACCESS_KEY="${{ vars.AWS_SECRET_ACCESS_KEY }}" \
|
||||||
|
--from-file=private_key.pem=/tmp/rsa_key.pem \
|
||||||
|
--dry-run=client -o yaml | kubectl apply -f -
|
||||||
|
else
|
||||||
|
kubectl create secret generic backend-secrets -n gohorsejobsdev \
|
||||||
|
--from-literal=MTU="${{ vars.MTU }}" \
|
||||||
|
--from-literal=DATABASE_URL="${{ vars.DATABASE_URL }}" \
|
||||||
|
--from-literal=AMQP_URL="${{ vars.AMQP_URL }}" \
|
||||||
|
--from-literal=JWT_SECRET="${{ vars.JWT_SECRET }}" \
|
||||||
|
--from-literal=JWT_EXPIRATION="${{ vars.JWT_EXPIRATION }}" \
|
||||||
|
--from-literal=PASSWORD_PEPPER="${{ vars.PASSWORD_PEPPER }}" \
|
||||||
|
--from-literal=COOKIE_SECRET="${{ vars.COOKIE_SECRET }}" \
|
||||||
|
--from-literal=COOKIE_DOMAIN="${{ vars.COOKIE_DOMAIN }}" \
|
||||||
|
--from-literal=BACKEND_PORT="${{ vars.BACKEND_PORT }}" \
|
||||||
|
--from-literal=BACKEND_HOST="${{ vars.BACKEND_HOST }}" \
|
||||||
|
--from-literal=ENV="${{ vars.ENV }}" \
|
||||||
|
--from-literal=CORS_ORIGINS="${{ vars.CORS_ORIGINS }}" \
|
||||||
|
--from-literal=S3_BUCKET="${{ vars.S3_BUCKET }}" \
|
||||||
|
--from-literal=AWS_REGION="${{ vars.AWS_REGION }}" \
|
||||||
|
--from-literal=AWS_ENDPOINT="${{ vars.AWS_ENDPOINT }}" \
|
||||||
|
--from-literal=AWS_ACCESS_KEY_ID="${{ vars.AWS_ACCESS_KEY_ID }}" \
|
||||||
|
--from-literal=AWS_SECRET_ACCESS_KEY="${{ vars.AWS_SECRET_ACCESS_KEY }}" \
|
||||||
|
--from-literal=RSA_PRIVATE_KEY_BASE64="${{ vars.RSA_PRIVATE_KEY_BASE64 }}" \
|
||||||
|
--dry-run=client -o yaml | kubectl apply -f -
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Deploy to K3s
|
- name: Deploy to K3s
|
||||||
run: |
|
run: |
|
||||||
|
|
|
||||||
51
.github/workflows/migrate.yml
vendored
Normal file
51
.github/workflows/migrate.yml
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
name: Validate RSA and Run Migrations
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ dev ]
|
||||||
|
workflow_dispatch: {}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
migrate:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: '1.20'
|
||||||
|
|
||||||
|
- name: Validate RSA_PRIVATE_KEY_BASE64 secret
|
||||||
|
env:
|
||||||
|
RSA_B64: ${{ secrets.RSA_PRIVATE_KEY_BASE64 }}
|
||||||
|
run: |
|
||||||
|
if [ -z "$RSA_B64" ]; then
|
||||||
|
echo "RSA_PRIVATE_KEY_BASE64 secret is missing. Add it in repository secrets." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "$RSA_B64" > rsa_base64.txt
|
||||||
|
if ! base64 -d rsa_base64.txt > /tmp/key.pem 2>/dev/null; then
|
||||||
|
# try convert literal \n
|
||||||
|
printf '%b' "$RSA_B64" > /tmp/key.pem || true
|
||||||
|
fi
|
||||||
|
if ! openssl pkey -in /tmp/key.pem -noout -text >/dev/null 2>&1; then
|
||||||
|
echo "RSA private key is invalid" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Validate DATABASE_URL secret
|
||||||
|
if: ${{ always() }}
|
||||||
|
run: |
|
||||||
|
if [ -z "${{ secrets.DATABASE_URL }}" ]; then
|
||||||
|
echo "DATABASE_URL secret is missing. Set up your DB connection string in secrets." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Run migrations
|
||||||
|
env:
|
||||||
|
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||||
|
run: |
|
||||||
|
cd backend
|
||||||
|
go run ./cmd/manual_migrate
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
|
|
@ -33,60 +34,69 @@ func main() {
|
||||||
}
|
}
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
// List of migrations to run (in order)
|
// Discover migrations directory from several probable locations
|
||||||
migrations := []string{
|
possibleDirs := []string{
|
||||||
"024_create_external_services_credentials.sql",
|
"migrations",
|
||||||
"025_create_chat_tables.sql",
|
"backend/migrations",
|
||||||
"026_create_system_settings.sql",
|
"../migrations",
|
||||||
"027_create_email_system.sql",
|
"/home/yamamoto/lab/gohorsejobs/backend/migrations",
|
||||||
"028_add_avatar_url_to_users.sql",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, migFile := range migrations {
|
var migrationsDir string
|
||||||
log.Printf("Processing migration: %s", migFile)
|
for _, d := range possibleDirs {
|
||||||
|
if fi, err := os.Stat(d); err == nil && fi.IsDir() {
|
||||||
// Try multiple paths
|
migrationsDir = d
|
||||||
paths := []string{
|
|
||||||
"migrations/" + migFile,
|
|
||||||
"backend/migrations/" + migFile,
|
|
||||||
"../migrations/" + migFile,
|
|
||||||
"/home/yamamoto/lab/gohorsejobs/backend/migrations/" + migFile,
|
|
||||||
}
|
|
||||||
|
|
||||||
var content []byte
|
|
||||||
var readErr error
|
|
||||||
|
|
||||||
for _, p := range paths {
|
|
||||||
content, readErr = os.ReadFile(p)
|
|
||||||
if readErr == nil {
|
|
||||||
log.Printf("Found migration at: %s", p)
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if content == nil {
|
if migrationsDir == "" {
|
||||||
log.Fatalf("Could not find migration file %s. Last error: %v", migFile, readErr)
|
log.Fatal("Could not find migrations directory; looked in common locations")
|
||||||
}
|
}
|
||||||
|
|
||||||
statements := strings.Split(string(content), ";")
|
entries, err := os.ReadDir(migrationsDir)
|
||||||
for _, stmt := range statements {
|
if err != nil {
|
||||||
trimmed := strings.TrimSpace(stmt)
|
log.Fatalf("Failed reading migrations dir %s: %v", migrationsDir, err)
|
||||||
if trimmed == "" {
|
}
|
||||||
|
|
||||||
|
var files []string
|
||||||
|
for _, e := range entries {
|
||||||
|
if e.IsDir() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
log.Printf("Executing: %s", trimmed)
|
name := e.Name()
|
||||||
_, err = db.Exec(trimmed)
|
if strings.HasSuffix(name, ".sql") {
|
||||||
|
files = append(files, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort filenames to ensure chronological order
|
||||||
|
sort.Strings(files)
|
||||||
|
|
||||||
|
for _, migFile := range files {
|
||||||
|
fullPath := migrationsDir + "/" + migFile
|
||||||
|
log.Printf("Processing migration: %s (from %s)", migFile, fullPath)
|
||||||
|
|
||||||
|
content, err := os.ReadFile(fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "already exists") {
|
log.Fatalf("Could not read migration %s: %v", fullPath, err)
|
||||||
log.Printf("Warning (ignored): %v", err)
|
|
||||||
} else {
|
|
||||||
log.Printf("FAILED executing: %s\nError: %v", trimmed, err)
|
|
||||||
// Verify if we should stop. For now, continue best effort or fail?
|
|
||||||
// Use fatal for critical schema errors not "already exists"
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Execute the whole file. lib/pq supports multi-statement Exec.
|
||||||
|
sqlText := string(content)
|
||||||
|
log.Printf("Executing migration file %s", fullPath)
|
||||||
|
_, err = db.Exec(sqlText)
|
||||||
|
if err != nil {
|
||||||
|
errStr := err.Error()
|
||||||
|
// Tolerable errors: object already exists, column doesn't exist in some contexts,
|
||||||
|
// or duplicate key when updates are guarded by intent. Log and continue.
|
||||||
|
if strings.Contains(errStr, "already exists") || strings.Contains(errStr, "column") && strings.Contains(errStr, "does not exist") || strings.Contains(errStr, "duplicate key value violates unique constraint") {
|
||||||
|
log.Printf("Warning while applying %s: %v", migFile, err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
log.Fatalf("Failed applying migration %s: %v", migFile, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Migration %s applied successfully", migFile)
|
log.Printf("Migration %s applied successfully", migFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,17 +11,29 @@ CREATE TABLE IF NOT EXISTS regions (
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS cities (
|
CREATE TABLE IF NOT EXISTS cities (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
region_id INT NOT NULL,
|
region_id INT,
|
||||||
name VARCHAR(100) NOT NULL,
|
name VARCHAR(100) NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
|
|
||||||
FOREIGN KEY (region_id) REFERENCES regions(id) ON DELETE CASCADE
|
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Indexes
|
-- Ensure column and constraints exist when table already existed without them
|
||||||
CREATE INDEX idx_regions_country ON regions(country_code);
|
ALTER TABLE cities
|
||||||
CREATE INDEX idx_cities_region ON cities(region_id);
|
ADD COLUMN IF NOT EXISTS region_id INT;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.table_constraints tc
|
||||||
|
JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name
|
||||||
|
WHERE tc.table_name = 'cities' AND tc.constraint_type = 'FOREIGN KEY' AND kcu.column_name = 'region_id'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE cities ADD CONSTRAINT fk_cities_region FOREIGN KEY (region_id) REFERENCES regions(id) ON DELETE CASCADE;
|
||||||
|
END IF;
|
||||||
|
END$$;
|
||||||
|
|
||||||
|
-- Indexes (safe if table/column already existed)
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_regions_country ON regions(country_code);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_cities_region ON cities(region_id);
|
||||||
|
|
||||||
-- Comments
|
-- Comments
|
||||||
COMMENT ON TABLE regions IS 'Global Regions (States, Provinces, Prefectures)';
|
COMMENT ON TABLE regions IS 'Global Regions (States, Provinces, Prefectures)';
|
||||||
|
|
|
||||||
5
backend/start_dev.sh
Executable file
5
backend/start_dev.sh
Executable file
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/bin/sh
|
||||||
|
export GOPATH=/go
|
||||||
|
export PATH=/go/bin:/usr/local/go/bin:$PATH
|
||||||
|
go install github.com/air-verse/air@v1.60.0
|
||||||
|
air
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#:schema node_modules/wrangler/config-schema.json
|
#:schema node_modules/wrangler/config-schema.json
|
||||||
name = "gohorsejobs-frontend"
|
name = "gohorsejobs-frontend"
|
||||||
compatibility_date = "2024-09-23"
|
compatibility_date = "2024-09-23"
|
||||||
compatibility_flags = ["nodejs_als"]
|
compatibility_flags = ["nodejs_compat"]
|
||||||
pages_build_output_dir = ".vercel/output/static"
|
pages_build_output_dir = ".vercel/output/static"
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,10 @@ spec:
|
||||||
terminationGracePeriodSeconds: 10
|
terminationGracePeriodSeconds: 10
|
||||||
imagePullSecrets:
|
imagePullSecrets:
|
||||||
- name: forgejo-registry-secret
|
- name: forgejo-registry-secret
|
||||||
|
dnsConfig:
|
||||||
|
options:
|
||||||
|
- name: ndots
|
||||||
|
value: "1"
|
||||||
containers:
|
containers:
|
||||||
- name: backend
|
- name: backend
|
||||||
image: pipe.gohorsejobs.com/bohessefm/gohorsejobs:latest
|
image: pipe.gohorsejobs.com/bohessefm/gohorsejobs:latest
|
||||||
|
|
@ -43,7 +47,6 @@ spec:
|
||||||
cpu: "800m" # Prioridade de CPU garantida
|
cpu: "800m" # Prioridade de CPU garantida
|
||||||
limits:
|
limits:
|
||||||
memory: "1024Mi"
|
memory: "1024Mi"
|
||||||
cpu: "1000m"
|
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /health
|
path: /health
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,10 @@ spec:
|
||||||
terminationGracePeriodSeconds: 10
|
terminationGracePeriodSeconds: 10
|
||||||
imagePullSecrets:
|
imagePullSecrets:
|
||||||
- name: forgejo-registry-secret
|
- name: forgejo-registry-secret
|
||||||
|
dnsConfig:
|
||||||
|
options:
|
||||||
|
- name: ndots
|
||||||
|
value: "1"
|
||||||
containers:
|
containers:
|
||||||
- name: backoffice
|
- name: backoffice
|
||||||
image: pipe.gohorsejobs.com/bohessefm/backoffice:latest
|
image: pipe.gohorsejobs.com/bohessefm/backoffice:latest
|
||||||
|
|
@ -44,7 +48,6 @@ spec:
|
||||||
cpu: "500m"
|
cpu: "500m"
|
||||||
limits:
|
limits:
|
||||||
memory: "2Gi"
|
memory: "2Gi"
|
||||||
cpu: "1000m"
|
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /health
|
path: /health
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue