improovements
This commit is contained in:
parent
fa7f791873
commit
4205922617
6 changed files with 65 additions and 29 deletions
4
go.mod
4
go.mod
|
|
@ -1,8 +1,8 @@
|
||||||
module box
|
module box
|
||||||
|
|
||||||
go 1.23.0
|
go 1.26.0
|
||||||
|
|
||||||
toolchain go1.24.5
|
toolchain go1.26.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/jackc/pgx/v5 v5.7.6
|
github.com/jackc/pgx/v5 v5.7.6
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ func (a *App) migrate(ctx context.Context) error {
|
||||||
deploy_token TEXT NOT NULL DEFAULT '',
|
deploy_token TEXT NOT NULL DEFAULT '',
|
||||||
provision_state TEXT NOT NULL DEFAULT 'provisioned',
|
provision_state TEXT NOT NULL DEFAULT 'provisioned',
|
||||||
provision_error TEXT NOT NULL DEFAULT '',
|
provision_error TEXT NOT NULL DEFAULT '',
|
||||||
|
last_deployed_at TIMESTAMPTZ NULL,
|
||||||
|
last_deployed_commit TEXT NOT NULL DEFAULT '',
|
||||||
db_name TEXT NOT NULL DEFAULT '',
|
db_name TEXT NOT NULL DEFAULT '',
|
||||||
db_user TEXT NOT NULL DEFAULT '',
|
db_user TEXT NOT NULL DEFAULT '',
|
||||||
db_password TEXT NOT NULL DEFAULT '',
|
db_password TEXT NOT NULL DEFAULT '',
|
||||||
|
|
@ -52,6 +54,8 @@ func (a *App) migrate(ctx context.Context) error {
|
||||||
`ALTER TABLE projects ADD COLUMN IF NOT EXISTS repo_private BOOLEAN NOT NULL DEFAULT true;`,
|
`ALTER TABLE projects ADD COLUMN IF NOT EXISTS repo_private BOOLEAN NOT NULL DEFAULT true;`,
|
||||||
`ALTER TABLE projects ADD COLUMN IF NOT EXISTS provision_state TEXT NOT NULL DEFAULT 'provisioned';`,
|
`ALTER TABLE projects ADD COLUMN IF NOT EXISTS provision_state TEXT NOT NULL DEFAULT 'provisioned';`,
|
||||||
`ALTER TABLE projects ADD COLUMN IF NOT EXISTS provision_error TEXT NOT NULL DEFAULT '';`,
|
`ALTER TABLE projects ADD COLUMN IF NOT EXISTS provision_error TEXT NOT NULL DEFAULT '';`,
|
||||||
|
`ALTER TABLE projects ADD COLUMN IF NOT EXISTS last_deployed_at TIMESTAMPTZ NULL;`,
|
||||||
|
`ALTER TABLE projects ADD COLUMN IF NOT EXISTS last_deployed_commit TEXT NOT NULL DEFAULT '';`,
|
||||||
`ALTER TABLE projects ADD COLUMN IF NOT EXISTS db_name TEXT NOT NULL DEFAULT '';`,
|
`ALTER TABLE projects ADD COLUMN IF NOT EXISTS db_name TEXT NOT NULL DEFAULT '';`,
|
||||||
`ALTER TABLE projects ADD COLUMN IF NOT EXISTS db_user TEXT NOT NULL DEFAULT '';`,
|
`ALTER TABLE projects ADD COLUMN IF NOT EXISTS db_user TEXT NOT NULL DEFAULT '';`,
|
||||||
`ALTER TABLE projects ADD COLUMN IF NOT EXISTS db_password TEXT NOT NULL DEFAULT '';`,
|
`ALTER TABLE projects ADD COLUMN IF NOT EXISTS db_password TEXT NOT NULL DEFAULT '';`,
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *App) handleDeployWebhook(w http.ResponseWriter, r *http.Request) {
|
func (a *App) handleDeployWebhook(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
@ -125,9 +126,21 @@ func (a *App) buildProjectBinaryFromRepoDir(p Project, repoDir string) error {
|
||||||
if err := a.syncStaticAssetsForProject(p); err != nil {
|
if err := a.syncStaticAssetsForProject(p); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
commit, err := runCmdOut("git", "-C", repoDir, "rev-parse", "HEAD")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("resolve latest commit failed: %w", err)
|
||||||
|
}
|
||||||
|
if err := a.updateLastDeployment(p.ID, strings.TrimSpace(commit), time.Now().UTC()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) updateLastDeployment(projectID int64, commit string, deployedAt time.Time) error {
|
||||||
|
_, err := a.db.Exec(`UPDATE projects SET last_deployed_at=$1, last_deployed_commit=$2 WHERE id=$3`, deployedAt, strings.TrimSpace(commit), projectID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) syncProjectRepoFromMain(p Project, projectDir string) error {
|
func (a *App) syncProjectRepoFromMain(p Project, projectDir string) error {
|
||||||
repoDir := filepath.Join(projectDir, "repo")
|
repoDir := filepath.Join(projectDir, "repo")
|
||||||
plainRepoURL := strings.TrimSpace(p.RepoURL)
|
plainRepoURL := strings.TrimSpace(p.RepoURL)
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *App) listProjects(ctx context.Context, userID string) ([]Project, error) {
|
func (a *App) listProjects(ctx context.Context, userID string) ([]Project, error) {
|
||||||
rows, err := a.db.QueryContext(ctx, `SELECT p.id, p.user_id, p.name, p.slug, p.description, p.repo_url, p.repo_private, p.service_name, p.route_host, p.workspace_id, w.name, w.unix_user, p.deploy_token, p.provision_state, p.provision_error, p.db_name, p.db_user, p.db_password, p.created_at
|
rows, err := a.db.QueryContext(ctx, `SELECT p.id, p.user_id, p.name, p.slug, p.description, p.repo_url, p.repo_private, p.service_name, p.route_host, p.workspace_id, w.name, w.unix_user, p.deploy_token, p.provision_state, p.provision_error, p.last_deployed_at, p.last_deployed_commit, p.db_name, p.db_user, p.db_password, p.created_at
|
||||||
FROM projects p
|
FROM projects p
|
||||||
JOIN workspaces w ON w.id = p.workspace_id
|
JOIN workspaces w ON w.id = p.workspace_id
|
||||||
WHERE p.user_id=$1 ORDER BY p.created_at DESC`, userID)
|
WHERE p.user_id=$1 ORDER BY p.created_at DESC`, userID)
|
||||||
|
|
@ -20,9 +20,14 @@ func (a *App) listProjects(ctx context.Context, userID string) ([]Project, error
|
||||||
var out []Project
|
var out []Project
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var p Project
|
var p Project
|
||||||
if err := rows.Scan(&p.ID, &p.UserID, &p.Name, &p.Slug, &p.Description, &p.RepoURL, &p.RepoPrivate, &p.ServiceName, &p.RouteHost, &p.WorkspaceID, &p.Workspace, &p.UnixUser, &p.DeployToken, &p.ProvisionState, &p.ProvisionError, &p.DBName, &p.DBUser, &p.DBPassword, &p.CreatedAt); err != nil {
|
var deployedAt sql.NullTime
|
||||||
|
if err := rows.Scan(&p.ID, &p.UserID, &p.Name, &p.Slug, &p.Description, &p.RepoURL, &p.RepoPrivate, &p.ServiceName, &p.RouteHost, &p.WorkspaceID, &p.Workspace, &p.UnixUser, &p.DeployToken, &p.ProvisionState, &p.ProvisionError, &deployedAt, &p.LastDeployedCommit, &p.DBName, &p.DBUser, &p.DBPassword, &p.CreatedAt); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if deployedAt.Valid {
|
||||||
|
t := deployedAt.Time.UTC()
|
||||||
|
p.LastDeployedAt = &t
|
||||||
|
}
|
||||||
p.WebhookURL = a.deployWebhookURL(p.ID, p.DeployToken)
|
p.WebhookURL = a.deployWebhookURL(p.ID, p.DeployToken)
|
||||||
p.AppURL = a.appURL(p.RouteHost)
|
p.AppURL = a.appURL(p.RouteHost)
|
||||||
out = append(out, p)
|
out = append(out, p)
|
||||||
|
|
@ -55,15 +60,20 @@ func (a *App) createProject(ctx context.Context, user User, workspaceID int64, n
|
||||||
}
|
}
|
||||||
defer tx.Rollback()
|
defer tx.Rollback()
|
||||||
|
|
||||||
err = tx.QueryRowContext(ctx, `INSERT INTO projects (user_id, workspace_id, name, slug, description, repo_url, repo_private, service_name, route_host, deploy_token, provision_state, provision_error) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,'pending','') RETURNING id, user_id, name, slug, description, repo_url, repo_private, service_name, route_host, workspace_id, deploy_token, provision_state, provision_error, db_name, db_user, db_password, created_at`,
|
var createdDeployedAt sql.NullTime
|
||||||
|
err = tx.QueryRowContext(ctx, `INSERT INTO projects (user_id, workspace_id, name, slug, description, repo_url, repo_private, service_name, route_host, deploy_token, provision_state, provision_error) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,'pending','') RETURNING id, user_id, name, slug, description, repo_url, repo_private, service_name, route_host, workspace_id, deploy_token, provision_state, provision_error, last_deployed_at, last_deployed_commit, db_name, db_user, db_password, created_at`,
|
||||||
user.Username, workspaceID, name, slug, description, repoURL, private, serviceName, routeHost, deployToken,
|
user.Username, workspaceID, name, slug, description, repoURL, private, serviceName, routeHost, deployToken,
|
||||||
).Scan(&p.ID, &p.UserID, &p.Name, &p.Slug, &p.Description, &p.RepoURL, &p.RepoPrivate, &p.ServiceName, &p.RouteHost, &p.WorkspaceID, &p.DeployToken, &p.ProvisionState, &p.ProvisionError, &p.DBName, &p.DBUser, &p.DBPassword, &p.CreatedAt)
|
).Scan(&p.ID, &p.UserID, &p.Name, &p.Slug, &p.Description, &p.RepoURL, &p.RepoPrivate, &p.ServiceName, &p.RouteHost, &p.WorkspaceID, &p.DeployToken, &p.ProvisionState, &p.ProvisionError, &createdDeployedAt, &p.LastDeployedCommit, &p.DBName, &p.DBUser, &p.DBPassword, &p.CreatedAt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "duplicate key") {
|
if strings.Contains(err.Error(), "duplicate key") {
|
||||||
return Project{}, errors.New("project with this name already exists")
|
return Project{}, errors.New("project with this name already exists")
|
||||||
}
|
}
|
||||||
return Project{}, err
|
return Project{}, err
|
||||||
}
|
}
|
||||||
|
if createdDeployedAt.Valid {
|
||||||
|
t := createdDeployedAt.Time.UTC()
|
||||||
|
p.LastDeployedAt = &t
|
||||||
|
}
|
||||||
if err := tx.Commit(); err != nil {
|
if err := tx.Commit(); err != nil {
|
||||||
return Project{}, err
|
return Project{}, err
|
||||||
}
|
}
|
||||||
|
|
@ -144,15 +154,20 @@ func (a *App) provisionProject(ctx context.Context, p *Project) error {
|
||||||
|
|
||||||
func (a *App) getProject(ctx context.Context, userID string, projectID int64) (Project, error) {
|
func (a *App) getProject(ctx context.Context, userID string, projectID int64) (Project, error) {
|
||||||
var p Project
|
var p Project
|
||||||
err := a.db.QueryRowContext(ctx, `SELECT p.id, p.user_id, p.name, p.slug, p.description, p.repo_url, p.repo_private, p.service_name, p.route_host, p.workspace_id, w.name, w.unix_user, p.deploy_token, p.provision_state, p.provision_error, p.db_name, p.db_user, p.db_password, p.created_at
|
var deployedAt sql.NullTime
|
||||||
|
err := a.db.QueryRowContext(ctx, `SELECT p.id, p.user_id, p.name, p.slug, p.description, p.repo_url, p.repo_private, p.service_name, p.route_host, p.workspace_id, w.name, w.unix_user, p.deploy_token, p.provision_state, p.provision_error, p.last_deployed_at, p.last_deployed_commit, p.db_name, p.db_user, p.db_password, p.created_at
|
||||||
FROM projects p JOIN workspaces w ON w.id=p.workspace_id WHERE p.user_id=$1 AND p.id=$2`, userID, projectID).
|
FROM projects p JOIN workspaces w ON w.id=p.workspace_id WHERE p.user_id=$1 AND p.id=$2`, userID, projectID).
|
||||||
Scan(&p.ID, &p.UserID, &p.Name, &p.Slug, &p.Description, &p.RepoURL, &p.RepoPrivate, &p.ServiceName, &p.RouteHost, &p.WorkspaceID, &p.Workspace, &p.UnixUser, &p.DeployToken, &p.ProvisionState, &p.ProvisionError, &p.DBName, &p.DBUser, &p.DBPassword, &p.CreatedAt)
|
Scan(&p.ID, &p.UserID, &p.Name, &p.Slug, &p.Description, &p.RepoURL, &p.RepoPrivate, &p.ServiceName, &p.RouteHost, &p.WorkspaceID, &p.Workspace, &p.UnixUser, &p.DeployToken, &p.ProvisionState, &p.ProvisionError, &deployedAt, &p.LastDeployedCommit, &p.DBName, &p.DBUser, &p.DBPassword, &p.CreatedAt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
return Project{}, errors.New("project not found")
|
return Project{}, errors.New("project not found")
|
||||||
}
|
}
|
||||||
return Project{}, err
|
return Project{}, err
|
||||||
}
|
}
|
||||||
|
if deployedAt.Valid {
|
||||||
|
t := deployedAt.Time.UTC()
|
||||||
|
p.LastDeployedAt = &t
|
||||||
|
}
|
||||||
p.WebhookURL = a.deployWebhookURL(p.ID, p.DeployToken)
|
p.WebhookURL = a.deployWebhookURL(p.ID, p.DeployToken)
|
||||||
p.AppURL = a.appURL(p.RouteHost)
|
p.AppURL = a.appURL(p.RouteHost)
|
||||||
return p, nil
|
return p, nil
|
||||||
|
|
|
||||||
|
|
@ -45,27 +45,29 @@ type User struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Project struct {
|
type Project struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
UserID string `json:"-"`
|
UserID string `json:"-"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Slug string `json:"slug"`
|
Slug string `json:"slug"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
RepoURL string `json:"repo_url"`
|
RepoURL string `json:"repo_url"`
|
||||||
RepoPrivate bool `json:"repo_private"`
|
RepoPrivate bool `json:"repo_private"`
|
||||||
ServiceName string `json:"service_name"`
|
ServiceName string `json:"service_name"`
|
||||||
RouteHost string `json:"route_host"`
|
RouteHost string `json:"route_host"`
|
||||||
AppURL string `json:"app_url"`
|
AppURL string `json:"app_url"`
|
||||||
WorkspaceID int64 `json:"workspace_id"`
|
WorkspaceID int64 `json:"workspace_id"`
|
||||||
Workspace string `json:"workspace"`
|
Workspace string `json:"workspace"`
|
||||||
UnixUser string `json:"unix_user"`
|
UnixUser string `json:"unix_user"`
|
||||||
WebhookURL string `json:"webhook_url"`
|
WebhookURL string `json:"webhook_url"`
|
||||||
DeployToken string `json:"-"`
|
DeployToken string `json:"-"`
|
||||||
ProvisionState string `json:"provision_state"`
|
ProvisionState string `json:"provision_state"`
|
||||||
ProvisionError string `json:"provision_error,omitempty"`
|
ProvisionError string `json:"provision_error,omitempty"`
|
||||||
DBName string `json:"db_name,omitempty"`
|
LastDeployedAt *time.Time `json:"last_deployed_at,omitempty"`
|
||||||
DBUser string `json:"db_user,omitempty"`
|
LastDeployedCommit string `json:"last_deployed_commit,omitempty"`
|
||||||
DBPassword string `json:"-"`
|
DBName string `json:"db_name,omitempty"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
DBUser string `json:"db_user,omitempty"`
|
||||||
|
DBPassword string `json:"-"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type EnvVar struct {
|
type EnvVar struct {
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,8 @@ function renderProjects() {
|
||||||
<p class="meta">slug: ${escapeHtml(p.slug)} | service: ${escapeHtml(p.service_name)}</p>
|
<p class="meta">slug: ${escapeHtml(p.slug)} | service: ${escapeHtml(p.service_name)}</p>
|
||||||
<p class="meta">route host: ${escapeHtml(p.route_host || 'not set')}</p>
|
<p class="meta">route host: ${escapeHtml(p.route_host || 'not set')}</p>
|
||||||
<p class="meta">app url: ${p.app_url ? `<a class="link" href="${escapeHtml(p.app_url)}" target="_blank" rel="noreferrer">${escapeHtml(p.app_url)}</a>` : 'not set'}</p>
|
<p class="meta">app url: ${p.app_url ? `<a class="link" href="${escapeHtml(p.app_url)}" target="_blank" rel="noreferrer">${escapeHtml(p.app_url)}</a>` : 'not set'}</p>
|
||||||
|
<p class="meta">last deploy: ${p.last_deployed_at ? escapeHtml(new Date(p.last_deployed_at).toLocaleString()) : 'never'}</p>
|
||||||
|
<p class="meta">latest commit: <code>${escapeHtml(p.last_deployed_commit || 'n/a')}</code></p>
|
||||||
<p class="meta">deploy webhook: <code>${escapeHtml(p.webhook_url || 'n/a')}</code></p>
|
<p class="meta">deploy webhook: <code>${escapeHtml(p.webhook_url || 'n/a')}</code></p>
|
||||||
<p class="meta">provisioning: ${escapeHtml(p.provision_state || 'unknown')}${p.provision_error ? ` | error: ${escapeHtml(p.provision_error)}` : ''}</p>
|
<p class="meta">provisioning: ${escapeHtml(p.provision_state || 'unknown')}${p.provision_error ? ` | error: ${escapeHtml(p.provision_error)}` : ''}</p>
|
||||||
<p>${escapeHtml(p.description || '')}</p>
|
<p>${escapeHtml(p.description || '')}</p>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue