working db scanner

This commit is contained in:
2026-09-01 01:26:54 +01:00
parent 06ae7f16ec
commit 17ce3314f5
27 changed files with 378 additions and 942 deletions
+2 -1
View File
@@ -1,5 +1,6 @@
*.env
*.sh
*.sqlite3
.data
zampler.files
zampler.sqlite3
internal/server/static
+12 -3
View File
@@ -5,20 +5,29 @@ go 1.25.7
require (
code.aneur.in/go/record v0.0.0-20260704022026-db3f74ccdeb4
code.aneur.in/go/rest v0.0.0-20260710144250-d3e2b415e4ac
code.aneur.in/go/sqaffold v0.0.0-20260831231301-efa486fea8fa
code.aneur.in/go/sqan v0.0.0-20260831231908-19f540e808b6
code.aneur.in/go/sqimple v0.0.0-20260710160754-2a04163d6eff
code.aneur.in/go/validate v0.0.0-20260705223845-8fab67ff77fe
code.aneur.in/go/version v0.0.0-20260706210436-9f229dde97c9
github.com/google/uuid v1.6.0
github.com/gorilla/mux v1.8.1
github.com/rs/zerolog v1.35.1
github.com/spf13/cobra v1.10.2
modernc.org/sqlite v1.57.0
)
require (
code.aneur.in/go/sqan v0.0.0-20260831212500-d13ee5cf13d1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gobwas/glob v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-isatty v0.0.24 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/spf13/pflag v1.0.10 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/sys v0.47.0 // indirect
modernc.org/libc v1.74.4 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
)
+60 -7
View File
@@ -2,29 +2,49 @@ code.aneur.in/go/record v0.0.0-20260704022026-db3f74ccdeb4 h1:cE8tCjhHePkEMBEQbk
code.aneur.in/go/record v0.0.0-20260704022026-db3f74ccdeb4/go.mod h1:nIqf1d1YWOPSVrbBFJY2ZBEMMFSfQCgXL7nFVXOzVqo=
code.aneur.in/go/rest v0.0.0-20260710144250-d3e2b415e4ac h1:rmdYhq+CsQzPqK/tQ6G/DtWIS+IIME47Draei/mR9BM=
code.aneur.in/go/rest v0.0.0-20260710144250-d3e2b415e4ac/go.mod h1:HtXodmPBkxQyYkXn4UHURJEs44a+Jm+4tUaKXj246uE=
code.aneur.in/go/sqan v0.0.0-20260831212500-d13ee5cf13d1 h1:gLmIh9vibSVDqVeIjnukD3wTn2Nc65A3F+ciEnzcFzE=
code.aneur.in/go/sqan v0.0.0-20260831212500-d13ee5cf13d1/go.mod h1:0o6TJqTFx7qPLHiUtUxeN2cLR/xylxtJZyghXamEfy4=
code.aneur.in/go/sqaffold v0.0.0-20260831231301-efa486fea8fa h1:97WLloin1C/OOPQYyyALij+Prsa82gxw0kBvkDnEIb8=
code.aneur.in/go/sqaffold v0.0.0-20260831231301-efa486fea8fa/go.mod h1:a3LqC1+uDSP2adLySuBEQmk/t2HJ+BNmIVMF+eSdC5s=
code.aneur.in/go/sqan v0.0.0-20260831231908-19f540e808b6 h1:zuxg66WgJpxH0dHemFJasJ2i82jnSxL8c3FJdOwQInA=
code.aneur.in/go/sqan v0.0.0-20260831231908-19f540e808b6/go.mod h1:0o6TJqTFx7qPLHiUtUxeN2cLR/xylxtJZyghXamEfy4=
code.aneur.in/go/sqimple v0.0.0-20260710160754-2a04163d6eff h1:DAZPseBNreEYH4XQG087NfQ+PpHIlpYVThRXKmRkupc=
code.aneur.in/go/sqimple v0.0.0-20260710160754-2a04163d6eff/go.mod h1:WT64312MyLtL4EiJWRyBFh+Tl//ls2+B+YsFV+BkPJ0=
code.aneur.in/go/validate v0.0.0-20260705223845-8fab67ff77fe h1:jGSnKHtPM6mmcBjucsqduISqzhnPwZ7s1pTA6D7RWxg=
code.aneur.in/go/validate v0.0.0-20260705223845-8fab67ff77fe/go.mod h1:xGuqmQj25KOUB+vD2L3Z+EDmdhZZB8ZRJK4Csj+LW6U=
code.aneur.in/go/version v0.0.0-20260706210436-9f229dde97c9 h1:OquK0VYWHUts7u/23A6IiZ0PkznhIihj9U0DBSXfeM0=
code.aneur.in/go/version v0.0.0-20260706210436-9f229dde97c9/go.mod h1:G26bUe3Jroua1UDRzJO12ssn6OPcTsizffKJ+5/FK7U=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/gobwas/glob v1.0.0 h1:p+FKbLEIsK1yZ39/OINwFvqNb5oyPY4H8xcy6uYu8dg=
github.com/gobwas/glob v1.0.0/go.mod h1:oWCdo522i2P1n/hMXGNWs7yoV4wy/ciZuUIbvKj5rkc=
github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 h1:LMLX+LgTNWpfvCBdFebv6EsYotImrt/Ppc5cXIriCSo=
github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI=
github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -36,9 +56,42 @@ github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3A
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.29.1 h1:MKgdCV3WykTSPqpVrnxdEDS0HEd2FHpKZDzxzU5LyeI=
modernc.org/cc/v4 v4.29.1/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU=
modernc.org/ccgo/v4 v4.34.6/go.mod h1:SZ8YcN9NG7XVsQYdm6jYBvi8PQP1qi+kqB6OhjqI3Fk=
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/gc/v3 v3.1.4 h1:2g65LGVSmFQrXeITAw97x7hCRvZFcyE1uDP+7Vng7JI=
modernc.org/gc/v3 v3.1.4/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.74.4 h1:fX1Omw4o2/1C2iRkkIsrQTasJQldLhRmuPreXLoWs9k=
modernc.org/libc v1.74.4/go.mod h1:eeQAS9W3sZeKYMFubydxJpII9ybHWshk+7or7bLG9co=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.57.0 h1:qNQP6xnx5M0ISNtlnxoOX0+cD5bJ0/gr9aMmndFczzg=
modernc.org/sqlite v1.57.0/go.mod h1:yCJ2cmAaIkHQ25oXWrF8H4O1lIfPYPR26yCEDj2P3pQ=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
+3 -3
View File
@@ -3,13 +3,13 @@ package api
import (
"net/http"
"code.aneur.in/zampler/zampler/internal/samples"
"code.aneur.in/zampler/zampler/internal/database"
"github.com/gorilla/mux"
"github.com/rs/zerolog"
)
type API struct {
db *samples.DB
db *database.DB
log zerolog.Logger
}
@@ -20,7 +20,7 @@ func (api *API) ConfigureRouter(r *mux.Router) {
r.Path("/file/{id}").Methods(http.MethodGet, http.MethodOptions).HandlerFunc(api.GetFile)
}
func NewAPI(db *samples.DB, log zerolog.Logger) *API {
func NewAPI(db *database.DB, log zerolog.Logger) *API {
api := &API{
db: db,
+43 -29
View File
@@ -19,37 +19,33 @@ func (api *API) GetFile(w http.ResponseWriter, req *http.Request) {
vars := mux.Vars(req)
id := vars["id"]
file, ok := api.db.Get(id)
if !ok {
rest.ErrNotFound.WriteJSON(w)
record, err := api.db.Select("files").Where("id = ?", id).QueryRecord()
if err != nil {
rest.WriteErrorJSON(w, err)
return
}
rest.WriteResponseJSON(w, http.StatusOK, file.DTO())
rest.WriteResponseJSON(w, http.StatusOK, record)
}
func (api *API) GetFileByPath(w http.ResponseWriter, req *http.Request) {
vars := mux.Vars(req)
file, ok := api.db.GetByPath(fmt.Sprintf("/%s", vars["path"]))
if !ok {
rest.ErrNotFound.WriteJSON(w)
return
record, err := api.db.Select("files").Where("rel_path = ?", fmt.Sprintf("/%s", vars["path"])).QueryRecord()
if err != nil {
rest.WriteErrorJSON(w, err)
}
rest.WriteResponseJSON(w, http.StatusOK, file.DTO())
rest.WriteResponseJSON(w, http.StatusOK, record)
}
func (api *API) ListFiles(w http.ResponseWriter, req *http.Request) {
uq := req.URL.Query()
// q := api.db.Query()
// Read in params
filename := ""
path := ""
asc := true
direction := "asc"
sort := "path"
offset := 0
limit := MaxLimit
@@ -62,9 +58,9 @@ func (api *API) ListFiles(w http.ResponseWriter, req *http.Request) {
switch uq.Get("direction") {
case "", "asc":
asc = true
direction = "asc"
case "desc":
asc = false
direction = "desc"
default:
rest.ErrBadRequest.WithMessage("invalid sort direction").WriteJSON(w)
return
@@ -108,30 +104,48 @@ func (api *API) ListFiles(w http.ResponseWriter, req *http.Request) {
}
// Apply params
q := api.db.Query()
if filename != "" {
q.SearchFilename(filename)
} else if path != "" {
q.SearchPath(path)
}
totalCount := q.Count()
query := api.db.Select("files")
switch sort {
case "filename":
q.SortByFilename(asc)
query.OrderBy("filename asc")
case "path":
q.SortByPath(asc)
query.OrderBy("abs_path asc")
}
q.Offset(offset).Limit(limit)
query.Offset(offset).Limit(limit)
rows, err := query.Query()
if err != nil {
rest.WriteErrorJSON(w, err)
return
}
totalCount := 0
for rows.Next() {
totalCount++
}
if err := rows.Err(); err != nil {
rest.WriteErrorJSON(w, err)
return
}
if filename != "" {
query.Where("filename like ?", filename)
} else if path != "" {
query.Where("abs_path like ?", path)
}
records, err := query.QueryRecords()
if err != nil {
rest.WriteErrorJSON(w, err)
return
}
// Output result
output := dto.NewList(q.ResultDTOs()...).
output := dto.NewList(records...).
WithTotalCount(totalCount).
WithParam("sort", sort).
WithDirection(asc).
WithDirection(direction).
WithParam("offset", offset).
WithParam("limit", limit).
MaybeWithParam("filename", filename).
+28 -14
View File
@@ -3,29 +3,33 @@ package cli
import (
"context"
"database/sql"
"fmt"
"net"
"net/http"
"strconv"
"code.aneur.in/go/sqan"
"code.aneur.in/zampler/zampler/internal/api"
"code.aneur.in/zampler/zampler/internal/db"
"code.aneur.in/zampler/zampler/internal/samples"
"code.aneur.in/zampler/zampler/internal/database"
"code.aneur.in/zampler/zampler/internal/server"
"code.aneur.in/zampler/zampler/internal/server/middlewares"
"code.aneur.in/zampler/zampler/internal/types"
"github.com/gorilla/mux"
"github.com/rs/zerolog"
"github.com/spf13/cobra"
_ "modernc.org/sqlite"
)
type CLI struct {
db *db.DB
db *database.DB
log zerolog.Logger
}
func (cli *CLI) Cmd() *cobra.Command {
cmd := &cobra.Command{
Use: "zampler root-dir ...",
Args: cobra.ExactArgs(2),
Args: cobra.ExactArgs(1),
PreRunE: cli.PreRunE,
RunE: cli.RunE,
}
@@ -34,9 +38,10 @@ func (cli *CLI) Cmd() *cobra.Command {
f.StringP("address", "a", "", "HTTP bind address")
f.IntP("port", "p", 7777, "HTTP listen port")
f.StringP("url", "u", "", "frontend URL")
f.StringP("sqlite", "s", "zampler.sqlite3", "path to SQLite database")
f.StringP("log-level", "l", "info", "logging level (trace, debug, info, warn, error)")
return cmd
@@ -51,8 +56,6 @@ func (cli *CLI) PreRunE(cmd *cobra.Command, _ []string) error {
cli.log = cli.log.Level(level)
cli.db = samples.NewDB(cli.log)
return nil
}
@@ -60,18 +63,22 @@ func (cli *CLI) RunE(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
dbFile, rootDir := args[0], args[1]
host, _ := cmd.Flags().GetString("host")
port, _ := cmd.Flags().GetInt("port")
url, _ := cmd.Flags().GetString("url")
dbFile, _ := cmd.Flags().GetString("sqlite")
rootDir := args[0]
// Init database
conn, err := sql.Open("sqlite", dbFile)
if err != nil {
return err
}
cli.db = db.New(ctx, cli.log, conn)
cli.db = database.New(ctx, cli.log, conn)
if err := cli.db.Migrate(); err != nil {
fmt.Println(err)
return err
}
@@ -102,12 +109,19 @@ func (cli *CLI) RunE(cmd *cobra.Command, args []string) error {
// Initial scan of root directories
go func() {
files, err := cli.db.MultiScan(rootDirs...)
if err != nil {
errch <- err
scanner := sqan.NewScanner(rootDir).WithExtensions("aac", "flac", "m4a", "mp3", "ogg", "wav")
for simpleFile := range scanner.Scan() {
file, err := types.NewFile(simpleFile)
if err != nil {
cli.log.Err(err).Send()
continue
}
if err := cli.db.UpdateFile(file); err != nil {
cli.log.Err(err).Send()
continue
}
cli.log.Trace().Msgf("Scanned %s", file.RelativePath)
}
cli.db.Import(files)
}()
cli.log.Info().Int("port", port).Msg("Listening for HTTP connections")
+75
View File
@@ -0,0 +1,75 @@
package database
import (
"context"
"database/sql"
"code.aneur.in/go/record"
"code.aneur.in/go/sqaffold"
"code.aneur.in/go/sqimple"
"github.com/rs/zerolog"
)
type DB struct {
ctx context.Context
log zerolog.Logger
conn *sql.DB
scaffold *sqaffold.Scaffold
}
func (db *DB) Delete(from string) *sqimple.DeleteStatement {
return sqimple.Delete(from).ExecFunc(db.sqimpleExec)
}
func (db *DB) Exec(query string, args ...any) (sql.Result, error) {
return db.scaffold.Exec(query, args...)
}
func (db *DB) Insert(into string) *sqimple.InsertStatement {
return sqimple.Insert(into).ExecFunc(db.sqimpleExec)
}
func (db *DB) Migrate() error {
return db.scaffold.Migrate()
}
func (db *DB) Query(query string, args ...any) ([]record.Record, error) {
return db.scaffold.Query(query, args...)
}
func (db *DB) QueryOne(query string, args ...any) (record.Record, error) {
return db.scaffold.QueryOne(query, args...)
}
func (db *DB) Select(from string) *sqimple.SelectQuery {
return sqimple.Select(from).QueryFunc(db.sqimpleQuery).QueryRowFunc(db.sqimpleQueryRow)
}
func (db *DB) Update(table string) *sqimple.UpdateStatement {
return sqimple.Update(table).ExecFunc(db.sqimpleExec)
}
func (db *DB) sqimpleExec(query sqimple.Query) (sql.Result, error) {
return db.Exec(query.String(), query.Args()...)
}
func (db *DB) sqimpleQuery(query sqimple.Query) (*sql.Rows, error) {
return db.conn.QueryContext(db.ctx, query.String(), query.Args()...)
}
func (db *DB) sqimpleQueryRow(query sqimple.Query) *sql.Row {
return db.conn.QueryRowContext(db.ctx, query.String(), query.Args()...)
}
func New(ctx context.Context, log zerolog.Logger, conn *sql.DB) *DB {
db := &DB{
ctx: ctx,
log: log,
conn: conn,
}
db.scaffold = sqaffold.NewScaffold(db.ctx, conn).WithApp("zampler", db.migration())
return db
}
+25
View File
@@ -0,0 +1,25 @@
package database
import (
"code.aneur.in/zampler/zampler/internal/types"
)
func (db *DB) UpdateFile(file *types.File) error {
_, err := db.Insert("files").
Columns("id", "hash", "root_dir", "abs_path", "rel_dir", "rel_path", "filename", "ext", "size", "modified").
Values(map[string]any{
"id": file.ID,
"hash": file.Hash,
"root_dir": file.RootDir,
"abs_path": file.AbsolutePath,
"rel_dir": file.RelativeDir,
"rel_path": file.RelativePath,
"filename": file.Filename,
"ext": file.Extension,
"size": file.Size,
"modified": file.Modified.UnixMilli(),
}).
Exec()
return err
}
+33
View File
@@ -0,0 +1,33 @@
package database
import "code.aneur.in/go/version"
func (db *DB) migration() version.Migration {
return version.Migration{
"0.1.0": version.Patch{
Up: func() error {
_, err := db.Exec(`
create table files (
id text not null,
hash text,
root_dir text not null,
abs_path text not null,
rel_dir text not null,
rel_path text not null,
filename text not null,
ext text not null,
size integer not null,
modified integer not null,
constraint samples__unq__id unique (id) on conflict replace
)`)
return err
},
Down: func() error {
_, err := db.Exec("drop table files")
return err
},
},
}
}
-17
View File
@@ -1,17 +0,0 @@
package db
import "code.aneur.in/zampler/zampler/internal/types"
func (db *DB) GetConfig(name string) (*types.Config, error) {
row, err := db.QueryOne("select name, value from config where name = ?", name)
if err != nil {
return nil, err
}
return types.NewConfig(row), nil
}
func (db *DB) SetConfig(config *types.Config) error {
_, err := db.Exec("insert into config (name, value) values (?, ?)", config.Name(), config.Value())
return err
}
-47
View File
@@ -1,47 +0,0 @@
package db
import (
"context"
"database/sql"
"code.aneur.in/go/record"
"github.com/rs/zerolog"
)
type DB struct {
Ctx context.Context
Log zerolog.Logger
SQL *sql.DB
}
func (db *DB) Exec(query string, args ...any) (sql.Result, error) {
return db.SQL.ExecContext(db.Ctx, query, args...)
}
func (db *DB) Query(query string, args ...any) ([]record.Record, error) {
rows, err := db.SQL.QueryContext(db.Ctx, query, args...)
if err != nil {
return nil, Err(err)
}
return ScanRecords(rows)
}
func (db *DB) QueryOne(query string, args ...any) (record.Record, error) {
rows, err := db.SQL.QueryContext(db.Ctx, query, args...)
if err != nil {
return nil, Err(err)
}
return ScanOneRecord(rows)
}
func New(ctx context.Context, log zerolog.Logger, sql *sql.DB) *DB {
db := &DB{
Ctx: ctx,
Log: log,
SQL: sql,
}
return db
}
-44
View File
@@ -1,44 +0,0 @@
package db
type Error struct {
StatusCode int
Text string
Previous error
}
func (e *Error) Error() string {
return e.Text
}
func (e *Error) Is(target error) bool {
if other, ok := target.(*Error); ok {
if other.StatusCode > 0 {
if e.StatusCode != other.StatusCode {
return false
}
}
if other.Text != "" {
if e.Text != other.Text {
return false
}
}
}
return true
}
func Err(err error) *Error {
return NewError(501, err.Error(), err)
}
func NewError(statusCode int, text string, previous error) *Error {
err := &Error{
StatusCode: statusCode,
Text: text,
Previous: previous,
}
return err
}
-63
View File
@@ -1,63 +0,0 @@
package db
import (
"code.aneur.in/go/record"
"code.aneur.in/go/version"
"code.aneur.in/zampler/zampler/internal/types"
)
func (db *DB) Migrate() error {
currentVersion, err := db.GetVersion()
if err != nil {
return err
}
migration, err := db.Migration().Match(&version.Constraint{Gt: currentVersion})
if err != nil {
return err
}
versions, _ := migration.Versions()
if len(versions) == 0 {
db.Log.Info().Msg("No migrations needed")
} else {
db.Log.Info().Msg("Migrating")
var updateVersion *version.Version
migrateErr := migration.Up(func(applied, next *version.Version) {
updateVersion = applied
db.Log.Info().Msgf("Migrated to v%s", applied)
})
if updateVersion != nil {
config := types.NewConfig(record.Record{"name": "version", "value": updateVersion.Text})
if err := db.SetConfig(config); err != nil {
db.Log.Err(err).Msg("Failed to save version")
return err
}
}
if migrateErr != nil {
db.Log.Err(migrateErr).Msg("Migration failed")
return migrateErr
}
db.Log.Info().Msg("Migration complete")
}
return nil
}
func (db *DB) RequiresMigration() (bool, error) {
currentVersion, err := db.GetVersion()
if err != nil {
return false, err
}
codeVersion := db.GetVersionInCode()
return currentVersion.Less(codeVersion), nil
}
-25
View File
@@ -1,25 +0,0 @@
package db
import "code.aneur.in/go/version"
func (db *DB) Migration() *version.Migration {
return &version.Migration{
"0.1.0": version.Patch{
Up: func() error {
_, err := db.SQL.ExecContext(db.Ctx, `
create table config (
name text not null,
value text,
constraint config__unq__name unique (name) on conflict replace
)`)
return err
},
Down: func() error {
_, err := db.SQL.ExecContext(db.Ctx, "drop table config")
return err
},
},
}
}
-167
View File
@@ -1,167 +0,0 @@
package db
import (
"database/sql"
"errors"
"fmt"
"code.aneur.in/go/record"
)
func ScanOneRecord(rows *sql.Rows) (record.Record, error) {
records, err := ScanRecords(rows)
if err != nil {
return nil, err
}
if len(records) == 0 {
return nil, errors.New("no rows")
}
return records[0], nil
}
// ScanRecords transforms SQL row data into record.Record objects, making them easier to handle with type safety.
//
// https://go.dev/ref/spec#Types
func ScanRecords(rows *sql.Rows) ([]record.Record, error) {
columns, err := rows.ColumnTypes()
if err != nil {
return nil, err
}
data := []record.Record{}
for rows.Next() {
values := []any{}
for _, column := range columns {
switch column.ScanType().Name() {
case "bool":
var value bool
values = append(values, &value)
case "byte":
var value byte
values = append(values, &value)
case "complex64":
var value complex64
values = append(values, &value)
case "complex128":
var value complex128
values = append(values, &value)
case "float32":
var value float32
values = append(values, &value)
case "float64":
var value float64
values = append(values, &value)
case "int":
var value int
values = append(values, &value)
case "int8":
var value int8
values = append(values, &value)
case "int16":
var value int16
values = append(values, &value)
case "int32":
var value int32
values = append(values, &value)
case "int64":
var value int64
values = append(values, &value)
case "rune":
var value rune
values = append(values, &value)
case "string":
var value string
values = append(values, &value)
case "uint":
var value uint
values = append(values, &value)
case "uint8":
var value uint8
values = append(values, &value)
case "uint16":
var value uint16
values = append(values, &value)
case "uint32":
var value uint32
values = append(values, &value)
case "uint64":
var value uint64
values = append(values, &value)
default:
return nil, fmt.Errorf("unknown scan type %q", column.ScanType().Name())
}
}
err := rows.Scan(values...)
if err != nil {
return nil, err
}
row := record.Record{}
for i, column := range columns {
switch column.ScanType().Name() {
case "bool":
value := values[i].(*bool)
row[column.Name()] = *value
case "byte":
value := values[i].(*byte)
row[column.Name()] = *value
case "complex64":
value := values[i].(*complex64)
row[column.Name()] = *value
case "complex128":
value := values[i].(*complex128)
row[column.Name()] = *value
case "float32":
value := values[i].(*float32)
row[column.Name()] = *value
case "float64":
value := values[i].(*float64)
row[column.Name()] = *value
case "int":
value := values[i].(*int)
row[column.Name()] = *value
case "int8":
value := values[i].(*int8)
row[column.Name()] = *value
case "int16":
value := values[i].(*int16)
row[column.Name()] = *value
case "int32":
value := values[i].(*int32)
row[column.Name()] = *value
case "int64":
value := values[i].(*int64)
row[column.Name()] = *value
case "rune":
value := values[i].(*rune)
row[column.Name()] = *value
case "string":
value := values[i].(*string)
row[column.Name()] = *value
case "uint":
value := values[i].(*uint)
row[column.Name()] = *value
case "uint8":
value := values[i].(*uint8)
row[column.Name()] = *value
case "uint16":
value := values[i].(*uint16)
row[column.Name()] = *value
case "uint32":
value := values[i].(*uint32)
row[column.Name()] = *value
case "uint64":
value := values[i].(*uint64)
row[column.Name()] = *value
}
}
data = append(data, row)
}
return data, nil
}
-30
View File
@@ -1,30 +0,0 @@
package db
import "code.aneur.in/go/version"
func (db *DB) GetVersion() (*version.Version, error) {
config, err := db.GetConfig("version")
if err != nil {
switch err.Error() {
case "SQL logic error: no such table: config (1)":
return version.Parse("0")
}
return nil, err
}
if config == nil {
return version.Parse("0")
}
return version.Parse(config.Value())
}
func (db *DB) GetVersionInCode() *version.Version {
migrations, err := db.Migration().Versions()
if err != nil {
return nil
}
return migrations[len(migrations)-1]
}
+2 -5
View File
@@ -31,11 +31,8 @@ func (l *List[T]) WithCount(name string, value int) *List[T] {
return l
}
func (l *List[T]) WithDirection(asc bool) *List[T] {
if asc {
return l.WithParam("direction", "asc")
}
return l.WithParam("direction", "desc")
func (l *List[T]) WithDirection(direction string) *List[T] {
return l.WithParam("direction", direction)
}
func (l *List[T]) WithParam(name string, value any) *List[T] {
-63
View File
@@ -1,63 +0,0 @@
package samples
import (
"maps"
"github.com/rs/zerolog"
)
type DB struct {
log zerolog.Logger
files Files
pathIndex map[string]string
}
func (db *DB) Files() map[string]*File {
return db.files
}
func (db *DB) Get(id string) (*File, bool) {
if db.files[id] != nil {
return db.files[id], true
}
return nil, false
}
func (db *DB) GetByPath(path string) (*File, bool) {
if db.pathIndex[path] != "" {
id := db.pathIndex[path]
return db.Get(id)
}
return nil, false
}
func (db *DB) Import(files Files) {
maps.Copy(db.files, files)
pathIndex := map[string]string{}
for id, file := range files {
pathIndex[file.Path] = id
}
maps.Copy(db.pathIndex, pathIndex)
}
func (db *DB) Query() *Query {
return NewQuery(db.files)
}
func NewDB(log zerolog.Logger) *DB {
db := &DB{
log: log.With().Str("namespace", "samples.DB").Logger(),
files: Files{},
pathIndex: map[string]string{},
}
return db
}
-98
View File
@@ -1,98 +0,0 @@
package samples
import (
"crypto/sha256"
"encoding/hex"
"os"
"path"
"code.aneur.in/zampler/zampler/internal/dto"
)
type File struct {
RootDir string
Path string
lastHash string
}
func (file *File) AbsPath() string {
return path.Join(file.RootDir, file.Path)
}
func (file *File) DTO() *dto.File {
return &dto.File{
ID: file.ID(),
Path: file.Path,
Size: file.Size(),
Hash: file.Hash(),
}
}
func (f *File) Extension() string {
return parseExtension(f.Path)
}
func (file *File) Filename() string {
return path.Base(file.Path)
}
func (file *File) Hash() string {
if file.lastHash == "" {
file.lastHash, _ = file.safeHash()
}
return file.lastHash
}
func (f *File) ID() string {
b := []byte(f.AbsPath())
sum := sha256.Sum256(b)
return hex.EncodeToString(sum[0:])
}
func (f *File) Info() (os.FileInfo, error) {
return os.Stat(f.AbsPath())
}
func (f *File) Size() int64 {
if info, err := f.Info(); err == nil {
return info.Size()
}
return 0
}
func (file *File) String() string {
return file.AbsPath()
}
func (file *File) safeHash() (string, error) {
b, err := os.ReadFile(file.AbsPath())
if err != nil {
return "", err
}
sum := sha256.Sum256(b)
hash := hex.EncodeToString(sum[0:])
return hash, nil
}
func NewFile(rootDir, path string) (*File, error) {
file := &File{
RootDir: rootDir,
Path: path,
}
hash, err := file.safeHash()
if err != nil {
return nil, err
} else {
file.lastHash = hash
}
return file, nil
}
-13
View File
@@ -1,13 +0,0 @@
package samples
type Files map[string]*File
func (files Files) PathMap() map[string]string {
out := map[string]string{}
for _, file := range files {
out[file.Path] = file.ID()
}
return out
}
-11
View File
@@ -1,11 +0,0 @@
package samples
import "strings"
func parseExtension(path string) string {
if i := strings.LastIndexByte(path, '.'); i > -1 {
return strings.ToLower(path[i+1:])
}
return ""
}
-151
View File
@@ -1,151 +0,0 @@
package samples
import (
"sort"
"strings"
"code.aneur.in/zampler/zampler/internal/dto"
)
type Query struct {
files []*File
sortAsc bool
sortMode string
}
// Len implements [sort.Interface].
func (q *Query) Len() int {
return len(q.files)
}
// Less implements [sort.Interface].
func (q *Query) Less(i int, j int) bool {
a := q.files[i]
b := q.files[j]
switch q.sortMode {
case "filename":
if q.sortAsc {
return a.Filename() < b.Filename()
} else {
return b.Filename() < a.Filename()
}
case "path":
if q.sortAsc {
return a.Path < b.Path
} else {
return b.Path < a.Path
}
}
return true
}
// Swap implements [sort.Interface].
func (q *Query) Swap(i int, j int) {
tmp := q.files[i]
q.files[i] = q.files[j]
q.files[j] = tmp
}
func (q *Query) Count() int {
return len(q.files)
}
func (q *Query) Limit(num int) *Query {
if num > len(q.files) {
num = len(q.files)
}
q.files = q.files[0:num]
return q
}
func (q *Query) Offset(num int) *Query {
if num > len(q.files) {
num = len(q.files)
}
q.files = q.files[num:]
return q
}
func (q *Query) Result() []*File {
return q.files
}
func (q *Query) ResultDTOs() []*dto.File {
dtos := []*dto.File{}
for _, file := range q.Result() {
dtos = append(dtos, file.DTO())
}
return dtos
}
func (q *Query) SearchFilename(text string) *Query {
text = strings.ToLower(text)
filtered := []*File{}
for _, file := range q.files {
filename := strings.ToLower(file.Filename())
if strings.Contains(filename, text) {
filtered = append(filtered, file)
}
}
q.files = filtered
return q
}
func (q *Query) SearchPath(text string) *Query {
text = strings.ToLower(text)
filtered := []*File{}
for _, file := range q.files {
path := strings.ToLower(file.Path)
if strings.Contains(path, text) {
filtered = append(filtered, file)
}
}
q.files = filtered
return q
}
func (q *Query) SortByFilename(asc bool) *Query {
q.sortAsc = asc
q.sortMode = "filename"
sort.Stable(q)
return q
}
func (q *Query) SortByPath(asc bool) *Query {
q.sortAsc = asc
q.sortMode = "path"
sort.Stable(q)
return q
}
func NewQuery(files Files) *Query {
data := []*File{}
for _, file := range files {
data = append(data, file)
}
q := &Query{
files: data,
}
return q
}
-86
View File
@@ -1,86 +0,0 @@
package samples
import (
"errors"
"maps"
"os"
"path"
"slices"
)
var ScanExtensions = []string{"aac", "flac", "m4a", "mp3", "ogg", "wav"}
func (db *DB) MultiScan(rootDirs ...string) (Files, error) {
files := Files{}
for _, rootDir := range rootDirs {
moreFiles, err := db.Scan(rootDir)
if err != nil {
return nil, err
}
maps.Copy(files, moreFiles)
}
return files, nil
}
func (db *DB) Scan(rootDir string) (Files, error) {
if !path.IsAbs(rootDir) {
return nil, errors.New("rootDir must be absolute path")
}
log := db.log.With().Str("rootDir", rootDir).Logger()
log.Debug().Msg("Scanning for files")
paths, err := scanRecursive(rootDir)
if err != nil {
return nil, err
}
l := len(rootDir)
files := Files{}
for _, path := range paths {
file, err := NewFile(rootDir, path[l:])
if err != nil {
log.Warn().Err(err).Str("path", file.Path).Msg("Failed to read file")
}
id := file.ID()
files[id] = file
log.Trace().Str("id", id).Str("path", file.Path).Str("hash", file.Hash()).Msg("Found")
}
log.Debug().Int("files", len(files)).Msg("Completed scan")
return files, nil
}
func scanRecursive(dir string, exts ...string) ([]string, error) {
paths := []string{}
entries, err := os.ReadDir(dir)
if err != nil {
return nil, err
}
for _, entry := range entries {
path := path.Join(dir, entry.Name())
if entry.IsDir() {
subPaths, err := scanRecursive(path)
if err != nil {
return nil, err
}
paths = append(paths, subPaths...)
} else {
ext := parseExtension(path)
if slices.Contains(ScanExtensions, ext) {
paths = append(paths, path)
}
}
}
return paths, nil
}
+41 -41
View File
@@ -1,56 +1,56 @@
package server
import (
"fmt"
"net/http"
"os"
// import (
// "fmt"
// "net/http"
// "os"
"github.com/gorilla/mux"
)
// "github.com/gorilla/mux"
// )
func (srv *Server) ReadFile(w http.ResponseWriter, req *http.Request) {
vars := mux.Vars(req)
// func (srv *Server) ReadFile(w http.ResponseWriter, req *http.Request) {
// vars := mux.Vars(req)
id := vars["id"]
file, ok := srv.db.Get(id)
// id := vars["id"]
// file, ok := srv.db.Get(id)
if !ok {
w.WriteHeader(http.StatusNotFound)
w.Write([]byte{})
return
}
// if !ok {
// w.WriteHeader(http.StatusNotFound)
// w.Write([]byte{})
// return
// }
data, err := os.ReadFile(file.AbsPath())
if err != nil {
srv.log.Err(err).Msg("Failed to read file")
// data, err := os.ReadFile(file.AbsPath())
// if err != nil {
// srv.log.Err(err).Msg("Failed to read file")
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte{})
}
// w.WriteHeader(http.StatusInternalServerError)
// w.Write([]byte{})
// }
w.Header().Set("content-disposition", fmt.Sprintf("attachment; filename=\"%s\"", file.Filename()))
w.Write(data)
}
// w.Header().Set("content-disposition", fmt.Sprintf("attachment; filename=\"%s\"", file.Filename()))
// w.Write(data)
// }
func (srv *Server) ReadFileByPath(w http.ResponseWriter, req *http.Request) {
vars := mux.Vars(req)
// func (srv *Server) ReadFileByPath(w http.ResponseWriter, req *http.Request) {
// vars := mux.Vars(req)
file, ok := srv.db.GetByPath(fmt.Sprintf("/%s", vars["path"]))
// file, ok := srv.db.GetByPath(fmt.Sprintf("/%s", vars["path"]))
if !ok {
w.WriteHeader(http.StatusNotFound)
w.Write([]byte{})
return
}
// if !ok {
// w.WriteHeader(http.StatusNotFound)
// w.Write([]byte{})
// return
// }
data, err := os.ReadFile(file.AbsPath())
if err != nil {
srv.log.Err(err).Msg("Failed to read file")
// data, err := os.ReadFile(file.AbsPath())
// if err != nil {
// srv.log.Err(err).Msg("Failed to read file")
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte{})
}
// w.WriteHeader(http.StatusInternalServerError)
// w.Write([]byte{})
// }
w.Header().Set("content-disposition", fmt.Sprintf("attachment; filename=\"%s\"", file.Filename()))
w.Write(data)
}
// w.Header().Set("content-disposition", fmt.Sprintf("attachment; filename=\"%s\"", file.Filename()))
// w.Write(data)
// }
+5 -5
View File
@@ -7,8 +7,8 @@ import (
"strings"
"code.aneur.in/go/rest"
"code.aneur.in/zampler/zampler/internal/database"
"code.aneur.in/zampler/zampler/internal/dto"
"code.aneur.in/zampler/zampler/internal/samples"
"github.com/gorilla/mux"
"github.com/rs/zerolog"
)
@@ -19,7 +19,7 @@ var staticFiles embed.FS
type Server struct {
url string
db *samples.DB
db *database.DB
log zerolog.Logger
static http.Handler
@@ -28,8 +28,8 @@ type Server struct {
func (srv *Server) ConfigureRouter(r *mux.Router) {
r.Path("/config.json").Methods(http.MethodGet, http.MethodOptions).HandlerFunc(srv.GetConfig)
r.Path("/files/{path:.+}").Methods(http.MethodGet, http.MethodOptions).HandlerFunc(srv.ReadFileByPath)
r.Path("/file/{id}").Methods(http.MethodGet, http.MethodOptions).HandlerFunc(srv.ReadFile)
// r.Path("/files/{path:.+}").Methods(http.MethodGet, http.MethodOptions).HandlerFunc(srv.ReadFileByPath)
// r.Path("/file/{id}").Methods(http.MethodGet, http.MethodOptions).HandlerFunc(srv.ReadFile)
}
func (srv *Server) ConfigureStaticAssets(r *mux.Router) {
@@ -46,7 +46,7 @@ func (srv *Server) GetConfig(w http.ResponseWriter, req *http.Request) {
})
}
func NewServer(db *samples.DB, log zerolog.Logger, url string) *Server {
func NewServer(db *database.DB, log zerolog.Logger, url string) *Server {
srv := &Server{
url: url,
-19
View File
@@ -1,19 +0,0 @@
package types
import "code.aneur.in/go/record"
type Config struct {
Data record.Record
}
func (config *Config) Name() string {
return config.Data.String("name")
}
func (config *Config) Value() string {
return config.Data.String("value")
}
func NewConfig(row record.Record) *Config {
return &Config{Data: row}
}
+49
View File
@@ -0,0 +1,49 @@
package types
import (
"crypto/sha256"
"encoding/hex"
"os"
"time"
"code.aneur.in/go/sqan"
)
type File struct {
ID string `json:"id,omitempty"`
Hash string `json:"hash,omitempty"`
RootDir string `json:"rootDir,omitempty"`
AbsolutePath string `json:"absolutePath,omitempty"`
RelativeDir string `json:"relativeDir,omitempty"`
RelativePath string `json:"relativePath,omitempty"`
Filename string `json:"filename,omitempty"`
Extension string `json:"extension,omitempty"`
Size int64 `json:"size,omitempty"`
Modified time.Time `json:"modified"`
}
func NewFile(other *sqan.File) (*File, error) {
f := &File{
RootDir: other.RootDir,
AbsolutePath: other.AbsolutePath,
RelativeDir: other.RelativeDir,
RelativePath: other.RelativePath,
Filename: other.Filename,
Extension: other.Extension,
Size: other.Size,
Modified: other.Modified,
}
b := []byte(f.AbsolutePath)
sum := sha256.Sum256(b)
f.ID = hex.EncodeToString(sum[0:])
b, err := os.ReadFile(f.AbsolutePath)
if err == nil {
sum = sha256.Sum256(b)
f.Hash = hex.EncodeToString(sum[0:])
}
return f, err
}