Compare commits

...

9 Commits

Author SHA1 Message Date
Adam Veldhousen b55917d4eb
remove GA for now
ci.vdhsn.com/push Build is passing Details
2023-07-15 18:43:44 -05:00
Adam Veldhousen 1bbbd13606
test 2023-07-15 18:39:20 -05:00
Adam Veldhousen 5bc4939db2
add ga 2023-07-15 18:34:57 -05:00
Adam Veldhousen d2ba8cb90f
add configs for auth in proxies
ci.vdhsn.com/push Build was killed Details
2023-07-15 18:27:06 -05:00
Adam Veldhousen e00e9c27d3
only build on push to trunk
ci.vdhsn.com/push Build was killed Details
2023-07-15 18:25:19 -05:00
Adam Veldhousen 70d300df5e
add catawiki catalog sync, DX improvements, ci
ci.vdhsn.com/push Build was killed Details
- drone ci build using kaniko

This builds images extremely slowly, I've implemented some custom fixes to the
plugin to improve performance but it's still slow to build images. I will
try to implement a DinD solution.
There are issues in kaniko about this: https://github.com/GoogleContainerTools/kaniko/issues/875

Squashed commit of the following:

commit 1fd65ad139
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Sat Jul 15 18:17:55 2023 -0500

    add deploy script

commit 075e15f218
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Sat Jul 15 17:53:24 2023 -0500

    bump limits

commit 2d45234e7b
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Sat Jul 15 17:51:19 2023 -0500

    sync button in tilt, support syncing all targets simultaneously

commit 75e73a1171
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Sat Jul 15 17:03:50 2023 -0500

    create admin user on startup

commit 40cf74560d
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Fri Jul 14 11:14:00 2023 -0500

    test more cache

commit 1fbbfe7548
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Fri Jul 14 11:11:34 2023 -0500

    test tmpfs

commit 0f48c098d9
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Fri Jul 14 11:08:32 2023 -0500

    add tmpfs

commit d37142cfef
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Fri Jul 14 11:02:39 2023 -0500

    crank verbosity

commit 178eb5bc38
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Fri Jul 14 10:44:08 2023 -0500

    bump requests

commit b0aa0b39f0
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Fri Jul 14 10:40:04 2023 -0500

    test fewer concurrent builds

commit 221e378a53
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Thu Jul 13 20:40:18 2023 -0500

    test

commit 6c03914341
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Thu Jul 13 19:01:22 2023 -0500

    snapshot mode for faster build

commit 91c56d430e
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Wed Jul 12 22:40:12 2023 -0500

    try script

commit 4a119c2523
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Wed Jul 12 22:28:41 2023 -0500

    test promote

commit b1062a4001
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Wed Jul 12 22:26:58 2023 -0500

    test

commit c06804dfe6
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Wed Jul 12 17:47:48 2023 -0500

    beta promote and ignore

commit 643e104c37
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Wed Jul 12 16:21:39 2023 -0500

    tweakies

commit 478e65ac70
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Wed Jul 12 16:09:54 2023 -0500

    un woops

commit 966d48c193
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Wed Jul 12 15:23:08 2023 -0500

    woops

commit 2829743d17
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Wed Jul 12 15:22:03 2023 -0500

    test internal routing

commit e08e68b8ad
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Wed Jul 12 14:07:31 2023 -0500

    push images

commit 5705bca068
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Wed Jul 12 13:40:48 2023 -0500

    ugh

commit 1699b08d5f
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Wed Jul 12 13:39:49 2023 -0500

    fixup client builds

commit 9ec8616ae1
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Wed Jul 12 13:36:36 2023 -0500

    update go mods

commit 163b25fd67
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Wed Jul 12 02:11:02 2023 -0500

    fix fronend builds

commit e5cc7d5c79
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Wed Jul 12 01:56:08 2023 -0500

    resources

commit 8088b95c1e
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Wed Jul 12 01:38:21 2023 -0500

    big test

commit 38fef91c49
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Wed Jul 12 01:26:09 2023 -0500

    testicles

commit 53baa0a647
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Wed Jul 12 01:24:50 2023 -0500

    testicles

commit 16901ec5bb
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Wed Jul 12 01:23:38 2023 -0500

    testicles

commit 3478d4f512
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Wed Jul 12 01:19:58 2023 -0500

    test 2

commit 16298ada78
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Wed Jul 12 01:13:53 2023 -0500

    more test

commit 80524a1d06
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Wed Jul 12 01:09:03 2023 -0500

    test

commit b9916204de
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Wed Jul 12 00:19:41 2023 -0500

    tools image

commit 6e414b3a1b
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Wed Jul 12 00:00:23 2023 -0500

    test

commit abb6183e88
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Tue Jul 11 22:55:58 2023 -0500

    added catawiki scrape

commit 2643817e93
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Tue Jul 11 14:40:05 2023 -0500

    add catawiki syncer
2023-07-15 18:18:56 -05:00
Adam Veldhousen 389c6ed5e8
auth kustomize
ci.vdhsn.com/push Build is failing Details
2023-07-10 03:46:29 -05:00
Adam Veldhousen fc9307e359
auth deployment
ci.vdhsn.com/push Build is failing Details
2023-07-10 01:52:02 -05:00
Adam Veldhousen ad7d811c35
Squashed commit of the following:
ci.vdhsn.com/push Build is failing Details
commit e891ada9e8
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Fri Jul 7 20:10:40 2023 -0500

    added login and checking of roles to admin page

commit abb8e565cd
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Fri Jul 7 18:23:24 2023 -0500

    analytics + login ux updates

commit 4ab08d20b8
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Sat Jul 1 03:07:36 2023 -0500

    grpc reflection

commit fe329d2336
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Sat Jul 1 03:07:22 2023 -0500

    tests for auth

commit 043f387224
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Tue Jun 20 09:43:03 2023 -0500

    first pass at login/logout and sign up form

commit 5a191a2c72
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Mon Jun 19 10:30:24 2023 -0500

    implement auth

commit 649bcefbef
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Sun Jun 18 18:29:20 2023 -0500

    added login control to page

commit 4227fc048a
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Wed Jun 14 19:10:54 2023 -0500

    early pass at auth service
2023-07-07 20:11:25 -05:00
72 changed files with 3139 additions and 93 deletions

View File

@ -1,41 +1,296 @@
kind: pipeline
type: kubernetes
name: Build Images
name: Build & Push Images
trigger:
branch:
- trunk
event:
- push
steps:
- name: Build & Publish Catalog
image: plugins/kaniko
image: git.vdhsn.com/barretthousen/drone-kaniko:v1.0.1
volumes:
- name: cache
path: /kaniko
settings:
verbosity: debug
dockerfile: "./src/Dockerfile.prod-backend"
context: "./src"
target: production
registry: git.vdhsn.com
repo: git.vdhsn.com/barretthousen/service-catalog
enable_cache: true
cache_repo: git.vdhsn.com/barretthousen/ci-cache
skip_unused_stages: true
build_args:
- "service=catalog"
tags:
- ${DRONE_COMMIT_SHA}
- beta
custom_labels:
- com.barretthousen.service=catalog
- com.barretthousen.version=${DRONE_COMMIT_SHA}
- com.barretthousen.git-ref=${DRONE_COMMIT_SHA}
- com.barretthousen.build-date=${DRONE_BUILD_STARTED}
- com.barrethousen.builder=${DRONE_COMMIT_AUTHOR}
username:
from_secret: DOCKER_USERNAME
password:
from_secret: DOCKER_PASSWORD
args:
- "service=catalog"
no-push: true
tags:
- ${DRONE_COMMIT_SHA}
- name: Build & Publish Runner
image: plugins/kaniko
settings:
verbosity: debug
dockerfile: "./src/Dockerfile.prod-backend"
context: "./src"
target: production
registry: git.vdhsn.com
repo: git.vdhsn.com/barretthousen/service-runner
enable_cache: true
cache_repo: git.vdhsn.com/barretthousen/ci-cache
snapshot_mode: redo
skip_unused_stages: true
build_args:
- "service=runner"
tags:
- ${DRONE_COMMIT_SHA}
custom_labels:
- com.barretthousen.service=runner
- com.barretthousen.version=${DRONE_COMMIT_SHA}
- com.barretthousen.git-ref=${DRONE_COMMIT_SHA}
- com.barretthousen.build-date=${DRONE_BUILD_STARTED}
- com.barrethousen.builder=${DRONE_COMMIT_AUTHOR}
username:
from_secret: DOCKER_USERNAME
password:
from_secret: DOCKER_PASSWORD
args:
- "service=runner"
no-push: true
- name: Build & Publish Auth
image: plugins/kaniko
settings:
verbosity: debug
dockerfile: "./src/Dockerfile.prod-backend"
context: "./src"
target: production
registry: git.vdhsn.com
repo: git.vdhsn.com/barretthousen/service-auth
enable_cache: true
cache_repo: git.vdhsn.com/barretthousen/ci-cache
snapshot_mode: redo
skip_unused_stages: true
build_args:
- "service=auth"
tags:
- ${DRONE_COMMIT_SHA}
- beta
custom_labels:
- com.barretthousen.service=auth
- com.barretthousen.version=${DRONE_COMMIT_SHA}
- com.barretthousen.git-ref=${DRONE_COMMIT_SHA}
- com.barretthousen.build-date=${DRONE_BUILD_STARTED}
- com.barrethousen.builder=${DRONE_COMMIT_AUTHOR}
username:
from_secret: DOCKER_USERNAME
password:
from_secret: DOCKER_PASSWORD
- name: Build & Publish Proxy Admin
image: plugins/kaniko
settings:
verbosity: debug
dockerfile: "./src/Dockerfile.prod-backend"
context: "./src"
target: production
registry: git.vdhsn.com
repo: git.vdhsn.com/barretthousen/service-proxy-admin
enable_cache: true
cache_repo: git.vdhsn.com/barretthousen/ci-cache
snapshot_mode: redo
skip_unused_stages: true
build_args:
- "service=proxy-admin"
tags:
- ${DRONE_COMMIT_SHA}
- beta
custom_labels:
- com.barretthousen.service=proxy-admin
- com.barretthousen.version=${DRONE_COMMIT_SHA}
- com.barretthousen.git-ref=${DRONE_COMMIT_SHA}
- com.barretthousen.build-date=${DRONE_BUILD_STARTED}
- com.barrethousen.builder=${DRONE_COMMIT_AUTHOR}
username:
from_secret: DOCKER_USERNAME
password:
from_secret: DOCKER_PASSWORD
depends_on:
- Build & Publish Catalog
- Build & Publish Runner
- Build & Publish Auth
- name: Build & Publish Proxy Web
image: plugins/kaniko
settings:
verbosity: debug
dockerfile: "./src/Dockerfile.prod-backend"
context: "./src"
target: production
registry: git.vdhsn.com
repo: git.vdhsn.com/barretthousen/service-proxy-web
enable_cache: true
cache_repo: git.vdhsn.com/barretthousen/ci-cache
snapshot_mode: redo
skip_unused_stages: true
build_args:
- "service=proxy-web"
tags:
- ${DRONE_COMMIT_SHA}
- beta
custom_labels:
- com.barretthousen.service=proxy-web
- com.barretthousen.version=${DRONE_COMMIT_SHA}
- com.barretthousen.git-ref=${DRONE_COMMIT_SHA}
- com.barretthousen.build-date=${DRONE_BUILD_STARTED}
- com.barrethousen.builder=${DRONE_COMMIT_AUTHOR}
username:
from_secret: DOCKER_USERNAME
password:
from_secret: DOCKER_PASSWORD
depends_on:
- Build & Publish Catalog
- Build & Publish Runner
- Build & Publish Auth
- name: Build & Publish Web Client
image: plugins/kaniko
settings:
verbosity: debug
dockerfile: "./src/Dockerfile.frontend"
context: "./src/web-client"
target: production
registry: git.vdhsn.com
repo: git.vdhsn.com/barretthousen/service-proxy-web
enable_cache: true
cache_repo: git.vdhsn.com/barretthousen/ci-cache
snapshot_mode: redo
skip_unused_stages: true
build_args:
- "service=proxy-web"
tags:
- ${DRONE_COMMIT_SHA}
- beta
custom_labels:
- com.barretthousen.service=proxy-web
- com.barretthousen.version=${DRONE_COMMIT_SHA}
- com.barretthousen.git-ref=${DRONE_COMMIT_SHA}
- com.barretthousen.build-date=${DRONE_BUILD_STARTED}
- com.barrethousen.builder=${DRONE_COMMIT_AUTHOR}
username:
from_secret: DOCKER_USERNAME
password:
from_secret: DOCKER_PASSWORD
depends_on:
- Build & Publish Catalog
- Build & Publish Runner
- Build & Publish Auth
- Build & Publish Proxy Admin
- Build & Publish Proxy Web
- name: Build & Publish Admin Client
image: plugins/kaniko
settings:
verbosity: debug
dockerfile: "./src/Dockerfile.frontend"
context: "./src/admin-client"
target: production
registry: git.vdhsn.com
repo: git.vdhsn.com/barretthousen/client-admin-client
enable_cache: true
cache_repo: git.vdhsn.com/barretthousen/ci-cache
snapshot_mode: redo
skip_unused_stages: true
build_args:
- "service=admin-client"
tags:
- ${DRONE_COMMIT_SHA}
- beta
username:
from_secret: DOCKER_USERNAME
password:
from_secret: DOCKER_PASSWORD
depends_on:
- Build & Publish Catalog
- Build & Publish Runner
- Build & Publish Auth
- Build & Publish Proxy Admin
- Build & Publish Proxy Web
- name: Build Success Notify
image: appleboy/drone-discord
environment:
DISCORD_WEBHOOK_ID:
from_secret: DISCORD_WEBHOOK_ID
DISCORD_WEBHOOK_TOKEN:
from_secret: DISCORD_WEBHOOK_TOKEN
settings:
username: Drone CI
message: Barretthousen Image Builds Complete
webhook_id:
from_secret: DISCORD_WEBHOOK_ID
webhook_token:
from_secret: DISCORD_WEBHOOK_TOKEN
depends_on:
- Build & Publish Catalog
- Build & Publish Runner
- Build & Publish Auth
- Build & Publish Proxy Admin
- Build & Publish Proxy Web
- Build & Publish Web Client
- Build & Publish Admin Client
---
kind: pipeline
type: kubernetes
name: Promote Beta -> Prod
trigger:
branch:
- trunk
event:
- promote
steps:
- name: Promote Services
image: alpine
environment:
REGISTRY_NAME: "https://git.vdhsn.com"
CONTENT_TYPE: "application/vnd.docker.distribution.manifest.v2+json"
DOCKER_USERNAME:
from_secret: DOCKER_USERNAME
DOCKER_PASSWORD:
from_secret: DOCKER_PASSWORD
commands:
- ./hack/promote.sh barretthousen/catalog beta prod
- name: Deploy Success
image: appleboy/drone-discord
environment:
DISCORD_WEBHOOK_ID:
from_secret: DISCORD_WEBHOOK_ID
DISCORD_WEBHOOK_TOKEN:
from_secret: DISCORD_WEBHOOK_TOKEN
settings:
username: Drone CI
message: Barretthousen Prod deployment success
webhook_id:
from_secret: DISCORD_WEBHOOK_ID
webhook_token:
from_secret: DISCORD_WEBHOOK_TOKEN
depends_on:
- Promote Services
volumes:
- name: cache
temp:
medium: memory

View File

@ -52,6 +52,20 @@ VERSION = $(shell git rev-parse --verify --short HEAD)
GIT_REF = $(shell git rev-parse --verify HEAD)
BUILD_DATE := $(shell date +%Y-%m-%d-%T)
.PHONY: build-tools-image
build-tools-image:
@docker build \
--label 'com.barretthousen.version=$(VERSION)' \
--label 'com.barretthousen.git-ref=$(GIT_REF)' \
--label 'com.barretthousen.build-date=$(BUILD_DATE)' \
--label 'com.barrethousen.builder=$(BUILD_INITIATOR)' \
-t git.vdhsn.com/barretthousen/tools):$(VERSION) \
-t git.vdhsn.com/barretthousen/tools):v1 \
-f ./src/Dockerfile.tools ./src
@docker push git.vdhsn.com/barretthousen/tools:$(VERSION)
@docker push git.vdhsn.com/barretthousen/tools:v1
.PHONY: build-client-image
build-client-image:
@docker build --target=production \
@ -66,7 +80,7 @@ build-client-image:
-f ./src/Dockerfile.frontend ./src/$(SERVICE)
@docker push git.vdhsn.com/barretthousen/client-$(SERVICE):$(VERSION)
@[ ! -z $(ENV) ] && docker tag git.vdhsn.com/barretthousen/client-$(SERVICE):$(VERSION) git.vdhsn.com/barretthousen/client-$(SERVICE):beta || true
@[ ! -z $(ENV) ] && docker tag git.vdhsn.com/barretthousen/client-$(SERVICE):$(VERSION) git.vdhsn.com/barretthousen/client-$(SERVICE):$(ENV) || true
@[ ! -z $(ENV) ] && docker push git.vdhsn.com/barretthousen/client-$(SERVICE):$(ENV) || true
.PHONY: build-backend-image
@ -84,8 +98,8 @@ build-backend-image:
@docker push git.vdhsn.com/barretthousen/service-$(SERVICE):$(VERSION)
@[ ! -z $(ENV) ] && docker tag git.vdhsn.com/barretthousen/service-$(SERVICE):$(VERSION) git.vdhsn.com/barretthousen/service-$(SERVICE):beta || true
@[ ! -z $(ENV) ] && docker push git.vdhsn.com/barretthousen/service-$(SERVICE):beta || true
@[ ! -z $(ENV) ] && docker tag git.vdhsn.com/barretthousen/service-$(SERVICE):$(VERSION) git.vdhsn.com/barretthousen/service-$(SERVICE):$(ENV) || true
@[ ! -z $(ENV) ] && docker push git.vdhsn.com/barretthousen/service-$(SERVICE):$(ENV) || true
# TODO: investigate tagging without needing to pull? https://dille.name/blog/2018/09/20/how-to-tag-docker-images-without-pulling-them/
.PHONY: promote-client-prod
@ -124,3 +138,4 @@ age_identity=$(shell sops -d ./env/master.json)
@echo "# public key: $(shell echo '$(age_identity)' | jq -r '.public_key')" >> $@
@echo "$(shell echo '$(age_identity)' | jq -r '.private_key')" >> $@
@echo "$@ created!"
@echo "export SOPS_AGE_KEY_FILE=$(PWD)/env/.age.txt"

View File

@ -1,5 +1,6 @@
# [Barretthousen](https://barretthousen.com)
[![Build Status](https://ci.vdhsn.com/api/badges/Barretthousen/barretthousen/status.svg?ref=refs/heads/trunk)](https://ci.vdhsn.com/Barretthousen/barretthousen)
Search and get alerts for items across the most popular auction sites.
Built with microservice architecture and various fancy tools for learning purposes

View File

@ -7,6 +7,7 @@ print("""
load('ext://helm_resource', 'helm_resource', 'helm_repo')
load('ext://deployment', 'deployment_create')
load('ext://restart_process', 'docker_build_with_restart')
load('ext://uibutton', 'cmd_button', 'location', 'text_input')
helm_repo('bitnami', 'https://charts.bitnami.com/bitnami', labels=["9-repos"])
helm_repo('traefik', 'https://traefik.github.io/charts', labels=["9-repos"])
@ -144,14 +145,18 @@ k8s_resource(
)
bh_backend_service(service="runner", migrateDB=True, port_forwards=[
bh_backend_service(service="auth", migrateDB=True, port_forwards=[
port_forward(2345, name='Delve port')
])
bh_backend_service(service="catalog", migrateDB=True, port_forwards=[
bh_backend_service(service="runner", migrateDB=True, port_forwards=[
port_forward(2346, 2345, name='Delve port')
])
bh_backend_service(service="catalog", migrateDB=True, port_forwards=[
port_forward(2347, 2345, name='Delve port')
])
bh_backend_service(service="proxy-admin", port_forwards=[
port_forward(8082, 80, name="HTTP API @ localhost:8082")
], deps=['ingress'])
@ -160,5 +165,36 @@ bh_backend_service(service="proxy-web", port_forwards=[
port_forward(8081, 80, name="HTTP API @ localhost:8081")
], deps=['ingress'])
bh_client(service='web')
bh_client(service='admin')
bh_client(service='web', deps=["proxy-web-local"])
bh_client(service='admin', deps=["proxy-admin-local"])
createAdminCmd = [
'./hack/create_user.sh', 'admin@barretthousen.com', 'admin', 'ADMINISTRATOR'
]
loginAdminCmd = [
'curl', '-vvvv', '-X POST', '-H "Content-Type: application/json"',
'-d "{\"email\":\"admin@barretthousen.com\", \"password\":\"admin\"}"',
'http://bh.localhost:8000/api/v1/user'
]
syncCatalogsCmd = [
'curl', '-vvvv', '-X', 'PUT',
'-H', 'Content-Type: application/json',
'-H', 'bh-session-id: 2',
'-d', '{\"targetSite\":\"All\"}',
'http://admin.localhost:8000/api/v1/sync'
]
local(createAdminCmd, quiet=False, echo_off=True)
local(loginAdminCmd, quiet=False, echo_off=True)
local(syncCatalogsCmd, quiet=False, echo_off=True)
cmd_button(
name='Sync Catalogs',
argv=syncCatalogsCmd,
text='Sync Catalogs',
location=location.NAV,
icon_name='sync')

View File

@ -61,6 +61,7 @@ helm install --upgrade bh-db bitnami/postgresql -n 'barretthousen-beta' \
export ENV=beta; # if not specified, will only build and push for the git commit
make build-backend-image SERVICE=catalog
make build-backend-image SERVICE=runner
make build-backend-image SERVICE=auth
make build-backend-image SERVICE=proxy-admin
make build-backend-image SERVICE=proxy-web
@ -78,6 +79,9 @@ kubectl rollout status -n barretthousen-beta deployment runner-beta -w
kubectl rollout restart -n barretthousen-beta deployment catalog-beta
kubectl rollout status -n barretthousen-beta deployment catalog-beta -w
kubectl rollout restart -n barretthousen-beta deployment auth-beta
kubectl rollout status -n barretthousen-beta deployment auth-beta -w
kubectl rollout restart -n barretthousen-beta deployment proxy-admin-beta
kubectl rollout status -n barretthousen-beta deployment proxy-admin-beta -w
@ -95,6 +99,7 @@ kubectl rollout status -n barretthousen-beta deployment admin-client-beta -w
```sh
make promote-backend-prod SERVICE=catalog
make promote-backend-prod SERVICE=runner
make promote-backend-prod SERVICE=auth
make promote-backend-prod SERVICE=proxy-admin
make promote-backend-prod SERVICE=proxy-web

72
env/base/auth-deployment.yaml vendored Normal file
View File

@ -0,0 +1,72 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: auth
spec:
replicas: 1
selector:
matchLabels:
service: auth
template:
metadata:
labels:
service: auth
spec:
serviceAccountName: barretthousen-service
containers:
- name: auth
image: barretthousen/service-auth:latest
imagePullPolicy: Always
ports:
- containerPort: 5001
name: grpc
command:
- /opt/auth
args:
- -migrate
resources:
limits:
cpu: "250m"
memory: "128Mi"
volumeMounts:
- mountPath: /config/
name: auth-config
volumes:
- name: auth-config
secret:
secretName: auth-config
---
apiVersion: v1
kind: Service
metadata:
name: auth
spec:
selector:
service: auth
ports:
- port: 5001
targetPort: 5001
name: grpc
---
apiVersion: v1
kind: Secret
metadata:
name: auth-config
stringData:
config.yaml: |
log_level: 2
port: 5001
db_service:
scheme: postgres
port: 5432
host: bh-db
name: bh
user: auth-service
password: auth-service
db_migrate:
scheme: postgres
port: 5432
host: bh-db
name: bh
user: postgres
password: bh-admin

View File

@ -46,6 +46,7 @@ spec:
ports:
- port: 5001
targetPort: 5001
name: grpc
---
apiVersion: v1
kind: Secret

View File

@ -1,6 +1,7 @@
resources:
- ./image-pull-secret.yaml
- ./namespace.yaml
- ./auth-deployment.yaml
- ./catalog-deployment.yaml
- ./runner-deployment.yaml
- ./proxy-admin-deployment.yaml

View File

@ -54,3 +54,4 @@ stringData:
port: 80
endpoints:
runner: runner:5001
auth: auth:5001

View File

@ -54,3 +54,4 @@ stringData:
port: 80
endpoints:
catalog: catalog:5001
auth: auth:5001

View File

@ -46,7 +46,7 @@ spec:
ports:
- port: 5001
targetPort: 5001
name: grpc
---
apiVersion: v1
kind: Secret

22
env/beta/auth-secret.yaml vendored Normal file
View File

@ -0,0 +1,22 @@
apiVersion: v1
kind: Secret
metadata:
name: auth-config
stringData:
config.yaml: |
log_level: 2
port: 5001
db_service:
scheme: postgres
port: 5432
host: bh-db
name: bh
user: auth-service
password: auth-service
db_migrate:
scheme: postgres
port: 5432
host: bh-db
name: bh
user: postgres
password: bh-admin-beta

View File

@ -7,10 +7,11 @@ namespace: barretthousen-beta
patchesStrategicMerge:
- sync-cronjob.yaml
- auth-secret.yaml
- catalog-secret.yaml
- runner-secret.yaml
- proxy-admin-secret.yaml
- proxy-web-secret.yaml
- runner-secret.yaml
patches:
- target:
@ -27,6 +28,13 @@ patches:
- op: replace
path: /spec/rules/0/host
value: beta.barretthousen.com
- target:
kind: Deployment
name: auth
patch: |-
- op: replace
path: /spec/template/spec/containers/0/image
value: git.vdhsn.com/barretthousen/service-auth:beta
- target:
kind: Deployment
name: catalog

View File

@ -9,3 +9,4 @@ stringData:
access_control_allow_origin: "beta.admin.barretthousen.com"
endpoints:
runner: runner-beta:5001
auth: auth-beta:5001

View File

@ -8,4 +8,5 @@ stringData:
port: 80
access_control_allow_origin: "beta.barretthousen.com"
endpoints:
auth: auth-beta:5001
catalog: catalog-beta:5001

11
env/local/debug-auth.yaml vendored Normal file
View File

@ -0,0 +1,11 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: auth
spec:
template:
spec:
containers:
- name: auth
ports:
- containerPort: 2345

View File

@ -8,6 +8,7 @@ nameSuffix: -local
namespace: barretthousen-local
patchesStrategicMerge:
- debug-auth.yaml
- debug-catalog.yaml
- debug-runner.yaml
- runner-secret.yaml

View File

@ -8,3 +8,4 @@ stringData:
port: 80
endpoints:
runner: runner-local:5001
auth: auth-local:5001

View File

@ -8,3 +8,4 @@ stringData:
port: 80
endpoints:
catalog: catalog-local:5001
auth: auth-local:5001

View File

@ -8,6 +8,13 @@ patchesStrategicMerge:
- sync-cronjob.yaml
patches:
- target:
kind: Deployment
name: auth
patch: |-
- op: replace
path: /spec/template/spec/containers/0/image
value: git.vdhsn.com/barretthousen/service-auth:prod
- target:
kind: Deployment
name: catalog

View File

@ -14,6 +14,6 @@ spec:
[
"curl",
"http://proxy-admin/api/v1/sync",
'-d=''{"target":"liveauctioneers"}''',
'-d=''{"target":"All"}''',
"-vvvv",
]

View File

@ -1,6 +1,7 @@
go 1.19
use (
./src/auth
./src/catalog
./src/lib
./src/proxy-admin

64
hack/build_all.sh Executable file
View File

@ -0,0 +1,64 @@
#!/bin/bash
build(){
export ENV=${1:-beta}; # if not specified, will only build and push for the git commit
make build-backend-image SERVICE=auth &
make build-backend-image SERVICE=catalog &
make build-backend-image SERVICE=runner &
make build-backend-image SERVICE=proxy-admin &
make build-backend-image SERVICE=proxy-web &
time wait;
# for prod drop `beta prefix
make build-client-image SERVICE=web-client &
make build-client-image SERVICE=admin-client &
time wait;
kubectl rollout restart -n barretthousen-beta deployment runner-beta &
kubectl rollout restart -n barretthousen-beta deployment catalog-beta &
kubectl rollout restart -n barretthousen-beta deployment auth-beta &
kubectl rollout restart -n barretthousen-beta deployment proxy-admin-beta &
kubectl rollout restart -n barretthousen-beta deployment proxy-web-beta &
kubectl rollout restart -n barretthousen-beta deployment web-client-beta &
kubectl rollout restart -n barretthousen-beta deployment admin-client-beta &
kubectl rollout status -n barretthousen-beta deployment runner-beta -w
kubectl rollout status -n barretthousen-beta deployment catalog-beta -w
kubectl rollout status -n barretthousen-beta deployment auth-beta -w
kubectl rollout status -n barretthousen-beta deployment proxy-admin-beta -w
kubectl rollout status -n barretthousen-beta deployment proxy-web-beta -w
kubectl rollout status -n barretthousen-beta deployment admin-client-beta -w
kubectl rollout status -n barretthousen-beta deployment web-client-beta -w
}
promote() {
make promote-backend-prod SERVICE=catalog &
make promote-backend-prod SERVICE=runner &
make promote-backend-prod SERVICE=auth &
make promote-backend-prod SERVICE=proxy-admin &
make promote-backend-prod SERVICE=proxy-web &
make promote-client-prod SERVICE=web-client &
make promote-client-prod SERVICE=admin-client &
time wait;
kubectl rollout restart -n barretthousen deployment runner &
kubectl rollout restart -n barretthousen deployment catalog &
kubectl rollout restart -n barretthousen deployment auth &
kubectl rollout restart -n barretthousen deployment proxy-admin &
kubectl rollout restart -n barretthousen deployment proxy-web &
kubectl rollout restart -n barretthousen deployment web-client &
kubectl rollout restart -n barretthousen deployment admin-client &
kubectl rollout status -n barretthousen deployment runner -w
kubectl rollout status -n barretthousen deployment catalog -w
kubectl rollout status -n barretthousen deployment auth -w
kubectl rollout status -n barretthousen deployment proxy-admin -w
kubectl rollout status -n barretthousen deployment proxy-web -w
kubectl rollout status -n barretthousen deployment admin-client -w
kubectl rollout status -n barretthousen deployment web-client -w
}
build $@

26
hack/create_user.sh Executable file
View File

@ -0,0 +1,26 @@
#!/bin/bash
main(){
local EMAIL=${1};
if [ -z "${EMAIL}" ]; then
echo "first argument must be an EMAIL address"
exit 1;
fi
local PASSWORD=${2};
if [ -z "${PASSWORD}" ]; then
echo "second argument must be a PASSWORD"
exit 1;
fi
local ROLE=${3:-USER};
local HOST=${4:-"http://bh.localhost:8000"};
curl -X PUT -H 'Content-Type: application/json' \
-d "{ \"email\":\"${EMAIL}\", \"password\":\"${PASSWORD}\", \"role\":\"${ROLE}\" }" \
"${HOST}/api/v1/user";
}
main $@;

19
hack/kaniko-test.sh Executable file
View File

@ -0,0 +1,19 @@
#!/bin/bash
docker run --rm \
-e DRONE_COMMIT_REF=refs/heads/trunk \
-e DRONE_REPO_BRANCH=trunk \
-e PLUGIN_REGISTRY=git.vdhsn.com \
-e PLUGIN_REPO=git.vdhsn.com/barretthousen/service-catalog \
-e PLUGIN_BUILD_ARGS="service=catalog" \
-e PLUGIN_TAGS=$(git rev-parse --verify --short HEAD) \
-e PLUGIN_CONTEXT="./src" \
-e PLUGIN_DOCKERFILE="./src/Dockerfile.prod-backend" \
-e PLUGIN_USERNAME=${DOCKER_USERNAME} \
-e PLUGIN_PASSWORD=${DOCKER_PASSWORD} \
-e PLUGIN_ENABLE_CACHE=true \
-e PLUGIN_CACHE_REPO='git.vdhsn.com/barretthousen/ci-cache' \
-e PLUGIN_SKIP_UNUSED_STAGES=true \
-v $(pwd):/drone \
-w /drone \
plugins/kaniko:linux-amd64

26
hack/promote.sh Executable file
View File

@ -0,0 +1,26 @@
#!/bin/sh
main(){
local REPOSITORY=${1};
if [ -z "${REPOSITORY}" ]; then
echo "First argument must be container repository";
exit 1;
fi
local FROM=${2:-beta};
local TO=${3:-prod};
local CONTENT_TYPE="application/vnd.docker.distribution.manifest.v2+json";
local REGISTRY_URL="https://git.vdhsn.com/v2/${REPOSITORY}/manifests";
export MANIFEST=$(curl -u "${DOCKER_USERNAME}:${DOCKER_PASSWORD}" \
-H "Accept: ${CONTENT_TYPE}" "${REGISTRY_URL}/${FROM}");
curl -u "${DOCKER_USERNAME}:${DOCKER_PASSWORD}" \
-X PUT -H "Content-Type: ${CONTENT_TYPE}" -d "${MANIFEST}" "${REGISTRY_URL}/${TO}";
echo "";
}

View File

@ -1 +1,4 @@
.idea
web-client/*
admin-client/*

View File

@ -1,14 +1,11 @@
FROM golang:1.19 as prod-builder
RUN go install github.com/bufbuild/buf/cmd/buf@v1.17.0
RUN go install github.com/kyleconroy/sqlc/cmd/sqlc@latest
COPY . /go/src
FROM git.vdhsn.com/barretthousen/tools:v1 as prod-builder
WORKDIR /go/src/
RUN /go/bin/sqlc generate -f /go/src/sqlc.yaml
RUN /go/bin/buf mod update /go/src && /go/bin/buf generate
COPY . /go/src/
RUN /go/bin/sqlc generate -f /go/src/sqlc.yaml \
&& /go/bin/buf mod update /go/src && /go/bin/buf generate
ARG service

4
src/Dockerfile.tools Normal file
View File

@ -0,0 +1,4 @@
FROM golang:1.19
RUN go install github.com/bufbuild/buf/cmd/buf@v1.17.0
RUN go install github.com/kyleconroy/sqlc/cmd/sqlc@latest

View File

@ -0,0 +1,95 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
interface FormValidations {
email?: boolean;
general?: boolean;
}
export const validations: FormValidations = {};
let email: string = '';
let password: string = '';
let passwordConfirmation: string = '';
const dispatch = createEventDispatcher();
const execLogin = () =>
dispatch('login', {
email,
password
});
const execRegister = () => {
if (password !== passwordConfirmation) {
console.log("password and confirm don't match");
return;
}
dispatch('register', {
email,
password
});
};
let showPassword = false;
let showRegistration = false;
const revealPass = () => (showPassword = !showPassword);
const toggleFormType = () => (showRegistration = !showRegistration);
$: ctaTxt = showRegistration ? 'Up' : 'In';
$: toggleCtaTxt = !showRegistration ? 'Up' : 'In';
$: ctaFunc = showRegistration ? execRegister : execLogin;
</script>
<form class="flex" style="max-width: 600px;" on:submit|preventDefault={ctaFunc}>
<span class="flex flex-col justify-around w-20">
<label for="email"> Email </label>
<label for="password"> Password&nbsp;</label>
</span>
<span class="flex flex-col">
<span class="flex">
<input
class="px-2 py-1 border-r-0 grow invalid:border-red-500 invalid:border-2"
type="email"
required
bind:value={email}
/>
<button class="border-l-0 px-2 py-1 disabled:text-gray-500">
Sign {ctaTxt}
</button>
</span>
<span class="flex">
<input
class="px-2 py-1 w-full"
name="password"
type={showPassword ? 'text' : 'password'}
required
placeholder={showRegistration ? 'Password' : ''}
on:blur={() => (showPassword = false)}
on:change={(evt) => (password = evt?.target?.value)}
/>
{#if showRegistration}
<input
class="px-2 py-1 border-r-0 invalid:border-red-500 invalid:border-2"
name="confirm_password"
type={showPassword ? 'text' : 'password'}
pattern={password}
required
placeholder="Confirm Password"
on:blur={() => (showPassword = false)}
on:change={(evt) => (passwordConfirmation = evt?.target?.value)}
/>
<button
class="border-l-0 px-2 py-1 w-16"
tabindex="-1"
on:click|stopPropagation={revealPass}
>
{showPassword ? 'Hide' : 'Show'}
</button>
{/if}
</span>
</span>
</form>

View File

@ -0,0 +1,119 @@
import { browser } from '$app/environment';
import { env } from '$env/dynamic/public';
import { writable } from 'svelte/store';
const API_HOST = `${browser ? '' : env.BH_CLIENT_INTERNAL_API_HOST}/api/v1`;
interface SessionInfo {
sessionId?: string;
account?: {
id: string;
email: string;
role: 'BIDDER' | 'USER' | 'ADMINISTRATOR' | 'ANONYMOUS';
createdTs: string;
};
}
export const getSession = () => {
if (browser) {
const strSession = localStorage.getItem('bh-session');
if (strSession) {
const data = JSON.parse(strSession);
if (data?.sessionId) {
return data;
}
}
}
return null;
}
export const session = writable<SessionInfo>(getSession(), (set) => {
const session = getSession();
if (session) {
set(session);
}
});
interface LoginAction {
email: string;
password: string;
}
export const loginAction = async ({ email, password }: LoginAction): Promise<SessionInfo> => {
try {
const sessionStr = localStorage.getItem('bh-session');
if (sessionStr) {
const data = JSON.parse(sessionStr);
if (data.sessionId) {
console.log('already authenticated');
session.set(data);
return data;
}
localStorage.removeItem('bh-session');
}
const response = await fetch(
new Request(`${API_HOST}/user`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
password
})
})
);
const data = await response.json();
console.log(data);
if (data.sessionId) {
localStorage.setItem('bh-session', JSON.stringify(data));
return data;
}
console.trace("got this on login attempt:", data);
} catch (e) {
console.error(e);
}
return {};
};
interface RegisterAction {
email: string;
password: string;
role?: string;
}
export const registerAction = async ({ email, password, role }: RegisterAction) => {
try {
const response = await fetch(
new Request(`${API_HOST}/user`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
password,
role: role || 'USER'
})
})
);
const data = await response.json();
console.log(data);
} catch (e) {
console.error(e);
}
};
export const logoutAction = () => {
localStorage.removeItem('bh-session');
session.set({});
};

View File

@ -1,5 +1,34 @@
<script lang="ts">
import '../app.css';
import AuthForm from '$lib/AuthForm.svelte';
import { loginAction, logoutAction, registerAction, session } from '$lib/state';
async function onLogin(evt: CustomEvent) {
const { email, password } = evt.detail;
await loginAction({ email, password });
}
async function onLogout() {
logoutAction();
}
interface SessionInfo {
sessionId?: string;
account?: {
id: string;
email: string;
role: 'BIDDER' | 'USER' | 'ADMINISTRATOR' | 'ANONYMOUS';
createdTs: string;
};
}
let sessionVal: SessionInfo;
session.subscribe((v) => (sessionVal = v));
$: isLoggedIn = !!sessionVal?.sessionId;
</script>
<svelte:head>
@ -26,8 +55,19 @@
<li class="flex grow justify-center">
<span class="flex grow" style="max-width: 75%;"> Admin Stuff </span>
</li>
<li class="px-6">
<li class="pr-5">
<!-- <span>I want email alerts!</span> -->
{#if !isLoggedIn}
<AuthForm on:login={onLogin} />
{:else}
<span>
{sessionVal.account?.email}
</span>
<button
class="bg-bh-gold border-bh-black px-2 py-1 text-bh-black"
on:click|stopPropagation={onLogout}>Log out</button
>
{/if}
</li>
</ul>
</nav>

View File

@ -7,6 +7,7 @@
import { fade } from 'svelte/transition';
import { onDestroy, onMount } from 'svelte';
import { invalidateAll } from '$app/navigation';
import { getSession } from '$lib/state';
export let data: PageData;
@ -27,7 +28,8 @@
new Request('/api/v1/sync', {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
'Content-Type': 'application/json',
'bh-session-id': getSession()?.sessionId
},
body: JSON.stringify({ targetSite: detail.target })
})

View File

@ -1,6 +1,7 @@
import { browser } from '$app/environment';
import type { PageLoad } from './$types';
import { env } from '$env/dynamic/public';
import { getSession } from '$lib/state';
const API_HOST = `${browser ? '' : env.BH_CLIENT_INTERNAL_API_HOST}/api/v1`;
@ -26,8 +27,21 @@ interface ScrapeStatusPageData {
export const load = (async ({ fetch, url }): Promise<ScrapeStatusPageData> => {
const searchParams = new SearchParameters(url);
const limit = searchParams.getLimit();
console.log(getSession());
try {
const response = await fetch(API_HOST + `/sync${searchParams.toQueryString()}`);
const response = await fetch(
new Request(API_HOST + `/sync${searchParams.toQueryString()}`,
{
method: 'GET',
headers: {
'bh-session-id': getSession()?.sessionId,
}
}
)
);
const { active, complete, activeTotal, completeTotal, total, page } = await response.json();
return {

View File

@ -0,0 +1,25 @@
tests:
- description: "Register User"
request:
path: "/v1/user"
method: "PUT"
response:
statusCodes: [200]
- description: "Login"
request:
path: "/v1/user"
method: "POST"
response:
statusCodes: [200]
- description: "Account Details"
request:
path: "/v1/user"
method: "GET"
response:
statusCodes: [200]
- description: "Verify Account"
request:
path: "/v1/user/verify"
method: "GET"
response:
statusCodes: [200]

64
src/auth/api/client.go Normal file
View File

@ -0,0 +1,64 @@
package api
import (
"context"
"strconv"
"time"
aapi "git.vdhsn.com/barretthousen/barretthousen/src/auth/api/grpc"
"git.vdhsn.com/barretthousen/barretthousen/src/auth/internal/domain"
"google.golang.org/grpc"
)
func NewAuthServiceClient(conn grpc.ClientConnInterface) *AuthServiceClient {
return &AuthServiceClient{
AuthClient: aapi.NewAuthClient(conn),
}
}
type AuthServiceClient struct {
aapi.AuthClient
}
type CheckSessionParams struct {
SessionID string
}
func (asc *AuthServiceClient) CheckSession(ctx context.Context, in CheckSessionParams) (out Account, err error) {
if in.SessionID == "" {
err = domain.ErrInvalidSession
return
}
var sessionIDInt int
if sessionIDInt, err = strconv.Atoi(in.SessionID); err != nil {
return
}
var accountResult *aapi.Account
if accountResult, err = asc.GetSession(ctx, &aapi.SessionFilter{
SessionId: int32(sessionIDInt),
}); err != nil {
return
}
out = Account{
ID: int(accountResult.Id),
Created: accountResult.CreatedTs.AsTime(),
Email: accountResult.Email,
Verified: accountResult.VerifiedTs.AsTime(),
Role: accountResult.Role,
}
return
}
type Account struct {
ID int `json:"id"`
Created time.Time `json:"created"`
Verified time.Time `json:"verified,omitempty"`
Email string `json:"email"`
PasswordHash string `json:"password_hash,omitempty"`
Role string `json:"role"`
Enabled bool `json:"enabled"`
}

View File

@ -0,0 +1,78 @@
syntax = "proto3";
package main;
import "google/protobuf/timestamp.proto";
import "google/api/annotations.proto";
option go_package = "git.vdhsn.com/barretthousen/barretthousen/src/auth/api/grpc";
service Auth {
rpc CreateAccount(CreateAccountInput) returns (Account) {
option (google.api.http) = {
put: "/v1/user"
body: "*"
};
}
rpc Login(LoginInput) returns (LoginResult) {
option (google.api.http) = {
post: "/v1/user"
body: "*"
};
}
rpc VerifyAccount(VerificationParameters) returns (AccountVerifiedResult) {
option (google.api.http) = {
get: "/v1/user/verify"
};
}
rpc GetAccount(AccountFilter) returns (Account) {
option (google.api.http) = {
get: "/v1/user"
};
}
rpc GetSession(SessionFilter) returns (Account) { }
}
message SessionFilter {
int32 sessionId = 1;
}
message CreateAccountInput {
string email = 1;
string password = 2;
string role = 3;
}
message LoginInput {
string email = 1;
string password = 2;
}
message LoginResult {
string sessionId = 1;
Account account = 2;
}
message AccountFilter {
int32 id = 1;
string email = 2;
}
message VerificationParameters {
string token = 1;
string email = 2;
}
message AccountVerifiedResult {}
message Account {
int32 id = 1;
string email = 2;
string role = 3;
google.protobuf.Timestamp createdTs = 4;
google.protobuf.Timestamp verifiedTs = 5;
}

42
src/auth/go.mod Normal file
View File

@ -0,0 +1,42 @@
module git.vdhsn.com/barretthousen/barretthousen/src/auth
go 1.19
require (
git.vdhsn.com/barretthousen/barretthousen/src/lib v1.0.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2
go.uber.org/dig v1.16.1
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1
google.golang.org/protobuf v1.30.0
)
require (
github.com/BurntSushi/toml v1.2.1 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/ilyakaznacheev/cleanenv v1.4.2 // indirect
github.com/jackc/puddle v1.3.0 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/pressly/goose/v3 v3.11.0 // indirect
golang.org/x/net v0.9.0 // indirect
golang.org/x/sync v0.2.0 // indirect
golang.org/x/sys v0.8.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect
)
require (
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.14.0
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.3.2 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgtype v1.14.0 // indirect
github.com/jackc/pgx/v4 v4.18.1
go.uber.org/automaxprocs v1.5.2 // indirect
golang.org/x/crypto v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
google.golang.org/grpc v1.55.0
)
replace git.vdhsn.com/barretthousen/barretthousen/src/lib v1.0.0 => ../lib

262
src/auth/go.sum Normal file
View File

@ -0,0 +1,262 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 h1:gDLXvp5S9izjldquuoAhDzccbskOL6tDC5jMSyx3zxE=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2/go.mod h1:7pdNwVWBBHGiCxa9lAszqCJMbfTISJ7oMftp8+UGV08=
github.com/ilyakaznacheev/cleanenv v1.4.2 h1:nRqiriLMAC7tz7GzjzUTBHfzdzw6SQ7XvTagkFqe/zU=
github.com/ilyakaznacheev/cleanenv v1.4.2/go.mod h1:i0owW+HDxeGKE0/JPREJOdSCPIyOnmh6C0xhWAkF/xA=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgconn v1.14.0 h1:vrbA9Ud87g6JdFWkHTJXppVce58qPIdP7N8y0Ml/A7Q=
github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E=
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
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/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0=
github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
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/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw=
github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0=
github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0=
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
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/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
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/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
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/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/pressly/goose/v3 v3.11.0 h1:krazmHhfT6SxJGqtTjddwTsL2Xwje2piTQYRH8KLECI=
github.com/pressly/goose/v3 v3.11.0/go.mod h1:ofR04pV2CYY1q/y7CNjoFQuzW4lGSVKwMI/m9lnAjVo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
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/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
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.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/automaxprocs v1.5.2 h1:2LxUOGiR3O6tw8ui5sZa2LAaHnsviZdVOUZw4fvbnME=
go.uber.org/automaxprocs v1.5.2/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
go.uber.org/dig v1.16.1 h1:+alNIBsl0qfY0j6epRubp/9obgtrObRAc5aD+6jbWY8=
go.uber.org/dig v1.16.1/go.mod h1:557JTAUZT5bUK0SvCwikmLPPtdQhfvLYtO5tJgQSbnk=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
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.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/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-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
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.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-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-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/sqlite v1.21.1 h1:GyDFqNnESLOhwwDRaHGdp2jKLDzpyT/rNLglX3ZkMSU=
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 h1:slmdOY3vp8a7KQbHkL+FLbvbkgMqmXojpFUO/jENuqQ=
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3/go.mod h1:oVgVk4OWVDi43qWBEyGhXgYxt7+ED4iYNpTngSLX2Iw=

View File

@ -0,0 +1,111 @@
-- +goose Up
START TRANSACTION;
CREATE SCHEMA IF NOT EXISTS auth;
CREATE TABLE IF NOT EXISTS auth.account (
id SERIAL PRIMARY KEY,
createdTs TIMESTAMP NOT NULL DEFAULT NOW(),
verifiedTs TIMESTAMP NULL,
email VARCHAR(512) NOT NULL,
passwordHash VARCHAR(512) NOT NULL,
role VARCHAR(64) NOT NULL DEFAULT 'BIDDER',
enabled BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS auth.account_verification (
id SERIAL PRIMARY KEY,
accountId INT NOT NULL,
createdTs TIMESTAMP NOT NULL DEFAULT NOW(),
verificationToken VARCHAR(128) NOT NULL UNIQUE,
CONSTRAINT fk_account_id FOREIGN KEY (accountId) REFERENCES auth.account(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS auth.sessions (
id SERIAL PRIMARY KEY,
accountId INT NOT NULL,
createdTs TIMESTAMP NOT NULL DEFAULT NOW(),
CONSTRAINT fk_account_id FOREIGN KEY (accountId) REFERENCES auth.account(id) ON DELETE CASCADE
);
-- +goose StatementBegin
CREATE OR REPLACE FUNCTION auth.bh_register_account(
p_email VARCHAR(512),
p_passwordHash VARCHAR(512),
p_role VARCHAR(64))
RETURNS INTEGER
LANGUAGE plpgsql AS $BODY$
DECLARE
account_id INTEGER;
BEGIN
SELECT acc.id INTO account_id FROM auth.account acc WHERE acc.email = p_email;
IF account_id IS NULL OR account_id = 0 THEN
INSERT INTO auth.account (
email,
passwordHash,
role
) VALUES (
p_email,
p_passwordHash,
p_role
) RETURNING id INTO account_id;
ELSE
-- 0 means there is a duplicate account
account_id = 0;
END IF;
RETURN account_id;
END;
$BODY$;
-- +goose StatementEnd
-- +goose StatementBegin
CREATE OR REPLACE FUNCTION auth.bh_verify_account(
p_verification_token VARCHAR(128))
RETURNS INTEGER
LANGUAGE plpgsql AS $BODY$
DECLARE
account_id INTEGER;
verification_id INTEGER;
BEGIN
SELECT
id, accountid INTO verification_id, account_id
FROM auth.account_verification
WHERE verificationtoken = p_verification_token;
IF account_id IS NULL OR account_id = 0 THEN
-- 0 means there is a duplicate account
account_id = 0;
ELSE
UPDATE auth.account SET verifiedTs=NOW() WHERE id = account_id;
DELETE FROM auth.account_verification WHERE id = verification_id;
END IF;
RETURN account_id;
END;
$BODY$;
-- +goose StatementEnd
-- +goose StatementBegin
DO
$do$
BEGIN
IF NOT EXISTS (
SELECT FROM pg_catalog.pg_roles
WHERE rolname = 'auth-service') THEN
CREATE USER "auth-service" WITH PASSWORD 'auth-service';
END IF;
END
$do$;
-- +goose StatementEnd
GRANT CONNECT ON DATABASE bh to "auth-service";
GRANT USAGE ON SCHEMA auth TO "auth-service";
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA auth TO "auth-service";
GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA auth TO "auth-service";
COMMIT;
-- +goose Down

View File

@ -0,0 +1,46 @@
-- name: RegisterAccount :one
SELECT auth.bh_register_account(
sqlc.arg(email),
sqlc.arg(passwordHash),
sqlc.arg(role)
);
-- name: GetAccountByEmailOrId :one
SELECT
id,
createdTs,
verifiedTs,
email,
role,
enabled
FROM auth.account
WHERE
email = sqlc.arg(email) OR id = sqlc.arg(accountId);
-- name: GetSessionAccount :one
SELECT
acc.id,
acc.createdTs,
acc.verifiedTs,
acc.email,
acc.role,
acc.enabled
FROM auth.account acc
JOIN auth.sessions sess ON sess.accountId = acc.id
WHERE sess.id = sqlc.arg(sessionId) AND acc.enabled = TRUE;
-- name: VerifyAccount :one
SELECT auth.bh_verify_account(
sqlc.arg(token)
);
-- name: CreateSession :one
INSERT INTO auth.sessions (
accountId,
createdTs
) SELECT acc.id, NOW() FROM auth.account acc WHERE
(acc.email = sqlc.arg(email) OR acc.id = sqlc.arg(accountId))
AND acc.passwordHash = sqlc.arg(passwordHash)
RETURNING *;

View File

@ -0,0 +1,152 @@
package data
import (
"context"
"errors"
"fmt"
"time"
"git.vdhsn.com/barretthousen/barretthousen/src/auth/internal/data/postgres"
"github.com/jackc/pgx/v4"
)
type (
PGRunnerStorage struct {
*postgres.Queries
}
RegisterAccountInput struct {
Email string
PasswordHash string
Role string
}
)
var (
ErrSessionNotFound = errors.New("session not found")
ErrAccountNotFound = errors.New("account not found by the provided email or id")
ErrCouldNotCreateSession = errors.New("could not create session")
)
func (db *PGRunnerStorage) RegisterAccount(ctx context.Context, in RegisterAccountInput) (accountId int, err error) {
var accId int32
if accId, err = db.Queries.RegisterAccount(ctx, postgres.RegisterAccountParams{
Email: in.Email,
Passwordhash: in.PasswordHash,
Role: in.Role,
}); err != nil {
err = fmt.Errorf("could not register a new account with DB: %w", err)
return
}
accountId = int(accId)
return
}
type (
GetAccountInput struct {
AccountID int
Email string
}
AccountResult struct {
ID int `json:"id"`
Created time.Time `json:"created"`
Verified time.Time `json:"verified,omitempty"`
Email string `json:"email"`
PasswordHash string `json:"password_hash,omitempty"`
Role string `json:"role"`
Enabled bool `json:"enabled"`
}
)
func (db *PGRunnerStorage) GetAccount(ctx context.Context, in GetAccountInput) (out AccountResult, err error) {
var row postgres.GetAccountByEmailOrIdRow
if row, err = db.Queries.GetAccountByEmailOrId(ctx, postgres.GetAccountByEmailOrIdParams{
Email: in.Email,
Accountid: int32(in.AccountID),
}); errors.Is(err, pgx.ErrNoRows) {
err = ErrAccountNotFound
return
} else if err != nil {
err = fmt.Errorf("could not execute GetSessionAccount query: %w", err)
return
}
out = AccountResult{
ID: int(row.ID),
Email: row.Email,
Role: row.Role,
Created: row.Createdts,
Verified: row.Verifiedts.Time,
Enabled: true,
}
return
}
func (db *PGRunnerStorage) GetAccountBySession(ctx context.Context, sessionID int) (out AccountResult, err error) {
var row postgres.GetSessionAccountRow
if row, err = db.Queries.GetSessionAccount(ctx, int32(sessionID)); errors.Is(err, pgx.ErrNoRows) {
err = ErrSessionNotFound
return
} else if err != nil {
err = fmt.Errorf("could not execute GetSessionAccount query: %w", err)
return
}
out = AccountResult{
ID: int(row.ID),
Email: row.Email,
Role: row.Role,
Created: row.Createdts,
Verified: row.Verifiedts.Time,
Enabled: true,
}
return
}
type (
CreateSessionInput struct {
Email string
PasswordHash string
}
Session struct {
ID int
AccountID int
Created time.Time
}
)
func (db *PGRunnerStorage) CreateSession(ctx context.Context, in CreateSessionInput) (out Session, err error) {
var session postgres.AuthSession
if session, err = db.Queries.CreateSession(ctx, postgres.CreateSessionParams{
Email: in.Email,
Passwordhash: in.PasswordHash,
}); errors.Is(err, pgx.ErrNoRows) {
err = ErrCouldNotCreateSession
return
} else if err != nil {
err = fmt.Errorf("could not execute query: %w", err)
return
}
out = Session{
ID: int(session.ID),
AccountID: int(session.Accountid),
Created: session.Createdts,
}
return
}
type VerifyAccountInput struct {
Token string
}
func (db *PGRunnerStorage) VerifyAccount(ctx context.Context, in VerifyAccountInput) (err error) {
err = errors.New("Unimplemented")
return
}

View File

@ -0,0 +1,187 @@
package domain
import (
"context"
"errors"
"fmt"
"time"
"git.vdhsn.com/barretthousen/barretthousen/src/auth/internal/data"
"git.vdhsn.com/barretthousen/barretthousen/src/lib/kernel"
)
type (
Domain struct {
Storage
PasswordHasher
}
HashedPassword []byte
PasswordHasher interface {
Hash(string) HashedPassword
Compare(string, HashedPassword) bool
}
Storage interface {
RegisterAccount(context.Context, data.RegisterAccountInput) (int, error)
GetAccountBySession(context.Context, int) (data.AccountResult, error)
GetAccount(context.Context, data.GetAccountInput) (data.AccountResult, error)
CreateSession(context.Context, data.CreateSessionInput) (data.Session, error)
VerifyAccount(context.Context, data.VerifyAccountInput) error
}
)
type (
CreateAccountCommand struct {
Email string
Password string
Role Role
}
AccountCreated struct {
Account
}
)
func (d *Domain) CreateAccount(ctx context.Context, in CreateAccountCommand) (out AccountCreated, err error) {
if in.Email == "" {
err = errors.New("email cannot be empty")
return
}
if in.Password == "" {
err = errors.New("password cannot be empty")
return
}
if in.Role == EmptyRole {
in.Role = UserRole
}
var accountID int
if accountID, err = d.Storage.RegisterAccount(ctx, data.RegisterAccountInput{
Email: in.Email,
PasswordHash: in.Password,
Role: in.Role.String(),
}); err != nil {
err = fmt.Errorf("could not register account: %w", err)
return
}
var ar data.AccountResult
if ar, err = d.Storage.GetAccount(ctx, data.GetAccountInput{
Email: in.Email,
AccountID: accountID,
}); err != nil {
err = fmt.Errorf("could not find account by ID: %w", err)
return
}
out = AccountCreated{
Account: Account{
ID: ar.ID,
Created: ar.Created,
Verified: ar.Verified,
Email: ar.Email,
PasswordHash: ar.PasswordHash,
Role: Role(ar.Role),
Enabled: ar.Enabled,
},
}
return
}
type (
LoginCommand struct {
Email string
Password string
}
LoggedIn struct {
Account
SessionID int
Created time.Time
}
)
var (
ErrInvalidLogin = errors.New("invalid credentials provided")
ErrInvalidSession = errors.New("invalid session id provided")
)
func (d *Domain) Login(ctx context.Context, in LoginCommand) (out LoggedIn, err error) {
kernel.TraceLog.Printf("%q login attempt", in.Email)
var sess data.Session
if sess, err = d.Storage.CreateSession(ctx, data.CreateSessionInput{
Email: in.Email,
PasswordHash: in.Password,
}); errors.Is(err, data.ErrCouldNotCreateSession) {
err = ErrInvalidLogin
return
} else if err != nil {
kernel.ErrorLog.Printf("Error creating session in storage for %q: %w", in.Email, err)
err = ErrInvalidLogin
return
}
var ar data.AccountResult
if ar, err = d.Storage.GetAccount(ctx, data.GetAccountInput{
AccountID: sess.AccountID,
}); err != nil {
err = fmt.Errorf("could not find account by ID: %w", err)
return
}
out = LoggedIn{
Account: Account{
ID: ar.ID,
Created: ar.Created,
Verified: ar.Verified,
Email: ar.Email,
Role: Role(ar.Role),
Enabled: ar.Enabled,
},
Created: sess.Created,
SessionID: sess.ID,
}
return
}
type (
GetAccountCommand struct {
SessionID int
}
)
func (d *Domain) GetAccount(ctx context.Context, in GetAccountCommand) (out Account, err error) {
var ar data.AccountResult
if ar, err = d.Storage.GetAccountBySession(ctx, in.SessionID); errors.Is(err, data.ErrSessionNotFound) {
err = ErrInvalidSession
return
} else if err != nil {
kernel.ErrorLog.Printf("Error getting account by session id: %w", err)
err = fmt.Errorf("could not get account: %w", err)
return
}
out = Account{
ID: ar.ID,
Created: ar.Created,
Verified: ar.Verified,
Email: ar.Email,
Role: Role(ar.Role),
Enabled: ar.Enabled,
}
return
}
type (
VerifyAccountCommand struct{}
AccountVerified struct{}
)
func (d *Domain) VerifyAccount(ctx context.Context, in VerifyAccountCommand) (out AccountVerified, err error) {
err = errors.New("Unimplemented")
return
}

View File

@ -0,0 +1,166 @@
package domain
import (
"context"
"errors"
"reflect"
"strings"
"testing"
"git.vdhsn.com/barretthousen/barretthousen/src/auth/internal/data"
)
func TestDomain_CreateAccount(t *testing.T) {
SetupTest()
tests := map[string]struct {
input CreateAccountCommand
wantOut AccountCreated
wantErr bool
}{
"new account": {
input: CreateAccountCommand{
Email: "test@example.com",
Password: "password",
},
wantOut: AccountCreated{
Account: Account{
ID: 0,
Email: "test@example.com",
PasswordHash: "password",
Enabled: true,
Role: UserRole,
},
},
},
"no email": {
input: CreateAccountCommand{
Password: "password",
Role: UserRole,
},
wantErr: true,
},
"no password": {
input: CreateAccountCommand{
Email: "test@example.com",
Role: UserRole,
},
wantErr: true,
},
"no inputs": {
wantErr: true,
},
}
c := ioc.Scope("CreateAccountTest")
Must(c.Provide(func(s Storage, ms *MockStorage) *Domain {
return &Domain{Storage: s}
}))
for name, v := range tests {
params := v
t.Run(name, func(t *testing.T) {
Must(c.Invoke(func(sut *Domain, ms *MockStorage) {
var a Account
ms.RegisterAccountFunc = func(ctx context.Context, rai data.RegisterAccountInput) (int, error) {
a = Account{
Email: rai.Email,
PasswordHash: rai.PasswordHash,
Role: Role(rai.Role),
Enabled: true,
}
return 0, nil
}
ms.GetAccountFunc = func(ctx context.Context, gai data.GetAccountInput) (data.AccountResult, error) {
if gai.Email != a.Email {
return data.AccountResult{}, errors.New("error")
}
return data.AccountResult{
Email: a.Email,
PasswordHash: a.PasswordHash,
Role: a.Role.String(),
Enabled: true,
}, nil
}
out, err := sut.CreateAccount(context.Background(), params.input)
if (err != nil) != params.wantErr {
t.Errorf("Domain.CreateAccount() error = %v, wantErr %v", err, params.wantErr)
return
}
if !reflect.DeepEqual(out, params.wantOut) {
t.Errorf("Domain.CreateAccount():\n%+v\n, want:\n%+v", out, params.wantOut)
}
}))
})
}
}
func TestDomain_Login(t *testing.T) {
tests := map[string]struct {
input LoginCommand
wantOut LoggedIn
wantErr bool
}{
"Login Valid User": {
input: LoginCommand{
Email: "test@example.com",
Password: "password",
},
wantOut: LoggedIn{
Account: Account{
ID: 1,
Email: "test@example.com",
PasswordHash: "",
Enabled: true,
Role: UserRole,
},
},
},
}
c := ioc.Scope("LoginTest")
Must(c.Provide(func(s Storage) *Domain {
return &Domain{Storage: s}
}))
for name, v := range tests {
params := v
t.Run(name, func(t *testing.T) {
Must(c.Invoke(func(sut *Domain, ms *MockStorage) {
ms.CreateSessionFunc = func(ctx context.Context, in data.CreateSessionInput) (data.Session, error) {
if strings.EqualFold(in.Email, "test@example.com") {
return data.Session{AccountID: 1}, nil
}
return data.Session{}, data.ErrCouldNotCreateSession
}
ms.GetAccountFunc = func(ctx context.Context, gai data.GetAccountInput) (data.AccountResult, error) {
if strings.EqualFold(gai.Email, "test@example.com") || gai.AccountID == 1 {
return data.AccountResult{
ID: 1,
Email: "test@example.com",
Role: UserRole.String(),
Enabled: true,
}, nil
}
return data.AccountResult{}, data.ErrSessionNotFound
}
out, err := sut.Login(context.Background(), params.input)
if (err != nil) != params.wantErr {
t.Errorf("Domain.Login() error = %v, wantErr %v", err, params.wantErr)
return
}
if !reflect.DeepEqual(out, params.wantOut) {
t.Errorf("Domain.Login() = %v, want %v", out, params.wantOut)
}
}))
})
}
}

View File

@ -0,0 +1,32 @@
package domain
import "time"
const (
UserRole = Role("BIDDER")
AdminRole = Role("ADMINISRATOR")
AnonymousRole = Role("ANONYMOUS")
EmptyRole = Role("")
)
type Role string
func (r Role) String() string {
return string(r)
}
type Account struct {
ID int `json:"id"`
Created time.Time `json:"created"`
Verified time.Time `json:"verified,omitempty"`
Email string `json:"email"`
PasswordHash string `json:"password_hash,omitempty"`
Role Role `json:"role"`
Enabled bool `json:"enabled"`
}
type Session struct {
ID int
AccountID int
Created time.Time
}

View File

@ -0,0 +1,70 @@
package domain
import (
"context"
"git.vdhsn.com/barretthousen/barretthousen/src/auth/internal/data"
"go.uber.org/dig"
)
var ioc dig.Container
func SetupTest() {
ioc = *dig.New()
if err := ioc.Provide(func() (Storage, *MockStorage) {
ms := &MockStorage{}
return ms, ms
}); err != nil {
panic(err)
}
}
// Must panics is err is not nil
func Must(err error) {
if err != nil {
panic(err)
}
}
type MockStorage struct {
RegisterAccountFunc func(context.Context, data.RegisterAccountInput) (int, error)
GetAccountFunc func(context.Context, data.GetAccountInput) (data.AccountResult, error)
CreateSessionFunc func(context.Context, data.CreateSessionInput) (data.Session, error)
}
// CreateSession implements Storage.
func (m *MockStorage) CreateSession(ctx context.Context, in data.CreateSessionInput) (data.Session, error) {
if m.CreateSessionFunc == nil {
panic("CreateSessionFunc is unset")
}
return m.CreateSessionFunc(ctx, in)
}
// GetAccount implements Storage.
func (m *MockStorage) GetAccount(ctx context.Context, in data.GetAccountInput) (data.AccountResult, error) {
if m.GetAccountFunc == nil {
panic("GetAccountFunc is unset")
}
return m.GetAccountFunc(ctx, in)
}
// GetAccountBySession implements Storage.
func (m *MockStorage) GetAccountBySession(ctx context.Context, in int) (data.AccountResult, error) {
panic("GetAccountBySession not implemented")
}
// RegisterAccount implements Storage.
func (m *MockStorage) RegisterAccount(ctx context.Context, in data.RegisterAccountInput) (int, error) {
if m.RegisterAccountFunc == nil {
panic("RegisterAccountFunc is unset")
}
return m.RegisterAccountFunc(ctx, in)
}
// VerifyAccount implements Storage.
func (*MockStorage) VerifyAccount(context.Context, data.VerifyAccountInput) error {
panic("unimplemented")
}

View File

@ -0,0 +1,108 @@
package internal
import (
"context"
"errors"
"fmt"
api "git.vdhsn.com/barretthousen/barretthousen/src/auth/api/grpc"
"git.vdhsn.com/barretthousen/barretthousen/src/auth/internal/domain"
"git.vdhsn.com/barretthousen/barretthousen/src/lib/kernel"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
)
func NewAuthServer(d *domain.Domain) func(grpcServer grpc.ServiceRegistrar, endpoint string) {
return func(grpcServer grpc.ServiceRegistrar, endpoint string) {
api.RegisterAuthServer(grpcServer, &authHandler{domain: d})
}
}
type authHandler struct {
api.UnimplementedAuthServer
domain *domain.Domain
}
func (a *authHandler) CreateAccount(ctx context.Context, in *api.CreateAccountInput) (*api.Account, error) {
kernel.TraceLog.Printf("Attempting to create user: %q - %q", in.Email, in.Password)
account, err := a.domain.CreateAccount(ctx, domain.CreateAccountCommand{
Email: in.Email,
Password: in.Password,
Role: domain.Role(in.Role),
})
if err != nil {
return nil, err
}
return &api.Account{
Id: int32(account.ID),
Email: account.Email,
Role: account.Role.String(),
CreatedTs: timestamppb.New(account.Created),
VerifiedTs: timestamppb.New(account.Verified),
}, nil
}
func (a *authHandler) Login(ctx context.Context, in *api.LoginInput) (out *api.LoginResult, err error) {
var result domain.LoggedIn
if result, err = a.domain.Login(ctx, domain.LoginCommand{
Email: in.Email,
Password: in.Password,
}); errors.Is(err, domain.ErrInvalidLogin) {
err = status.Errorf(codes.PermissionDenied, "Email or password invalid.")
return
} else if err != nil {
err = fmt.Errorf("could not login: %w", err)
return
}
if err = grpc.SetHeader(ctx, metadata.New(map[string]string{
"bh-session-id": fmt.Sprintf("%d", result.SessionID),
})); err != nil {
err = status.Errorf(codes.Internal, "could not set header")
return
}
out = &api.LoginResult{
SessionId: fmt.Sprintf("%d", result.SessionID),
Account: &api.Account{
Id: int32(result.ID),
Email: result.Email,
Role: result.Role.String(),
CreatedTs: timestamppb.New(result.Created),
VerifiedTs: nil,
},
}
return
}
func (a *authHandler) VerifyAccount(ctx context.Context, in *api.VerificationParameters) (*api.AccountVerifiedResult, error) {
return nil, status.Errorf(codes.Unimplemented, "method VerifyAccount not implemented")
}
func (a *authHandler) GetAccount(ctx context.Context, in *api.AccountFilter) (*api.Account, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetAccount not implemented")
}
func (a *authHandler) GetSession(ctx context.Context, in *api.SessionFilter) (out *api.Account, err error) {
var result domain.Account
if result, err = a.domain.GetAccount(ctx, domain.GetAccountCommand{
SessionID: int(in.SessionId),
}); err != nil {
err = status.Error(codes.Unauthenticated, "forbidden")
return
}
out = &api.Account{
Id: int32(result.ID),
Email: result.Email,
Role: result.Role.String(),
CreatedTs: timestamppb.New(result.Created),
VerifiedTs: nil,
}
return
}

100
src/auth/main.go Normal file
View File

@ -0,0 +1,100 @@
package main
import (
"context"
"embed"
"flag"
"fmt"
"git.vdhsn.com/barretthousen/barretthousen/src/auth/internal"
"git.vdhsn.com/barretthousen/barretthousen/src/auth/internal/data"
"git.vdhsn.com/barretthousen/barretthousen/src/auth/internal/data/postgres"
"git.vdhsn.com/barretthousen/barretthousen/src/auth/internal/domain"
"git.vdhsn.com/barretthousen/barretthousen/src/lib/kernel"
"github.com/jackc/pgx/v4/pgxpool"
"go.uber.org/dig"
)
type (
authApp struct {
LogLevel kernel.LogLevel `yaml:"log_level" yaml-default:"0"`
Port int `yaml:"port" `
DB_Service kernel.PostgresConnection `yaml:"db_service"`
DB_Migrate kernel.PostgresConnection `yaml:"db_migrate"`
}
)
var (
migrate = flag.Bool("migrate", false, "migrates postgres db")
client = flag.String("client", "", "Runs this service in client mode, to invoke sync job. Takes GRPC endpoint")
target = flag.String("target", "", "To be used with client mode. The target to sync")
//go:embed internal/data/postgres/migrations/*.sql
dbMigrateScript embed.FS
)
func main() {
flag.Parse()
kernel.Run(context.Background(), &authApp{
LogLevel: kernel.LevelTrace,
Port: 5001,
})
}
func (app *authApp) Start(ctx context.Context) error {
if *migrate {
if err := kernel.MigrateDB(ctx, app.DB_Migrate, dbMigrateScript, "auth"); err != nil {
return fmt.Errorf("could not execute db migration: %w", err)
}
}
ioc := dig.New()
var err error
if err = ioc.Provide(func() kernel.PostgresConnection {
return app.DB_Service
}); err != nil {
return err
}
if err = ioc.Provide(func(pgCfg kernel.PostgresConnection) (*pgxpool.Pool, error) {
return kernel.NewDBConnection(ctx, pgCfg)
}); err != nil {
return err
}
if err = ioc.Provide(func(pgConn *pgxpool.Pool) *postgres.Queries {
return postgres.New(pgConn)
}); err != nil {
return err
}
if err = ioc.Provide(func(queries *postgres.Queries) domain.Storage {
return &data.PGRunnerStorage{Queries: queries}
}); err != nil {
return err
}
if err = ioc.Provide(func(rs domain.Storage) *domain.Domain {
return &domain.Domain{
Storage: rs,
}
}); err != nil {
return err
}
return ioc.Invoke(func(d *domain.Domain) error {
authService := internal.NewAuthServer(d)
if _, err := kernel.StartGRPCServer(ctx, app.Port, authService); err != nil {
return err
}
return nil
})
}
func (app *authApp) OnStop(ctx context.Context) {
}
func (app *authApp) GetLogLevel() kernel.LogLevel { return app.LogLevel }

View File

@ -18,7 +18,7 @@ require (
github.com/joho/godotenv v1.5.1 // indirect
github.com/pressly/goose/v3 v3.11.0 // indirect
golang.org/x/net v0.9.0 // indirect
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
golang.org/x/sync v0.2.0 // indirect
golang.org/x/sys v0.8.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect

View File

@ -180,8 +180,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

View File

@ -5,7 +5,7 @@ go 1.19
require (
github.com/jackc/pgx/v4 v4.18.1
go.uber.org/automaxprocs v1.5.2
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
golang.org/x/sync v0.2.0
google.golang.org/grpc v1.55.0
)

View File

@ -175,8 +175,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

View File

@ -10,8 +10,10 @@ import (
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/backoff"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/reflection"
)
type ServerBuilder func(grpc.ServiceRegistrar, string)
@ -38,6 +40,8 @@ func StartGRPCServer(ctx context.Context, port int, sb ServerBuilder, opts ...gr
grpcServerInstance = grpc.NewServer(opts...)
reflection.Register(grpcServerInstance)
sb(grpcServerInstance, endpoint)
if err = grpcServerInstance.Serve(listener); err != nil {

View File

@ -3,9 +3,11 @@ module git.vdhsn.com/barretthousen/barretthousen/src/proxy-admin
go 1.19
require (
git.vdhsn.com/barretthousen/barretthousen/src/auth v1.0.0
git.vdhsn.com/barretthousen/barretthousen/src/lib v1.0.0
git.vdhsn.com/barretthousen/barretthousen/src/runner v1.0.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2
go.uber.org/dig v1.16.1
google.golang.org/grpc v1.55.0
)
@ -39,3 +41,5 @@ require (
replace git.vdhsn.com/barretthousen/barretthousen/src/lib v1.0.0 => ../lib
replace git.vdhsn.com/barretthousen/barretthousen/src/runner v1.0.0 => ../runner
replace git.vdhsn.com/barretthousen/barretthousen/src/auth v1.0.0 => ../auth

View File

@ -143,6 +143,7 @@ go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/automaxprocs v1.5.2 h1:2LxUOGiR3O6tw8ui5sZa2LAaHnsviZdVOUZw4fvbnME=
go.uber.org/automaxprocs v1.5.2/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
go.uber.org/dig v1.16.1 h1:+alNIBsl0qfY0j6epRubp/9obgtrObRAc5aD+6jbWY8=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=

View File

@ -7,9 +7,12 @@ import (
"strings"
"time"
authApi "git.vdhsn.com/barretthousen/barretthousen/src/auth/api"
aapi "git.vdhsn.com/barretthousen/barretthousen/src/auth/api/grpc"
"git.vdhsn.com/barretthousen/barretthousen/src/lib/kernel"
api "git.vdhsn.com/barretthousen/barretthousen/src/runner/api/grpc"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"go.uber.org/dig"
"google.golang.org/grpc"
"google.golang.org/grpc/backoff"
"google.golang.org/grpc/credentials/insecure"
@ -20,12 +23,39 @@ type ProxyAdminApp struct {
Port int `yaml:"port" `
Endpoints struct {
Runner string `yaml:"runner" `
Auth string `yaml:"auth"`
} `yaml:"endpoints"`
}
type AuthService interface {
CheckSession(context.Context, authApi.CheckSessionParams) (authApi.Account, error)
}
func (app *ProxyAdminApp) Start(ctx context.Context) error {
grpcMux := runtime.NewServeMux()
err := api.RegisterRunnerHandlerFromEndpoint(ctx, grpcMux, app.Endpoints.Runner, []grpc.DialOption{
ioc := dig.New()
var err error
if err = ioc.Provide(func() *runtime.ServeMux {
return runtime.NewServeMux()
}); err != nil {
return err
}
if err = ioc.Provide(func() (grpc.ClientConnInterface, error) {
return kernel.DialGRPC(app.Endpoints.Auth)
}); err != nil {
return err
}
if err = ioc.Provide(func(conn grpc.ClientConnInterface) AuthService {
return authApi.NewAuthServiceClient(conn)
}); err != nil {
return err
}
if err = ioc.Invoke(func(grpcMux *runtime.ServeMux, authClient AuthService) error {
// TODO: refactor into kernel package
if err := api.RegisterRunnerHandlerFromEndpoint(ctx, grpcMux, app.Endpoints.Runner, []grpc.DialOption{
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithConnectParams(grpc.ConnectParams{
Backoff: backoff.Config{
@ -33,23 +63,56 @@ func (app *ProxyAdminApp) Start(ctx context.Context) error {
},
MinConnectTimeout: time.Second,
}),
})
if err != nil {
}); err != nil {
return err
}
// TODO: refactor into kernel package
if err := aapi.RegisterAuthHandlerFromEndpoint(ctx, grpcMux, app.Endpoints.Auth, []grpc.DialOption{
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithConnectParams(grpc.ConnectParams{
Backoff: backoff.Config{
MaxDelay: time.Second * 3,
},
MinConnectTimeout: time.Second,
}),
}); err != nil {
return err
}
kernel.TraceLog.Printf("%+v", app)
httpServer := &http.Server{
Addr: fmt.Sprintf("0.0.0.0:%d", app.Port),
ReadHeaderTimeout: time.Second,
Handler: http.StripPrefix("/api", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
kernel.TraceLog.Printf("{ \"Client\": \"%s\", \"Path\":\"%s\", \"User-Agent\":\"%s\", \"Host\":\"%s\", \"Origin\":\"%s\"} ", r.RemoteAddr, r.URL, r.UserAgent(), r.Host, r.Header.Get("Origin"))
// TODO: move to a middleware package
if strings.HasPrefix(r.Host, "proxy-") {
w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
}
// TODO: move to a middleware package
if !(strings.HasSuffix(r.URL.Path, "user") && r.Method == http.MethodPost) {
sessIdStr := r.Header.Get("bh-session-id")
kernel.TraceLog.Printf("session %s", sessIdStr)
account, err := authClient.CheckSession(r.Context(), authApi.CheckSessionParams{
SessionID: sessIdStr,
})
if err != nil {
kernel.ErrorLog.Printf("error calling auth service: %v", err)
http.Error(w, "must be logged in as admin", http.StatusForbidden)
return
}
kernel.TraceLog.Printf("{ \"session-id\":\"%s\", \"email\":\"%s\", \"accountId\":\"%d\", \"role\":\"%s\"}", sessIdStr, account.Email, account.ID, account.Role)
if account.Role != "ADMINISTRATOR" {
http.Error(w, "must be administrator", http.StatusUnauthorized)
return
}
}
grpcMux.ServeHTTP(w, r)
})),
}
@ -57,6 +120,11 @@ func (app *ProxyAdminApp) Start(ctx context.Context) error {
kernel.InfoLog.Printf("Starting HTTP proxy @ %q", httpServer.Addr)
return httpServer.ListenAndServe()
}); err != nil {
return err
}
return nil
}
func (app *ProxyAdminApp) OnStop(ctx context.Context) {

View File

@ -27,7 +27,7 @@ require (
go.uber.org/automaxprocs v1.5.2 // indirect
golang.org/x/crypto v0.8.0 // indirect
golang.org/x/net v0.9.0 // indirect
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
golang.org/x/sync v0.2.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
@ -39,3 +39,5 @@ require (
replace git.vdhsn.com/barretthousen/barretthousen/src/lib v1.0.0 => ../lib
replace git.vdhsn.com/barretthousen/barretthousen/src/catalog v1.0.0 => ../catalog
replace git.vdhsn.com/barretthousen/barretthousen/src/auth v1.0.0 => ../auth

View File

@ -178,8 +178,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

View File

@ -7,6 +7,7 @@ import (
"strings"
"time"
auth_api "git.vdhsn.com/barretthousen/barretthousen/src/auth/api/grpc"
api "git.vdhsn.com/barretthousen/barretthousen/src/catalog/api/grpc"
"git.vdhsn.com/barretthousen/barretthousen/src/lib/kernel"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
@ -19,13 +20,14 @@ type ProxyClientApp struct {
LogLevel kernel.LogLevel `yaml:"log_level" yaml-default:"0"`
Port int `yaml:"port"`
Endpoints struct {
Catalog string `yaml:"catalog" env:"CATALOG_ENDPOINT"`
Catalog string `yaml:"catalog"`
Auth string `yaml:"auth"`
} `yaml:"endpoints" env:"PROXY_CLIENT_SERVICES"`
}
func (app *ProxyClientApp) Start(ctx context.Context) error {
grpcMux := runtime.NewServeMux()
err := api.RegisterCatalogHandlerFromEndpoint(ctx, grpcMux, app.Endpoints.Catalog, []grpc.DialOption{
grpcOpts := []grpc.DialOption{
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithConnectParams(grpc.ConnectParams{
Backoff: backoff.Config{
@ -33,8 +35,13 @@ func (app *ProxyClientApp) Start(ctx context.Context) error {
},
MinConnectTimeout: time.Second,
}),
})
if err != nil {
}
if err := api.RegisterCatalogHandlerFromEndpoint(ctx, grpcMux, app.Endpoints.Catalog, grpcOpts); err != nil {
return err
}
if err := auth_api.RegisterAuthHandlerFromEndpoint(ctx, grpcMux, app.Endpoints.Auth, grpcOpts); err != nil {
return err
}

View File

@ -0,0 +1,158 @@
package catawiki
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"time"
catalog "git.vdhsn.com/barretthousen/barretthousen/src/catalog/api"
"git.vdhsn.com/barretthousen/barretthousen/src/lib/kernel"
"git.vdhsn.com/barretthousen/barretthousen/src/runner/internal/domain"
"golang.org/x/sync/errgroup"
)
func init() {
kernel.TraceLog.Println("Registering AuctionFinder catawiki")
domain.RegisterAuctionFinder(
CatawikiAuctionFinder("catawiki"),
)
}
type CatawikiAuctionFinder string
func (cw CatawikiAuctionFinder) String() string {
return string(cw)
}
func (cw CatawikiAuctionFinder) Find(ctx context.Context, limit int, results chan<- catalog.Auction) (err error) {
defer close(results)
pageSize := 50
if limit > 2500 {
limit = 2500
}
if limit <= 0 {
limit = 2500
}
errg, errgCtx := errgroup.WithContext(ctx)
errg.SetLimit(5)
kernel.TraceLog.Printf("[Catawiki] fetching %d pages or up to %d results in %d batches", limit/pageSize, limit, pageSize)
for p := 1; p <= limit/pageSize; p++ {
pageIdx := p
errg.Go(func() error {
auctionR, err := GetUpcomingAuctions(errgCtx, limit, pageIdx)
if errors.Is(err, ErrNoResults) {
kernel.TraceLog.Println("no more results from catawiki api")
return err
} else if err != nil {
return err
}
for _, r := range auctionR {
select {
case <-errgCtx.Done():
kernel.TraceLog.Println("[Catawiki] group context exited early")
return nil
default:
results <- r
kernel.TraceLog.Printf("[Catawiki]: %+v", r)
}
}
return nil
})
}
if err = errg.Wait(); err != nil {
kernel.ErrorLog.Printf("[Catawiki] could not get results f: %v", err)
return
}
return
}
var ErrNoResults = errors.New("No results found")
func GetUpcomingAuctions(ctx context.Context, limit int, page int) (results []catalog.Auction, err error) {
if limit <= 25 {
limit = 25
} else if limit >= 50 {
limit = 50
}
if page < 1 {
page = 1
}
url := fmt.Sprintf("https://www.catawiki.com/buyer/api/v1/auctions?locale=en&=published_at_desc&per_page=%d&page=%d", limit, page)
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
req.Header.Add("Accept", "application/json")
req.Header.Add("Accept-Language", "en-US,en;q=0.5")
req.Header.Add("User-Agent", "barretthousen.com Auction Search Engine")
kernel.TraceLog.Printf("[Catawiki] Loading page %d of %d results", page, limit)
var res *http.Response
if res, err = http.DefaultClient.Do(req); err != nil {
return
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
err = fmt.Errorf("bad response code: %d", res.StatusCode)
return
}
type Payload struct {
Auctions []struct {
ID int `json:"id"`
StartAt string `json:"start_at"`
CloseAt string `json:"close_at"`
LotCount int `json:"lot_count"`
Title string `json:"title"`
URL string `json:"url"`
Status string `json:"status"`
Sellers []struct {
ID int `json:"id"`
Name string `json:"name"`
} `json:"sellers"`
} `json:"auctions"`
}
var p Payload
if err = json.NewDecoder(res.Body).Decode(&p); err != nil {
return
}
if len(p.Auctions) <= 0 {
err = ErrNoResults
return
}
results = make([]catalog.Auction, len(p.Auctions))
for idx, auction := range p.Auctions {
results[idx] = catalog.Auction{
Title: auction.Title,
Description: "",
SourceSiteURL: "https://www.catawiki.com",
SourceSiteName: "CataWiki",
SourceURL: auction.URL,
Country: "",
Province: "",
ItemCount: auction.LotCount,
}
results[idx].Start, _ = time.Parse(time.RFC3339, auction.StartAt)
results[idx].End, _ = time.Parse(time.RFC3339, auction.CloseAt)
}
return
}

View File

@ -0,0 +1,44 @@
package catawiki
import (
"context"
"reflect"
"testing"
catalog "git.vdhsn.com/barretthousen/barretthousen/src/catalog/api"
)
func TestGetUpcomingAuctions(t *testing.T) {
type args struct {
limit int
page int
}
tests := []struct {
name string
args args
wantResults []catalog.Auction
wantErr bool
}{
{
name: "1 Page, 25 Results",
args: args{
limit: 25,
page: 1,
},
wantResults: make([]catalog.Auction, 25),
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotResults, err := GetUpcomingAuctions(context.TODO(), tt.args.limit, tt.args.page)
if (err != nil) != tt.wantErr {
t.Errorf("GetUpcomingAuctions() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(gotResults, tt.wantResults) {
t.Errorf("GetUpcomingAuctions() = %v, want %v", gotResults, tt.wantResults)
}
})
}
}

View File

@ -71,15 +71,13 @@ type GetUpcomingSaleIDsInput struct {
}
func LAGetUpcomingSaleIDs(ctx context.Context, in GetUpcomingSaleIDsInput) (ids LACatalogIDs, total int, err error) {
if in.Limit == 0 {
in.Limit = 128
}
req, _ := http.NewRequestWithContext(
ctx,
http.MethodGet,
fmt.Sprintf(
"https://search-party-prod.liveauctioneers.com/search/catalogsearch?page=%d&sort=saleStart&pageSize=%d",
"https://search-party-prod.liveauctioneers.com/search/catalogsearch?client_version=5.0&client=web&offset=300&sort=saleStart&page=%d&pageSize=%d&",
in.Page,
in.Limit,
),

View File

@ -2,6 +2,7 @@ package domain
import (
"context"
"errors"
"fmt"
"strings"
"time"
@ -60,17 +61,32 @@ type (
func (domain Domain) StartSync(ctx context.Context, in FindNewUpcomingInput) (out FindNewUpcomingOutput, err error) {
kernel.TraceLog.Printf("%+v", in)
finder := targetsImpls["liveauctioneers"]
if in.TargetSite == "All" || in.TargetSite == "" {
for k, v := range targetsImpls {
if out.Job, err = domain.Storage.CreateScrapeJob(ctx, k); err != nil {
err = fmt.Errorf("could not create new scrape job record: %w", err)
continue
}
kernel.InfoLog.Printf("Scrape Job %d starting", out.Job.ID)
go domain.executeScrapeJob(v, out.Job.ID)
}
} else if finder, ok := targetsImpls[in.TargetSite]; ok {
if out.Job, err = domain.Storage.CreateScrapeJob(ctx, in.TargetSite); err != nil {
err = fmt.Errorf("could not create new scrape job record: %w", err)
return
}
kernel.InfoLog.Printf("Scrape Job %d starting", out.Job.ID)
// TODO: make everything after this line async and run after return
go domain.executeScrapeJob(finder, out.Job.ID)
} else {
kernel.TraceLog.Println("could not find target")
err = errors.New("No scrape job found by name")
return
}
return
}
@ -116,13 +132,13 @@ func (domain *Domain) executeScrapeJob(finder UpcomingAuctionFinder, jobID int)
errs := &strings.Builder{}
for auction := range found {
total++
if !auction.Start.After(time.Now()) {
if !auction.Start.After(time.Now().Add(-24 * time.Hour)) {
continue
}
ace, err := domain.CatalogService.UpdateUpcomingAuction(ctx, auction)
if err != nil {
kernel.TraceLog.Printf("could not import upcoming auction: %s", err.Error())
kernel.ErrorLog.Printf("[%s] could not import upcoming auction: %s", finder.String(), err.Error())
fmt.Fprintf(errs, "{ \"AuctionFingerprint\": \"%s\", \"error\": \"%s\" }\n", ace.Fingerprint, err.Error())
continue
}
@ -132,9 +148,10 @@ func (domain *Domain) executeScrapeJob(finder UpcomingAuctionFinder, jobID int)
}
}
kernel.TraceLog.Printf("[%s] waiting for results...", finder.String())
if err := errGroup.Wait(); err != nil {
err = fmt.Errorf("an issue occurred while finding upcoming items iteration: %w", err)
fmt.Fprintf(errs, "{ \"error\": \"%s\" }", err.Error())
fmt.Fprintf(errs, "{\"error\": \"%s\" }", err.Error())
}
var completedJob ScrapeJob
@ -143,9 +160,9 @@ func (domain *Domain) executeScrapeJob(finder UpcomingAuctionFinder, jobID int)
AuctionCount: count,
Errors: errs.String(),
}); err != nil {
kernel.ErrorLog.Printf("Could not complete scrape job, failing: %v", err)
kernel.ErrorLog.Printf("[%s] Could not complete scrape job, failing: %v", finder.String(), err)
}
kernel.InfoLog.Printf("Scrape Job %d completed in %v. Successfully imported %d/%d", jobID, completedJob.Completed.Sub(completedJob.Started), count, total)
kernel.InfoLog.Printf("[%s] Scrape Job %d completed in %v. Successfully imported %d/%d", finder.String(), jobID, completedJob.Completed.Sub(completedJob.Started), count, total)
return
}

View File

@ -16,6 +16,7 @@ import (
"go.uber.org/dig"
"google.golang.org/grpc"
_ "git.vdhsn.com/barretthousen/barretthousen/src/runner/internal/domain/catawiki"
_ "git.vdhsn.com/barretthousen/barretthousen/src/runner/internal/domain/liveauctioneers"
)

View File

@ -1,5 +1,14 @@
version: "2"
sql:
- queries: "auth/internal/data/postgres/queries.sql"
schema: "auth/internal/data/postgres/migrations"
engine: "postgresql"
gen:
go:
sql_package: pgx/v4
package: "postgres"
out: "auth/internal/data/postgres"
- queries: "runner/internal/data/postgres/queries.sql"
schema: "runner/internal/data/postgres/migrations"
engine: "postgresql"

View File

@ -8,6 +8,10 @@
"name": "web-client",
"version": "0.0.1",
"dependencies": {
"@analytics/google-analytics": "^1.0.7",
"@segment/snippet": "^4.16.2",
"analytics": "^0.8.9",
"analytics-plugin-do-not-track": "^0.1.5",
"luxon": "^3.3.0"
},
"devDependencies": {
@ -46,6 +50,76 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@analytics/cookie-utils": {
"version": "0.2.12",
"resolved": "https://registry.npmjs.org/@analytics/cookie-utils/-/cookie-utils-0.2.12.tgz",
"integrity": "sha512-2h/yuIu3kmu+ZJlKmlT6GoRvUEY2k1BbQBezEv5kGhnn9KpmzPz715Y3GmM2i+m7Y0QmBdVUoA260dQZkofs2A==",
"dependencies": {
"@analytics/global-storage-utils": "^0.1.7"
}
},
"node_modules/@analytics/core": {
"version": "0.12.7",
"resolved": "https://registry.npmjs.org/@analytics/core/-/core-0.12.7.tgz",
"integrity": "sha512-etmIPCoxWLoUZ/o1o2zvIk4cdVHa8I1xUQtTuLA+YXQ4SsFbm75ZoMXJBqWrNSENpqCJgoL6hizl5uTbkNN+1Q==",
"funding": [
{
"type": "GitHub Sponsors",
"url": "https://github.com/sponsors/davidwells"
}
],
"dependencies": {
"@analytics/global-storage-utils": "^0.1.7",
"@analytics/type-utils": "^0.6.2",
"analytics-utils": "^1.0.12"
}
},
"node_modules/@analytics/global-storage-utils": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/@analytics/global-storage-utils/-/global-storage-utils-0.1.7.tgz",
"integrity": "sha512-V+spzGLZYm4biZT4uefaylm80SrLXf8WOTv9hCgA46cLcyxx3LD4GCpssp1lj+RcWLl/uXJQBRO4Mnn/o1x6Gw==",
"dependencies": {
"@analytics/type-utils": "^0.6.2"
}
},
"node_modules/@analytics/google-analytics": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@analytics/google-analytics/-/google-analytics-1.0.7.tgz",
"integrity": "sha512-KZ69NaMIi5kOcouzqI8cu7tZgQl7ziGiRahfU6zniUf32G8bv7wQDh73JFz1NwO6gBPloUc+5BzEoWzScM5Rgw=="
},
"node_modules/@analytics/localstorage-utils": {
"version": "0.1.10",
"resolved": "https://registry.npmjs.org/@analytics/localstorage-utils/-/localstorage-utils-0.1.10.tgz",
"integrity": "sha512-uJS+Jp1yLG5VFCgA5T82ZODYBS0xuDQx0NtAZrgbqt9j51BX3TcgmOez5LVkrUNu/lpbxjCLq35I4TKj78VmOQ==",
"dependencies": {
"@analytics/global-storage-utils": "^0.1.7"
}
},
"node_modules/@analytics/session-storage-utils": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/@analytics/session-storage-utils/-/session-storage-utils-0.0.7.tgz",
"integrity": "sha512-PSv40UxG96HVcjY15e3zOqU2n8IqXnH8XvTkg1X43uXNTKVSebiI2kUjA3Q7ESFbw5DPwcLbJhV7GforpuBLDw==",
"dependencies": {
"@analytics/global-storage-utils": "^0.1.7"
}
},
"node_modules/@analytics/storage-utils": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/@analytics/storage-utils/-/storage-utils-0.4.2.tgz",
"integrity": "sha512-AXObwyVQw9h2uJh1t2hUgabtVxzYpW+7uKVbdHQK80vr3Td5rrmCxrCxarh7HUuAgSDZ0bZWqmYxVgmwKceaLg==",
"dependencies": {
"@analytics/cookie-utils": "^0.2.12",
"@analytics/global-storage-utils": "^0.1.7",
"@analytics/localstorage-utils": "^0.1.10",
"@analytics/session-storage-utils": "^0.0.7",
"@analytics/type-utils": "^0.6.2"
}
},
"node_modules/@analytics/type-utils": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/@analytics/type-utils/-/type-utils-0.6.2.tgz",
"integrity": "sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg=="
},
"node_modules/@esbuild/android-arm": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz",
@ -541,6 +615,27 @@
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
"dev": true
},
"node_modules/@ndhoule/each": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@ndhoule/each/-/each-2.0.1.tgz",
"integrity": "sha512-wHuJw6x+rF6Q9Skgra++KccjBozCr9ymtna0FhxmV/8xT/hZ2ExGYR8SV8prg8x4AH/7mzDYErNGIVHuzHeybw==",
"dependencies": {
"@ndhoule/keys": "^2.0.0"
}
},
"node_modules/@ndhoule/keys": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@ndhoule/keys/-/keys-2.0.0.tgz",
"integrity": "sha512-vtCqKBC1Av6dsBA8xpAO+cgk051nfaI+PnmTZep2Px0vYrDvpUmLxv7z40COlWH5yCpu3gzNhepk+02yiQiZNw=="
},
"node_modules/@ndhoule/map": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@ndhoule/map/-/map-2.0.1.tgz",
"integrity": "sha512-WOEf2An9mL4DVY6NHgaRmFC82pZGrmzW4I0hpPPdczDP4Gp5+Q1Nny77x3w0qzENA8+cbgd9+Lx2ClSTLvkB0g==",
"dependencies": {
"@ndhoule/each": "^2.0.1"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -745,6 +840,14 @@
}
}
},
"node_modules/@segment/snippet": {
"version": "4.16.2",
"resolved": "https://registry.npmjs.org/@segment/snippet/-/snippet-4.16.2.tgz",
"integrity": "sha512-2fgsrt4U+vKv14ohOAsViCEzeZotaawF2Il7YUbmYVrhPn8Hq7xuGznHKRdZeoxScQ87X36xDX2Fzh5bAYRN7g==",
"dependencies": {
"@ndhoule/map": "^2.0.1"
}
},
"node_modules/@sveltejs/adapter-node": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-1.2.4.tgz",
@ -834,6 +937,12 @@
"integrity": "sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==",
"dev": true
},
"node_modules/@types/dlv": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@types/dlv/-/dlv-1.1.2.tgz",
"integrity": "sha512-OyiZ3jEKu7RtGO1yp9oOdK0cTwZ/10oE9PDJ6fyN3r9T5wkyOcvr6awdugjYdqF6KVO5eUvt7jx7rk2Eylufow==",
"peer": true
},
"node_modules/@types/estree": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz",
@ -1110,6 +1219,38 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/analytics": {
"version": "0.8.9",
"resolved": "https://registry.npmjs.org/analytics/-/analytics-0.8.9.tgz",
"integrity": "sha512-oTbUzQpncMTslakqfK70GgB6bopk5hY+uuekwnadMkDyqNLgcD02KRzteTnO7q5Ko6wDECVtT8xi/6OuAMZykA==",
"funding": [
{
"type": "GitHub Sponsors",
"url": "https://github.com/sponsors/davidwells"
}
],
"dependencies": {
"@analytics/core": "^0.12.7",
"@analytics/storage-utils": "^0.4.2"
}
},
"node_modules/analytics-plugin-do-not-track": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/analytics-plugin-do-not-track/-/analytics-plugin-do-not-track-0.1.5.tgz",
"integrity": "sha512-Bnadur6Y8UB2GrZD11SXAmIVraPpsVINcU8cgbq6ynJIE00xrcJ/RhqFdqqIM7Heyuze93FhcEKLl5WGV9N9mA=="
},
"node_modules/analytics-utils": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/analytics-utils/-/analytics-utils-1.0.12.tgz",
"integrity": "sha512-WvV2YWgsnXLxaY0QYux0crpBAg/0JA763NmbMVz22jKhMPo7dpTBet8G2IlF7ixTjLDzGlkHk1ZaKqqQmjJ+4w==",
"dependencies": {
"@analytics/type-utils": "^0.6.2",
"dlv": "^1.1.3"
},
"peerDependencies": {
"@types/dlv": "^1.0.0"
}
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@ -1600,8 +1741,7 @@
"node_modules/dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
"dev": true
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
},
"node_modules/doctrine": {
"version": "3.0.0",

View File

@ -39,6 +39,10 @@
},
"type": "module",
"dependencies": {
"@analytics/google-analytics": "^1.0.7",
"@segment/snippet": "^4.16.2",
"analytics": "^0.8.9",
"analytics-plugin-do-not-track": "^0.1.5",
"luxon": "^3.3.0"
}
}

View File

@ -1,3 +1,4 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {

View File

@ -0,0 +1,101 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
interface FormValidations {
email?: boolean;
general?: boolean;
}
export const validations: FormValidations = {};
let email: string = '';
let password: string = '';
let passwordConfirmation: string = '';
const dispatch = createEventDispatcher();
const execLogin = () =>
dispatch('login', {
email,
password
});
const execRegister = () => {
if (password !== passwordConfirmation) {
console.log("password and confirm don't match");
return;
}
dispatch('register', {
email,
password
});
};
let showPassword = false;
let showRegistration = false;
const revealPass = () => (showPassword = !showPassword);
const toggleFormType = () => (showRegistration = !showRegistration);
$: ctaTxt = showRegistration ? 'Up' : 'In';
$: toggleCtaTxt = !showRegistration ? 'Up' : 'In';
$: ctaFunc = showRegistration ? execRegister : execLogin;
</script>
<form class="flex" style="max-width: 600px;" on:submit|preventDefault={ctaFunc}>
<span class="flex flex-col justify-around w-20">
<label for="email"> Email </label>
<label for="password"> Password&nbsp;</label>
</span>
<span class="flex flex-col">
<span class="flex">
<input
class="px-2 py-1 border-r-0 grow invalid:border-red-500 invalid:border-2"
type="email"
required
bind:value={email}
/>
<button class="border-l-0 px-2 py-1 disabled:text-gray-500">
Sign {ctaTxt}
</button>
</span>
<span class="flex">
<input
class="px-2 py-1 w-full"
name="password"
type={showPassword ? 'text' : 'password'}
required
placeholder={showRegistration ? 'Password' : ''}
on:blur={() => (showPassword = false)}
on:change={(evt) => (password = evt?.target?.value)}
/>
{#if showRegistration}
<input
class="px-2 py-1 border-r-0 invalid:border-red-500 invalid:border-2"
name="confirm_password"
type={showPassword ? 'text' : 'password'}
pattern={password}
required
placeholder="Confirm Password"
on:blur={() => (showPassword = false)}
on:change={(evt) => (passwordConfirmation = evt?.target?.value)}
/>
<button
class="border-l-0 px-2 py-1 w-16"
tabindex="-1"
on:click|stopPropagation={revealPass}
>
{showPassword ? 'Hide' : 'Show'}
</button>
{/if}
</span>
</span>
<button
class="w-20 bg-bh-gold text-bh-black border-bh-black"
on:click|stopPropagation={toggleFormType}
>
I Want to Sign {toggleCtaTxt}
</button>
</form>

View File

@ -12,7 +12,7 @@
}
</script>
<form class="flex bg-bh-black rounded-md grow" on:submit|preventDefault={() => execSearch()}>
<input class="border-none grow py-1 px-2 mr-1 rounded-l-md" name="terms" bind:value={query} />
<button class="border-none rounded-r-md py-1 px-2">Submit</button>
<form class="flex bg-bh-black grow" on:submit|preventDefault={() => execSearch()}>
<input class="border-none grow py-1 px-2 mr-1" name="terms" bind:value={query} />
<button class="border-none py-1 px-2">Submit</button>
</form>

View File

@ -0,0 +1,116 @@
import { browser } from '$app/environment';
import { env } from '$env/dynamic/public';
import { writable } from 'svelte/store';
const API_HOST = `${browser ? '' : env.BH_CLIENT_INTERNAL_API_HOST}/api/v1`;
interface SessionInfo {
sessionId?: string;
account?: {
id: string;
email: string;
role: 'BIDDER' | 'USER' | 'ADMINISTRATOR' | 'ANONYMOUS';
createdTs: string;
};
}
export const getSession = () => {
const strSession = localStorage.getItem('bh-session');
if (strSession) {
const data = JSON.parse(strSession);
if (data?.sessionId) {
return data;
}
}
return null;
}
export const session = writable<SessionInfo>(undefined, (set) => {
const session = getSession();
if (session) {
set(session);
}
});
interface LoginAction {
email: string;
password: string;
}
export const loginAction = async ({ email, password }: LoginAction): Promise<SessionInfo> => {
try {
const sessionStr = localStorage.getItem('bh-session');
if (sessionStr) {
const data = JSON.parse(sessionStr);
if (data.sessionId) {
console.log('already authenticated');
session.set(data);
return data;
}
localStorage.removeItem('bh-session');
}
const response = await fetch(
new Request(`${API_HOST}/user`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
password
})
})
);
const data = await response.json();
console.log(data);
if (data.sessionId) {
localStorage.setItem('bh-session', JSON.stringify(data));
return data;
}
console.trace("got this on login attempt:", data);
} catch (e) {
console.error(e);
}
return {};
};
interface RegisterAction {
email: string;
password: string;
role?: string;
}
export const registerAction = async ({ email, password, role }: RegisterAction) => {
try {
const response = await fetch(
new Request(`${API_HOST}/user`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
password,
role: role || 'USER'
})
})
);
const data = await response.json();
console.log(data);
} catch (e) {
console.error(e);
}
};
export const logoutAction = () => {
localStorage.removeItem('bh-session');
session.set({});
};

View File

@ -1,27 +1,93 @@
<script lang="ts">
import type { LayoutData } from './$types';
import { goto } from '$app/navigation';
import Analytics from 'analytics';
// import doNotTrack from 'analytics-plugin-do-not-track';
// import googleAnalytics from '@analytics/google-analytics';
const analytics = Analytics({
app: 'Barretthousen - Web Client',
version: '1.0',
debug: process.env.NODE_ENV !== 'production',
plugins: [
// googleAnalytics({
// measurementIds: ['UA-143763293-1']
// })
// doNotTrack()
]
});
/* Track a page view */
analytics.page();
import SearchBox from '$lib/SearchBox.svelte';
import AuthForm from '$lib/AuthForm.svelte';
import '../app.css';
import { loginAction, logoutAction, registerAction, session } from '$lib/state';
export let data: LayoutData;
async function onLogin(evt: CustomEvent) {
const { email, password } = evt.detail;
await loginAction({ email, password });
}
async function onLogout() {
analytics.track('user.logout', {});
logoutAction();
}
async function onRegister(evt: CustomEvent) {
const { email, password } = evt.detail;
analytics.track('user.register', { email });
await registerAction({ email, password });
}
async function onSubmit(evt: CustomEvent) {
let { query } = evt.detail;
// TODO: refactor to one source of truth for building query string parameters
await goto(query ? `/?query=${query}` : '/', {
invalidateAll: true
});
analytics.track('user.query', { query });
}
interface SessionInfo {
sessionId?: string;
account?: {
id: string;
email: string;
role: 'BIDDER' | 'USER' | 'ADMINISTRATOR' | 'ANONYMOUS';
createdTs: string;
};
}
let sessionVal: SessionInfo;
session.subscribe((v) => {
sessionVal = v;
if (v && v.sessionId && v.account) {
const { id, email, role } = v.account;
analytics.identify(id, { email, role });
}
});
$: isLoggedIn = !!sessionVal?.sessionId;
// $: pageTitle =
// data.query !== ''
// ? `'${data.query}' Search`
// : 'Barretthousen: The best rare collectibles from all over the web';
</script>
<svelte:head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>
Barretthousen: "{data.query}" results
</title>
<title>Barretthousen: The best rare collectibles from all over the web</title>
<meta name="description" content="Search results for '{data.query}'" />
</svelte:head>
@ -43,8 +109,19 @@
<SearchBox on:search={onSubmit} query={data.query} page={data.page} />
</span>
</li>
<li class="px-6">
<li class="pr-5">
<!-- <span>I want email alerts!</span> -->
{#if !isLoggedIn}
<AuthForm on:login={onLogin} on:register={onRegister} />
{:else}
<span>
{sessionVal.account?.email}
</span>
<button
class="bg-bh-gold border-bh-black px-2 py-1 text-bh-black"
on:click|stopPropagation={onLogout}>Log out</button
>
{/if}
</li>
</ul>
</nav>