Skip to content

Commit 39d0a14

Browse files
authored
Merge pull request #65 from Jannes-Dailidow/main
I needed a moment to review
2 parents 6bf5dfc + c6f4271 commit 39d0a14

File tree

4 files changed

+215
-0
lines changed

4 files changed

+215
-0
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
github.com/DATA-DOG/go-sqlmock v1.5.2
77
github.com/Microsoft/go-winio v0.6.2
88
github.com/atotto/clipboard v0.1.4
9+
github.com/bobg/go-generics/v4 v4.2.0
910
github.com/charmbracelet/bubbles v0.21.0
1011
github.com/charmbracelet/bubbletea v1.3.10
1112
github.com/charmbracelet/lipgloss v1.1.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE
1212
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
1313
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
1414
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
15+
github.com/bobg/go-generics/v4 v4.2.0 h1:c3eX8rlFCRrxFnUepwQIA174JK7WuckbdRHf5ARCl7w=
16+
github.com/bobg/go-generics/v4 v4.2.0/go.mod h1:KVwpxEYErjvcqjJSJqVNZd/JEq3SsQzb9t01+82pZGw=
1517
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
1618
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
1719
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=

internal/db/bun_adapter.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import (
1212
"strings"
1313
"time"
1414

15+
"github.com/bobg/go-generics/v4/slices"
16+
"github.com/toeirei/keymaster/internal/db/tags"
1517
"github.com/toeirei/keymaster/internal/model"
1618
"github.com/uptrace/bun"
1719
)
@@ -201,6 +203,28 @@ func systemKeyModelToModel(skm SystemKeyModel) model.SystemKey {
201203
return model.SystemKey{ID: skm.ID, Serial: skm.Serial, PublicKey: skm.PublicKey, PrivateKey: skm.PrivateKey, IsActive: skm.IsActive}
202204
}
203205

206+
func getMultipleAccountsBun(ctx context.Context, bdb *bun.DB, opts ...func(*bun.SelectQuery) *bun.SelectQuery) ([]model.Account, error) {
207+
// create select
208+
var data []AccountModel
209+
// retrieve data
210+
if err := bdb.NewSelect().Model(&data).Apply(opts...).OrderExpr("label, hostname, username").Scan(ctx); err != nil {
211+
return nil, err
212+
}
213+
// map data to model
214+
return slices.Map(data, accountModelToModel), nil
215+
}
216+
217+
func GetAccountsByTagBun(ctx context.Context, bdb *bun.DB, tag string) ([]model.Account, error) {
218+
tag_qb, err := tags.GetTagQueryBuilder(tag)
219+
if err != nil {
220+
return nil, err
221+
}
222+
223+
return getMultipleAccountsBun(ctx, bdb, func(sq *bun.SelectQuery) *bun.SelectQuery {
224+
return sq.ApplyQueryBuilder(tag_qb)
225+
})
226+
}
227+
204228
// GetAllAccountsBun returns all accounts ordered by label, hostname, username.
205229
func GetAllAccountsBun(bdb *bun.DB) ([]model.Account, error) {
206230
ctx := context.Background()

internal/db/tags/tags.go

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
package tags
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"regexp"
7+
"strings"
8+
9+
"github.com/uptrace/bun"
10+
)
11+
12+
const tagDelimiterChar string = "|"
13+
const tagPatternExp string = `^[a-zA-Z0-9_\-+*/.:~=]+$`
14+
const tagEscapeChar string = "!"
15+
const tagEscapedChars string = `%_[]^-{}`
16+
17+
var tagPattern = regexp.MustCompile(tagPatternExp)
18+
19+
// TODO vendor out to seperate package
20+
func reducex[T any, S ~[]T, U any](s S, f func(T, U) (U, error)) (U, error) {
21+
var zero U
22+
var result U
23+
for _, t := range s {
24+
var err error
25+
result, err = f(t, result)
26+
if err != nil {
27+
return zero, err
28+
}
29+
}
30+
return result, nil
31+
}
32+
33+
func parseTag(expr string, qb bun.QueryBuilder, mode bool, negate bool) (bun.QueryBuilder, error) {
34+
var err error
35+
36+
expr = strings.TrimSpace(expr)
37+
38+
// and
39+
if exprs := splitOnTopLevelChar(expr, '&'); len(exprs) > 1 {
40+
// TODO test
41+
return reducex(exprs, func(expr string, qb bun.QueryBuilder) (bun.QueryBuilder, error) {
42+
return parseTag(expr, qb, true != negate, negate)
43+
})
44+
45+
// for _, expr = range exprs {
46+
// qb, err = parseTag(expr, qb, true != negate, negate)
47+
// if err != nil {
48+
// return nil, err
49+
// }
50+
// }
51+
// return qb, nil
52+
}
53+
54+
// or
55+
if exprs := splitOnTopLevelChar(expr, '|'); len(exprs) > 1 {
56+
// TODO test
57+
return reducex(exprs, func(expr string, qb bun.QueryBuilder) (bun.QueryBuilder, error) {
58+
return parseTag(expr, qb, false != negate, negate)
59+
})
60+
61+
// for _, expr = range exprs {
62+
// qb, err = parseTag(expr, qb, false != negate, negate)
63+
// if err != nil {
64+
// return nil, err
65+
// }
66+
// }
67+
// return qb, nil
68+
}
69+
70+
// negation
71+
expr, negated := strings.CutPrefix(expr, "!")
72+
73+
expr = strings.TrimSpace(expr)
74+
75+
// braces
76+
if strings.HasPrefix(expr, "(") && strings.HasSuffix(expr, ")") {
77+
expr = expr[1 : len(expr)-1] // removes braces
78+
79+
operator := map[bool]string{
80+
true: " AND ",
81+
false: " OR ",
82+
}[mode]
83+
84+
if negated {
85+
// return nil, fmt.Errorf("negating braces is unsupported: %s", expr)
86+
// Does not work because bun is a *****....
87+
// ... is what i would say, but i didn't even check if sql supports it.
88+
// operator += "NOT "
89+
90+
// well, i think i got an idea ^^
91+
negate = !negate
92+
}
93+
94+
qb = qb.WhereGroup(operator, func(qb bun.QueryBuilder) bun.QueryBuilder {
95+
qb, err = parseTag(expr, qb, true != negate, negate)
96+
return qb
97+
})
98+
99+
if err != nil {
100+
return nil, err
101+
}
102+
return qb, nil
103+
}
104+
105+
// raw tag value
106+
{
107+
if !tagPattern.MatchString(expr) {
108+
return nil, fmt.Errorf("invalid tag: %s", expr)
109+
}
110+
111+
// escape special chars just to be sure
112+
for _, c := range tagEscapedChars {
113+
expr = strings.ReplaceAll(expr, string(c), tagEscapeChar+string(c))
114+
}
115+
// enable wildcards
116+
expr = strings.ReplaceAll(expr, "**", "%")
117+
expr = strings.ReplaceAll(expr, "*", "_")
118+
119+
query := map[bool]string{
120+
true: "tag NOT LIKE ? ESCAPE '" + tagEscapeChar + "'",
121+
false: "tag LIKE ? ESCAPE '" + tagEscapeChar + "'",
122+
}[negated != negate]
123+
124+
if mode {
125+
return qb.Where(query, expr), nil
126+
} else {
127+
return qb.WhereOr(query, expr), nil
128+
}
129+
}
130+
}
131+
132+
func splitOnTopLevelChar(expr string, op rune) []string {
133+
var result []string
134+
depth := 0
135+
start := 0
136+
137+
for i, ch := range expr {
138+
switch ch {
139+
case '(':
140+
depth++
141+
case ')':
142+
depth--
143+
case op:
144+
if depth == 0 {
145+
result = append(result, expr[start:i])
146+
start = i + 1
147+
}
148+
}
149+
}
150+
151+
result = append(result, expr[start:])
152+
return result
153+
}
154+
155+
func ValidateTag(tag string) error {
156+
sq := &bun.SelectQuery{}
157+
_, err := parseTag(tag, sq.QueryBuilder(), true, false)
158+
return err
159+
}
160+
161+
func GetTagQueryBuilder(tag string) (func(bun.QueryBuilder) bun.QueryBuilder, error) {
162+
if err := ValidateTag(tag); err != nil {
163+
return nil, err
164+
}
165+
return func(qb bun.QueryBuilder) bun.QueryBuilder {
166+
qb, _ = parseTag(tag, qb, true, false)
167+
return qb
168+
}, nil
169+
}
170+
171+
func SplitTags(tag string) ([]string, error) {
172+
tag, exists_prefix := strings.CutPrefix(tag, tagDelimiterChar)
173+
tag, exists_suffix := strings.CutSuffix(tag, tagDelimiterChar)
174+
if !exists_prefix || !exists_suffix {
175+
return nil, errors.New("Prefix or suffix is missing")
176+
}
177+
178+
return strings.Split(tag, tagDelimiterChar), nil
179+
}
180+
181+
func SplitTagsSafe(tag string) []string {
182+
tags, _ := SplitTags(tag)
183+
return tags
184+
}
185+
186+
func JoinTags(tags []string) string {
187+
return tagDelimiterChar + strings.Join(tags, tagDelimiterChar) + tagDelimiterChar
188+
}

0 commit comments

Comments
 (0)