// repoplace.go is where a repository has BEEN.
//
// A repository appears under a namespace — a team when a team holds it,
// its owner's handle otherwise — and that namespace changes when it
// moves into or out of a team. Every link anybody saved carries the
// namespace of the moment, so a move made them all 404 (c12a84b2, G-52
// as jva measured it from jan's seat): the bare /r/<repo> form was the
// only saved link that survived. The bare form redirects because the
// forge knows where the repository IS; the old namespaced form can only
// redirect if the forge remembers where it WAS. This table is that
// memory, and placeRepo is the one writer of a repository's namespace,
// so every move records its predecessor whichever door it came through.
package oltp
import (
"database/sql"
"errors"
"time"
)
const repoPlaceSchema = `
CREATE TABLE IF NOT EXISTS repo_place_past (
repo TEXT NOT NULL,
namespace TEXT NOT NULL, -- a namespace the repository once appeared under
until TEXT NOT NULL, -- when it left
PRIMARY KEY (repo, namespace)
);`
// execer is what placeRepo needs of a *sql.Tx or a *sql.DB.
type execer interface {
Exec(query string, args ...any) (sql.Result, error)
QueryRow(query string, args ...any) *sql.Row
}
// placeRepo moves a repository to ns, remembering the namespace it
// leaves. The namespace it arrives in stops being a past one, so a
// repository that comes home resolves there as current and not as a
// redirect to itself. An empty former namespace (a store nobody held)
// is nothing to remember: it had no URL of its own.
func placeRepo(x execer, repo, ns, by string, at time.Time) error {
var cur string
if err := x.QueryRow(`SELECT namespace FROM repo WHERE name = ?`, repo).Scan(&cur); err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
when := at.UTC().Format(time.RFC3339)
if cur != "" && cur != ns {
if _, err := x.Exec(`INSERT OR REPLACE INTO repo_place_past (repo, namespace, until) VALUES (?, ?, ?)`, repo, cur, when); err != nil {
return err
}
}
if _, err := x.Exec(`DELETE FROM repo_place_past WHERE repo = ? AND namespace = ?`, repo, ns); err != nil {
return err
}
_, err := x.Exec(`UPDATE repo SET namespace = ?, namespace_by = ?, namespace_at = ? WHERE name = ?`, ns, by, when, repo)
return err
}
// WasPlacedAt reports whether the repository once appeared under ns and
// no longer does — the question a saved link asks.
func (d *DB) WasPlacedAt(repo, ns string) bool {
if repo == "" || ns == "" {
return false
}
var one int
return d.db.QueryRow(`SELECT 1 FROM repo_place_past WHERE repo = ? AND namespace = ?`, repo, ns).Scan(&one) == nil
}