feat: continue basic setup
Some checks failed
Go build / build (push) Failing after 1m58s

- setup ent scheme
- add command to create users
- document API
- add helpers to create migrations
- add command to run migrations
- add basic compose file
This commit is contained in:
Peter 2024-06-19 21:19:37 +02:00
parent 5a5d3a12b5
commit 9ea9a8f658
Signed by: prskr
GPG key ID: F56BED6903BC5E37
81 changed files with 3051 additions and 74 deletions

4
.devcontainer/.env Normal file
View file

@ -0,0 +1,4 @@
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_DB=postgres
POSTGRES_HOSTNAME=localhost

13
.devcontainer/Dockerfile Normal file
View file

@ -0,0 +1,13 @@
FROM mcr.microsoft.com/devcontainers/go:1-1.22-bookworm
# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here>
# [Optional] Uncomment the next lines to use go get to install anything else you need
# USER vscode
# RUN go get -x <your-dependency-or-tool>
# USER root
# [Optional] Uncomment this line to install global node packages.
# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1

36
.devcontainer/compose.yml Normal file
View file

@ -0,0 +1,36 @@
volumes:
postgres-data:
services:
app:
build:
context: .
dockerfile: Dockerfile
env_file:
# Ensure that the variables in .env match the same variables in devcontainer.json
- .env
volumes:
- ../..:/workspaces:cached
# Overrides default command so things don't shut down after the process ends.
command: sleep infinity
# Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function.
network_mode: service:db
# Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
# (Adding the "ports" property to this file will not forward from a Codespace.)
db:
image: postgres:latest
restart: unless-stopped
volumes:
- postgres-data:/var/lib/postgresql/data
env_file:
# Ensure that the variables in .env match the same variables in devcontainer.json
- .env
# Add "forwardPorts": ["5432"] to **devcontainer.json** to forward PostgreSQL locally.
# (Adding the "ports" property to this file will not forward from a Codespace.)

View file

@ -0,0 +1,23 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/go-postgres
{
"name": "Go & PostgreSQL",
"dockerComposeFile": "compose.yml",
"service": "app",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}"
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Configure tool-specific properties.
// "customizations": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [5432],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "go version",
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}

27
.editorconfig Normal file
View file

@ -0,0 +1,27 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
tab_width = 4
indent_style = space
insert_final_newline = false
max_line_length = 120
trim_trailing_whitespace = true
[*.go]
indent_style = tab
ij_smart_tabs = true
ij_go_GROUP_CURRENT_PROJECT_IMPORTS = true
ij_go_group_stdlib_imports = true
ij_go_import_sorting = goimports
ij_go_local_group_mode = project
ij_go_move_all_imports_in_one_declaration = true
ij_go_move_all_stdlib_imports_in_one_group = true
ij_go_remove_redundant_import_aliases = true
[*.{yml,yaml}]
indent_size = 2
tab_width = 2
insert_final_newline = true

5
.gitignore vendored
View file

@ -25,4 +25,7 @@ data/
# IDE configs # IDE configs
.idea/ .idea/
.vscode/
# Generated files
generated/
internal/ent/

11
.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,11 @@
{
"recommendations": [
"golang.Go",
"a-h.templ",
"oderwat.indent-rainbow",
"esbenp.prettier-vscode",
"bradlc.vscode-tailwindcss",
"redhat.vscode-yaml",
"ms-azuretools.vscode-docker"
]
}

3
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"redhat.telemetry.enabled": false
}

432
assets/api/ent.graphql Normal file
View file

@ -0,0 +1,432 @@
directive @goField(forceResolver: Boolean, name: String, omittable: Boolean) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION
directive @goModel(model: String, models: [String!], forceGenerate: Boolean) on OBJECT | INPUT_OBJECT | SCALAR | ENUM | INTERFACE | UNION
"""
Define a Relay Cursor type:
https://relay.dev/graphql/connections.htm#sec-Cursor
"""
scalar Cursor
type Index implements Node {
id: ID!
createTime: Time!
updateTime: Time!
siteID: ID!
name: String!
revision: String!
index: Site!
}
"""
IndexWhereInput is used for filtering Index objects.
Input was generated by ent.
"""
input IndexWhereInput {
not: IndexWhereInput
and: [IndexWhereInput!]
or: [IndexWhereInput!]
"""
id field predicates
"""
id: ID
idNEQ: ID
idIn: [ID!]
idNotIn: [ID!]
idGT: ID
idGTE: ID
idLT: ID
idLTE: ID
"""
create_time field predicates
"""
createTime: Time
createTimeNEQ: Time
createTimeIn: [Time!]
createTimeNotIn: [Time!]
createTimeGT: Time
createTimeGTE: Time
createTimeLT: Time
createTimeLTE: Time
"""
update_time field predicates
"""
updateTime: Time
updateTimeNEQ: Time
updateTimeIn: [Time!]
updateTimeNotIn: [Time!]
updateTimeGT: Time
updateTimeGTE: Time
updateTimeLT: Time
updateTimeLTE: Time
"""
site_id field predicates
"""
siteID: ID
siteIDNEQ: ID
siteIDIn: [ID!]
siteIDNotIn: [ID!]
"""
name field predicates
"""
name: String
nameNEQ: String
nameIn: [String!]
nameNotIn: [String!]
nameGT: String
nameGTE: String
nameLT: String
nameLTE: String
nameContains: String
nameHasPrefix: String
nameHasSuffix: String
nameEqualFold: String
nameContainsFold: String
"""
revision field predicates
"""
revision: String
revisionNEQ: String
revisionIn: [String!]
revisionNotIn: [String!]
revisionGT: String
revisionGTE: String
revisionLT: String
revisionLTE: String
revisionContains: String
revisionHasPrefix: String
revisionHasSuffix: String
revisionEqualFold: String
revisionContainsFold: String
"""
index edge predicates
"""
hasIndex: Boolean
hasIndexWith: [SiteWhereInput!]
}
"""
An object with an ID.
Follows the [Relay Global Object Identification Specification](https://relay.dev/graphql/objectidentification.htm)
"""
interface Node @goModel(model: "code.icb4dc0.de/prskr/searcherside/internal/ent.Noder") {
"""
The id of the object.
"""
id: ID!
}
"""
Possible directions in which to order a list of items when provided an `orderBy` argument.
"""
enum OrderDirection {
"""
Specifies an ascending order for a given `orderBy` argument.
"""
ASC
"""
Specifies a descending order for a given `orderBy` argument.
"""
DESC
}
"""
Information about pagination in a connection.
https://relay.dev/graphql/connections.htm#sec-undefined.PageInfo
"""
type PageInfo {
"""
When paginating forwards, are there more items?
"""
hasNextPage: Boolean!
"""
When paginating backwards, are there more items?
"""
hasPreviousPage: Boolean!
"""
When paginating backwards, the cursor to continue.
"""
startCursor: Cursor
"""
When paginating forwards, the cursor to continue.
"""
endCursor: Cursor
}
type Query {
"""
Fetches an object given its ID.
"""
node(
"""
ID of the object.
"""
id: ID!
): Node
"""
Lookup nodes by a list of IDs.
"""
nodes(
"""
The list of node IDs.
"""
ids: [ID!]!
): [Node]!
sites: [Site!]!
users: [User!]!
}
type Site implements Node {
id: ID!
createTime: Time!
updateTime: Time!
name: String!
siteMembers: [UserStaff!]
indices: [Index!]
}
"""
SiteWhereInput is used for filtering Site objects.
Input was generated by ent.
"""
input SiteWhereInput {
not: SiteWhereInput
and: [SiteWhereInput!]
or: [SiteWhereInput!]
"""
id field predicates
"""
id: ID
idNEQ: ID
idIn: [ID!]
idNotIn: [ID!]
idGT: ID
idGTE: ID
idLT: ID
idLTE: ID
"""
create_time field predicates
"""
createTime: Time
createTimeNEQ: Time
createTimeIn: [Time!]
createTimeNotIn: [Time!]
createTimeGT: Time
createTimeGTE: Time
createTimeLT: Time
createTimeLTE: Time
"""
update_time field predicates
"""
updateTime: Time
updateTimeNEQ: Time
updateTimeIn: [Time!]
updateTimeNotIn: [Time!]
updateTimeGT: Time
updateTimeGTE: Time
updateTimeLT: Time
updateTimeLTE: Time
"""
name field predicates
"""
name: String
nameNEQ: String
nameIn: [String!]
nameNotIn: [String!]
nameGT: String
nameGTE: String
nameLT: String
nameLTE: String
nameContains: String
nameHasPrefix: String
nameHasSuffix: String
nameEqualFold: String
nameContainsFold: String
"""
site_members edge predicates
"""
hasSiteMembers: Boolean
hasSiteMembersWith: [UserStaffWhereInput!]
"""
indices edge predicates
"""
hasIndices: Boolean
hasIndicesWith: [IndexWhereInput!]
}
"""
The builtin Time type
"""
scalar Time
type User implements Node {
id: ID!
createTime: Time!
updateTime: Time!
email: String!
givenName: String
surname: String
staffs: [UserStaff!]
}
type UserStaff implements Node {
id: ID!
createTime: Time!
updateTime: Time!
userID: ID!
siteID: ID!
users: User!
site: Site!
}
"""
UserStaffWhereInput is used for filtering UserStaff objects.
Input was generated by ent.
"""
input UserStaffWhereInput {
not: UserStaffWhereInput
and: [UserStaffWhereInput!]
or: [UserStaffWhereInput!]
"""
id field predicates
"""
id: ID
idNEQ: ID
idIn: [ID!]
idNotIn: [ID!]
idGT: ID
idGTE: ID
idLT: ID
idLTE: ID
"""
create_time field predicates
"""
createTime: Time
createTimeNEQ: Time
createTimeIn: [Time!]
createTimeNotIn: [Time!]
createTimeGT: Time
createTimeGTE: Time
createTimeLT: Time
createTimeLTE: Time
"""
update_time field predicates
"""
updateTime: Time
updateTimeNEQ: Time
updateTimeIn: [Time!]
updateTimeNotIn: [Time!]
updateTimeGT: Time
updateTimeGTE: Time
updateTimeLT: Time
updateTimeLTE: Time
"""
user_id field predicates
"""
userID: ID
userIDNEQ: ID
userIDIn: [ID!]
userIDNotIn: [ID!]
"""
site_id field predicates
"""
siteID: ID
siteIDNEQ: ID
siteIDIn: [ID!]
siteIDNotIn: [ID!]
"""
users edge predicates
"""
hasUsers: Boolean
hasUsersWith: [UserWhereInput!]
"""
site edge predicates
"""
hasSite: Boolean
hasSiteWith: [SiteWhereInput!]
}
"""
UserWhereInput is used for filtering User objects.
Input was generated by ent.
"""
input UserWhereInput {
not: UserWhereInput
and: [UserWhereInput!]
or: [UserWhereInput!]
"""
id field predicates
"""
id: ID
idNEQ: ID
idIn: [ID!]
idNotIn: [ID!]
idGT: ID
idGTE: ID
idLT: ID
idLTE: ID
"""
create_time field predicates
"""
createTime: Time
createTimeNEQ: Time
createTimeIn: [Time!]
createTimeNotIn: [Time!]
createTimeGT: Time
createTimeGTE: Time
createTimeLT: Time
createTimeLTE: Time
"""
update_time field predicates
"""
updateTime: Time
updateTimeNEQ: Time
updateTimeIn: [Time!]
updateTimeNotIn: [Time!]
updateTimeGT: Time
updateTimeGTE: Time
updateTimeLT: Time
updateTimeLTE: Time
"""
email field predicates
"""
email: String
emailNEQ: String
emailIn: [String!]
emailNotIn: [String!]
emailGT: String
emailGTE: String
emailLT: String
emailLTE: String
emailContains: String
emailHasPrefix: String
emailHasSuffix: String
emailEqualFold: String
emailContainsFold: String
"""
given_name field predicates
"""
givenName: String
givenNameNEQ: String
givenNameIn: [String!]
givenNameNotIn: [String!]
givenNameGT: String
givenNameGTE: String
givenNameLT: String
givenNameLTE: String
givenNameContains: String
givenNameHasPrefix: String
givenNameHasSuffix: String
givenNameIsNil: Boolean
givenNameNotNil: Boolean
givenNameEqualFold: String
givenNameContainsFold: String
"""
surname field predicates
"""
surname: String
surnameNEQ: String
surnameIn: [String!]
surnameNotIn: [String!]
surnameGT: String
surnameGTE: String
surnameLT: String
surnameLTE: String
surnameContains: String
surnameHasPrefix: String
surnameHasSuffix: String
surnameIsNil: Boolean
surnameNotNil: Boolean
surnameEqualFold: String
surnameContainsFold: String
"""
staffs edge predicates
"""
hasStaffs: Boolean
hasStaffsWith: [UserStaffWhereInput!]
}

View file

@ -0,0 +1,164 @@
openapi: 3.1.0
info:
title: Searcherside
license:
name: MIT
url: https://code.icb4dc0.de/prskr/searcherside/src/branch/main/LICENSE
version: 0.1.0
servers:
- url: http://localhost:3000/api/v1
tags:
- name: index
description: Interact with indices
- name: search
description: Search in indices
paths:
/site/{siteId}/index/{indexName}:
put:
tags:
- index
summary: Create a new index by uploading an archive of all documents to index
operationId: upsertIndexForSite
parameters:
- name: siteId
in: path
description: ID of the site for which to upsert a new index
required: true
schema:
type: string
format: uuid
- name: indexName
in: path
description: Name of the index to create
required: true
examples:
Writerside:
description: |
Writerside index of the format <module>/<instance>
value: web/g
schema:
type: string
requestBody:
content:
multipart/form-data:
schema:
type: object
properties:
filename:
type: string
format: binary
responses:
'202':
description: Accepted upload of file
/site/{siteId}/preview-search/{indexName}:
get:
tags:
- search
summary: Search in the latest index version of the specified site
security:
- jwtAuth: []
operationId: previewSearch
parameters:
- name: siteId
in: path
description: ID of the site for which to upsert a new index
required: true
schema:
type: string
format: uuid
- name: indexName
in: path
description: Name of the index to create
required: true
examples:
Writerside:
description: |
Writerside index of the format <module>/<instance>
value: web/g
schema:
type: string
- name: query
in: query
description: Search query
required: true
schema:
type: string
- name: isExactSearch
in: query
description: Whether only exact matches should be considered
required: false
schema:
type: boolean
- name: maxHits
in: query
description: Maximum number of items to return
required: false
schema:
type: integer
responses:
'200':
description: Successfully searched in index
content:
application/json:
schema:
$ref: "#/components/schemas/SearchResponse"
components:
schemas:
MatchLevel:
type: string
enum: [none, full]
HighlightResult:
type: object
additionalProperties:
type: object
properties:
value:
type: string
matchLevel:
$ref: "#/components/schemas/MatchLevel"
fullyHighlighted:
type: bool
matchedWords:
type: array
items:
type: string
SearchHit:
type: object
properties:
objectID:
type: string
mainTitle:
type: string
pageTitle:
type: string
url:
type: string
breadcrumbs:
type: string
_snippetResult:
type: object
properties:
content:
type: object
properties:
value:
type: string
matchLevel:
$ref: "#/components/schemas/MatchLevel"
_highlightResult:
$ref: "#/components/schemas/HighlightResult"
SearchResponse:
type: object
properties:
hits:
type: array
items:
$ref: "#/components/schemas/SearchHit"
securitySchemes:
jwtAuth:
type: apiKey
name: api_key
in: header

1
assets/api/types.graphql Normal file
View file

@ -0,0 +1 @@
scalar UUID

9
compose.yml Normal file
View file

@ -0,0 +1,9 @@
services:
postgres:
image: docker.io/postgres:16-alpine
environment:
POSTGRES_USER: searcherside
POSTGRES_PASSWORD: 1n1t-r00t!
POSTGRES_DB: searcherside
ports:
- "5432:5432"

View file

@ -1,3 +1,6 @@
server: http:
http: readHeaderTimeout: 15s
readHeaderTimeout: 15s
db:
driver: postgres
url: 'postgresql://searcherside:1n1t-r00t!@localhost:5432/searcherside?sslmode=disable'

10
core/cq/user.go Normal file
View file

@ -0,0 +1,10 @@
package cq
type CreateUserRequest struct {
Email string
Password []byte
Admin bool
}
type CreateUserResponse struct {
}

12
core/domain/user.go Normal file
View file

@ -0,0 +1,12 @@
package domain
import "github.com/google/uuid"
type User struct {
ID uuid.UUID
Email string
GivenName string
Surname string
IsAdmin bool
}

34
core/ports/db.go Normal file
View file

@ -0,0 +1,34 @@
package ports
import (
"context"
"ariga.io/atlas/sql/migrate"
"code.icb4dc0.de/prskr/searcherside/internal/ent"
"entgo.io/ent/dialect"
)
type Driver string
const (
DriverPostgres = Driver(dialect.Postgres)
DriverSQLite = Driver(dialect.SQLite)
)
func (t Driver) String() string {
return string(t)
}
type MigrationRequest struct {
Driver Driver
URL string
}
type RevisionReadWriter interface {
migrate.RevisionReadWriter
Client() *ent.Client
}
type Migrator interface {
Migrate(ctx context.Context, req MigrationRequest) error
}

6
core/ports/pw_hash.go Normal file
View file

@ -0,0 +1,6 @@
package ports
type PasswordHashAlgorithm interface {
Hash(password []byte) ([]byte, error)
Validate(password []byte, hash []byte) error
}

View file

@ -0,0 +1,26 @@
package ports
import (
"context"
"io"
"github.com/google/uuid"
"code.icb4dc0.de/prskr/searcherside/core/cq"
"code.icb4dc0.de/prskr/searcherside/core/domain"
)
type UserReadRepository interface {
UserByID(ctx context.Context, id uuid.UUID) (*domain.User, error)
UserByEmail(ctx context.Context, email string) (*domain.User, error)
}
type UserWriteRepository interface {
CreateUser(ctx context.Context, user cq.CreateUserRequest) (*cq.CreateUserResponse, error)
}
type UserRepository interface {
io.Closer
UserReadRepository
UserWriteRepository
}

View file

@ -0,0 +1,143 @@
package services
import (
"bytes"
"crypto/rand"
"errors"
"fmt"
"strconv"
"golang.org/x/crypto/argon2"
"code.icb4dc0.de/prskr/searcherside/core/ports"
)
const (
argon2idSaltLength = 16
argon2idKeyLength uint32 = 32
)
var _ ports.PasswordHashAlgorithm = (*Argon2IDHashAlgorithm)(nil)
var ErrPasswordHashMismatch = errors.New("password hash mismatch")
type Argon2IDHashAlgorithm struct {
Argon2Params
}
func (a *Argon2IDHashAlgorithm) Hash(password []byte) ([]byte, error) {
params := a.Argon2Params
if params == (Argon2Params{}) {
params = DefaultArgon2Params
}
salt := make([]byte, argon2idSaltLength)
_, _ = rand.Read(salt)
pwh := &PasswordHash{
Hash: argon2.IDKey(password, salt, params.Iterations, params.Memory, params.Threads, argon2idKeyLength),
Salt: salt,
Params: &a.Argon2Params,
}
return pwh.MarshalText()
}
func (a *Argon2IDHashAlgorithm) Validate(password []byte, hash []byte) error {
var pwh PasswordHash
if err := pwh.UnmarshalText(hash); err != nil {
return fmt.Errorf("failed parse hash: %w", err)
}
params, ok := pwh.Params.(*Argon2Params)
if !ok {
return fmt.Errorf("hash params type mismatch: %T", pwh.Params)
}
actual := argon2.IDKey(password, pwh.Salt, params.Iterations, params.Memory, params.Threads, argon2idKeyLength)
if !bytes.Equal(actual, pwh.Hash) {
return ErrPasswordHashMismatch
}
return nil
}
var (
_ hashParams = (*Argon2Params)(nil)
)
var DefaultArgon2Params = Argon2Params{
Iterations: 3,
Memory: 64 * 1024,
Threads: 2,
}
type Argon2Params struct {
Iterations uint32
Memory uint32
Threads uint8
}
func (*Argon2Params) Algorithm() string {
return "argon2id"
}
// MarshalText implements encoding.TextMarshaler.
func (p *Argon2Params) MarshalText() (text []byte, err error) {
if p == nil || *p == (Argon2Params{}) {
return DefaultArgon2Params.MarshalText()
}
return []byte(fmt.Sprintf("v=19$m=%d,t=%d,p=%d", p.Memory, p.Iterations, p.Threads)), nil
}
// UnmarshalText implements encoding.TextUnmarshaler.
func (p *Argon2Params) UnmarshalText(text []byte) error {
parts := bytes.Split(text, []byte(","))
if len(parts) < 3 {
return fmt.Errorf("invalid argon2 parameter: %s", text)
}
for i := range parts {
keyValue := bytes.Split(parts[i], []byte("="))
if len(keyValue) != 2 {
return fmt.Errorf("invalid argon2 parameter: %s", parts[i])
}
switch string(keyValue[0]) {
case "v":
continue
case "m":
parsed, err := strconv.ParseUint(string(keyValue[1]), 10, 32)
if err != nil {
return fmt.Errorf("failed to parse argon2 memory value %s: %w", keyValue[1], err)
}
p.Memory = uint32(parsed)
case "t":
parsed, err := strconv.ParseUint(string(keyValue[1]), 10, 32)
if err != nil {
return fmt.Errorf("failed to parse argon2 iteration value %s: %w", keyValue[1], err)
}
p.Iterations = uint32(parsed)
case "p":
parsed, err := strconv.ParseUint(string(keyValue[1]), 10, 8)
if err != nil {
return fmt.Errorf("failed to parse argon2 threads value %s: %w", keyValue[1], err)
}
p.Threads = uint8(parsed)
default:
return fmt.Errorf("unexpected parameter key: %s", keyValue[0])
}
}
return nil
}
func paramsForAlgorithm(alg string) hashParams {
switch alg {
case "argon2id":
return new(Argon2Params)
default:
return nil
}
}

View file

@ -0,0 +1,86 @@
package services_test
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"code.icb4dc0.de/prskr/searcherside/core/services"
)
func TestArgon2IDHashAlgorithm_Hash(t *testing.T) {
type fields struct {
Argon2Params services.Argon2Params
}
type args struct {
password []byte
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
{
name: "Basic test",
fields: fields{
Argon2Params: services.DefaultArgon2Params,
},
args: args{
password: []byte("hello_world1234!"),
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := &services.Argon2IDHashAlgorithm{
Argon2Params: tt.fields.Argon2Params,
}
got, err := a.Hash(tt.args.password)
if (err != nil) != tt.wantErr {
t.Errorf("Hash() error = %v, wantErr %v", err, tt.wantErr)
return
}
t.Log(string(got))
var pwh services.PasswordHash
assert.NoError(t, pwh.UnmarshalText(got))
assert.NotEmpty(t, pwh.Hash)
assert.NotEmpty(t, pwh.Salt)
assert.NotZero(t, pwh.Params)
})
}
}
func TestArgon2IDHashAlgorithm_Validate(t *testing.T) {
type args struct {
password []byte
hash []byte
}
tests := []struct {
name string
args args
wantErr assert.ErrorAssertionFunc
}{
{
name: "Happy path",
args: args{
hash: []byte("$argon2id$v=19$m=65536,t=3,p=2$Huar9oxLB/FrNHbY2EldpA$NfSo9dk+YEJ8AewO6y1GHoNyW8HBXo4CDdwQNXmeq4g"),
password: []byte("hello_world1234!"),
},
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
return assert.NoError(t, err, i)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var a services.Argon2IDHashAlgorithm
tt.wantErr(t, a.Validate(tt.args.password, tt.args.hash), fmt.Sprintf("Validate(%v, %v)", tt.args.password, tt.args.hash))
})
}
}

View file

@ -12,7 +12,7 @@ import (
"github.com/blevesearch/bleve/v2/mapping" "github.com/blevesearch/bleve/v2/mapping"
"code.icb4dc0.de/prskr/searcherside/core/ports" "code.icb4dc0.de/prskr/searcherside/core/ports"
"code.icb4dc0.de/prskr/searcherside/internal/archive" "code.icb4dc0.de/prskr/searcherside/infrastructure/archive"
) )
var _ ports.Indexer = (*BleveIndexer)(nil) var _ ports.Indexer = (*BleveIndexer)(nil)

View file

@ -0,0 +1,81 @@
package services
import (
"bytes"
"encoding"
"encoding/base64"
"errors"
)
var (
_ encoding.TextMarshaler = (*PasswordHash)(nil)
_ encoding.TextUnmarshaler = (*PasswordHash)(nil)
)
var ErrPasswordHashFormat = errors.New("invalid password hash format")
type hashParams interface {
Algorithm() string
encoding.TextMarshaler
encoding.TextUnmarshaler
}
type PasswordHash struct {
Hash []byte
Salt []byte
Params hashParams
}
func (p *PasswordHash) MarshalText() (text []byte, err error) {
buf := bytes.NewBuffer(make([]byte, 0, 256))
buf.WriteRune('$')
buf.WriteString(p.Params.Algorithm())
buf.WriteRune('$')
params, err := p.Params.MarshalText()
if err != nil {
return nil, err
}
buf.Write(params)
buf.WriteRune('$')
encodedSalt := make([]byte, base64.RawStdEncoding.EncodedLen(len(p.Salt)))
base64.RawStdEncoding.Encode(encodedSalt, p.Salt)
buf.Write(encodedSalt)
buf.WriteRune('$')
encodedCipherText := make([]byte, base64.RawStdEncoding.EncodedLen(len(p.Hash)))
base64.RawStdEncoding.Encode(encodedCipherText, p.Hash)
buf.Write(encodedCipherText)
return buf.Bytes(), nil
}
func (p *PasswordHash) UnmarshalText(text []byte) error {
split := bytes.Split(text, []byte("$"))
if len(split) < 6 {
return ErrPasswordHashFormat
}
p.Params = paramsForAlgorithm(string(split[1]))
if err := p.Params.UnmarshalText(split[len(split)-3]); err != nil {
return err
}
salt := split[len(split)-2]
p.Salt = make([]byte, base64.RawStdEncoding.DecodedLen(len(salt)))
if _, err := base64.RawStdEncoding.Decode(p.Salt, salt); err != nil {
return err
}
hash := split[len(split)-1]
p.Hash = make([]byte, base64.RawStdEncoding.DecodedLen(len(hash)))
if _, err := base64.RawStdEncoding.Decode(p.Hash, hash); err != nil {
return err
}
return nil
}

View file

@ -0,0 +1,34 @@
package services_test
import (
"testing"
"code.icb4dc0.de/prskr/searcherside/core/services"
)
func TestPasswordHash_UnmarshalText(t *testing.T) {
type args struct {
text []byte
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "Basic test",
args: args{
text: []byte("$argon2id$v=19$m=65536,t=3,p=2$RiKQX+VDY9FtotJETFq8WQ$uCWUzPOlKpIinzryBfy31IHjhn5miKvtS5hER5H+1RE"),
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := &services.PasswordHash{}
if err := p.UnmarshalText(tt.args.text); (err != nil) != tt.wantErr {
t.Errorf("UnmarshalText() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View file

@ -10,7 +10,7 @@ import (
"github.com/klauspost/compress/zstd" "github.com/klauspost/compress/zstd"
"code.icb4dc0.de/prskr/searcherside/core/ports" "code.icb4dc0.de/prskr/searcherside/core/ports"
"code.icb4dc0.de/prskr/searcherside/internal/archive" "code.icb4dc0.de/prskr/searcherside/infrastructure/archive"
) )
var _ ports.Archiver = (*TarZSTIndexArchiver)(nil) var _ ports.Archiver = (*TarZSTIndexArchiver)(nil)

5
generate.go Normal file
View file

@ -0,0 +1,5 @@
package main
//go:generate go run -mod=mod github.com/a-h/templ/cmd/templ generate
//go:generate go run -mod=mod ./infrastructure/db/entc.go
//go:generate go run -mod=mod github.com/99designs/gqlgen

95
go.mod
View file

@ -5,19 +5,40 @@ go 1.22
toolchain go1.22.4 toolchain go1.22.4
require ( require (
ariga.io/atlas v0.19.1-0.20240203083654-5948b60a8e43
entgo.io/contrib v0.5.0
entgo.io/ent v0.13.1
github.com/99designs/gqlgen v0.17.48
github.com/a-h/templ v0.2.707
github.com/alecthomas/kong v0.9.0 github.com/alecthomas/kong v0.9.0
github.com/blevesearch/bleve/v2 v2.4.0 github.com/blevesearch/bleve/v2 v2.4.0
github.com/go-chi/chi/v5 v5.0.12 github.com/go-chi/chi/v5 v5.0.12
github.com/go-chi/cors v1.2.1 github.com/go-chi/cors v1.2.1
github.com/go-chi/jwtauth/v5 v5.3.1 github.com/go-chi/jwtauth/v5 v5.3.1
github.com/google/uuid v1.6.0
github.com/hashicorp/go-multierror v1.1.1
github.com/jackc/pgx/v5 v5.6.0
github.com/klauspost/compress v1.17.8 github.com/klauspost/compress v1.17.8
github.com/lestrrat-go/jwx/v2 v2.0.21 github.com/lestrrat-go/jwx/v2 v2.0.21
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.5.0
github.com/stretchr/testify v1.9.0
github.com/testcontainers/testcontainers-go v0.31.0
github.com/testcontainers/testcontainers-go/modules/postgres v0.31.0
github.com/vektah/gqlparser/v2 v2.5.12
golang.org/x/crypto v0.24.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.30.1
) )
require ( require (
dario.cat/mergo v1.0.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/Microsoft/hcsshim v0.11.4 // indirect
github.com/RoaringBitmap/roaring v1.9.4 // indirect github.com/RoaringBitmap/roaring v1.9.4 // indirect
github.com/agext/levenshtein v1.2.1 // indirect
github.com/agnivade/levenshtein v1.1.1 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/bits-and-blooms/bitset v1.13.0 // indirect github.com/bits-and-blooms/bitset v1.13.0 // indirect
github.com/blevesearch/bleve_index_api v1.1.9 // indirect github.com/blevesearch/bleve_index_api v1.1.9 // indirect
github.com/blevesearch/geo v0.1.20 // indirect github.com/blevesearch/geo v0.1.20 // indirect
@ -36,25 +57,93 @@ require (
github.com/blevesearch/zapx/v14 v14.3.10 // indirect github.com/blevesearch/zapx/v14 v14.3.10 // indirect
github.com/blevesearch/zapx/v15 v15.3.13 // indirect github.com/blevesearch/zapx/v15 v15.3.13 // indirect
github.com/blevesearch/zapx/v16 v16.1.1 // indirect github.com/blevesearch/zapx/v16 v16.1.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/containerd/containerd v1.7.15 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/cpuguy83/dockercfg v0.3.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
github.com/distribution/reference v0.5.0 // indirect
github.com/docker/docker v25.0.5+incompatible // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-openapi/inflect v0.19.0 // indirect
github.com/goccy/go-json v0.10.3 // indirect github.com/goccy/go-json v0.10.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect
github.com/golang/protobuf v1.5.4 // indirect github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hashicorp/hcl/v2 v2.13.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/lestrrat-go/blackmagic v1.0.2 // indirect github.com/lestrrat-go/blackmagic v1.0.2 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/httprc v1.0.5 // indirect github.com/lestrrat-go/httprc v1.0.5 // indirect
github.com/lestrrat-go/iter v1.0.2 // indirect github.com/lestrrat-go/iter v1.0.2 // indirect
github.com/lestrrat-go/option v1.0.1 // indirect github.com/lestrrat-go/option v1.0.1 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
github.com/moby/patternmatcher v0.6.0 // indirect
github.com/moby/sys/sequential v0.5.0 // indirect
github.com/moby/sys/user v0.1.0 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/mschoch/smat v0.2.0 // indirect github.com/mschoch/smat v0.2.0 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/segmentio/asm v1.2.0 // indirect github.com/segmentio/asm v1.2.0 // indirect
github.com/shirou/gopsutil/v3 v3.23.12 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/sosodev/duration v1.3.1 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/urfave/cli/v2 v2.27.2 // indirect
github.com/vmihailenco/msgpack/v5 v5.0.0-beta.9 // indirect
github.com/vmihailenco/tagparser v0.1.2 // indirect
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
github.com/zclconf/go-cty v1.8.0 // indirect
go.etcd.io/bbolt v1.3.10 // indirect go.etcd.io/bbolt v1.3.10 // indirect
golang.org/x/crypto v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.21.0 // indirect golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect
google.golang.org/grpc v1.58.3 // indirect
google.golang.org/protobuf v1.34.1 // indirect google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
modernc.org/libc v1.52.1 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.8.0 // indirect
modernc.org/strutil v1.2.0 // indirect
modernc.org/token v1.1.0 // indirect
) )

315
go.sum
View file

@ -1,11 +1,47 @@
ariga.io/atlas v0.19.1-0.20240203083654-5948b60a8e43 h1:GwdJbXydHCYPedeeLt4x/lrlIISQ4JTH1mRWuE5ZZ14=
ariga.io/atlas v0.19.1-0.20240203083654-5948b60a8e43/go.mod h1:uj3pm+hUTVN/X5yfdBexHlZv+1Xu5u5ZbZx7+CDavNU=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
entgo.io/contrib v0.5.0 h1:M4IqodImfUm327RDwNAITLNz3PsxVeC3rD4DPeVA8Gs=
entgo.io/contrib v0.5.0/go.mod h1:q8dXQCmzqpSlVdT2bWDydjgznGcy3y4zmsYmVFC9V/U=
entgo.io/ent v0.13.1 h1:uD8QwN1h6SNphdCCzmkMN3feSUzNnVvV/WIkHKMbzOE=
entgo.io/ent v0.13.1/go.mod h1:qCEmo+biw3ccBn9OyL4ZK5dfpwg++l1Gxwac5B1206A=
github.com/99designs/gqlgen v0.17.48 h1:Wgk7n9PIdnmpsC1aJJV4eiZCGkAkoamKOtXAp/crpzQ=
github.com/99designs/gqlgen v0.17.48/go.mod h1:hYeQ+ygPbcapbaHtHMbZ1DHMVNT+1tGU+fI+Hy4kqIo=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8=
github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w=
github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE=
github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk=
github.com/RoaringBitmap/roaring v1.9.4 h1:yhEIoH4YezLYT04s1nHehNO64EKFTop/wBhxv2QzDdQ= github.com/RoaringBitmap/roaring v1.9.4 h1:yhEIoH4YezLYT04s1nHehNO64EKFTop/wBhxv2QzDdQ=
github.com/RoaringBitmap/roaring v1.9.4/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90= github.com/RoaringBitmap/roaring v1.9.4/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90=
github.com/a-h/templ v0.2.707 h1:T1Gkd2ugbRglZ9rYw/VBchWOSZVKmetDbBkm4YubM7U=
github.com/a-h/templ v0.2.707/go.mod h1:5cqsugkq9IerRNucNsI4DEamdHPsoGMQy99DzydLhM8=
github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8=
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU= github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU=
github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/kong v0.9.0 h1:G5diXxc85KvoV2f0ZRVuMsi45IrBgx9zDNGNj165aPA= github.com/alecthomas/kong v0.9.0 h1:G5diXxc85KvoV2f0ZRVuMsi45IrBgx9zDNGNj165aPA=
github.com/alecthomas/kong v0.9.0/go.mod h1:Y47y5gKfHp1hDc7CH7OeXgLIpp+Q2m1Ni0L5s3bI8Os= github.com/alecthomas/kong v0.9.0/go.mod h1:Y47y5gKfHp1hDc7CH7OeXgLIpp+Q2m1Ni0L5s3bI8Os=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= 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/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE= github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
@ -45,39 +81,114 @@ github.com/blevesearch/zapx/v15 v15.3.13 h1:6EkfaZiPlAxqXz0neniq35my6S48QI94W/wy
github.com/blevesearch/zapx/v15 v15.3.13/go.mod h1:Turk/TNRKj9es7ZpKK95PS7f6D44Y7fAFy8F4LXQtGg= github.com/blevesearch/zapx/v15 v15.3.13/go.mod h1:Turk/TNRKj9es7ZpKK95PS7f6D44Y7fAFy8F4LXQtGg=
github.com/blevesearch/zapx/v16 v16.1.1 h1:k+fDKs4ylqqw+X1PopzoxMbDdwgMOaXSbRCo0jnfR2Q= github.com/blevesearch/zapx/v16 v16.1.1 h1:k+fDKs4ylqqw+X1PopzoxMbDdwgMOaXSbRCo0jnfR2Q=
github.com/blevesearch/zapx/v16 v16.1.1/go.mod h1:Zmq22YL64zvplIjUftIsYX3pV085F0wff8zukUZUww4= github.com/blevesearch/zapx/v16 v16.1.1/go.mod h1:Zmq22YL64zvplIjUftIsYX3pV085F0wff8zukUZUww4=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes=
github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E=
github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE=
github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
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/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-chi/jwtauth/v5 v5.3.1 h1:1ePWrjVctvp1tyBq5b/2ER8Th/+RbYc7x4qNsc5rh5A= github.com/go-chi/jwtauth/v5 v5.3.1 h1:1ePWrjVctvp1tyBq5b/2ER8Th/+RbYc7x4qNsc5rh5A=
github.com/go-chi/jwtauth/v5 v5.3.1/go.mod h1:6Fl2RRmWXs3tJYE1IQGX81FsPoGqDwq9c15j52R5q80= github.com/go-chi/jwtauth/v5 v5.3.1/go.mod h1:6Fl2RRmWXs3tJYE1IQGX81FsPoGqDwq9c15j52R5q80=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4=
github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/geo v0.0.0-20230421003525-6adc56603217 h1:HKlyj6in2JV6wVkmQ4XmG/EIm+SCYlPZ+V4GWit7Z+I= github.com/golang/geo v0.0.0-20230421003525-6adc56603217 h1:HKlyj6in2JV6wVkmQ4XmG/EIm+SCYlPZ+V4GWit7Z+I=
github.com/golang/geo v0.0.0-20230421003525-6adc56603217/go.mod h1:8wI0hitZ3a1IxZfeH3/5I97CI8i5cLGsYe7xNhQGs9U= github.com/golang/geo v0.0.0-20230421003525-6adc56603217/go.mod h1:8wI0hitZ3a1IxZfeH3/5I97CI8i5cLGsYe7xNhQGs9U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.5/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.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
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/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
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/hashicorp/hcl/v2 v2.13.0 h1:0Apadu1w6M11dyGFxWnmhhcMjkbAiKCv7G1r/2QgCNc=
github.com/hashicorp/hcl/v2 v2.13.0/go.mod h1:e4z5nxYlWNPdDSNYX+ph14EvWYMFm3eP0zIUqPc2jr0=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= 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/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k=
github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
@ -90,42 +201,228 @@ github.com/lestrrat-go/jwx/v2 v2.0.21 h1:jAPKupy4uHgrHFEdjVjNkUgoBKtVDgrQPB/h55F
github.com/lestrrat-go/jwx/v2 v2.0.21/go.mod h1:09mLW8zto6bWL9GbwnqAli+ArLf+5M33QLQPDggkUWM= github.com/lestrrat-go/jwx/v2 v2.0.21/go.mod h1:09mLW8zto6bWL9GbwnqAli+ArLf+5M33QLQPDggkUWM=
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
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-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg=
github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
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/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4=
github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4=
github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/testcontainers/testcontainers-go v0.31.0 h1:W0VwIhcEVhRflwL9as3dhY6jXjVCA27AkmbnZ+UTh3U=
github.com/testcontainers/testcontainers-go v0.31.0/go.mod h1:D2lAoA0zUFiSY+eAflqK5mcUx/A5hrrORaEQrd0SefI=
github.com/testcontainers/testcontainers-go/modules/postgres v0.31.0 h1:isAwFS3KNKRbJMbWv+wolWqOFUECmjYZ+sIRZCIBc/E=
github.com/testcontainers/testcontainers-go/modules/postgres v0.31.0/go.mod h1:ZNYY8vumNCEG9YI59A9d6/YaMY49uwRhmeU563EzFGw=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI=
github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM=
github.com/vektah/gqlparser/v2 v2.5.12 h1:COMhVVnql6RoaF7+aTBWiTADdpLGyZWU3K/NwW0ph98=
github.com/vektah/gqlparser/v2 v2.5.12/go.mod h1:WQQjFc+I1YIzoPvZBhUQX7waZgg3pMLi0r8KymvAE2w=
github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
github.com/vmihailenco/msgpack/v5 v5.0.0-beta.9 h1:iBRIniTnWOo0kqkg3k3XR8Vn6OCkVlIuZNo0UoBrKx4=
github.com/vmihailenco/msgpack/v5 v5.0.0-beta.9/go.mod h1:HVxBVPUK/+fZMonk4bi1islLa8V3cfnBug0+4dykPzo=
github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc=
github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zclconf/go-cty v1.8.0 h1:s4AvqaeQzJIu3ndv4gVIhplVD0krU+bgrcLSVUnaWuA=
github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=
go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0= go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0=
go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ= go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o=
go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 h1:mchzmB1XO2pMaKFRqk/+MV3mgGG96aqaPXaMifQU47w=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/exp v0.0.0-20231108232855-2478ac86f678/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g=
google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw=
google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM=
google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ=
google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY=
gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
modernc.org/cc/v4 v4.21.2 h1:dycHFB/jDc3IyacKipCNSDrjIC0Lm1hyoWOZTRR20Lk=
modernc.org/cc/v4 v4.21.2/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v4 v4.17.10 h1:6wrtRozgrhCxieCeJh85QsxkX/2FFrT9hdaWPlbn4Zo=
modernc.org/ccgo/v4 v4.17.10/go.mod h1:0NBHgsqTTpm9cA5z2ccErvGZmtntSM9qD2kFAs6pjXM=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/libc v1.52.1 h1:uau0VoiT5hnR+SpoWekCKbLqm7v6dhRL3hI+NQhgN3M=
modernc.org/libc v1.52.1/go.mod h1:HR4nVzFDSDizP620zcMCgjb1/8xk2lg5p/8yjfGv1IQ=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
modernc.org/sqlite v1.30.1 h1:YFhPVfu2iIgUf9kuA1CR7iiHdcEEsI2i+yjRYHscyxk=
modernc.org/sqlite v1.30.1/go.mod h1:DUmsiWQDaAvU4abhc/N+djlom/L2o8f7gZ95RCvyoLU=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=

36
gqlgen.yml Normal file
View file

@ -0,0 +1,36 @@
schema:
- assets/api/*.graphql
autobind:
- code.icb4dc0.de/prskr/searcherside/internal/ent
- code.icb4dc0.de/prskr/searcherside/internal/ent/site
exec:
layout: follow-schema
dir: infrastructure/api/graphql/generated
package: generated
resolver:
layout: follow-schema
dir: handlers/graphql/
# Disable the generated getters for all models and interfaces.
omit_getters: true
models:
UUID:
model:
- github.com/99designs/gqlgen/graphql.ID
- github.com/99designs/gqlgen/graphql.Int
- github.com/99designs/gqlgen/graphql.Int64
- github.com/99designs/gqlgen/graphql.Int32
- github.com/99designs/gqlgen/graphql.UUID
ID:
model:
- code.icb4dc0.de/prskr/searcherside/infrastructure/db/schema/uuidgql.UUID
Node:
model:
- code.icb4dc0.de/prskr/searcherside/internal/ent.Noder
AccessLevel:
model:
- code.icb4dc0.de/prskr/searcherside/core/domain.AccessLevel

View file

@ -11,7 +11,7 @@ import (
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"code.icb4dc0.de/prskr/searcherside/core/ports" "code.icb4dc0.de/prskr/searcherside/core/ports"
"code.icb4dc0.de/prskr/searcherside/internal/logging" logging2 "code.icb4dc0.de/prskr/searcherside/infrastructure/logging"
) )
type IndexHandler struct { type IndexHandler struct {
@ -20,10 +20,10 @@ type IndexHandler struct {
} }
func (h IndexHandler) IngestIndex(writer http.ResponseWriter, req *http.Request) { func (h IndexHandler) IngestIndex(writer http.ResponseWriter, req *http.Request) {
logger := logging.GetLogger(req.Context()) logger := logging2.GetLogger(req.Context())
if err := req.ParseMultipartForm(h.MaxMemoryBytes); err != nil { if err := req.ParseMultipartForm(h.MaxMemoryBytes); err != nil {
logger.WarnContext(req.Context(), "Failed to parse multipart form", logging.Error(err)) logger.WarnContext(req.Context(), "Failed to parse multipart form", logging2.Error(err))
http.Error(writer, "Failed to parse multipart form", http.StatusInternalServerError) http.Error(writer, "Failed to parse multipart form", http.StatusInternalServerError)
return return
} }
@ -47,7 +47,7 @@ func (h IndexHandler) IngestIndex(writer http.ResponseWriter, req *http.Request)
indexTempFile, err := os.CreateTemp(os.TempDir(), "searcherside-index-*") indexTempFile, err := os.CreateTemp(os.TempDir(), "searcherside-index-*")
if err != nil { if err != nil {
logger.ErrorContext(req.Context(), "Failed to create temporary index file", logging.Error(err)) logger.ErrorContext(req.Context(), "Failed to create temporary index file", logging2.Error(err))
http.Error(writer, "Failed to create temporary index file", http.StatusInternalServerError) http.Error(writer, "Failed to create temporary index file", http.StatusInternalServerError)
return return
} }
@ -56,23 +56,23 @@ func (h IndexHandler) IngestIndex(writer http.ResponseWriter, req *http.Request)
stream, err := indexFile.Open() stream, err := indexFile.Open()
if err != nil { if err != nil {
logger.ErrorContext(req.Context(), "Failed to open index file", logging.Error(err)) logger.ErrorContext(req.Context(), "Failed to open index file", logging2.Error(err))
http.Error(writer, "Failed to open index file", http.StatusBadRequest) http.Error(writer, "Failed to open index file", http.StatusBadRequest)
return return
} }
if _, err := io.Copy(io.MultiWriter(hash, indexTempFile), stream); err != nil { if _, err := io.Copy(io.MultiWriter(hash, indexTempFile), stream); err != nil {
logger.ErrorContext(req.Context(), "Failed to copy index file", logging.Error(err)) logger.ErrorContext(req.Context(), "Failed to copy index file", logging2.Error(err))
http.Error(writer, "Failed to copy index file", http.StatusBadRequest) http.Error(writer, "Failed to copy index file", http.StatusBadRequest)
return return
} }
if err := stream.Close(); err != nil { if err := stream.Close(); err != nil {
logger.ErrorContext(req.Context(), "Failed to close index file", logging.Error(err)) logger.ErrorContext(req.Context(), "Failed to close index file", logging2.Error(err))
} }
if err := indexTempFile.Close(); err != nil { if err := indexTempFile.Close(); err != nil {
logger.ErrorContext(req.Context(), "Failed to close temporary index file", logging.Error(err)) logger.ErrorContext(req.Context(), "Failed to close temporary index file", logging2.Error(err))
http.Error(writer, "Failed to close temporary index file", http.StatusInternalServerError) http.Error(writer, "Failed to close temporary index file", http.StatusInternalServerError)
return return
} }
@ -86,7 +86,7 @@ func (h IndexHandler) IngestIndex(writer http.ResponseWriter, req *http.Request)
}) })
if indexErr != nil { if indexErr != nil {
logger.ErrorContext(req.Context(), "Failed to ingest index", logging.Error(indexErr)) logger.ErrorContext(req.Context(), "Failed to ingest index", logging2.Error(indexErr))
} }
}() }()

View file

@ -3,6 +3,10 @@ package v1
import ( import (
"net/http" "net/http"
"code.icb4dc0.de/prskr/searcherside/handlers/graphql"
"code.icb4dc0.de/prskr/searcherside/internal/ent"
"github.com/99designs/gqlgen/graphql/handler"
"github.com/99designs/gqlgen/graphql/playground"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/go-chi/cors" "github.com/go-chi/cors"
"github.com/go-chi/jwtauth/v5" "github.com/go-chi/jwtauth/v5"
@ -14,7 +18,11 @@ func Mount(
authSecret []byte, authSecret []byte,
indexHandler IndexHandler, indexHandler IndexHandler,
searchHandler SearchHandler, searchHandler SearchHandler,
dbClient *ent.Client,
) { ) {
graphQlServer := handler.NewDefaultServer(graphql.NewSchema(dbClient))
r.Mount("/graphql", graphQlServer)
r.Mount("/graphql/playground", playground.Handler("SearcherSide", "/api/v1/graphql"))
r.Group(func(r chi.Router) { r.Group(func(r chi.Router) {
jwtAuth := jwtauth.New(jwa.HS256.String(), authSecret, nil) jwtAuth := jwtauth.New(jwa.HS256.String(), authSecret, nil)

View file

@ -9,7 +9,7 @@ import (
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"code.icb4dc0.de/prskr/searcherside/core/ports" "code.icb4dc0.de/prskr/searcherside/core/ports"
"code.icb4dc0.de/prskr/searcherside/internal/logging" logging2 "code.icb4dc0.de/prskr/searcherside/infrastructure/logging"
) )
type SearchHandler struct { type SearchHandler struct {
@ -17,7 +17,7 @@ type SearchHandler struct {
} }
func (h SearchHandler) PreviewSearch(writer http.ResponseWriter, request *http.Request) { func (h SearchHandler) PreviewSearch(writer http.ResponseWriter, request *http.Request) {
logger := logging.GetLogger(request.Context()) logger := logging2.GetLogger(request.Context())
idxKey := ports.IndexKey{ idxKey := ports.IndexKey{
Module: chi.URLParam(request, "module"), Module: chi.URLParam(request, "module"),
@ -27,19 +27,19 @@ func (h SearchHandler) PreviewSearch(writer http.ResponseWriter, request *http.R
logger.Info("Get searcher for index", slog.String("module", idxKey.Module), slog.String("instance", idxKey.Instance)) logger.Info("Get searcher for index", slog.String("module", idxKey.Module), slog.String("instance", idxKey.Instance))
searcher, err := h.Curator.Searcher(idxKey) searcher, err := h.Curator.Searcher(idxKey)
if err != nil { if err != nil {
logger.Error("Error getting searcher", logging.Error(err)) logger.Error("Error getting searcher", logging2.Error(err))
} }
result, err := searcher.Search(request.Context(), searchRequestFrom(request)) result, err := searcher.Search(request.Context(), searchRequestFrom(request))
if err != nil { if err != nil {
logger.Error("Failed to search", logging.Error(err)) logger.Error("Failed to search", logging2.Error(err))
http.Error(writer, "Failed to search", http.StatusInternalServerError) http.Error(writer, "Failed to search", http.StatusInternalServerError)
} }
writer.Header().Set("Content-Type", "application/json") writer.Header().Set("Content-Type", "application/json")
encoder := json.NewEncoder(writer) encoder := json.NewEncoder(writer)
if err := encoder.Encode(result); err != nil { if err := encoder.Encode(result); err != nil {
logger.Error("Failed to encode search result", logging.Error(err)) logger.Error("Failed to encode search result", logging2.Error(err))
http.Error(writer, "Failed to encode search result", http.StatusInternalServerError) http.Error(writer, "Failed to encode search result", http.StatusInternalServerError)
return return
} }

46
handlers/cli/migrate.go Normal file
View file

@ -0,0 +1,46 @@
package cli
import (
"context"
"errors"
"fmt"
"github.com/alecthomas/kong"
"code.icb4dc0.de/prskr/searcherside/core/ports"
"code.icb4dc0.de/prskr/searcherside/infrastructure/config"
"code.icb4dc0.de/prskr/searcherside/internal/ent"
)
type MigrateHandler struct {
DB config.DB `embed:"" prefix:"db."`
}
func (m *MigrateHandler) Run(ctx context.Context, migrator ports.Migrator, client *ent.Client) (err error) {
defer func() {
err = errors.Join(err, client.Close())
}()
req := ports.MigrationRequest{
Driver: m.DB.Driver,
URL: m.DB.URL,
}
if err := migrator.Migrate(ctx, req); err != nil {
return fmt.Errorf("failed to apply migrations: %w", err)
}
return nil
}
func (m *MigrateHandler) AfterApply(kctx *kong.Context) error {
migrator, client, err := m.DB.Migrator()
if err != nil {
return fmt.Errorf("failed to initialize migrator: %w", err)
}
kctx.Bind(client)
kctx.BindTo(migrator, (*ports.Migrator)(nil))
return nil
}

View file

@ -11,13 +11,17 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"github.com/alecthomas/kong"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"code.icb4dc0.de/prskr/searcherside/infrastructure/config"
logging2 "code.icb4dc0.de/prskr/searcherside/infrastructure/logging"
"code.icb4dc0.de/prskr/searcherside/internal/cli"
"code.icb4dc0.de/prskr/searcherside/internal/ent"
"code.icb4dc0.de/prskr/searcherside/core/services" "code.icb4dc0.de/prskr/searcherside/core/services"
v1 "code.icb4dc0.de/prskr/searcherside/handlers/api/v1" v1 "code.icb4dc0.de/prskr/searcherside/handlers/api/v1"
"code.icb4dc0.de/prskr/searcherside/infrastructure/api" "code.icb4dc0.de/prskr/searcherside/infrastructure/api/middlewares"
"code.icb4dc0.de/prskr/searcherside/internal/flags"
"code.icb4dc0.de/prskr/searcherside/internal/logging"
) )
const ( const (
@ -33,19 +37,23 @@ type ServerHandler struct {
ParseMaxMemoryBytes int64 `env:"HTTP_PARSE_MAX_MEMORY_BYTES" name:"parse-max-memory-bytes" help:"Parse max memory bytes" default:"33554432"` ParseMaxMemoryBytes int64 `env:"HTTP_PARSE_MAX_MEMORY_BYTES" name:"parse-max-memory-bytes" help:"Parse max memory bytes" default:"33554432"`
} `embed:"" prefix:"http."` } `embed:"" prefix:"http."`
Auth struct { Auth struct {
JwtSecret flags.HexString `env:"AUTH_JWT_SECRET" name:"jwt-secret" help:"JWT secret"` JwtSecret cli.HexString `env:"AUTH_JWT_SECRET" name:"jwt-secret" help:"JWT secret"`
} `embed:"" prefix:"auth."` } `embed:"" prefix:"auth."`
DB config.DB `embed:"" prefix:"db."`
} }
func (h *ServerHandler) Run(ctx context.Context, logger *slog.Logger) error { func (h *ServerHandler) Run(ctx context.Context, entClient *ent.Client, logger *slog.Logger) (err error) {
defer func() {
err = errors.Join(err, entClient.Close())
}()
indexCurator, err := services.NewFileIndexCurator( indexCurator, err := services.NewFileIndexCurator(
filepath.Join(h.DataDirectory, "searcherside.json"), filepath.Join(h.DataDirectory, "searcherside.json"),
services.BleveIndexer{DataDirectory: h.DataDirectory}, services.BleveIndexer{DataDirectory: h.DataDirectory},
services.TarZSTIndexArchiver{DataDirectory: h.DataDirectory}, services.TarZSTIndexArchiver{DataDirectory: h.DataDirectory},
) )
if err != nil { if err != nil {
logger.Error("Failed to create index curator", logging.Error(err)) logger.Error("Failed to create index curator", logging2.Error(err))
return err return err
} }
@ -55,7 +63,7 @@ func (h *ServerHandler) Run(ctx context.Context, logger *slog.Logger) error {
} }
r := chi.NewRouter() r := chi.NewRouter()
r.Use(api.LoggingMiddleware) r.Use(middlewares.LoggingMiddleware)
r.Route("/api/v1", func(r chi.Router) { r.Route("/api/v1", func(r chi.Router) {
indexHandler := v1.IndexHandler{ indexHandler := v1.IndexHandler{
@ -67,7 +75,7 @@ func (h *ServerHandler) Run(ctx context.Context, logger *slog.Logger) error {
Curator: indexCurator, Curator: indexCurator,
} }
v1.Mount(r, secret, indexHandler, searchHandler) v1.Mount(r, secret, indexHandler, searchHandler, entClient)
}) })
srv := http.Server{ srv := http.Server{
@ -75,7 +83,7 @@ func (h *ServerHandler) Run(ctx context.Context, logger *slog.Logger) error {
Handler: r, Handler: r,
ReadHeaderTimeout: h.Config.ReadHeaderTimeout, ReadHeaderTimeout: h.Config.ReadHeaderTimeout,
BaseContext: func(listener net.Listener) context.Context { BaseContext: func(listener net.Listener) context.Context {
return logging.ContextWithLogger(ctx, logger) return logging2.ContextWithLogger(ctx, logger)
}, },
} }
@ -83,7 +91,7 @@ func (h *ServerHandler) Run(ctx context.Context, logger *slog.Logger) error {
go func() { go func() {
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
logger.Error("Failed to start server", logging.Error(err)) logger.Error("Failed to start server", logging2.Error(err))
} }
}() }()
@ -91,13 +99,23 @@ func (h *ServerHandler) Run(ctx context.Context, logger *slog.Logger) error {
logger.Info("Shutting down server") logger.Info("Shutting down server")
shutdownCtx, cancel := context.WithTimeout(context.Background(), h.Config.ShutDownTimeout) shutdownCtx, cancel := context.WithTimeout(context.Background(), h.Config.ShutDownTimeout)
if err := srv.Shutdown(shutdownCtx); err != nil { if err := srv.Shutdown(shutdownCtx); err != nil {
logger.Error("Failed to shutdown server", logging.Error(err)) logger.Error("Failed to shutdown server", logging2.Error(err))
} }
cancel() cancel()
return nil return nil
} }
func (h *ServerHandler) AfterApply(kctx *kong.Context) error {
if entClient, err := h.DB.Client(); err != nil {
return err
} else {
kctx.Bind(entClient)
}
return nil
}
func (h *ServerHandler) jwtSecret() ([]byte, error) { func (h *ServerHandler) jwtSecret() ([]byte, error) {
if len(h.Auth.JwtSecret) == 0 { if len(h.Auth.JwtSecret) == 0 {
h.Auth.JwtSecret = make([]byte, jwtSecretLength) h.Auth.JwtSecret = make([]byte, jwtSecretLength)

View file

@ -10,19 +10,17 @@ import (
"github.com/lestrrat-go/jwx/v2/jwt" "github.com/lestrrat-go/jwx/v2/jwt"
"code.icb4dc0.de/prskr/searcherside/core/ports" "code.icb4dc0.de/prskr/searcherside/core/ports"
"code.icb4dc0.de/prskr/searcherside/internal/flags" "code.icb4dc0.de/prskr/searcherside/internal/cli"
) )
var ( var ErrJwtSecretRequired = errors.New("JWT secret is required")
ErrJwtSecretRequired = errors.New("JWT secret is required")
)
type TokenHandler struct { type TokenHandler struct {
Token struct { Token struct {
Secret flags.HexString `name:"secret" help:"JWT secret"` Secret cli.HexString `name:"secret" help:"JWT secret"`
Lifetime time.Duration `name:"lifetime" help:"JWT lifetime" default:"24h"` Lifetime time.Duration `name:"lifetime" help:"JWT lifetime" default:"24h"`
Subject string `name:"subject" help:"JWT subject" default:"${WHOAMI=nobody}"` Subject string `name:"subject" help:"JWT subject" default:"${WHOAMI=nobody}"`
Claims []flags.TokenClaim `name:"claims" help:"JWT claims"` Claims []cli.TokenClaim `name:"claims" help:"JWT claims"`
} `embed:"" prefix:"token."` } `embed:"" prefix:"token."`
} }

View file

@ -0,0 +1,49 @@
package cli
import (
"context"
"errors"
"github.com/alecthomas/kong"
"code.icb4dc0.de/prskr/searcherside/core/cq"
"code.icb4dc0.de/prskr/searcherside/core/ports"
"code.icb4dc0.de/prskr/searcherside/core/services"
"code.icb4dc0.de/prskr/searcherside/infrastructure/config"
"code.icb4dc0.de/prskr/searcherside/infrastructure/repository"
)
type CreateUserHandler struct {
Email string `arg:""`
Password []byte `arg:"" type:"password"`
Admin bool `name:"admin" help:"Should the user be an admin" default:"false"`
DB config.DB `embed:"" prefix:"db."`
}
func (h *CreateUserHandler) Run(ctx context.Context, userRepo ports.UserRepository) (err error) {
defer func() {
err = errors.Join(err, userRepo.Close())
}()
_, err = userRepo.CreateUser(ctx, cq.CreateUserRequest{
Email: h.Email,
Password: h.Password,
Admin: h.Admin,
})
return err
}
func (h *CreateUserHandler) AfterApply(kctx *kong.Context) error {
client, err := h.DB.Client()
if err != nil {
return err
}
kctx.BindTo(
repository.NewEntUserRepository(client, new(services.Argon2IDHashAlgorithm)),
(*ports.UserRepository)(nil),
)
return nil
}

23
handlers/cli/users.go Normal file
View file

@ -0,0 +1,23 @@
package cli
import (
"github.com/alecthomas/kong"
"code.icb4dc0.de/prskr/searcherside/infrastructure/config"
)
type UsersHandler struct {
Create CreateUserHandler `cmd:"" name:"create" help:"Create user"`
DB config.DB `embed:"" prefix:"db."`
}
func (h *UsersHandler) AfterApply(kctx *kong.Context) error {
if entClient, err := h.DB.Client(); err != nil {
return err
} else {
kctx.Bind(entClient)
}
return nil
}

View file

@ -0,0 +1,38 @@
package graphql
// This file will be automatically regenerated based on the schema, any resolver implementations
// will be copied through when generating and any unknown code will be moved to the end.
// Code generated by github.com/99designs/gqlgen version v0.17.48
import (
"context"
"code.icb4dc0.de/prskr/searcherside/infrastructure/api/graphql/generated"
"code.icb4dc0.de/prskr/searcherside/internal/ent"
"github.com/google/uuid"
)
// Node is the resolver for the node field.
func (r *queryResolver) Node(ctx context.Context, id uuid.UUID) (ent.Noder, error) {
return r.client.Noder(ctx, id)
}
// Nodes is the resolver for the nodes field.
func (r *queryResolver) Nodes(ctx context.Context, ids []uuid.UUID) ([]ent.Noder, error) {
return r.client.Noders(ctx, ids)
}
// Sites is the resolver for the sites field.
func (r *queryResolver) Sites(ctx context.Context) ([]*ent.Site, error) {
return r.client.Site.Query().All(ctx)
}
// Users is the resolver for the users field.
func (r *queryResolver) Users(ctx context.Context) ([]*ent.User, error) {
return r.client.User.Query().All(ctx)
}
// Query returns generated.QueryResolver implementation.
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
type queryResolver struct{ *Resolver }

View file

@ -0,0 +1,20 @@
package graphql
import (
"code.icb4dc0.de/prskr/searcherside/infrastructure/api/graphql/generated"
"code.icb4dc0.de/prskr/searcherside/internal/ent"
"github.com/99designs/gqlgen/graphql"
)
// NewSchema creates a graphql executable schema.
func NewSchema(client *ent.Client) graphql.ExecutableSchema {
return generated.NewExecutableSchema(generated.Config{
Resolvers: &Resolver{
client: client,
},
})
}
type Resolver struct {
client *ent.Client
}

View file

@ -1,4 +1,4 @@
package api package middlewares
import ( import (
"encoding/hex" "encoding/hex"
@ -8,7 +8,7 @@ import (
"strconv" "strconv"
"time" "time"
"code.icb4dc0.de/prskr/searcherside/internal/logging" "code.icb4dc0.de/prskr/searcherside/infrastructure/logging"
) )
func LoggingMiddleware(next http.Handler) http.Handler { func LoggingMiddleware(next http.Handler) http.Handler {

View file

@ -0,0 +1,29 @@
package config
import (
"code.icb4dc0.de/prskr/searcherside/core/ports"
"code.icb4dc0.de/prskr/searcherside/infrastructure/db/migrate"
"code.icb4dc0.de/prskr/searcherside/internal/ent"
"code.icb4dc0.de/prskr/searcherside/scripts/migrations"
)
type DB struct {
Driver ports.Driver `env:"DB_DRIVER" name:"driver" default:"sqlite" help:"DB driver, either postgres or sqlite"`
URL string `env:"DB_URL" name:"url" default:"sqlite://file::memory:?cache=shared" help:"Connection URL"`
}
func (d DB) Client() (*ent.Client, error) {
return ent.Open(d.Driver.String(), d.URL)
}
func (d DB) Migrator() (ports.Migrator, *ent.Client, error) {
client, err := d.Client()
if err != nil {
return nil, nil, err
}
return migrate.AtlasMigrator{
MigrationsFS: migrations.Fs,
RevisionRW: migrate.NewDBRevisionRW(client),
}, client, nil
}

View file

@ -0,0 +1,17 @@
package config
import (
"log/slog"
)
type Logging struct {
AddSource bool `env:"LOG_ADD_SOURCE" name:"add-source" default:"false"`
Level slog.Level `env:"LOG_LEVEL" name:"level" default:"info" help:"Log level to apply"`
}
func (l Logging) Options() *slog.HandlerOptions {
return &slog.HandlerOptions{
Level: l.Level,
AddSource: l.AddSource,
}
}

View file

@ -56,14 +56,8 @@ func (y Yaml) Loader(r io.Reader) (kong.Resolver, error) {
} }
return kong.ResolverFunc(func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (any, error) { return kong.ResolverFunc(func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (any, error) {
path := strings.Split(flag.Name, y.separator()) if val := lookup(config, y.normalize(strings.Split(flag.Name, y.separator()))); val != nil {
return val, nil
if n := parent.Node(); n != nil && n.Type != kong.ApplicationNode {
for _, prefix := range append(n.Aliases, n.Name) {
if val := lookup(config, y.normalize(append([]string{prefix}, path...))); val != nil {
return val, nil
}
}
} }
return nil, nil return nil, nil

44
infrastructure/db/entc.go Normal file
View file

@ -0,0 +1,44 @@
//go:build ignore
package main
import (
"log"
"path/filepath"
"entgo.io/contrib/entgql"
"entgo.io/ent/entc"
"entgo.io/ent/entc/gen"
)
func main() {
ex, err := entgql.NewExtension(
entgql.WithWhereInputs(true),
// Tell Ent to generate a GraphQL schema for
// the Ent schema in a file named ent.graphql.
entgql.WithSchemaGenerator(),
entgql.WithSchemaPath(filepath.Join("assets", "api", "ent.graphql")),
entgql.WithConfigPath("gqlgen.yml"),
)
if err != nil {
log.Fatalf("creating entgql extension: %v", err)
}
opts := []entc.Option{
entc.Extensions(ex),
}
generatorConfig := &gen.Config{
Target: filepath.Join("internal", "ent"),
Package: "code.icb4dc0.de/prskr/searcherside/internal/ent",
Features: []gen.Feature{
gen.FeatureVersionedMigration,
gen.FeatureUpsert,
gen.FeaturePrivacy,
gen.FeatureEntQL,
},
}
if err := entc.Generate("./infrastructure/db/schema", generatorConfig, opts...); err != nil {
log.Fatalf("running ent codegen: %v", err)
}
}

View file

@ -0,0 +1,80 @@
package migrate
import (
"context"
"database/sql"
"errors"
"fmt"
"io/fs"
"code.icb4dc0.de/prskr/searcherside/core/ports"
"ariga.io/atlas/sql/migrate"
"ariga.io/atlas/sql/postgres"
"ariga.io/atlas/sql/sqlite"
)
var _ ports.Migrator = (*AtlasMigrator)(nil)
type AtlasMigrator struct {
MigrationsFS fs.FS
RevisionRW ports.RevisionReadWriter
}
func (a AtlasMigrator) Migrate(ctx context.Context, req ports.MigrationRequest) (err error) {
dialectFS, err := fs.Sub(a.MigrationsFS, req.Driver.String())
if err != nil {
return fmt.Errorf("no migrations sub-directory found for dialect %s: %w", req.Driver, err)
}
migrateDriver, conn, err := migrationDriverFor(req.Driver, req.URL)
if err != nil {
return err
}
defer func() {
err = errors.Join(err, conn.Close())
}()
executor, err := migrate.NewExecutor(migrateDriver, readOnlyFSDir{FS: dialectFS}, a.RevisionRW)
if err != nil {
return err
}
pendingFiles, err := executor.Pending(ctx)
if err != nil {
return err
}
for idx := range pendingFiles {
if err = executor.Execute(ctx, pendingFiles[idx]); err != nil {
return err
}
}
return nil
}
func migrationDriverFor(driverName ports.Driver, url string) (drv migrate.Driver, db *sql.DB, err error) {
conn, err := sql.Open(driverName.String(), url)
if err != nil {
return nil, nil, err
}
defer func() {
if err != nil {
err = errors.Join(err, conn.Close())
}
}()
switch driverName {
case ports.DriverSQLite:
drv, err = sqlite.Open(conn)
return drv, conn, err
case ports.DriverPostgres:
drv, err = postgres.Open(conn)
return drv, conn, err
default:
return nil, nil, fmt.Errorf("unknown driver: %s", driverName)
}
}

View file

@ -0,0 +1,71 @@
package migrate_test
import (
"context"
"fmt"
"path/filepath"
"testing"
"code.icb4dc0.de/prskr/searcherside/core/ports"
"code.icb4dc0.de/prskr/searcherside/infrastructure/db/migrate"
_ "code.icb4dc0.de/prskr/searcherside/internal/db"
"code.icb4dc0.de/prskr/searcherside/internal/ent"
"code.icb4dc0.de/prskr/searcherside/scripts/migrations"
"entgo.io/ent/dialect"
)
func TestAtlasMigrator_Migrate_SQLite(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "gophershare.db")
req := ports.MigrationRequest{
Driver: ports.DriverSQLite,
URL: fmt.Sprintf("file://%s?_pragma=foreign_keys(1)", dbPath),
}
client, err := ent.Open(dialect.SQLite, req.URL)
if err != nil {
t.Fatalf("failed to open sqlite3 client: %v", err)
}
migrator := migrate.AtlasMigrator{
MigrationsFS: migrations.Fs,
RevisionRW: migrate.NewDBRevisionRW(client),
}
if err := migrator.Migrate(context.Background(), req); err != nil {
t.Fatalf("failed to migrate: %v", err)
}
}
func TestAtlasMigrator_Migrate_Postgres(t *testing.T) {
connString, closer, err := dbtest.TestPostgresDatabase(context.Background())
if err != nil {
t.Fatalf("failed to setup Postgres test DB: %v", err)
}
t.Cleanup(func() {
if err := closer.Close(context.Background()); err != nil {
t.Errorf("failed to close Postgres test DB: %v", err)
}
})
req := ports.MigrationRequest{
Driver: ports.DriverPostgres,
URL: connString,
}
client, err := ent.Open(dialect.Postgres, req.URL)
if err != nil {
t.Fatalf("failed to open sqlite3 client: %v", err)
}
migrator := migrate.AtlasMigrator{
MigrationsFS: migrations.Fs,
RevisionRW: migrate.NewDBRevisionRW(client),
}
if err = migrator.Migrate(context.Background(), req); err != nil {
t.Fatalf("failed to migrate: %v", err)
}
}

View file

@ -0,0 +1,144 @@
package migrate
import (
"context"
"errors"
"time"
"code.icb4dc0.de/prskr/searcherside/core/ports"
"code.icb4dc0.de/prskr/searcherside/internal/ent"
"code.icb4dc0.de/prskr/searcherside/internal/ent/migration"
"ariga.io/atlas/sql/migrate"
"github.com/jackc/pgx/v5/pgconn"
"modernc.org/sqlite"
)
var _ ports.RevisionReadWriter = (*DBRevisionRW)(nil)
func NewDBRevisionRW(client *ent.Client) ports.RevisionReadWriter {
return &DBRevisionRW{
client: client,
}
}
type DBRevisionRW struct {
client *ent.Client
}
func (rw DBRevisionRW) Client() *ent.Client {
return rw.client
}
func (DBRevisionRW) Ident() *migrate.TableIdent {
return &migrate.TableIdent{
Name: migration.Table,
}
}
func (rw DBRevisionRW) ReadRevisions(ctx context.Context) ([]*migrate.Revision, error) {
allMigrations, err := rw.client.Migration.
Query().
All(ctx)
if err != nil && !ignoreError(err) {
return nil, err
}
revs := make([]*migrate.Revision, len(allMigrations))
for idx := range allMigrations {
revs[idx] = revisionOf(allMigrations[idx])
}
return revs, nil
}
func (rw DBRevisionRW) ReadRevision(ctx context.Context, s string) (*migrate.Revision, error) {
m, err := rw.client.Migration.
Query().
Where(migration.VersionEQ(s)).
Only(ctx)
if err != nil {
if ignoreError(err) {
return nil, migrate.ErrRevisionNotExist
}
return nil, err
}
return revisionOf(m), nil
}
func (rw DBRevisionRW) WriteRevision(ctx context.Context, revision *migrate.Revision) error {
err := rw.client.Migration.Create().
SetVersion(revision.Version).
SetDescription(revision.Description).
SetType(uint(revision.Type)).
SetApplied(revision.Applied).
SetTotal(revision.Total).
SetExecutedAt(revision.ExecutedAt).
SetExecutionTime(int64(revision.ExecutionTime)).
SetError(revision.Error).
SetErrorStmt(revision.ErrorStmt).
SetHash(revision.Hash).
SetPartialHashes(revision.PartialHashes).
SetOperatorVersion(revision.OperatorVersion).
OnConflictColumns(migration.FieldVersion).
UpdateNewValues().
Exec(ctx)
if ignoreError(err) {
return nil
}
return err
}
func (rw DBRevisionRW) DeleteRevision(ctx context.Context, s string) error {
_, err := rw.client.Migration.Delete().Where(migration.VersionEQ(s)).Exec(ctx)
return err
}
func revisionOf(m *ent.Migration) *migrate.Revision {
if m == nil {
return new(migrate.Revision)
}
return &migrate.Revision{
Version: m.Version,
Description: m.Description,
Type: migrate.RevisionType(m.Type),
Applied: m.Applied,
Total: m.Total,
ExecutedAt: m.ExecutedAt,
ExecutionTime: time.Duration(m.ExecutionTime),
Error: m.Error,
ErrorStmt: m.ErrorStmt,
Hash: m.Hash,
PartialHashes: m.PartialHashes,
OperatorVersion: m.OperatorVersion,
}
}
func ignoreError(err error) bool {
if ent.IsNotFound(err) {
return true
}
var postgresErr *pgconn.PgError
if errors.As(err, &postgresErr) {
switch postgresErr.Code {
case "42P01":
return true
}
}
// DB specific errors
var sqliteErr *sqlite.Error
if errors.As(err, &sqliteErr) {
if sqliteErr.Code() == 1 {
return true
}
}
return false
}

View file

@ -0,0 +1,79 @@
package migrate
import (
"errors"
"fmt"
"io"
"io/fs"
"path/filepath"
"ariga.io/atlas/sql/migrate"
)
var _ migrate.Dir = (*readOnlyFSDir)(nil)
type readOnlyFSDir struct {
fs.FS
}
func (m readOnlyFSDir) WriteFile(string, []byte) error {
return errors.New("migration directory is read-only")
}
func (m readOnlyFSDir) Files() ([]migrate.File, error) {
files, err := fs.ReadDir(m, ".")
if err != nil {
return nil, err
}
filesList := make([]migrate.File, 0, len(files))
for idx := range files {
f := files[idx]
if f.IsDir() || filepath.Ext(f.Name()) != ".sql" {
continue
}
scriptFile, err := m.Open(f.Name())
if err != nil {
return nil, fmt.Errorf("failed to open %s: %w", f.Name(), err)
}
defer func() {
_ = scriptFile.Close()
}()
scriptData, err := io.ReadAll(scriptFile)
if err != nil {
return nil, fmt.Errorf("failed to read %s: %w", f.Name(), err)
}
filesList = append(filesList, migrate.NewLocalFile(f.Name(), scriptData))
}
return filesList, nil
}
func (m readOnlyFSDir) Checksum() (migrate.HashFile, error) {
var hf migrate.HashFile
f, err := m.Open(migrate.HashFileName)
if err != nil {
return nil, err
}
defer func() {
_ = f.Close()
}()
sumData, err := io.ReadAll(f)
if err != nil {
return nil, fmt.Errorf("failed to %s: %w", migrate.HashFileName, err)
}
if err := hf.UnmarshalText(sumData); err != nil {
return nil, fmt.Errorf("failed to unmarshal %s: %w", migrate.HashFileName, err)
}
return hf, nil
}

View file

@ -0,0 +1,59 @@
package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/edge"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
"entgo.io/ent/schema/mixin"
"github.com/google/uuid"
)
type Index struct {
ent.Schema
}
func (Index) Fields() []ent.Field {
return []ent.Field{
field.UUID("id", uuid.UUID{}).
Default(uuid.New).
Immutable().
Unique(),
field.UUID("site_id", uuid.UUID{}).
Default(uuid.New).
Immutable().
Unique(),
field.String("name").
NotEmpty(),
field.String("revision").
Immutable().
NotEmpty(),
}
}
func (Index) Edges() []ent.Edge {
return []ent.Edge{
edge.From("index", Site.Type).
Field("site_id").
Ref("indices").
Unique().
Immutable().
Required(),
}
}
func (Index) Indexes() []ent.Index {
return []ent.Index{
index.Fields("site_id", "name", "revision").
Unique(),
}
}
func (Index) Mixin() []ent.Mixin {
return []ent.Mixin{
mixin.Time{},
}
}

View file

@ -0,0 +1,62 @@
package schema
import (
"ariga.io/atlas/sql/migrate"
"entgo.io/contrib/entgql"
"entgo.io/ent"
"entgo.io/ent/schema"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/mixin"
)
type Migration struct {
ent.Schema
}
func (Migration) Fields() []ent.Field {
return []ent.Field{
field.String("version").
Unique().
Immutable(),
field.String("description").
Optional(),
field.Uint("type").
Default(uint(migrate.RevisionTypeUnknown)),
field.Int("applied").
Optional(),
field.Int("total").
Optional(),
field.Time("executed_at"),
field.Int64("execution_time"),
field.String("error").
Optional(),
field.String("error_stmt").
Optional(),
field.String("hash"),
field.Strings("partial_hashes"),
field.String("operator_version"),
}
}
func (Migration) Mixin() []ent.Mixin {
return []ent.Mixin{
mixin.Time{},
}
}
func (Migration) Annotations() []schema.Annotation {
return []schema.Annotation{
entgql.Skip(entgql.SkipAll),
}
}

View file

@ -0,0 +1,46 @@
package schema
import (
"entgo.io/contrib/entgql"
"entgo.io/ent"
"entgo.io/ent/schema"
"entgo.io/ent/schema/edge"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/mixin"
"github.com/google/uuid"
)
type Site struct {
ent.Schema
}
func (Site) Fields() []ent.Field {
return []ent.Field{
field.UUID("id", uuid.UUID{}).
Immutable().
Default(uuid.New),
field.String("name").
Immutable().
Unique(),
}
}
func (Site) Edges() []ent.Edge {
return []ent.Edge{
edge.To("site_members", UserStaff.Type),
edge.To("indices", Index.Type),
}
}
func (Site) Mixin() []ent.Mixin {
return []ent.Mixin{
mixin.Time{},
}
}
func (Site) Annotations() []schema.Annotation {
return []schema.Annotation{
entgql.QueryField(),
}
}

View file

@ -0,0 +1,69 @@
package schema
import (
"entgo.io/contrib/entgql"
"entgo.io/ent"
"entgo.io/ent/schema"
"entgo.io/ent/schema/edge"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
"entgo.io/ent/schema/mixin"
"github.com/google/uuid"
)
type User struct {
ent.Schema
}
func (User) Fields() []ent.Field {
return []ent.Field{
field.UUID("id", uuid.UUID{}).
Default(uuid.New).
Immutable().
Unique(),
field.String("email").
NotEmpty().
Immutable().
Unique(),
field.String("given_name").
Optional(),
field.String("surname").
Optional(),
field.Bool("is_admin").
Default(false).
Annotations(entgql.Skip(entgql.SkipAll)),
field.Bytes("password").
NotEmpty().
Sensitive().
Annotations(entgql.Skip(entgql.SkipAll)),
}
}
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("staffs", UserStaff.Type),
}
}
func (User) Indexes() []ent.Index {
return []ent.Index{
index.Fields("email"),
}
}
func (User) Mixin() []ent.Mixin {
return []ent.Mixin{
mixin.Time{},
}
}
func (User) Annotations() []schema.Annotation {
return []schema.Annotation{
entgql.QueryField(),
}
}

View file

@ -0,0 +1,52 @@
package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/edge"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/mixin"
"github.com/google/uuid"
)
type UserStaff struct {
ent.Schema
}
func (UserStaff) Fields() []ent.Field {
return []ent.Field{
field.UUID("id", uuid.UUID{}).
Default(uuid.New).
Immutable().
Unique(),
field.UUID("user_id", uuid.UUID{}).
Immutable(),
field.UUID("site_id", uuid.UUID{}).
Immutable(),
}
}
func (UserStaff) Edges() []ent.Edge {
return []ent.Edge{
edge.From("users", User.Type).
Field("user_id").
Ref("staffs").
Unique().
Immutable().
Required(),
edge.From("site", Site.Type).
Field("site_id").
Ref("site_members").
Unique().
Immutable().
Required(),
}
}
func (UserStaff) Mixin() []ent.Mixin {
return []ent.Mixin{
mixin.Time{},
}
}

View file

@ -0,0 +1,24 @@
package uuidgql
import (
"fmt"
"io"
"strconv"
"github.com/99designs/gqlgen/graphql"
"github.com/google/uuid"
)
func MarshalUUID(u uuid.UUID) graphql.Marshaler {
return graphql.WriterFunc(func(w io.Writer) {
_, _ = io.WriteString(w, strconv.Quote(u.String()))
})
}
func UnmarshalUUID(v interface{}) (u uuid.UUID, err error) {
s, ok := v.(string)
if !ok {
return u, fmt.Errorf("invalid type %T, expect string", v)
}
return uuid.Parse(s)
}

View file

@ -0,0 +1,65 @@
package repository
import (
"context"
"fmt"
"github.com/google/uuid"
"code.icb4dc0.de/prskr/searcherside/core/cq"
"code.icb4dc0.de/prskr/searcherside/core/domain"
"code.icb4dc0.de/prskr/searcherside/core/ports"
"code.icb4dc0.de/prskr/searcherside/internal/ent"
"code.icb4dc0.de/prskr/searcherside/internal/shred"
)
var _ ports.UserRepository = (*EntUserRepository)(nil)
func NewEntUserRepository(client *ent.Client, hashAlgorithm ports.PasswordHashAlgorithm) *EntUserRepository {
return &EntUserRepository{
client: client,
hashAlgorithm: hashAlgorithm,
}
}
type EntUserRepository struct {
client *ent.Client
hashAlgorithm ports.PasswordHashAlgorithm
}
func (e EntUserRepository) Close() error {
return e.client.Close()
}
func (e EntUserRepository) CreateUser(ctx context.Context, user cq.CreateUserRequest) (*cq.CreateUserResponse, error) {
defer func() {
shred.Bytes(user.Password)
}()
hashedPassword, err := e.hashAlgorithm.Hash(user.Password)
if err != nil {
return nil, fmt.Errorf("failed to hash password: %w", err)
}
_, err = e.client.User.Create().
SetEmail(user.Email).
SetPassword(hashedPassword).
SetIsAdmin(user.Admin).
Save(ctx)
if err != nil {
return nil, fmt.Errorf("failed to create user: %w", err)
}
return new(cq.CreateUserResponse), nil
}
func (e EntUserRepository) UserByID(ctx context.Context, id uuid.UUID) (*domain.User, error) {
//TODO implement me
panic("implement me")
}
func (e EntUserRepository) UserByEmail(ctx context.Context, email string) (*domain.User, error) {
//TODO implement me
panic("implement me")
}

View file

@ -12,15 +12,16 @@ import (
"code.icb4dc0.de/prskr/searcherside/core/ports" "code.icb4dc0.de/prskr/searcherside/core/ports"
clih "code.icb4dc0.de/prskr/searcherside/handlers/cli" clih "code.icb4dc0.de/prskr/searcherside/handlers/cli"
"code.icb4dc0.de/prskr/searcherside/infrastructure/config" "code.icb4dc0.de/prskr/searcherside/infrastructure/config"
"code.icb4dc0.de/prskr/searcherside/internal/cli"
) )
type App struct { type App struct {
Logging struct { Logging config.Logging `embed:"" prefix:"logging."`
Level slog.Level `env:"LOG_LEVEL" help:"Log level" default:"warn"`
} `embed:"" prefix:"logging."`
Serve clih.ServerHandler `cmd:"" name:"serve" help:"Start the server" aliases:"server"` Serve clih.ServerHandler `cmd:"" name:"serve" help:"Start the server" aliases:"server"`
Token clih.TokenHandler `cmd:"" name:"token" help:"Generate a token"` Migrate clih.MigrateHandler `cmd:"" name:"migrate" help:"Apply database migrations"`
User clih.UsersHandler `cmd:"" name:"user" help:"Manage users"`
Token clih.TokenHandler `cmd:"" name:"token" help:"Generate a token"`
} }
func (a *App) Execute() error { func (a *App) Execute() error {
@ -40,6 +41,7 @@ func (a *App) Execute() error {
a, a,
kong.Name("searcherside"), kong.Name("searcherside"),
kong.Description("SearcherSide"), kong.Description("SearcherSide"),
kong.NamedMapper("password", cli.PasswordMapper{}),
kong.Bind(ports.CWD(wd)), kong.Bind(ports.CWD(wd)),
kong.BindTo(os.Stdout, (*ports.STDOUT)(nil)), kong.BindTo(os.Stdout, (*ports.STDOUT)(nil)),
kong.BindTo(ctx, (*context.Context)(nil)), kong.BindTo(ctx, (*context.Context)(nil)),
@ -54,11 +56,7 @@ func (a *App) Execute() error {
} }
func (a *App) AfterApply(kongCtx *kong.Context) error { func (a *App) AfterApply(kongCtx *kong.Context) error {
loggingOpts := slog.HandlerOptions{ defaultLogger := slog.New(slog.NewJSONHandler(os.Stderr, a.Logging.Options()))
Level: a.Logging.Level,
}
defaultLogger := slog.New(slog.NewJSONHandler(os.Stderr, &loggingOpts))
slog.SetDefault(defaultLogger) slog.SetDefault(defaultLogger)

View file

@ -1,4 +1,4 @@
package flags package cli
import ( import (
"fmt" "fmt"

View file

@ -1,4 +1,4 @@
package flags package cli
import ( import (
"encoding/hex" "encoding/hex"

View file

@ -0,0 +1,43 @@
package cli
import (
"bufio"
"fmt"
"os"
"reflect"
"github.com/alecthomas/kong"
)
var _ kong.Mapper = (*PasswordMapper)(nil)
type PasswordMapper struct {
}
func (p PasswordMapper) Decode(ctx *kong.DecodeContext, target reflect.Value) error {
token := ctx.Scan.Pop()
switch v := token.Value.(type) {
case []byte:
target.SetBytes(v)
case string:
if len(v) == 0 {
return nil
}
if v == "-" {
scanner := bufio.NewScanner(os.Stdin)
if scanner.Scan() {
target.SetBytes(scanner.Bytes())
return nil
} else {
return fmt.Errorf("failed to read password from stdin: %v", scanner.Err())
}
}
target.SetBytes([]byte(v))
default:
return fmt.Errorf("expected bool but got %q (%T)", token.Value, token.Value)
}
return nil
}

View file

@ -0,0 +1,15 @@
package dbtest
import "context"
type DbCloser interface {
Close(ctx context.Context) error
}
type NoOpCloser func(context.Context) error
func (NoOpCloser) Close(context.Context) error { return nil }
type CloserFunc func(ctx context.Context) error
func (f CloserFunc) Close(ctx context.Context) error { return f(ctx) }

View file

@ -0,0 +1,19 @@
package dbtest
import (
"context"
"fmt"
"entgo.io/ent/dialect"
)
func TestDatabaseForDialect(ctx context.Context, dbDialect string) (string, DbCloser, error) {
switch dbDialect {
case dialect.Postgres:
return TestPostgresDatabase(ctx)
case dialect.SQLite:
return TestSQLiteDB(), NoOpCloser(nil), nil
default:
return "", nil, fmt.Errorf("unsupported dialect: %s", dbDialect)
}
}

View file

@ -0,0 +1,39 @@
package dbtest
import (
"context"
"github.com/testcontainers/testcontainers-go"
tcpostgres "github.com/testcontainers/testcontainers-go/modules/postgres"
"github.com/testcontainers/testcontainers-go/wait"
)
func TestPostgresDatabase(ctx context.Context) (string, DbCloser, error) {
container, err := tcpostgres.RunContainer(
ctx,
withImage("docker.io/postgres:16-alpine"),
tcpostgres.WithDatabase("gophershare"),
tcpostgres.WithUsername("postgres"),
tcpostgres.WithPassword("postgres"),
testcontainers.WithWaitStrategy(
wait.ForListeningPort("5432/tcp"),
),
)
if err != nil {
return "", nil, err
}
connString, err := container.ConnectionString(ctx, "sslmode=disable")
if err != nil {
return "", nil, err
}
return connString, CloserFunc(container.Terminate), nil
}
func withImage(image string) testcontainers.CustomizeRequestOption {
return func(req *testcontainers.GenericContainerRequest) error {
req.Image = image
return nil
}
}

View file

@ -0,0 +1,5 @@
package dbtest
func TestSQLiteDB() (connString string) {
return "sqlite://dev?mode=memory"
}

13
internal/db/init.go Normal file
View file

@ -0,0 +1,13 @@
package db
import (
"database/sql"
"github.com/jackc/pgx/v5/stdlib"
"modernc.org/sqlite"
)
func init() {
sql.Register("sqlite3", Sqlite3DriverWrapper{Driver: new(sqlite.Driver)})
sql.Register("postgres", PgxDriverWrapper{Driver: new(stdlib.Driver)})
}

View file

@ -0,0 +1,15 @@
package db
import (
"database/sql/driver"
"github.com/jackc/pgx/v5/stdlib"
)
type PgxDriverWrapper struct {
*stdlib.Driver
}
func (d PgxDriverWrapper) Open(name string) (conn driver.Conn, err error) {
return d.Driver.Open(name)
}

View file

@ -0,0 +1,27 @@
package db
import (
"database/sql/driver"
"modernc.org/sqlite"
)
type sqlite3DriverConn interface {
Exec(string, []driver.Value) (driver.Result, error)
}
type Sqlite3DriverWrapper struct {
*sqlite.Driver
}
func (d Sqlite3DriverWrapper) Open(name string) (conn driver.Conn, err error) {
conn, err = d.Driver.Open(name)
if err != nil {
return
}
_, err = conn.(sqlite3DriverConn).Exec("PRAGMA foreign_keys = ON;", nil)
if err != nil {
_ = conn.Close()
}
return
}

View file

@ -0,0 +1,77 @@
package main
import (
"context"
"errors"
"log"
"os"
"os/signal"
"path/filepath"
"time"
"code.icb4dc0.de/prskr/searcherside/internal/db/dbtest"
"code.icb4dc0.de/prskr/searcherside/internal/ent/migrate"
atlas "ariga.io/atlas/sql/migrate"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql/schema"
_ "code.icb4dc0.de/prskr/searcherside/internal/db"
)
var (
dir = filepath.Join("scripts", "migrations")
targets = []string{dialect.SQLite, dialect.Postgres}
)
func main() {
if len(os.Args) != 2 {
log.Fatalln("migration name is required. Use: 'go run -mod=mod code.icb4dc0.de/prskr/searcherside/internal/migrations <name>'")
}
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
for _, d := range targets {
if err := generateMigrations(ctx, d, os.Args[1]); err != nil {
cancel()
log.Fatalf("failed to generate migrations: %v", err)
}
}
cancel()
}
func generateMigrations(ctx context.Context, dbDialect, name string) (err error) {
migrationsDir := filepath.Join(dir, dbDialect)
if err := os.MkdirAll(migrationsDir, 0o755); err != nil {
return err
}
dir, err := atlas.NewLocalDir(migrationsDir)
if err != nil {
return err
}
opts := []schema.MigrateOption{
schema.WithDir(dir),
schema.WithMigrationMode(schema.ModeReplay),
schema.WithDialect(dbDialect),
schema.WithFormatter(atlas.DefaultFormatter),
schema.WithDropColumn(true),
schema.WithDropIndex(true),
}
connectionString, closer, err := dbtest.TestDatabaseForDialect(ctx, dbDialect)
if err != nil {
return err
}
defer func() {
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
err = errors.Join(err, closer.Close(shutdownCtx))
cancel()
}()
return migrate.NamedDiff(ctx, connectionString, name, opts...)
}

7
internal/shred/shred.go Normal file
View file

@ -0,0 +1,7 @@
package shred
func Bytes(b []byte) {
for i := range b {
b[i] = 0
}
}

View file

@ -5,6 +5,7 @@ import (
"os" "os"
"code.icb4dc0.de/prskr/searcherside/internal" "code.icb4dc0.de/prskr/searcherside/internal"
_ "code.icb4dc0.de/prskr/searcherside/internal/db"
) )
func main() { func main() {

6
scripts/migrations/fs.go Normal file
View file

@ -0,0 +1,6 @@
package migrations
import "embed"
//go:embed postgres/* sqlite3/*
var Fs embed.FS

View file

@ -0,0 +1,20 @@
-- Create "migrations" table
CREATE TABLE "migrations"
(
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"create_time" timestamptz NOT NULL,
"update_time" timestamptz NOT NULL,
"version" character varying NOT NULL UNIQUE,
"description" character varying NULL,
"type" bigint NOT NULL DEFAULT 0,
"applied" bigint NULL,
"total" bigint NULL,
"executed_at" timestamptz NOT NULL,
"execution_time" bigint NOT NULL,
"error" character varying NULL,
"error_stmt" character varying NULL,
"hash" character varying NOT NULL,
"partial_hashes" jsonb NOT NULL,
"operator_version" character varying NOT NULL,
PRIMARY KEY ("id")
);

View file

@ -0,0 +1,16 @@
-- Create "sites" table
CREATE TABLE "sites" ("id" uuid NOT NULL, "create_time" timestamptz NOT NULL, "update_time" timestamptz NOT NULL, "name" character varying NOT NULL, PRIMARY KEY ("id"));
-- Create index "sites_name_key" to table: "sites"
CREATE UNIQUE INDEX "sites_name_key" ON "sites" ("name");
-- Create "indexes" table
CREATE TABLE "indexes" ("id" uuid NOT NULL, "create_time" timestamptz NOT NULL, "update_time" timestamptz NOT NULL, "name" character varying NOT NULL, "revision" character varying NOT NULL, "site_id" uuid NOT NULL, PRIMARY KEY ("id"), CONSTRAINT "indexes_sites_indices" FOREIGN KEY ("site_id") REFERENCES "sites" ("id") ON UPDATE NO ACTION ON DELETE NO ACTION);
-- Create index "index_site_id_name_revision" to table: "indexes"
CREATE UNIQUE INDEX "index_site_id_name_revision" ON "indexes" ("site_id", "name", "revision");
-- Create "users" table
CREATE TABLE "users" ("id" uuid NOT NULL, "create_time" timestamptz NOT NULL, "update_time" timestamptz NOT NULL, "email" character varying NOT NULL, "given_name" character varying NULL, "surname" character varying NULL, "is_admin" boolean NOT NULL DEFAULT false, "password" bytea NOT NULL, PRIMARY KEY ("id"));
-- Create index "user_email" to table: "users"
CREATE INDEX "user_email" ON "users" ("email");
-- Create index "users_email_key" to table: "users"
CREATE UNIQUE INDEX "users_email_key" ON "users" ("email");
-- Create "user_staffs" table
CREATE TABLE "user_staffs" ("id" uuid NOT NULL, "create_time" timestamptz NOT NULL, "update_time" timestamptz NOT NULL, "site_id" uuid NOT NULL, "user_id" uuid NOT NULL, PRIMARY KEY ("id"), CONSTRAINT "user_staffs_sites_site_members" FOREIGN KEY ("site_id") REFERENCES "sites" ("id") ON UPDATE NO ACTION ON DELETE NO ACTION, CONSTRAINT "user_staffs_users_staffs" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON UPDATE NO ACTION ON DELETE NO ACTION);

View file

@ -0,0 +1,3 @@
h1:e8aTUvgTZiilJzovujvwuOMj6Zp6nOrtbNYfCrLhj6U=
0000000000001_migrations.sql h1:a0T3g7xDO2r2e2+Xa6RKHHCG0LOVPsHZ4RBBefLgEeM=
20240613192625_init.sql h1:R4uCBBTpnV/xKAJ6UiVsXSwHNlcJSqAa+GlsMsF1k/A=

View file

@ -0,0 +1,22 @@
-- Create "migrations" table
CREATE TABLE `migrations`
(
`id` integer NOT NULL PRIMARY KEY AUTOINCREMENT,
`create_time` datetime NOT NULL,
`update_time` datetime NOT NULL,
`version` text NOT NULL,
`description` text NULL,
`type` integer NOT NULL DEFAULT (0),
`applied` integer NULL,
`total` integer NULL,
`executed_at` datetime NOT NULL,
`execution_time` integer NOT NULL,
`error` text NULL,
`error_stmt` text NULL,
`hash` text NOT NULL,
`partial_hashes` json NOT NULL,
`operator_version` text NOT NULL
);
-- Create index "migrations_version_key" to table: "migrations"
CREATE UNIQUE INDEX `migrations_version_key` ON `migrations` (`version`);

View file

@ -0,0 +1,16 @@
-- Create "indexes" table
CREATE TABLE `indexes` (`id` uuid NOT NULL, `create_time` datetime NOT NULL, `update_time` datetime NOT NULL, `name` text NOT NULL, `revision` text NOT NULL, `site_id` uuid NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `indexes_sites_indices` FOREIGN KEY (`site_id`) REFERENCES `sites` (`id`) ON DELETE NO ACTION);
-- Create index "index_site_id_name_revision" to table: "indexes"
CREATE UNIQUE INDEX `index_site_id_name_revision` ON `indexes` (`site_id`, `name`, `revision`);
-- Create "sites" table
CREATE TABLE `sites` (`id` uuid NOT NULL, `create_time` datetime NOT NULL, `update_time` datetime NOT NULL, `name` text NOT NULL, PRIMARY KEY (`id`));
-- Create index "sites_name_key" to table: "sites"
CREATE UNIQUE INDEX `sites_name_key` ON `sites` (`name`);
-- Create "users" table
CREATE TABLE `users` (`id` uuid NOT NULL, `create_time` datetime NOT NULL, `update_time` datetime NOT NULL, `email` text NOT NULL, `given_name` text NULL, `surname` text NULL, `is_admin` bool NOT NULL DEFAULT (false), `password` blob NOT NULL, PRIMARY KEY (`id`));
-- Create index "users_email_key" to table: "users"
CREATE UNIQUE INDEX `users_email_key` ON `users` (`email`);
-- Create index "user_email" to table: "users"
CREATE INDEX `user_email` ON `users` (`email`);
-- Create "user_staffs" table
CREATE TABLE `user_staffs` (`id` uuid NOT NULL, `create_time` datetime NOT NULL, `update_time` datetime NOT NULL, `site_id` uuid NOT NULL, `user_id` uuid NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `user_staffs_sites_site_members` FOREIGN KEY (`site_id`) REFERENCES `sites` (`id`) ON DELETE NO ACTION, CONSTRAINT `user_staffs_users_staffs` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE NO ACTION);

View file

@ -0,0 +1,3 @@
h1:3BIgTNHGncd1iITEfa+rpbqA95p4H7nztnPkei2xG5s=
0000000000001_migrations.sql h1:j9JgrK+hLY1sFjpqRBSBt9h0/fLCzeEMBiqHhi6Pm4M=
20240613192622_init.sql h1:45ohikB4+ZqgsDTUkHRaShghInD1dea7TRNKjawqm5I=

1
testdata/password vendored Normal file
View file

@ -0,0 +1 @@
asdfasdfasdf

8
tools.go Normal file
View file

@ -0,0 +1,8 @@
//go:build tools
package main
import (
_ "github.com/99designs/gqlgen/api"
_ "github.com/a-h/templ"
)