Compare commits

..

24 Commits

Author SHA1 Message Date
Adam Veldhousen 3954a7a2c6
deleted some code
ci.vdhsn.com/push Build is failing Details
2023-06-11 23:03:33 -05:00
Adam Veldhousen 865a8c670d
cleanup
ci.vdhsn.com/push Build is failing Details
2023-06-11 22:53:58 -05:00
Adam Veldhousen bf6e10a856
consume from kafka 2023-06-11 22:51:34 -05:00
Adam Veldhousen ebe90899f0
if time is zero return nil
ci.vdhsn.com/push Build is failing Details
2023-06-11 21:25:42 -05:00
Adam Veldhousen 81f5921a40
added admin panel 2023-06-11 21:23:23 -05:00
Adam Veldhousen af796d16c8
runner publishes to kafka
ci.vdhsn.com/push Build is failing Details
2023-06-10 21:17:21 -05:00
Adam Veldhousen f576844b74
added kafka 2023-06-10 20:50:09 -05:00
Adam Veldhousen 67d76a30ca
bunch of updates before big interview prep
ci.vdhsn.com/push Build is failing Details
2023-06-10 18:59:31 -05:00
Adam Veldhousen bd31e9ec35
add drone ci
ci.vdhsn.com/push Build is failing Details
2023-06-02 18:47:17 -05:00
Adam Veldhousen eb4fe808d7
fails because browser cannot find /api/v1 2023-06-02 18:30:15 -05:00
Adam Veldhousen 4fcce1ccd9
fix up tiltfile 2023-06-02 17:19:36 -05:00
Adam Veldhousen 38563eb675
switch to secrets 2023-06-02 17:19:18 -05:00
Adam Veldhousen 7abc8666b9
Squashed commit of the following:
commit 98bff364a9
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Tue May 30 22:58:12 2023 -0500

    add deployment docs

commit cdcae78359
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Sat May 27 15:24:34 2023 -0500

    origin env

commit 49c01169d3
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Sat May 27 15:07:49 2023 -0500

    fix build for web-client

commit b4fd08f40f
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Fri May 26 22:33:32 2023 -0500

    update cat endpoint

commit 3edb03e297
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Fri May 26 22:31:01 2023 -0500

    add more configmaps

commit d011b29427
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Fri May 26 22:19:10 2023 -0500

    update configmaps

commit 1049be1bcc
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Fri May 26 21:49:23 2023 -0500

    rearrange

commit b82c24b339
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Fri May 26 21:27:17 2023 -0500

    tweaks

commit 2aa0960b00
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Fri May 26 21:20:49 2023 -0500

    encrypt just the pull secret and merge it in

commit 54261d6089
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Fri May 26 21:13:08 2023 -0500

    try to get kustomization to be happy

commit f09a42837b
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Fri May 26 21:09:41 2023 -0500

    encrypt kustomization.yaml

commit a5dd2c34b0
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Fri May 26 20:51:53 2023 -0500

    add sops and beta specific decryption key

commit 6a88c36646
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Fri May 26 20:18:14 2023 -0500

    attempt to use var replacement

commit 756330e79c
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Fri May 26 19:53:04 2023 -0500

    some lifecycle fixes for app and access control stuff, to get data flowing to the client again

commit 2e0e78ae28
Merge: ebfccca e945fc2
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Fri May 26 19:22:13 2023 -0500

    Merge remote-tracking branch 'origin/feat/mvp' into feat/mvp-deploy-beta

    * origin/feat/mvp:
      pagination and linking improvements
      use 1 table for catalog data + search
      re arrange some things

commit ebfcccabfe
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Fri May 26 19:08:27 2023 -0500

    add prod builds and migrate env to a namespaced configuration in k8s

commit e945fc2f79
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Fri May 26 01:41:44 2023 -0500

    pagination and linking improvements

commit 7970ff99b0
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Fri May 26 01:41:17 2023 -0500

    use 1 table for catalog data + search

commit 5221c50814
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Thu May 25 19:48:00 2023 -0500

    re arrange some things

commit 000ff9a711
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Wed May 24 22:57:23 2023 -0500

    cronjob that invokes runner

commit c4cebcea6d
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Wed May 24 18:46:12 2023 -0500

    refactor grpc dial into kernel, trying to figure out runner import slowness

commit d09e5aaf53
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Wed May 24 01:44:04 2023 -0500

    uses k8s to run frontend, has ingress
2023-05-30 23:04:04 -05:00
Adam Veldhousen e945fc2f79
pagination and linking improvements 2023-05-26 01:47:50 -05:00
Adam Veldhousen 7970ff99b0
use 1 table for catalog data + search 2023-05-26 01:41:17 -05:00
Adam Veldhousen 5221c50814
re arrange some things 2023-05-25 19:48:00 -05:00
Adam Veldhousen c4cebcea6d
refactor grpc dial into kernel, trying to figure out runner import slowness 2023-05-24 18:46:12 -05:00
Adam Veldhousen d09e5aaf53
uses k8s to run frontend, has ingress 2023-05-24 01:44:04 -05:00
Adam Veldhousen be6bae7dcf
fixed pagination 2023-05-21 21:15:48 -05:00
Adam Veldhousen fb95d41695 Web client (#5)
- [x] Basic site layout
- [x] Home page with initial search
- [x] Search bar
- [x] Pagination
- [x] Linking to source sites
- [ ] Seller info and linking to their sites
- [ ] Infinite scrolling

Co-authored-by: Adam Veldhousen <adamveld12@gmail.com>
Reviewed-on: #5
2023-05-20 06:45:31 +00:00
Adam Veldhousen d1c0369308
forgot migrations 🤦 2023-05-13 21:57:23 -05:00
Adam Veldhousen fa8516b26f Catalog service (#3)
DoD:

- [x] Runner service can post upcoming catalogs
- [x] Deduplication of catalogs
- [x] Endpoints for web client to load catalogs
- [x] http -> GRPC proxy service that web client can use
- [x] DB schema, migrations

Co-authored-by: Adam Veldhousen <adamveld12@gmail.com>
Reviewed-on: #3
2023-05-14 01:53:34 +00:00
Adam Veldhousen a20b132280
Squashed commit of the following:
commit 30d2938bd6
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Sat May 6 22:11:09 2023 -0500

    runner service finished

commit 8e33be359a
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Sat Apr 29 17:25:05 2023 -0500

    add error handling for runner main

commit 118d4ffcc6
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Sat Apr 29 17:24:28 2023 -0500

    refactor GRPC connection management

commit 852d0f4131
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Sat Apr 29 17:20:41 2023 -0500

    refactor tiltfile and kustomize for better integration

commit 73fe1eb1d7
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Thu Apr 27 20:31:18 2023 -0500

    use full gobin path for buf in make task

commit 96393815ee
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Thu Apr 20 23:14:17 2023 -0500

    runner runs

commit 18b9523c8b
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Wed Apr 19 21:04:50 2023 -0500

    working on scraping from LA

commit 2699f0c14d
Author: Adam Veldhousen <adamveld12@gmail.com>
Date:   Wed Apr 19 19:47:51 2023 -0500

    runner starts up and runs migrations
2023-05-06 22:12:12 -05:00
Adam Veldhousen 6cd624ced1
runner works, can run locally 2023-04-16 21:15:47 -05:00
118 changed files with 3903 additions and 4302 deletions

View File

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

View File

@ -8,11 +8,8 @@ GOBIN = $(shell go env GOPATH)/bin
################### ###################
.PHONY: setup .PHONY: setup
setup: $(GOBIN)/sqlc $(GOBIN)/buf setup: $(GOBIN)/sqlc $(GOBIN)/buf ./env/.age.txt
@awk '{ print $$1 }' .tool-versions | xargs -I {} asdf plugin add {} || true
@asdf install || true @asdf install || true
@cd ./src/web-client && npm i
@cd ./src/admin-client && npm i
.PHONY: gen .PHONY: gen
gen: $(GOBIN)/sqlc buf.lock gen: $(GOBIN)/sqlc buf.lock
@ -29,63 +26,49 @@ clobber:
KUBECONFIG=$(KUBECONFIG) ctlptl delete cluster kind-bh-local || true KUBECONFIG=$(KUBECONFIG) ctlptl delete cluster kind-bh-local || true
@rm -f $(KUBECONFIG) @rm -f $(KUBECONFIG)
.PHONY: secrets
secrets: ./env/.age.txt
.PHONY: acceptance-test .PHONY: acceptance-test
acceptance-test: acceptance-test:
@docker run -it --rm \ docker run -it --rm \
-v $(PWD)/src/$(SERVICE)/acceptance-tests.yml:/tests/tests.yml \ -v $(pwd)/src/acceptance-tests.yml:/tests/tests.yml \
-e "TEST_HOST=$(ORIGIN)" \ -e "TEST_HOST=bh.localhost" \
-e "TEST_ENV=local" \
nytimes/httptest nytimes/httptest
############################## ##############################
# Container Image Building # Container Image Building
# see ./doc/deployment.md for details
############################## ##############################
SERVICE = "catalog" SERVICE = "catalog"
ENV ?= "" ENV = ""
ORIGIN = "https://barretthousen.com"
BUILD_INITIATOR = "Development Machine" BUILD_INITIATOR = "Development Machine"
VERSION = $(shell git rev-parse --verify --short HEAD) VERSION = $(shell git rev-parse --verify --short HEAD)
GIT_REF = $(shell git rev-parse --verify HEAD) GIT_REF = $(shell git rev-parse --verify HEAD)
BUILD_DATE := $(shell date +%Y-%m-%d-%T) 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 .PHONY: build-client-image
build-client-image: build-client-image:
@docker build --target=production \ docker build --target=production \
--label 'com.barretthousen.service=$(SERVICE)' \ --label 'com.barretthousen.service=$(SERVICE)' \
--label 'com.barretthousen.version=$(VERSION)' \ --label 'com.barretthousen.version=$(VERSION)' \
--label 'com.barretthousen.git-ref=$(GIT_REF)' \ --label 'com.barretthousen.git-ref=$(GIT_REF)' \
--label 'com.barretthousen.build-date=$(BUILD_DATE)' \ --label 'com.barretthousen.build-date=$(BUILD_DATE)' \
--label 'com.barrethousen.builder=$(BUILD_INITIATOR)' \ --label 'com.barrethousen.builder=$(BUILD_INITIATOR)' \
--build-arg 'service=$(SERVICE)' \ --build-arg 'origin=$(ORIGIN)' \
-t barretthousen/client-$(SERVICE):$(VERSION) \ -t barretthousen/client-$(SERVICE):$(VERSION) \
-t git.vdhsn.com/barretthousen/client-$(SERVICE):$(VERSION) \ -t git.vdhsn.com/barretthousen/client-$(SERVICE):$(VERSION) \
-f ./src/Dockerfile.frontend ./src/$(SERVICE) -f ./src/$(SERVICE)/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 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 @[ ! -z $(ENV) ] && docker push git.vdhsn.com/barretthousen/client-$(SERVICE):$(ENV) || true
.PHONY: build-backend-image .PHONY: build-backend-image
build-backend-image: build-backend-image:
@docker build --target=production \ docker build --target=production \
--label 'com.barretthousen.service=$(SERVICE)' \ --label 'com.barretthousen.service=$(SERVICE)' \
--label 'com.barretthousen.version="$(VERSION)"' \ --label 'com.barretthousen.version="$(VERSION)"' \
--label 'com.barretthousen.git-ref="$(GIT_REF)"' \ --label 'com.barretthousen.git-ref="$(GIT_REF)"' \
@ -94,29 +77,14 @@ build-backend-image:
--build-arg 'service=$(SERVICE)' \ --build-arg 'service=$(SERVICE)' \
-t barretthousen/service-$(SERVICE):$(VERSION) \ -t barretthousen/service-$(SERVICE):$(VERSION) \
-t git.vdhsn.com/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 -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 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 @[ ! -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 buf.lock: $(GOBIN)/buf
@$(GOBIN)/buf mod update ./src @$(GOBIN)/buf mod update ./src
@ -138,4 +106,3 @@ age_identity=$(shell sops -d ./env/master.json)
@echo "# public key: $(shell echo '$(age_identity)' | jq -r '.public_key')" >> $@ @echo "# public key: $(shell echo '$(age_identity)' | jq -r '.public_key')" >> $@
@echo "$(shell echo '$(age_identity)' | jq -r '.private_key')" >> $@ @echo "$(shell echo '$(age_identity)' | jq -r '.private_key')" >> $@
@echo "$@ created!" @echo "$@ created!"
@echo "export SOPS_AGE_KEY_FILE=$(PWD)/env/.age.txt"

View File

@ -1,105 +1,83 @@
# [Barretthousen](https://barretthousen.com) # [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. Search and get alerts for items across the most popular auction sites.
Built with microservice architecture and various fancy tools for learning purposes Built with microservice architecture, for learning purposes
### Links ### Links
- [Keybase Team Chat](keybase://team/barretthousen) - [Keybase Team Chat](keybase://team/barretthousen)
- [Source](https://git.vdhsn.com/Barretthousen/barretthousen) - [Source](https://git.vdhsn.com/Barretthousen/barretthousen)
- [Auction terms glossary](https://auctionsneapolitan.com/Auction-Terms-Glossary.html)
- [User's Site](https://barretthousen.com) - [User's Site](https://barretthousen.com)
- [Admin Panel](https://admin.barretthousen.com) - [Admin Panel](https://admin.barretthousen.com)
- [(Staging) User's Site](https://beta.barretthousen.com)
- [(Staging) Admin Panel](https://beta.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
## Technology ## Technology
This project is for fun and learning so there are a lot of tools at play: For Users
- [Prod](https://barretthousen.com)
- [Staging](https://beta.barretthousen.com)
Infra For Admins
- [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]():
Backend - [Prod](https://beta.barretthousen.com)
- [Go]() - [Staging](https://admin.beta.barretthousen.com)
- [goose](): Go SQL db migration tool - [Architecture Diagram (use diagrams.net)](./doc/Barretthousen_mvp.drawio)
- [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]():
Frontend ![Arcchitecure Diagram MVP](./doc/Barretthousen_mvp.drawio.svg)
- [Typescript]():
- [SvelteKit](https://kit.svelte.dev/):
- [TailwindCSS]():
- [Diagram (use diagrams.net)](./doc/bh_design.drawio)
![Diagram](./doc/bh_design_overview.svg)
### Services ### Services
> [Auction terms glossary](https://auctionsneapolitan.com/Auction-Terms-Glossary.html) - Web-client
- [Web-client](./src/web-client/)
Frontend site that users access to browse upcoming auctions and subscribe for updates to their searches Frontend site that users access to browse upcoming auctions and subscribe for updates to their searches
- [Admin-client](./src/admin-client/) - Catalog
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. API for searching upcoming auctions.
- [Runner](./src/runner/) - Ringman
Manages email subscriptions and sending emails to users about upcoming auctions.
- Runner
Scrapes sites for upcoming auctions. Scrapes sites for upcoming auctions.
Transforms the data into a suitable shape and stores it for use by the frontend. Transforms the data into a suitable shape and stores it for use by the frontend.
- [Ringman](./src/ringman/) - Auctioneer
Manages email subscriptions and sending emails to users about upcoming auctions. Enables site administrators to kicks off scrape and email jobs in Runner and Ringman, and view status on current/past jobs.
- [Proxy-web](./src/proxy-web/) - BHDB
Proxies JSON HTTP API requests for the web client to GRPC based services downstream Monolithic postgres datbabase tying it all together
- [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)
Everything you need is in the makefile: Install `asdf` and run `hack/asdf_plugin_setup`
```sh ```sh
# install asdf tools, sqlc, buf # install asdf tools, build tooling, encryption key for sops
make setup make setup
# generate protobufs and sql boilerplate # generate protobufs and sql boilerplate
make gen make gen
# spin up a k8s cluster, build and deploy services locally w/ hot reloading - be patient this takes a few minutes first run # spin up a k8s cluster, build and deploy services locally w/ hot reloading
make dev make dev
# build production docker images for the backend microservices, optionally push to the respective env # build production docker images for the backend microservices, optionally push to the respective env
make build-backend-image SERVICE=[catalog, runner, proxy-web, proxy-admin] [ENV=[beta, prod]] make build-backend-image SERVICE=[catalog, runner, proxy-client, proxy-admin] [ENV=[beta, prod]]
# build client docker image for web frontends, optionally push to the respective env # build client docker image for web frontends, optionally push to the respective env
make build-client-image SERVICE=[web-client, admin-client] [ENV=[beta, prod]] make build-client-image SERVICE=[web-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://helm_resource', 'helm_resource', 'helm_repo')
load('ext://deployment', 'deployment_create') load('ext://deployment', 'deployment_create')
load('ext://restart_process', 'docker_build_with_restart') 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('bitnami', 'https://charts.bitnami.com/bitnami', labels=["9-repos"])
helm_repo('traefik', 'https://traefik.github.io/charts', 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('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( helm_resource(
'ingress', 'ingress',
@ -52,16 +52,55 @@ helm_resource(
labels=["9-data"] 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']): 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) basepath = './src/{}-client'.format(service)
docker_build( docker_build(
ref='barretthousen/client-{}-client'.format(service), ref='barretthousen/client-{}-client'.format(service),
context=basepath, context=basepath,
dockerfile='./src/Dockerfile.frontend'.format(service), dockerfile=basepath +'/Dockerfile.dev-frontend'.format(service),
target='development', target='development',
build_args={
"service": '{}-client'.format(service)
},
entrypoint='vite dev --port=80 --host=0.0.0.0 --strictPort --logLevel info', entrypoint='vite dev --port=80 --host=0.0.0.0 --strictPort --logLevel info',
live_update=[ live_update=[
sync(basepath + '/src', '/opt/{}-client/src'.format(service)), sync(basepath + '/src', '/opt/{}-client/src'.format(service)),
@ -82,6 +121,7 @@ def bh_backend_service(service="", port_forwards=[], migrateDB=False, devMode=Tr
'{}-go-compile'.format(service), '{}-go-compile'.format(service),
'CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -gcflags "all=-N -l" -o .bin/{}-debug ./src/{}'.format(service, 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'], deps=['./src/{}'.format(service), './src/lib'],
resource_deps=deps,
labels=['3-compilation'] labels=['3-compilation']
) )
@ -118,7 +158,7 @@ def bh_backend_service(service="", port_forwards=[], migrateDB=False, devMode=Tr
'./src/runner', './src/runner',
'./src/catalog', './src/catalog',
'./src/proxy-admin', './src/proxy-admin',
'./src/proxy-web', './src/proxy-client',
'./src/lib' './src/lib'
], ],
live_update=[ live_update=[
@ -145,56 +185,43 @@ 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=[ bh_backend_service(service="runner", migrateDB=True, port_forwards=[
port_forward(2346, 2345, name='Delve port') port_forward(2345, name='Delve port')
]) ], deps=["postgres", "kafka"])
bh_backend_service(service="catalog", migrateDB=True, port_forwards=[ bh_backend_service(service="catalog", migrateDB=True, port_forwards=[
port_forward(2347, 2345, name='Delve port') port_forward(2346, 2345, name='Delve port')
]) ], deps=["postgres", "kafka"])
bh_backend_service(service="proxy-admin", port_forwards=[ bh_backend_service(service="proxy-admin", port_forwards=[
port_forward(8082, 80, name="HTTP API @ localhost:8082") port_forward(8082, 80, name="HTTP API @ localhost:8082")
], deps=['ingress']) ], deps=['ingress'])
bh_backend_service(service="proxy-web", port_forwards=[ bh_backend_service(service="proxy-client", port_forwards=[
port_forward(8081, 80, name="HTTP API @ localhost:8081") port_forward(8081, 80, name="HTTP API @ localhost:8081")
], deps=['ingress']) ], deps=['ingress'])
bh_client(service='web', deps=["proxy-web-local"]) bh_client(service='web')
bh_client(service='admin', deps=["proxy-admin-local"]) bh_client(service='admin')
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

@ -0,0 +1,637 @@
<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

After

Width:  |  Height:  |  Size: 183 KiB

View File

@ -1,177 +0,0 @@
<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

Before

Width:  |  Height:  |  Size: 36 KiB

View File

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

View File

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

View File

@ -1,72 +0,0 @@
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,7 +46,6 @@ spec:
ports: ports:
- port: 5001 - port: 5001
targetPort: 5001 targetPort: 5001
name: grpc
--- ---
apiVersion: v1 apiVersion: v1
kind: Secret kind: Secret

View File

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

View File

@ -4,24 +4,5 @@ metadata:
name: bh-registry name: bh-registry
type: kubernetes.io/dockerconfigjson type: kubernetes.io/dockerconfigjson
stringData: stringData:
.dockerconfigjson: ENC[AES256_GCM,data:bfqlh7Vy3HDYFtgv56xO+8lXOLO9bQWRC16N8hAzv6xJaIN6CmXDwFzoLoGWPrP9s/o446tuOEJEylf5z/ITnLtdGJgMsN13Xk7OiF9B2unV8yOOrzt6U6R2s5cFpbSL3tAHQmDKHxRrzbvyV2J3magen7oHQWbkwkOQq7FqV/k7wFly+bei1u+YLJ9hq798Xa5HG9j4LsVWi5izKt1BBss2xFlo3yzEFqNmQ+AzcUN1uK1xwStplK4IKC36rewONDS+yyqj830LLShb,iv:qDwYxBqK+ZamBcWEuF+UEfW8gLFROagaBqVAc1tCjUI=,tag:OYhChcvisxP0r3kQ4hq4SA==,type:str] .dockerconfigjson: |
sops: { "auths": {} }
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,11 +1,10 @@
resources: resources:
- ./image-pull-secret.yaml - ./image-pull-secret.yaml
- ./namespace.yaml - ./namespace.yaml
- ./auth-deployment.yaml
- ./catalog-deployment.yaml - ./catalog-deployment.yaml
- ./runner-deployment.yaml - ./runner-deployment.yaml
- ./proxy-admin-deployment.yaml - ./proxy-admin-deployment.yaml
- ./proxy-web-deployment.yaml - ./proxy-client-deployment.yaml
- ./sync-cronjob.yaml - ./sync-cronjob.yaml
- ./web-client-deployment.yaml - ./web-client-deployment.yaml
- ./admin-client-deployment.yaml - ./admin-client-deployment.yaml

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,12 +1,11 @@
apiVersion: v1 apiVersion: v1
kind: Secret kind: Secret
metadata: metadata:
name: proxy-web-config name: proxy-client-config
stringData: stringData:
config.yaml: | config.yaml: |
log_level: 2 log_level: 2
port: 80 port: 80
access_control_allow_origin: "beta.barretthousen.com" access_control_allow_origin: "beta.barretthousen.com"
endpoints: endpoints:
auth: auth-beta:5001
catalog: catalog-beta:5001 catalog: catalog-beta:5001

View File

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

View File

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

View File

@ -8,16 +8,18 @@ nameSuffix: -local
namespace: barretthousen-local namespace: barretthousen-local
patchesStrategicMerge: patchesStrategicMerge:
- debug-auth.yaml
- debug-catalog.yaml - debug-catalog.yaml
- debug-runner.yaml - debug-runner.yaml
- runner-secret.yaml
- sync-cronjob.yaml - sync-cronjob.yaml
- image-pull-secret.yaml
- proxy-admin-secret.yaml
- proxy-web-secret.yaml
patches: patches:
- target:
kind: CronJob
name: runner-sync
patch: |-
- op: replace
path: /spec/schedule
value: "* * * * *"
- target: - target:
kind: Ingress kind: Ingress
name: admin name: admin
@ -32,23 +34,3 @@ patches:
- op: replace - op: replace
path: /spec/rules/0/host path: /spec/rules/0/host
value: bh.localhost 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,11 +0,0 @@
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,8 +1,8 @@
apiVersion: v1 apiVersion: v1
kind: Secret kind: ConfigMap
metadata: metadata:
name: auth-config name: catalog-config
stringData: data:
config.yaml: | config.yaml: |
log_level: 2 log_level: 2
port: 5001 port: 5001
@ -11,8 +11,8 @@ stringData:
port: 5432 port: 5432
host: bh-db host: bh-db
name: bh name: bh
user: auth-service user: catalog-service
password: auth-service password: catalog-service
db_migrate: db_migrate:
scheme: postgres scheme: postgres
port: 5432 port: 5432

View File

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

View File

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

11
env/prod/proxy-client-configmap.yaml vendored Normal file
View File

@ -0,0 +1,11 @@
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

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

View File

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

View File

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

View File

@ -1,225 +1,29 @@
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/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/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/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/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/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/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/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/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/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/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/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/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/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/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/elastic/elastic-transport-go/v8 v8.0.0-alpha/go.mod h1:87Tcz8IVNe6rVSLdBux1o/PEItLtyabHU3naC7IoqKI=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/elastic/go-elasticsearch/v8 v8.0.0/go.mod h1:8NCWP26meGbncX+R9sxo2JD8IqBjRTuS7yXMstHpd40=
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/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/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/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
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/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= 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= 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/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 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 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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/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= 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=

View File

@ -1,64 +0,0 @@
#!/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 $@

View File

@ -1,26 +0,0 @@
#!/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 $@;

View File

@ -1,19 +0,0 @@
#!/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

View File

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

View File

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

View File

@ -1,55 +0,0 @@
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,11 +1,15 @@
FROM git.vdhsn.com/barretthousen/tools:v1 as prod-builder 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
WORKDIR /go/src/ WORKDIR /go/src/
COPY . /go/src/ RUN /go/bin/sqlc generate -f /go/src/sqlc.yaml
RUN /go/bin/buf mod update /go/src && /go/bin/buf generate
RUN /go/bin/sqlc generate -f /go/src/sqlc.yaml \
&& /go/bin/buf mod update /go/src && /go/bin/buf generate
ARG service ARG service
@ -18,7 +22,7 @@ FROM alpine as production
ARG service ARG service
COPY --from=prod-builder /opt/${service} /opt/${service} COPY --from=builder /opt/${service} /opt/${service}
ENV SERVICE=${SERVICE} ENV SERVICE=${SERVICE}

View File

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

View File

@ -0,0 +1,9 @@
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

@ -0,0 +1,42 @@
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,12 +1,38 @@
# Admin client # create-svelte
Admin panel to drive backend "administrative" functionality Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
Uses [SvelteKit](), [TailwindCSS](), [Typescript (poorly)]() ## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
## Development ```bash
# create a new project in the current directory
npm create svelte@latest
Follow the [Contributing section](../../README.md#contributing) to get the environment stood up locally. # create a new project in my-app
npm create svelte@latest my-app
```
Browse to [http://admin.localhost:8000](http://admin.localhost:8000). ## 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.

View File

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

View File

@ -1,95 +0,0 @@
<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

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

View File

@ -1,119 +0,0 @@
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,34 +1,5 @@
<script lang="ts"> <script lang="ts">
import '../app.css'; 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> </script>
<svelte:head> <svelte:head>
@ -55,19 +26,8 @@
<li class="flex grow justify-center"> <li class="flex grow justify-center">
<span class="flex grow" style="max-width: 75%;"> Admin Stuff </span> <span class="flex grow" style="max-width: 75%;"> Admin Stuff </span>
</li> </li>
<li class="pr-5"> <li class="px-6">
<!-- <span>I want email alerts!</span> --> <!-- <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> </li>
</ul> </ul>
</nav> </nav>

View File

@ -1,96 +1,61 @@
<script lang="ts"> <script lang="ts">
import type { PageData } from './$types'; import type { PageData } from './$types';
import { fade } from 'svelte/transition';
import { invalidateAll } from '$app/navigation';
import StartScrapeForm from '$lib/StartScrapeForm.svelte'; import StartScrapeForm from '$lib/StartScrapeForm.svelte';
import ScrapeJobResult from '$lib/ScrapeJobResult.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; export let data: PageData;
$: activeJobCount = data.active.length; $: completedJobs = data.jobs.filter(({ completedTs }) => completedTs !== null);
$: activeJobs = data.jobs.filter(({ completedTs }) => completedTs === null);
let disableSync = false; $: activeJobCount = activeJobs.length;
$: completedJobCount = completedJobs.length;
var intervalId: any;
onMount(() => {
intervalId = setInterval(invalidateAll, 2000);
});
onDestroy(() => clearInterval(intervalId));
async function onScrape({ detail }) { async function onScrape({ detail }) {
disableSync = true;
try { try {
const response = await fetch( const response = await fetch(
new Request('/api/v1/sync', { new Request('/api/v1/sync', {
method: 'PUT', method: 'PUT',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json'
'bh-session-id': getSession()?.sessionId
}, },
body: JSON.stringify({ targetSite: detail.target }) body: JSON.stringify({ targetSite: detail.target })
}) })
); );
} catch (error) {
} finally { const scrapeJob = await response.json();
disableSync = false; data.jobs.push(scrapeJob);
} console.log(scrapeJob);
} catch (error) {}
} }
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> </script>
<section class="flex w-full flex-col justify-center" in:fade out:fade> <section class="flex w-full flex-col justify-center" in:fade out:fade>
<section in:fade> <h1 class="text-2xl mb-8">Sync Status</h1>
<h1 class="text-2xl pb-5">Sync Status: {data.total} jobs</h1> <div class="mb-8">
</section> <StartScrapeForm on:scrape={onScrape} />
<section class="pb-5"> </div>
<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> <section>
<Pager page={data.page} itemCount={data.total} pageSize={limit} createUrl={buildQueryString} /> <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>
</section> </section>
</section> </section>

View File

@ -1,9 +1,9 @@
import { browser } from '$app/environment'; import { browser } from '$app/environment';
import type { PageLoad } from './$types'; import type { PageLoad } from './$types';
import { env } from '$env/dynamic/public';
import { getSession } from '$lib/state';
const API_HOST = `${browser ? '' : env.BH_CLIENT_INTERNAL_API_HOST}/api/v1`; // TODO: change to env var
const API_HOST = `${browser ? '' : 'http://proxy-admin-local'}/api/v1`;
interface Job { interface Job {
id: Number; id: Number;
@ -15,89 +15,18 @@ interface Job {
} }
interface ScrapeStatusPageData { interface ScrapeStatusPageData {
page: number; jobs: Job[]
total: number;
activeTotal: number;
completeTotal: number;
limit: number;
active: Job[];
complete: Job[];
} }
export const load = (async ({ fetch, url }): Promise<ScrapeStatusPageData> => { export const load = (async ({ fetch, url }): Promise<ScrapeStatusPageData> => {
const searchParams = new SearchParameters(url);
const limit = searchParams.getLimit();
console.log(getSession());
try { try {
const response = await fetch( const response = await fetch(API_HOST + `/sync`);
new Request(API_HOST + `/sync${searchParams.toQueryString()}`, const {results } = await response.json();
{
method: 'GET',
headers: {
'bh-session-id': getSession()?.sessionId,
}
}
)
);
const { active, complete, activeTotal, completeTotal, total, page } = await response.json();
return { return {
active, jobs: results || []
complete,
activeTotal,
completeTotal,
total,
page,
limit
}; };
} catch (e) { } catch (e) {
console.log(e); console.log(e);
return { return { jobs:[] };
activeTotal: 0,
completeTotal: 0,
active: [],
complete: [],
total: 0,
page: 1,
limit
};
} }
}) satisfies PageLoad; }) 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: { kit: {
env: { env: {
publicPrefix: 'BH_CLIENT' publicPrefix: 'BH_WEB_CLIENT'
}, },
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. // 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. // If your environment is not supported or you settled on a specific environment, switch out the adapter.

View File

@ -1,25 +0,0 @@
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]

View File

@ -1,64 +0,0 @@
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

@ -1,78 +0,0 @@
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;
}

View File

@ -1,42 +0,0 @@
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

View File

@ -1,262 +0,0 @@
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

@ -1,111 +0,0 @@
-- +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

@ -1,46 +0,0 @@
-- 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

@ -1,152 +0,0 @@
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

@ -1,187 +0,0 @@
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

@ -1,166 +0,0 @@
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

@ -1,32 +0,0 @@
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

@ -1,70 +0,0 @@
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

@ -1,108 +0,0 @@
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
}

View File

@ -1,100 +0,0 @@
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,7 +1,16 @@
tests: tests:
- description: "Search for top" - description: "trigger scrape"
request: request:
path: "/api/v1/upcoming" path: "/v1/findNewUpcoming"
method: "GET" 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}"
response: response:
statusCodes: [200] statusCodes: [200]

View File

@ -1,64 +1,38 @@
package api package api
import ( import (
"context"
"time" "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 { type Auction struct {
Fingerprint string `json:"fingerprint,omitempty"` ID int `json:"id,omitempty"`
Title string `json:"title,omitempty"` Fingerprint string `json:"fingerprint,omitempty"`
Description string `json:"description,omitempty"` Title string `json:"title,omitempty"`
SourceSiteURL string `json:"source_site_url,omitempty"` Description string `json:"description,omitempty"`
SourceSiteName string `json:"source_site_name,omitempty"` ItemCount int `json:"itemCount,omitempty"`
SourceURL string `json:"source_url,omitempty"` Start time.Time `json:"start,omitempty"`
Country string `json:"country,omitempty"` End time.Time `json:"end,omitempty"`
Province string `json:"province,omitempty"` Source SourceDetail `json:"source"`
ItemCount int `json:"itemCount,omitempty"` Seller SellerDetail `json:"seller"`
Start time.Time `json:"start,omitempty"` Address AddressDetail `json:"address"`
End time.Time `json:"end,omitempty"`
} }
type AuctionCreatedEvent struct { type SourceDetail struct {
Fingerprint string Name string `json:"name"`
ID int AuctionURL string `json:"auctionUrl"`
Duplicate bool SiteURL string `json:"siteURL"`
} }
func (css *CatalogServiceClient) UpdateUpcomingAuction(ctx context.Context, a Auction) (AuctionCreatedEvent, error) { type SellerDetail struct {
ac, err := css.ImportAuction(ctx, &capi.ImportAuctionMessage{ ID int `json:"id"`
Items: int32(a.ItemCount), Name string `json:"name"`
Start: timestamppb.New(a.Start), SiteURL string `json:"siteUrl"`
End: timestamppb.New(a.End), }
Title: a.Title,
Description: a.Description, type AddressDetail struct {
SourceSiteURL: a.SourceSiteURL, CountryCode string `json:"country"`
SourceSiteName: a.SourceSiteName, Lat float64 `json:"lat"`
SourceURL: a.SourceURL, Long float64 `json:"lng"`
Country: a.Country, State string `json:"state"`
Province: a.Province, City string `json:"city"`
})
if err != nil {
return AuctionCreatedEvent{}, err
}
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 { message AuctionSearchCriteria {

3
src/catalog/config.yaml Normal file
View File

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

View File

@ -18,7 +18,7 @@ require (
github.com/joho/godotenv v1.5.1 // indirect github.com/joho/godotenv v1.5.1 // indirect
github.com/pressly/goose/v3 v3.11.0 // indirect github.com/pressly/goose/v3 v3.11.0 // indirect
golang.org/x/net v0.9.0 // indirect golang.org/x/net v0.9.0 // indirect
golang.org/x/sync v0.2.0 // indirect golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
golang.org/x/sys v0.8.0 // indirect golang.org/x/sys v0.8.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // 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 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= 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-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.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-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-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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

View File

@ -0,0 +1,58 @@
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

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

View File

@ -5,7 +5,7 @@ go 1.19
require ( require (
github.com/jackc/pgx/v4 v4.18.1 github.com/jackc/pgx/v4 v4.18.1
go.uber.org/automaxprocs v1.5.2 go.uber.org/automaxprocs v1.5.2
golang.org/x/sync v0.2.0 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
google.golang.org/grpc v1.55.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 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= 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-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.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-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-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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

View File

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

View File

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

View File

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

View File

@ -143,7 +143,6 @@ 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/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/automaxprocs v1.5.2 h1:2LxUOGiR3O6tw8ui5sZa2LAaHnsviZdVOUZw4fvbnME= go.uber.org/automaxprocs v1.5.2 h1:2LxUOGiR3O6tw8ui5sZa2LAaHnsviZdVOUZw4fvbnME=
go.uber.org/automaxprocs v1.5.2/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= 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.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= 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/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=

View File

@ -4,127 +4,53 @@ import (
"context" "context"
"fmt" "fmt"
"net/http" "net/http"
"strings"
"time" "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" "git.vdhsn.com/barretthousen/barretthousen/src/lib/kernel"
api "git.vdhsn.com/barretthousen/barretthousen/src/runner/api/grpc" api "git.vdhsn.com/barretthousen/barretthousen/src/runner/api/grpc"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"go.uber.org/dig"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/backoff" "google.golang.org/grpc/backoff"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
) )
type ProxyAdminApp struct { type ProxyAdminApp struct {
LogLevel kernel.LogLevel `yaml:"log_level" yaml-default:"0"` LogLevel kernel.LogLevel `yaml:"log_level" env:"BH_LOG_LEVEL" env-default:"0" yaml-default:"0"`
Port int `yaml:"port" ` Port int `yaml:"port" env:"PROXY_ADMIN_PORT"`
Endpoints struct { Endpoints struct {
Runner string `yaml:"runner" ` Runner string `yaml:"runner" env:"RUNNER_ENDPOINT"`
Auth string `yaml:"auth"` } `yaml:"endpoints" env:"PROXY_ADMIN_SERVICES"`
} `yaml:"endpoints"`
}
type AuthService interface {
CheckSession(context.Context, authApi.CheckSessionParams) (authApi.Account, error)
} }
func (app *ProxyAdminApp) Start(ctx context.Context) error { func (app *ProxyAdminApp) Start(ctx context.Context) error {
ioc := dig.New() grpcMux := runtime.NewServeMux()
err := api.RegisterRunnerHandlerFromEndpoint(ctx, grpcMux, app.Endpoints.Runner, []grpc.DialOption{
var err error grpc.WithTransportCredentials(insecure.NewCredentials()),
if err = ioc.Provide(func() *runtime.ServeMux { grpc.WithConnectParams(grpc.ConnectParams{
return runtime.NewServeMux() Backoff: backoff.Config{
}); err != nil { MaxDelay: time.Second * 3,
},
MinConnectTimeout: time.Second,
}),
})
if err != nil {
return err return err
} }
if err = ioc.Provide(func() (grpc.ClientConnInterface, error) { kernel.TraceLog.Printf("%+v", app)
return kernel.DialGRPC(app.Endpoints.Auth)
}); err != nil { httpServer := &http.Server{
return err 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(conn grpc.ClientConnInterface) AuthService { kernel.InfoLog.Printf("Starting HTTP proxy @ %q", httpServer.Addr)
return authApi.NewAuthServiceClient(conn)
}); err != nil {
return err
}
if err = ioc.Invoke(func(grpcMux *runtime.ServeMux, authClient AuthService) error { return httpServer.ListenAndServe()
// 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) { func (app *ProxyAdminApp) OnStop(ctx context.Context) {

View File

@ -1,4 +1,4 @@
module git.vdhsn.com/barretthousen/barretthousen/src/proxy-web module git.vdhsn.com/barretthousen/barretthousen/src/proxy-client
go 1.19 go 1.19
@ -27,7 +27,7 @@ require (
go.uber.org/automaxprocs v1.5.2 // indirect go.uber.org/automaxprocs v1.5.2 // indirect
golang.org/x/crypto v0.8.0 // indirect golang.org/x/crypto v0.8.0 // indirect
golang.org/x/net v0.9.0 // indirect golang.org/x/net v0.9.0 // indirect
golang.org/x/sync v0.2.0 // indirect golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
golang.org/x/sys v0.8.0 // indirect golang.org/x/sys v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect golang.org/x/text v0.9.0 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
@ -39,5 +39,3 @@ require (
replace git.vdhsn.com/barretthousen/barretthousen/src/lib v1.0.0 => ../lib 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/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 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= 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-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.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-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-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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

View File

@ -4,10 +4,8 @@ import (
"context" "context"
"fmt" "fmt"
"net/http" "net/http"
"strings"
"time" "time"
auth_api "git.vdhsn.com/barretthousen/barretthousen/src/auth/api/grpc"
api "git.vdhsn.com/barretthousen/barretthousen/src/catalog/api/grpc" api "git.vdhsn.com/barretthousen/barretthousen/src/catalog/api/grpc"
"git.vdhsn.com/barretthousen/barretthousen/src/lib/kernel" "git.vdhsn.com/barretthousen/barretthousen/src/lib/kernel"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
@ -17,17 +15,17 @@ import (
) )
type ProxyClientApp struct { type ProxyClientApp struct {
LogLevel kernel.LogLevel `yaml:"log_level" yaml-default:"0"` LogLevel kernel.LogLevel `yaml:"log_level" env:"BH_LOG_LEVEL" env-default:"0" yaml-default:"0"`
Port int `yaml:"port"` Port int `yaml:"port" env:"PROXY_CLIENT_PORT"`
Endpoints struct { AccessControlAllowOrigin string `yaml:"access_control_allow_origin", env:"PROXY_CLIENT_CORS_ORIGIN`
Catalog string `yaml:"catalog"` Endpoints struct {
Auth string `yaml:"auth"` Catalog string `yaml:"catalog" env:"CATALOG_ENDPOINT"`
} `yaml:"endpoints" env:"PROXY_CLIENT_SERVICES"` } `yaml:"endpoints" env:"PROXY_CLIENT_SERVICES"`
} }
func (app *ProxyClientApp) Start(ctx context.Context) error { func (app *ProxyClientApp) Start(ctx context.Context) error {
grpcMux := runtime.NewServeMux() grpcMux := runtime.NewServeMux()
grpcOpts := []grpc.DialOption{ err := api.RegisterCatalogHandlerFromEndpoint(ctx, grpcMux, app.Endpoints.Catalog, []grpc.DialOption{
grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithConnectParams(grpc.ConnectParams{ grpc.WithConnectParams(grpc.ConnectParams{
Backoff: backoff.Config{ Backoff: backoff.Config{
@ -35,13 +33,8 @@ func (app *ProxyClientApp) Start(ctx context.Context) error {
}, },
MinConnectTimeout: time.Second, 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 return err
} }
@ -51,12 +44,9 @@ func (app *ProxyClientApp) Start(ctx context.Context) error {
Addr: fmt.Sprintf("0.0.0.0:%d", app.Port), Addr: fmt.Sprintf("0.0.0.0:%d", app.Port),
ReadHeaderTimeout: time.Second, ReadHeaderTimeout: time.Second,
Handler: http.StripPrefix("/api", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 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")) 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
if strings.HasPrefix(r.Host, "proxy-") { w.Header().Set("Access-Control-Allow-Origin", app.AccessControlAllowOrigin)
w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
}
grpcMux.ServeHTTP(w, r) grpcMux.ServeHTTP(w, r)
})), })),
} }

View File

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

View File

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

21
src/runner/config.yaml Normal file
View File

@ -0,0 +1,21 @@
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,6 +18,10 @@ require (
github.com/ilyakaznacheev/cleanenv v1.4.2 // indirect github.com/ilyakaznacheev/cleanenv v1.4.2 // indirect
github.com/jackc/puddle v1.3.0 // indirect github.com/jackc/puddle v1.3.0 // indirect
github.com/joho/godotenv v1.5.1 // 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 github.com/pressly/goose/v3 v3.11.0 // indirect
golang.org/x/net v0.9.0 // indirect golang.org/x/net v0.9.0 // indirect
golang.org/x/sys v0.8.0 // indirect golang.org/x/sys v0.8.0 // indirect
@ -34,6 +38,7 @@ require (
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgtype v1.14.0 // indirect github.com/jackc/pgtype v1.14.0 // indirect
github.com/jackc/pgx/v4 v4.18.1 github.com/jackc/pgx/v4 v4.18.1
github.com/segmentio/kafka-go v0.4.40
go.uber.org/automaxprocs v1.5.2 // indirect go.uber.org/automaxprocs v1.5.2 // indirect
golang.org/x/crypto v0.8.0 // indirect golang.org/x/crypto v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect golang.org/x/text v0.9.0 // indirect

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,55 @@
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: GetCompletedJobs :many -- name: GetJobs :many
SELECT id, SELECT id,
startedTs, startedTs,
completedTs, completedTs,
@ -6,31 +6,7 @@ SELECT id,
auctionsFound, auctionsFound,
errors errors
FROM runner.scrapejob FROM runner.scrapejob
WHERE completedts is not null OR NOW() >= startedts + (30 ||' minutes')::interval ORDER BY startedTs DESC;
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 -- name: GetJobByID :one
SELECT id, SELECT id,

View File

@ -64,54 +64,22 @@ func (db *PGRunnerStorage) CompleteScrapeJob(ctx context.Context, ID int, status
return return
} }
func (db *PGRunnerStorage) GetJobs(ctx context.Context, page int32, limit int32) (out domain.GetJobsResult, err error) { func (db *PGRunnerStorage) GetJobs(ctx context.Context) (results []domain.ScrapeJob, err error) {
var completeJobs []postgres.RunnerScrapejob var jobs []postgres.RunnerScrapejob
if completeJobs, err = db.Queries.GetCompletedJobs(ctx, postgres.GetCompletedJobsParams{ if jobs, err = db.Queries.GetJobs(ctx); err != nil {
Page: page,
Pagesize: limit,
}); err != nil {
err = fmt.Errorf("Couldn't get jobs from DB: %w", err) err = fmt.Errorf("Couldn't get jobs from DB: %w", err)
return return
} }
var activeJobs []postgres.RunnerScrapejob for _, j := range jobs {
if activeJobs, err = db.Queries.GetActiveJobs(ctx, postgres.GetActiveJobsParams{ results = append(results, domain.ScrapeJob{
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), ID: int(j.ID),
Started: j.Startedts, Started: j.Startedts,
Completed: j.Completedts.Time,
TargetSite: j.Targetsitename, TargetSite: j.Targetsitename,
AuctionsFound: int(j.Auctionsfound), AuctionsFound: int(j.Auctionsfound),
Errors: j.Errors, Errors: j.Errors,
} })
if j.Completedts.Valid {
result[i].Completed = j.Completedts.Time
}
} }
return return

View File

@ -1,158 +0,0 @@
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

@ -1,44 +0,0 @@
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,13 +71,15 @@ type GetUpcomingSaleIDsInput struct {
} }
func LAGetUpcomingSaleIDs(ctx context.Context, in GetUpcomingSaleIDsInput) (ids LACatalogIDs, total int, err error) { func LAGetUpcomingSaleIDs(ctx context.Context, in GetUpcomingSaleIDsInput) (ids LACatalogIDs, total int, err error) {
in.Limit = 128 if in.Limit == 0 {
in.Limit = 128
}
req, _ := http.NewRequestWithContext( req, _ := http.NewRequestWithContext(
ctx, ctx,
http.MethodGet, http.MethodGet,
fmt.Sprintf( fmt.Sprintf(
"https://search-party-prod.liveauctioneers.com/search/catalogsearch?client_version=5.0&client=web&offset=300&sort=saleStart&page=%d&pageSize=%d&", "https://search-party-prod.liveauctioneers.com/search/catalogsearch?page=%d&sort=saleStart&pageSize=%d",
in.Page, in.Page,
in.Limit, in.Limit,
), ),
@ -197,16 +199,26 @@ func LAGetSaleInfo(ctx context.Context, catIDs LACatalogIDs) (results []catalog.
results = make([]catalog.Auction, len(apiResults.Data.Catalogs)) results = make([]catalog.Auction, len(apiResults.Data.Catalogs))
for idx, c := range apiResults.Data.Catalogs { for idx, c := range apiResults.Data.Catalogs {
results[idx] = catalog.Auction{ results[idx] = catalog.Auction{
Title: c.Title, Title: c.Title,
Description: c.Description, Description: c.Description,
SourceSiteURL: "https://www.liveauctioneers.com", Start: time.Unix(c.SaleStartTS, 0),
SourceSiteName: "Live Auctioneers", End: time.Unix(c.SaleStartTS, 0).Add(time.Hour * 8),
SourceURL: fmt.Sprintf("https://www.liveauctioneers.com/catalog/%d", c.ID), ItemCount: c.ItemCount,
Start: time.Unix(c.SaleStartTS, 0), Source: catalog.SourceDetail{
End: time.Unix(c.SaleStartTS, 0).Add(time.Hour * 8), SiteURL: "https://www.liveauctioneers.com",
ItemCount: c.ItemCount, Name: "Live Auctioneers",
Country: c.Address.CountryCode, AuctionURL: fmt.Sprintf("https://www.liveauctioneers.com/catalog/%d", c.ID),
Province: c.Address.City, },
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),
},
} }
} }

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