Skip to content

Commit 3d69d40

Browse files
committed
feat: upsert seed data, deploy script, GitHub Actions CI/CD
- Seed uses INSERT ON CONFLICT UPDATE (no DB wipe needed) - deploy.sh: one-command push+build+restart - GitHub Actions: auto-deploy on push to main via self-hosted runner
1 parent bce63e1 commit 3d69d40

3 files changed

Lines changed: 95 additions & 22 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Deploy SnapKit
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths-ignore: ['docs/**', '*.md']
7+
8+
jobs:
9+
deploy:
10+
runs-on: self-hosted
11+
steps:
12+
- name: Pull latest code
13+
run: cd /opt/snapkit && git pull origin main
14+
15+
- name: Build Docker image
16+
run: cd /opt/snapkit && docker build -t snapkit:latest .
17+
18+
- name: Restart container
19+
run: |
20+
docker rm -f snapkit 2>/dev/null || true
21+
docker run -d --name snapkit \
22+
--network dokploy-network \
23+
-v /opt/snapkit/brands:/app/brands \
24+
-v /opt/snapkit-data:/app/data \
25+
--restart unless-stopped \
26+
--label 'traefik.enable=true' \
27+
--label 'traefik.http.routers.snapkit.rule=Host(`snapkit.vibery.app`)' \
28+
--label 'traefik.http.routers.snapkit.entrypoints=websecure' \
29+
--label 'traefik.http.routers.snapkit.tls.certresolver=letsencrypt' \
30+
--label 'traefik.http.services.snapkit.loadbalancer.server.port=8080' \
31+
snapkit:latest
32+
33+
- name: Verify deployment
34+
run: |
35+
sleep 5
36+
STATUS=$(curl -sk -o /dev/null -w "%{http_code}" https://snapkit.vibery.app/api/sizes)
37+
if [ "$STATUS" != "200" ]; then
38+
echo "Deploy verification failed (status: $STATUS)"
39+
docker logs snapkit 2>&1 | tail -20
40+
exit 1
41+
fi
42+
echo "Deploy OK"

deploy.sh

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#!/bin/bash
2+
# Deploy SnapKit to VPS (51.79.176.217)
3+
# Usage: ./deploy.sh
4+
5+
set -e
6+
7+
SERVER="ubuntu@51.79.176.217"
8+
APP_DIR="/opt/snapkit"
9+
10+
echo "=== Pushing to GitHub ==="
11+
git push origin main
12+
13+
echo "=== Building on server ==="
14+
ssh -o ServerAliveInterval=5 $SERVER "cd $APP_DIR && git pull && docker build -t snapkit:latest ."
15+
16+
echo "=== Restarting container ==="
17+
ssh -o ServerAliveInterval=5 $SERVER "docker rm -f snapkit 2>/dev/null; docker run -d --name snapkit \
18+
--network dokploy-network \
19+
-v $APP_DIR/brands:/app/brands \
20+
-v /opt/snapkit-data:/app/data \
21+
--restart unless-stopped \
22+
--label 'traefik.enable=true' \
23+
--label 'traefik.http.routers.snapkit.rule=Host(\`snapkit.vibery.app\`)' \
24+
--label 'traefik.http.routers.snapkit.entrypoints=websecure' \
25+
--label 'traefik.http.routers.snapkit.tls.certresolver=letsencrypt' \
26+
--label 'traefik.http.services.snapkit.loadbalancer.server.port=8080' \
27+
snapkit:latest"
28+
29+
echo "=== Verifying ==="
30+
sleep 3
31+
STATUS=$(curl -sk -o /dev/null -w "%{http_code}" https://snapkit.vibery.app/api/sizes)
32+
if [ "$STATUS" = "200" ]; then
33+
echo "Deploy OK — https://snapkit.vibery.app"
34+
else
35+
echo "Deploy FAILED (status: $STATUS)"
36+
ssh $SERVER "docker logs snapkit 2>&1 | tail -10"
37+
exit 1
38+
fi

server/handlers/seed.go

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,9 @@ import (
88
"snapkit/models"
99
)
1010

11-
// SeedBrands inserts default brands if none exist
11+
// SeedBrands upserts default brands and templates (safe to run repeatedly)
1212
func SeedBrands() {
13-
var count int
14-
db.DB.QueryRow("SELECT COUNT(*) FROM brands").Scan(&count)
15-
if count > 0 {
16-
return
17-
}
18-
log.Println("Seeding default brands...")
13+
log.Println("Syncing seed data...")
1914
now := time.Now().UTC().Format(time.RFC3339)
2015

2116
brands := []models.Brand{
@@ -50,8 +45,13 @@ func SeedBrands() {
5045

5146
for _, b := range brands {
5247
_, err := db.DB.Exec(
53-
`INSERT OR IGNORE INTO brands (id, name, slug, colors, fonts, logos, backgrounds, watermark, default_text_color, default_overlay, created_at, updated_at)
54-
VALUES (?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?)`,
48+
`INSERT INTO brands (id, name, slug, colors, fonts, logos, backgrounds, watermark, default_text_color, default_overlay, created_at, updated_at)
49+
VALUES (?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?)
50+
ON CONFLICT(id) DO UPDATE SET
51+
name=excluded.name, slug=excluded.slug, colors=excluded.colors, fonts=excluded.fonts,
52+
logos=excluded.logos, backgrounds=excluded.backgrounds,
53+
default_text_color=excluded.default_text_color, default_overlay=excluded.default_overlay,
54+
updated_at=excluded.updated_at`,
5555
b.ID, b.Name, b.Slug,
5656
models.MustJSON(b.Colors), models.MustJSON(b.Fonts),
5757
models.MustJSON(b.Logos), models.MustJSON(b.Backgrounds),
@@ -61,35 +61,28 @@ func SeedBrands() {
6161
log.Printf("Seed brand %s error: %v", b.ID, err)
6262
}
6363
}
64-
log.Printf("Seeded %d brands", len(brands))
65-
66-
// Seed default templates
67-
var tCount int
68-
db.DB.QueryRow("SELECT COUNT(*) FROM templates").Scan(&tCount)
69-
if tCount > 0 {
70-
return
71-
}
7264

7365
templates := []struct {
7466
ID, Name, Brand, Layout, Size string
7567
Params map[string]string
7668
}{
77-
// GOHA templates (migrated from D1)
7869
{"agency-default", "Agency Default", "goha", "agency-split", "agency-wide", map[string]string{"title": "Your Headline Here", "subtitle": "Supporting text goes here", "feature_image": "/brands/goha/bg/default-background.png"}},
7970
{"overlay-dark", "Dark Overlay", "goha", "overlay-center", "fb-post", map[string]string{"title": "This is dark title", "subtitle": "This is subtitle", "title_color": "#FFFFFF", "subtitle_color": "#CCCCCC", "overlay": "dark"}},
8071
{"goha-corporate", "GOHA Corporate", "goha", "split-left", "og-image", map[string]string{"title": "Corporate Solutions", "subtitle": "Strategic Growth Partner", "bg_color": "#0f1629", "title_color": "#FFD700", "subtitle_color": "#FFFFFF"}},
8172
{"goha-minimal", "GOHA Minimal", "goha", "text-only", "ig-post", map[string]string{"title": "Simple. Elegant. Effective.", "bg_color": "#1a1a3e", "title_color": "#FFFFFF", "subtitle_color": "#FFD700"}},
8273
{"goha-professional", "GOHA Professional", "goha", "overlay-center", "yt-thumbnail", map[string]string{"title": "Professional Excellence", "subtitle": "Building Trust Through Quality", "bg_color": "#1a1a3e", "title_color": "#FFD700", "subtitle_color": "#FFFFFF", "overlay": "dark", "bg_image": "/brands/goha/bg/default-background.png"}},
83-
84-
// Trung Nguyen templates (migrated from D1)
8574
{"trungnguyen-showcase", "Trung Nguyên Showcase", "trungnguyen", "brand-showcase", "ig-post", map[string]string{"title": "Robot Máy Ép Nhựa – Xu Hướng Tự Động Hóa Ngành Nhựa", "cta_text": "XEM NGAY", "footer_text": "0986 403 790 – 098 210 3223\ndinhduong@trungnguyentw.com\ntrungnguyentw.com", "accent_color": "#fc7400", "title_color": "#FFFFFF", "frame_image": "/brands/trungnguyen/frame_left.png", "feature_image": "/brands/trungnguyen/sample_cover_2.jpg"}},
8675
}
8776

8877
for _, t := range templates {
8978
db.DB.Exec(
90-
"INSERT OR IGNORE INTO templates (id, name, brand, layout, size, params, auto_feature_image, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?)",
79+
`INSERT INTO templates (id, name, brand, layout, size, params, auto_feature_image, created_at, updated_at)
80+
VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?)
81+
ON CONFLICT(id) DO UPDATE SET
82+
name=excluded.name, brand=excluded.brand, layout=excluded.layout, size=excluded.size,
83+
params=excluded.params, updated_at=excluded.updated_at`,
9184
t.ID, t.Name, t.Brand, t.Layout, t.Size, models.MustJSON(t.Params), now, now,
9285
)
9386
}
94-
log.Printf("Seeded %d templates", len(templates))
87+
log.Printf("Synced %d brands, %d templates", len(brands), len(templates))
9588
}

0 commit comments

Comments
 (0)