Fix incorrect snippet queries (and generify some utilities)

This commit is contained in:
Ben Visness 2024-06-20 19:17:06 -05:00
parent 06781b3b16
commit 5427092708
16 changed files with 82 additions and 78 deletions

7
go.mod
View File

@ -44,7 +44,7 @@ require (
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dlclark/regexp2 v1.4.0 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect
github.com/evanw/esbuild v0.21.4 // indirect github.com/evanw/esbuild v0.21.4
github.com/huandu/xstrings v1.3.2 // indirect github.com/huandu/xstrings v1.3.2 // indirect
github.com/imdario/mergo v0.3.12 // indirect github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect
@ -59,11 +59,10 @@ require (
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/teambition/rrule-go v1.7.2 // indirect github.com/teambition/rrule-go v1.7.2 // indirect
go.uber.org/atomic v1.10.0 // indirect go.uber.org/atomic v1.10.0 // indirect
golang.org/x/net v0.6.0 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7 // indirect golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.5.0 // indirect golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect golang.org/x/text v0.7.0 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

14
go.sum
View File

@ -117,8 +117,8 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@ -282,8 +282,6 @@ github.com/teacat/noire v1.1.0/go.mod h1:cetGlnqr+9yKJcFgRgYXOWJY66XIrrjUsGBwNlN
github.com/teambition/rrule-go v1.7.2 h1:goEajFWYydfCgavn2m/3w5U+1b3PGqPUHx/fFSVfTy0= github.com/teambition/rrule-go v1.7.2 h1:goEajFWYydfCgavn2m/3w5U+1b3PGqPUHx/fFSVfTy0=
github.com/teambition/rrule-go v1.7.2/go.mod h1:mBJ1Ht5uboJ6jexKdNUJg2NcwP8uUMNvStWXlJD3MvU= github.com/teambition/rrule-go v1.7.2/go.mod h1:mBJ1Ht5uboJ6jexKdNUJg2NcwP8uUMNvStWXlJD3MvU=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/wellington/go-libsass v0.9.2 h1:6Ims04UDdBs6/CGSVK5JC8FNikR5ssrsMMKE/uaO5Q8=
github.com/wellington/go-libsass v0.9.2/go.mod h1:mxgxgam0N0E+NAUMHLcu20Ccfc3mVpDkyrLDayqfiTs=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.3.6/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.6/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
@ -310,6 +308,8 @@ golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs= golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs=
@ -339,8 +339,6 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -350,8 +348,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7 h1:ZrnxWX62AgTKOSagEqxvb3ffipvEDX2pl7E1TdqLqIc= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -402,8 +400,6 @@ golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=

View File

@ -129,7 +129,7 @@ func FetchEmbed(ctx context.Context, urlStr string, httpClient *http.Client, max
filename := "" filename := ""
u, err := url.Parse(urlStr) u, err := url.Parse(urlStr)
if err == nil { if err == nil {
lastSlash := utils.IntMax(strings.LastIndex(u.Path, "/"), 0) lastSlash := utils.Max(strings.LastIndex(u.Path, "/"), 0)
filename = u.Path[lastSlash:] filename = u.Path[lastSlash:]
} }
result := Embed{ result := Embed{

View File

@ -99,16 +99,19 @@ func FetchSnippets(
TRUE TRUE
`, `,
) )
allIDs := make([]int, 0, len(q.IDs)+len(tagSnippetIDs)+len(projectSnippetIDs)) allSnippetIDs := make([]int, 0, len(q.IDs)+len(tagSnippetIDs)+len(projectSnippetIDs))
allIDs = append(allIDs, q.IDs...) allSnippetIDs = append(allSnippetIDs, q.IDs...)
allIDs = append(allIDs, tagSnippetIDs...) allSnippetIDs = append(allSnippetIDs, tagSnippetIDs...)
allIDs = append(allIDs, projectSnippetIDs...) allSnippetIDs = append(allSnippetIDs, projectSnippetIDs...)
if len(allIDs) > 0 && len(q.OwnerIDs) > 0 { if len(allSnippetIDs) == 0 {
qb.Add(`AND (snippet.id = ANY ($?) OR snippet.owner_id = ANY ($?))`, allIDs, q.OwnerIDs) // We already managed to filter out all snippets, and all further
// parts of this query are more filters, so we can just fail everything
// else from right here.
qb.Add(`AND FALSE`)
} else if len(q.OwnerIDs) > 0 {
qb.Add(`AND (snippet.id = ANY ($?) OR snippet.owner_id = ANY ($?))`, allSnippetIDs, q.OwnerIDs)
} else { } else {
if len(allIDs) > 0 { qb.Add(`AND snippet.id = ANY ($?)`, allSnippetIDs)
qb.Add(`AND snippet.id = ANY ($?)`, allIDs)
}
if len(q.OwnerIDs) > 0 { if len(q.OwnerIDs) > 0 {
qb.Add(`AND snippet.owner_id = ANY ($?)`, q.OwnerIDs) qb.Add(`AND snippet.owner_id = ANY ($?)`, q.OwnerIDs)
} }

View File

@ -289,7 +289,7 @@ func seedProject(ctx context.Context, tx pgx.Tx, input models.Project, owners []
input.ForumEnabled, input.BlogEnabled, input.ForumEnabled, input.BlogEnabled,
utils.OrDefault(input.DateCreated, time.Now()), utils.OrDefault(input.DateCreated, time.Now()),
) )
latestProjectId = utils.IntMax(latestProjectId, project.ID) latestProjectId = utils.Max(latestProjectId, project.ID)
// Create forum (even if unused) // Create forum (even if unused)
forum := db.MustQueryOne[models.Subforum](ctx, tx, forum := db.MustQueryOne[models.Subforum](ctx, tx,

View File

@ -117,7 +117,7 @@
<legend>Owners</legend> <legend>Owners</legend>
<div class="pa3"> <div class="pa3">
<div class="flex"> <div class="flex">
<input class="flex-grow-1 no-border bl bt bb br-0" id="owner_name" type="text" placeholder="Enter a username" /> <input class="flex-grow-1 no-border bl bt bb br-0" id="owner_name" type="text" placeholder="Enter another owner's username" />
<button class="flex no-padding no-border pa3 bt br bb bl-0 also-focus" id="owner_add"><span class="flex w1">{{ svg "add" }}</span></button> <button class="flex no-padding no-border pa3 bt br bb bl-0 also-focus" id="owner_add"><span class="flex w1">{{ svg "add" }}</span></button>
</div> </div>
<div id="owners_error" class="f6"></div> <div id="owners_error" class="f6"></div>
@ -163,11 +163,13 @@
</legend> </legend>
<div class="pa3 input-group"> <div class="pa3 input-group">
<div id="links" class="flex flex-column g3 relative"> <div id="links" class="flex flex-column g3 relative">
<div>Primary Links</div>
<div>Secondary Links</div>
</div> </div>
<template id="link_row"> <template id="link_row">
<div class="link_row w-100 flex flex-row items-center" data-tmpl="root"> <div class="link_row w-100 flex flex-row items-center" data-tmpl="root">
<span class="link_handle svgicon pr3 pointer grab" onmousedown="startLinkDrag(event)">{{ svg "draggable" }}</span> <span class="link_handle svgicon pr3 pointer grab" onmousedown="startLinkDrag(event)">{{ svg "draggable" }}</span>
<input data-tmpl="nameInput" class="link_name mr3 w4" type="text" placeholder="Name" /> <input data-tmpl="nameInput" class="link_name mr3 w5" type="text" placeholder="Name" />
<input data-tmpl="urlInput" class="link_url flex-grow-1" type="url" placeholder="Link" /> <input data-tmpl="urlInput" class="link_url flex-grow-1" type="url" placeholder="Link" />
<a class="delete_link svgicon link--normal pl3 f3" href="javascript:;" onclick="deleteLink(event)">{{ svg "delete" }}</a> <a class="delete_link svgicon link--normal pl3 f3" href="javascript:;" onclick="deleteLink(event)">{{ svg "delete" }}</a>
</div> </div>

View File

@ -44,7 +44,7 @@ func getTwitchUsersByLogin(ctx context.Context, logins []string) ([]twitchUser,
for i := 0; i < numChunks; i++ { for i := 0; i < numChunks; i++ {
query := url.Values{} query := url.Values{}
query.Add("first", "100") query.Add("first", "100")
for _, login := range logins[i*100 : utils.IntMin((i+1)*100, len(logins))] { for _, login := range logins[i*100 : utils.Min((i+1)*100, len(logins))] {
query.Add("login", login) query.Add("login", login)
} }
req, err := http.NewRequestWithContext(ctx, "GET", buildUrl("/users", query.Encode()), nil) req, err := http.NewRequestWithContext(ctx, "GET", buildUrl("/users", query.Encode()), nil)
@ -94,7 +94,7 @@ func getStreamStatus(ctx context.Context, twitchIDs []string) ([]streamStatus, e
for i := 0; i < numChunks; i++ { for i := 0; i < numChunks; i++ {
query := url.Values{} query := url.Values{}
query.Add("first", "100") query.Add("first", "100")
for _, tid := range twitchIDs[i*100 : utils.IntMin((i+1)*100, len(twitchIDs))] { for _, tid := range twitchIDs[i*100 : utils.Min((i+1)*100, len(twitchIDs))] {
query.Add("user_id", tid) query.Add("user_id", tid)
} }
req, err := http.NewRequestWithContext(ctx, "GET", buildUrl("/streams", query.Encode()), nil) req, err := http.NewRequestWithContext(ctx, "GET", buildUrl("/streams", query.Encode()), nil)

View File

@ -8,6 +8,7 @@ import (
"time" "time"
"git.handmade.network/hmn/hmn/src/oops" "git.handmade.network/hmn/hmn/src/oops"
"golang.org/x/exp/constraints"
) )
// Returns the provided value, or a default value if the input was zero. // Returns the provided value, or a default value if the input was zero.
@ -37,29 +38,26 @@ func Must1[T any](v T, err error) T {
return v return v
} }
func IntMin(a, b int) int { func Min[T constraints.Ordered](a, b T) T {
if a < b { if a < b {
return a return a
} }
return b return b
} }
func IntMax(a, b int) int { func Max[T constraints.Ordered](a, b T) T {
if a > b { if a > b {
return a return a
} }
return b return b
} }
func IntClamp(min, t, max int) int { func Clamp[T constraints.Ordered](min, t, max T) T {
return IntMax(min, IntMin(t, max)) return Max(min, Min(t, max))
} }
func Int64Max(a, b int64) int64 { func ClampSlice[T any](s []T, max int) []T {
if a > b { return s[:Min(len(s), max)]
return a
}
return b
} }
func DurationRoundUp(d time.Duration, interval time.Duration) time.Duration { func DurationRoundUp(d time.Duration, interval time.Duration) time.Duration {
@ -67,7 +65,7 @@ func DurationRoundUp(d time.Duration, interval time.Duration) time.Duration {
} }
func NumPages(numThings, thingsPerPage int) int { func NumPages(numThings, thingsPerPage int) int {
return IntMax(int(math.Ceil(float64(numThings)/float64(thingsPerPage))), 1) return Max(int(math.Ceil(float64(numThings)/float64(thingsPerPage))), 1)
} }
/* /*

View File

@ -102,8 +102,8 @@ func BlogIndex(c *RequestContext) ResponseData {
FirstUrl: c.UrlContext.BuildBlog(1), FirstUrl: c.UrlContext.BuildBlog(1),
LastUrl: c.UrlContext.BuildBlog(numPages), LastUrl: c.UrlContext.BuildBlog(numPages),
PreviousUrl: c.UrlContext.BuildBlog(utils.IntClamp(1, page-1, numPages)), PreviousUrl: c.UrlContext.BuildBlog(utils.Clamp(1, page-1, numPages)),
NextUrl: c.UrlContext.BuildBlog(utils.IntClamp(1, page+1, numPages)), NextUrl: c.UrlContext.BuildBlog(utils.Clamp(1, page+1, numPages)),
}, },
ShowContent: len(entries) <= 5, ShowContent: len(entries) <= 5,

View File

@ -53,8 +53,8 @@ func Feed(c *RequestContext) ResponseData {
FirstUrl: hmnurl.BuildFeed(), FirstUrl: hmnurl.BuildFeed(),
LastUrl: hmnurl.BuildFeedWithPage(numPages), LastUrl: hmnurl.BuildFeedWithPage(numPages),
NextUrl: hmnurl.BuildFeedWithPage(utils.IntClamp(1, page+1, numPages)), NextUrl: hmnurl.BuildFeedWithPage(utils.Clamp(1, page+1, numPages)),
PreviousUrl: hmnurl.BuildFeedWithPage(utils.IntClamp(1, page-1, numPages)), PreviousUrl: hmnurl.BuildFeedWithPage(utils.Clamp(1, page-1, numPages)),
} }
posts, err := fetchAllPosts(c, (page-1)*feedPostsPerPage, feedPostsPerPage) posts, err := fetchAllPosts(c, (page-1)*feedPostsPerPage, feedPostsPerPage)

View File

@ -198,8 +198,8 @@ func Forum(c *RequestContext) ResponseData {
FirstUrl: c.UrlContext.BuildForum(currentSubforumSlugs, 1), FirstUrl: c.UrlContext.BuildForum(currentSubforumSlugs, 1),
LastUrl: c.UrlContext.BuildForum(currentSubforumSlugs, numPages), LastUrl: c.UrlContext.BuildForum(currentSubforumSlugs, numPages),
NextUrl: c.UrlContext.BuildForum(currentSubforumSlugs, utils.IntClamp(1, page+1, numPages)), NextUrl: c.UrlContext.BuildForum(currentSubforumSlugs, utils.Clamp(1, page+1, numPages)),
PreviousUrl: c.UrlContext.BuildForum(currentSubforumSlugs, utils.IntClamp(1, page-1, numPages)), PreviousUrl: c.UrlContext.BuildForum(currentSubforumSlugs, utils.Clamp(1, page-1, numPages)),
}, },
Subforums: subforums, Subforums: subforums,
}, c.Perf) }, c.Perf)
@ -375,8 +375,8 @@ func ForumThread(c *RequestContext) ResponseData {
FirstUrl: c.UrlContext.BuildForumThread(currentSubforumSlugs, thread.ID, thread.Title, 1), FirstUrl: c.UrlContext.BuildForumThread(currentSubforumSlugs, thread.ID, thread.Title, 1),
LastUrl: c.UrlContext.BuildForumThread(currentSubforumSlugs, thread.ID, thread.Title, numPages), LastUrl: c.UrlContext.BuildForumThread(currentSubforumSlugs, thread.ID, thread.Title, numPages),
NextUrl: c.UrlContext.BuildForumThread(currentSubforumSlugs, thread.ID, thread.Title, utils.IntClamp(1, page+1, numPages)), NextUrl: c.UrlContext.BuildForumThread(currentSubforumSlugs, thread.ID, thread.Title, utils.Clamp(1, page+1, numPages)),
PreviousUrl: c.UrlContext.BuildForumThread(currentSubforumSlugs, thread.ID, thread.Title, utils.IntClamp(1, page-1, numPages)), PreviousUrl: c.UrlContext.BuildForumThread(currentSubforumSlugs, thread.ID, thread.Title, utils.Clamp(1, page-1, numPages)),
} }
postsAndStuff, err := hmndata.FetchPosts(c, c.Conn, c.CurrentUser, hmndata.PostsQuery{ postsAndStuff, err := hmndata.FetchPosts(c, c.Conn, c.CurrentUser, hmndata.PostsQuery{

View File

@ -117,7 +117,7 @@ func csrfMiddleware(h Handler) Handler {
func securityTimerMiddleware(duration time.Duration, h Handler) Handler { func securityTimerMiddleware(duration time.Duration, h Handler) Handler {
// NOTE(asaf): Will make sure that the request takes at least `duration` to finish. Adds a 10% random duration. // NOTE(asaf): Will make sure that the request takes at least `duration` to finish. Adds a 10% random duration.
return func(c *RequestContext) ResponseData { return func(c *RequestContext) ResponseData {
additionalDuration := time.Duration(rand.Int63n(utils.Int64Max(1, int64(duration)/10))) additionalDuration := time.Duration(rand.Int63n(utils.Max(1, int64(duration)/10)))
timer := time.NewTimer(duration + additionalDuration) timer := time.NewTimer(duration + additionalDuration)
res := h(c) res := h(c)
select { select {

View File

@ -16,7 +16,7 @@ func getPageInfo(
totalPages int, totalPages int,
ok bool, ok bool,
) { ) {
totalPages = utils.IntMax(1, int(math.Ceil(float64(totalItems)/float64(itemsPerPage)))) totalPages = utils.Max(1, int(math.Ceil(float64(totalItems)/float64(itemsPerPage))))
ok = true ok = true
page = 1 page = 1

View File

@ -28,7 +28,7 @@ func ParsePageNumber(
} }
} }
if page < 1 || numPages < page { if page < 1 || numPages < page {
return utils.IntClamp(1, page, numPages), false return utils.Clamp(1, page, numPages), false
} }
return page, true return page, true

View File

@ -126,12 +126,12 @@ func ProjectIndex(c *RequestContext) ResponseData {
AllProjects: true, AllProjects: true,
OfficialProjects: officialProjects[:utils.IntMin(len(officialProjects), projectsPerSection)], OfficialProjects: officialProjects[:utils.Min(len(officialProjects), projectsPerSection)],
OfficialProjectsLink: hmnurl.BuildProjectIndex(1, "official"), OfficialProjectsLink: hmnurl.BuildProjectIndex(1, "official"),
PersonalProjects: personalProjects[:utils.IntMin(len(personalProjects), projectsPerSection)], PersonalProjects: personalProjects[:utils.Min(len(personalProjects), projectsPerSection)],
PersonalProjectsLink: hmnurl.BuildProjectIndex(1, "personal"), PersonalProjectsLink: hmnurl.BuildProjectIndex(1, "personal"),
// Current jam stuff set later // Current jam stuff set later
PreviousJamProjects: previousJamProjects[:utils.IntMin(len(previousJamProjects), projectsPerSection)], PreviousJamProjects: previousJamProjects[:utils.Min(len(previousJamProjects), projectsPerSection)],
PreviousJamProjectsLink: hmnurl.BuildProjectIndex(1, previousJam.UrlSlug), PreviousJamProjectsLink: hmnurl.BuildProjectIndex(1, previousJam.UrlSlug),
PreviousJamLink: hmnurl.BuildJamIndexAny(previousJam.UrlSlug), PreviousJamLink: hmnurl.BuildJamIndexAny(previousJam.UrlSlug),
PreviousJamSlug: previousJam.UrlSlug, PreviousJamSlug: previousJam.UrlSlug,
@ -140,7 +140,7 @@ func ProjectIndex(c *RequestContext) ResponseData {
} }
if currentJam != nil { if currentJam != nil {
tmpl.CurrentJamProjects = currentJamProjects[:utils.IntMin(len(currentJamProjects), projectsPerSection)] tmpl.CurrentJamProjects = currentJamProjects[:utils.Min(len(currentJamProjects), projectsPerSection)]
tmpl.CurrentJamProjectsLink = hmnurl.BuildProjectIndex(1, currentJam.UrlSlug) tmpl.CurrentJamProjectsLink = hmnurl.BuildProjectIndex(1, currentJam.UrlSlug)
tmpl.CurrentJamLink = hmnurl.BuildJamIndexAny(currentJam.UrlSlug) tmpl.CurrentJamLink = hmnurl.BuildJamIndexAny(currentJam.UrlSlug)
tmpl.CurrentJamSlug = currentJam.UrlSlug tmpl.CurrentJamSlug = currentJam.UrlSlug
@ -195,12 +195,12 @@ func ProjectIndex(c *RequestContext) ResponseData {
FirstUrl: hmnurl.BuildProjectIndex(1, cat), FirstUrl: hmnurl.BuildProjectIndex(1, cat),
LastUrl: hmnurl.BuildProjectIndex(numPages, cat), LastUrl: hmnurl.BuildProjectIndex(numPages, cat),
NextUrl: hmnurl.BuildProjectIndex(utils.IntClamp(1, page+1, numPages), cat), NextUrl: hmnurl.BuildProjectIndex(utils.Clamp(1, page+1, numPages), cat),
PreviousUrl: hmnurl.BuildProjectIndex(utils.IntClamp(1, page-1, numPages), cat), PreviousUrl: hmnurl.BuildProjectIndex(utils.Clamp(1, page-1, numPages), cat),
} }
firstProjectIndex := (page - 1) * projectsPerPage firstProjectIndex := (page - 1) * projectsPerPage
endIndex := utils.IntMin(firstProjectIndex+projectsPerPage, len(projects)) endIndex := utils.Min(firstProjectIndex+projectsPerPage, len(projects))
pageProjects := projects[firstProjectIndex:endIndex] pageProjects := projects[firstProjectIndex:endIndex]
baseData := getBaseData(c, "Projects", []templates.Breadcrumb{ baseData := getBaseData(c, "Projects", []templates.Breadcrumb{
@ -474,7 +474,7 @@ func ProjectHomepage(c *RequestContext) ResponseData {
return c.ErrorResponse(http.StatusInternalServerError, err) return c.ErrorResponse(http.StatusInternalServerError, err)
} }
templateData.RecentActivity = templateData.RecentActivity[:utils.IntMin(len(templateData.RecentActivity)-1, maxRecentActivity)] templateData.RecentActivity = utils.ClampSlice(templateData.RecentActivity, maxRecentActivity)
followUrl := "" followUrl := ""
following := false following := false

View File

@ -75,44 +75,50 @@ func FetchTimeline(ctx context.Context, conn db.ConnOrTx, currentUser *models.Us
perf.StartBlock("TIMELINE", "Fetch timeline data") perf.StartBlock("TIMELINE", "Fetch timeline data")
if len(q.UserIDs) > 0 || len(q.ProjectIDs) > 0 { if len(q.UserIDs) > 0 || len(q.ProjectIDs) > 0 {
users, err := hmndata.FetchUsers(ctx, conn, currentUser, hmndata.UsersQuery{ var err error
UserIDs: q.UserIDs,
}) if len(q.UserIDs) > 0 {
if err != nil { users, err = hmndata.FetchUsers(ctx, conn, currentUser, hmndata.UsersQuery{
return nil, oops.New(err, "failed to fetch users") UserIDs: q.UserIDs,
})
if err != nil {
return nil, oops.New(err, "failed to fetch users")
}
} }
// NOTE(asaf): Clear out invalid users in case we banned someone after they got followed // NOTE(asaf): Clear out invalid users in case we banned someone after they got followed
q.UserIDs = q.UserIDs[0:0] validUserIDs := make([]int, 0, len(q.UserIDs))
for _, u := range users { for _, u := range users {
q.UserIDs = append(q.UserIDs, u.ID) validUserIDs = append(validUserIDs, u.ID)
} }
projects, err = hmndata.FetchProjects(ctx, conn, currentUser, hmndata.ProjectsQuery{ if len(q.ProjectIDs) > 0 {
ProjectIDs: q.ProjectIDs, projects, err = hmndata.FetchProjects(ctx, conn, currentUser, hmndata.ProjectsQuery{
}) ProjectIDs: q.ProjectIDs,
if err != nil { })
return nil, oops.New(err, "failed to fetch projects") if err != nil {
return nil, oops.New(err, "failed to fetch projects")
}
} }
// NOTE(asaf): The original projectIDs might container hidden/abandoned projects, // NOTE(asaf): The original projectIDs might contain hidden/abandoned projects,
// so we recreate it after the projects get filtered by FetchProjects. // so we recreate it after the projects get filtered by FetchProjects.
q.ProjectIDs = q.ProjectIDs[0:0] validProjectIDs := make([]int, 0, len(q.ProjectIDs))
for _, p := range projects { for _, p := range projects {
q.ProjectIDs = append(q.ProjectIDs, p.Project.ID) validProjectIDs = append(validProjectIDs, p.Project.ID)
} }
snippets, err = hmndata.FetchSnippets(ctx, conn, currentUser, hmndata.SnippetQuery{ snippets, err = hmndata.FetchSnippets(ctx, conn, currentUser, hmndata.SnippetQuery{
OwnerIDs: q.UserIDs, OwnerIDs: validUserIDs,
ProjectIDs: q.ProjectIDs, ProjectIDs: validProjectIDs,
}) })
if err != nil { if err != nil {
return nil, oops.New(err, "failed to fetch user snippets") return nil, oops.New(err, "failed to fetch user snippets")
} }
posts, err = hmndata.FetchPosts(ctx, conn, currentUser, hmndata.PostsQuery{ posts, err = hmndata.FetchPosts(ctx, conn, currentUser, hmndata.PostsQuery{
UserIDs: q.UserIDs, UserIDs: validUserIDs,
ProjectIDs: q.ProjectIDs, ProjectIDs: validProjectIDs,
SortDescending: true, SortDescending: true,
}) })
if err != nil { if err != nil {
@ -120,8 +126,8 @@ func FetchTimeline(ctx context.Context, conn db.ConnOrTx, currentUser *models.Us
} }
streamers, err = hmndata.FetchTwitchStreamers(ctx, conn, hmndata.TwitchStreamersQuery{ streamers, err = hmndata.FetchTwitchStreamers(ctx, conn, hmndata.TwitchStreamersQuery{
UserIDs: q.UserIDs, UserIDs: validUserIDs,
ProjectIDs: q.ProjectIDs, ProjectIDs: validProjectIDs,
}) })
if err != nil { if err != nil {
return nil, oops.New(err, "failed to fetch streamers") return nil, oops.New(err, "failed to fetch streamers")