sabisan/internal/store/migrations.go
V fac53d7b85
All checks were successful
Publish / Test, build, and push image (push) Successful in 3m38s
Refactor app and store layout
2026-05-17 00:03:50 +01:00

146 lines
5.2 KiB
Go

package store
import (
"context"
"database/sql"
"errors"
"golang.org/x/crypto/bcrypt"
)
func (s *Store) Migrate(adminUsername, adminPassword string) error {
stmts := []string{
`create table if not exists site_content (
id integer primary key check (id = 1),
hero_title text not null,
hero_subtitle text not null,
intro_title text not null,
intro_text text not null,
about_name text not null,
about_role text not null,
about_bio text not null,
email text not null,
phone text not null,
location text not null,
hero_image text not null,
about_image text not null
)`,
`create table if not exists projects (
id integer primary key autoincrement,
slug text not null unique,
title text not null,
location text not null,
year text not null,
category text not null,
description text not null,
cover_image text not null,
featured integer not null default 0,
created_at datetime not null default current_timestamp
)`,
`create table if not exists project_images (
id integer primary key autoincrement,
project_id integer not null references projects(id) on delete cascade,
path text not null,
caption text not null,
position integer not null default 0
)`,
`create table if not exists contact_requests (
id integer primary key autoincrement,
name text not null,
email text not null,
message text not null,
created_at datetime not null default current_timestamp
)`,
`create table if not exists admin_users (
id integer primary key autoincrement,
username text not null unique,
password_hash blob not null
)`,
`create table if not exists sessions (
token_hash text primary key,
user_id integer not null references admin_users(id) on delete cascade,
expires_at datetime not null
)`,
}
for _, stmt := range stmts {
if _, err := s.db.Exec(stmt); err != nil {
return err
}
}
return s.seed(adminUsername, adminPassword)
}
func (s *Store) seed(adminUsername, adminPassword string) error {
var count int
if err := s.db.QueryRow(`select count(*) from site_content`).Scan(&count); err != nil {
return err
}
if count == 0 {
_, err := s.db.Exec(`insert into site_content (
id, hero_title, hero_subtitle, intro_title, intro_text, about_name, about_role, about_bio,
email, phone, location, hero_image, about_image
) values (1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
"Archi Folio",
"Spatial design, architecture, and interiors shaped through quiet detail.",
"Selected residential and cultural spaces",
"A compact portfolio for showing image-led architectural work, interior concepts, and project narratives.",
"Alex Morgan",
"Architect & Interior Designer",
"I design calm, functional spaces with attention to proportion, material, light, and the daily rituals of the people who use them.",
"studio@example.com",
"+44 20 0000 0000",
"London, United Kingdom",
"/static/placeholders/hero.svg",
"/static/placeholders/about.svg",
)
if err != nil {
return err
}
}
if err := s.db.QueryRow(`select count(*) from projects`).Scan(&count); err != nil {
return err
}
if count == 0 {
projects := []Project{
{Slug: "courtyard-house", Title: "Courtyard House", Location: "Bath, UK", Year: "2025", Category: "Residential", Description: "A private house organized around a quiet internal garden, using warm timber, stone, and filtered daylight.", CoverImage: "/static/placeholders/project-1.svg", Featured: true},
{Slug: "atelier-apartment", Title: "Atelier Apartment", Location: "London, UK", Year: "2024", Category: "Interior", Description: "A compact apartment refit with integrated storage, gallery-like surfaces, and a flexible work area.", CoverImage: "/static/placeholders/project-2.svg", Featured: true},
{Slug: "gallery-room", Title: "Gallery Room", Location: "Amsterdam, NL", Year: "2024", Category: "Cultural", Description: "A small exhibition environment designed for shifting light levels, sculpture, and intimate events.", CoverImage: "/static/placeholders/project-3.svg", Featured: true},
}
for _, p := range projects {
id, err := s.CreateProject(context.Background(), p)
if err != nil {
return err
}
for i := 0; i < 3; i++ {
if _, err := s.db.Exec(`insert into project_images (project_id, path, caption, position) values (?, ?, ?, ?)`, id, p.CoverImage, p.Title, i); err != nil {
return err
}
}
}
}
hash, err := bcrypt.GenerateFromPassword([]byte(adminPassword), bcrypt.DefaultCost)
if err != nil {
return err
}
var user AdminUser
err = s.db.QueryRow(`select id, username, password_hash from admin_users order by id asc limit 1`).Scan(&user.ID, &user.Username, &user.PasswordHash)
if errors.Is(err, sql.ErrNoRows) {
_, err = s.db.Exec(`insert into admin_users (username, password_hash) values (?, ?)`, adminUsername, hash)
return err
}
if err != nil {
return err
}
if user.Username == adminUsername && bcrypt.CompareHashAndPassword(user.PasswordHash, []byte(adminPassword)) == nil {
return nil
}
_, err = s.db.Exec(`update admin_users set username = ?, password_hash = ? where id = ?`, adminUsername, hash, user.ID)
if err != nil {
return err
}
_, err = s.db.Exec(`delete from sessions`)
return err
}