Compare commits

..

12 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
Adam Veldhousen 898ec6ec3d feat/admin-panel-pagination (#12)
ci.vdhsn.com/push Build is failing Details
- [x] Add pagination features to the API
- [x] Add pagination to api call on frontend
- [x] Add pagination controls

Co-authored-by: Adam Veldhousen <adamveld12@gmail.com>
Reviewed-on: #12
2023-06-14 00:09:07 +00:00
Adam Veldhousen 92c1fd6f55
note about startup
ci.vdhsn.com/push Build is failing Details
2023-06-12 20:40:38 -05:00
Adam Veldhousen bfb79bc2a9
MVP build (#1)
ci.vdhsn.com/push Build is failing Details
- [x] Runner
- [x] Catalog
- [x] Web Client
- [x] Deployed to beta
- [x] Deployed to prod

Co-authored-by: Adam Veldhousen <adamveld12@gmail.com>
Reviewed-on: #1
2023-06-12 20:25:55 -05:00
118 changed files with 4298 additions and 3899 deletions

View File

@ -1,51 +1,296 @@
kind: pipeline
type: kubernetes
name: Build Images
name: Build & Push Images
trigger:
branch:
- trunk
event:
- push
steps:
- name: Build & Publish
image: plugins/kaniko
- name: Build & Publish Catalog
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
- 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}
# ---
# kind: pipeline
# type: kubernetes
# name: Deploy beta
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
# trigger:
# event:
# - push
# branch:
# - trunk
- 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
# steps:
# - name: discord notification
# 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: Weekly Server Update Complete!
# webhook_id:
# from_secret: DISCORD_WEBHOOK_ID
# webhook_token:
# from_secret: DISCORD_WEBHOOK_TOKEN
- 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

@ -8,8 +8,11 @@ GOBIN = $(shell go env GOPATH)/bin
###################
.PHONY: setup
setup: $(GOBIN)/sqlc $(GOBIN)/buf ./env/.age.txt
setup: $(GOBIN)/sqlc $(GOBIN)/buf
@awk '{ print $$1 }' .tool-versions | xargs -I {} asdf plugin add {} || true
@asdf install || true
@cd ./src/web-client && npm i
@cd ./src/admin-client && npm i
.PHONY: gen
gen: $(GOBIN)/sqlc buf.lock
@ -26,49 +29,63 @@ clobber:
KUBECONFIG=$(KUBECONFIG) ctlptl delete cluster kind-bh-local || true
@rm -f $(KUBECONFIG)
.PHONY: secrets
secrets: ./env/.age.txt
.PHONY: acceptance-test
acceptance-test:
docker run -it --rm \
-v $(pwd)/src/acceptance-tests.yml:/tests/tests.yml \
-e "TEST_HOST=bh.localhost" \
-e "TEST_ENV=local" \
@docker run -it --rm \
-v $(PWD)/src/$(SERVICE)/acceptance-tests.yml:/tests/tests.yml \
-e "TEST_HOST=$(ORIGIN)" \
nytimes/httptest
##############################
# Container Image Building
# see ./doc/deployment.md for details
##############################
SERVICE = "catalog"
ENV = ""
ORIGIN = "https://barretthousen.com"
ENV ?= ""
BUILD_INITIATOR = "Development Machine"
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 \
@docker build --target=production \
--label 'com.barretthousen.service=$(SERVICE)' \
--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)' \
--build-arg 'origin=$(ORIGIN)' \
--build-arg 'service=$(SERVICE)' \
-t barretthousen/client-$(SERVICE):$(VERSION) \
-t git.vdhsn.com/barretthousen/client-$(SERVICE):$(VERSION) \
-f ./src/$(SERVICE)/Dockerfile.frontend ./src/$(SERVICE)
-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):$(ENV) || true
@[ ! -z $(ENV) ] && docker push git.vdhsn.com/barretthousen/client-$(SERVICE):$(VERSION) || true
@[ ! -z $(ENV) ] && docker push git.vdhsn.com/barretthousen/client-$(SERVICE):$(ENV) || true
.PHONY: build-backend-image
build-backend-image:
docker build --target=production \
@docker build --target=production \
--label 'com.barretthousen.service=$(SERVICE)' \
--label 'com.barretthousen.version="$(VERSION)"' \
--label 'com.barretthousen.git-ref="$(GIT_REF)"' \
@ -77,14 +94,29 @@ build-backend-image:
--build-arg 'service=$(SERVICE)' \
-t barretthousen/service-$(SERVICE):$(VERSION) \
-t git.vdhsn.com/barretthousen/service-$(SERVICE):$(VERSION) \
-t git.vdhsn.com/barretthousen/service-$(SERVICE):$(ENV) \
-f ./src/Dockerfile.prod-backend ./src
@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):$(ENV) || true
@[ ! -z $(ENV) ] && docker push git.vdhsn.com/barretthousen/service-$(SERVICE):$(VERSION) || 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
promote-client-prod:
@docker pull git.vdhsn.com/barretthousen/client-$(SERVICE):beta
@docker tag git.vdhsn.com/barretthousen/client-$(SERVICE):beta git.vdhsn.com/barretthousen/client-$(SERVICE):prod
@docker push git.vdhsn.com/barretthousen/client-$(SERVICE):prod
.PHONY: promote-backend-prod
promote-backend-prod:
@docker pull git.vdhsn.com/barretthousen/service-$(SERVICE):beta
@docker tag git.vdhsn.com/barretthousen/service-$(SERVICE):beta git.vdhsn.com/barretthousen/service-$(SERVICE):prod
@docker push git.vdhsn.com/barretthousen/service-$(SERVICE):prod
####################
# File targets
####################
buf.lock: $(GOBIN)/buf
@$(GOBIN)/buf mod update ./src
@ -106,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,83 +1,105 @@
# [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, for learning purposes
Built with microservice architecture and various fancy tools for learning purposes
### Links
- [Keybase Team Chat](keybase://team/barretthousen)
- [Source](https://git.vdhsn.com/Barretthousen/barretthousen)
- [Auction terms glossary](https://auctionsneapolitan.com/Auction-Terms-Glossary.html)
- [User's Site](https://barretthousen.com)
- [Admin Panel](https://admin.barretthousen.com)
### Goals
1. Ability to search upcoming and live auctions across major auction sites
2. Get an email digest of upcoming auctions for the week
Future goals
1. Get email alerts when these auctions are about to go live
2. Specify search criteria for email alerts
- [(Staging) User's Site](https://beta.barretthousen.com)
- [(Staging) Admin Panel](https://beta.admin.barretthousen.com)
## Technology
For Users
- [Prod](https://barretthousen.com)
- [Staging](https://beta.barretthousen.com)
This project is for fun and learning so there are a lot of tools at play:
For Admins
Infra
- [Tilt](https://tilt.dev): local development env orchestration
- [Ctlptl](https://github.com/tilt-dev/ctlptl): Automate local kubernetes clusters for local dev
- [Docker]()
- [Postgres]():
- [Traefik]():
- [Kustomize]():
- [Helm]():
- [Prod](https://beta.barretthousen.com)
- [Staging](https://admin.beta.barretthousen.com)
- [Architecture Diagram (use diagrams.net)](./doc/Barretthousen_mvp.drawio)
Backend
- [Go]()
- [goose](): Go SQL db migration tool
- [Sqlc](https://sqlc.dev/): Generate Go code to query SQL databases from migrations in a type safe way
- [Buf](): Nice frontend for protoc and .proto libraries
- [Protobufs]():
![Arcchitecure Diagram MVP](./doc/Barretthousen_mvp.drawio.svg)
Frontend
- [Typescript]():
- [SvelteKit](https://kit.svelte.dev/):
- [TailwindCSS]():
- [Diagram (use diagrams.net)](./doc/bh_design.drawio)
![Diagram](./doc/bh_design_overview.svg)
### Services
- Web-client
> [Auction terms glossary](https://auctionsneapolitan.com/Auction-Terms-Glossary.html)
- [Web-client](./src/web-client/)
Frontend site that users access to browse upcoming auctions and subscribe for updates to their searches
- Catalog
- [Admin-client](./src/admin-client/)
Enables site administrators to kicks off scrape and email jobs in Runner and Ringman, and view status on current/past jobs.
- [Catalog](./src/catalog/)
API for searching upcoming auctions.
- Ringman
Manages email subscriptions and sending emails to users about upcoming auctions.
- Runner
- [Runner](./src/runner/)
Scrapes sites for upcoming auctions.
Transforms the data into a suitable shape and stores it for use by the frontend.
- Auctioneer
Enables site administrators to kicks off scrape and email jobs in Runner and Ringman, and view status on current/past jobs.
- [Ringman](./src/ringman/)
Manages email subscriptions and sending emails to users about upcoming auctions.
- BHDB
Monolithic postgres datbabase tying it all together
- [Proxy-web](./src/proxy-web/)
Proxies JSON HTTP API requests for the web client to GRPC based services downstream
- [Proxy-admin](./src/proxy-admin/)
Proxies JSON HTTP API requests for the admin client to GRPC based services downstream
### Contributing
## Contributing
You will need the following prereqs installed:
- Docker
- Make
- [asdf](https://asdf-vm.com/guide/getting-started.html)
Install `asdf` and run `hack/asdf_plugin_setup`
Everything you need is in the makefile:
```sh
# install asdf tools, build tooling, encryption key for sops
# install asdf tools, sqlc, buf
make setup
# generate protobufs and sql boilerplate
make gen
# spin up a k8s cluster, build and deploy services locally w/ hot reloading
# spin up a k8s cluster, build and deploy services locally w/ hot reloading - be patient this takes a few minutes first run
make dev
# build production docker images for the backend microservices, optionally push to the respective env
make build-backend-image SERVICE=[catalog, runner, proxy-client, proxy-admin] [ENV=[beta, prod]]
make build-backend-image SERVICE=[catalog, runner, proxy-web, proxy-admin] [ENV=[beta, prod]]
# build client docker image for web frontends, optionally push to the respective env
make build-client-image SERVICE=[web-client] [ENV=[beta, prod]]
make build-client-image SERVICE=[web-client, admin-client] [ENV=[beta, prod]]
# acceptance tests
make acceptance-test SERVICE=[runner,catalog] ORIGIN=[beta.barretthousen.com,beta.admin.barretthousen.com]
```
For how to deploy [read me](./doc/deployment.md).

117
Tiltfile
View File

@ -7,11 +7,11 @@ 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"])
helm_repo('grafana', 'https://grafana.github.io/helm-charts', labels=["9-repos"])
helm_repo('kafka-ui-github', 'https://provectus.github.io/kafka-ui', labels=["9-repos"])
helm_resource(
'ingress',
@ -52,55 +52,16 @@ helm_resource(
labels=["9-data"]
)
helm_resource(
'kafka',
'bitnami/kafka',
namespace='barretthousen-local',
flags=[
'--set', 'numIoThreads=1',
'--set', 'numNetworkThreads=1',
],
port_forwards=[port_forward(9092, 9092, name='kafka')],
resource_deps=['bitnami'],
labels=["9-data"]
)
helm_resource(
'kafka-ui',
'kafka-ui-github/kafka-ui',
namespace='barretthousen-local',
flags=[
'--set', 'envs.config.KAFKA_CLUSTERS_0_NAME=bh-kafka',
'--set', 'envs.config.KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS=kafka:9092'
],
port_forwards=[port_forward(9090, 8080, name='kafka-ui')],
resource_deps=['kafka'],
labels=["9-data"]
)
def bh_client(service="", port_forwards=[], labels=['2-services'], deps=['ingress']):
# docker_build('example-nodejs-image', '.',
# build_args={'node_env': 'development'},
# entrypoint='yarn run nodemon --ext js,mustache /app/index.js',
# live_update=[
# sync('.', '/app'),
# run('cd /app && yarn install', trigger=['./package.json', './yarn.lock']),
# # if all that changed was start-time.txt, make sure the server
# # reloads so that it will reflect the new startup time
# run('touch /app/index.js', trigger='./start-time.txt'),
# # add a congrats message!
# run('sed -i "s/Hello cats!/{}/g" /app/views/index.mustache'.
# format(congrats)),
# ])
basepath = './src/{}-client'.format(service)
docker_build(
ref='barretthousen/client-{}-client'.format(service),
context=basepath,
dockerfile=basepath +'/Dockerfile.dev-frontend'.format(service),
dockerfile='./src/Dockerfile.frontend'.format(service),
target='development',
build_args={
"service": '{}-client'.format(service)
},
entrypoint='vite dev --port=80 --host=0.0.0.0 --strictPort --logLevel info',
live_update=[
sync(basepath + '/src', '/opt/{}-client/src'.format(service)),
@ -121,7 +82,6 @@ def bh_backend_service(service="", port_forwards=[], migrateDB=False, devMode=Tr
'{}-go-compile'.format(service),
'CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -gcflags "all=-N -l" -o .bin/{}-debug ./src/{}'.format(service, service),
deps=['./src/{}'.format(service), './src/lib'],
resource_deps=deps,
labels=['3-compilation']
)
@ -158,7 +118,7 @@ def bh_backend_service(service="", port_forwards=[], migrateDB=False, devMode=Tr
'./src/runner',
'./src/catalog',
'./src/proxy-admin',
'./src/proxy-client',
'./src/proxy-web',
'./src/lib'
],
live_update=[
@ -185,43 +145,56 @@ k8s_resource(
)
bh_backend_service(service="auth", migrateDB=True, port_forwards=[
port_forward(2345, name='Delve port')
])
bh_backend_service(service="runner", migrateDB=True, port_forwards=[
port_forward(2345, name='Delve port')
], deps=["postgres", "kafka"])
port_forward(2346, 2345, name='Delve port')
])
bh_backend_service(service="catalog", migrateDB=True, port_forwards=[
port_forward(2346, 2345, name='Delve port')
], deps=["postgres", "kafka"])
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'])
bh_backend_service(service="proxy-client", port_forwards=[
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')
# local_resource(
# 'dev-web-client',
# dir='./src/web-client',
# cmd='npm i',
# serve_dir='./src/web-client',
# serve_cmd='npm run dev',
# ignore=['./src/web-client/src'],
# deps=[
# './src/web-client/package.json',
# './src/web-client/svelte.config.js',
# './src/web-client/vite.config.ts'
# ],
# readiness_probe=probe(10, 2, 10, 1, 3, http_get=http_get_action(8080, 'localhost', 'http')),
# links=[
# link(url='http://localhost:8080', name='Web Client')
# ],
# labels=['2-services']
# )

View File

@ -1,637 +0,0 @@
<mxfile host="app.diagrams.net" modified="2023-04-16T17:34:01.973Z" agent="Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0" etag="g01UfqVrYfXmJxj10a0k" version="21.1.7" type="device">
<diagram name="High level" id="BL8KaFisCtBiJRqkGTb9">
<mxGraphModel dx="3802" dy="1124" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="JNN8aSlnDbWN0y9jmZyT-1" value="&lt;div&gt;Web Client&lt;/div&gt;" style="whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
<mxGeometry y="110" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="H4J2X_9uFRljxSkwpIMP-39" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="JNN8aSlnDbWN0y9jmZyT-2" target="H4J2X_9uFRljxSkwpIMP-36">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-2" value="&lt;div&gt;Users&lt;/div&gt;" style="ellipse;shape=cloud;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="-310" y="110" width="120" height="80" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-24" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0;entryDx=0;entryDy=52.5;entryPerimeter=0;" edge="1" parent="1" source="JNN8aSlnDbWN0y9jmZyT-3" target="JNN8aSlnDbWN0y9jmZyT-17">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-3" value="&lt;div&gt;Catalog&lt;/div&gt;" style="whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
<mxGeometry x="150" y="200" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-10" value="" style="endArrow=classic;html=1;rounded=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=1.013;exitY=0.613;exitDx=0;exitDy=0;exitPerimeter=0;" edge="1" parent="1" source="JNN8aSlnDbWN0y9jmZyT-1" target="JNN8aSlnDbWN0y9jmZyT-3">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="-80" y="130" as="sourcePoint" />
<mxPoint y="150" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-25" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0;entryDx=0;entryDy=52.5;entryPerimeter=0;" edge="1" parent="1" source="JNN8aSlnDbWN0y9jmZyT-11" target="JNN8aSlnDbWN0y9jmZyT-17">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-11" value="&lt;div&gt;Ringman&lt;/div&gt;" style="whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
<mxGeometry x="150" y="90" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-12" value="" style="endArrow=classic;html=1;rounded=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=0.6;exitDx=0;exitDy=0;exitPerimeter=0;" edge="1" parent="1" source="JNN8aSlnDbWN0y9jmZyT-1" target="JNN8aSlnDbWN0y9jmZyT-11">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="-70" y="140" as="sourcePoint" />
<mxPoint x="10" y="300" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-17" value="&lt;div&gt;BHDB&lt;/div&gt;" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" vertex="1" parent="1">
<mxGeometry x="470" y="110" width="60" height="80" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-23" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="JNN8aSlnDbWN0y9jmZyT-19" target="JNN8aSlnDbWN0y9jmZyT-17">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-19" value="&lt;div&gt;Runner&lt;/div&gt;" style="whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
<mxGeometry x="460" y="290" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-20" value="&lt;div&gt;Runner&lt;/div&gt;" style="whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
<mxGeometry x="470" y="300" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-21" value="&lt;div&gt;Runner&lt;/div&gt;" style="whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
<mxGeometry x="480" y="310" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-22" value="&lt;div&gt;Runner&lt;/div&gt;" style="whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
<mxGeometry x="490" y="320" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-26" value="&lt;div&gt;Catalog&lt;/div&gt;" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="-190" y="440" width="180" height="90" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-27" value="GetUpcoming(searchCriteria)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-26">
<mxGeometry y="30" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-1" value="&lt;div&gt;CreateUpcoming(Sale)&lt;/div&gt;" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-26">
<mxGeometry y="60" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-30" value="&lt;div&gt;Ringman&lt;/div&gt;" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry y="440" width="180" height="180" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-31" value="Subscribe(email)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-30">
<mxGeometry y="30" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-3" value="VerifySubscription(email,token)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-30">
<mxGeometry y="60" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-32" value="&lt;div&gt;Unsubscribe(email)&lt;/div&gt;" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-30">
<mxGeometry y="90" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-33" value="&lt;div&gt;SendEmails()&lt;/div&gt;" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-30">
<mxGeometry y="120" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="H4J2X_9uFRljxSkwpIMP-34" value="GetJobs(searchCriteria)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-30">
<mxGeometry y="150" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-34" value="Runner" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="193" y="440" width="177" height="90" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-35" value="&lt;div&gt;FindNewUpcoming(criteria)&lt;br&gt;&lt;/div&gt;" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-34">
<mxGeometry y="30" width="177" height="30" as="geometry" />
</mxCell>
<mxCell id="H4J2X_9uFRljxSkwpIMP-1" value="GetJobs(searchCriteria)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-34">
<mxGeometry y="60" width="177" height="30" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-64" value="&lt;div&gt;Auction&lt;/div&gt;" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;" vertex="1" parent="1">
<mxGeometry x="-190" y="680" width="180" height="300" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-65" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-64">
<mxGeometry y="30" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-66" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-65">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-67" value="ID" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-65">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-68" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-64">
<mxGeometry y="60" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-69" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-68">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-70" value="Title" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-68">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-71" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-64">
<mxGeometry y="90" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-72" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-71">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-73" value="Description" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-71">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-74" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-64">
<mxGeometry y="120" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-75" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-74">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-76" value="&lt;div&gt;StartTS&lt;/div&gt;" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-74">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-121" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-64">
<mxGeometry y="150" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-122" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-121">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-123" value="&lt;div&gt;EndTS&lt;/div&gt;" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-121">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-118" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-64">
<mxGeometry y="180" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-119" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-118">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-120" value="&lt;div&gt;SourceSiteName&lt;/div&gt;" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-118">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-81" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-64">
<mxGeometry y="210" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-82" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-81">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-83" value="&lt;div&gt;SourceURL&lt;/div&gt;" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-81">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-84" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-64">
<mxGeometry y="240" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-85" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-84">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-86" value="Photo1" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-84">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-87" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-64">
<mxGeometry y="270" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-88" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-87">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-89" value="Photo2" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-87">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-93" value="&lt;div&gt;User&lt;/div&gt;" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;" vertex="1" parent="1">
<mxGeometry y="680" width="180" height="150" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-94" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-93">
<mxGeometry y="30" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-95" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-94">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-96" value="ID" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-94">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-146" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-93">
<mxGeometry y="60" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-147" value="UQ" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-146">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-148" value="EmailAddress" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-146">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-97" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-93">
<mxGeometry y="90" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-98" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-97">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-99" value="EmailVerifiedTS" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-97">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-4" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-93">
<mxGeometry y="120" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-5" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="S1HCfoW5zDfGPMNCMrQO-4">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-6" value="CreatedTS" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="S1HCfoW5zDfGPMNCMrQO-4">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-124" value="&lt;div&gt;ScrapeJob&lt;/div&gt;" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;" vertex="1" parent="1">
<mxGeometry x="193" y="680" width="180" height="210" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-125" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-124">
<mxGeometry y="30" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-126" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-125">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-127" value="&lt;div&gt;ID&lt;/div&gt;" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-125">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-128" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;strokeColor=default;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-124">
<mxGeometry y="60" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-129" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-128">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-130" value="StartedTS" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-128">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-131" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-124">
<mxGeometry y="90" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-132" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-131">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-133" value="CompletedTS" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-131">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-134" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-124">
<mxGeometry y="120" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-135" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-134">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-136" value="AuctionsFound" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-134">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-137" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-124">
<mxGeometry y="150" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-138" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-137">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-139" value="TargetSiteName" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-137">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-149" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-124">
<mxGeometry y="180" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-150" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-149">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-151" value="Errors" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-149">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-156" value="EmailSubscription" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;" vertex="1" parent="1">
<mxGeometry y="840" width="180" height="150" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-157" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-156">
<mxGeometry y="30" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-158" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-157">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-159" value="ID" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-157">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-178" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-156">
<mxGeometry y="60" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-179" value="UQ" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-178">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-180" value="UserID" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-178">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-175" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-156">
<mxGeometry y="90" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-176" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-175">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-177" value="&lt;div&gt;SearchCriteria&lt;/div&gt;" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=0;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-175">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-166" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-156">
<mxGeometry y="120" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-167" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;strokeColor=inherit;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-166">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-168" value="&lt;div&gt;LastSentTS&lt;/div&gt;" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="JNN8aSlnDbWN0y9jmZyT-166">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-10" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="S1HCfoW5zDfGPMNCMrQO-7" target="JNN8aSlnDbWN0y9jmZyT-11">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-38" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="S1HCfoW5zDfGPMNCMrQO-7" target="JNN8aSlnDbWN0y9jmZyT-22">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="290" y="320" />
<mxPoint x="290" y="360" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="H4J2X_9uFRljxSkwpIMP-2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0;entryDx=0;entryDy=52.5;entryPerimeter=0;" edge="1" parent="1" source="S1HCfoW5zDfGPMNCMrQO-7" target="JNN8aSlnDbWN0y9jmZyT-17">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="290" y="320" />
<mxPoint x="290" y="163" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-7" value="&lt;div&gt;Auctioneer&lt;/div&gt;" style="whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
<mxGeometry y="280" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-11" value="Auctioneer" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="388" y="440" width="182" height="120" as="geometry" />
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-12" value="&lt;div&gt;TriggerSendEmails&lt;/div&gt;" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="S1HCfoW5zDfGPMNCMrQO-11">
<mxGeometry y="30" width="182" height="30" as="geometry" />
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-39" value="TriggerFindNewUpcoming" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="S1HCfoW5zDfGPMNCMrQO-11">
<mxGeometry y="60" width="182" height="30" as="geometry" />
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-34" value="GetCommandHistory" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="S1HCfoW5zDfGPMNCMrQO-11">
<mxGeometry y="90" width="182" height="30" as="geometry" />
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-13" value="CommandHistory" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;" vertex="1" parent="1">
<mxGeometry x="388" y="680" width="180" height="240" as="geometry" />
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-14" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" vertex="1" parent="S1HCfoW5zDfGPMNCMrQO-13">
<mxGeometry y="30" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-15" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="S1HCfoW5zDfGPMNCMrQO-14">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-16" value="&lt;div&gt;ID&lt;/div&gt;" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="S1HCfoW5zDfGPMNCMrQO-14">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-17" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="S1HCfoW5zDfGPMNCMrQO-13">
<mxGeometry y="60" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-18" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="S1HCfoW5zDfGPMNCMrQO-17">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-19" value="Name" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="S1HCfoW5zDfGPMNCMrQO-17">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-35" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="S1HCfoW5zDfGPMNCMrQO-13">
<mxGeometry y="90" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-36" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="S1HCfoW5zDfGPMNCMrQO-35">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-37" value="Initiator" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="S1HCfoW5zDfGPMNCMrQO-35">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-20" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="S1HCfoW5zDfGPMNCMrQO-13">
<mxGeometry y="120" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-21" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="S1HCfoW5zDfGPMNCMrQO-20">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-22" value="ExecutedTS" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="S1HCfoW5zDfGPMNCMrQO-20">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-23" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="S1HCfoW5zDfGPMNCMrQO-13">
<mxGeometry y="150" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-24" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="S1HCfoW5zDfGPMNCMrQO-23">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-25" value="Target" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="S1HCfoW5zDfGPMNCMrQO-23">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-26" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="S1HCfoW5zDfGPMNCMrQO-13">
<mxGeometry y="180" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-27" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="S1HCfoW5zDfGPMNCMrQO-26">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-28" value="Params" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="S1HCfoW5zDfGPMNCMrQO-26">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-29" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="S1HCfoW5zDfGPMNCMrQO-13">
<mxGeometry y="210" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-30" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="S1HCfoW5zDfGPMNCMrQO-29">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-31" value="Errors" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="S1HCfoW5zDfGPMNCMrQO-29">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="H4J2X_9uFRljxSkwpIMP-41" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="S1HCfoW5zDfGPMNCMrQO-32" target="H4J2X_9uFRljxSkwpIMP-38">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-32" value="Admin" style="ellipse;shape=cloud;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="-310" y="280" width="120" height="80" as="geometry" />
</mxCell>
<mxCell id="H4J2X_9uFRljxSkwpIMP-3" value="SendEmailJob" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;" vertex="1" parent="1">
<mxGeometry y="1000" width="180" height="180" as="geometry" />
</mxCell>
<mxCell id="H4J2X_9uFRljxSkwpIMP-4" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" vertex="1" parent="H4J2X_9uFRljxSkwpIMP-3">
<mxGeometry y="30" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="H4J2X_9uFRljxSkwpIMP-5" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="H4J2X_9uFRljxSkwpIMP-4">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="H4J2X_9uFRljxSkwpIMP-6" value="ID" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="H4J2X_9uFRljxSkwpIMP-4">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="H4J2X_9uFRljxSkwpIMP-19" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" vertex="1" parent="H4J2X_9uFRljxSkwpIMP-3">
<mxGeometry y="60" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="H4J2X_9uFRljxSkwpIMP-20" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="H4J2X_9uFRljxSkwpIMP-19">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="H4J2X_9uFRljxSkwpIMP-21" value="StartedTS" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=0;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="H4J2X_9uFRljxSkwpIMP-19">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="H4J2X_9uFRljxSkwpIMP-22" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" vertex="1" parent="H4J2X_9uFRljxSkwpIMP-3">
<mxGeometry y="90" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="H4J2X_9uFRljxSkwpIMP-23" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="H4J2X_9uFRljxSkwpIMP-22">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="H4J2X_9uFRljxSkwpIMP-24" value="CompletedTS" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=0;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="H4J2X_9uFRljxSkwpIMP-22">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="H4J2X_9uFRljxSkwpIMP-25" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" vertex="1" parent="H4J2X_9uFRljxSkwpIMP-3">
<mxGeometry y="120" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="H4J2X_9uFRljxSkwpIMP-26" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="H4J2X_9uFRljxSkwpIMP-25">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="H4J2X_9uFRljxSkwpIMP-27" value="EmailsSent" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=0;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="H4J2X_9uFRljxSkwpIMP-25">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="H4J2X_9uFRljxSkwpIMP-28" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" vertex="1" parent="H4J2X_9uFRljxSkwpIMP-3">
<mxGeometry y="150" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="H4J2X_9uFRljxSkwpIMP-29" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="H4J2X_9uFRljxSkwpIMP-28">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="H4J2X_9uFRljxSkwpIMP-30" value="Errors" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=0;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="H4J2X_9uFRljxSkwpIMP-28">
<mxGeometry x="30" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="H4J2X_9uFRljxSkwpIMP-40" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="H4J2X_9uFRljxSkwpIMP-36" target="JNN8aSlnDbWN0y9jmZyT-1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="H4J2X_9uFRljxSkwpIMP-36" value="&lt;div&gt;@&lt;/div&gt;&lt;div&gt;alpha&lt;/div&gt;&lt;div&gt;beta&lt;br&gt;&lt;/div&gt;" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.direct_data;whiteSpace=wrap;align=center;" vertex="1" parent="1">
<mxGeometry x="-170" y="120" width="140" height="60" as="geometry" />
</mxCell>
<mxCell id="H4J2X_9uFRljxSkwpIMP-42" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="H4J2X_9uFRljxSkwpIMP-38" target="S1HCfoW5zDfGPMNCMrQO-7">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="H4J2X_9uFRljxSkwpIMP-38" value="&lt;div&gt;admin&lt;/div&gt;&lt;div&gt;alpha-admin&lt;/div&gt;&lt;div&gt;beta-admin&lt;br&gt;&lt;/div&gt;" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.direct_data;whiteSpace=wrap;align=center;" vertex="1" parent="1">
<mxGeometry x="-160" y="290" width="130" height="60" as="geometry" />
</mxCell>
<mxCell id="H4J2X_9uFRljxSkwpIMP-43" value="" style="sketch=0;aspect=fixed;pointerEvents=1;shadow=0;dashed=0;html=1;strokeColor=none;labelPosition=center;verticalLabelPosition=bottom;verticalAlign=top;align=center;fillColor=#00188D;shape=mxgraph.mscae.enterprise.lock" vertex="1" parent="1">
<mxGeometry x="-119" y="240" width="39" height="50" as="geometry" />
</mxCell>
<mxCell id="H4J2X_9uFRljxSkwpIMP-44" value="" style="sketch=0;aspect=fixed;pointerEvents=1;shadow=0;dashed=0;html=1;strokeColor=none;labelPosition=center;verticalLabelPosition=bottom;verticalAlign=top;align=center;fillColor=#00188D;shape=mxgraph.mscae.enterprise.lock_unlocked" vertex="1" parent="1">
<mxGeometry x="-119" y="70" width="37" height="50" as="geometry" />
</mxCell>
<mxCell id="H4J2X_9uFRljxSkwpIMP-51" value="Web Client" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="-380" y="440" width="180" height="90" as="geometry" />
</mxCell>
<mxCell id="H4J2X_9uFRljxSkwpIMP-52" value="SearchPage" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="H4J2X_9uFRljxSkwpIMP-51">
<mxGeometry y="30" width="180" height="30" as="geometry" />
</mxCell>
<mxCell id="H4J2X_9uFRljxSkwpIMP-53" value="SubscriptionsPage" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="H4J2X_9uFRljxSkwpIMP-51">
<mxGeometry y="60" width="180" height="30" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 183 KiB

177
doc/bh_design.drawio Normal file
View File

@ -0,0 +1,177 @@
<mxfile host="app.diagrams.net" modified="2023-06-12T21:54:22.543Z" agent="Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/112.0" etag="x7K_nP_hUPP84gUb8Ro3" version="21.1.7" type="device">
<diagram name="High level" id="BL8KaFisCtBiJRqkGTb9">
<mxGraphModel dx="2053" dy="1131" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="JNN8aSlnDbWN0y9jmZyT-1" value="&lt;div&gt;Web Client&lt;/div&gt;" style="whiteSpace=wrap;html=1;aspect=fixed;" parent="1" vertex="1">
<mxGeometry x="-320" y="70" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-6" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="JNN8aSlnDbWN0y9jmZyT-2" target="wSJkOsJQY9M_Z095WERL-4">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-2" value="&lt;div&gt;Users&lt;/div&gt;" style="ellipse;shape=cloud;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="-840" y="110" width="120" height="80" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-17" value="&lt;div&gt;catalog-db&lt;/div&gt;" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" parent="1" vertex="1">
<mxGeometry x="80" y="57.5" width="80" height="105" as="geometry" />
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-54" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="JNN8aSlnDbWN0y9jmZyT-19" target="wSJkOsJQY9M_Z095WERL-50">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="JNN8aSlnDbWN0y9jmZyT-19" value="&lt;div&gt;Runner&lt;/div&gt;" style="whiteSpace=wrap;html=1;aspect=fixed;" parent="1" vertex="1">
<mxGeometry x="-60" y="440" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-10" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="S1HCfoW5zDfGPMNCMrQO-32" target="wSJkOsJQY9M_Z095WERL-8">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="S1HCfoW5zDfGPMNCMrQO-32" value="Admin" style="ellipse;shape=cloud;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="-830" y="400" width="120" height="80" as="geometry" />
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-19" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="wSJkOsJQY9M_Z095WERL-4" target="JNN8aSlnDbWN0y9jmZyT-1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-21" value="&lt;div&gt;Path: /&lt;/div&gt;" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="wSJkOsJQY9M_Z095WERL-19">
<mxGeometry x="0.4522" y="-2" relative="1" as="geometry">
<mxPoint x="1" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-20" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="wSJkOsJQY9M_Z095WERL-4" target="wSJkOsJQY9M_Z095WERL-18">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-22" value="Path: /api" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="wSJkOsJQY9M_Z095WERL-20">
<mxGeometry x="0.3611" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-4" value="bh.com" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.direct_data;whiteSpace=wrap;align=center;" vertex="1" parent="1">
<mxGeometry x="-640.5" y="120" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-5" value="" style="sketch=0;aspect=fixed;pointerEvents=1;shadow=0;dashed=0;html=1;strokeColor=none;labelPosition=center;verticalLabelPosition=bottom;verticalAlign=top;align=center;fillColor=#00188D;shape=mxgraph.mscae.enterprise.lock_unlocked" vertex="1" parent="1">
<mxGeometry x="-579" y="70" width="37" height="50" as="geometry" />
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-23" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="wSJkOsJQY9M_Z095WERL-8" target="wSJkOsJQY9M_Z095WERL-13">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-25" value="&lt;div&gt;Path:/&lt;/div&gt;" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="wSJkOsJQY9M_Z095WERL-23">
<mxGeometry x="0.43" y="-2" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-24" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="wSJkOsJQY9M_Z095WERL-8" target="wSJkOsJQY9M_Z095WERL-17">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-26" value="Path:/api" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="wSJkOsJQY9M_Z095WERL-24">
<mxGeometry x="0.3263" y="-1" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-8" value="&lt;div&gt;admin.bh.com&lt;/div&gt;" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.direct_data;whiteSpace=wrap;align=center;" vertex="1" parent="1">
<mxGeometry x="-630.5" y="410" width="160.5" height="60" as="geometry" />
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-9" value="" style="sketch=0;aspect=fixed;pointerEvents=1;shadow=0;dashed=0;html=1;strokeColor=none;labelPosition=center;verticalLabelPosition=bottom;verticalAlign=top;align=center;fillColor=#00188D;shape=mxgraph.mscae.enterprise.lock" vertex="1" parent="1">
<mxGeometry x="-570" y="360" width="39" height="50" as="geometry" />
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-13" value="Admin Client" style="whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
<mxGeometry x="-320" y="350" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-30" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="wSJkOsJQY9M_Z095WERL-17" target="JNN8aSlnDbWN0y9jmZyT-19">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-31" value="&lt;div&gt;/v1/sync&lt;/div&gt;" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="wSJkOsJQY9M_Z095WERL-30">
<mxGeometry x="0.2947" y="1" relative="1" as="geometry">
<mxPoint y="-9" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-46" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="wSJkOsJQY9M_Z095WERL-17" target="wSJkOsJQY9M_Z095WERL-42">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="-190" y="480" />
<mxPoint x="-190" y="290" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-47" value="&lt;div&gt;/v1/notify&lt;/div&gt;" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="wSJkOsJQY9M_Z095WERL-46">
<mxGeometry x="-0.359" relative="1" as="geometry">
<mxPoint x="70" y="-111" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-17" value="&lt;div&gt;Proxy Admin&lt;/div&gt;" style="whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
<mxGeometry x="-320" y="440" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-28" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="wSJkOsJQY9M_Z095WERL-18" target="wSJkOsJQY9M_Z095WERL-27">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="-190" y="200" />
<mxPoint x="-190" y="110" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-29" value="&lt;div&gt;/v1/upcoming&lt;/div&gt;" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="wSJkOsJQY9M_Z095WERL-28">
<mxGeometry x="-0.2889" y="-2" relative="1" as="geometry">
<mxPoint x="58" y="-63" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-43" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="wSJkOsJQY9M_Z095WERL-18" target="wSJkOsJQY9M_Z095WERL-42">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="-190" y="200" />
<mxPoint x="-190" y="290" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-45" value="&lt;div&gt;/v1/register&lt;/div&gt;&lt;div&gt;/v1/verify&lt;/div&gt;&lt;div&gt;/v1/unsubscribe&lt;br&gt;&lt;/div&gt;" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="wSJkOsJQY9M_Z095WERL-43">
<mxGeometry x="0.3185" relative="1" as="geometry">
<mxPoint x="32" y="-30" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-18" value="Proxy Web" style="whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
<mxGeometry x="-320" y="160" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-55" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="wSJkOsJQY9M_Z095WERL-27" target="JNN8aSlnDbWN0y9jmZyT-17">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-27" value="&lt;div&gt;Catalog&lt;/div&gt;" style="whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
<mxGeometry x="-70" y="70" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-52" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="wSJkOsJQY9M_Z095WERL-42" target="wSJkOsJQY9M_Z095WERL-49">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-42" value="&lt;div&gt;Ringman&lt;/div&gt;" style="whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
<mxGeometry x="-60" y="250" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-49" value="ringman-db" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" vertex="1" parent="1">
<mxGeometry x="80" y="237.5" width="80" height="105" as="geometry" />
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-50" value="runner-db" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" vertex="1" parent="1">
<mxGeometry x="80" y="427.5" width="80" height="105" as="geometry" />
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-57" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="wSJkOsJQY9M_Z095WERL-56" target="wSJkOsJQY9M_Z095WERL-17">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-58" value="curl /api/v1/sync" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="wSJkOsJQY9M_Z095WERL-57">
<mxGeometry x="0.275" y="-2" relative="1" as="geometry">
<mxPoint x="58" y="41" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-56" value="&lt;div&gt;sync-cronjob&lt;/div&gt;" style="whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
<mxGeometry x="-320" y="600" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-61" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="wSJkOsJQY9M_Z095WERL-59" target="wSJkOsJQY9M_Z095WERL-17">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="-120" y="560" />
<mxPoint x="-280" y="560" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-59" value="&lt;div&gt;notify-cronjob&lt;/div&gt;" style="whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
<mxGeometry x="-160" y="600" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="wSJkOsJQY9M_Z095WERL-60" value="curl /api/v1/notify" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="1">
<mxGeometry x="-70" y="580" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -5,8 +5,10 @@ Services deployed (using kustomize):
- [catalog](./src/catalog)
- [runner](./src/runner)
- [proxy-admin](./src/proxy-admin)
- [proxy-client](./src/proxy-client)
- [proxy-web](./src/proxy-web)
- [web-client](./src/web-client)
- [admin-client](./src/admin-client)
Platform components deployed (using helm):
@ -54,35 +56,53 @@ helm install --upgrade bh-db bitnami/postgresql -n 'barretthousen-beta' \
### Deployment steps
1. Build and publish prod images
1. Build and publish images to `beta`
```sh
make build-backend-image SERVICE=catalog env=beta
make build-backend-image SERVICE=runner env=beta
make build-backend-image SERVICE=proxy-admin env=beta
make build-backend-image SERVICE=proxy-client env=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
make build-client-image SERVICE=web-client env=beta
# for prod drop `beta prefix
make build-client-image SERVICE=web-client
make build-client-image SERVICE=admin-client
```
2. Rolling restart deployments in the beta env
```sh
# for prod drop `-beta` suffix
kubectl rollout restart -n barretthousen-beta deployment runner-beta
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
kubectl rollout restart -n barretthousen-beta deployment proxy-client-beta
kubectl rollout status -n barretthousen-beta deployment proxy-client-beta -w
kubectl rollout restart -n barretthousen-beta deployment proxy-web-beta
kubectl rollout status -n barretthousen-beta deployment proxy-web-beta -w
kubectl rollout restart -n barretthousen-beta deployment web-client-beta
kubectl rollout status -n barretthousen-beta deployment web-client-beta -w
kubectl rollout restart -n barretthousen-beta deployment admin-client-beta
kubectl rollout status -n barretthousen-beta deployment admin-client-beta -w
```
3. Promote images to prod
```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
## Prod
TBD
make promote-client-prod SERVICE=web-client
make promote-client-prod SERVICE=admin-client
```

View File

@ -19,6 +19,8 @@ spec:
stdin: true
tty: true
env:
- name: BH_CLIENT_INTERNAL_API_HOST
value: "http://proxy-admin"
- name: ORIGIN
value: https://admin.barretthousen.com
ports:

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

@ -13,7 +13,7 @@ spec:
path: "/api"
backend:
service:
name: proxy-client
name: proxy-web
port:
number: 80
- pathType: ImplementationSpecific

View File

@ -4,5 +4,24 @@ metadata:
name: bh-registry
type: kubernetes.io/dockerconfigjson
stringData:
.dockerconfigjson: |
{ "auths": {} }
.dockerconfigjson: ENC[AES256_GCM,data:bfqlh7Vy3HDYFtgv56xO+8lXOLO9bQWRC16N8hAzv6xJaIN6CmXDwFzoLoGWPrP9s/o446tuOEJEylf5z/ITnLtdGJgMsN13Xk7OiF9B2unV8yOOrzt6U6R2s5cFpbSL3tAHQmDKHxRrzbvyV2J3magen7oHQWbkwkOQq7FqV/k7wFly+bei1u+YLJ9hq798Xa5HG9j4LsVWi5izKt1BBss2xFlo3yzEFqNmQ+AzcUN1uK1xwStplK4IKC36rewONDS+yyqj830LLShb,iv:qDwYxBqK+ZamBcWEuF+UEfW8gLFROagaBqVAc1tCjUI=,tag:OYhChcvisxP0r3kQ4hq4SA==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age1d5vst0g82v6xml29ydsrxefmf3vclgm6dj3npw6mefa7yu9xueaqztjqlg
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBaRG1ERkdkTXMvUllCSHdS
TXVBVWNMd0hYeXMvTXh6OFVTYXV0MkVoOEJ3Ck9XakJTbHMyTWpvazFzYUtNcmtx
NTVoVnUwWkpKYjg4MWs1dmxpT3JGRFUKLS0tIHdHRk8yL1lCRk9DM0haYjN4Z1Ry
d25rRklvOUdLQlU0S2l0WXBpUXhyR2MKQgJXQgxp0T2rr0V2NjwSjWFlzNyig5vW
S8PW6OpCOyfMqzz5NWTdUVymY7UEdAguwZH+MY2DdvEn3NM/TcnRwA==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2023-05-27T02:48:45Z"
mac: ENC[AES256_GCM,data:SCjcJPn7hg9sUFYlOUKAVJBXKNIrcz/x3aqyX43xf7UO7Zo/pGDp1JDaKA7lCaKTgPEAe1zRRv6LjejNGX3DlpmxMS6o2xaI3nb0e0CnLj9t9t57L5svrciwh9wOennWj26DirgzAB+uqCJ/NGOJh4S8yTPOF5MgBNkqNw6FN94=,iv:YTtckdYzKnBBqbQYvjw9FpvGHsUxX6MnAeNopYhFe7I=,tag:BPUitJtY65JbnanHJgJatg==,type:str]
pgp: []
encrypted_regex: ^(data|stringData)$
version: 3.7.3

View File

@ -1,10 +1,11 @@
resources:
- ./image-pull-secret.yaml
- ./namespace.yaml
- ./auth-deployment.yaml
- ./catalog-deployment.yaml
- ./runner-deployment.yaml
- ./proxy-admin-deployment.yaml
- ./proxy-client-deployment.yaml
- ./proxy-web-deployment.yaml
- ./sync-cronjob.yaml
- ./web-client-deployment.yaml
- ./admin-client-deployment.yaml

View File

@ -53,4 +53,5 @@ stringData:
log_level: 2
port: 80
endpoints:
runner: runner-local:5001
runner: runner:5001
auth: auth:5001

View File

@ -1,45 +1,45 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: proxy-client
name: proxy-web
spec:
selector:
matchLabels:
service: proxy-client
service: proxy-web
template:
metadata:
labels:
service: proxy-client
service: proxy-web
spec:
serviceAccountName: barretthousen-service
containers:
- name: proxy-client
image: barretthousen/service-proxy-client:latest
- name: proxy-web
image: barretthousen/service-proxy-web:latest
imagePullPolicy: Always
ports:
- containerPort: 80
name: http
command:
- /opt/proxy-client
- /opt/proxy-web
resources:
limits:
memory: "128Mi"
cpu: "250m"
volumeMounts:
- name: proxy-client-config
- name: proxy-web-config
mountPath: /config/
volumes:
- name: proxy-client-config
- name: proxy-web-config
secret:
secretName: proxy-client-config
secretName: proxy-web-config
---
apiVersion: v1
kind: Service
metadata:
name: proxy-client
name: proxy-web
spec:
selector:
service: proxy-client
service: proxy-web
ports:
- port: 80
targetPort: 80
@ -47,11 +47,11 @@ spec:
apiVersion: v1
kind: Secret
metadata:
name: proxy-client-config
name: proxy-web-config
stringData:
config.yaml: |
log_level: 2
port: 80
access_control_allow_origin: "*"
endpoints:
catalog: catalog-local:5001
catalog: catalog:5001
auth: auth:5001

View File

@ -46,7 +46,7 @@ spec:
ports:
- port: 5001
targetPort: 5001
name: grpc
---
apiVersion: v1
kind: Secret
@ -56,6 +56,7 @@ stringData:
config.yaml: |
log_level: 2
port: 5001
catalog_endpoint: catalog:5001
db_service:
scheme: postgres
port: 5432

View File

@ -3,7 +3,7 @@ kind: CronJob
metadata:
name: runner-sync
spec:
schedule: "*/2 * * * *"
schedule: "0 * * * *"
jobTemplate:
spec:
template:

View File

@ -19,6 +19,8 @@ spec:
stdin: true
tty: true
env:
- name: BH_CLIENT_INTERNAL_API_HOST
value: "http://proxy-web"
- name: ORIGIN
value: https://barretthousen.com
ports:

View File

@ -1,8 +1,8 @@
apiVersion: v1
kind: ConfigMap
kind: Secret
metadata:
name: catalog-config
data:
name: auth-config
stringData:
config.yaml: |
log_level: 2
port: 5001
@ -11,8 +11,8 @@ data:
port: 5432
host: bh-db
name: bh
user: catalog-service
password: catalog-service
user: auth-service
password: auth-service
db_migrate:
scheme: postgres
port: 5432

View File

@ -7,10 +7,11 @@ namespace: barretthousen-beta
patchesStrategicMerge:
- sync-cronjob.yaml
- auth-secret.yaml
- catalog-secret.yaml
- proxy-admin-secret.yaml
- proxy-client-secret.yaml
- runner-secret.yaml
- proxy-admin-secret.yaml
- proxy-web-secret.yaml
patches:
- target:
@ -19,7 +20,7 @@ patches:
patch: |-
- op: replace
path: /spec/rules/0/host
value: admin.beta.barretthousen.com
value: beta.admin.barretthousen.com
- target:
kind: Ingress
name: web
@ -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
@ -41,6 +49,7 @@ patches:
- op: replace
path: /spec/template/spec/containers/0/image
value: git.vdhsn.com/barretthousen/service-runner:beta
- target:
kind: Deployment
name: proxy-admin
@ -50,11 +59,26 @@ patches:
value: git.vdhsn.com/barretthousen/service-proxy-admin:beta
- target:
kind: Deployment
name: proxy-client
name: proxy-web
patch: |-
- op: replace
path: /spec/template/spec/containers/0/image
value: git.vdhsn.com/barretthousen/service-proxy-client:beta
value: git.vdhsn.com/barretthousen/service-proxy-web:beta
- target:
kind: Deployment
name: admin-client
patch: |-
- op: replace
path: /spec/template/spec/containers/0/image
value: git.vdhsn.com/barretthousen/client-admin-client:beta
- op: replace
path: /spec/template/spec/containers/0/env/1/value
value: https://beta.admin.barretthousen.com
- op: replace
path: /spec/template/spec/containers/0/env/0/value
value: http://proxy-admin-beta
- target:
kind: Deployment
name: web-client
@ -63,5 +87,8 @@ patches:
path: /spec/template/spec/containers/0/image
value: git.vdhsn.com/barretthousen/client-web-client:beta
- op: replace
path: /spec/template/spec/containers/0/env/0/value
path: /spec/template/spec/containers/0/env/1/value
value: https://beta.barretthousen.com
- op: replace
path: /spec/template/spec/containers/0/env/0/value
value: http://proxy-web-beta

View File

@ -6,5 +6,7 @@ stringData:
config.yaml: |
log_level: 2
port: 80
access_control_allow_origin: "beta.admin.barretthousen.com"
endpoints:
runner: runner-beta:5001
auth: auth-beta:5001

View File

@ -1,11 +1,12 @@
apiVersion: v1
kind: Secret
metadata:
name: proxy-client-config
name: proxy-web-config
stringData:
config.yaml: |
log_level: 2
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

8
env/local/image-pull-secret.yaml vendored Normal file
View File

@ -0,0 +1,8 @@
apiVersion: v1
kind: Secret
metadata:
name: bh-registry
type: kubernetes.io/dockerconfigjson
stringData:
.dockerconfigjson: |
{ "auths": {} }

View File

@ -8,18 +8,16 @@ nameSuffix: -local
namespace: barretthousen-local
patchesStrategicMerge:
- debug-auth.yaml
- debug-catalog.yaml
- debug-runner.yaml
- runner-secret.yaml
- sync-cronjob.yaml
- image-pull-secret.yaml
- proxy-admin-secret.yaml
- proxy-web-secret.yaml
patches:
- target:
kind: CronJob
name: runner-sync
patch: |-
- op: replace
path: /spec/schedule
value: "* * * * *"
- target:
kind: Ingress
name: admin
@ -34,3 +32,23 @@ patches:
- op: replace
path: /spec/rules/0/host
value: bh.localhost
- target:
kind: Deployment
name: admin-client
patch: |-
- op: replace
path: /spec/template/spec/containers/0/env/0/value
value: http://proxy-admin-local
- op: replace
path: /spec/template/spec/containers/0/env/1/value
value: http://admin.localhost:8000
- target:
kind: Deployment
name: web-client
patch: |-
- op: replace
path: /spec/template/spec/containers/0/env/0/value
value: http://proxy-web-local
- op: replace
path: /spec/template/spec/containers/0/env/1/value
value: http://bh.localhost:8000

View File

@ -1,10 +1,11 @@
apiVersion: v1
kind: ConfigMap
kind: Secret
metadata:
name: proxy-admin-config
data:
stringData:
config.yaml: |
log_level: 2
port: 80
endpoints:
runner: runner-beta:5001
runner: runner-local:5001
auth: auth-local:5001

11
env/local/proxy-web-secret.yaml vendored Normal file
View File

@ -0,0 +1,11 @@
apiVersion: v1
kind: Secret
metadata:
name: proxy-web-config
stringData:
config.yaml: |
log_level: 2
port: 80
endpoints:
catalog: catalog-local:5001
auth: auth-local:5001

View File

@ -1,12 +1,12 @@
apiVersion: v1
kind: ConfigMap
kind: Secret
metadata:
name: runner-config
data:
stringData:
config.yaml: |
log_level: 2
port: 5001
catalog_endpoint: catalog-beta:5001
catalog_endpoint: catalog-local:5001
db_service:
scheme: postgres
port: 5432
@ -20,4 +20,4 @@ data:
host: bh-db
name: bh
user: postgres
password: bh-admin-beta
password: bh-admin

View File

@ -2,31 +2,19 @@ resources:
- ../base
commonLabels:
environment: prod
#nameSuffix: -prod
namespace: barretthousen
patchesStrategicMerge:
- sync-cronjob.yaml
- catalog-configmap.yaml
- proxy-admin-configmap.yaml
- proxy-client-configmap.yaml
- runner-configmap.yaml
patches:
- target:
kind: Ingress
name: admin
kind: Deployment
name: auth
patch: |-
- op: replace
path: /spec/rules/0/host
value: admin.barretthousen.com
- target:
kind: Ingress
name: web
patch: |-
- op: replace
path: /spec/rules/0/host
value: barretthousen.com
path: /spec/template/spec/containers/0/image
value: git.vdhsn.com/barretthousen/service-auth:prod
- target:
kind: Deployment
name: catalog
@ -50,11 +38,11 @@ patches:
value: git.vdhsn.com/barretthousen/service-proxy-admin:prod
- target:
kind: Deployment
name: proxy-client
name: proxy-web
patch: |-
- op: replace
path: /spec/template/spec/containers/0/image
value: git.vdhsn.com/barretthousen/service-proxy-client:prod
value: git.vdhsn.com/barretthousen/service-proxy-web:prod
- target:
kind: Deployment
name: web-client
@ -62,6 +50,10 @@ patches:
- op: replace
path: /spec/template/spec/containers/0/image
value: git.vdhsn.com/barretthousen/client-web-client:prod
- target:
kind: Deployment
name: admin-client
patch: |-
- op: replace
path: /spec/template/spec/containers/0/env/0/value
value: https://barretthousen.com
path: /spec/template/spec/containers/0/image
value: git.vdhsn.com/barretthousen/client-admin-client:prod

View File

@ -1,11 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: proxy-client-config
data:
config.yaml: |
log_level: 2
port: 80
access_control_allow_origin: "beta.barretthousen.com"
endpoints:
catalog: catalog-beta:5001

View File

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

View File

@ -1,9 +1,10 @@
go 1.19
use (
./src/auth
./src/catalog
./src/lib
./src/proxy-admin
./src/proxy-client
./src/proxy-web
./src/runner
)

View File

@ -1,29 +1,225 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY=
cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E=
cloud.google.com/go/accesscontextmanager v1.7.0/go.mod h1:CEGLewx8dwa33aDAZQujl7Dx+uYhS0eay198wB/VumQ=
cloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw=
cloud.google.com/go/analytics v0.19.0/go.mod h1:k8liqf5/HCnOUkbawNtrWWc+UAzyDlW89doe8TtoDsE=
cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8=
cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8=
cloud.google.com/go/apigeeregistry v0.6.0/go.mod h1:BFNzW7yQVLZ3yj0TKcwzb8n25CFBri51GVGOEUcgQsc=
cloud.google.com/go/apikeys v0.6.0/go.mod h1:kbpXu5upyiAlGkKrJgQl8A0rKNNJ7dQ377pdroRSSi8=
cloud.google.com/go/appengine v1.7.1/go.mod h1:IHLToyb/3fKutRysUlFO0BPt5j7RiQ45nrzEJmKTo6E=
cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k=
cloud.google.com/go/artifactregistry v1.13.0/go.mod h1:uy/LNfoOIivepGhooAUpL1i30Hgee3Cu0l4VTWHUC08=
cloud.google.com/go/asset v1.13.0/go.mod h1:WQAMyYek/b7NBpYq/K4KJWcRqzoalEsxz/t/dTk4THw=
cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E=
cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU=
cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss=
cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g=
cloud.google.com/go/beyondcorp v0.5.0/go.mod h1:uFqj9X+dSfrheVp7ssLTaRHd2EHqSL4QZmH4e8WXGGU=
cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU=
cloud.google.com/go/billing v1.13.0/go.mod h1:7kB2W9Xf98hP9Sr12KfECgfGclsH3CQR0R08tnRlRbc=
cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q=
cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8=
cloud.google.com/go/channel v1.12.0/go.mod h1:VkxCGKASi4Cq7TbXxlaBezonAYpp1GCnKMY6tnMQnLU=
cloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb4wQGAbIgL1s=
cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA=
cloud.google.com/go/cloudtasks v1.10.0/go.mod h1:NDSoTLkZ3+vExFEWu2UJV1arUyzVDAiZtdWcsUyNwBs=
cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs=
cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w=
cloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8L7isCQe9pQA=
cloud.google.com/go/containeranalysis v0.9.0/go.mod h1:orbOANbwk5Ejoom+s+DUCTTJ7IBdBQJDcSylAx/on9s=
cloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8=
cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE=
cloud.google.com/go/dataform v0.7.0/go.mod h1:7NulqnVozfHvWUBpMDfKMUESr+85aJsC/2O0o3jWPDE=
cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8=
cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM=
cloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJObeO1ppRs=
cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4=
cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c=
cloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c=
cloud.google.com/go/datastream v1.7.0/go.mod h1:uxVRMm2elUSPuh65IbZpzJNMbuzkcvu5CjMqVIUHrww=
cloud.google.com/go/deploy v1.8.0/go.mod h1:z3myEJnA/2wnB4sgjqdMfgxCA0EqC3RBTNcVPs93mtQ=
cloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE=
cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4=
cloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb7TvwOceQ2tbs=
cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE=
cloud.google.com/go/edgecontainer v1.0.0/go.mod h1:cttArqZpBB2q58W/upSG++ooo6EsblxDIolxa3jSjbY=
cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU=
cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M=
cloud.google.com/go/eventarc v1.11.0/go.mod h1:PyUjsUKPWoRBCHeOxZd/lbOOjahV41icXyUY5kSTvVY=
cloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg=
cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE=
cloud.google.com/go/functions v1.13.0/go.mod h1:EU4O007sQm6Ef/PwRsI8N2umygGqPBS/IZQKBQBcJ3c=
cloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0=
cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg=
cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw=
cloud.google.com/go/gkehub v0.12.0/go.mod h1:djiIwwzTTBrF5NaXCGv3mf7klpEMcST17VBTVVDcuaw=
cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y=
cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo=
cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0=
cloud.google.com/go/iap v1.7.1/go.mod h1:WapEwPc7ZxGt2jFGB/C/bm+hP0Y6NXzOYGjpPnmMS74=
cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4=
cloud.google.com/go/iot v1.6.0/go.mod h1:IqdAsmE2cTYYNO1Fvjfzo9po179rAtJeVGUvkLN3rLE=
cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI=
cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY=
cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo=
cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M=
cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo=
cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA=
cloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY=
cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I=
cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM=
cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo=
cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw=
cloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM=
cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY=
cloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU=
cloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ=
cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI=
cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ=
cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc=
cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw=
cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs=
cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk=
cloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc=
cloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs=
cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4=
cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM=
cloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdDTb69oW0vNHYDBTbB4NvMD9c=
cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac=
cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ=
cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ=
cloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI=
cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA=
cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14=
cloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg=
cloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc=
cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU=
cloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0=
cloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag=
cloud.google.com/go/servicecontrol v1.11.1/go.mod h1:aSnNNlwEFBY+PWGQ2DoM0JJ/QUXqV5/ZD9DOLB7SnUk=
cloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8vd4f//bQLtvzkPPT/s=
cloud.google.com/go/servicemanagement v1.8.0/go.mod h1:MSS2TDlIEQD/fzsSGfCdJItQveu9NXnUniTrq/L8LK4=
cloud.google.com/go/serviceusage v1.6.0/go.mod h1:R5wwQcbOWsyuOfbP9tGdAnCAc6B9DRwPG1xtWMDeuPA=
cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A=
cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M=
cloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI=
cloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw=
cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c=
cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc=
cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM=
cloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk=
cloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos=
cloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ=
cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU=
cloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0=
cloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY=
cloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY=
cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes=
cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg=
cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng=
cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/elastic/elastic-transport-go/v8 v8.0.0-alpha/go.mod h1:87Tcz8IVNe6rVSLdBux1o/PEItLtyabHU3naC7IoqKI=
github.com/elastic/go-elasticsearch/v8 v8.0.0/go.mod h1:8NCWP26meGbncX+R9sxo2JD8IqBjRTuS7yXMstHpd40=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.11.0/go.mod h1:VnHyVMpzcLvCFt9yUz1UnCwHLhwx1WguiVDV7pTG/tI=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ=
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI=
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
github.com/klauspost/compress v1.16.4 h1:91KN02FnsOYhuunwU4ssRe8lc2JosWmizWa91B5v1PU=
github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.uber.org/dig v1.0.0/go.mod h1:z+dSd2TP9Usi48jL8M3v63iSBVkiwtVyMKxMZYYauPg=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

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,2 +1,4 @@
web-client
.idea
web-client/*
admin-client/*

View File

@ -1,4 +1,4 @@
FROM golang:1.19-alpine as builder
FROM golang:1.19-alpine as dev-builder
ARG service
@ -8,7 +8,7 @@ FROM alpine as development
ARG service
COPY --from=builder /go/bin/dlv /go/bin/dlv
COPY --from=dev-builder /go/bin/dlv /go/bin/dlv
COPY .bin/${service}-debug /opt/
COPY ./src /go/src/

55
src/Dockerfile.frontend Normal file
View File

@ -0,0 +1,55 @@
FROM node:lts AS development
ARG service
COPY . /opt/${service}
WORKDIR /opt/${service}
RUN npm install && npm install -g vite
CMD vite dev --port=80 --host=0.0.0.0 --strictPort --logLevel info
FROM node:lts AS build
ARG service
ENV PROTOCOL_HEADER=x-forwarded-proto
ENV HOST_HEADER=x-forwarded-host
WORKDIR /opt/${service}
COPY . /opt/${service}
RUN npm ci && npm run build \
&& cp -v /opt/${service}/package.json /opt/${service}/build \
&& cp -v /opt/${service}/package-lock.json /opt/${service}/build \
&& cd /opt/${service}/build && npm ci --omit dev
FROM node:lts AS production
ARG service
LABEL com.barretthousen.service=${service}
LABEL com.barretthousen.tags="frontend,public"
LABEL com.barretthousen.release-date="2023-05-01"
LABEL com.barretthousen.version="alpha-0.0.1"
LABEL com.barretthousen.git-ref="000000000000000000000000000000000000000000000000"
ENV ENV=production
ENV PROTOCOL_HEADER=x-forwarded-proto
ENV HOST_HEADER=x-forwarded-host
ENV BH_CLIENT_SERVICE=${service}
ENV ORIGIN="https://barretthousen.com"
COPY --from=build /opt/${service}/build/ /opt/${service}/
WORKDIR /opt/${service}
ENV PORT 80
EXPOSE 80
ENTRYPOINT ["node", "."]

View File

@ -1,15 +1,11 @@
FROM golang:1.19 as 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
@ -22,7 +18,7 @@ FROM alpine as production
ARG service
COPY --from=builder /opt/${service} /opt/${service}
COPY --from=prod-builder /opt/${service} /opt/${service}
ENV SERVICE=${SERVICE}

View File

@ -1,4 +1,4 @@
FROM golang:1.19-alpine as builder
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

@ -1,9 +0,0 @@
FROM node:lts AS development
COPY . /opt/admin-client
WORKDIR /opt/admin-client
RUN npm install && npm install -g vite
CMD vite dev --port=80 --host=0.0.0.0 --strictPort --logLevel info

View File

@ -1,42 +0,0 @@
FROM node:lts AS build
ARG origin=https://barretthousen.com
ENV PROTOCOL_HEADER=x-forwarded-proto
ENV HOST_HEADER=x-forwarded-host
ENV ORIGIN=${origin}
WORKDIR /opt/admin-client
COPY . /opt/admin-client
RUN npm run build \
&& cp -v /opt/admin-client/package.json /opt/admin-client/build \
&& cp -v /opt/admin-client/package-lock.json /opt/admin-client/build \
&& cd /opt/admin-client/build && npm ci --omit dev
FROM node:lts AS production
LABEL com.barretthousen.service="admin-client"
LABEL com.barretthousen.tags="frontend,public"
LABEL com.barretthousen.release-date="2023-05-01"
LABEL com.barretthousen.version="alpha-0.0.1"
LABEL com.barretthousen.git-ref="000000000000000000000000000000000000000000000000"
ARG origin=https://barretthousen.com
ENV ENV=production
ENV PROTOCOL_HEADER=x-forwarded-proto
ENV HOST_HEADER=x-forwarded-host
ENV ORIGIN=${origin}
WORKDIR /opt
COPY --from=build /opt/admin-client/build/ /opt/admin-client/
ENV PORT 80
EXPOSE 80
ENTRYPOINT ["node", "admin-client"]

View File

@ -1,38 +1,12 @@
# create-svelte
# Admin client
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
Admin panel to drive backend "administrative" functionality
## Creating a project
Uses [SvelteKit](), [TailwindCSS](), [Typescript (poorly)]()
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm create svelte@latest
## Development
# create a new project in my-app
npm create svelte@latest my-app
```
Follow the [Contributing section](../../README.md#contributing) to get the environment stood up locally.
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```bash
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
Browse to [http://admin.localhost:8000](http://admin.localhost:8000).

View File

@ -1,11 +1,11 @@
{
"name": "web-client",
"name": "adminb-client",
"version": "0.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "web-client",
"name": "adminb-client",
"version": "0.0.1",
"dependencies": {
"luxon": "^3.3.0"

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,38 @@
<script lang="ts">
import { fade } from 'svelte/transition';
export let page: number = 1;
export let itemCount: number = 0;
export let pageSize: number = 1;
interface PagerParams {
page: number;
pageCount: number;
pageSize: number;
}
export let createUrl: (
//<reference types="svelte" />
arg0: PagerParams
) => string = (): string => '';
$: pageCount = Math.max(1, Math.ceil(itemCount / pageSize));
</script>
<ol class="flex justify-between w-full center text-lg" style="padding: 0 10%;">
{#if page > 1}
<li in:fade>
<a href={createUrl({ page: page - 1, pageCount, pageSize })}> &lt;Previous </a>
</li>
{:else}
<li />
{/if}
<li>Page {page} of {pageCount}</li>
{#if page < pageCount}
<li in:fade>
<a href={createUrl({ page: page + 1, pageCount, pageSize })}> Next&gt; </a>
</li>
{:else}
<li />
{/if}
</ol>

View File

@ -1,12 +1,14 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
export let disable: boolean = false;
let target: string = 'All';
const dispatch = createEventDispatcher();
function execScrape() {
dispatch('scrape', { target });
if (!disable) dispatch('scrape', { target });
}
</script>
@ -15,6 +17,7 @@
<option>All</option>
<option>liveauctioneers</option>
</select>
<button class="border-none rounded-r-md bg-bh-gold text-bh-black py-1 px-2">Start Sync</button>
<button class="border-none rounded-r-md bg-bh-gold text-bh-black py-1 px-2" disabled={disable}
>Start Sync</button
>
</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

@ -1,61 +1,96 @@
<script lang="ts">
import type { PageData } from './$types';
import { fade } from 'svelte/transition';
import { invalidateAll } from '$app/navigation';
import StartScrapeForm from '$lib/StartScrapeForm.svelte';
import ScrapeJobResult from '$lib/ScrapeJobResult.svelte';
import Pager from '$lib/Pager.svelte';
import { fade } from 'svelte/transition';
import { onDestroy, onMount } from 'svelte';
import { invalidateAll } from '$app/navigation';
import { getSession } from '$lib/state';
export let data: PageData;
$: completedJobs = data.jobs.filter(({ completedTs }) => completedTs !== null);
$: activeJobs = data.jobs.filter(({ completedTs }) => completedTs === null);
$: activeJobCount = activeJobs.length;
$: completedJobCount = completedJobs.length;
$: activeJobCount = data.active.length;
let disableSync = false;
var intervalId: any;
onMount(() => {
intervalId = setInterval(invalidateAll, 2000);
});
onDestroy(() => clearInterval(intervalId));
async function onScrape({ detail }) {
disableSync = true;
try {
const response = await fetch(
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 })
})
);
const scrapeJob = await response.json();
data.jobs.push(scrapeJob);
console.log(scrapeJob);
} catch (error) {}
} catch (error) {
} finally {
disableSync = false;
}
}
function buildQueryString({ page, pageSize }: { page: number; pageSize: number }): string {
const qs = [
data.query === '' ? data.query : `query=${data.query}`,
page <= 1 ? '' : `page=${page}`,
`limit=${pageSize}`
]
.filter((x) => x !== '')
.join('&');
return qs !== '' ? `/?${qs}` : qs;
}
const limit = 16;
</script>
<section class="flex w-full flex-col justify-center" in:fade out:fade>
<h1 class="text-2xl mb-8">Sync Status</h1>
<div class="mb-8">
<StartScrapeForm on:scrape={onScrape} />
</div>
<section in:fade>
<h1 class="text-2xl pb-5">Sync Status: {data.total} jobs</h1>
</section>
<section class="pb-5">
<h2 class="text-2xl">{activeJobCount} In Progress</h2>
{#key data.activeTotal}
<ul class="flex">
{#each data.active as job, i}
<li id="job-{job.id}" in:fade={{ delay: i * 90 }}>
<ScrapeJobResult {job} />
</li>
{/each}
</ul>
{/key}
</section>
<StartScrapeForm on:scrape={onScrape} disable={disableSync} />
<section class="py-10">
<h2 class="text-2xl">Completed</h2>
<Pager
page={data.page}
itemCount={data.completeTotal}
pageSize={limit}
createUrl={buildQueryString}
/>
{#key data.completeTotal + data.page}
<ul class="flex flex-wrap justify-between">
{#each data.complete as job, i}
<li id="job-{job.id}" in:fade={{ delay: i * 90 }}>
<ScrapeJobResult {job} />
</li>
{/each}
</ul>
{/key}
</section>
<section>
<h2>{activeJobCount} In Progress Jobs</h2>
<ul class="flex">
{#each activeJobs as j, i}
<li in:fade>
<ScrapeJobResult job={j} />
</li>
<!-- {#if i < 10}
{/if} -->
{/each}
</ul>
<h2>{completedJobCount} Complete</h2>
<ul class="flex flex-wrap justify-between">
{#each completedJobs as j, i}
<li in:fade>
<ScrapeJobResult job={j} />
</li>
<!-- {#if i < 5}
{/if} -->
{/each}
</ul>
<Pager page={data.page} itemCount={data.total} pageSize={limit} createUrl={buildQueryString} />
</section>
</section>

View File

@ -1,9 +1,9 @@
import { browser } from '$app/environment';
import type { PageLoad } from './$types';
import { env } from '$env/dynamic/public';
import { getSession } from '$lib/state';
// TODO: change to env var
const API_HOST = `${browser ? '' : 'http://proxy-admin-local'}/api/v1`;
const API_HOST = `${browser ? '' : env.BH_CLIENT_INTERNAL_API_HOST}/api/v1`;
interface Job {
id: Number;
@ -15,18 +15,89 @@ interface Job {
}
interface ScrapeStatusPageData {
jobs: Job[]
page: number;
total: number;
activeTotal: number;
completeTotal: number;
limit: number;
active: Job[];
complete: Job[];
}
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`);
const {results } = await response.json();
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 {
jobs: results || []
active,
complete,
activeTotal,
completeTotal,
total,
page,
limit
};
} catch (e) {
console.log(e);
return { jobs:[] };
return {
activeTotal: 0,
completeTotal: 0,
active: [],
complete: [],
total: 0,
page: 1,
limit
};
}
}) satisfies PageLoad;
class SearchParameters {
page: number;
limit: number;
constructor(url: URL) {
this.page = Number(url.searchParams.get('page') || 1);
if (this.page < 0) {
this.page = 0;
}
this.limit = Number(url.searchParams.get('limit') || 12);
if (this.limit > 32) {
this.limit = 32;
} else if (this.limit < 12) {
this.limit = 12;
}
}
getPage(): number {
return this.page || 1;
}
getLimit(): number {
return this.limit || 12;
}
toQueryString(): string {
const qs = Object.entries(this)
.filter((t) => t.length > 0 && t[1])
.map((t) => `${t[0]}=${t[1]}`)
.join('&');
return qs === '' ? '' : `?${qs}`;
}
}

View File

@ -9,7 +9,7 @@ const config = {
kit: {
env: {
publicPrefix: 'BH_WEB_CLIENT'
publicPrefix: 'BH_CLIENT'
},
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported or you settled on a specific environment, switch out the adapter.

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

@ -1,16 +1,7 @@
tests:
- description: "trigger scrape"
- description: "Search for top"
request:
path: "/v1/findNewUpcoming"
method: "POST"
headers:
authorization: "token ${SECRET_AUTH_TOKEN}"
response:
statusCodes: [200]
- description: "get list of jobs"
request:
path: "/v1/jobs"
headers:
authorization: "token ${SECRET_AUTH_TOKEN}"
path: "/api/v1/upcoming"
method: "GET"
response:
statusCodes: [200]

View File

@ -1,38 +1,64 @@
package api
import (
"context"
"time"
capi "git.vdhsn.com/barretthousen/barretthousen/src/catalog/api/grpc"
"google.golang.org/grpc"
"google.golang.org/protobuf/types/known/timestamppb"
)
func NewCatalogServiceClient(conn grpc.ClientConnInterface) *CatalogServiceClient {
return &CatalogServiceClient{
CatalogClient: capi.NewCatalogClient(conn),
}
}
type CatalogServiceClient struct {
capi.CatalogClient
}
type Auction struct {
ID int `json:"id,omitempty"`
Fingerprint string `json:"fingerprint,omitempty"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
ItemCount int `json:"itemCount,omitempty"`
Start time.Time `json:"start,omitempty"`
End time.Time `json:"end,omitempty"`
Source SourceDetail `json:"source"`
Seller SellerDetail `json:"seller"`
Address AddressDetail `json:"address"`
Fingerprint string `json:"fingerprint,omitempty"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
SourceSiteURL string `json:"source_site_url,omitempty"`
SourceSiteName string `json:"source_site_name,omitempty"`
SourceURL string `json:"source_url,omitempty"`
Country string `json:"country,omitempty"`
Province string `json:"province,omitempty"`
ItemCount int `json:"itemCount,omitempty"`
Start time.Time `json:"start,omitempty"`
End time.Time `json:"end,omitempty"`
}
type SourceDetail struct {
Name string `json:"name"`
AuctionURL string `json:"auctionUrl"`
SiteURL string `json:"siteURL"`
type AuctionCreatedEvent struct {
Fingerprint string
ID int
Duplicate bool
}
type SellerDetail struct {
ID int `json:"id"`
Name string `json:"name"`
SiteURL string `json:"siteUrl"`
}
func (css *CatalogServiceClient) UpdateUpcomingAuction(ctx context.Context, a Auction) (AuctionCreatedEvent, error) {
ac, err := css.ImportAuction(ctx, &capi.ImportAuctionMessage{
Items: int32(a.ItemCount),
Start: timestamppb.New(a.Start),
End: timestamppb.New(a.End),
Title: a.Title,
Description: a.Description,
SourceSiteURL: a.SourceSiteURL,
SourceSiteName: a.SourceSiteName,
SourceURL: a.SourceURL,
Country: a.Country,
Province: a.Province,
})
if err != nil {
return AuctionCreatedEvent{}, err
}
type AddressDetail struct {
CountryCode string `json:"country"`
Lat float64 `json:"lat"`
Long float64 `json:"lng"`
State string `json:"state"`
City string `json:"city"`
return AuctionCreatedEvent{
Fingerprint: ac.Auction.Fingerprint,
ID: int(ac.Auction.Id),
Duplicate: ac.Duplicate,
}, nil
}

View File

@ -14,8 +14,8 @@ service Catalog {
};
}
// rpc ImportAuction(ImportAuctionMessage) returns (AuctionCreated) {
// }
rpc ImportAuction(ImportAuctionMessage) returns (AuctionCreated) {
}
}
message AuctionSearchCriteria {

View File

@ -1,3 +0,0 @@
log_level: ERROR
service: {}

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

@ -1,58 +0,0 @@
package kafka
import (
"context"
"errors"
"io"
"git.vdhsn.com/barretthousen/barretthousen/src/lib/kernel"
kafka "github.com/segmentio/kafka-go"
)
type kafkaConsumer struct {
*kafka.Conn
}
func (c *kafkaConsumer) Close() error {
return c.Conn.Close()
}
func (c *kafkaConsumer) ConsumeAsync(ctx context.Context, output chan<- []byte) {
for {
batch := c.ReadBatch(1024, 1024*50)
for {
msg, err := batch.ReadMessage()
if err != nil && !errors.Is(err, io.EOF) || len(msg.Value) == 0 {
break
}
kernel.TraceLog.Printf("header: %+v, value: %+v", msg.Headers, msg.Value)
select {
case output <- msg.Value:
case <-ctx.Done():
batch.Close()
return
}
}
batch.Close()
select {
case <-ctx.Done():
return
}
}
}
func NewConsumer(topic string) (*kafkaConsumer, error) {
conn, err := kafka.DialLeader(context.Background(), "tcp", "kafka:9092", topic, 0)
if err != nil {
return nil, err
}
return &kafkaConsumer{
Conn: conn,
}, nil
}

View File

@ -1,55 +0,0 @@
package internal
import (
"context"
"encoding/json"
"io"
api "git.vdhsn.com/barretthousen/barretthousen/src/catalog/api"
"git.vdhsn.com/barretthousen/barretthousen/src/catalog/internal/domain"
"git.vdhsn.com/barretthousen/barretthousen/src/lib/kernel"
)
type KafkaConsumer interface {
io.Closer
ConsumeAsync(context.Context, chan<- []byte)
}
type AuctionImporter interface {
ImportAuction(context.Context, domain.ImportAuctionMessage) (domain.AuctionCreated, error)
}
func RunIndexer(ctx context.Context, c KafkaConsumer, importer AuctionImporter) {
defer c.Close()
msgs := make(chan []byte, 64)
go c.ConsumeAsync(ctx, msgs)
for msg := range msgs {
var auction api.Auction
if err := json.Unmarshal(msg, &auction); err != nil {
kernel.TraceLog.Printf("could not ingest: %w", err)
continue
}
_, err := importer.ImportAuction(ctx, domain.ImportAuctionMessage{
Auction: domain.Auction{
ItemCount: auction.ItemCount,
Start: auction.Start,
End: auction.End,
Title: auction.Title,
Description: auction.Description,
SourceSiteURL: auction.Source.SiteURL,
SourceSiteName: auction.Source.Name,
SourceURL: auction.Source.AuctionURL,
Country: auction.Address.CountryCode,
Province: auction.Address.State,
},
})
if err != nil {
kernel.ErrorLog.Printf("could not import auction: %w", err)
continue
}
}
}

View File

@ -8,7 +8,6 @@ import (
"git.vdhsn.com/barretthousen/barretthousen/src/catalog/internal"
"git.vdhsn.com/barretthousen/barretthousen/src/catalog/internal/data"
"git.vdhsn.com/barretthousen/barretthousen/src/catalog/internal/data/kafka"
"git.vdhsn.com/barretthousen/barretthousen/src/catalog/internal/data/postgres"
"git.vdhsn.com/barretthousen/barretthousen/src/catalog/internal/domain"
"git.vdhsn.com/barretthousen/barretthousen/src/lib/kernel"
@ -18,10 +17,10 @@ import (
type (
catalogApp struct {
LogLevel kernel.LogLevel `yaml:"log_level" env:"BH_LOG_LEVEL" env-default:"0" yaml-default:"0"`
Port int `yaml:"port" env:"catalog_PORT"`
DB_Service kernel.PostgresConnection `yaml:"db_service" env:"catalog_DB_SERVICE"`
DB_Migrate kernel.PostgresConnection `yaml:"db_migrate" env:"catalog_DB_MIGRATE"`
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"`
}
)
@ -81,15 +80,7 @@ func (app *catalogApp) Start(ctx context.Context) error {
return err
}
if err = ioc.Provide(func() (internal.KafkaConsumer, error) {
return kafka.NewConsumer("runner.sync_results")
}); err != nil {
return err
}
return ioc.Invoke(func(d *domain.Usecase, consumer internal.KafkaConsumer) error {
go internal.RunIndexer(ctx, consumer, d)
return ioc.Invoke(func(d *domain.Usecase) error {
catalogService := internal.NewCatalogServer(d)
if _, err := kernel.StartGRPCServer(ctx, app.Port, catalogService); err != nil {

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

@ -38,6 +38,8 @@ func Run(parent context.Context, app App) {
return
}
TraceLog.Printf("Using config: %+v", app)
if err := app.Start(ctx); err != nil {
ErrorLog.Println(err)
return

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

@ -4,53 +4,127 @@ import (
"context"
"fmt"
"net/http"
"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"
)
type ProxyAdminApp struct {
LogLevel kernel.LogLevel `yaml:"log_level" env:"BH_LOG_LEVEL" env-default:"0" yaml-default:"0"`
Port int `yaml:"port" env:"PROXY_ADMIN_PORT"`
LogLevel kernel.LogLevel `yaml:"log_level" yaml-default:"0"`
Port int `yaml:"port" `
Endpoints struct {
Runner string `yaml:"runner" env:"RUNNER_ENDPOINT"`
} `yaml:"endpoints" env:"PROXY_ADMIN_SERVICES"`
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{
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithConnectParams(grpc.ConnectParams{
Backoff: backoff.Config{
MaxDelay: time.Second * 3,
},
MinConnectTimeout: time.Second,
}),
})
if err != nil {
ioc := dig.New()
var err error
if err = ioc.Provide(func() *runtime.ServeMux {
return runtime.NewServeMux()
}); 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\"} ", r.RemoteAddr, r.URL.Path)
grpcMux.ServeHTTP(w, r)
})),
if err = ioc.Provide(func() (grpc.ClientConnInterface, error) {
return kernel.DialGRPC(app.Endpoints.Auth)
}); err != nil {
return err
}
kernel.InfoLog.Printf("Starting HTTP proxy @ %q", httpServer.Addr)
if err = ioc.Provide(func(conn grpc.ClientConnInterface) AuthService {
return authApi.NewAuthServiceClient(conn)
}); err != nil {
return err
}
return httpServer.ListenAndServe()
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{
MaxDelay: time.Second * 3,
},
MinConnectTimeout: time.Second,
}),
}); 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)
})),
}
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

@ -1,4 +1,4 @@
module git.vdhsn.com/barretthousen/barretthousen/src/proxy-client
module git.vdhsn.com/barretthousen/barretthousen/src/proxy-web
go 1.19
@ -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

@ -4,8 +4,10 @@ import (
"context"
"fmt"
"net/http"
"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"
@ -15,17 +17,17 @@ import (
)
type ProxyClientApp struct {
LogLevel kernel.LogLevel `yaml:"log_level" env:"BH_LOG_LEVEL" env-default:"0" yaml-default:"0"`
Port int `yaml:"port" env:"PROXY_CLIENT_PORT"`
AccessControlAllowOrigin string `yaml:"access_control_allow_origin", env:"PROXY_CLIENT_CORS_ORIGIN`
Endpoints struct {
Catalog string `yaml:"catalog" env:"CATALOG_ENDPOINT"`
LogLevel kernel.LogLevel `yaml:"log_level" yaml-default:"0"`
Port int `yaml:"port"`
Endpoints struct {
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
}
@ -44,9 +51,12 @@ func (app *ProxyClientApp) Start(ctx context.Context) error {
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\" } ", r.RemoteAddr, r.URL, r.UserAgent())
// TODO: pull the allowed origin host names from the config file
w.Header().Set("Access-Control-Allow-Origin", app.AccessControlAllowOrigin)
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"))
if strings.HasPrefix(r.Host, "proxy-") {
w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
}
grpcMux.ServeHTTP(w, r)
})),
}

View File

@ -1,16 +1,12 @@
tests:
- description: "trigger scrape"
- description: "trigger sync"
request:
path: "/v1/findNewUpcoming"
method: "POST"
headers:
authorization: "token ${SECRET_AUTH_TOKEN}"
path: "/v1/sync"
method: "PUT"
response:
statusCodes: [200]
- description: "get list of jobs"
request:
path: "/v1/jobs"
headers:
authorization: "token ${SECRET_AUTH_TOKEN}"
path: "/v1/sync?page=1&limit=6"
response:
statusCodes: [200]

View File

@ -37,11 +37,14 @@ message SyncStatus {
message StatusFilter {
int32 page = 1;
int32 id = 2;
int32 limit = 2;
}
message SyncStatusList {
repeated SyncStatus results = 1;
int32 page = 2;
int32 total = 3;
repeated SyncStatus active = 1;
repeated SyncStatus complete = 2;
int32 page = 3;
int32 total = 4;
int32 activeTotal = 5;
int32 completeTotal = 6;
}

View File

@ -1,21 +0,0 @@
log_level: 2
port: 5001
kafka_bootstrap_servers:
- kafka
db_service:
scheme: postgres
port: 5432
host: bh-db
name: bh
user: runner-service
password: runner-service
db_migrate:
scheme: postgres
port: 5432
host: bh-db
name: bh
user: postgres
password: bh-admin

View File

@ -18,10 +18,6 @@ require (
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/klauspost/compress v1.16.4 // indirect
github.com/moby/patternmatcher v0.5.0 // indirect
github.com/moby/sys/sequential v0.5.0 // indirect
github.com/pierrec/lz4/v4 v4.1.17 // indirect
github.com/pressly/goose/v3 v3.11.0 // indirect
golang.org/x/net v0.9.0 // indirect
golang.org/x/sys v0.8.0 // indirect
@ -38,7 +34,6 @@ require (
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
github.com/segmentio/kafka-go v0.4.40
go.uber.org/automaxprocs v1.5.2 // indirect
golang.org/x/crypto v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect

File diff suppressed because it is too large Load Diff

View File

@ -1,55 +0,0 @@
package kafka
import (
"context"
"encoding/json"
"fmt"
"time"
kafka "github.com/segmentio/kafka-go"
)
type kafkaProducer struct {
*kafka.Conn
}
func (c *kafkaProducer) Close() error {
return c.Conn.Close()
}
func (c *kafkaProducer) SendMessageJSON(headers map[string]string, msg interface{}) (err error) {
var data []byte
if data, err = json.Marshal(msg); err != nil {
err = fmt.Errorf("could not marshal message into JSON: %w", err)
return
}
h := []kafka.Header{}
for k, v := range headers {
h = append(h, kafka.Header{
Key: k,
Value: []byte(v),
})
}
if _, err = c.WriteMessages(kafka.Message{
Time: time.Now().UTC(),
Headers: h,
Value: data,
}); err != nil {
return
}
return
}
func NewProducer(topic string) (*kafkaProducer, error) {
conn, err := kafka.DialLeader(context.Background(), "tcp", "kafka:9092", topic, 0)
if err != nil {
return nil, err
}
return &kafkaProducer{
Conn: conn,
}, nil
}

View File

@ -1,4 +1,4 @@
-- name: GetJobs :many
-- name: GetCompletedJobs :many
SELECT id,
startedTs,
completedTs,
@ -6,7 +6,31 @@ SELECT id,
auctionsFound,
errors
FROM runner.scrapejob
ORDER BY startedTs DESC;
WHERE completedts is not null OR NOW() >= startedts + (30 ||' minutes')::interval
ORDER BY startedTs DESC
OFFSET (sqlc.arg(page)::INTEGER * sqlc.arg(pageSize)::INTEGER)
LIMIT sqlc.arg(pageSize)::INTEGER;
-- name: GetActiveJobs :many
SELECT id,
startedTs,
completedTs,
targetSiteName,
auctionsFound,
errors
FROM runner.scrapejob
WHERE completedTs is null AND NOW() < startedTs + (30 ||' minutes')::interval
ORDER BY startedTs DESC
OFFSET (sqlc.arg(page)::INTEGER * sqlc.arg(pageSize)::INTEGER)
LIMIT sqlc.arg(pageSize)::INTEGER;
-- name: GetJobCounts :one
SELECT COUNT(*) AS total,
(SELECT COUNT(*) FROM runner.scrapejob
WHERE completedts is not null OR NOW() >= startedts + (30 ||' minutes')::interval) AS completed,
(SELECT COUNT(*) FROM runner.scrapejob
WHERE completedts is null AND NOW() < startedts + (30 ||' minutes')::interval) AS active
FROM runner.scrapejob;
-- name: GetJobByID :one
SELECT id,

View File

@ -64,22 +64,54 @@ func (db *PGRunnerStorage) CompleteScrapeJob(ctx context.Context, ID int, status
return
}
func (db *PGRunnerStorage) GetJobs(ctx context.Context) (results []domain.ScrapeJob, err error) {
var jobs []postgres.RunnerScrapejob
if jobs, err = db.Queries.GetJobs(ctx); err != nil {
func (db *PGRunnerStorage) GetJobs(ctx context.Context, page int32, limit int32) (out domain.GetJobsResult, err error) {
var completeJobs []postgres.RunnerScrapejob
if completeJobs, err = db.Queries.GetCompletedJobs(ctx, postgres.GetCompletedJobsParams{
Page: page,
Pagesize: limit,
}); err != nil {
err = fmt.Errorf("Couldn't get jobs from DB: %w", err)
return
}
for _, j := range jobs {
results = append(results, domain.ScrapeJob{
var activeJobs []postgres.RunnerScrapejob
if activeJobs, err = db.Queries.GetActiveJobs(ctx, postgres.GetActiveJobsParams{
Page: 0,
Pagesize: 64,
}); err != nil {
err = fmt.Errorf("Couldn't get jobs from DB: %w", err)
return
}
var jobCounts postgres.GetJobCountsRow
if jobCounts, err = db.Queries.GetJobCounts(ctx); err != nil {
err = fmt.Errorf("couldn't get total job count: %w", err)
return
}
out.Total = int(jobCounts.Total)
out.ActiveTotal = int(jobCounts.Active)
out.CompletedTotal = int(jobCounts.Completed)
out.Active = mapScrapeJob(activeJobs)
out.Complete = mapScrapeJob(completeJobs)
return
}
func mapScrapeJob(jobs []postgres.RunnerScrapejob) (result []domain.ScrapeJob) {
result = make([]domain.ScrapeJob, len(jobs))
for i, j := range jobs {
result[i] = domain.ScrapeJob{
ID: int(j.ID),
Started: j.Startedts,
Completed: j.Completedts.Time,
TargetSite: j.Targetsitename,
AuctionsFound: int(j.Auctionsfound),
Errors: j.Errors,
})
}
if j.Completedts.Valid {
result[i].Completed = j.Completedts.Time
}
}
return

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
}
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,
),
@ -199,26 +197,16 @@ func LAGetSaleInfo(ctx context.Context, catIDs LACatalogIDs) (results []catalog.
results = make([]catalog.Auction, len(apiResults.Data.Catalogs))
for idx, c := range apiResults.Data.Catalogs {
results[idx] = catalog.Auction{
Title: c.Title,
Description: c.Description,
Start: time.Unix(c.SaleStartTS, 0),
End: time.Unix(c.SaleStartTS, 0).Add(time.Hour * 8),
ItemCount: c.ItemCount,
Source: catalog.SourceDetail{
SiteURL: "https://www.liveauctioneers.com",
Name: "Live Auctioneers",
AuctionURL: fmt.Sprintf("https://www.liveauctioneers.com/catalog/%d", c.ID),
},
Address: catalog.AddressDetail{
CountryCode: c.Address.CountryCode,
City: c.Address.City,
State: c.Address.State,
Lat: c.Address.Lat,
Long: c.Address.Long,
},
Seller: catalog.SellerDetail{
ID: int(c.SellerID),
},
Title: c.Title,
Description: c.Description,
SourceSiteURL: "https://www.liveauctioneers.com",
SourceSiteName: "Live Auctioneers",
SourceURL: fmt.Sprintf("https://www.liveauctioneers.com/catalog/%d", c.ID),
Start: time.Unix(c.SaleStartTS, 0),
End: time.Unix(c.SaleStartTS, 0).Add(time.Hour * 8),
ItemCount: c.ItemCount,
Country: c.Address.CountryCode,
Province: c.Address.City,
}
}

Some files were not shown because too many files have changed in this diff Show More