rename database package -> query
This commit is contained in:
@@ -5,7 +5,7 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"maxwarden/config"
|
||||
"maxwarden/database"
|
||||
"maxwarden/query"
|
||||
"maxwarden/handlers"
|
||||
"maxwarden/security"
|
||||
"maxwarden/tasks"
|
||||
@@ -22,7 +22,7 @@ func main() {
|
||||
|
||||
config.Init()
|
||||
security.Init()
|
||||
database.Init()
|
||||
query.Init()
|
||||
handlers.Init()
|
||||
tasks.Init()
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package entries
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"maxwarden/database"
|
||||
"maxwarden/query"
|
||||
"maxwarden/security"
|
||||
"maxwarden/users"
|
||||
"sort"
|
||||
@@ -24,7 +24,7 @@ type Secret struct {
|
||||
}
|
||||
|
||||
type EntryFilter struct {
|
||||
Filter database.Filter
|
||||
Filter query.Filter
|
||||
MasterKey string
|
||||
UserId int32
|
||||
}
|
||||
@@ -74,7 +74,7 @@ func Filter(f EntryFilter) ([]Secret, error) {
|
||||
output = OrderByDescription(output, f.Filter.OrderDescending)
|
||||
|
||||
// pagination
|
||||
output = database.PaginateSlice(output, f.Filter)
|
||||
output = query.PaginateSlice(output, f.Filter)
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
. "maragu.dev/gomponents"
|
||||
. "maragu.dev/gomponents/html"
|
||||
|
||||
"maxwarden/database"
|
||||
"maxwarden/query"
|
||||
"maxwarden/entries"
|
||||
|
||||
"maxwarden/middleware"
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
func VaultHxHandler(w http.ResponseWriter, r *http.Request) {
|
||||
identity := middleware.GetIdentity(r)
|
||||
|
||||
filter := database.ParseFilterFromRequest(r)
|
||||
filter := query.ParseFilterFromRequest(r)
|
||||
filter.Pagination.Enabled = true
|
||||
|
||||
entryFilter := entries.EntryFilter{
|
||||
@@ -32,7 +32,7 @@ func VaultHxHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// fetch entities from filter function
|
||||
// this first counts the possible items before pagination
|
||||
searchFilter := entries.EntryFilter{
|
||||
Filter: database.NewFilterFromSearch(filter.Search),
|
||||
Filter: query.NewFilterFromSearch(filter.Search),
|
||||
UserId: identity.UserID,
|
||||
MasterKey: identity.MasterKey,
|
||||
}
|
||||
@@ -46,7 +46,7 @@ func VaultHxHandler(w http.ResponseWriter, r *http.Request) {
|
||||
entryFilter.Filter.Pagination.GeneratePagination(len(searchItems), len(entryList))
|
||||
|
||||
// Col header names and referenced database col names
|
||||
cols := []database.ColInfo{
|
||||
cols := []query.ColInfo{
|
||||
{DbName: "Description", DisplayName: "Description", Sortable: true},
|
||||
{DisplayName: "Username"},
|
||||
{DisplayName: "Password"},
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
package database
|
||||
// This file provides the QueryBuilder API for building dynamic queries.
|
||||
// API for building dynamic SQL queries from structured database filters
|
||||
package query
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
@@ -14,9 +16,9 @@ type QueryBuilder struct {
|
||||
BaseSQL string // initial sql string to build query from
|
||||
Subquery bool // wraps query in parenthesis
|
||||
Single bool // returns single entity
|
||||
Paginate bool
|
||||
ItemsPerPage int
|
||||
PageNum int // index from 1
|
||||
PaginationEnabled bool
|
||||
CurrentPage int
|
||||
MaxItemsPerPage int
|
||||
OrderBy []string
|
||||
OrderDescending bool
|
||||
GroupBy []string
|
||||
@@ -119,6 +121,57 @@ func Get[T any](qb *QueryBuilder, db *sqlx.DB, manualParams ...interface{}) (T,
|
||||
return entity, err
|
||||
}
|
||||
|
||||
// Set query builder properties from a `Filter` object
|
||||
// NOTE: The Filter.Search map does not generate a QueryWhere[] on the builder
|
||||
// Either create them manually for fine-grain control, or use the `SetBuilderWhereFromFilter` instead.
|
||||
func SetBuilderFromFilter(qb *QueryBuilder, f Filter) {
|
||||
qb.PaginationEnabled = f.Pagination.Enabled
|
||||
qb.CurrentPage = f.Pagination.CurrentPage
|
||||
qb.MaxItemsPerPage = f.Pagination.MaxItemsPerPage
|
||||
|
||||
if f.OrderBy != "" {
|
||||
qb.OrderBy = []string{f.OrderBy}
|
||||
qb.OrderDescending = f.OrderDescending
|
||||
}
|
||||
}
|
||||
|
||||
// Append to QueryBuilder.QueryWhere[] array from a Filter.Search hashmap
|
||||
// NOTE: This automatically generate WHERE clause uses the LIKE operator, with the search item wrapped in wildcards.
|
||||
// This is nice when you want to do a simple "search" on a column, but you probably don't want this function if you are doing
|
||||
// something more specific with your search results.
|
||||
//
|
||||
// - Filter.Search keys map to column names
|
||||
// - Filter.Search values map to SQL parameters
|
||||
func SetBuilderWhereFromFilter(qb *QueryBuilder, f Filter) {
|
||||
for k, v := range f.Search {
|
||||
qb.Where = append(qb.Where, QueryFilter{
|
||||
Column: k, Operator: LIKE, Parameter: Wildcard(v),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap the input in SQL wildcards
|
||||
func Wildcard(i interface{}) string {
|
||||
v := reflect.ValueOf(i)
|
||||
|
||||
output := ""
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.String:
|
||||
output = "%" + v.String() + "%"
|
||||
case reflect.Int:
|
||||
output = fmt.Sprintf("%%%d%%", v.Int())
|
||||
case reflect.Float64:
|
||||
output = fmt.Sprintf("%%%f%%", v.Float())
|
||||
case reflect.Bool:
|
||||
output = fmt.Sprintf("%%%t%%", v.Bool())
|
||||
default:
|
||||
output = fmt.Sprintf("%%%v%%", i)
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
// internal functions
|
||||
|
||||
func buildWhere[T any](qb *QueryBuilder) (string, []interface{}, error) {
|
||||
@@ -277,18 +330,18 @@ func buildSelect[T any](qb *QueryBuilder) (string, []interface{}, error) {
|
||||
}
|
||||
|
||||
// pagination
|
||||
if !qb.Single && qb.Paginate {
|
||||
if qb.PageNum <= 0 {
|
||||
qb.PageNum = 1
|
||||
if !qb.Single && qb.PaginationEnabled {
|
||||
if qb.CurrentPage <= 0 {
|
||||
qb.CurrentPage = 1
|
||||
}
|
||||
|
||||
if qb.ItemsPerPage <= 0 {
|
||||
qb.ItemsPerPage = 10
|
||||
if qb.MaxItemsPerPage <= 0 {
|
||||
qb.MaxItemsPerPage = 10
|
||||
}
|
||||
|
||||
sql += fmt.Sprintf("LIMIT %d ", qb.ItemsPerPage)
|
||||
sql += fmt.Sprintf("LIMIT %d ", qb.MaxItemsPerPage)
|
||||
|
||||
offset := (qb.PageNum - 1) * qb.ItemsPerPage
|
||||
offset := (qb.CurrentPage - 1) * qb.MaxItemsPerPage
|
||||
sql += fmt.Sprintf("OFFSET %d", offset)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package database
|
||||
package query
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
@@ -1,4 +1,4 @@
|
||||
package database
|
||||
package query
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -3,7 +3,7 @@ package ui
|
||||
import (
|
||||
. "maxwarden/basic"
|
||||
|
||||
"maxwarden/database"
|
||||
"maxwarden/query"
|
||||
|
||||
. "maragu.dev/gomponents"
|
||||
hx "maragu.dev/gomponents-htmx"
|
||||
@@ -20,7 +20,7 @@ const FORM_PAGINATION_SUFFIX = "_paginationForm"
|
||||
func BindSearch(elId string, identifier string) Node {
|
||||
return Group{
|
||||
FormAttr(elId + FORM_BIND_SUFFIX),
|
||||
Name(database.SEARCH_URL_KEY_PREFIX + identifier),
|
||||
Name(query.SEARCH_URL_KEY_PREFIX + identifier),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ type AutoTableOptions struct {
|
||||
|
||||
// THE TABLE
|
||||
// Note that "aboveTable" node is not swapped with HTMX, but "belowTable" is.
|
||||
func AutoTable[E any](tableId string, url string, cols []database.ColInfo, f database.Filter, entities []E, aboveTable Node, rowComponent func(E) Node, belowTable Node, opts AutoTableOptions) Node {
|
||||
func AutoTable[E any](tableId string, url string, cols []query.ColInfo, f query.Filter, entities []E, aboveTable Node, rowComponent func(E) Node, belowTable Node, opts AutoTableOptions) Node {
|
||||
paginationButton := func(icon string, page int) Node {
|
||||
return Button(
|
||||
InlineStyle(`
|
||||
@@ -58,7 +58,7 @@ func AutoTable[E any](tableId string, url string, cols []database.ColInfo, f dat
|
||||
}
|
||||
`),
|
||||
Icon(icon, 16),
|
||||
hx.Get(url+database.QueryParamsFromPagenum(page, f)),
|
||||
hx.Get(url+query.QueryParamsFromPagenum(page, f)),
|
||||
hx.Swap(CSSID(tableId)),
|
||||
hx.Target(CSSID(tableId)),
|
||||
hx.Select(CSSID(tableId)),
|
||||
@@ -83,9 +83,9 @@ func AutoTable[E any](tableId string, url string, cols []database.ColInfo, f dat
|
||||
hx.Swap("outerHTML"),
|
||||
hx.Target(CSSID(tableId)),
|
||||
hx.Select(CSSID(tableId)),
|
||||
Input(Type("hidden"), Name(database.ORDER_BY_URL_KEY), Value(f.OrderBy)),
|
||||
Input(Type("hidden"), Name(database.ORDER_DESC_URL_KEY), Value(ToString(f.OrderDescending))),
|
||||
Input(Type("hidden"), Name(database.ITEMS_PER_PAGE_URL_KEY), Value(ToString(f.Pagination.MaxItemsPerPage))),
|
||||
Input(Type("hidden"), Name(query.ORDER_BY_URL_KEY), Value(f.OrderBy)),
|
||||
Input(Type("hidden"), Name(query.ORDER_DESC_URL_KEY), Value(ToString(f.OrderDescending))),
|
||||
Input(Type("hidden"), Name(query.ITEMS_PER_PAGE_URL_KEY), Value(ToString(f.Pagination.MaxItemsPerPage))),
|
||||
),
|
||||
},
|
||||
),
|
||||
@@ -123,9 +123,9 @@ func AutoTable[E any](tableId string, url string, cols []database.ColInfo, f dat
|
||||
// ---- TABLE HEADER ----
|
||||
THead(
|
||||
Tr(
|
||||
Map(cols, func(col database.ColInfo) Node {
|
||||
Map(cols, func(col query.ColInfo) Node {
|
||||
return Th(
|
||||
If(col.DisplayPosition == database.COL_POS_RIGHT,
|
||||
If(col.DisplayPosition == query.COL_POS_RIGHT,
|
||||
InlineStyle("$me { text-align: right; }"),
|
||||
),
|
||||
InlineStyle(`
|
||||
@@ -147,7 +147,7 @@ func AutoTable[E any](tableId string, url string, cols []database.ColInfo, f dat
|
||||
),
|
||||
If(col.Sortable,
|
||||
Group{
|
||||
hx.Get(url + database.QueryParamsFromOrderBy(col.DbName, !f.OrderDescending && (col.DbName == f.OrderBy), f)),
|
||||
hx.Get(url + query.QueryParamsFromOrderBy(col.DbName, !f.OrderDescending && (col.DbName == f.OrderBy), f)),
|
||||
hx.Swap(CSSID(tableId)),
|
||||
hx.Target(CSSID(tableId)),
|
||||
hx.Select(CSSID(tableId)),
|
||||
@@ -175,10 +175,10 @@ func AutoTable[E any](tableId string, url string, cols []database.ColInfo, f dat
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
`),
|
||||
If(col.DisplayPosition == database.COL_POS_LEFT,
|
||||
If(col.DisplayPosition == query.COL_POS_LEFT,
|
||||
InlineStyle("$me { flex-direction: row; }"),
|
||||
),
|
||||
If(col.DisplayPosition == database.COL_POS_RIGHT,
|
||||
If(col.DisplayPosition == query.COL_POS_RIGHT,
|
||||
InlineStyle("$me { flex-direction: row-reverse; }"),
|
||||
),
|
||||
Text(col.DisplayName),
|
||||
@@ -267,11 +267,11 @@ func AutoTable[E any](tableId string, url string, cols []database.ColInfo, f dat
|
||||
hx.Swap("outerHTML"),
|
||||
|
||||
MapMapWithKey(f.Search, func(s string, v string) Node {
|
||||
return Input(Type("hidden"), Name(database.SEARCH_URL_KEY_PREFIX+s), Value(v))
|
||||
return Input(Type("hidden"), Name(query.SEARCH_URL_KEY_PREFIX+s), Value(v))
|
||||
}),
|
||||
|
||||
Input(Type("hidden"), Name(database.ORDER_BY_URL_KEY), Value(f.OrderBy)),
|
||||
Input(Type("hidden"), Name(database.ORDER_DESC_URL_KEY), Value(ToString(f.OrderDescending))),
|
||||
Input(Type("hidden"), Name(query.ORDER_BY_URL_KEY), Value(f.OrderBy)),
|
||||
Input(Type("hidden"), Name(query.ORDER_DESC_URL_KEY), Value(ToString(f.OrderDescending))),
|
||||
Select(
|
||||
IfElse(opts.Compact,
|
||||
InlineStyle("$me { padding: $2; }"),
|
||||
@@ -287,7 +287,7 @@ func AutoTable[E any](tableId string, url string, cols []database.ColInfo, f dat
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
`),
|
||||
Name(database.ITEMS_PER_PAGE_URL_KEY),
|
||||
Name(query.ITEMS_PER_PAGE_URL_KEY),
|
||||
Option(If(f.Pagination.MaxItemsPerPage == 5, Selected()), Text("5"), Value("5")),
|
||||
Option(If(f.Pagination.MaxItemsPerPage == 10, Selected()), Text("10"), Value("10")),
|
||||
Option(If(f.Pagination.MaxItemsPerPage == 25, Selected()), Text("25"), Value("25")),
|
||||
@@ -355,17 +355,17 @@ func AutotableSearch(c ...Node) Node {
|
||||
// for simple datasets because why not. This also gives you the option to "upgrade"
|
||||
// to the "full" table later on, since you are using the same api
|
||||
func AutoTableLite[E any](columnNames []string, entities []E, rowComponent func(E) Node, opts AutoTableOptions) Node {
|
||||
cols := []database.ColInfo{}
|
||||
cols := []query.ColInfo{}
|
||||
|
||||
for _, v := range columnNames {
|
||||
cols = append(cols, database.ColInfo{DisplayName: v})
|
||||
cols = append(cols, query.ColInfo{DisplayName: v})
|
||||
}
|
||||
|
||||
return AutoTable(
|
||||
"",
|
||||
"",
|
||||
cols,
|
||||
database.Filter{},
|
||||
query.Filter{},
|
||||
entities,
|
||||
nil,
|
||||
rowComponent,
|
||||
|
||||
@@ -2,7 +2,7 @@ package users
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"maxwarden/database"
|
||||
"maxwarden/query"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
@@ -19,38 +19,38 @@ type User struct {
|
||||
}
|
||||
|
||||
func FetchById(id int32) (User, error) {
|
||||
qb := &database.QueryBuilder{}
|
||||
qb := &query.QueryBuilder{}
|
||||
qb.BaseSQL = "SELECT * FROM users u WHERE u.id = ?"
|
||||
|
||||
return database.Get[User](qb, database.DB, id)
|
||||
return query.Get[User](qb, query.DB, id)
|
||||
}
|
||||
|
||||
func FetchByUsername(username string) (User, error) {
|
||||
qb := &database.QueryBuilder{}
|
||||
qb := &query.QueryBuilder{}
|
||||
qb.BaseSQL = "SELECT * FROM users u WHERE u.username = ?"
|
||||
|
||||
return database.Get[User](qb, database.DB, username)
|
||||
return query.Get[User](qb, query.DB, username)
|
||||
}
|
||||
|
||||
func FetchSecurityStamp(userid int) (string, error) {
|
||||
qb := &database.QueryBuilder{}
|
||||
qb := &query.QueryBuilder{}
|
||||
qb.BaseSQL = "SELECT u.security_stamp FROM users u WHERE u.id = ?"
|
||||
|
||||
return database.Get[string](qb, database.DB, userid)
|
||||
return query.Get[string](qb, query.DB, userid)
|
||||
}
|
||||
|
||||
func Update(user User) (sql.Result, error) {
|
||||
qb := &database.QueryBuilder{}
|
||||
qb := &query.QueryBuilder{}
|
||||
qb.BaseSQL = "UPDATE users"
|
||||
|
||||
qb.Setters = []database.QuerySetter{
|
||||
qb.Setters = []query.QuerySetter{
|
||||
{Column: "failed_attempts", Parameter: user.FailedAttempts},
|
||||
{Column: "data", Parameter: user.Data},
|
||||
}
|
||||
|
||||
qb.Where = []database.QueryFilter{
|
||||
{Column: "id", Parameter: user.ID},
|
||||
qb.Where = []query.QueryFilter{
|
||||
{Column: "id", Operator: query.EQ, Parameter: user.ID},
|
||||
}
|
||||
|
||||
return database.Update[User](qb, database.DB)
|
||||
return query.Update[User](qb, query.DB)
|
||||
}
|
||||
|
||||
12
vendor/modules.txt
vendored
12
vendor/modules.txt
vendored
@@ -1,13 +1,7 @@
|
||||
# github.com/PuerkitoBio/goquery v1.10.2
|
||||
## explicit; go 1.23
|
||||
# github.com/andybalholm/cascadia v1.3.3
|
||||
## explicit; go 1.16
|
||||
# github.com/aymerick/douceur v0.2.0
|
||||
## explicit
|
||||
github.com/aymerick/douceur/css
|
||||
github.com/aymerick/douceur/parser
|
||||
# github.com/btcsuite/btcd v0.20.1-beta
|
||||
## explicit; go 1.12
|
||||
# github.com/btcsuite/btcutil v1.0.2
|
||||
## explicit; go 1.13
|
||||
github.com/btcsuite/btcutil/base58
|
||||
@@ -72,8 +66,6 @@ github.com/joho/godotenv
|
||||
github.com/jonboulle/clockwork
|
||||
# github.com/kr/pretty v0.3.1
|
||||
## explicit; go 1.12
|
||||
# github.com/kr/text v0.2.0
|
||||
## explicit
|
||||
# github.com/mattn/go-sqlite3 v1.14.24
|
||||
## explicit; go 1.19
|
||||
github.com/mattn/go-sqlite3
|
||||
@@ -90,8 +82,6 @@ github.com/pmezard/go-difflib/difflib
|
||||
# github.com/robfig/cron/v3 v3.0.1
|
||||
## explicit; go 1.12
|
||||
github.com/robfig/cron/v3
|
||||
# github.com/rogpeppe/go-internal v1.12.0
|
||||
## explicit; go 1.20
|
||||
# github.com/sethvargo/go-diceware v0.5.0
|
||||
## explicit; go 1.22
|
||||
github.com/sethvargo/go-diceware/diceware
|
||||
@@ -110,8 +100,6 @@ go.uber.org/atomic
|
||||
## explicit; go 1.23.0
|
||||
golang.org/x/crypto/bcrypt
|
||||
golang.org/x/crypto/blowfish
|
||||
# golang.org/x/exp v0.0.0-20250305212735-054e65f0b394
|
||||
## explicit; go 1.23.0
|
||||
# golang.org/x/net v0.37.0
|
||||
## explicit; go 1.23.0
|
||||
golang.org/x/net/html
|
||||
|
||||
Reference in New Issue
Block a user