list all (wip)

This commit is contained in:
2025-10-21 19:39:49 +02:00
parent ec631e3c3a
commit 381a4e4988
4 changed files with 225 additions and 21 deletions

View File

@@ -43,6 +43,7 @@ func NewServer(data *storage.Repository, port int) *HTTPServer {
// Get information about the server
r.Get("/version", s.Information)
r.Route("/projects", func(r chi.Router) {
r.Get("/all", s.ProjectsHandler)
r.Get("/{name}", func(w http.ResponseWriter, r *http.Request) {})
r.Post("/{name}", s.ProjectPostHandler)
r.Delete("/{name}", func(w http.ResponseWriter, r *http.Request) {})
@@ -91,3 +92,14 @@ func (s *HTTPServer) ProjectPostHandler(w http.ResponseWriter, r *http.Request)
w.WriteHeader(201)
}
func (s *HTTPServer) ProjectsHandler(w http.ResponseWriter, r *http.Request) {
prs, err := s.data.List()
if err != nil {
slog.Error("failed to fetch all the projects from the database", "err", err)
internalServerError(err, w, r)
return
}
ok(prs, w, r)
}

View File

@@ -1,18 +1,20 @@
-- +goose Up
CREATE TABLE Projects (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
uuid TEXT NOT NULL,
name TEXT NOT NULL
);
CREATE INDEX Projects_uuid_IDX ON Projects (uuid);
CREATE TABLE Repositories (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
uuid TEXT NOT NULL,
name TEXT NOT NULL,
schedule TEXT NOT NULL,
"source" TEXT NOT NULL,
destination TEXT NOT NULL,
project INTEGER NOT NULL
);
CREATE INDEX Repositories_uuid_IDX ON Repositories (uuid);
-- +goose Down
DROP TABLE Projects;
DROP TABLE Repositories;

View File

@@ -2,10 +2,12 @@ package storage
import (
"database/sql"
"errors"
"fmt"
"mirror-sync/pkg/project"
_ "github.com/glebarez/go-sqlite"
"github.com/google/uuid"
)
type (
@@ -27,6 +29,70 @@ func OpenDB(path string) (*Repository, error) {
}
func (r *Repository) Save(pr project.Project) (err error) {
exists, err := r.ProjectExistsByName(pr.Name)
if err != nil {
return err
}
if exists {
return r.Update(pr)
}
return r.Create(pr)
}
func (r *Repository) ProjectExistsByUUID(uuid string) (bool, error) {
row := r.db.QueryRow("SELECT uuid FROM Projects WHERE uuid = ?", uuid)
if row.Err() != nil {
return false, fmt.Errorf("failed to get row from database: %w", row.Err())
}
var id string
if err := row.Scan(&id); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return false, nil
}
return false, fmt.Errorf("failed to scan row: %w", err)
}
return true, nil
}
func (r *Repository) ProjectExistsByName(name string) (bool, error) {
row := r.db.QueryRow("SELECT uuid FROM Projects WHERE name = ?", name)
if row.Err() != nil {
return false, fmt.Errorf("failed to get row from database: %w", row.Err())
}
var uuid string
if err := row.Scan(&uuid); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return false, nil
}
return false, fmt.Errorf("failed to scan row: %w", err)
}
return true, nil
}
func (r *Repository) RepositoryExistsByName(name string) (bool, error) {
row := r.db.QueryRow("SELECT uuid FROM Repositories WHERE name = ?", name)
if row.Err() != nil {
return false, fmt.Errorf("failed to get row from database: %w", row.Err())
}
var uuid string
if err := row.Scan(&uuid); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return false, nil
}
return false, fmt.Errorf("failed to scan row: %w", err)
}
return true, nil
}
func (r *Repository) Create(pr project.Project) error {
tx, err := r.db.Begin()
if err != nil {
return fmt.Errorf("failed to create transaction: %s", err)
@@ -39,35 +105,159 @@ func (r *Repository) Save(pr project.Project) (err error) {
tx.Commit()
}()
stmt, err := tx.Prepare("INSERT INTO Projects (name) VALUES (?)")
// Create Project entry
projectUUID := uuid.NewString()
stmt, err := tx.Prepare("INSERT INTO Projects (uuid, name) VALUES (?, ?)")
if err != nil {
return fmt.Errorf("failed to create statement: %s", err)
}
if _, err := stmt.Exec(pr.Name); err != nil {
if _, err := stmt.Exec(projectUUID, pr.Name); err != nil {
return fmt.Errorf("failed to execute sql query: %s", err)
}
rows, err := tx.Query("SELECT id FROM Projects WHERE name = ?", pr.Name)
// Create repositories entries
stmt, err = tx.Prepare("INSERT INTO Repositories (uuid, name, source, destination, schedule, project) VALUES (?, ?, ?, ?, ?, ?)")
if err != nil {
return fmt.Errorf("failed to query project id: %s", err)
}
defer rows.Close()
var id int
rows.Next()
if err := rows.Scan(&id); err != nil {
return fmt.Errorf("failed to query project id: %s", err)
return fmt.Errorf("failed to create statement: %s", err)
}
for _, repo := range pr.Repositories {
stmt, err := tx.Prepare("INSERT INTO Repositories (name, source, destination, schedule, project) VALUES (?, ?, ?, ?, ?)")
if err != nil {
return fmt.Errorf("failed to create statement: %s", err)
}
if _, err := stmt.Exec(repo.Name, repo.Source, repo.Destination, repo.Schedule, id); err != nil {
repoUUID := uuid.NewString()
if _, err := stmt.Exec(repoUUID, repo.Name, repo.Source, repo.Destination, repo.Schedule, projectUUID); err != nil {
return fmt.Errorf("failed to execute sql query: %s", err)
}
}
return nil
}
func (r *Repository) ProjectUUID(name string) (string, error) {
row := r.db.QueryRow("SELECT uuid FROM Projects WHERE name = ?", name)
if row.Err() != nil {
return "", fmt.Errorf("failed to get row from database: %w", row.Err())
}
var uuid string
if err := row.Scan(&uuid); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return "", nil
}
return "", fmt.Errorf("failed to scan row: %w", err)
}
return uuid, nil
}
func (r *Repository) RepositoryUUID(name string) (string, error) {
row := r.db.QueryRow("SELECT uuid FROM Repositories WHERE name = ?", name)
if row.Err() != nil {
return "", fmt.Errorf("failed to get row from database: %w", row.Err())
}
var uuid string
if err := row.Scan(&uuid); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return "", nil
}
return "", fmt.Errorf("failed to scan row: %w", err)
}
return uuid, nil
}
func (r *Repository) Update(pr project.Project) error {
tx, err := r.db.Begin()
if err != nil {
return fmt.Errorf("failed to create transaction: %s", err)
}
defer func() {
if err != nil {
tx.Rollback()
return
}
tx.Commit()
}()
projectUUID, err := r.ProjectUUID(pr.Name)
if err != nil {
return fmt.Errorf("failed to get project uuid: %w", err)
}
stmt, err := tx.Prepare("UPDATE Repositories SET schedule = ?, source = ?, destination = ? WHERE uuid = ?")
if err != nil {
return fmt.Errorf("failed to create statement: %w", err)
}
// this loop does NOT remove orphan
for _, repo := range pr.Repositories {
// checks if the repo exists
exists, err := r.RepositoryExistsByName(repo.Name)
if err != nil {
return fmt.Errorf("failed to fetch uuid from the database: %w", err)
}
if exists {
// if it exists, just update it
uuid, err := r.RepositoryUUID(repo.Name)
if err != nil {
return fmt.Errorf("failed to get uuid from database: %w", err)
}
if _, err := stmt.Exec(repo.Schedule, repo.Source, repo.Destination, uuid); err != nil {
return fmt.Errorf("failed to update repository entry for %s::'%s'", uuid, repo.Name)
}
} else {
// if not, create a new uuid and create the entry
repoUUID := uuid.NewString()
if _, err := stmt.Exec(repoUUID, repo.Name, repo.Source, repo.Destination, repo.Schedule, projectUUID); err != nil {
return fmt.Errorf("failed to execute sql query: %s", err)
}
}
if _, err := stmt.Exec(repo.Schedule, repo.Source, repo.Destination, repo.Name); err != nil {
return fmt.Errorf("failed to update repository entry for '%s'", repo.Name)
}
}
return nil
}
func (r *Repository) List() ([]project.Project, error) {
var prs []project.Project
rows, err := r.db.Query("SELECT uuid, name WHERE Projects")
if err != nil {
return nil, fmt.Errorf("failed to get the list of projects: %w", err)
}
defer rows.Close()
stmt, err := r.db.Prepare("SELECT name, schedule, source, destination WHERE uuid = ?")
if err != nil {
return nil, fmt.Errorf("invalid syntax: %w", err)
}
for rows.Next() {
var pr project.Project
var prUUID string
if err := rows.Scan(&prUUID, &pr.Name); err != nil {
return nil, fmt.Errorf("failed to scan project name: %w", err)
}
for rows.Next() {
var repo project.Repository
rows, err := stmt.Query(prUUID)
if err != nil {
return nil, fmt.Errorf("failed to get the list of projects: %w", err)
}
if err := rows.Scan(&repo.Name, &repo.Schedule, &repo.Source, &repo.Destination); err != nil {
return nil, fmt.Errorf("failed to scan repository entry: %w", err)
}
pr.Repositories = append(pr.Repositories, repo)
}
prs = append(prs, pr)
}
return prs, nil
}

2
go.mod
View File

@@ -8,6 +8,7 @@ require (
github.com/go-git/go-git/v6 v6.0.0-20250929195514-145daf2492dd
github.com/goccy/go-yaml v1.18.0
github.com/google/subcommands v1.2.0
github.com/google/uuid v1.6.0
github.com/pressly/goose/v3 v3.26.0
)
@@ -21,7 +22,6 @@ require (
github.com/go-git/gcfg/v2 v2.0.2 // indirect
github.com/go-git/go-billy/v6 v6.0.0-20250627091229-31e2a16eef30 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/kevinburke/ssh_config v1.4.0 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect