From 1ca54d6114bb218406329f07fb12d211247f2642 Mon Sep 17 00:00:00 2001 From: soup Date: Thu, 14 Nov 2024 17:39:31 -0500 Subject: [PATCH] Initial commit --- .gitignore | 3 + Makefile | 10 ++ backend/bin/shelves.go | 34 +++++ backend/db/db.go | 155 ++++++++++++++++++++ backend/http/middleware.go | 106 +++++++++++++ backend/routes/home.go | 7 + backend/routes/routes.go | 22 +++ backend/routes/setup.go | 24 +++ backend/templates/form.go | 39 +++++ backend/templates/page.go | 15 ++ backend/templates/templates.go | 28 ++++ backend/templates/tmpls/form.tmpl.html | 30 ++++ backend/templates/tmpls/page_base.tmpl.html | 12 ++ flake.lock | 82 +++++++++++ flake.nix | 31 ++++ frontend.go | 6 + frontend/css/mu.css | 0 go.mod | 24 +++ go.sum | 42 ++++++ gomod2nix.toml | 48 ++++++ tools.go | 7 + 21 files changed, 725 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 backend/bin/shelves.go create mode 100644 backend/db/db.go create mode 100644 backend/http/middleware.go create mode 100644 backend/routes/home.go create mode 100644 backend/routes/routes.go create mode 100644 backend/routes/setup.go create mode 100644 backend/templates/form.go create mode 100644 backend/templates/page.go create mode 100644 backend/templates/templates.go create mode 100644 backend/templates/tmpls/form.tmpl.html create mode 100644 backend/templates/tmpls/page_base.tmpl.html create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 frontend.go create mode 100644 frontend/css/mu.css create mode 100644 go.mod create mode 100644 go.sum create mode 100644 gomod2nix.toml create mode 100644 tools.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..612bed1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +shelves.db +shelves.db* +shelves diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0b5a80b --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +.PHONY: build + +build: + go build backend/bin/shelves.go + +run: + go run backend/bin/shelves.go + +watch: + fd . . | entr -cr make run diff --git a/backend/bin/shelves.go b/backend/bin/shelves.go new file mode 100644 index 0000000..be32810 --- /dev/null +++ b/backend/bin/shelves.go @@ -0,0 +1,34 @@ +package main + +import ( + "fmt" + "log" + "net/http" + "os" + + _ "github.com/mattn/go-sqlite3" + + "shelves/backend/db" + shelvesHttp "shelves/backend/http" + "shelves/backend/routes" +) + +func main() { + os.Remove("./shelves.db") + db, migrateResult, err := db.Open("./shelves.db") + if err != nil { + log.Fatalf("Failed to open DB: %v", err) + } + if migrateResult.MigrationError != nil { + log.Printf("An error was encountered while upgrading the database schema. You are on version %v, but the latest is %v. The error was: %v", migrateResult.SchemaVerNew, migrateResult.SchemaVerLatest, migrateResult.MigrationError) + } + schemaNeedsUpdating := migrateResult.SchemaVerNew != migrateResult.SchemaVerLatest + if schemaNeedsUpdating { + log.Printf("Your database schema needs to be updated. The application will continue to run, but you may encounter errors.\n") + } + _ = db + + routes := routes.Routes() + fmt.Println("Listening on localhost:8999") + http.ListenAndServe("localhost:8999", shelvesHttp.Log(shelvesHttp.WithCtx(db, routes))) +} diff --git a/backend/db/db.go b/backend/db/db.go new file mode 100644 index 0000000..d8bd182 --- /dev/null +++ b/backend/db/db.go @@ -0,0 +1,155 @@ +package db + +import ( + "database/sql" + "fmt" + + _ "github.com/mattn/go-sqlite3" +) + +var _MIGRATIONS = [...]string{ + ` +create table owner_settings ( + id integer primary key, + + display_name text not null, + + password_hash blob not null, + password_salt blob not null, + + ts_updated text not null default current_timestamp +) strict; + +create trigger owner_settings_ts_update +after update on owner_settings +for each row +begin + update owner_settings set ts_updated = current_timestamp where rowid = new.rowid; +end; + +create table blob ( + id integer primary key, + + data blob not null, + + ts_created text not null default current_timestamp, + ts_updated text not null default current_timestamp +) strict; + +create trigger blob_ts_update +after update on blob +for each row +begin + update blob set ts_updated = current_timestamp where rowid = new.rowid; +end; + +create table item ( + id integer primary key, + + name text not null, + description text not null, + rating integer, + + ts_created text not null default current_timestamp, + ts_updated text not null default current_timestamp, + + image_blob_id integer not null, + foreign key (image_blob_id) references blob(id) on delete cascade +) strict; + +create trigger item_ts_update +after update on item +for each row +begin + update item set ts_updated = current_timestamp where rowid = new.rowid; +end; + +create table feed_event ( + id integer primary key, + + feed_event_type text not null, + data_json text not null, + + ts_created text not null default current_timestamp, + + item_id integer not null, + foreign key (item_id) references item(id) on delete cascade +) strict; + +create table session ( + id integer primary key, + + session_id text not null unique +) strict; +`, +} + +type MigrateResult struct { + SchemaVerPrev int + SchemaVerNew int + SchemaVerLatest int + MigrationError error +} + +func migrate(db *sql.DB) (MigrateResult, error) { + result := MigrateResult{ + SchemaVerLatest: len(_MIGRATIONS), + } + err := db.QueryRow("PRAGMA user_version").Scan(&result.SchemaVerPrev) + if err != nil { + return result, err + } + + migrationsToRun := _MIGRATIONS[result.SchemaVerPrev:] + for i, m := range migrationsToRun { + _, err = db.Exec(m) + if err != nil { + result.MigrationError = err + break + } + result.SchemaVerNew = result.SchemaVerPrev + i + 1 + } + + updateQueryStr := fmt.Sprintf("PRAGMA user_version = %d;", result.SchemaVerNew) + _, err = db.Exec(updateQueryStr) + if err != nil { + return result, err + } + + return result, nil +} + +func initConnection(db *sql.DB) error { + _, err := db.Exec(` +PRAGMA journal_mode = WAL; +PRAGMA busy_timeout = 5000; +PRAGMA synchronous = NORMAL; +PRAGMA cache_size = 1000000000; +PRAGMA foreign_keys = true; +PRAGMA temp_store = memory; + `) + return err +} + +func Open(path string) (*sql.DB, MigrateResult, error) { + if path == "" { + path = "./shelves.db" + } + + db, err := sql.Open("sqlite3", path) + if err != nil { + return nil, MigrateResult{}, err + } + + err = initConnection(db) + if err != nil { + return nil, MigrateResult{}, err + } + + result, err := migrate(db) + if err != nil { + return nil, result, err + } + + return db, result, nil +} diff --git a/backend/http/middleware.go b/backend/http/middleware.go new file mode 100644 index 0000000..0379549 --- /dev/null +++ b/backend/http/middleware.go @@ -0,0 +1,106 @@ +package http + +import ( + "context" + "database/sql" + "log" + "net/http" + "time" + + "shelves/backend/routes" +) + +type LogWrapper struct { + statusCode *int + + http.ResponseWriter +} + +func (w LogWrapper) WriteHeader(statusCode int) { + *w.statusCode = statusCode + + w.ResponseWriter.WriteHeader(statusCode) +} + +func Log(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + statusCode := 200 + wrapper := LogWrapper{statusCode: &statusCode, ResponseWriter: w} + start := time.Now() + next.ServeHTTP(wrapper, r) + + log.Println(r.Method, r.URL.Path, statusCode, time.Since(start)) + }) +} + +type AuthInfo struct { + is_admin bool +} + +func checkAuthed(db *sql.DB, r *http.Request) (AuthInfo, error) { + cookie, err := r.Cookie("SHELVES_OWNER_SESSION_ID") + if err == http.ErrNoCookie { + return AuthInfo{}, nil + } + if err != nil { + return AuthInfo{}, err + } + + sessionId := cookie.Value + + sessionCount := 0 + err = db.QueryRow("select count(*) from session where session_id = ?", sessionId).Scan(&sessionCount) + if err != nil { + return AuthInfo{}, err + } + + return AuthInfo{is_admin: sessionCount == 1}, nil +} + +func queryNeedsOwnerSetup(db *sql.DB) (bool, error) { + count := 0 + err := db.QueryRow("select count(*) from owner_settings").Scan(&count) + if err != nil { + return false, err + } + + return count != 1, nil +} + +type Ctx struct { + DB *sql.DB + Auth AuthInfo +} + +func WithCtx(db *sql.DB, next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + needsOwnerSetup, err := queryNeedsOwnerSetup(db) + if err != nil { + log.Printf("Error while querying owner_settings: %v\n", err) + } + + if needsOwnerSetup { + if r.URL.Path != "/setup" { + w.Header().Add("Location", "/setup") + w.WriteHeader(http.StatusSeeOther) + return + } else { + routes.SetupGet(w, r) + return + } + } + + ctx := r.Context() + auth, err := checkAuthed(db, r) + if err != nil { + log.Printf("Error while querying auth info: %v\n", err) + } + ctx = context.WithValue(ctx, "__ctx", Ctx{ + DB: db, + Auth: auth, + }) + r = r.WithContext(ctx) + + next.ServeHTTP(w, r) + }) +} diff --git a/backend/routes/home.go b/backend/routes/home.go new file mode 100644 index 0000000..685ac8f --- /dev/null +++ b/backend/routes/home.go @@ -0,0 +1,7 @@ +package routes + +import "net/http" + +func HomeGet(w http.ResponseWriter, req *http.Request) { + w.Write([]byte("Hello, world")) +} diff --git a/backend/routes/routes.go b/backend/routes/routes.go new file mode 100644 index 0000000..ce6a782 --- /dev/null +++ b/backend/routes/routes.go @@ -0,0 +1,22 @@ +package routes + +import ( + "embed" + "html/template" + "net/http" + + "shelves" +) + +func html(w http.ResponseWriter, s template.HTML) { + w.Header().Add("Content-Type", "text/html") + w.Write([]byte(s)) +} + +func Routes() *http.ServeMux { + mux := http.NewServeMux() + mux.HandleFunc("GET /", HomeGet) + mux.Handle("GET /static", http.FileServerFS(shelves.Frontend)) + + return mux +} diff --git a/backend/routes/setup.go b/backend/routes/setup.go new file mode 100644 index 0000000..c7a3db7 --- /dev/null +++ b/backend/routes/setup.go @@ -0,0 +1,24 @@ +package routes + +import ( + "net/http" + "shelves/backend/templates" +) + +func SetupGet(w http.ResponseWriter, r *http.Request) { + html(w, templates.PageBase{Title: "Set up", Body: templates.Form{ + Action: "/setup", + Fields: []templates.Field{ + templates.Field{ + Label: "Password", + Type: "password", + Name: "password", + }, + templates.Field{ + Label: "Confirm password", + Type: "password", + Name: "password-confirmation", + }, + }, + }.HTML()}.HTML()) +} diff --git a/backend/templates/form.go b/backend/templates/form.go new file mode 100644 index 0000000..781e114 --- /dev/null +++ b/backend/templates/form.go @@ -0,0 +1,39 @@ +package templates + +import "html/template" + +type Field struct { + Label string + Name string + Error string + Type string + Value string + Placeholder string + Valid bool +} + +func (f *Field) init() { + if f.Type == "" { + f.Type = "text" + } + + f.Valid = f.Valid || f.Error == "" +} + +type Form struct { + Action string + Method string + Fields []Field +} + +func (f Form) HTML() template.HTML { + if f.Method == "" { + f.Method = "POST" + } + + for _, f := range f.Fields { + f.init() + } + + return tmpls.renderHtml("form.tmpl.html", f) +} diff --git a/backend/templates/page.go b/backend/templates/page.go new file mode 100644 index 0000000..a5c7744 --- /dev/null +++ b/backend/templates/page.go @@ -0,0 +1,15 @@ +package templates + +import "html/template" + +type PageBase struct { + Title string + Head template.HTML + Body template.HTML + BodyBefore template.HTML + BodyAfter template.HTML +} + +func (pb PageBase) HTML() template.HTML { + return tmpls.renderHtml("page_base.tmpl.html", pb) +} diff --git a/backend/templates/templates.go b/backend/templates/templates.go new file mode 100644 index 0000000..23db34c --- /dev/null +++ b/backend/templates/templates.go @@ -0,0 +1,28 @@ +package templates + +import ( + "embed" + "fmt" + "html/template" + "strings" +) + +//go:embed tmpls/* +var files embed.FS + +type Template struct { + *template.Template +} + +var tmpls = Template{template.Must(template.ParseFS(files, "tmpls/*"))} + +func (tmpl Template) renderHtml(name string, data any) template.HTML { + writer := &strings.Builder{} + err := tmpl.ExecuteTemplate(writer, name, data) + + if err != nil { + fmt.Fprint(writer, err) + } + + return template.HTML(writer.String()) +} diff --git a/backend/templates/tmpls/form.tmpl.html b/backend/templates/tmpls/form.tmpl.html new file mode 100644 index 0000000..ca4d7f0 --- /dev/null +++ b/backend/templates/tmpls/form.tmpl.html @@ -0,0 +1,30 @@ + +{{define "attrs"}} + placeholder="{{.Placeholder}}" + aria-invalid="{{.Valid}}" + {{if .Error}} + aria-describedby="{{.Name}}-error" + {{end}} +{{end}} + +
+ {{range .Fields}} + + {{if .Error}} + {{.Error}} + {{end}} + {{end}} +
diff --git a/backend/templates/tmpls/page_base.tmpl.html b/backend/templates/tmpls/page_base.tmpl.html new file mode 100644 index 0000000..72a6b3b --- /dev/null +++ b/backend/templates/tmpls/page_base.tmpl.html @@ -0,0 +1,12 @@ + + + + {{.Title}} - Shelves + {{.Head}} + + + {{.BodyBefore}} + {{.Body}} + {{.BodyAfter}} + + diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..135500a --- /dev/null +++ b/flake.lock @@ -0,0 +1,82 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "gomod2nix": { + "inputs": { + "flake-utils": [ + "flake-utils" + ], + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1729448365, + "narHash": "sha256-oquZeWTYWTr5IxfwEzgsxjtD8SSFZYLdO9DaQb70vNU=", + "owner": "nix-community", + "repo": "gomod2nix", + "rev": "5d387097aa716f35dd99d848dc26d8d5b62a104c", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "gomod2nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1725930920, + "narHash": "sha256-RVhD9hnlTT2nJzPHlAqrWqCkA7T6CYrP41IoVRkciZM=", + "path": "/nix/store/20yis5w6g397plssim663hqxdiiah2wr-source", + "rev": "44a71ff39c182edaf25a7ace5c9454e7cba2c658", + "type": "path" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "gomod2nix": "gomod2nix", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..92fdd5e --- /dev/null +++ b/flake.nix @@ -0,0 +1,31 @@ +{ + + inputs.nixpkgs.url = "nixpkgs"; + inputs.flake-utils.url = "github:numtide/flake-utils"; + inputs.gomod2nix = { + url = "github:nix-community/gomod2nix"; + inputs.nixpkgs.follows = "nixpkgs"; + inputs.flake-utils.follows = "flake-utils"; + }; + + outputs = inputs: + (inputs.flake-utils.lib.eachDefaultSystem (system: + let + pkgs = inputs.nixpkgs.legacyPackages.${system}; + pkgsG = inputs.gomod2nix.legacyPackages.${system}; + in { + devShells.default = + let goEnv = pkgsG.mkGoEnv { pwd = ./.; }; + in pkgs.mkShell { + packages = with pkgs; [ + gnumake + entr + fd + goEnv + (sqlite.override { interactive = true; }) + pkgsG.gomod2nix + ]; + }; + } + )); +} diff --git a/frontend.go b/frontend.go new file mode 100644 index 0000000..42b7464 --- /dev/null +++ b/frontend.go @@ -0,0 +1,6 @@ +package shelves + +import "embed" + +//go:embed frontend/* +var Frontend embed.FS diff --git a/frontend/css/mu.css b/frontend/css/mu.css new file mode 100644 index 0000000..e69de29 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..03b2ea1 --- /dev/null +++ b/go.mod @@ -0,0 +1,24 @@ +module shelves + +go 1.22 + +require ( + github.com/mattn/go-sqlite3 v1.14.24 + golang.org/x/tools/gopls v0.16.2 +) + +require ( + github.com/BurntSushi/toml v1.2.1 // indirect + github.com/google/go-cmp v0.6.0 // indirect + golang.org/x/exp/typeparams v0.0.0-20221212164502-fae10dda9338 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.23.0 // indirect + golang.org/x/telemetry v0.0.0-20240829154258-f29ab539cc98 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/tools v0.22.1-0.20240829175637-39126e24d653 // indirect + golang.org/x/vuln v1.0.4 // indirect + honnef.co/go/tools v0.4.7 // indirect + mvdan.cc/gofumpt v0.6.0 // indirect + mvdan.cc/xurls/v2 v2.5.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..ea8fd80 --- /dev/null +++ b/go.sum @@ -0,0 +1,42 @@ +github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/safehtml v0.1.0 h1:EwLKo8qawTKfsi0orxcQAZzu07cICaBeFMegAU9eaT8= +github.com/google/safehtml v0.1.0/go.mod h1:L4KWwDsUJdECRAEpZoBn3O64bQaywRscowZjJAzjHnU= +github.com/jba/templatecheck v0.7.0 h1:wjTb/VhGgSFeim5zjWVePBdaMo28X74bGLSABZV+zIA= +github.com/jba/templatecheck v0.7.0/go.mod h1:n1Etw+Rrw1mDDD8dDRsEKTwMZsJ98EkktgNJC6wLUGo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= +github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +golang.org/x/exp/typeparams v0.0.0-20221212164502-fae10dda9338 h1:2O2DON6y3XMJiQRAS1UWU+54aec2uopH3x7MAiqGW6Y= +golang.org/x/exp/typeparams v0.0.0-20221212164502-fae10dda9338/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240829154258-f29ab539cc98 h1:Wm3cG5X6sZ0RSVRc/H1/sciC4AT6HAKgLCSH2lbpR/c= +golang.org/x/telemetry v0.0.0-20240829154258-f29ab539cc98/go.mod h1:m7R/r+o5h7UvF2JD9n2iLSGY4v8v+zNSyTJ6xynLrqs= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/tools v0.22.1-0.20240829175637-39126e24d653 h1:6bJEg2w2kUHWlfdJaESYsmNfI1LKAZQi6zCa7LUn7eI= +golang.org/x/tools v0.22.1-0.20240829175637-39126e24d653/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/tools/gopls v0.16.2 h1:K1z03MlikHfaMTtG01cUeL5FAOTJnITuNe0TWOcg8tM= +golang.org/x/tools/gopls v0.16.2/go.mod h1:Hj8YxzfHfFyRK5muTZy5oO6/0nL7CZWu28ZNac7tXF0= +golang.org/x/vuln v1.0.4 h1:SP0mPeg2PmGCu03V+61EcQiOjmpri2XijexKdzv8Z1I= +golang.org/x/vuln v1.0.4/go.mod h1:NbJdUQhX8jY++FtuhrXs2Eyx0yePo9pF7nPlIjo9aaQ= +honnef.co/go/tools v0.4.7 h1:9MDAWxMoSnB6QoSqiVr7P5mtkT9pOc1kSxchzPCnqJs= +honnef.co/go/tools v0.4.7/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0= +mvdan.cc/gofumpt v0.6.0 h1:G3QvahNDmpD+Aek/bNOLrFR2XC6ZAdo62dZu65gmwGo= +mvdan.cc/gofumpt v0.6.0/go.mod h1:4L0wf+kgIPZtcCWXynNS2e6bhmj73umwnuXSZarixzA= +mvdan.cc/xurls/v2 v2.5.0 h1:lyBNOm8Wo71UknhUs4QTFUNNMyxy2JEIaKKo0RWOh+8= +mvdan.cc/xurls/v2 v2.5.0/go.mod h1:yQgaGQ1rFtJUzkmKiHYSSfuQxqfYmd//X6PxvholpeE= diff --git a/gomod2nix.toml b/gomod2nix.toml new file mode 100644 index 0000000..d3217f6 --- /dev/null +++ b/gomod2nix.toml @@ -0,0 +1,48 @@ +schema = 3 + +[mod] + [mod."github.com/BurntSushi/toml"] + version = "v1.2.1" + hash = "sha256-Z1dlsUTjF8SJZCknYKt7ufJz8NPGg9P9+W17DQn+LO0=" + [mod."github.com/google/go-cmp"] + version = "v0.6.0" + hash = "sha256-qgra5jze4iPGP0JSTVeY5qV5AvEnEu39LYAuUCIkMtg=" + [mod."github.com/mattn/go-sqlite3"] + version = "v1.14.24" + hash = "sha256-taGKFZFQlR5++5b2oZ1dYS3RERKv6yh1gniNWhb4egg=" + [mod."golang.org/x/exp/typeparams"] + version = "v0.0.0-20221212164502-fae10dda9338" + hash = "sha256-IdVdYrszhwtJ63OmlyzuqJ261dGyYulHd/MW9RKkIZg=" + [mod."golang.org/x/mod"] + version = "v0.20.0" + hash = "sha256-nXYnY2kpbVkaZ/7Mf7FmxwGDX7N4cID3gKjGghmVRp4=" + [mod."golang.org/x/sync"] + version = "v0.8.0" + hash = "sha256-usvF0z7gq1vsX58p4orX+8WHlv52pdXgaueXlwj2Wss=" + [mod."golang.org/x/sys"] + version = "v0.23.0" + hash = "sha256-tC6QVLu72bADgINz26FUGdmYqKgsU45bHPg7sa0ZV7w=" + [mod."golang.org/x/telemetry"] + version = "v0.0.0-20240829154258-f29ab539cc98" + hash = "sha256-j7MO14OZekObngeg84+nrDTSX8Op7piYL1ljAsdQsig=" + [mod."golang.org/x/text"] + version = "v0.16.0" + hash = "sha256-hMTO45upjEuA4sJzGplJT+La2n3oAfHccfYWZuHcH+8=" + [mod."golang.org/x/tools"] + version = "v0.22.1-0.20240829175637-39126e24d653" + hash = "sha256-rSWFraJ8stahqWf4VdJd/ueiR/0acy7F2+hBKDOz9gA=" + [mod."golang.org/x/tools/gopls"] + version = "v0.16.2" + hash = "sha256-26/OFkFEpXEzWYeW/oMfHf+ejkNJgNpxcckdkH6OZcg=" + [mod."golang.org/x/vuln"] + version = "v1.0.4" + hash = "sha256-1HadvTIdPSLPtKtODD3hhl0OByTAN+csvDNUQagzAUw=" + [mod."honnef.co/go/tools"] + version = "v0.4.7" + hash = "sha256-aE44owPaESdPFiCyKUoUmUPchfHASwTuF/LZDlwLGVY=" + [mod."mvdan.cc/gofumpt"] + version = "v0.6.0" + hash = "sha256-2M9yKlAN0Oe88vBfMED0SJE0XMJfzOMd8Bd2ot/ZMko=" + [mod."mvdan.cc/xurls/v2"] + version = "v2.5.0" + hash = "sha256-9hPXZ/t15+LG9fji1gyeWhUrYOr6eGyKYg3a1SmHJpQ=" diff --git a/tools.go b/tools.go new file mode 100644 index 0000000..a6a1bc5 --- /dev/null +++ b/tools.go @@ -0,0 +1,7 @@ +// +build tools + +package main + +import ( + _ "golang.org/x/tools/gopls" +)