- 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:
parent
5a5d3a12b5
commit
9ea9a8f658
81 changed files with 3051 additions and 74 deletions
4
.devcontainer/.env
Normal file
4
.devcontainer/.env
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
POSTGRES_USER=postgres
|
||||||
|
POSTGRES_PASSWORD=postgres
|
||||||
|
POSTGRES_DB=postgres
|
||||||
|
POSTGRES_HOSTNAME=localhost
|
13
.devcontainer/Dockerfile
Normal file
13
.devcontainer/Dockerfile
Normal 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
36
.devcontainer/compose.yml
Normal 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.)
|
23
.devcontainer/devcontainer.json
Normal file
23
.devcontainer/devcontainer.json
Normal 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
27
.editorconfig
Normal 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
5
.gitignore
vendored
|
@ -25,4 +25,7 @@ data/
|
||||||
|
|
||||||
# IDE configs
|
# IDE configs
|
||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
|
||||||
|
# Generated files
|
||||||
|
generated/
|
||||||
|
internal/ent/
|
11
.vscode/extensions.json
vendored
Normal file
11
.vscode/extensions.json
vendored
Normal 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
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"redhat.telemetry.enabled": false
|
||||||
|
}
|
432
assets/api/ent.graphql
Normal file
432
assets/api/ent.graphql
Normal 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!]
|
||||||
|
}
|
164
assets/api/searcherside_v1.yaml
Normal file
164
assets/api/searcherside_v1.yaml
Normal 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
1
assets/api/types.graphql
Normal file
|
@ -0,0 +1 @@
|
||||||
|
scalar UUID
|
9
compose.yml
Normal file
9
compose.yml
Normal 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"
|
|
@ -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
10
core/cq/user.go
Normal 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
12
core/domain/user.go
Normal 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
34
core/ports/db.go
Normal 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
6
core/ports/pw_hash.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package ports
|
||||||
|
|
||||||
|
type PasswordHashAlgorithm interface {
|
||||||
|
Hash(password []byte) ([]byte, error)
|
||||||
|
Validate(password []byte, hash []byte) error
|
||||||
|
}
|
26
core/ports/repositories.go
Normal file
26
core/ports/repositories.go
Normal 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
|
||||||
|
}
|
143
core/services/argon2id_hasher.go
Normal file
143
core/services/argon2id_hasher.go
Normal 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
|
||||||
|
}
|
||||||
|
}
|
86
core/services/argon2id_hasher_test.go
Normal file
86
core/services/argon2id_hasher_test.go
Normal 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))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
81
core/services/password_hash.go
Normal file
81
core/services/password_hash.go
Normal 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
|
||||||
|
}
|
34
core/services/password_hash_test.go
Normal file
34
core/services/password_hash_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
5
generate.go
Normal 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
95
go.mod
|
@ -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
315
go.sum
|
@ -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
36
gqlgen.yml
Normal 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
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
46
handlers/cli/migrate.go
Normal 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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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."`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
49
handlers/cli/user_create.go
Normal file
49
handlers/cli/user_create.go
Normal 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
23
handlers/cli/users.go
Normal 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
|
||||||
|
}
|
38
handlers/graphql/ent.resolvers.go
Normal file
38
handlers/graphql/ent.resolvers.go
Normal 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 }
|
20
handlers/graphql/resolver.go
Normal file
20
handlers/graphql/resolver.go
Normal 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
|
||||||
|
}
|
|
@ -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 {
|
29
infrastructure/config/db.go
Normal file
29
infrastructure/config/db.go
Normal 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
|
||||||
|
}
|
17
infrastructure/config/logging.go
Normal file
17
infrastructure/config/logging.go
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
|
@ -56,15 +56,9 @@ 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 {
|
||||||
|
|
||||||
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 val, nil
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}), nil
|
}), nil
|
||||||
|
|
44
infrastructure/db/entc.go
Normal file
44
infrastructure/db/entc.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
80
infrastructure/db/migrate/atlas_migrator.go
Normal file
80
infrastructure/db/migrate/atlas_migrator.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
71
infrastructure/db/migrate/atlas_migrator_test.go
Normal file
71
infrastructure/db/migrate/atlas_migrator_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
144
infrastructure/db/migrate/db_revision_rw.go
Normal file
144
infrastructure/db/migrate/db_revision_rw.go
Normal 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
|
||||||
|
}
|
79
infrastructure/db/migrate/fs.go
Normal file
79
infrastructure/db/migrate/fs.go
Normal 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
|
||||||
|
}
|
59
infrastructure/db/schema/index.go
Normal file
59
infrastructure/db/schema/index.go
Normal 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{},
|
||||||
|
}
|
||||||
|
}
|
62
infrastructure/db/schema/migration.go
Normal file
62
infrastructure/db/schema/migration.go
Normal 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),
|
||||||
|
}
|
||||||
|
}
|
46
infrastructure/db/schema/site.go
Normal file
46
infrastructure/db/schema/site.go
Normal 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(),
|
||||||
|
}
|
||||||
|
}
|
69
infrastructure/db/schema/user.go
Normal file
69
infrastructure/db/schema/user.go
Normal 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(),
|
||||||
|
}
|
||||||
|
}
|
52
infrastructure/db/schema/user_staff.go
Normal file
52
infrastructure/db/schema/user_staff.go
Normal 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{},
|
||||||
|
}
|
||||||
|
}
|
24
infrastructure/db/schema/uuidgql/uuidgql.go
Normal file
24
infrastructure/db/schema/uuidgql/uuidgql.go
Normal 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)
|
||||||
|
}
|
65
infrastructure/repository/EntUserRepository.go
Normal file
65
infrastructure/repository/EntUserRepository.go
Normal 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")
|
||||||
|
}
|
|
@ -12,14 +12,15 @@ 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"`
|
||||||
|
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"`
|
Token clih.TokenHandler `cmd:"" name:"token" help:"Generate a token"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package flags
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
|
@ -1,4 +1,4 @@
|
||||||
package flags
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
43
internal/cli/password_mapper.go
Normal file
43
internal/cli/password_mapper.go
Normal 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
|
||||||
|
}
|
15
internal/db/dbtest/close.go
Normal file
15
internal/db/dbtest/close.go
Normal 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) }
|
19
internal/db/dbtest/for_dialect.go
Normal file
19
internal/db/dbtest/for_dialect.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
39
internal/db/dbtest/postgres.go
Normal file
39
internal/db/dbtest/postgres.go
Normal 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
|
||||||
|
}
|
||||||
|
}
|
5
internal/db/dbtest/sqlite.go
Normal file
5
internal/db/dbtest/sqlite.go
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package dbtest
|
||||||
|
|
||||||
|
func TestSQLiteDB() (connString string) {
|
||||||
|
return "sqlite://dev?mode=memory"
|
||||||
|
}
|
13
internal/db/init.go
Normal file
13
internal/db/init.go
Normal 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)})
|
||||||
|
}
|
15
internal/db/pgx_driver_wrapper.go
Normal file
15
internal/db/pgx_driver_wrapper.go
Normal 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)
|
||||||
|
}
|
27
internal/db/sqlite_driver_wrapper.go
Normal file
27
internal/db/sqlite_driver_wrapper.go
Normal 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
|
||||||
|
}
|
77
internal/migrations/main.go
Normal file
77
internal/migrations/main.go
Normal 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
7
internal/shred/shred.go
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package shred
|
||||||
|
|
||||||
|
func Bytes(b []byte) {
|
||||||
|
for i := range b {
|
||||||
|
b[i] = 0
|
||||||
|
}
|
||||||
|
}
|
1
main.go
1
main.go
|
@ -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
6
scripts/migrations/fs.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package migrations
|
||||||
|
|
||||||
|
import "embed"
|
||||||
|
|
||||||
|
//go:embed postgres/* sqlite3/*
|
||||||
|
var Fs embed.FS
|
20
scripts/migrations/postgres/0000000000001_migrations.sql
Normal file
20
scripts/migrations/postgres/0000000000001_migrations.sql
Normal 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")
|
||||||
|
);
|
16
scripts/migrations/postgres/20240613192625_init.sql
Normal file
16
scripts/migrations/postgres/20240613192625_init.sql
Normal 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);
|
3
scripts/migrations/postgres/atlas.sum
Normal file
3
scripts/migrations/postgres/atlas.sum
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
h1:e8aTUvgTZiilJzovujvwuOMj6Zp6nOrtbNYfCrLhj6U=
|
||||||
|
0000000000001_migrations.sql h1:a0T3g7xDO2r2e2+Xa6RKHHCG0LOVPsHZ4RBBefLgEeM=
|
||||||
|
20240613192625_init.sql h1:R4uCBBTpnV/xKAJ6UiVsXSwHNlcJSqAa+GlsMsF1k/A=
|
22
scripts/migrations/sqlite3/0000000000001_migrations.sql
Normal file
22
scripts/migrations/sqlite3/0000000000001_migrations.sql
Normal 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`);
|
16
scripts/migrations/sqlite3/20240613192622_init.sql
Normal file
16
scripts/migrations/sqlite3/20240613192622_init.sql
Normal 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);
|
3
scripts/migrations/sqlite3/atlas.sum
Normal file
3
scripts/migrations/sqlite3/atlas.sum
Normal 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
1
testdata/password
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
asdfasdfasdf
|
8
tools.go
Normal file
8
tools.go
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
//go:build tools
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/99designs/gqlgen/api"
|
||||||
|
_ "github.com/a-h/templ"
|
||||||
|
)
|
Loading…
Reference in a new issue