Compare commits

...

78 Commits

Author SHA1 Message Date
standardci
5d9d2d0c8d chore(release): publish new version
- @standardnotes/analytics@1.39.0
 - @standardnotes/api-gateway@1.36.0
 - @standardnotes/auth-server@1.49.3
 - @standardnotes/syncing-server@1.10.11
2022-11-04 09:09:58 +00:00
Karol Sójko
34e11fd5b0 feat(analytics): move the analytics report from api-gateway to analytics 2022-11-04 10:07:33 +01:00
Karol Sójko
dc1f19ed04 chore: fix commands 2022-11-04 09:50:36 +01:00
standardci
ff7c52a05e chore(release): publish new version
- @standardnotes/analytics@1.38.0
 - @standardnotes/api-gateway@1.35.1
 - @standardnotes/auth-server@1.49.2
 - @standardnotes/domain-events-infra@1.9.7
 - @standardnotes/domain-events@2.74.0
 - @standardnotes/event-store@1.6.2
 - @standardnotes/files-server@1.8.2
 - @standardnotes/scheduler-server@1.13.3
 - @standardnotes/syncing-server@1.10.10
 - @standardnotes/websockets-server@1.4.2
 - @standardnotes/workspace-server@1.17.2
2022-11-04 08:41:48 +00:00
Karol Sójko
d5684326b1 feat: add analytics worker service 2022-11-04 09:39:30 +01:00
standardci
017c55d190 chore(release): publish new version
- @standardnotes/auth-server@1.49.1
2022-11-03 13:31:06 +00:00
Karol Sójko
2504887e8d fix(auth): updating offline subscription end date 2022-11-03 14:29:17 +01:00
standardci
805e63379c chore(release): publish new version
- @standardnotes/scheduler-server@1.13.2
2022-11-03 10:03:57 +00:00
Karol Sójko
dcb20e6ea6 fix(scheduler): specs 2022-11-03 11:01:55 +01:00
standardci
786b94380b chore(release): publish new version
- @standardnotes/analytics@1.37.0
 - @standardnotes/api-gateway@1.35.0
 - @standardnotes/auth-server@1.49.0
 - @standardnotes/syncing-server@1.10.9
2022-11-03 09:53:56 +00:00
Karol Sójko
460d6a8d0f feat(auth): add analytics for subscription reactivating 2022-11-03 10:51:43 +01:00
standardci
0dbc929c8e chore(release): publish new version
- @standardnotes/api-gateway@1.34.1
 - @standardnotes/auth-server@1.48.2
 - @standardnotes/common@1.44.0
 - @standardnotes/domain-events-infra@1.9.6
 - @standardnotes/domain-events@2.73.1
 - @standardnotes/event-store@1.6.1
 - @standardnotes/files-server@1.8.1
 - @standardnotes/predicates@1.5.3
 - @standardnotes/scheduler-server@1.13.1
 - @standardnotes/security@1.5.3
 - @standardnotes/syncing-server@1.10.8
 - @standardnotes/websockets-server@1.4.1
 - @standardnotes/workspace-server@1.17.1
2022-11-03 09:25:39 +00:00
Karol Sójko
0c5305acf6 feat(common): add subscription cancelled email message identifier 2022-11-03 10:23:18 +01:00
standardci
34139efafb chore(release): publish new version
- @standardnotes/event-store@1.6.0
2022-11-03 09:21:17 +00:00
Karol Sójko
eb53c3896f feat(event-store): add discount events to event store 2022-11-03 10:18:55 +01:00
standardci
2af4c6fb55 chore(release): publish new version
- @standardnotes/scheduler-server@1.13.0
2022-11-03 09:10:52 +00:00
Karol Sójko
d66f784538 feat(scheduler): add publishing exit discount withdraw requested event 2022-11-03 10:08:52 +01:00
standardci
f127241857 chore(release): publish new version
- @standardnotes/auth-server@1.48.1
2022-11-02 13:24:02 +00:00
Karol Sójko
5b0d9dd394 fix(auth): controller name 2022-11-02 14:22:13 +01:00
standardci
ee29d18484 chore(release): publish new version
- @standardnotes/api-gateway@1.34.0
 - @standardnotes/auth-server@1.48.0
 - @standardnotes/event-store@1.5.8
 - @standardnotes/files-server@1.8.0
 - @standardnotes/scheduler-server@1.12.0
 - @standardnotes/syncing-server@1.10.7
 - @standardnotes/time@1.13.0
 - @standardnotes/websockets-server@1.4.0
 - @standardnotes/workspace-server@1.17.0
2022-11-02 12:25:40 +00:00
Karol Sójko
2255f856f9 feat(auth): add processing user requests 2022-11-02 13:23:49 +01:00
standardci
f2415527f0 chore(release): publish new version
- @standardnotes/api-gateway@1.33.6
 - @standardnotes/auth-server@1.47.7
 - @standardnotes/domain-events-infra@1.9.5
 - @standardnotes/domain-events@2.73.0
 - @standardnotes/event-store@1.5.7
 - @standardnotes/files-server@1.7.5
 - @standardnotes/scheduler-server@1.11.7
 - @standardnotes/syncing-server@1.10.6
 - @standardnotes/websockets-server@1.3.5
 - @standardnotes/workspace-server@1.16.6
2022-11-02 10:33:04 +00:00
Karol Sójko
59eb70ce62 feat(domain-events): add exit discount events 2022-11-02 11:31:10 +01:00
standardci
1d18725bc5 chore(release): publish new version
- @standardnotes/api-gateway@1.33.5
 - @standardnotes/auth-server@1.47.6
 - @standardnotes/common@1.43.0
 - @standardnotes/domain-events-infra@1.9.4
 - @standardnotes/domain-events@2.72.1
 - @standardnotes/event-store@1.5.6
 - @standardnotes/files-server@1.7.4
 - @standardnotes/predicates@1.5.2
 - @standardnotes/scheduler-server@1.11.6
 - @standardnotes/security@1.5.2
 - @standardnotes/syncing-server@1.10.5
 - @standardnotes/websockets-server@1.3.4
 - @standardnotes/workspace-server@1.16.5
2022-11-01 09:30:17 +00:00
Karol Sójko
d4af1d743e feat(common): add user request type 2022-11-01 10:28:00 +01:00
standardci
9d1a357b5b chore(release): publish new version
- @standardnotes/auth-server@1.47.5
 - @standardnotes/event-store@1.5.5
 - @standardnotes/scheduler-server@1.11.5
 - @standardnotes/syncing-server@1.10.4
 - @standardnotes/workspace-server@1.16.4
2022-11-01 06:48:36 +00:00
Karol Sójko
5160cc36dd fix: force utf8mb4 charset on typeorm 2022-11-01 07:46:15 +01:00
standardci
f05e1dbdf0 chore(release): publish new version
- @standardnotes/api-gateway@1.33.4
 - @standardnotes/auth-server@1.47.4
 - @standardnotes/common@1.42.0
 - @standardnotes/domain-events-infra@1.9.3
 - @standardnotes/domain-events@2.72.0
 - @standardnotes/event-store@1.5.4
 - @standardnotes/files-server@1.7.3
 - @standardnotes/predicates@1.5.1
 - @standardnotes/scheduler-server@1.11.4
 - @standardnotes/security@1.5.1
 - @standardnotes/syncing-server@1.10.3
 - @standardnotes/websockets-server@1.3.3
 - @standardnotes/workspace-server@1.16.3
2022-10-31 12:55:39 +00:00
Karol Sójko
7b797f0cba feat(domain-events): add exit discount applied event 2022-10-31 13:53:48 +01:00
standardci
f823826044 chore(release): publish new version
- @standardnotes/event-store@1.5.3
2022-10-31 11:01:45 +00:00
Karol Sójko
9589403c9d fix(event-store): add subscription reactivate handler 2022-10-31 11:59:52 +01:00
standardci
2757b18e17 chore(release): publish new version
- @standardnotes/api-gateway@1.33.3
 - @standardnotes/auth-server@1.47.3
 - @standardnotes/domain-events-infra@1.9.2
 - @standardnotes/domain-events@2.71.0
 - @standardnotes/event-store@1.5.2
 - @standardnotes/files-server@1.7.2
 - @standardnotes/scheduler-server@1.11.3
 - @standardnotes/syncing-server@1.10.2
 - @standardnotes/websockets-server@1.3.2
 - @standardnotes/workspace-server@1.16.2
2022-10-31 10:58:12 +00:00
Karol Sójko
6e8481bb2f feat(domain-events): add subscription reactivated event 2022-10-31 11:56:09 +01:00
standardci
72760d942e chore(release): publish new version
- @standardnotes/api-gateway@1.33.2
 - @standardnotes/auth-server@1.47.2
 - @standardnotes/domain-events-infra@1.9.1
 - @standardnotes/domain-events@2.70.0
 - @standardnotes/event-store@1.5.1
 - @standardnotes/files-server@1.7.1
 - @standardnotes/scheduler-server@1.11.2
 - @standardnotes/syncing-server@1.10.1
 - @standardnotes/websockets-server@1.3.1
 - @standardnotes/workspace-server@1.16.1
2022-10-26 09:08:06 +00:00
Karol Sójko
88d4d211b8 feat(domain-events): add subscription reactivation discount requested event 2022-10-26 11:05:49 +02:00
standardci
abfa373083 chore(release): publish new version
- @standardnotes/api-gateway@1.33.1
2022-10-24 13:30:10 +00:00
Karol Sójko
23b05caea2 fix(api-gateway): remove invite declining endpoint 2022-10-24 15:27:20 +02:00
standardci
d3f4027c3c chore(release): publish new version
- @standardnotes/auth-server@1.47.1
2022-10-24 13:26:41 +00:00
Karol Sójko
f8433c106f fix(auth): accepting shared subscription for inviters with multiple subscriptions 2022-10-24 15:24:45 +02:00
standardci
39d7a09cdf chore(release): publish new version
- @standardnotes/api-gateway@1.33.0
 - @standardnotes/auth-server@1.47.0
 - @standardnotes/websockets-server@1.3.0
 - @standardnotes/workspace-server@1.16.0
2022-10-24 11:46:41 +00:00
Karol Sójko
771a555b4f feat(auth): change accepting invitations to be an authorized endpoint 2022-10-24 13:44:45 +02:00
standardci
4ea7309001 chore(release): publish new version
- @standardnotes/auth-server@1.46.1
2022-10-20 12:05:01 +00:00
Karol Sójko
126e6a3c2f fix(auth): additional response message to accepting invitation 2022-10-20 14:02:30 +02:00
Karol Sójko
53be3a2f83 fix: linting command for syncing server 2022-10-20 13:37:02 +02:00
Karol Sójko
655409d078 fix: workspace name for auth 2022-10-20 13:33:17 +02:00
Karol Sójko
43f68900ff fix: docker image definition 2022-10-20 13:11:27 +02:00
Karol Sójko
01597a2518 fix: add aws_ecr_registry secret declaration 2022-10-20 13:07:51 +02:00
Karol Sójko
fd7a38ead1 fix: deploying worker images 2022-10-20 13:04:47 +02:00
standardci
f777e1f168 chore(release): publish new version
- @standardnotes/scheduler-server@1.11.1
2022-10-20 10:44:42 +00:00
Karol Sójko
23ced9427f fix(scheduler): add wait-for script for docker purposes 2022-10-20 12:42:27 +02:00
Karol Sójko
c9fd718af4 fix: run e2e on all applications 2022-10-20 12:31:08 +02:00
Karol Sójko
072c2770b1 fix: common server application workflow name 2022-10-20 10:08:35 +02:00
Karol Sójko
54e4775ca3 fix: workflow names 2022-10-20 10:07:57 +02:00
Karol Sójko
d4849cb4c2 fix: worker service name 2022-10-20 10:06:12 +02:00
Karol Sójko
38cd19281f fix: duplicate node name 2022-10-20 10:04:55 +02:00
Karol Sójko
1eaaa31c4e fix: workflows naming conention 2022-10-20 10:03:21 +02:00
Karol Sójko
256f070700 fix: workflows at root directory requirement 2022-10-20 10:01:29 +02:00
Karol Sójko
400f3593b2 fix: rearrange workflows 2022-10-20 09:59:52 +02:00
Karol Sójko
475a9559cf fix: add debug flag for docker buildx 2022-10-20 09:30:28 +02:00
Karol Sójko
cbf7d8c7a4 fix: configure ecr registry for Docker 2022-10-19 15:15:58 +02:00
Karol Sójko
2d7cee9d14 fix: syncing server release workflow 2022-10-19 14:54:17 +02:00
Karol Sójko
ef05946832 fix: scheduler release workflow 2022-10-19 14:51:59 +02:00
Karol Sójko
24a9078b22 fix: accessing job outputs 2022-10-19 14:48:45 +02:00
Karol Sójko
c5d19ad0b2 fix: files release workflow 2022-10-19 14:25:04 +02:00
Karol Sójko
f004653286 fix: event store release workflow 2022-10-19 14:23:02 +02:00
Karol Sójko
4850807ac1 fix: outputs referencing 2022-10-19 14:21:07 +02:00
Karol Sójko
526a21dde3 fix: cache directories 2022-10-19 14:16:03 +02:00
Karol Sójko
9a3ab6d1c1 fix: bundle temp dir cache setup 2022-10-19 14:12:16 +02:00
Karol Sójko
6bde0ec7ab fix: auth release workflow 2022-10-19 14:02:46 +02:00
Karol Sójko
5b1d755670 fix: job conditionals 2022-10-19 13:59:23 +02:00
Karol Sójko
e58d2ec233 fix: bundling and publishing to separate registries 2022-10-19 13:57:02 +02:00
Karol Sójko
6742655a04 fix: debug parameter passing 2022-10-19 13:42:54 +02:00
Karol Sójko
02b36a1c11 fix: api-gateway release workflow 2022-10-19 13:38:05 +02:00
Karol Sójko
363c236d3e fix: websockets workflow 2022-10-19 13:30:49 +02:00
Karol Sójko
c49634ae18 fix: add missing docker buildx setup for publishing 2022-10-19 13:22:34 +02:00
Karol Sójko
8fd06c4c8c fix: docker tags on publishing workflow 2022-10-19 11:42:52 +02:00
Karol Sójko
935661ed35 fix: temp dir creation 2022-10-19 11:27:29 +02:00
Karol Sójko
6764250f6d fix: common workflow 2022-10-19 11:12:49 +02:00
131 changed files with 3382 additions and 2220 deletions

39
.github/workflows/analytics.yml vendored Normal file
View File

@@ -0,0 +1,39 @@
name: Analytics Server
concurrency:
group: analytics
cancel-in-progress: true
on:
push:
tags:
- '*standardnotes/analytics*'
workflow_dispatch:
jobs:
call_server_application_workflow:
name: Server Application
uses: standardnotes/server/.github/workflows/common-server-application.yml@main
with:
service_name: analytics
workspace_name: "@standardnotes/analytics"
e2e_tag_parameter_name: analytics_image_tag
deploy_web: false
package_path: packages/analytics
secrets: inherit
newrelic:
needs: call_server_application_workflow
runs-on: ubuntu-latest
steps:
- name: Create New Relic deployment marker for Worker
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_ANALYTICS_WORKER_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"

View File

@@ -1,161 +0,0 @@
name: Api Gateway
concurrency:
group: api_gateway
cancel-in-progress: true
on:
push:
tags:
- '*standardnotes/api-gateway*'
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build
run: yarn build
- name: Lint
run: yarn lint:api-gateway
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Publish Docker image for E2E testing
run: |
yarn docker build @standardnotes/api-gateway -t standardnotes/api-gateway:${{ github.sha }}
docker push standardnotes/api-gateway:${{ github.sha }}
- name: Run E2E test suite
uses: convictional/trigger-workflow-and-wait@v1.6.3
with:
owner: standardnotes
repo: e2e
github_token: ${{ secrets.CI_PAT_TOKEN }}
workflow_file_name: testing-with-stable-client.yml
wait_interval: 30
client_payload: '{"api_gateway_image_tag": "${{ github.sha }}"}'
propagate_failure: true
trigger_workflow: true
wait_workflow: true
publish-aws-ecr:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build locally
run: yarn build
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: api-gateway
IMAGE_TAG: ${{ github.sha }}
run: |
yarn docker build @standardnotes/api-gateway -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:latest
docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
publish-docker-hub:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build locally
run: yarn build
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Publish Docker image as stable
run: |
yarn docker build @standardnotes/api-gateway -t standardnotes/api-gateway:latest
docker push standardnotes/api-gateway:latest
deploy-web:
needs: publish-aws-ecr
runs-on: ubuntu-latest
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Download task definition
run: |
aws ecs describe-task-definition --task-definition api-gateway-prod --query taskDefinition > task-definition.json
- name: Fill in the new version in the Amazon ECS task definition
run: |
jq '(.containerDefinitions[] | select(.name=="api-gateway-prod") | .environment[] | select(.name=="VERSION")).value = "${{ github.sha }}"' task-definition.json > tmp.json && mv tmp.json task-definition.json
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def-prod
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: api-gateway-prod
image: ${{ secrets.AWS_ECR_REGISTRY }}/api-gateway:${{ github.sha }}
- name: Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def-prod.outputs.task-definition }}
service: api-gateway-prod
cluster: prod
wait-for-service-stability: true
newrelic:
needs: deploy-web
runs-on: ubuntu-latest
steps:
- name: Create New Relic deployment marker for Web
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_API_GATEWAY_WEB_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"

37
.github/workflows/api-gateway.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: Api Gateway
concurrency:
group: api_gateway
cancel-in-progress: true
on:
push:
tags:
- '*standardnotes/api-gateway*'
workflow_dispatch:
jobs:
call_server_application_workflow:
name: Server Application
uses: standardnotes/server/.github/workflows/common-server-application.yml@main
with:
service_name: api-gateway
workspace_name: "@standardnotes/api-gateway"
e2e_tag_parameter_name: api_gateway_image_tag
deploy_worker: false
package_path: packages/api-gateway
secrets: inherit
newrelic:
needs: call_server_application_workflow
runs-on: ubuntu-latest
steps:
- name: Create New Relic deployment marker for Web
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_API_GATEWAY_WEB_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"

View File

@@ -1,206 +0,0 @@
name: Auth Server
concurrency:
group: auth
cancel-in-progress: true
on:
push:
tags:
- '*standardnotes/auth-server*'
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build
run: yarn build
- name: Lint
run: yarn lint:auth
- name: Test
run: yarn test:auth
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Publish Docker image for E2E testing
run: |
yarn docker build @standardnotes/auth-server -t standardnotes/auth:${{ github.sha }}
docker push standardnotes/auth:${{ github.sha }}
- name: Run E2E test suite
uses: convictional/trigger-workflow-and-wait@v1.6.3
with:
owner: standardnotes
repo: e2e
github_token: ${{ secrets.CI_PAT_TOKEN }}
workflow_file_name: testing-with-stable-client.yml
wait_interval: 30
client_payload: '{"auth_image_tag": "${{ github.sha }}"}'
propagate_failure: true
trigger_workflow: true
wait_workflow: true
publish-aws-ecr:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build locally
run: yarn build
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: auth
IMAGE_TAG: ${{ github.sha }}
run: |
yarn docker build @standardnotes/auth-server -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:latest
docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
publish-docker-hub:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build locally
run: yarn build
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Publish Docker image as stable
run: |
yarn docker build @standardnotes/auth-server -t standardnotes/auth:latest
docker push standardnotes/auth:latest
deploy-web:
needs: publish-aws-ecr
runs-on: ubuntu-latest
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Download task definition
run: |
aws ecs describe-task-definition --task-definition auth-prod --query taskDefinition > task-definition.json
- name: Fill in the new version in the Amazon ECS task definition
run: |
jq '(.containerDefinitions[] | select(.name=="auth-prod") | .environment[] | select(.name=="VERSION")).value = "${{ github.sha }}"' task-definition.json > tmp.json && mv tmp.json task-definition.json
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def-prod
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: auth-prod
image: ${{ secrets.AWS_ECR_REGISTRY }}/auth:${{ github.sha }}
- name: Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def-prod.outputs.task-definition }}
service: auth-prod
cluster: prod
wait-for-service-stability: true
deploy-worker:
needs: publish-aws-ecr
runs-on: ubuntu-latest
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Download task definition
run: |
aws ecs describe-task-definition --task-definition auth-worker-prod --query taskDefinition > task-definition.json
- name: Fill in the new version in the Amazon ECS task definition
run: |
jq '(.containerDefinitions[] | select(.name=="auth-worker-prod") | .environment[] | select(.name=="VERSION")).value = "${{ github.sha }}"' task-definition.json > tmp.json && mv tmp.json task-definition.json
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def-prod
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: auth-worker-prod
image: ${{ secrets.AWS_ECR_REGISTRY }}/auth:${{ github.sha }}
- name: Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def-prod.outputs.task-definition }}
service: auth-worker-prod
cluster: prod
wait-for-service-stability: true
newrelic:
needs: [ deploy-web, deploy-worker ]
runs-on: ubuntu-latest
steps:
- name: Create New Relic deployment marker for Web
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_AUTH_WEB_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"
- name: Create New Relic deployment marker for Worker
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_AUTH_WORKER_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"

46
.github/workflows/auth.yml vendored Normal file
View File

@@ -0,0 +1,46 @@
name: Auth Server
concurrency:
group: auth
cancel-in-progress: true
on:
push:
tags:
- '*standardnotes/auth-server*'
workflow_dispatch:
jobs:
call_server_application_workflow:
name: Server Application
uses: standardnotes/server/.github/workflows/common-server-application.yml@main
with:
service_name: auth
workspace_name: "@standardnotes/auth-server"
e2e_tag_parameter_name: auth_image_tag
package_path: packages/auth
secrets: inherit
newrelic:
needs: call_server_application_workflow
runs-on: ubuntu-latest
steps:
- name: Create New Relic deployment marker for Web
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_AUTH_WEB_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"
- name: Create New Relic deployment marker for Worker
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_AUTH_WORKER_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"

55
.github/workflows/common-deploy.yml vendored Normal file
View File

@@ -0,0 +1,55 @@
name: Reusable Server Application Deployment Workflow
on:
workflow_call:
inputs:
service_name:
required: true
type: string
docker_image:
required: true
type: string
secrets:
AWS_ACCESS_KEY_ID:
required: true
AWS_SECRET_ACCESS_KEY:
required: true
AWS_ECR_REGISTRY:
required: true
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Download task definition
run: |
aws ecs describe-task-definition --task-definition ${{ inputs.service_name }}-prod --query taskDefinition > task-definition.json
- name: Fill in the new version in the Amazon ECS task definition
run: |
jq '(.containerDefinitions[] | select(.name=="${{ inputs.service_name }}-prod") | .environment[] | select(.name=="VERSION")).value = "${{ github.sha }}"' task-definition.json > tmp.json && mv tmp.json task-definition.json
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def-prod
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: ${{ inputs.service_name }}-prod
image: ${{ secrets.AWS_ECR_REGISTRY }}/${{ inputs.docker_image }}
- name: Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def-prod.outputs.task-definition }}
service: ${{ inputs.service_name }}-prod
cluster: prod
wait-for-service-stability: true

View File

@@ -0,0 +1,96 @@
name: Reusable Publish Docker Image Workflow
on:
workflow_call:
inputs:
service_name:
required: true
type: string
bundle_dir:
required: true
type: string
package_path:
required: true
type: string
workspace_name:
required: true
type: string
secrets:
DOCKER_USERNAME:
required: true
DOCKER_PASSWORD:
required: true
AWS_ACCESS_KEY_ID:
required: true
AWS_SECRET_ACCESS_KEY:
required: true
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Cache build
id: cache-build
uses: actions/cache@v3
with:
path: |
packages/**/dist
${{ inputs.bundle_dir }}
key: ${{ runner.os }}-${{ inputs.service_name }}-build-${{ github.sha }}
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build
if: steps.cache-build.outputs.cache-hit != 'true'
run: yarn build ${{ inputs.package_path }}
- name: Bundle
if: steps.cache-build.outputs.cache-hit != 'true'
run: yarn workspace ${{ inputs.workspace_name }} bundle --no-compress --output-directory ${{ inputs.bundle_dir }}
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Set up QEMU
uses: docker/setup-qemu-action@master
with:
platforms: all
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@master
- name: Publish Docker image
uses: docker/build-push-action@v3
with:
builder: ${{ steps.buildx.outputs.name }}
context: ${{ inputs.bundle_dir }}
file: ${{ inputs.bundle_dir }}/${{ inputs.package_path }}/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: |
standardnotes/${{ inputs.service_name }}:latest
standardnotes/${{ inputs.service_name }}:${{ github.sha }}
${{ steps.login-ecr.outputs.registry }}/${{ inputs.service_name }}:${{ github.sha }}
${{ steps.login-ecr.outputs.registry }}/${{ inputs.service_name }}:latest

View File

@@ -0,0 +1,236 @@
name: Reusable Server Application Workflow
on:
workflow_call:
inputs:
service_name:
required: true
type: string
workspace_name:
required: true
type: string
deploy_web:
required: false
default: true
type: boolean
deploy_worker:
required: false
default: true
type: boolean
e2e_tag_parameter_name:
required: false
type: string
package_path:
required: true
type: string
secrets:
DOCKER_USERNAME:
required: true
DOCKER_PASSWORD:
required: true
CI_PAT_TOKEN:
required: true
AWS_ACCESS_KEY_ID:
required: true
AWS_SECRET_ACCESS_KEY:
required: true
jobs:
build:
runs-on: ubuntu-latest
outputs:
temp_dir: ${{ steps.bundle-dir.outputs.temp_dir }}
steps:
- uses: actions/checkout@v3
- name: Create Bundle Dir
id: bundle-dir
run: echo "temp_dir=$(mktemp -d -t ${{ inputs.service_name }}-${{ github.sha }}-XXXXXXX)" >> $GITHUB_OUTPUT
- name: Cache build
id: cache-build
uses: actions/cache@v3
with:
path: |
packages/**/dist
${{ steps.bundle-dir.outputs.temp_dir }}
key: ${{ runner.os }}-${{ inputs.service_name }}-build-${{ github.sha }}
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build
run: yarn build ${{ inputs.package_path }}
- name: Bundle
run: yarn workspace ${{ inputs.workspace_name }} bundle --no-compress --output-directory ${{ steps.bundle-dir.outputs.temp_dir }}
lint:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v3
- name: Cache build
id: cache-build
uses: actions/cache@v3
with:
path: |
packages/**/dist
${{ needs.build.outputs.temp_dir }}
key: ${{ runner.os }}-${{ inputs.service_name }}-build-${{ github.sha }}
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build
if: steps.cache-build.outputs.cache-hit != 'true'
run: yarn build ${{ inputs.package_path }}
- name: Lint
run: yarn lint:${{ inputs.service_name }}
test:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v3
- name: Cache build
id: cache-build
uses: actions/cache@v3
with:
path: |
packages/**/dist
${{ needs.build.outputs.temp_dir }}
key: ${{ runner.os }}-${{ inputs.service_name }}-build-${{ github.sha }}
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build
if: steps.cache-build.outputs.cache-hit != 'true'
run: yarn build ${{ inputs.package_path }}
- name: Test
run: yarn test ${{ inputs.package_path }}
e2e:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v3
- name: Cache build
id: cache-build
uses: actions/cache@v3
with:
path: |
packages/**/dist
${{ needs.build.outputs.temp_dir }}
key: ${{ runner.os }}-${{ inputs.service_name }}-build-${{ github.sha }}
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build
if: steps.cache-build.outputs.cache-hit != 'true'
run: yarn build ${{ inputs.package_path }}
- name: Bundle
if: steps.cache-build.outputs.cache-hit != 'true'
run: yarn workspace ${{ inputs.workspace_name }} bundle --no-compress --output-directory ${{ needs.build.outputs.temp_dir }}
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Set up QEMU
uses: docker/setup-qemu-action@master
with:
platforms: all
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@master
- name: Publish Docker image for E2E testing
uses: docker/build-push-action@v3
with:
builder: ${{ steps.buildx.outputs.name }}
context: ${{ needs.build.outputs.temp_dir }}
file: ${{ needs.build.outputs.temp_dir }}/${{ inputs.package_path }}/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: standardnotes/${{ inputs.service_name }}:${{ github.sha }}
- name: Run E2E test suite
uses: convictional/trigger-workflow-and-wait@v1.6.3
with:
owner: standardnotes
repo: e2e
github_token: ${{ secrets.CI_PAT_TOKEN }}
workflow_file_name: testing-with-stable-client.yml
wait_interval: 30
client_payload: '{"${{ inputs.e2e_tag_parameter_name }}": "${{ github.sha }}"}'
propagate_failure: true
trigger_workflow: true
wait_workflow: true
publish:
needs: [ build, test, lint, e2e ]
name: Publish Docker Image
uses: standardnotes/server/.github/workflows/common-docker-image.yml@main
with:
service_name: ${{ inputs.service_name }}
bundle_dir: ${{ needs.build.outputs.temp_dir }}
package_path: ${{ inputs.package_path }}
workspace_name: ${{ inputs.workspace_name }}
secrets: inherit
deploy-web:
if: ${{ inputs.deploy_web }}
needs: publish
name: Deploy Web
uses: standardnotes/server/.github/workflows/common-deploy.yml@main
with:
service_name: ${{ inputs.service_name }}
docker_image: ${{ inputs.service_name }}:${{ github.sha }}
secrets: inherit
deploy-worker:
if: ${{ inputs.deploy_worker }}
needs: publish
name: Deploy Worker
uses: standardnotes/server/.github/workflows/common-deploy.yml@main
with:
service_name: ${{ inputs.service_name }}-worker
docker_image: ${{ inputs.service_name }}:${{ github.sha }}
secrets: inherit

View File

@@ -1,136 +0,0 @@
name: Event Store
concurrency:
group: event-store
cancel-in-progress: true
on:
push:
tags:
- '*standardnotes/event-store*'
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- run: yarn build
- run: yarn lint:event-store
- run: yarn test:event-store
publish-aws-ecr:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build locally
run: yarn build
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: event-store
IMAGE_TAG: ${{ github.sha }}
run: |
yarn docker build @standardnotes/event-store -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:latest
docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
publish-docker-hub:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build locally
run: yarn build
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build, tag, and push image to Docker Hub
run: |
yarn docker build @standardnotes/event-store -t standardnotes/event-store:${{ github.sha }}
docker push standardnotes/event-store:${{ github.sha }}
docker tag standardnotes/event-store:${{ github.sha }} standardnotes/event-store:latest
docker push standardnotes/event-store:latest
deploy-worker:
needs: publish-aws-ecr
runs-on: ubuntu-latest
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Download task definition
run: |
aws ecs describe-task-definition --task-definition event-store-prod --query taskDefinition > task-definition.json
- name: Fill in the new version in the Amazon ECS task definition
run: |
jq '(.containerDefinitions[] | select(.name=="event-store-prod") | .environment[] | select(.name=="VERSION")).value = "${{ github.sha }}"' task-definition.json > tmp.json && mv tmp.json task-definition.json
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def-prod
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: event-store-prod
image: ${{ secrets.AWS_ECR_REGISTRY }}/event-store:${{ github.sha }}
- name: Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def-prod.outputs.task-definition }}
service: event-store-prod
cluster: prod
wait-for-service-stability: true
newrelic:
needs: [ deploy-worker ]
runs-on: ubuntu-latest
steps:
- name: Create New Relic deployment marker for Worker
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_EVENT_STORE_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"

39
.github/workflows/event-store.yml vendored Normal file
View File

@@ -0,0 +1,39 @@
name: Event Store
concurrency:
group: event-store
cancel-in-progress: true
on:
push:
tags:
- '*standardnotes/event-store*'
workflow_dispatch:
jobs:
call_server_application_workflow:
name: Server Application
uses: standardnotes/server/.github/workflows/common-server-application.yml@main
with:
service_name: event-store
workspace_name: "@standardnotes/event-store"
e2e_tag_parameter_name: event_store_image_tag
deploy_web: false
package_path: packages/event-store
secrets: inherit
newrelic:
needs: call_server_application_workflow
runs-on: ubuntu-latest
steps:
- name: Create New Relic deployment marker for Worker
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_EVENT_STORE_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"

View File

@@ -1,206 +0,0 @@
name: Files Server
concurrency:
group: files
cancel-in-progress: true
on:
push:
tags:
- '*standardnotes/files-server*'
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build
run: yarn build
- name: Lint
run: yarn lint:files
- name: Test
run: yarn test:files
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Publish Docker image for E2E testing
run: |
yarn docker build @standardnotes/files-server -t standardnotes/files:${{ github.sha }}
docker push standardnotes/files:${{ github.sha }}
- name: Run E2E test suite
uses: convictional/trigger-workflow-and-wait@v1.6.3
with:
owner: standardnotes
repo: e2e
github_token: ${{ secrets.CI_PAT_TOKEN }}
workflow_file_name: testing-with-stable-client.yml
wait_interval: 30
client_payload: '{"files_image_tag": "${{ github.sha }}"}'
propagate_failure: true
trigger_workflow: true
wait_workflow: true
publish-aws-ecr:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build locally
run: yarn build
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: files
IMAGE_TAG: ${{ github.sha }}
run: |
yarn docker build @standardnotes/files-server -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:latest
docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
publish-docker-hub:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build locally
run: yarn build
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build, tag, and push image to Docker Hub
run: |
yarn docker build @standardnotes/files-server -t standardnotes/files:latest
docker push standardnotes/files:latest
deploy-web:
needs: publish-aws-ecr
runs-on: ubuntu-latest
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Download task definition
run: |
aws ecs describe-task-definition --task-definition files-prod --query taskDefinition > task-definition.json
- name: Fill in the new version in the Amazon ECS task definition
run: |
jq '(.containerDefinitions[] | select(.name=="files-prod") | .environment[] | select(.name=="VERSION")).value = "${{ github.sha }}"' task-definition.json > tmp.json && mv tmp.json task-definition.json
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def-prod
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: files-prod
image: ${{ secrets.AWS_ECR_REGISTRY }}/files:${{ github.sha }}
- name: Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def-prod.outputs.task-definition }}
service: files-prod
cluster: prod
wait-for-service-stability: true
deploy-worker:
needs: publish-aws-ecr
runs-on: ubuntu-latest
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Download task definition
run: |
aws ecs describe-task-definition --task-definition files-worker-prod --query taskDefinition > task-definition.json
- name: Fill in the new version in the Amazon ECS task definition
run: |
jq '(.containerDefinitions[] | select(.name=="files-worker-prod") | .environment[] | select(.name=="VERSION")).value = "${{ github.sha }}"' task-definition.json > tmp.json && mv tmp.json task-definition.json
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def-prod
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: files-worker-prod
image: ${{ secrets.AWS_ECR_REGISTRY }}/files:${{ github.sha }}
- name: Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def-prod.outputs.task-definition }}
service: files-worker-prod
cluster: prod
wait-for-service-stability: true
newrelic:
needs: [ deploy-web, deploy-worker ]
runs-on: ubuntu-latest
steps:
- name: Create New Relic deployment marker for Web
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_FILES_WEB_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"
- name: Create New Relic deployment marker for Worker
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_FILES_WORKER_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"

46
.github/workflows/files.yml vendored Normal file
View File

@@ -0,0 +1,46 @@
name: Files Server
concurrency:
group: files
cancel-in-progress: true
on:
push:
tags:
- '*standardnotes/files-server*'
workflow_dispatch:
jobs:
call_server_application_workflow:
name: Server Application
uses: standardnotes/server/.github/workflows/common-server-application.yml@main
with:
service_name: files
workspace_name: "@standardnotes/files-server"
e2e_tag_parameter_name: files_image_tag
package_path: packages/files
secrets: inherit
newrelic:
needs: call_server_application_workflow
runs-on: ubuntu-latest
steps:
- name: Create New Relic deployment marker for Web
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_FILES_WEB_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"
- name: Create New Relic deployment marker for Worker
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_FILES_WORKER_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"

View File

@@ -1,136 +0,0 @@
name: Scheduler Server
concurrency:
group: scheduler
cancel-in-progress: true
on:
push:
tags:
- '*standardnotes/scheduler-server*'
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- run: yarn build
- run: yarn lint:scheduler
- run: yarn test:scheduler
publish-aws-ecr:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build locally
run: yarn build
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: scheduler-worker
IMAGE_TAG: ${{ github.sha }}
run: |
yarn docker build @standardnotes/scheduler-server -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:latest
docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
publish-docker-hub:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build locally
run: yarn build
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build, tag, and push image to Docker Hub
run: |
yarn docker build @standardnotes/scheduler-server -t standardnotes/scheduler:${{ github.sha }}
docker push standardnotes/scheduler:${{ github.sha }}
docker tag standardnotes/scheduler:${{ github.sha }} standardnotes/scheduler:latest
docker push standardnotes/scheduler:latest
deploy-worker:
needs: publish-aws-ecr
runs-on: ubuntu-latest
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Download task definition
run: |
aws ecs describe-task-definition --task-definition scheduler-worker-prod --query taskDefinition > task-definition.json
- name: Fill in the new version in the Amazon ECS task definition
run: |
jq '(.containerDefinitions[] | select(.name=="scheduler-worker-prod") | .environment[] | select(.name=="VERSION")).value = "${{ github.sha }}"' task-definition.json > tmp.json && mv tmp.json task-definition.json
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def-prod
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: scheduler-worker-prod
image: ${{ secrets.AWS_ECR_REGISTRY }}/scheduler-worker:${{ github.sha }}
- name: Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def-prod.outputs.task-definition }}
service: scheduler-worker-prod
cluster: prod
wait-for-service-stability: true
newrelic:
needs: [ deploy-worker ]
runs-on: ubuntu-latest
steps:
- name: Create New Relic deployment marker for Worker
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_SCHEDULER_WORKER_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"

39
.github/workflows/scheduler.yml vendored Normal file
View File

@@ -0,0 +1,39 @@
name: Scheduler Server
concurrency:
group: scheduler
cancel-in-progress: true
on:
push:
tags:
- '*standardnotes/scheduler-server*'
workflow_dispatch:
jobs:
call_server_application_workflow:
name: Server Application
uses: standardnotes/server/.github/workflows/common-server-application.yml@main
with:
service_name: scheduler
workspace_name: "@standardnotes/scheduler-server"
e2e_tag_parameter_name: scheduler_image_tag
deploy_web: false
package_path: packages/scheduler
secrets: inherit
newrelic:
needs: call_server_application_workflow
runs-on: ubuntu-latest
steps:
- name: Create New Relic deployment marker for Worker
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_SCHEDULER_WORKER_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"

View File

@@ -1,317 +0,0 @@
name: Server Application Reusable Workflow
on:
workflow_call:
inputs:
service_name:
required: true
type: string
workspace_name:
required: true
type: string
run_e2e_test_suite:
required: true
type: boolean
e2e_tag_parameter_name:
required: false
type: string
package_path:
required: true
type: string
secrets:
DOCKER_USERNAME:
required: true
DOCKER_PASSWORD:
required: true
CI_PAT_TOKEN:
required: true
AWS_ACCESS_KEY_ID:
required: true
AWS_SECRET_ACCESS_KEY:
required: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Cache build
id: cache-build
uses: actions/cache@v3
with:
path: packages/**/dist
key: ${{ runner.os }}-${{ inputs.service_name }}-build-${{ github.sha }}
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build
run: yarn build ${{ inputs.package_path }}
lint:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v3
- name: Cache build
id: cache-build
uses: actions/cache@v3
with:
path: packages/**/dist
key: ${{ runner.os }}-${{ inputs.service_name }}-build-${{ github.sha }}
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build
if: steps.cache-build.outputs.cache-hit != 'true'
run: yarn build ${{ inputs.package_path }}
- name: Lint
run: yarn lint:${{ inputs.service_name }}
test:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v3
- name: Cache build
id: cache-build
uses: actions/cache@v3
with:
path: packages/**/dist
key: ${{ runner.os }}-${{ inputs.service_name }}-build-${{ github.sha }}
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build
if: steps.cache-build.outputs.cache-hit != 'true'
run: yarn build ${{ inputs.package_path }}
- name: Test
run: yarn test ${{ inputs.package_path }}
e2e:
if: ${{ inputs.run_e2e_test_suite }} == true
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v3
- name: Cache build
id: cache-build
uses: actions/cache@v3
with:
path: packages/**/dist
key: ${{ runner.os }}-${{ inputs.service_name }}-build-${{ github.sha }}
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build
if: steps.cache-build.outputs.cache-hit != 'true'
run: yarn build ${{ inputs.package_path }}
- name: Bundle
id: bundle
run: |
TEMP_DIR=$(mktemp -d -t "${{ inputs.service_name }}-${{ github.sha }}")
echo "::set-output name=dir::$TEMP_DIR"
yarn workspace ${{ inputs.workspace_name }} bundle --no-compress --output-directory $TEMP_DIR
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Set up QEMU
uses: docker/setup-qemu-action@master
with:
platforms: all
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@master
- name: Publish Docker image for E2E testing
uses: docker/build-push-action@v3
with:
builder: ${{ steps.buildx.outputs.name }}
context: ${{ steps.bundle.outputs.dir }}
file: ${{ steps.bundle.outputs.dir }}/${{ inputs.package_path }}/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: standardnotes/${{ inputs.service_name }}:${{ github.sha }}
- name: Run E2E test suite
uses: convictional/trigger-workflow-and-wait@v1.6.3
with:
owner: standardnotes
repo: e2e
github_token: ${{ secrets.CI_PAT_TOKEN }}
workflow_file_name: testing-with-stable-client.yml
wait_interval: 30
client_payload: '{"${{ inputs.e2e_tag_parameter_name }}": "${{ github.sha }}"}'
propagate_failure: true
trigger_workflow: true
wait_workflow: true
publish:
needs: [ test, lint, e2e ]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Cache build
id: cache-build
uses: actions/cache@v3
with:
path: packages/**/dist
key: ${{ runner.os }}-${{ inputs.service_name }}-build-${{ github.sha }}
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build
if: steps.cache-build.outputs.cache-hit != 'true'
run: yarn build ${{ inputs.package_path }}
- name: Bundle
id: bundle
run: |
TEMP_DIR=$(mktemp -d -t "${{ inputs.service_name }}-${{ github.sha }}")
echo "::set-output name=dir::$TEMP_DIR"
yarn workspace ${{ inputs.workspace_name }} bundle --no-compress --output-directory $TEMP_DIR
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Publish Docker image
uses: docker/build-push-action@v3
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: ${{ inputs.service_name }}
IMAGE_TAG: ${{ github.sha }}
with:
builder: ${{ steps.buildx.outputs.name }}
context: ${{ steps.bundle.outputs.dir }}
file: ${{ steps.bundle.outputs.dir }}/${{ inputs.package_path }}/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: |
standardnotes/${{ inputs.service_name }}:latest
$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
$ECR_REGISTRY/$ECR_REPOSITORY:latest
deploy-web:
needs: publish
runs-on: ubuntu-latest
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Download task definition
run: |
aws ecs describe-task-definition --task-definition ${{ inputs.service_name }}-prod --query taskDefinition > task-definition.json
- name: Fill in the new version in the Amazon ECS task definition
run: |
jq '(.containerDefinitions[] | select(.name=="${{ inputs.service_name }}-prod") | .environment[] | select(.name=="VERSION")).value = "${{ github.sha }}"' task-definition.json > tmp.json && mv tmp.json task-definition.json
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def-prod
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: ${{ inputs.service_name }}-prod
image: ${{ secrets.AWS_ECR_REGISTRY }}/${{ inputs.service_name }}:${{ github.sha }}
- name: Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def-prod.outputs.task-definition }}
service: ${{ inputs.service_name }}-prod
cluster: prod
wait-for-service-stability: true
deploy-worker:
needs: publish
runs-on: ubuntu-latest
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Download task definition
run: |
aws ecs describe-task-definition --task-definition ${{ inputs.service_name }}-worker-prod --query taskDefinition > task-definition.json
- name: Fill in the new version in the Amazon ECS task definition
run: |
jq '(.containerDefinitions[] | select(.name=="${{ inputs.service_name }}-worker-prod") | .environment[] | select(.name=="VERSION")).value = "${{ github.sha }}"' task-definition.json > tmp.json && mv tmp.json task-definition.json
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def-prod
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: ${{ inputs.service_name }}-worker-prod
image: ${{ secrets.AWS_ECR_REGISTRY }}/${{ inputs.service_name }}:${{ github.sha }}
- name: Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def-prod.outputs.task-definition }}
service: ${{ inputs.service_name }}-worker-prod
cluster: prod
wait-for-service-stability: true

View File

@@ -1,48 +0,0 @@
name: Update SNJS Packages
on:
workflow_dispatch:
repository_dispatch:
types: [snjs-updated-event]
jobs:
SNJSUpdateEvent:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
ref: main
token: ${{ secrets.CI_PAT_TOKEN }}
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Setup git config
run: |
git config --global user.name "standardci"
git config --global user.email "ci@standardnotes.com"
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@v5
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.PASSPHRASE }}
git_user_signingkey: true
git_commit_gpgsign: true
- run: yarn install
- run: |
yarn upgrade:snjs
yarn install --no-immutable
- name: Create Pull Request
uses: peter-evans/create-pull-request@v4
with:
token: ${{ secrets.CI_PAT_TOKEN }}
title: "${{ 'chore(deps): upgrade snjs' }}"
body: Updates all packages prefixed with "@standardnotes/"
commit-message: "${{ 'chore(deps): upgrade snjs' }}"
delete-branch: true
committer: standardci <ci@standardnotes.com>
author: standardci <ci@standardnotes.com>

View File

@@ -1,313 +0,0 @@
name: Syncing Server
concurrency:
group: syncing_server
cancel-in-progress: true
on:
push:
tags:
- '*standardnotes/syncing-server*'
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Cache build
id: cache-build
uses: actions/cache@v3
with:
path: packages/**/dist
key: ${{ runner.os }}-syncing-server-build-${{ github.sha }}
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build
run: yarn build:syncing-server
lint:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v3
- name: Cache build
id: cache-build
uses: actions/cache@v3
with:
path: packages/**/dist
key: ${{ runner.os }}-syncing-server-build-${{ github.sha }}
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build
if: steps.cache-build.outputs.cache-hit != 'true'
run: yarn build:syncing-server
- name: Lint
run: yarn lint:syncing-server
test:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v3
- name: Cache build
id: cache-build
uses: actions/cache@v3
with:
path: packages/**/dist
key: ${{ runner.os }}-syncing-server-build-${{ github.sha }}
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build
if: steps.cache-build.outputs.cache-hit != 'true'
run: yarn build:syncing-server
- name: Test
run: yarn test:syncing-server
e2e:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v3
- name: Cache build
id: cache-build
uses: actions/cache@v3
with:
path: packages/**/dist
key: ${{ runner.os }}-syncing-server-build-${{ github.sha }}
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build
if: steps.cache-build.outputs.cache-hit != 'true'
run: yarn build:syncing-server
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Publish Docker image for E2E testing
run: |
yarn docker build @standardnotes/syncing-server -t standardnotes/syncing-server-js:${{ github.sha }}
docker push standardnotes/syncing-server-js:${{ github.sha }}
- name: Run E2E test suite
uses: convictional/trigger-workflow-and-wait@v1.6.3
with:
owner: standardnotes
repo: e2e
github_token: ${{ secrets.CI_PAT_TOKEN }}
workflow_file_name: testing-with-stable-client.yml
wait_interval: 30
client_payload: '{"syncing_server_js_image_tag": "${{ github.sha }}"}'
propagate_failure: true
trigger_workflow: true
wait_workflow: true
publish-aws-ecr:
needs: [ test, lint, e2e ]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Cache build
id: cache-build
uses: actions/cache@v3
with:
path: packages/**/dist
key: ${{ runner.os }}-syncing-server-build-${{ github.sha }}
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build
if: steps.cache-build.outputs.cache-hit != 'true'
run: yarn build:syncing-server
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: syncing-server-js
IMAGE_TAG: ${{ github.sha }}
run: |
yarn docker build @standardnotes/syncing-server -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:latest
docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
publish-docker-hub:
needs: [ test, lint, e2e ]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Cache build
id: cache-build
uses: actions/cache@v3
with:
path: packages/**/dist
key: ${{ runner.os }}-syncing-server-build-${{ github.sha }}
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build
if: steps.cache-build.outputs.cache-hit != 'true'
run: yarn build:syncing-server
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Publish Docker image as stable
run: |
yarn docker build @standardnotes/syncing-server -t standardnotes/syncing-server-js:latest
docker push standardnotes/syncing-server-js:latest
deploy-web:
needs: publish-aws-ecr
runs-on: ubuntu-latest
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Download task definition
run: |
aws ecs describe-task-definition --task-definition syncing-server-js-prod --query taskDefinition > task-definition.json
- name: Fill in the new version in the Amazon ECS task definition
run: |
jq '(.containerDefinitions[] | select(.name=="syncing-server-js-prod") | .environment[] | select(.name=="VERSION")).value = "${{ github.sha }}"' task-definition.json > tmp.json && mv tmp.json task-definition.json
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def-prod
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: syncing-server-js-prod
image: ${{ secrets.AWS_ECR_REGISTRY }}/syncing-server-js:${{ github.sha }}
- name: Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def-prod.outputs.task-definition }}
service: syncing-server-js-prod
cluster: prod
wait-for-service-stability: true
deploy-worker:
needs: publish-aws-ecr
runs-on: ubuntu-latest
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Download task definition
run: |
aws ecs describe-task-definition --task-definition syncing-server-js-worker-prod --query taskDefinition > task-definition.json
- name: Fill in the new version in the Amazon ECS task definition
run: |
jq '(.containerDefinitions[] | select(.name=="syncing-server-js-worker-prod") | .environment[] | select(.name=="VERSION")).value = "${{ github.sha }}"' task-definition.json > tmp.json && mv tmp.json task-definition.json
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def-prod
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: syncing-server-js-worker-prod
image: ${{ secrets.AWS_ECR_REGISTRY }}/syncing-server-js:${{ github.sha }}
- name: Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def-prod.outputs.task-definition }}
service: syncing-server-js-worker-prod
cluster: prod
wait-for-service-stability: true
newrelic:
needs: [ deploy-web, deploy-worker ]
runs-on: ubuntu-latest
steps:
- name: Create New Relic deployment marker for Web
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_SYNCING_SERVER_WEB_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"
- name: Create New Relic deployment marker for Worker
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_SYNCING_SERVER_WORKER_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"

46
.github/workflows/syncing-server.yml vendored Normal file
View File

@@ -0,0 +1,46 @@
name: Syncing Server
concurrency:
group: syncing_server
cancel-in-progress: true
on:
push:
tags:
- '*standardnotes/syncing-server*'
workflow_dispatch:
jobs:
call_server_application_workflow:
name: Server Application
uses: standardnotes/server/.github/workflows/common-server-application.yml@main
with:
service_name: syncing-server-js
workspace_name: "@standardnotes/syncing-server"
e2e_tag_parameter_name: syncing_server_js_image_tag
package_path: packages/syncing-server
secrets: inherit
newrelic:
needs: call_server_application_workflow
runs-on: ubuntu-latest
steps:
- name: Create New Relic deployment marker for Web
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_SYNCING_SERVER_WEB_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"
- name: Create New Relic deployment marker for Worker
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_SYNCING_SERVER_WORKER_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"

View File

@@ -1,264 +0,0 @@
name: Websockets Server
concurrency:
group: websockets
cancel-in-progress: true
on:
push:
tags:
- '*standardnotes/websockets-server*'
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Cache build
id: cache-build
uses: actions/cache@v3
with:
path: packages/**/dist
key: ${{ runner.os }}-websockets-build-${{ github.sha }}
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build
run: yarn build:websockets
lint:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v3
- name: Cache build
id: cache-build
uses: actions/cache@v3
with:
path: packages/**/dist
key: ${{ runner.os }}-websockets-build-${{ github.sha }}
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build
if: steps.cache-build.outputs.cache-hit != 'true'
run: yarn build:websockets
- name: Lint
run: yarn lint:websockets
test:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v3
- name: Cache build
id: cache-build
uses: actions/cache@v3
with:
path: packages/**/dist
key: ${{ runner.os }}-websockets-build-${{ github.sha }}
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build
if: steps.cache-build.outputs.cache-hit != 'true'
run: yarn build:websockets
- name: Test
run: yarn test:websockets
publish-aws-ecr:
needs: [ test, lint ]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Cache build
id: cache-build
uses: actions/cache@v3
with:
path: packages/**/dist
key: ${{ runner.os }}-websockets-build-${{ github.sha }}
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build
if: steps.cache-build.outputs.cache-hit != 'true'
run: yarn build:websockets
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: websockets
IMAGE_TAG: ${{ github.sha }}
run: |
yarn docker build @standardnotes/websockets-server -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:latest
docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
publish-docker-hub:
needs: [ test, lint ]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Cache build
id: cache-build
uses: actions/cache@v3
with:
path: packages/**/dist
key: ${{ runner.os }}-websockets-build-${{ github.sha }}
- name: Set up Node
uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
- name: Build
if: steps.cache-build.outputs.cache-hit != 'true'
run: yarn build:websockets
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Publish Docker image as stable
run: |
yarn docker build @standardnotes/websockets-server -t standardnotes/websockets:latest
docker push standardnotes/websockets:latest
deploy-web:
needs: publish-aws-ecr
runs-on: ubuntu-latest
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Download task definition
run: |
aws ecs describe-task-definition --task-definition websockets-prod --query taskDefinition > task-definition.json
- name: Fill in the new version in the Amazon ECS task definition
run: |
jq '(.containerDefinitions[] | select(.name=="websockets-prod") | .environment[] | select(.name=="VERSION")).value = "${{ github.sha }}"' task-definition.json > tmp.json && mv tmp.json task-definition.json
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def-prod
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: websockets-prod
image: ${{ secrets.AWS_ECR_REGISTRY }}/websockets:${{ github.sha }}
- name: Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def-prod.outputs.task-definition }}
service: websockets-prod
cluster: prod
wait-for-service-stability: true
deploy-worker:
needs: publish-aws-ecr
runs-on: ubuntu-latest
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Download task definition
run: |
aws ecs describe-task-definition --task-definition websockets-worker-prod --query taskDefinition > task-definition.json
- name: Fill in the new version in the Amazon ECS task definition
run: |
jq '(.containerDefinitions[] | select(.name=="websockets-worker-prod") | .environment[] | select(.name=="VERSION")).value = "${{ github.sha }}"' task-definition.json > tmp.json && mv tmp.json task-definition.json
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def-prod
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: websockets-worker-prod
image: ${{ secrets.AWS_ECR_REGISTRY }}/websockets:${{ github.sha }}
- name: Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def-prod.outputs.task-definition }}
service: websockets-worker-prod
cluster: prod
wait-for-service-stability: true
newrelic:
needs: [ deploy-web, deploy-worker ]
runs-on: ubuntu-latest
steps:
- name: Create New Relic deployment marker for Web
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_WEBSOCKETS_WEB_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"
- name: Create New Relic deployment marker for Worker
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_WEBSOCKETS_WORKER_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"

46
.github/workflows/websockets.yml vendored Normal file
View File

@@ -0,0 +1,46 @@
name: Websockets Server
concurrency:
group: websockets
cancel-in-progress: true
on:
push:
tags:
- '*standardnotes/websockets-server*'
workflow_dispatch:
jobs:
call_server_application_workflow:
name: Server Application
uses: standardnotes/server/.github/workflows/common-server-application.yml@main
with:
service_name: websockets
workspace_name: "@standardnotes/websockets-server"
e2e_tag_parameter_name: websockets_image_tag
package_path: packages/websockets
secrets: inherit
newrelic:
needs: call_server_application_workflow
runs-on: ubuntu-latest
steps:
- name: Create New Relic deployment marker for Web
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_WEBSOCKETS_WEB_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"
- name: Create New Relic deployment marker for Worker
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_WEBSOCKETS_WORKER_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"

View File

@@ -12,12 +12,11 @@ on:
jobs:
call_server_application_workflow:
name: Build, Test & Deploy Server Application
uses: standardnotes/server/.github/workflows/server-application.yml@main
name: Server Application
uses: standardnotes/server/.github/workflows/common-server-application.yml@main
with:
service_name: workspace
workspace_name: "@standardnotes/workspace-server"
run_e2e_test_suite: true
e2e_tag_parameter_name: workspace_image_tag
package_path: packages/workspace
secrets: inherit

730
.pnp.cjs generated

File diff suppressed because it is too large Load Diff

View File

@@ -14,12 +14,13 @@
"lint": "yarn workspaces foreach -p -j 10 --verbose run lint",
"lint:auth": "yarn workspace @standardnotes/auth-server lint",
"lint:scheduler": "yarn workspace @standardnotes/scheduler-server lint",
"lint:syncing-server": "yarn workspace @standardnotes/syncing-server lint",
"lint:syncing-server-js": "yarn workspace @standardnotes/syncing-server lint",
"lint:files": "yarn workspace @standardnotes/files-server lint",
"lint:api-gateway": "yarn workspace @standardnotes/api-gateway lint",
"lint:event-store": "yarn workspace @standardnotes/event-store lint",
"lint:websockets": "yarn workspace @standardnotes/websockets-server lint",
"lint:workspace": "yarn workspace @standardnotes/workspace-server lint",
"lint:analytics": "yarn workspace @standardnotes/analytics lint",
"clean": "yarn workspaces foreach -p --verbose run clean",
"setup:env": "cp .env.sample .env && yarn workspaces foreach -p --verbose run setup:env",
"start:auth": "yarn workspace @standardnotes/auth-server start",
@@ -32,6 +33,7 @@
"start:api-gateway": "yarn workspace @standardnotes/api-gateway start",
"start:websockets": "yarn workspace @standardnotes/websockets-server start",
"start:workspace": "yarn workspace @standardnotes/workspace-server start",
"start:analytics": "yarn workspace @standardnotes/analytics worker",
"release": "lerna version --conventional-graduate --conventional-commits --yes -m \"chore(release): publish new version\"",
"publish": "lerna publish from-git --yes --no-verify-access --loglevel verbose",
"postversion": "./scripts/push-tags-one-by-one.sh",

View File

@@ -0,0 +1,28 @@
LOG_LEVEL=debug
NODE_ENV=development
DB_HOST=127.0.0.1
DB_REPLICA_HOST=127.0.0.1
DB_PORT=3306
DB_USERNAME=analytics
DB_PASSWORD=changeme123
DB_DATABASE=analytics
DB_DEBUG_LEVEL=all # "all" | "query" | "schema" | "error" | "warn" | "info" | "log" | "migration"
DB_MIGRATIONS_PATH=dist/migrations/*.js
REDIS_URL=redis://cache
REDIS_EVENTS_CHANNEL=events
SNS_TOPIC_ARN=
SNS_AWS_REGION=
SQS_QUEUE_URL=
SQS_AWS_REGION=
# (Optional) New Relic Setup
NEW_RELIC_ENABLED=false
NEW_RELIC_APP_NAME=Analytics
NEW_RELIC_LICENSE_KEY=
NEW_RELIC_NO_CONFIG_FILE=true
NEW_RELIC_DISTRIBUTED_TRACING_ENABLED=false
NEW_RELIC_LOG_ENABLED=false
NEW_RELIC_LOG_LEVEL=info

View File

@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.39.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.38.0...@standardnotes/analytics@1.39.0) (2022-11-04)
### Features
* **analytics:** move the analytics report from api-gateway to analytics ([34e11fd](https://github.com/standardnotes/server/commit/34e11fd5b0ef09a056c90127065c9dfae3e0172a))
# [1.38.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.37.0...@standardnotes/analytics@1.38.0) (2022-11-04)
### Features
* add analytics worker service ([d568432](https://github.com/standardnotes/server/commit/d5684326b1301855d0e07415195d4b246292f9a9))
# [1.37.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.36.0...@standardnotes/analytics@1.37.0) (2022-11-03)
### Features
* **auth:** add analytics for subscription reactivating ([460d6a8](https://github.com/standardnotes/server/commit/460d6a8d0f17eee624feb5d2588086ae6f0996e4))
# [1.36.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.35.1...@standardnotes/analytics@1.36.0) (2022-10-19)
### Features

View File

@@ -0,0 +1,17 @@
FROM node:16.15.1-alpine
RUN apk add --update \
curl \
&& rm -rf /var/cache/apk/*
ENV NODE_ENV production
RUN corepack enable
WORKDIR /workspace
COPY ./ /workspace
ENTRYPOINT [ "/workspace/packages/analytics/docker/entrypoint.sh" ]
CMD [ "start-worker" ]

View File

@@ -4,30 +4,34 @@ import 'newrelic'
import { Logger } from 'winston'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { AnalyticsActivity } from '../src/Domain/Analytics/AnalyticsActivity'
import { Period } from '../src/Domain/Time/Period'
import { StatisticsMeasure } from '../src/Domain/Statistics/StatisticsMeasure'
import { AnalyticsStoreInterface } from '../src/Domain/Analytics/AnalyticsStoreInterface'
import { StatisticsStoreInterface } from '../src/Domain/Statistics/StatisticsStoreInterface'
import { PeriodKeyGeneratorInterface } from '../src/Domain/Time/PeriodKeyGeneratorInterface'
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
import TYPES from '../src/Bootstrap/Types'
import { Env } from '../src/Bootstrap/Env'
import {
DomainEventPublisherInterface,
DailyAnalyticsReportGeneratedEvent,
DomainEventService,
} from '@standardnotes/domain-events'
import {
AnalyticsActivity,
AnalyticsStoreInterface,
Period,
PeriodKeyGeneratorInterface,
StatisticsMeasure,
StatisticsStoreInterface,
} from '@standardnotes/analytics'
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
const requestReport = async (
analyticsStore: AnalyticsStoreInterface,
statisticsStore: StatisticsStoreInterface,
domainEventFactory: DomainEventFactoryInterface,
domainEventPublisher: DomainEventPublisherInterface,
periodKeyGenerator: PeriodKeyGeneratorInterface,
): Promise<void> => {
const analyticsOverTime = []
const analyticsOverTime: Array<{
name: string
period: number
counts: Array<{
periodKey: string
totalCount: number
}>
totalCount: number
}> = []
const thirtyDaysAnalyticsNames = [
AnalyticsActivity.GeneralActivity,
@@ -40,6 +44,7 @@ const requestReport = async (
AnalyticsActivity.SubscriptionRefunded,
AnalyticsActivity.ExistingCustomersChurn,
AnalyticsActivity.NewCustomersChurn,
AnalyticsActivity.SubscriptionReactivated,
]
for (const analyticsName of thirtyDaysAnalyticsNames) {
@@ -68,7 +73,11 @@ const requestReport = async (
}
}
const yesterdayActivityStatistics = []
const yesterdayActivityStatistics: Array<{
name: string
retention: number
totalCount: number
}> = []
const yesterdayActivityNames = [
AnalyticsActivity.LimitedDiscountOfferPurchased,
AnalyticsActivity.GeneralActivity,
@@ -113,7 +122,13 @@ const requestReport = async (
StatisticsMeasure.NewCustomers,
StatisticsMeasure.TotalCustomers,
]
const statisticMeasures = []
const statisticMeasures: Array<{
name: string
totalValue: number
average: number
increments: number
period: number
}> = []
for (const statisticMeasureName of statisticMeasureNames) {
for (const period of [Period.Yesterday, Period.ThisMonth]) {
statisticMeasures.push({
@@ -127,7 +142,11 @@ const requestReport = async (
}
const periodKeys = periodKeyGenerator.getDiscretePeriodKeys(Period.Last7Days)
const retentionOverDays = []
const retentionOverDays: Array<{
firstPeriodKey: string
secondPeriodKey: string
value: number
}> = []
for (let i = 0; i < periodKeys.length; i++) {
for (let j = 0; j < periodKeys.length - i; j++) {
const dailyRetention = await analyticsStore.calculateActivitiesRetention({
@@ -146,7 +165,10 @@ const requestReport = async (
}
const monthlyPeriodKeys = periodKeyGenerator.getDiscretePeriodKeys(Period.ThisYear)
const churnRates = []
const churnRates: Array<{
rate: number
periodKey: string
}> = []
for (const monthPeriodKey of monthlyPeriodKeys) {
const monthPeriod = periodKeyGenerator.convertPeriodKeyToPeriod(monthPeriodKey)
const dailyPeriodKeys = periodKeyGenerator.getDiscretePeriodKeys(monthPeriod)
@@ -178,39 +200,28 @@ const requestReport = async (
})
}
const event: DailyAnalyticsReportGeneratedEvent = {
type: 'DAILY_ANALYTICS_REPORT_GENERATED',
createdAt: new Date(),
meta: {
correlation: {
userIdentifier: '',
userIdentifierType: 'uuid',
},
origin: DomainEventService.ApiGateway,
},
payload: {
applicationStatistics: await statisticsStore.getYesterdayApplicationUsage(),
snjsStatistics: await statisticsStore.getYesterdaySNJSUsage(),
outOfSyncIncidents: await statisticsStore.getYesterdayOutOfSyncIncidents(),
activityStatistics: yesterdayActivityStatistics,
activityStatisticsOverTime: analyticsOverTime,
statisticMeasures,
retentionStatistics: [
{
firstActivity: AnalyticsActivity.Register,
secondActivity: AnalyticsActivity.GeneralActivity,
retention: {
periodKeys,
values: retentionOverDays,
},
const event = domainEventFactory.createDailyAnalyticsReportGeneratedEvent({
applicationStatistics: await statisticsStore.getYesterdayApplicationUsage(),
snjsStatistics: await statisticsStore.getYesterdaySNJSUsage(),
outOfSyncIncidents: await statisticsStore.getYesterdayOutOfSyncIncidents(),
activityStatistics: yesterdayActivityStatistics,
activityStatisticsOverTime: analyticsOverTime,
statisticMeasures,
retentionStatistics: [
{
firstActivity: AnalyticsActivity.Register,
secondActivity: AnalyticsActivity.GeneralActivity,
retention: {
periodKeys,
values: retentionOverDays,
},
],
churn: {
periodKeys: monthlyPeriodKeys,
values: churnRates,
},
],
churn: {
periodKeys: monthlyPeriodKeys,
values: churnRates,
},
}
})
await domainEventPublisher.publish(event)
}
@@ -226,10 +237,13 @@ void container.load().then((container) => {
const analyticsStore: AnalyticsStoreInterface = container.get(TYPES.AnalyticsStore)
const statisticsStore: StatisticsStoreInterface = container.get(TYPES.StatisticsStore)
const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.DomainEventFactory)
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.DomainEventPublisher)
const periodKeyGenerator: PeriodKeyGeneratorInterface = container.get(TYPES.PeriodKeyGenerator)
Promise.resolve(requestReport(analyticsStore, statisticsStore, domainEventPublisher, periodKeyGenerator))
Promise.resolve(
requestReport(analyticsStore, statisticsStore, domainEventFactory, domainEventPublisher, periodKeyGenerator),
)
.then(() => {
logger.info('Usage report generation complete')

View File

@@ -0,0 +1,29 @@
import 'reflect-metadata'
import 'newrelic'
import { Logger } from 'winston'
import { DomainEventSubscriberFactoryInterface } from '@standardnotes/domain-events'
import * as dayjs from 'dayjs'
import * as utc from 'dayjs/plugin/utc'
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
import TYPES from '../src/Bootstrap/Types'
import { Env } from '../src/Bootstrap/Env'
const container = new ContainerConfigLoader()
void container.load().then((container) => {
dayjs.extend(utc)
const env: Env = new Env()
env.load()
const logger: Logger = container.get(TYPES.Logger)
logger.info('Starting worker...')
const subscriberFactory: DomainEventSubscriberFactoryInterface = container.get(TYPES.DomainEventSubscriberFactory)
subscriberFactory.create().start()
setInterval(() => logger.info('Alive and kicking!'), 20 * 60 * 1000)
})

View File

@@ -0,0 +1,22 @@
#!/bin/sh
set -e
COMMAND=$1 && shift 1
case "$COMMAND" in
'start-worker' )
echo "Starting Worker..."
yarn workspace @standardnotes/analytics worker
;;
'report' )
echo "Starting Usage Report Generation..."
yarn workspace @standardnotes/analytics report
;;
* )
echo "Unknown command"
;;
esac
exec "$@"

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/analytics",
"version": "1.36.0",
"version": "1.39.0",
"engines": {
"node": ">=14.0.0 <17.0.0"
},
@@ -20,19 +20,41 @@
"clean": "rm -fr dist",
"build": "tsc --build",
"lint": "eslint . --ext .ts",
"test": "jest spec --coverage"
"lint:fix": "eslint . --ext .ts --fix",
"test": "jest --coverage --config=./jest.config.js --maxWorkers=50%",
"worker": "yarn node dist/bin/worker.js",
"report": "yarn node dist/bin/report.js",
"setup:env": "cp .env.sample .env",
"typeorm": "typeorm-ts-node-commonjs"
},
"devDependencies": {
"@types/ioredis": "^4.28.10",
"@types/jest": "^29.1.1",
"@types/newrelic": "^7.0.3",
"@types/node": "^18.0.0",
"@typescript-eslint/eslint-plugin": "^5.30.0",
"eslint": "^8.14.0",
"eslint-plugin-prettier": "^4.2.1",
"jest": "^29.1.2",
"ts-jest": "^29.0.3",
"typescript": "^4.8.4"
},
"dependencies": {
"@newrelic/winston-enricher": "^4.0.0",
"@sentry/node": "^7.3.0",
"@standardnotes/common": "workspace:*",
"@standardnotes/domain-events": "workspace:*",
"@standardnotes/domain-events-infra": "workspace:*",
"@standardnotes/time": "workspace:*",
"aws-sdk": "^2.1158.0",
"dayjs": "^1.11.6",
"dotenv": "^16.0.1",
"inversify": "^6.0.1",
"ioredis": "^5.2.3",
"reflect-metadata": "^0.1.13"
"mysql2": "^2.3.3",
"newrelic": "^9.0.0",
"reflect-metadata": "^0.1.13",
"typeorm": "^0.3.6",
"winston": "^3.8.1"
}
}

View File

@@ -0,0 +1,166 @@
import * as winston from 'winston'
import Redis from 'ioredis'
import * as AWS from 'aws-sdk'
import { Container } from 'inversify'
import {
DomainEventHandlerInterface,
DomainEventMessageHandlerInterface,
DomainEventSubscriberFactoryInterface,
} from '@standardnotes/domain-events'
import { Env } from './Env'
import TYPES from './Types'
import { AppDataSource } from './DataSource'
import { DomainEventFactory } from '../Domain/Event/DomainEventFactory'
import {
RedisDomainEventPublisher,
RedisDomainEventSubscriberFactory,
RedisEventMessageHandler,
SNSDomainEventPublisher,
SQSDomainEventSubscriberFactory,
SQSEventMessageHandler,
SQSNewRelicEventMessageHandler,
} from '@standardnotes/domain-events-infra'
import { Timer, TimerInterface } from '@standardnotes/time'
import { PeriodKeyGeneratorInterface } from '../Domain/Time/PeriodKeyGeneratorInterface'
import { PeriodKeyGenerator } from '../Domain/Time/PeriodKeyGenerator'
import { AnalyticsStoreInterface } from '../Domain/Analytics/AnalyticsStoreInterface'
import { RedisAnalyticsStore } from '../Infra/Redis/RedisAnalyticsStore'
import { StatisticsStoreInterface } from '../Domain/Statistics/StatisticsStoreInterface'
import { RedisStatisticsStore } from '../Infra/Redis/RedisStatisticsStore'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const newrelicFormatter = require('@newrelic/winston-enricher')
export class ContainerConfigLoader {
async load(): Promise<Container> {
const env: Env = new Env()
env.load()
const container = new Container()
await AppDataSource.initialize()
const redisUrl = env.get('REDIS_URL')
const isRedisInClusterMode = redisUrl.indexOf(',') > 0
let redis
if (isRedisInClusterMode) {
redis = new Redis.Cluster(redisUrl.split(','))
} else {
redis = new Redis(redisUrl)
}
container.bind(TYPES.Redis).toConstantValue(redis)
const newrelicWinstonFormatter = newrelicFormatter(winston)
const winstonFormatters = [winston.format.splat(), winston.format.json()]
if (env.get('NEW_RELIC_ENABLED', true) === 'true') {
winstonFormatters.push(newrelicWinstonFormatter())
}
const logger = winston.createLogger({
level: env.get('LOG_LEVEL') || 'info',
format: winston.format.combine(...winstonFormatters),
transports: [new winston.transports.Console({ level: env.get('LOG_LEVEL') || 'info' })],
})
container.bind<winston.Logger>(TYPES.Logger).toConstantValue(logger)
if (env.get('SNS_AWS_REGION', true)) {
container.bind<AWS.SNS>(TYPES.SNS).toConstantValue(
new AWS.SNS({
apiVersion: 'latest',
region: env.get('SNS_AWS_REGION', true),
}),
)
}
if (env.get('SQS_QUEUE_URL', true)) {
const sqsConfig: AWS.SQS.Types.ClientConfiguration = {
apiVersion: 'latest',
region: env.get('SQS_AWS_REGION', true),
}
if (env.get('SQS_ACCESS_KEY_ID', true) && env.get('SQS_SECRET_ACCESS_KEY', true)) {
sqsConfig.credentials = {
accessKeyId: env.get('SQS_ACCESS_KEY_ID', true),
secretAccessKey: env.get('SQS_SECRET_ACCESS_KEY', true),
}
}
container.bind<AWS.SQS>(TYPES.SQS).toConstantValue(new AWS.SQS(sqsConfig))
}
// env vars
container.bind(TYPES.REDIS_URL).toConstantValue(env.get('REDIS_URL'))
container.bind(TYPES.SNS_TOPIC_ARN).toConstantValue(env.get('SNS_TOPIC_ARN', true))
container.bind(TYPES.SNS_AWS_REGION).toConstantValue(env.get('SNS_AWS_REGION', true))
container.bind(TYPES.SQS_QUEUE_URL).toConstantValue(env.get('SQS_QUEUE_URL', true))
container.bind(TYPES.REDIS_EVENTS_CHANNEL).toConstantValue(env.get('REDIS_EVENTS_CHANNEL'))
container.bind(TYPES.NEW_RELIC_ENABLED).toConstantValue(env.get('NEW_RELIC_ENABLED', true))
// Repositories
// ORM
// Use Case
// Hanlders
// Services
container.bind<DomainEventFactory>(TYPES.DomainEventFactory).to(DomainEventFactory)
container.bind<PeriodKeyGeneratorInterface>(TYPES.PeriodKeyGenerator).toConstantValue(new PeriodKeyGenerator())
container
.bind<AnalyticsStoreInterface>(TYPES.AnalyticsStore)
.toConstantValue(new RedisAnalyticsStore(container.get(TYPES.PeriodKeyGenerator), container.get(TYPES.Redis)))
container
.bind<StatisticsStoreInterface>(TYPES.StatisticsStore)
.toConstantValue(new RedisStatisticsStore(container.get(TYPES.PeriodKeyGenerator), container.get(TYPES.Redis)))
container.bind<TimerInterface>(TYPES.Timer).toConstantValue(new Timer())
if (env.get('SNS_TOPIC_ARN', true)) {
container
.bind<SNSDomainEventPublisher>(TYPES.DomainEventPublisher)
.toConstantValue(new SNSDomainEventPublisher(container.get(TYPES.SNS), container.get(TYPES.SNS_TOPIC_ARN)))
} else {
container
.bind<RedisDomainEventPublisher>(TYPES.DomainEventPublisher)
.toConstantValue(
new RedisDomainEventPublisher(container.get(TYPES.Redis), container.get(TYPES.REDIS_EVENTS_CHANNEL)),
)
}
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([])
if (env.get('SQS_QUEUE_URL', true)) {
container
.bind<DomainEventMessageHandlerInterface>(TYPES.DomainEventMessageHandler)
.toConstantValue(
env.get('NEW_RELIC_ENABLED', true) === 'true'
? new SQSNewRelicEventMessageHandler(eventHandlers, container.get(TYPES.Logger))
: new SQSEventMessageHandler(eventHandlers, container.get(TYPES.Logger)),
)
container
.bind<DomainEventSubscriberFactoryInterface>(TYPES.DomainEventSubscriberFactory)
.toConstantValue(
new SQSDomainEventSubscriberFactory(
container.get(TYPES.SQS),
container.get(TYPES.SQS_QUEUE_URL),
container.get(TYPES.DomainEventMessageHandler),
),
)
} else {
container
.bind<DomainEventMessageHandlerInterface>(TYPES.DomainEventMessageHandler)
.toConstantValue(new RedisEventMessageHandler(eventHandlers, container.get(TYPES.Logger)))
container
.bind<DomainEventSubscriberFactoryInterface>(TYPES.DomainEventSubscriberFactory)
.toConstantValue(
new RedisDomainEventSubscriberFactory(
container.get(TYPES.Redis),
container.get(TYPES.DomainEventMessageHandler),
container.get(TYPES.REDIS_EVENTS_CHANNEL),
),
)
}
return container
}
}

View File

@@ -0,0 +1,40 @@
import { DataSource, LoggerOptions } from 'typeorm'
import { Env } from './Env'
const env: Env = new Env()
env.load()
const maxQueryExecutionTime = env.get('DB_MAX_QUERY_EXECUTION_TIME', true)
? +env.get('DB_MAX_QUERY_EXECUTION_TIME', true)
: 45_000
export const AppDataSource = new DataSource({
type: 'mysql',
charset: 'utf8mb4',
supportBigNumbers: true,
bigNumberStrings: false,
maxQueryExecutionTime,
replication: {
master: {
host: env.get('DB_HOST'),
port: parseInt(env.get('DB_PORT')),
username: env.get('DB_USERNAME'),
password: env.get('DB_PASSWORD'),
database: env.get('DB_DATABASE'),
},
slaves: [
{
host: env.get('DB_REPLICA_HOST'),
port: parseInt(env.get('DB_PORT')),
username: env.get('DB_USERNAME'),
password: env.get('DB_PASSWORD'),
database: env.get('DB_DATABASE'),
},
],
removeNodeErrorCount: 10,
},
entities: [],
migrations: [env.get('DB_MIGRATIONS_PATH', true) ?? 'dist/migrations/*.js'],
migrationsRun: true,
logging: <LoggerOptions>env.get('DB_DEBUG_LEVEL'),
})

View File

@@ -0,0 +1,24 @@
import { config, DotenvParseOutput } from 'dotenv'
import { injectable } from 'inversify'
@injectable()
export class Env {
private env?: DotenvParseOutput
public load(): void {
const output = config()
this.env = <DotenvParseOutput>output.parsed
}
public get(key: string, optional = false): string {
if (!this.env) {
this.load()
}
if (!process.env[key] && !optional) {
throw new Error(`Environment variable ${key} not set`)
}
return <string>process.env[key]
}
}

View File

@@ -0,0 +1,29 @@
const TYPES = {
Logger: Symbol.for('Logger'),
Redis: Symbol.for('Redis'),
SNS: Symbol.for('SNS'),
SQS: Symbol.for('SQS'),
// env vars
REDIS_URL: Symbol.for('REDIS_URL'),
SNS_TOPIC_ARN: Symbol.for('SNS_TOPIC_ARN'),
SNS_AWS_REGION: Symbol.for('SNS_AWS_REGION'),
SQS_QUEUE_URL: Symbol.for('SQS_QUEUE_URL'),
SQS_AWS_REGION: Symbol.for('SQS_AWS_REGION'),
REDIS_EVENTS_CHANNEL: Symbol.for('REDIS_EVENTS_CHANNEL'),
NEW_RELIC_ENABLED: Symbol.for('NEW_RELIC_ENABLED'),
// Repositories
// ORM
// Use Case
// Handlers
// Services
DomainEventPublisher: Symbol.for('DomainEventPublisher'),
DomainEventSubscriberFactory: Symbol.for('DomainEventSubscriberFactory'),
DomainEventFactory: Symbol.for('DomainEventFactory'),
DomainEventMessageHandler: Symbol.for('DomainEventMessageHandler'),
AnalyticsStore: Symbol.for('AnalyticsStore'),
StatisticsStore: Symbol.for('StatisticsStore'),
Timer: Symbol.for('Timer'),
PeriodKeyGenerator: Symbol.for('PeriodKeyGenerator'),
}
export default TYPES

View File

@@ -12,6 +12,7 @@ export enum AnalyticsActivity {
SubscriptionRefunded = 'subscription-refunded',
SubscriptionCancelled = 'subscription-cancelled',
SubscriptionExpired = 'subscription-expired',
SubscriptionReactivated = 'subscription-reactivated',
EmailUnbackedUpData = 'email-unbacked-up-data',
EmailBackup = 'email-backup',
LimitedDiscountOfferPurchased = 'limited-discount-offer-purchased',

View File

@@ -0,0 +1,174 @@
import 'reflect-metadata'
import { TimerInterface } from '@standardnotes/time'
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
import { Period } from '../Time/Period'
import { DomainEventFactory } from './DomainEventFactory'
describe('DomainEventFactory', () => {
let timer: TimerInterface
const createFactory = () => new DomainEventFactory(timer)
beforeEach(() => {
timer = {} as jest.Mocked<TimerInterface>
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(1)
timer.getUTCDate = jest.fn().mockReturnValue(new Date(1))
})
it('should create a DAILY_ANALYTICS_REPORT_GENERATED event', () => {
expect(
createFactory().createDailyAnalyticsReportGeneratedEvent({
snjsStatistics: [
{
version: '1-2-3',
count: 2,
},
],
applicationStatistics: [
{
version: '2-3-4',
count: 45,
},
],
activityStatistics: [
{
name: AnalyticsActivity.Register,
retention: 24,
totalCount: 45,
},
],
statisticMeasures: [
{
name: StatisticsMeasure.Income,
totalValue: 43,
average: 23,
increments: 5,
period: Period.Today,
},
],
activityStatisticsOverTime: [
{
name: AnalyticsActivity.Register,
period: Period.Last30Days,
counts: [
{
periodKey: '2022-10-9',
totalCount: 3,
},
],
totalCount: 123,
},
],
outOfSyncIncidents: 324,
retentionStatistics: [
{
firstActivity: AnalyticsActivity.Register,
secondActivity: AnalyticsActivity.Login,
retention: {
periodKeys: ['2022-10-9'],
values: [
{
firstPeriodKey: AnalyticsActivity.Register,
secondPeriodKey: AnalyticsActivity.Login,
value: 12,
},
],
},
},
],
churn: {
periodKeys: ['2022-10-9'],
values: [
{
rate: 12,
periodKey: '2022-10-9',
},
],
},
}),
).toEqual({
createdAt: expect.any(Date),
meta: {
correlation: {
userIdentifier: '',
userIdentifierType: 'uuid',
},
origin: 'analytics',
},
payload: {
activityStatistics: [
{
name: 'register',
retention: 24,
totalCount: 45,
},
],
activityStatisticsOverTime: [
{
counts: [
{
periodKey: '2022-10-9',
totalCount: 3,
},
],
name: 'register',
period: 9,
totalCount: 123,
},
],
applicationStatistics: [
{
count: 45,
version: '2-3-4',
},
],
churn: {
periodKeys: ['2022-10-9'],
values: [
{
periodKey: '2022-10-9',
rate: 12,
},
],
},
outOfSyncIncidents: 324,
retentionStatistics: [
{
firstActivity: 'register',
retention: {
periodKeys: ['2022-10-9'],
values: [
{
firstPeriodKey: 'register',
secondPeriodKey: 'login',
value: 12,
},
],
},
secondActivity: 'login',
},
],
snjsStatistics: [
{
count: 2,
version: '1-2-3',
},
],
statisticMeasures: [
{
average: 23,
increments: 5,
name: 'income',
period: 0,
totalValue: 43,
},
],
},
type: 'DAILY_ANALYTICS_REPORT_GENERATED',
})
})
})

View File

@@ -0,0 +1,75 @@
import { DomainEventService, DailyAnalyticsReportGeneratedEvent } from '@standardnotes/domain-events'
import { TimerInterface } from '@standardnotes/time'
import { inject, injectable } from 'inversify'
import TYPES from '../../Bootstrap/Types'
import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
@injectable()
export class DomainEventFactory implements DomainEventFactoryInterface {
constructor(@inject(TYPES.Timer) private timer: TimerInterface) {}
createDailyAnalyticsReportGeneratedEvent(dto: {
snjsStatistics: Array<{
version: string
count: number
}>
applicationStatistics: Array<{
version: string
count: number
}>
activityStatistics: Array<{
name: string
retention: number
totalCount: number
}>
statisticMeasures: Array<{
name: string
totalValue: number
average: number
increments: number
period: number
}>
activityStatisticsOverTime: Array<{
name: string
period: number
counts: Array<{
periodKey: string
totalCount: number
}>
totalCount: number
}>
outOfSyncIncidents: number
retentionStatistics: Array<{
firstActivity: string
secondActivity: string
retention: {
periodKeys: Array<string>
values: Array<{
firstPeriodKey: string
secondPeriodKey: string
value: number
}>
}
}>
churn: {
periodKeys: Array<string>
values: Array<{
rate: number
periodKey: string
}>
}
}): DailyAnalyticsReportGeneratedEvent {
return {
type: 'DAILY_ANALYTICS_REPORT_GENERATED',
createdAt: this.timer.getUTCDate(),
meta: {
correlation: {
userIdentifier: '',
userIdentifierType: 'uuid',
},
origin: DomainEventService.Analytics,
},
payload: dto,
}
}
}

View File

@@ -0,0 +1,55 @@
import { DailyAnalyticsReportGeneratedEvent } from '@standardnotes/domain-events'
export interface DomainEventFactoryInterface {
createDailyAnalyticsReportGeneratedEvent(dto: {
snjsStatistics: Array<{
version: string
count: number
}>
applicationStatistics: Array<{
version: string
count: number
}>
activityStatistics: Array<{
name: string
retention: number
totalCount: number
}>
statisticMeasures: Array<{
name: string
totalValue: number
average: number
increments: number
period: number
}>
activityStatisticsOverTime: Array<{
name: string
period: number
counts: Array<{
periodKey: string
totalCount: number
}>
totalCount: number
}>
outOfSyncIncidents: number
retentionStatistics: Array<{
firstActivity: string
secondActivity: string
retention: {
periodKeys: Array<string>
values: Array<{
firstPeriodKey: string
secondPeriodKey: string
value: number
}>
}
}>
churn: {
periodKeys: Array<string>
values: Array<{
rate: number
periodKey: string
}>
}
}): DailyAnalyticsReportGeneratedEvent
}

View File

@@ -3,6 +3,64 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.36.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.35.1...@standardnotes/api-gateway@1.36.0) (2022-11-04)
### Features
* **analytics:** move the analytics report from api-gateway to analytics ([34e11fd](https://github.com/standardnotes/api-gateway/commit/34e11fd5b0ef09a056c90127065c9dfae3e0172a))
## [1.35.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.35.0...@standardnotes/api-gateway@1.35.1) (2022-11-04)
**Note:** Version bump only for package @standardnotes/api-gateway
# [1.35.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.34.1...@standardnotes/api-gateway@1.35.0) (2022-11-03)
### Features
* **auth:** add analytics for subscription reactivating ([460d6a8](https://github.com/standardnotes/api-gateway/commit/460d6a8d0f17eee624feb5d2588086ae6f0996e4))
## [1.34.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.34.0...@standardnotes/api-gateway@1.34.1) (2022-11-03)
**Note:** Version bump only for package @standardnotes/api-gateway
# [1.34.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.33.6...@standardnotes/api-gateway@1.34.0) (2022-11-02)
### Features
* **auth:** add processing user requests ([2255f85](https://github.com/standardnotes/api-gateway/commit/2255f856f928e855ac94f8aca4e1fb81047f58f7))
## [1.33.6](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.33.5...@standardnotes/api-gateway@1.33.6) (2022-11-02)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.33.5](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.33.4...@standardnotes/api-gateway@1.33.5) (2022-11-01)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.33.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.33.3...@standardnotes/api-gateway@1.33.4) (2022-10-31)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.33.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.33.2...@standardnotes/api-gateway@1.33.3) (2022-10-31)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.33.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.33.1...@standardnotes/api-gateway@1.33.2) (2022-10-26)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.33.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.33.0...@standardnotes/api-gateway@1.33.1) (2022-10-24)
### Bug Fixes
* **api-gateway:** remove invite declining endpoint ([23b05ca](https://github.com/standardnotes/api-gateway/commit/23b05caea2197e36fa446ffb3c9a5e7598224f3e))
# [1.33.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.32.0...@standardnotes/api-gateway@1.33.0) (2022-10-24)
### Features
* **auth:** change accepting invitations to be an authorized endpoint ([771a555](https://github.com/standardnotes/api-gateway/commit/771a555b4f33452311cd5bf0b8cfcbc4f2f1c4dd))
# [1.32.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.31.2...@standardnotes/api-gateway@1.32.0) (2022-10-19)
### Features

View File

@@ -16,11 +16,6 @@ case "$COMMAND" in
yarn workspace @standardnotes/api-gateway start
;;
'report' )
echo "Starting Usage Report Generation..."
yarn workspace @standardnotes/api-gateway report
;;
* )
echo "Unknown command"
;;

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api-gateway",
"version": "1.32.0",
"version": "1.36.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -17,7 +17,6 @@
"lint": "eslint . --ext .ts",
"setup:env": "cp .env.sample .env",
"start": "yarn node dist/bin/server.js",
"report": "yarn node dist/bin/report.js",
"upgrade:snjs": "yarn ncu -u '@standardnotes/*'"
},
"dependencies": {

View File

@@ -26,17 +26,8 @@ export class SubscriptionInvitesController extends BaseHttpController {
await this.httpService.callAuthServer(request, response, `subscription-invites/${request.params.inviteUuid}`)
}
@httpGet('/:inviteUuid/accept')
@httpPost('/:inviteUuid/accept', TYPES.AuthMiddleware)
async acceptInvite(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, `subscription-invites/${request.params.inviteUuid}/accept`)
}
@httpGet('/:inviteUuid/decline')
async declineInvite(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
response,
`subscription-invites/${request.params.inviteUuid}/decline`,
)
}
}

View File

@@ -147,4 +147,9 @@ export class UsersController extends BaseHttpController {
async deleteUser(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/account', request.body)
}
@httpPost('/:userUuid/requests', TYPES.AuthMiddleware)
async submitRequest(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, `users/${request.params.userUuid}/requests`, request.body)
}
}

View File

@@ -3,6 +3,86 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.49.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.49.2...@standardnotes/auth-server@1.49.3) (2022-11-04)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.49.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.49.1...@standardnotes/auth-server@1.49.2) (2022-11-04)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.49.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.49.0...@standardnotes/auth-server@1.49.1) (2022-11-03)
### Bug Fixes
* **auth:** updating offline subscription end date ([2504887](https://github.com/standardnotes/server/commit/2504887e8d2d06608927e17a766cf0794dda2d4d))
# [1.49.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.48.2...@standardnotes/auth-server@1.49.0) (2022-11-03)
### Features
* **auth:** add analytics for subscription reactivating ([460d6a8](https://github.com/standardnotes/server/commit/460d6a8d0f17eee624feb5d2588086ae6f0996e4))
## [1.48.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.48.1...@standardnotes/auth-server@1.48.2) (2022-11-03)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.48.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.48.0...@standardnotes/auth-server@1.48.1) (2022-11-02)
### Bug Fixes
* **auth:** controller name ([5b0d9dd](https://github.com/standardnotes/server/commit/5b0d9dd3949d4da9d5ac3ca86a0e54ead2ce730d))
# [1.48.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.47.7...@standardnotes/auth-server@1.48.0) (2022-11-02)
### Features
* **auth:** add processing user requests ([2255f85](https://github.com/standardnotes/server/commit/2255f856f928e855ac94f8aca4e1fb81047f58f7))
## [1.47.7](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.47.6...@standardnotes/auth-server@1.47.7) (2022-11-02)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.47.6](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.47.5...@standardnotes/auth-server@1.47.6) (2022-11-01)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.47.5](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.47.4...@standardnotes/auth-server@1.47.5) (2022-11-01)
### Bug Fixes
* force utf8mb4 charset on typeorm ([5160cc3](https://github.com/standardnotes/server/commit/5160cc36ddc9e30551d5ad40a9e210d87091eec3))
## [1.47.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.47.3...@standardnotes/auth-server@1.47.4) (2022-10-31)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.47.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.47.2...@standardnotes/auth-server@1.47.3) (2022-10-31)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.47.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.47.1...@standardnotes/auth-server@1.47.2) (2022-10-26)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.47.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.47.0...@standardnotes/auth-server@1.47.1) (2022-10-24)
### Bug Fixes
* **auth:** accepting shared subscription for inviters with multiple subscriptions ([f8433c1](https://github.com/standardnotes/server/commit/f8433c106fe593f9132558d080b88d08cf9cfe86))
# [1.47.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.46.1...@standardnotes/auth-server@1.47.0) (2022-10-24)
### Features
* **auth:** change accepting invitations to be an authorized endpoint ([771a555](https://github.com/standardnotes/server/commit/771a555b4f33452311cd5bf0b8cfcbc4f2f1c4dd))
## [1.46.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.46.0...@standardnotes/auth-server@1.46.1) (2022-10-20)
### Bug Fixes
* **auth:** additional response message to accepting invitation ([126e6a3](https://github.com/standardnotes/server/commit/126e6a3c2f99c8e3ec994d8ce50d008b0f0ec43f))
# [1.46.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.45.0...@standardnotes/auth-server@1.46.0) (2022-10-19)
### Bug Fixes

View File

@@ -20,6 +20,7 @@ import '../src/Controller/SubscriptionSettingsController'
import '../src/Infra/InversifyExpressUtils/InversifyExpressAuthController'
import '../src/Infra/InversifyExpressUtils/InversifyExpressSubscriptionInvitesController'
import '../src/Infra/InversifyExpressUtils/InversifyExpressUserRequestsController'
import '../src/Infra/InversifyExpressUtils/InversifyExpressWebSocketsController'
import * as cors from 'cors'

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.46.0",
"version": "1.49.3",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -33,7 +33,7 @@
"@newrelic/winston-enricher": "^4.0.0",
"@sentry/node": "^7.3.0",
"@standardnotes/analytics": "workspace:*",
"@standardnotes/api": "^1.16.0",
"@standardnotes/api": "^1.19.0",
"@standardnotes/common": "workspace:*",
"@standardnotes/domain-events": "workspace:*",
"@standardnotes/domain-events-infra": "workspace:*",
@@ -49,7 +49,7 @@
"axios": "^0.27.2",
"bcryptjs": "2.4.3",
"cors": "2.8.5",
"dayjs": "^1.11.3",
"dayjs": "^1.11.6",
"dotenv": "^16.0.1",
"express": "^4.18.1",
"inversify": "^6.0.1",

View File

@@ -203,6 +203,9 @@ import { PaymentSuccessEventHandler } from '../Domain/Handler/PaymentSuccessEven
import { RefundProcessedEventHandler } from '../Domain/Handler/RefundProcessedEventHandler'
import { SubscriptionInvitesController } from '../Controller/SubscriptionInvitesController'
import { CreateCrossServiceToken } from '../Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken'
import { ProcessUserRequest } from '../Domain/UseCase/ProcessUserRequest/ProcessUserRequest'
import { UserRequestsController } from '../Controller/UserRequestsController'
import { SubscriptionReactivatedEventHandler } from '../Domain/Handler/SubscriptionReactivatedEventHandler'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const newrelicFormatter = require('@newrelic/winston-enricher')
@@ -266,6 +269,7 @@ export class ContainerConfigLoader {
// Controller
container.bind<AuthController>(TYPES.AuthController).to(AuthController)
container.bind<SubscriptionInvitesController>(TYPES.SubscriptionInvitesController).to(SubscriptionInvitesController)
container.bind<UserRequestsController>(TYPES.UserRequestsController).to(UserRequestsController)
// Repositories
container.bind<SessionRepositoryInterface>(TYPES.SessionRepository).to(MySQLSessionRepository)
@@ -438,6 +442,7 @@ export class ContainerConfigLoader {
container.bind<GetUserAnalyticsId>(TYPES.GetUserAnalyticsId).to(GetUserAnalyticsId)
container.bind<VerifyPredicate>(TYPES.VerifyPredicate).to(VerifyPredicate)
container.bind<CreateCrossServiceToken>(TYPES.CreateCrossServiceToken).to(CreateCrossServiceToken)
container.bind<ProcessUserRequest>(TYPES.ProcessUserRequest).to(ProcessUserRequest)
// Handlers
container.bind<UserRegisteredEventHandler>(TYPES.UserRegisteredEventHandler).to(UserRegisteredEventHandler)
@@ -489,6 +494,9 @@ export class ContainerConfigLoader {
container.bind<PaymentFailedEventHandler>(TYPES.PaymentFailedEventHandler).to(PaymentFailedEventHandler)
container.bind<PaymentSuccessEventHandler>(TYPES.PaymentSuccessEventHandler).to(PaymentSuccessEventHandler)
container.bind<RefundProcessedEventHandler>(TYPES.RefundProcessedEventHandler).to(RefundProcessedEventHandler)
container
.bind<SubscriptionReactivatedEventHandler>(TYPES.SubscriptionReactivatedEventHandler)
.to(SubscriptionReactivatedEventHandler)
// Services
container.bind<UAParser>(TYPES.DeviceDetector).toConstantValue(new UAParser())
@@ -600,6 +608,7 @@ export class ContainerConfigLoader {
['PAYMENT_FAILED', container.get(TYPES.PaymentFailedEventHandler)],
['PAYMENT_SUCCESS', container.get(TYPES.PaymentSuccessEventHandler)],
['REFUND_PROCESSED', container.get(TYPES.RefundProcessedEventHandler)],
['SUBSCRIPTION_REACTIVATED', container.get(TYPES.SubscriptionReactivatedEventHandler)],
])
if (env.get('SQS_QUEUE_URL', true)) {

View File

@@ -22,6 +22,7 @@ const maxQueryExecutionTime = env.get('DB_MAX_QUERY_EXECUTION_TIME', true)
export const AppDataSource = new DataSource({
type: 'mysql',
charset: 'utf8mb4',
supportBigNumbers: true,
bigNumberStrings: false,
maxQueryExecutionTime,

View File

@@ -6,6 +6,7 @@ const TYPES = {
// Controller
AuthController: Symbol.for('AuthController'),
SubscriptionInvitesController: Symbol.for('SubscriptionInvitesController'),
UserRequestsController: Symbol.for('UserRequestsController'),
// Repositories
UserRepository: Symbol.for('UserRepository'),
SessionRepository: Symbol.for('SessionRepository'),
@@ -121,6 +122,7 @@ const TYPES = {
GetUserAnalyticsId: Symbol.for('GetUserAnalyticsId'),
VerifyPredicate: Symbol.for('VerifyPredicate'),
CreateCrossServiceToken: Symbol.for('CreateCrossServiceToken'),
ProcessUserRequest: Symbol.for('ProcessUserRequest'),
// Handlers
UserRegisteredEventHandler: Symbol.for('UserRegisteredEventHandler'),
AccountDeletionRequestedEventHandler: Symbol.for('AccountDeletionRequestedEventHandler'),
@@ -143,6 +145,7 @@ const TYPES = {
PaymentFailedEventHandler: Symbol.for('PaymentFailedEventHandler'),
PaymentSuccessEventHandler: Symbol.for('PaymentSuccessEventHandler'),
RefundProcessedEventHandler: Symbol.for('RefundProcessedEventHandler'),
SubscriptionReactivatedEventHandler: Symbol.for('SubscriptionReactivatedEventHandler'),
// Services
DeviceDetector: Symbol.for('DeviceDetector'),
SessionService: Symbol.for('SessionService'),

View File

@@ -109,4 +109,15 @@ describe('AuthController', () => {
expect(response.status).toEqual(400)
})
it('should throw error on the delete user method as it is still a part of the payments server', async () => {
let caughtError = null
try {
await createController().deleteAccount({ userUuid: '1-2-3' })
} catch (error) {
caughtError = error
}
expect(caughtError).not.toBeNull()
})
})

View File

@@ -2,6 +2,7 @@ import { inject, injectable } from 'inversify'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import {
HttpStatusCode,
UserDeletionResponse,
UserRegistrationRequestParams,
UserRegistrationResponse,
UserServerInterface,
@@ -12,6 +13,7 @@ import { ClearLoginAttempts } from '../Domain/UseCase/ClearLoginAttempts'
import { Register } from '../Domain/UseCase/Register'
import { DomainEventFactoryInterface } from '../Domain/Event/DomainEventFactoryInterface'
import { ProtocolVersion } from '@standardnotes/common'
import { UserDeletionRequestParams } from '@standardnotes/api/dist/Domain/Request/User/UserDeletionRequestParams'
@injectable()
export class AuthController implements UserServerInterface {
@@ -22,6 +24,10 @@ export class AuthController implements UserServerInterface {
@inject(TYPES.DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface,
) {}
async deleteAccount(_params: UserDeletionRequestParams): Promise<UserDeletionResponse> {
throw new Error('This method is implemented on the payments server.')
}
async register(params: UserRegistrationRequestParams): Promise<UserRegistrationResponse> {
if (!params.email || !params.password) {
return {

View File

@@ -0,0 +1,43 @@
import 'reflect-metadata'
import { UserRequestType } from '@standardnotes/common'
import { ProcessUserRequest } from '../Domain/UseCase/ProcessUserRequest/ProcessUserRequest'
import { UserRequestsController } from './UserRequestsController'
describe('UserRequestsController', () => {
let processUserRequest: ProcessUserRequest
const createController = () => new UserRequestsController(processUserRequest)
beforeEach(() => {
processUserRequest = {} as jest.Mocked<ProcessUserRequest>
processUserRequest.execute = jest.fn().mockReturnValue({ success: true })
})
it('should process user request', async () => {
expect(
await createController().submitUserRequest({
userUuid: '1-2-3',
requestType: UserRequestType.ExitDiscount,
}),
).toEqual({
status: 200,
data: { success: true },
})
})
it('should not process user request', async () => {
processUserRequest.execute = jest.fn().mockReturnValue({ success: false })
expect(
await createController().submitUserRequest({
userUuid: '1-2-3',
requestType: UserRequestType.ExitDiscount,
}),
).toEqual({
status: 400,
data: { success: false },
})
})
})

View File

@@ -0,0 +1,34 @@
import {
HttpStatusCode,
UserRequestRequestParams,
UserRequestResponse,
UserRequestServerInterface,
} from '@standardnotes/api'
import { inject, injectable } from 'inversify'
import TYPES from '../Bootstrap/Types'
import { ProcessUserRequest } from '../Domain/UseCase/ProcessUserRequest/ProcessUserRequest'
@injectable()
export class UserRequestsController implements UserRequestServerInterface {
constructor(@inject(TYPES.ProcessUserRequest) private processUserRequest: ProcessUserRequest) {}
async submitUserRequest(params: UserRequestRequestParams): Promise<UserRequestResponse> {
const result = await this.processUserRequest.execute({
requestType: params.requestType,
userEmail: params.userEmail as string,
userUuid: params.userUuid,
})
if (!result.success) {
return {
status: HttpStatusCode.BadRequest,
data: result,
}
}
return {
status: HttpStatusCode.Success,
data: result,
}
}
}

View File

@@ -18,6 +18,29 @@ describe('DomainEventFactory', () => {
timer.getUTCDate = jest.fn().mockReturnValue(new Date(1))
})
it('should create a EXIT_DISCOUNT_APPLY_REQUESTED event', () => {
expect(
createFactory().createExitDiscountApplyRequestedEvent({
userEmail: 'test@test.te',
discountCode: 'exit-20',
}),
).toEqual({
createdAt: expect.any(Date),
meta: {
correlation: {
userIdentifier: 'test@test.te',
userIdentifierType: 'email',
},
origin: 'auth',
},
payload: {
userEmail: 'test@test.te',
discountCode: 'exit-20',
},
type: 'EXIT_DISCOUNT_APPLY_REQUESTED',
})
})
it('should create a WEB_SOCKET_MESSAGE_REQUESTED event', () => {
expect(
createFactory().createWebSocketMessageRequestedEvent({

View File

@@ -16,6 +16,7 @@ import {
DomainEventService,
EmailMessageRequestedEvent,
WebSocketMessageRequestedEvent,
ExitDiscountApplyRequestedEvent,
} from '@standardnotes/domain-events'
import { Predicate, PredicateVerificationResult } from '@standardnotes/predicates'
import { TimerInterface } from '@standardnotes/time'
@@ -28,6 +29,24 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
export class DomainEventFactory implements DomainEventFactoryInterface {
constructor(@inject(TYPES.Timer) private timer: TimerInterface) {}
createExitDiscountApplyRequestedEvent(dto: {
userEmail: string
discountCode: string
}): ExitDiscountApplyRequestedEvent {
return {
type: 'EXIT_DISCOUNT_APPLY_REQUESTED',
createdAt: this.timer.getUTCDate(),
meta: {
correlation: {
userIdentifier: dto.userEmail,
userIdentifierType: 'email',
},
origin: DomainEventService.Auth,
},
payload: dto,
}
}
createWebSocketMessageRequestedEvent(dto: { userUuid: Uuid; message: JSONString }): WebSocketMessageRequestedEvent {
return {
type: 'WEB_SOCKET_MESSAGE_REQUESTED',

View File

@@ -16,6 +16,7 @@ import {
PredicateVerifiedEvent,
EmailMessageRequestedEvent,
WebSocketMessageRequestedEvent,
ExitDiscountApplyRequestedEvent,
} from '@standardnotes/domain-events'
import { InviteeIdentifierType } from '../SharedSubscription/InviteeIdentifierType'
@@ -83,4 +84,8 @@ export interface DomainEventFactoryInterface {
predicate: Predicate
predicateVerificationResult: PredicateVerificationResult
}): PredicateVerifiedEvent
createExitDiscountApplyRequestedEvent(dto: {
userEmail: string
discountCode: string
}): ExitDiscountApplyRequestedEvent
}

View File

@@ -91,7 +91,9 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
[Period.Today, Period.ThisWeek, Period.ThisMonth],
)
const limitedDiscountPurchased = ['limited-10', 'limited-20'].includes(event.payload.discountCode as string)
const limitedDiscountPurchased = ['limited-10', 'limited-20', 'exit-20'].includes(
event.payload.discountCode as string,
)
if (limitedDiscountPurchased) {
await this.analyticsStore.markActivity([AnalyticsActivity.LimitedDiscountOfferPurchased], analyticsId, [
Period.Today,

View File

@@ -0,0 +1,78 @@
import 'reflect-metadata'
import { RoleName, SubscriptionName } from '@standardnotes/common'
import { SubscriptionReactivatedEvent } from '@standardnotes/domain-events'
import { Logger } from 'winston'
import { User } from '../User/User'
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
import { SubscriptionReactivatedEventHandler } from './SubscriptionReactivatedEventHandler'
import { AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
describe('SubscriptionReactivatedEventHandler', () => {
let userRepository: UserRepositoryInterface
let logger: Logger
let user: User
let event: SubscriptionReactivatedEvent
let getUserAnalyticsId: GetUserAnalyticsId
let analyticsStore: AnalyticsStoreInterface
const createHandler = () =>
new SubscriptionReactivatedEventHandler(userRepository, analyticsStore, getUserAnalyticsId, logger)
beforeEach(() => {
user = {
uuid: '123',
email: 'test@test.com',
roles: Promise.resolve([
{
name: RoleName.ProUser,
},
]),
} as jest.Mocked<User>
userRepository = {} as jest.Mocked<UserRepositoryInterface>
userRepository.findOneByEmail = jest.fn().mockReturnValue(user)
userRepository.save = jest.fn().mockReturnValue(user)
event = {} as jest.Mocked<SubscriptionReactivatedEvent>
event.createdAt = new Date(1)
event.payload = {
previousSubscriptionId: 1,
currentSubscriptionId: 2,
userEmail: 'test@test.com',
subscriptionName: SubscriptionName.PlusPlan,
subscriptionExpiresAt: 5,
discountCode: 'exit-20',
}
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
analyticsStore.markActivity = jest.fn()
logger = {} as jest.Mocked<Logger>
logger.info = jest.fn()
logger.warn = jest.fn()
})
it('should mark subscription reactivated activity for analytics', async () => {
await createHandler().handle(event)
expect(analyticsStore.markActivity).toHaveBeenCalledWith(['subscription-reactivated'], 3, [
Period.Today,
Period.ThisWeek,
Period.ThisMonth,
])
})
it('should not do anything if no user is found for specified email', async () => {
userRepository.findOneByEmail = jest.fn().mockReturnValue(null)
await createHandler().handle(event)
expect(analyticsStore.markActivity).not.toHaveBeenCalled()
})
})

View File

@@ -0,0 +1,34 @@
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
import { DomainEventHandlerInterface, SubscriptionReactivatedEvent } from '@standardnotes/domain-events'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
import TYPES from '../../Bootstrap/Types'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
@injectable()
export class SubscriptionReactivatedEventHandler implements DomainEventHandlerInterface {
constructor(
@inject(TYPES.UserRepository) private userRepository: UserRepositoryInterface,
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
@inject(TYPES.Logger) private logger: Logger,
) {}
async handle(event: SubscriptionReactivatedEvent): Promise<void> {
const user = await this.userRepository.findOneByEmail(event.payload.userEmail)
if (user === null) {
this.logger.warn(`Could not find user with email: ${event.payload.userEmail}`)
return
}
const { analyticsId } = await this.getUserAnalyticsId.execute({ userUuid: user.uuid })
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionReactivated], analyticsId, [
Period.Today,
Period.ThisWeek,
Period.ThisMonth,
])
}
}

View File

@@ -37,7 +37,11 @@ export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterf
return
}
await this.updateOfflineSubscriptionEndsAt(offlineUserSubscription, event.payload.timestamp)
await this.updateOfflineSubscriptionEndsAt(
offlineUserSubscription,
event.payload.subscriptionExpiresAt,
event.payload.timestamp,
)
await this.roleService.setOfflineUserRole(offlineUserSubscription)
@@ -92,9 +96,10 @@ export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterf
private async updateOfflineSubscriptionEndsAt(
offlineUserSubscription: OfflineUserSubscription,
subscriptionExpiresAt: number,
timestamp: number,
): Promise<void> {
offlineUserSubscription.endsAt = timestamp
offlineUserSubscription.endsAt = subscriptionExpiresAt
offlineUserSubscription.updatedAt = timestamp
await this.offlineUserSubscriptionRepository.save(offlineUserSubscription)

View File

@@ -108,6 +108,47 @@ describe('AcceptSharedSubscriptionInvitation', () => {
)
})
it('should create a shared subscription upon accepting the invitation if inviter has a second subscription', async () => {
const inviterSubscription1 = { endsAt: 1, planName: SubscriptionName.PlusPlan } as jest.Mocked<UserSubscription>
const inviterSubscription2 = { endsAt: 5, planName: SubscriptionName.PlusPlan } as jest.Mocked<UserSubscription>
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(3)
userSubscriptionRepository.findBySubscriptionIdAndType = jest
.fn()
.mockReturnValue([inviterSubscription1, inviterSubscription2])
expect(
await createUseCase().execute({
sharedSubscriptionInvitationUuid: '1-2-3',
}),
).toEqual({
success: true,
})
expect(sharedSubscriptionInvitationRepository.save).toHaveBeenCalledWith({
status: 'accepted',
subscriptionId: 3,
updatedAt: 3,
})
expect(userSubscriptionRepository.save).toHaveBeenCalledWith({
cancelled: false,
createdAt: 3,
endsAt: 5,
planName: 'PLUS_PLAN',
subscriptionId: 3,
subscriptionType: 'shared',
updatedAt: 3,
user: Promise.resolve(invitee),
})
expect(roleService.addUserRole).toHaveBeenCalledWith(invitee, 'PLUS_PLAN')
expect(subscriptionSettingService.applyDefaultSubscriptionSettingsForSubscription).toHaveBeenCalledWith(
inviteeSubscription,
'PLUS_PLAN',
'123',
)
})
it('should not create a shared subscription if invitiation is not found', async () => {
sharedSubscriptionInvitationRepository.findOneByUuidAndStatus = jest.fn().mockReturnValue(null)
expect(
@@ -116,6 +157,7 @@ describe('AcceptSharedSubscriptionInvitation', () => {
}),
).toEqual({
success: false,
message: 'Could not find the subscription invitation. It may have been already accepted or declined.',
})
expect(sharedSubscriptionInvitationRepository.save).not.toHaveBeenCalled()
@@ -132,6 +174,8 @@ describe('AcceptSharedSubscriptionInvitation', () => {
}),
).toEqual({
success: false,
message:
'Could not find the invitee in our user database. Please first register an account before accepting the invitation.',
})
expect(sharedSubscriptionInvitationRepository.save).not.toHaveBeenCalled()
@@ -148,6 +192,32 @@ describe('AcceptSharedSubscriptionInvitation', () => {
}),
).toEqual({
success: false,
message: 'The person that invited you does not have a running subscription with Standard Notes anymore.',
})
expect(sharedSubscriptionInvitationRepository.save).not.toHaveBeenCalled()
expect(userSubscriptionRepository.save).not.toHaveBeenCalled()
expect(roleService.addUserRole).not.toHaveBeenCalled()
expect(subscriptionSettingService.applyDefaultSubscriptionSettingsForSubscription).not.toHaveBeenCalled()
})
it('should not create a shared subscription if inviter subscriptions are not active', async () => {
const inviterSubscription1 = { endsAt: 1, planName: SubscriptionName.PlusPlan } as jest.Mocked<UserSubscription>
const inviterSubscription2 = { endsAt: 2, planName: SubscriptionName.PlusPlan } as jest.Mocked<UserSubscription>
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(3)
userSubscriptionRepository.findBySubscriptionIdAndType = jest
.fn()
.mockReturnValue([inviterSubscription1, inviterSubscription2])
expect(
await createUseCase().execute({
sharedSubscriptionInvitationUuid: '1-2-3',
}),
).toEqual({
success: false,
message: 'The person that invited you does not have a running subscription with Standard Notes anymore.',
})
expect(sharedSubscriptionInvitationRepository.save).not.toHaveBeenCalled()

View File

@@ -37,6 +37,7 @@ export class AcceptSharedSubscriptionInvitation implements UseCaseInterface {
if (sharedSubscriptionInvitation === null) {
return {
success: false,
message: 'Could not find the subscription invitation. It may have been already accepted or declined.',
}
}
@@ -44,6 +45,8 @@ export class AcceptSharedSubscriptionInvitation implements UseCaseInterface {
if (invitee === null) {
return {
success: false,
message:
'Could not find the invitee in our user database. Please first register an account before accepting the invitation.',
}
}
@@ -51,12 +54,17 @@ export class AcceptSharedSubscriptionInvitation implements UseCaseInterface {
sharedSubscriptionInvitation.subscriptionId,
UserSubscriptionType.Regular,
)
if (inviterUserSubscriptions.length !== 1) {
const timestamp = this.timer.getTimestampInMicroseconds()
const activeUserSubscriptions = inviterUserSubscriptions.filter((userSubscription: UserSubscription) => {
return userSubscription.endsAt >= timestamp
})
if (activeUserSubscriptions.length === 0) {
return {
success: false,
message: 'The person that invited you does not have a running subscription with Standard Notes anymore.',
}
}
const inviterUserSubscription = inviterUserSubscriptions[0]
const inviterUserSubscription = activeUserSubscriptions[0]
sharedSubscriptionInvitation.status = InvitationStatus.Accepted
sharedSubscriptionInvitation.updatedAt = this.timer.getTimestampInMicroseconds()

View File

@@ -1,3 +1 @@
export type AcceptSharedSubscriptionInvitationResponse = {
success: boolean
}
export type AcceptSharedSubscriptionInvitationResponse = { success: true } | { success: false; message: string }

View File

@@ -0,0 +1,94 @@
import 'reflect-metadata'
import { DomainEventPublisherInterface, ExitDiscountApplyRequestedEvent } from '@standardnotes/domain-events'
import { UserRequestType } from '@standardnotes/common'
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
import { UserSubscription } from '../../Subscription/UserSubscription'
import { UserSubscriptionRepositoryInterface } from '../../Subscription/UserSubscriptionRepositoryInterface'
import { ProcessUserRequest } from './ProcessUserRequest'
describe('ProcessUserRequest', () => {
let userSubscriptionRepository: UserSubscriptionRepositoryInterface
let domainEventFactory: DomainEventFactoryInterface
let domainEventPublisher: DomainEventPublisherInterface
const createUseCase = () =>
new ProcessUserRequest(userSubscriptionRepository, domainEventFactory, domainEventPublisher)
beforeEach(() => {
userSubscriptionRepository = {} as jest.Mocked<UserSubscriptionRepositoryInterface>
userSubscriptionRepository.findOneByUserUuid = jest.fn().mockReturnValue({
cancelled: true,
} as jest.Mocked<UserSubscription>)
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
domainEventFactory.createExitDiscountApplyRequestedEvent = jest
.fn()
.mockReturnValue({} as jest.Mocked<ExitDiscountApplyRequestedEvent>)
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
domainEventPublisher.publish = jest.fn()
})
it('should not process unsupported requests', async () => {
expect(
await createUseCase().execute({
userEmail: 'test@test.te',
userUuid: '1-2-3',
requestType: 'foobar' as UserRequestType,
}),
).toEqual({
success: false,
})
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
})
it('should not process uncancelled subscriptions', async () => {
userSubscriptionRepository.findOneByUserUuid = jest.fn().mockReturnValue({} as jest.Mocked<UserSubscription>)
expect(
await createUseCase().execute({
userEmail: 'test@test.te',
userUuid: '1-2-3',
requestType: UserRequestType.ExitDiscount,
}),
).toEqual({
success: false,
})
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
})
it('should not process non existing subscriptions', async () => {
userSubscriptionRepository.findOneByUserUuid = jest.fn().mockReturnValue(null)
expect(
await createUseCase().execute({
userEmail: 'test@test.te',
userUuid: '1-2-3',
requestType: UserRequestType.ExitDiscount,
}),
).toEqual({
success: false,
})
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
})
it('should publish an exit discount apply requested event', async () => {
expect(
await createUseCase().execute({
userEmail: 'test@test.te',
userUuid: '1-2-3',
requestType: UserRequestType.ExitDiscount,
}),
).toEqual({
success: true,
})
expect(domainEventPublisher.publish).toHaveBeenCalled()
})
})

View File

@@ -0,0 +1,46 @@
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { inject, injectable } from 'inversify'
import { UserRequestType } from '@standardnotes/common'
import TYPES from '../../../Bootstrap/Types'
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
import { UserSubscriptionRepositoryInterface } from '../../Subscription/UserSubscriptionRepositoryInterface'
import { UseCaseInterface } from '../UseCaseInterface'
import { ProcessUserRequestDTO } from './ProcessUserRequestDTO'
import { ProcessUserRequestResponse } from './ProcessUserRequestResponse'
@injectable()
export class ProcessUserRequest implements UseCaseInterface {
constructor(
@inject(TYPES.UserSubscriptionRepository) private userSubscriptionRepository: UserSubscriptionRepositoryInterface,
@inject(TYPES.DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface,
@inject(TYPES.DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface,
) {}
async execute(dto: ProcessUserRequestDTO): Promise<ProcessUserRequestResponse> {
if (dto.requestType !== UserRequestType.ExitDiscount) {
return {
success: false,
}
}
const subscription = await this.userSubscriptionRepository.findOneByUserUuid(dto.userUuid)
if (subscription === null || !subscription.cancelled) {
return {
success: false,
}
}
await this.domainEventPublisher.publish(
this.domainEventFactory.createExitDiscountApplyRequestedEvent({
userEmail: dto.userEmail,
discountCode: 'exit-20',
}),
)
return {
success: true,
}
}
}

View File

@@ -0,0 +1,7 @@
import { UserRequestType, Uuid } from '@standardnotes/common'
export type ProcessUserRequestDTO = {
userUuid: Uuid
userEmail: string
requestType: UserRequestType
}

View File

@@ -0,0 +1,3 @@
export type ProcessUserRequestResponse = {
success: boolean
}

View File

@@ -22,7 +22,7 @@ export class InversifyExpressSubscriptionInvitesController extends BaseHttpContr
super()
}
@httpGet('/:inviteUuid/accept')
@httpPost('/:inviteUuid/accept', TYPES.ApiGatewayAuthMiddleware)
async acceptInvite(request: Request): Promise<results.JsonResult> {
const response = await this.subscriptionInvitesController.acceptInvite({
api: request.query.api as ApiVersion,

View File

@@ -0,0 +1,24 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { controller, BaseHttpController, results, httpPost } from 'inversify-express-utils'
import TYPES from '../../Bootstrap/Types'
import { UserRequestsController } from '../../Controller/UserRequestsController'
@controller('/users/:userUuid/requests')
export class InversifyExpressUserRequestsController extends BaseHttpController {
constructor(@inject(TYPES.UserRequestsController) private userRequestsController: UserRequestsController) {
super()
}
@httpPost('/', TYPES.ApiGatewayAuthMiddleware)
async submitRequest(request: Request, response: Response): Promise<results.JsonResult> {
const result = await this.userRequestsController.submitUserRequest({
requestType: request.body.requestType,
userUuid: response.locals.user.uuid,
userEmail: response.locals.user.email,
})
return this.json(result.data, result.status)
}
}

View File

@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.44.0](https://github.com/standardnotes/server/compare/@standardnotes/common@1.43.0...@standardnotes/common@1.44.0) (2022-11-03)
### Features
* **common:** add subscription cancelled email message identifier ([0c5305a](https://github.com/standardnotes/server/commit/0c5305acf62aae047caedca25de25049c37f7653))
# [1.43.0](https://github.com/standardnotes/server/compare/@standardnotes/common@1.42.0...@standardnotes/common@1.43.0) (2022-11-01)
### Features
* **common:** add user request type ([d4af1d7](https://github.com/standardnotes/server/commit/d4af1d743ef914228a15f15b36a15dc9b612c704))
# [1.42.0](https://github.com/standardnotes/server/compare/@standardnotes/common@1.41.0...@standardnotes/common@1.42.0) (2022-10-31)
### Features
* **domain-events:** add exit discount applied event ([7b797f0](https://github.com/standardnotes/server/commit/7b797f0cbabfbca4f8bf8859c613dcff38d91df3))
# [1.41.0](https://github.com/standardnotes/server/compare/@standardnotes/common@1.40.0...@standardnotes/common@1.41.0) (2022-10-19)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/common",
"version": "1.41.0",
"version": "1.44.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -25,4 +25,6 @@ export enum EmailMessageIdentifier {
REFUND_REQUESTED = 'REFUND_REQUESTED',
RATE_ADJUSTMENT_NOTICE = 'RATE_ADJUSTMENT_NOTICE',
WORKSPACE_INVITE_CREATED = 'WORKSPACE_INVITE_CREATED',
EXIT_DISCOUNT = 'EXIT_DISCOUNT',
SUBSCRIPTION_CANCELLED = 'SUBSCRIPTION_CANCELLED',
}

View File

@@ -0,0 +1,3 @@
export enum UserRequestType {
ExitDiscount = 'exit-discount',
}

View File

@@ -23,6 +23,7 @@ export * from './Subscription/SubscriptionBillingFrequency'
export * from './Subscription/SubscriptionName'
export * from './Type/Either'
export * from './Type/Only'
export * from './User/UserRequestType'
export * from './Validator/UuidValidator'
export * from './Validator/ValidatorInterface'
export * from './Workspace/WorkspaceAccessLevel'

View File

@@ -3,6 +3,34 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.9.7](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.6...@standardnotes/domain-events-infra@1.9.7) (2022-11-04)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.9.6](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.5...@standardnotes/domain-events-infra@1.9.6) (2022-11-03)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.9.5](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.4...@standardnotes/domain-events-infra@1.9.5) (2022-11-02)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.9.4](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.3...@standardnotes/domain-events-infra@1.9.4) (2022-11-01)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.9.3](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.2...@standardnotes/domain-events-infra@1.9.3) (2022-10-31)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.9.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.1...@standardnotes/domain-events-infra@1.9.2) (2022-10-31)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.9.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.0...@standardnotes/domain-events-infra@1.9.1) (2022-10-26)
**Note:** Version bump only for package @standardnotes/domain-events-infra
# [1.9.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.27...@standardnotes/domain-events-infra@1.9.0) (2022-10-19)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events-infra",
"version": "1.9.0",
"version": "1.9.7",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -3,6 +3,44 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.74.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.73.1...@standardnotes/domain-events@2.74.0) (2022-11-04)
### Features
* add analytics worker service ([d568432](https://github.com/standardnotes/server/commit/d5684326b1301855d0e07415195d4b246292f9a9))
## [2.73.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.73.0...@standardnotes/domain-events@2.73.1) (2022-11-03)
**Note:** Version bump only for package @standardnotes/domain-events
# [2.73.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.72.1...@standardnotes/domain-events@2.73.0) (2022-11-02)
### Features
* **domain-events:** add exit discount events ([59eb70c](https://github.com/standardnotes/server/commit/59eb70ce62eddd1ac8031667bc38e495e9c17bba))
## [2.72.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.72.0...@standardnotes/domain-events@2.72.1) (2022-11-01)
**Note:** Version bump only for package @standardnotes/domain-events
# [2.72.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.71.0...@standardnotes/domain-events@2.72.0) (2022-10-31)
### Features
* **domain-events:** add exit discount applied event ([7b797f0](https://github.com/standardnotes/server/commit/7b797f0cbabfbca4f8bf8859c613dcff38d91df3))
# [2.71.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.70.0...@standardnotes/domain-events@2.71.0) (2022-10-31)
### Features
* **domain-events:** add subscription reactivated event ([6e8481b](https://github.com/standardnotes/server/commit/6e8481bb2f271f3b9e86ca7d2ac683f1fd6f6516))
# [2.70.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.69.0...@standardnotes/domain-events@2.70.0) (2022-10-26)
### Features
* **domain-events:** add subscription reactivation discount requested event ([88d4d21](https://github.com/standardnotes/server/commit/88d4d211b885d63b6063b4d673280e3f4fe1cc30))
# [2.69.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.68.0...@standardnotes/domain-events@2.69.0) (2022-10-19)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events",
"version": "2.69.0",
"version": "2.74.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -9,4 +9,5 @@ export enum DomainEventService {
Files = 'files',
Scheduler = 'scheduler',
Workspace = 'workspace',
Analytics = 'analytics',
}

View File

@@ -0,0 +1,8 @@
import { DomainEventInterface } from './DomainEventInterface'
import { ExitDiscountAppliedEventPayload } from './ExitDiscountAppliedEventPayload'
export interface ExitDiscountAppliedEvent extends DomainEventInterface {
type: 'EXIT_DISCOUNT_APPLIED'
payload: ExitDiscountAppliedEventPayload
}

View File

@@ -0,0 +1,4 @@
export interface ExitDiscountAppliedEventPayload {
userEmail: string
discountRate: number
}

View File

@@ -0,0 +1,7 @@
import { DomainEventInterface } from './DomainEventInterface'
import { ExitDiscountApplyRequestedEventPayload } from './ExitDiscountApplyRequestedEventPayload'
export interface ExitDiscountApplyRequestedEvent extends DomainEventInterface {
type: 'EXIT_DISCOUNT_APPLY_REQUESTED'
payload: ExitDiscountApplyRequestedEventPayload
}

View File

@@ -0,0 +1,4 @@
export interface ExitDiscountApplyRequestedEventPayload {
userEmail: string
discountCode: string
}

View File

@@ -0,0 +1,7 @@
import { DomainEventInterface } from './DomainEventInterface'
import { ExitDiscountWithdrawRequestedEventPayload } from './ExitDiscountWithdrawRequestedEventPayload'
export interface ExitDiscountWithdrawRequestedEvent extends DomainEventInterface {
type: 'EXIT_DISCOUNT_WITHDRAW_REQUESTED'
payload: ExitDiscountWithdrawRequestedEventPayload
}

View File

@@ -0,0 +1,4 @@
export interface ExitDiscountWithdrawRequestedEventPayload {
userEmail: string
discountCode: string
}

View File

@@ -0,0 +1,8 @@
import { DomainEventInterface } from './DomainEventInterface'
import { SubscriptionReactivatedEventPayload } from './SubscriptionReactivatedEventPayload'
export interface SubscriptionReactivatedEvent extends DomainEventInterface {
type: 'SUBSCRIPTION_REACTIVATED'
payload: SubscriptionReactivatedEventPayload
}

View File

@@ -0,0 +1,10 @@
import { SubscriptionName } from '@standardnotes/common'
export interface SubscriptionReactivatedEventPayload {
userEmail: string
previousSubscriptionId: number
currentSubscriptionId: number
subscriptionName: SubscriptionName
subscriptionExpiresAt: number
discountCode: string | null
}

View File

@@ -30,6 +30,12 @@ export * from './Event/EmailBackupRequestedEvent'
export * from './Event/EmailBackupRequestedEventPayload'
export * from './Event/EmailMessageRequestedEvent'
export * from './Event/EmailMessageRequestedEventPayload'
export * from './Event/ExitDiscountAppliedEvent'
export * from './Event/ExitDiscountAppliedEventPayload'
export * from './Event/ExitDiscountApplyRequestedEvent'
export * from './Event/ExitDiscountApplyRequestedEventPayload'
export * from './Event/ExitDiscountWithdrawRequestedEvent'
export * from './Event/ExitDiscountWithdrawRequestedEventPayload'
export * from './Event/ExtensionKeyGrantedEvent'
export * from './Event/ExtensionKeyGrantedEventPayload'
export * from './Event/FileRemovedEvent'
@@ -78,6 +84,8 @@ export * from './Event/SubscriptionPurchasedEvent'
export * from './Event/SubscriptionPurchasedEventPayload'
export * from './Event/SubscriptionRateAdjustedEvent'
export * from './Event/SubscriptionRateAdjustedEventPayload'
export * from './Event/SubscriptionReactivatedEvent'
export * from './Event/SubscriptionReactivatedEventPayload'
export * from './Event/SubscriptionReassignedEvent'
export * from './Event/SubscriptionReassignedEventPayload'
export * from './Event/SubscriptionRefundedEvent'

View File

@@ -3,6 +3,56 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.6.2](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.1...@standardnotes/event-store@1.6.2) (2022-11-04)
**Note:** Version bump only for package @standardnotes/event-store
## [1.6.1](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.0...@standardnotes/event-store@1.6.1) (2022-11-03)
**Note:** Version bump only for package @standardnotes/event-store
# [1.6.0](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.5.8...@standardnotes/event-store@1.6.0) (2022-11-03)
### Features
* **event-store:** add discount events to event store ([eb53c38](https://github.com/standardnotes/server/commit/eb53c3896f8c64747e30901722c0bdc63125128f))
## [1.5.8](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.5.7...@standardnotes/event-store@1.5.8) (2022-11-02)
**Note:** Version bump only for package @standardnotes/event-store
## [1.5.7](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.5.6...@standardnotes/event-store@1.5.7) (2022-11-02)
**Note:** Version bump only for package @standardnotes/event-store
## [1.5.6](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.5.5...@standardnotes/event-store@1.5.6) (2022-11-01)
**Note:** Version bump only for package @standardnotes/event-store
## [1.5.5](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.5.4...@standardnotes/event-store@1.5.5) (2022-11-01)
### Bug Fixes
* force utf8mb4 charset on typeorm ([5160cc3](https://github.com/standardnotes/server/commit/5160cc36ddc9e30551d5ad40a9e210d87091eec3))
## [1.5.4](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.5.3...@standardnotes/event-store@1.5.4) (2022-10-31)
**Note:** Version bump only for package @standardnotes/event-store
## [1.5.3](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.5.2...@standardnotes/event-store@1.5.3) (2022-10-31)
### Bug Fixes
* **event-store:** add subscription reactivate handler ([9589403](https://github.com/standardnotes/server/commit/9589403c9de9304f1183789e111f6e4cf58cb7ff))
## [1.5.2](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.5.1...@standardnotes/event-store@1.5.2) (2022-10-31)
**Note:** Version bump only for package @standardnotes/event-store
## [1.5.1](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.5.0...@standardnotes/event-store@1.5.1) (2022-10-26)
**Note:** Version bump only for package @standardnotes/event-store
# [1.5.0](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.4.6...@standardnotes/event-store@1.5.0) (2022-10-19)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/event-store",
"version": "1.5.0",
"version": "1.6.2",
"description": "Event Store Service",
"private": true,
"main": "dist/src/index.js",

View File

@@ -82,11 +82,17 @@ export class ContainerConfigLoader {
['SUBSCRIPTION_REVERT_REQUESTED', container.get(TYPES.EventHandler)],
['REFUND_PROCESSED', container.get(TYPES.EventHandler)],
['ACCOUNT_RESET_REQUESTED', container.get(TYPES.EventHandler)],
['DISCOUNT_APPLY_REQUESTED', container.get(TYPES.EventHandler)],
['DISCOUNT_APPLIED', container.get(TYPES.EventHandler)],
['DISCOUNT_WITHDRAW_REQUESTED', container.get(TYPES.EventHandler)],
['SUBSCRIPTION_RATE_ADJUSTED', container.get(TYPES.EventHandler)],
['REFUND_REQUESTED', container.get(TYPES.EventHandler)],
['INVOICE_GENERATED', container.get(TYPES.EventHandler)],
['WORKSPACE_INVITE_CREATED', container.get(TYPES.EventHandler)],
['SUBSCRIPTION_REACTIVATED', container.get(TYPES.EventHandler)],
['EXIT_DISCOUNT_APPLY_REQUESTED', container.get(TYPES.EventHandler)],
['EXIT_DISCOUNT_APPLIED', container.get(TYPES.EventHandler)],
['EXIT_DISCOUNT_WITHDRAW_REQUESTED', container.get(TYPES.EventHandler)],
])
container

View File

@@ -11,6 +11,7 @@ const maxQueryExecutionTime = env.get('DB_MAX_QUERY_EXECUTION_TIME', true)
export const AppDataSource = new DataSource({
type: 'mysql',
charset: 'utf8mb4',
supportBigNumbers: true,
bigNumberStrings: false,
maxQueryExecutionTime,

View File

@@ -3,6 +3,40 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.8.2](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.1...@standardnotes/files-server@1.8.2) (2022-11-04)
**Note:** Version bump only for package @standardnotes/files-server
## [1.8.1](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.0...@standardnotes/files-server@1.8.1) (2022-11-03)
**Note:** Version bump only for package @standardnotes/files-server
# [1.8.0](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.7.5...@standardnotes/files-server@1.8.0) (2022-11-02)
### Features
* **auth:** add processing user requests ([2255f85](https://github.com/standardnotes/files/commit/2255f856f928e855ac94f8aca4e1fb81047f58f7))
## [1.7.5](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.7.4...@standardnotes/files-server@1.7.5) (2022-11-02)
**Note:** Version bump only for package @standardnotes/files-server
## [1.7.4](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.7.3...@standardnotes/files-server@1.7.4) (2022-11-01)
**Note:** Version bump only for package @standardnotes/files-server
## [1.7.3](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.7.2...@standardnotes/files-server@1.7.3) (2022-10-31)
**Note:** Version bump only for package @standardnotes/files-server
## [1.7.2](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.7.1...@standardnotes/files-server@1.7.2) (2022-10-31)
**Note:** Version bump only for package @standardnotes/files-server
## [1.7.1](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.7.0...@standardnotes/files-server@1.7.1) (2022-10-26)
**Note:** Version bump only for package @standardnotes/files-server
# [1.7.0](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.6.18...@standardnotes/files-server@1.7.0) (2022-10-19)
### Features

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