···11+package knotserver
22+33+import (
44+ "compress/gzip"
55+ "fmt"
66+ "net/http"
77+ "strings"
88+99+ securejoin "github.com/cyphar/filepath-securejoin"
1010+ "github.com/go-chi/chi/v5"
1111+ "github.com/go-git/go-git/v5/plumbing"
1212+ "tangled.org/core/knotserver/git"
1313+)
1414+1515+func (h *Knot) Archive(w http.ResponseWriter, r *http.Request) {
1616+ var (
1717+ did = chi.URLParam(r, "did")
1818+ name = chi.URLParam(r, "name")
1919+ ref = chi.URLParam(r, "ref")
2020+ )
2121+ repo, err := securejoin.SecureJoin(did, name)
2222+ if err != nil {
2323+ gitError(w, "repository not found", http.StatusNotFound)
2424+ h.l.Error("git: failed to secure join repo path", "handler", "InfoRefs", "error", err)
2525+ return
2626+ }
2727+2828+ repoPath, err := securejoin.SecureJoin(h.c.Repo.ScanPath, repo)
2929+ if err != nil {
3030+ gitError(w, "repository not found", http.StatusNotFound)
3131+ h.l.Error("git: failed to secure join repo path", "handler", "InfoRefs", "error", err)
3232+ return
3333+ }
3434+3535+ gr, err := git.Open(repoPath, ref)
3636+3737+ immutableLink := fmt.Sprintf(
3838+ "https://%s/%s/%s/archive/%s",
3939+ h.c.Server.Hostname,
4040+ did,
4141+ name,
4242+ gr.Hash(),
4343+ )
4444+4545+ safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(ref).Short(), "/", "-")
4646+ filename := fmt.Sprintf("%s-%s.tar.gz", name, safeRefFilename)
4747+ w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
4848+ w.Header().Set("Content-Type", "application/gzip")
4949+ w.Header().Set("Link", fmt.Sprintf("<%s>; rel=\"immutable\"", immutableLink))
5050+5151+ gw := gzip.NewWriter(w)
5252+ defer gw.Close()
5353+5454+ err = gr.WriteTar(gw, "")
5555+ if err != nil {
5656+ // once we start writing to the body we can't report error anymore
5757+ // so we are only left with logging the error
5858+ h.l.Error("writing tar file", "error", err)
5959+ return
6060+ }
6161+6262+ err = gw.Flush()
6363+ if err != nil {
6464+ // once we start writing to the body we can't report error anymore
6565+ // so we are only left with logging the error
6666+ h.l.Error("flushing", "error", err.Error())
6767+ return
6868+ }
6969+}
+81
knotserver/db/db.go
···11+package db
22+33+import (
44+ "context"
55+ "database/sql"
66+ "log/slog"
77+ "strings"
88+99+ _ "github.com/mattn/go-sqlite3"
1010+ "tangled.org/core/log"
1111+)
1212+1313+type DB struct {
1414+ db *sql.DB
1515+ logger *slog.Logger
1616+}
1717+1818+func Setup(ctx context.Context, dbPath string) (*DB, error) {
1919+ // https://github.com/mattn/go-sqlite3#connection-string
2020+ opts := []string{
2121+ "_foreign_keys=1",
2222+ "_journal_mode=WAL",
2323+ "_synchronous=NORMAL",
2424+ "_auto_vacuum=incremental",
2525+ }
2626+2727+ logger := log.FromContext(ctx)
2828+ logger = log.SubLogger(logger, "db")
2929+3030+ db, err := sql.Open("sqlite3", dbPath+"?"+strings.Join(opts, "&"))
3131+ if err != nil {
3232+ return nil, err
3333+ }
3434+3535+ conn, err := db.Conn(ctx)
3636+ if err != nil {
3737+ return nil, err
3838+ }
3939+ defer conn.Close()
4040+4141+ _, err = conn.ExecContext(ctx, `
4242+ create table if not exists known_dids (
4343+ did text primary key
4444+ );
4545+4646+ create table if not exists public_keys (
4747+ id integer primary key autoincrement,
4848+ did text not null,
4949+ key text not null,
5050+ created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
5151+ unique(did, key),
5252+ foreign key (did) references known_dids(did) on delete cascade
5353+ );
5454+5555+ create table if not exists _jetstream (
5656+ id integer primary key autoincrement,
5757+ last_time_us integer not null
5858+ );
5959+6060+ create table if not exists events (
6161+ rkey text not null,
6262+ nsid text not null,
6363+ event text not null, -- json
6464+ created integer not null default (strftime('%s', 'now')),
6565+ primary key (rkey, nsid)
6666+ );
6767+6868+ create table if not exists migrations (
6969+ id integer primary key autoincrement,
7070+ name text unique
7171+ );
7272+ `)
7373+ if err != nil {
7474+ return nil, err
7575+ }
7676+7777+ return &DB{
7878+ db: db,
7979+ logger: logger,
8080+ }, nil
8181+}
-64
knotserver/db/init.go
···11-package db
22-33-import (
44- "database/sql"
55- "strings"
66-77- _ "github.com/mattn/go-sqlite3"
88-)
99-1010-type DB struct {
1111- db *sql.DB
1212-}
1313-1414-func Setup(dbPath string) (*DB, error) {
1515- // https://github.com/mattn/go-sqlite3#connection-string
1616- opts := []string{
1717- "_foreign_keys=1",
1818- "_journal_mode=WAL",
1919- "_synchronous=NORMAL",
2020- "_auto_vacuum=incremental",
2121- }
2222-2323- db, err := sql.Open("sqlite3", dbPath+"?"+strings.Join(opts, "&"))
2424- if err != nil {
2525- return nil, err
2626- }
2727-2828- // NOTE: If any other migration is added here, you MUST
2929- // copy the pattern in appview: use a single sql.Conn
3030- // for every migration.
3131-3232- _, err = db.Exec(`
3333- create table if not exists known_dids (
3434- did text primary key
3535- );
3636-3737- create table if not exists public_keys (
3838- id integer primary key autoincrement,
3939- did text not null,
4040- key text not null,
4141- created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
4242- unique(did, key),
4343- foreign key (did) references known_dids(did) on delete cascade
4444- );
4545-4646- create table if not exists _jetstream (
4747- id integer primary key autoincrement,
4848- last_time_us integer not null
4949- );
5050-5151- create table if not exists events (
5252- rkey text not null,
5353- nsid text not null,
5454- event text not null, -- json
5555- created integer not null default (strftime('%s', 'now')),
5656- primary key (rkey, nsid)
5757- );
5858- `)
5959- if err != nil {
6060- return nil, err
6161- }
6262-6363- return &DB{db: db}, nil
6464-}