All checks were successful
Publish / Test, build, and push image (push) Successful in 3m38s
146 lines
5.2 KiB
Go
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
|
|
}
|