feat: data preview envs (#4)
@@ -13,11 +13,14 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
strategy:
|
||||
matrix:
|
||||
name: [backend, frontend]
|
||||
name: Build ${{ matrix.name }}
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./web
|
||||
working-directory: ./${{ matrix.name }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@@ -26,7 +29,7 @@ jobs:
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: './web/.nvmrc'
|
||||
node-version-file: './${{ matrix.name }}/.nvmrc'
|
||||
|
||||
- name: Run npm install
|
||||
run: npm ci
|
||||
@@ -37,6 +40,19 @@ jobs:
|
||||
- name: Upload build output
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-output-web
|
||||
path: web/build
|
||||
name: build-output-${{ matrix.name }}
|
||||
if-no-files-found: error
|
||||
path: dist
|
||||
retention-days: 1
|
||||
|
||||
merge:
|
||||
name: Merge Artifacts
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- name: Merge Artifacts
|
||||
uses: actions/upload-artifact/merge@v4
|
||||
with:
|
||||
name: build-output
|
||||
retention-days: 1
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
parameters: ${{ steps.parameters.outputs.result }}
|
||||
|
||||
steps:
|
||||
- if: ${{ github.event.workflow_run.conclusion == 'failure' }}
|
||||
run: echo 'The triggering workflow failed' && exit 1
|
||||
@@ -105,7 +106,7 @@ jobs:
|
||||
repo: context.repo.repo,
|
||||
run_id: context.payload.workflow_run.id,
|
||||
});
|
||||
let matchArtifact = allArtifacts.data.artifacts.find((artifact) => artifact.name == "build-output-web");
|
||||
let matchArtifact = allArtifacts.data.artifacts.find((artifact) => artifact.name == "build-output");
|
||||
let download = await github.rest.actions.downloadArtifact({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
@@ -116,39 +117,41 @@ jobs:
|
||||
fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/build-output.zip`, Buffer.from(download.data));
|
||||
|
||||
- name: Unzip artifact
|
||||
run: unzip "${{ github.workspace }}/build-output.zip" -d "${{ github.workspace }}/build"
|
||||
run: unzip "${{ github.workspace }}/build-output.zip" -d "${{ github.workspace }}/dist"
|
||||
|
||||
- name: Deploy App
|
||||
env:
|
||||
TF_VAR_prefix_name: ${{ steps.parameters.outputs.name }}
|
||||
TF_VAR_prefix_event_type: ${{ steps.parameters.outputs.event }}
|
||||
TF_VAR_app_url: data.immich.app
|
||||
TF_VAR_app_name: immich-data
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }}
|
||||
DIST_DIR: ${{ github.workspace }}/dist
|
||||
ENVIRONMENT: ${{ github.ref == 'refs/heads/main' && 'prod' || 'dev' }}
|
||||
VMETRICS_API_TOKEN: ${{ secrets.VMETRICS_API_TOKEN }}
|
||||
uses: gruntwork-io/terragrunt-action@v2
|
||||
with:
|
||||
tg_version: '0.58.12'
|
||||
tofu_version: '1.7.1'
|
||||
tg_dir: 'deployment/modules/cloudflare/static-pages'
|
||||
tg_command: 'apply'
|
||||
tg_dir: 'deployment/modules/cloudflare/'
|
||||
tg_command: 'run-all apply'
|
||||
|
||||
- name: Deploy App Output
|
||||
id: terraform-output
|
||||
env:
|
||||
TF_VAR_prefix_name: ${{ steps.parameters.outputs.name }}
|
||||
TF_VAR_prefix_event_type: ${{ steps.parameters.outputs.event }}
|
||||
TF_VAR_app_url: data.immich.app
|
||||
TF_VAR_app_name: immich-data
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }}
|
||||
DIST_DIR: ${{ github.workspace }}/dist
|
||||
ENVIRONMENT: ${{ github.ref == 'refs/heads/main' && 'prod' || 'dev' }}
|
||||
VMETRICS_API_TOKEN: ${{ secrets.VMETRICS_API_TOKEN }}
|
||||
uses: gruntwork-io/terragrunt-action@v2
|
||||
with:
|
||||
tg_version: '0.58.12'
|
||||
tofu_version: '1.7.1'
|
||||
tg_dir: 'deployment/modules/cloudflare/static-pages'
|
||||
tg_dir: 'deployment/modules/cloudflare/'
|
||||
tg_command: 'output -json'
|
||||
|
||||
- name: Output Cleaning
|
||||
@@ -159,6 +162,7 @@ jobs:
|
||||
|
||||
- name: Publish to Cloudflare Pages
|
||||
uses: cloudflare/pages-action@v1
|
||||
if: ${{ matrix.name == 'frontend' }}
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN_PAGES_UPLOAD }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
@@ -169,7 +173,7 @@ jobs:
|
||||
|
||||
- name: Comment
|
||||
uses: actions-cool/maintain-one-comment@v3
|
||||
if: ${{ steps.parameters.outputs.event == 'pr' }}
|
||||
if: ${{ matrix.name == 'frontend' && steps.parameters.outputs.event == 'pr' }}
|
||||
with:
|
||||
number: ${{ fromJson(needs.checks.outputs.parameters).pr_number }}
|
||||
body: |
|
||||
|
||||
@@ -12,18 +12,19 @@ jobs:
|
||||
|
||||
- name: Destroy Docs Subdomain
|
||||
env:
|
||||
TF_VAR_prefix_name: 'pr-${{ github.event.number }}'
|
||||
TF_VAR_prefix_event_type: 'pr'
|
||||
TF_VAR_app_url: data.immich.app
|
||||
TF_VAR_app_name: immich-data
|
||||
TF_VAR_prefix_name: ${{ steps.parameters.outputs.name }}
|
||||
TF_VAR_prefix_event_type: ${{ steps.parameters.outputs.event }}
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }}
|
||||
DIST_DIR: ${{ github.workspace }}/dist
|
||||
ENVIRONMENT: ${{ github.ref == 'refs/heads/main' && 'prod' || 'dev' }}
|
||||
VMETRICS_API_TOKEN: ${{ secrets.VMETRICS_API_TOKEN }}
|
||||
uses: gruntwork-io/terragrunt-action@v2
|
||||
with:
|
||||
tg_version: '0.58.12'
|
||||
tofu_version: '1.7.1'
|
||||
tg_dir: 'deployment/modules/cloudflare/static-pages'
|
||||
tg_dir: 'deployment/modules/cloudflare/frontend'
|
||||
tg_command: 'destroy'
|
||||
|
||||
- name: Comment
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./web
|
||||
working-directory: ./frontend
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: './web/.nvmrc'
|
||||
node-version-file: './frontend/.nvmrc'
|
||||
|
||||
- name: Run npm install
|
||||
run: npm ci
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
# Custom rules
|
||||
dist
|
||||
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# AWS User-specific
|
||||
.idea/**/aws.xml
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# SonarLint plugin
|
||||
.idea/sonarlint/
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
@@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="EslintConfiguration">
|
||||
<files-pattern value="**/*.{js,ts,jsx,tsx,html,vue,json}" />
|
||||
<option name="fix-on-save" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/data.immich.app.iml" filepath="$PROJECT_DIR$/.idea/data.immich.app.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="PrettierConfiguration">
|
||||
<option name="myConfigurationMode" value="AUTOMATIC" />
|
||||
<option name="myRunOnSave" value="true" />
|
||||
<option name="myFilesPattern" value="**/*.{js,ts,jsx,tsx,vue,astro,json,yml,yaml,md}" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,16 @@
|
||||
module.exports = {
|
||||
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
project: true,
|
||||
sourceType: 'module',
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
plugins: ['@typescript-eslint', 'eslint-plugin-import'],
|
||||
rules: {
|
||||
curly: 2,
|
||||
'@typescript-eslint/no-floating-promises': 'error',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
},
|
||||
root: true,
|
||||
};
|
||||
@@ -0,0 +1,172 @@
|
||||
# Logs
|
||||
|
||||
logs
|
||||
_.log
|
||||
npm-debug.log_
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# Runtime data
|
||||
|
||||
pids
|
||||
_.pid
|
||||
_.seed
|
||||
\*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
|
||||
coverage
|
||||
\*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
|
||||
\*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
|
||||
\*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
|
||||
.cache/
|
||||
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.\*
|
||||
|
||||
# wrangler project
|
||||
|
||||
.dev.vars
|
||||
.wrangler/
|
||||
@@ -0,0 +1 @@
|
||||
20.17.0
|
||||
@@ -0,0 +1,17 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
*.md
|
||||
*.json
|
||||
coverage
|
||||
dist
|
||||
worker-configuration.d.ts
|
||||
|
||||
# Ignore files for PNPM, NPM and YARN
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"printWidth": 120,
|
||||
"semi": true,
|
||||
"organizeImportsSkipDestructiveCodeActions": true,
|
||||
"plugins": ["prettier-plugin-organize-imports"]
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "worker",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"deploy": "wrangler deploy",
|
||||
"dev": "wrangler dev",
|
||||
"start": "wrangler dev",
|
||||
"build": "wrangler deploy --dry-run --outdir=../dist/backend",
|
||||
"test": "vitest",
|
||||
"test:cov": "vitest --coverage",
|
||||
"cf-typegen": "wrangler types",
|
||||
"lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\" --max-warnings 0",
|
||||
"lint:fix": "npm run lint -- --fix",
|
||||
"format": "prettier --check .",
|
||||
"format:fix": "prettier --write .",
|
||||
"check": "tsc --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/vitest-pool-workers": "^0.5.8",
|
||||
"@cloudflare/workers-types": "^4.20240729.0",
|
||||
"@types/express": "^4.17.21",
|
||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||
"@typescript-eslint/parser": "^8.0.0",
|
||||
"@vitest/coverage-istanbul": "^2.1.1",
|
||||
"eslint": "^8.50.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"express": "^4.19.2",
|
||||
"prettier-plugin-organize-imports": "^4.0.0",
|
||||
"tsx": "^4.17.0",
|
||||
"typescript": "^5.5.2",
|
||||
"vite-node": "^2.1.1",
|
||||
"vite-plugin-node-polyfills": "^0.22.0",
|
||||
"wrangler": "^3.70.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.633.0",
|
||||
"@influxdata/influxdb-client": "^1.34.0",
|
||||
"fetch-retry": "^6.0.0",
|
||||
"fflate": "^0.8.2",
|
||||
"p-limit": "^6.1.0"
|
||||
},
|
||||
"volta": {
|
||||
"node": "20.17.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
interface WorkerEnv extends Omit<Env, 'ENVIRONMENT' | 'DEPLOYMENT_KEY'> {
|
||||
ENVIRONMENT: string;
|
||||
DEPLOYMENT_KEY: string;
|
||||
VMETRICS_API_TOKEN: string;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { IMetricsRepository } from './interface';
|
||||
import {
|
||||
CloudflareDeferredRepository,
|
||||
CloudflareMetricsRepository,
|
||||
HeaderMetricsProvider,
|
||||
InfluxMetricsProvider,
|
||||
} from './repository';
|
||||
|
||||
enum Header {
|
||||
PMTILES_DEPLOYMENT_KEY = 'PMTiles-Deployment-Key',
|
||||
CACHE_CONTROL = 'Cache-Control',
|
||||
ACCESS_CONTROL_ALLOW_ORIGIN = 'Access-Control-Allow-Origin',
|
||||
VARY = 'Vary',
|
||||
CONTENT_TYPE = 'Content-Type',
|
||||
CONTENT_ENCODING = 'Content-Encoding',
|
||||
SERVER_TIMING = 'Server-Timing',
|
||||
}
|
||||
|
||||
async function handleRequest(
|
||||
request: Request<unknown, IncomingRequestCfProperties>,
|
||||
env: WorkerEnv,
|
||||
deferredRepository: CloudflareDeferredRepository,
|
||||
metrics: IMetricsRepository,
|
||||
) {
|
||||
return new Response('Hello world!');
|
||||
}
|
||||
|
||||
export default {
|
||||
async fetch(request, env, ctx): Promise<Response> {
|
||||
const workerEnv = env as WorkerEnv;
|
||||
const deferredRepository = new CloudflareDeferredRepository(ctx);
|
||||
const headerProvider = new HeaderMetricsProvider();
|
||||
const influxProvider = new InfluxMetricsProvider(workerEnv.VMETRICS_API_TOKEN, env.ENVIRONMENT);
|
||||
deferredRepository.defer(() => influxProvider.flush());
|
||||
const metrics = new CloudflareMetricsRepository('data', request, [influxProvider, headerProvider]);
|
||||
|
||||
try {
|
||||
const response = await metrics.monitorAsyncFunction({ name: 'handle_request' }, handleRequest)(
|
||||
request,
|
||||
workerEnv,
|
||||
deferredRepository,
|
||||
metrics,
|
||||
);
|
||||
response.headers.set(Header.SERVER_TIMING, headerProvider.getTimingHeader());
|
||||
deferredRepository.runDeferred();
|
||||
return response;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return new Response('Internal Server Error', { status: 500 });
|
||||
}
|
||||
},
|
||||
} satisfies ExportedHandler<Env>;
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Metric } from './repository';
|
||||
|
||||
export interface IDeferredRepository {
|
||||
defer(promise: AsyncFn): void;
|
||||
runDeferred(): void;
|
||||
}
|
||||
|
||||
export type AsyncFn = (...args: any[]) => Promise<any>;
|
||||
export type Class = { new (...args: any[]): any };
|
||||
export type Operation = { name: string; tags?: { [key: string]: string } };
|
||||
export type Options = { monitorInvocations?: boolean; acceptedErrors?: Class[] };
|
||||
|
||||
export interface IMetricsRepository {
|
||||
monitorAsyncFunction<T extends AsyncFn>(
|
||||
operation: Operation,
|
||||
call: T,
|
||||
options?: Options,
|
||||
): (...args: Parameters<T>) => Promise<Awaited<ReturnType<T>>>;
|
||||
push(metric: Metric): void;
|
||||
}
|
||||
|
||||
export interface IMetricsProviderRepository {
|
||||
pushMetric(metric: Metric): void;
|
||||
flush(): void;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { AsyncFn, Operation, Options } from './interface';
|
||||
import { Metric } from './repository';
|
||||
|
||||
export function monitorAsyncFunction<T extends AsyncFn>(
|
||||
operationPrefix: string,
|
||||
operation: Operation,
|
||||
call: T,
|
||||
metricsWriteCallback: (metric: Metric) => void,
|
||||
options: Options = {},
|
||||
): (...args: Parameters<T>) => Promise<Awaited<ReturnType<T>>> {
|
||||
const { name: operationName, tags = {} } = operation;
|
||||
const { monitorInvocations = true, acceptedErrors = [] } = options;
|
||||
|
||||
const metric = Metric.create(`${operationPrefix}_${operationName}`);
|
||||
metric.addTags(tags);
|
||||
return async (...args: Parameters<T>) => {
|
||||
if (monitorInvocations) {
|
||||
metric.intField('invocation', 1);
|
||||
}
|
||||
return call(...args)
|
||||
.catch((e) => {
|
||||
if (!acceptedErrors || !acceptedErrors.some((acceptedError) => e instanceof acceptedError)) {
|
||||
console.error(e, `${operationName}_errors`);
|
||||
metric.intField('errors', 1);
|
||||
}
|
||||
throw e;
|
||||
})
|
||||
.finally(() => {
|
||||
metric.durationField('duration');
|
||||
metricsWriteCallback(metric);
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
import { Point } from '@influxdata/influxdb-client';
|
||||
import {
|
||||
AsyncFn,
|
||||
IDeferredRepository,
|
||||
IMetricsProviderRepository,
|
||||
IMetricsRepository,
|
||||
Operation,
|
||||
Options,
|
||||
} from './interface';
|
||||
import { monitorAsyncFunction } from './monitor';
|
||||
|
||||
export class CloudflareDeferredRepository implements IDeferredRepository {
|
||||
deferred: AsyncFn[] = [];
|
||||
constructor(private ctx: ExecutionContext) {}
|
||||
|
||||
defer(call: AsyncFn): void {
|
||||
this.deferred.push(call);
|
||||
}
|
||||
|
||||
runDeferred() {
|
||||
for (const call of this.deferred) {
|
||||
this.ctx.waitUntil(call());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class HeaderMetricsProvider implements IMetricsProviderRepository {
|
||||
private _metrics: string[] = [];
|
||||
constructor() {}
|
||||
|
||||
pushMetric(metric: Metric) {
|
||||
for (const [label, { value, type }] of metric.fields) {
|
||||
if (type === 'duration') {
|
||||
const suffix = label === 'duration' ? '' : `_${label.replace('_duration', '')}`;
|
||||
this._metrics.push(`${metric.name}${suffix};dur=${value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getTimingHeader() {
|
||||
return this._metrics.join(', ');
|
||||
}
|
||||
|
||||
flush() {
|
||||
console.log(this._metrics.join(', '));
|
||||
}
|
||||
}
|
||||
|
||||
export class InfluxMetricsProvider implements IMetricsProviderRepository {
|
||||
private metrics: string[] = [];
|
||||
constructor(
|
||||
private influxApiToken: string,
|
||||
private environment: string,
|
||||
) {}
|
||||
|
||||
pushMetric(metric: Metric) {
|
||||
const point = new Point(metric.name);
|
||||
for (const [key, value] of metric.tags) {
|
||||
point.tag(key, value);
|
||||
}
|
||||
for (const [key, { value, type }] of metric.fields) {
|
||||
if (type === 'duration') {
|
||||
point.intField(key, value);
|
||||
} else if (type === 'int') {
|
||||
point.intField(key, value);
|
||||
}
|
||||
}
|
||||
const influxLineProtocol = point.toLineProtocol()?.toString();
|
||||
if (influxLineProtocol) {
|
||||
this.metrics.push(influxLineProtocol);
|
||||
}
|
||||
}
|
||||
|
||||
async flush() {
|
||||
if (this.metrics.length === 0) {
|
||||
return;
|
||||
}
|
||||
const metrics = this.metrics.join('\n');
|
||||
if (this.environment === 'prod') {
|
||||
const response = await fetch('https://cf-workers.monitoring.immich.cloud/write', {
|
||||
method: 'POST',
|
||||
body: this.metrics.join('\n'),
|
||||
headers: {
|
||||
Authorization: `Token ${this.influxApiToken}`,
|
||||
},
|
||||
});
|
||||
await response.body?.cancel();
|
||||
} else {
|
||||
console.log(metrics);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Metric {
|
||||
private _tags: Map<string, string> = new Map();
|
||||
private _timestamp = performance.now();
|
||||
private _fields = new Map<string, { value: any; type: 'duration' | 'int' }>();
|
||||
private constructor(private _name: string) {}
|
||||
|
||||
static create(name: string) {
|
||||
return new Metric(name);
|
||||
}
|
||||
|
||||
get tags() {
|
||||
return this._tags;
|
||||
}
|
||||
|
||||
get timestamp() {
|
||||
return this._timestamp;
|
||||
}
|
||||
|
||||
get fields() {
|
||||
return this._fields;
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
addTag(key: string, value: string) {
|
||||
this._tags.set(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
addTags(tags: { [key: string]: string }) {
|
||||
for (const [key, value] of Object.entries(tags)) {
|
||||
this._tags.set(key, value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
durationField(key: string, duration?: number) {
|
||||
this._fields.set(key, { value: duration ?? performance.now() - this._timestamp, type: 'duration' });
|
||||
return this;
|
||||
}
|
||||
|
||||
intField(key: string, value: number) {
|
||||
this._fields.set(key, { value, type: 'int' });
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export class CloudflareMetricsRepository implements IMetricsRepository {
|
||||
private readonly defaultTags: { [key: string]: string };
|
||||
|
||||
constructor(
|
||||
private operationPrefix: string,
|
||||
request: Request<unknown, IncomingRequestCfProperties>,
|
||||
private metricsProviders: IMetricsProviderRepository[],
|
||||
) {
|
||||
this.defaultTags = {
|
||||
continent: request.cf?.continent ?? '',
|
||||
colo: request.cf?.colo ?? '',
|
||||
asOrg: request.cf?.asOrganization ?? '',
|
||||
};
|
||||
}
|
||||
|
||||
monitorAsyncFunction<T extends AsyncFn>(
|
||||
operation: Operation,
|
||||
call: T,
|
||||
options: Options = {},
|
||||
): (...args: Parameters<T>) => Promise<Awaited<ReturnType<T>>> {
|
||||
operation = { ...operation, tags: { ...operation.tags, ...this.defaultTags } };
|
||||
const callback = (metric: Metric) => {
|
||||
for (const provider of this.metricsProviders) {
|
||||
provider.pushMetric(metric);
|
||||
}
|
||||
};
|
||||
|
||||
return monitorAsyncFunction(this.operationPrefix, operation, call, callback, options);
|
||||
}
|
||||
|
||||
push(metric: Metric) {
|
||||
metric.addTags(this.defaultTags);
|
||||
for (const provider of this.metricsProviders) {
|
||||
provider.pushMetric(metric);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
|
||||
"lib": ["es2021"] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
|
||||
"jsx": "react-jsx" /* Specify what JSX code is generated. */,
|
||||
"module": "es2022" /* Specify what module code is generated. */,
|
||||
"moduleResolution": "Bundler" /* Specify how TypeScript looks up a file from a given module specifier. */,
|
||||
"types": [
|
||||
"@cloudflare/workers-types"
|
||||
],
|
||||
"resolveJsonModule": true /* Enable importing .json files */,
|
||||
"allowJs": true /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */,
|
||||
"checkJs": false /* Enable error reporting in type-checked JavaScript files. */,
|
||||
"noEmit": true /* Disable emitting files from a compilation. */,
|
||||
"isolatedModules": true /* Ensure that each file can be safely transpiled without relying on other imports. */,
|
||||
"allowSyntheticDefaultImports": true /* Allow 'import x from y' when a module doesn't have a default export. */,
|
||||
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
},
|
||||
"exclude": ["test"],
|
||||
"include": ["worker-configuration.d.ts", "src/**/*.ts", "vitest.config.mts"]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config';
|
||||
|
||||
export default defineWorkersConfig({
|
||||
test: {
|
||||
globals: true,
|
||||
poolOptions: {
|
||||
workers: {
|
||||
wrangler: { configPath: './wrangler.toml' },
|
||||
},
|
||||
},
|
||||
// globalSetup: './test/globalSetup.ts',
|
||||
coverage: {
|
||||
provider: 'istanbul',
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
// Generated by Wrangler on Tue Sep 10 2024 12:58:38 GMT+0100 (British Summer Time)
|
||||
// by running `wrangler types`
|
||||
|
||||
interface Env {
|
||||
KV: KVNamespace;
|
||||
DEPLOYMENT_KEY: '20240812';
|
||||
ENVIRONMENT: 'prod';
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#:schema node_modules/wrangler/config-schema.json
|
||||
name = "tiles-dev"
|
||||
main = "src/index.ts"
|
||||
compatibility_date = "2024-07-29"
|
||||
compatibility_flags = ["nodejs_compat"]
|
||||
|
||||
# Define environment variables for your Workers script
|
||||
[vars]
|
||||
DEPLOYMENT_KEY = "20240812"
|
||||
ENVIRONMENT = "production"
|
||||
@@ -0,0 +1,55 @@
|
||||
# This file is maintained automatically by "tofu init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.opentofu.org/cloudflare/cloudflare" {
|
||||
version = "4.42.0"
|
||||
constraints = "4.42.0"
|
||||
hashes = [
|
||||
"h1:+1iz3OcEEYBLeVS07cqRP5Kef0Y0XS8YEj/I1HH9J/A=",
|
||||
"h1:/XZtSyok+I0hAHqj4hcA2RP4G9uvTABG4oMGC3vlS6o=",
|
||||
"h1:5Z9Kfc5ufjqbLBXP9lZqQ11PAc4m6aTf/0M6miceOVQ=",
|
||||
"h1:6cupVsiQundwTt+/lSuiR3s1s1H0JDN1H7USaF/+nDg=",
|
||||
"h1:FasXNkaO7MNWy6GtuB/QFlnpQ7UuQQNotN1asY/r0Z8=",
|
||||
"h1:G+5pvEHQlVkPXW6HK1twiWHOV/pavtSpQthZ0wb0HYM=",
|
||||
"h1:HYhFNiRR1InZGjelCdQmg/KJHL2lFDlaWGSl2KA2fwk=",
|
||||
"h1:LkMNEQUnYBAx8EFvl1BU/AVG1cs1Xi62kCvTVvYvDZ8=",
|
||||
"h1:McPTXlnBSEzFGmVJHU7KyoBI3hJRh/eRcFTcijAfO/I=",
|
||||
"h1:MoqiPtyXewRdNsevZUMqb0rXXpeLUVxNVEzux4AC7y4=",
|
||||
"h1:RUgEZET04NprAFc4XAJwqqLiwNKaCTQEQEXtIZtNu1E=",
|
||||
"h1:UCneKnBS53RlyJsdCqCCWrw19GE48tzTyO2u/V5u24c=",
|
||||
"h1:aGbtjN8I2mLyW7aJ8KVRYuVdYeQCh3Ee8c3ixiijmjM=",
|
||||
"h1:phTeKoT4aUmPXQO6d7ZbSQ8Cu41dhosACCEiDpRsL/I=",
|
||||
"zh:1abb93dda2bf73b1656dc63673c752642bfa4c31c8ca1e83cdd278f42fa121c1",
|
||||
"zh:2b9c90fe4fffaadfeccd0f457bd1354ba2dba920c5525748d8f8f24656d6c7bd",
|
||||
"zh:710a0cf84406df09705115a62bd6a418188a3b884f615fe7122a6fc51ec59bc0",
|
||||
"zh:7875b38b281d17a24d89116b33f92b8b24292be160a2c618874a0f674171bd34",
|
||||
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
|
||||
"zh:ad28c94908c336ca97feabb02734a2c115eddaa5f4a625f00bfafb3ac65646e4",
|
||||
"zh:aee7861d44cf1a71a4846e1ffda20992259838ee600ca79d3abc80b43f4ff0f0",
|
||||
"zh:b1327e8ea04f0df5efc2b8848c358bf0d256716ad5372f91f5960296a1f68677",
|
||||
"zh:b9ec2310feffef38c1488bed038ad7942c54f930f2166a600b5fc850e377bddf",
|
||||
"zh:d04178727d7157aae8eb66b4f7318338c89cd685b53c67f5ce41de4160c2d484",
|
||||
"zh:d76c4bb5a5bfed710fcc8a1d919172cbfcbda0cec177f1d2cb44aff82a862a32",
|
||||
"zh:ddf3c126b5c98267240f960f8fed381115675793e5b86dd2cbdebeb628efb0cb",
|
||||
"zh:e3efb3b9409626f15931a81edaeb96e4baf462a24b32dac9d97cc36d1fef1f01",
|
||||
"zh:e925c963cddf5778829f0b26f72ad8bcbc1aed3510cfa0bd5a9e7cbf4c64a6e8",
|
||||
"zh:ff78a903b2432fbff7b04e84a23c71425589bad7a4b846e08bc131268b80900f",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.opentofu.org/hashicorp/http" {
|
||||
version = "3.4.5"
|
||||
hashes = [
|
||||
"h1:IkHNZ09hqg1TtLrUqUHIqoKXbNJE69dk917niblQf3U=",
|
||||
"zh:055a4431d33bb89b9848193152433eaead7cc2e6746d3436a5922419de2112bf",
|
||||
"zh:0bfabafea9f5e36802fcfc5a800831ec1767d896af889abc610014d02b09bdc2",
|
||||
"zh:300b4983fe1b43bd0a7dac1f94b30b3814f11c824224dd83fb45a521c02cea60",
|
||||
"zh:68f6958314ca5dc0868be70e37ec123b99b8828aa49f27fd2fdd13df05d31ab1",
|
||||
"zh:c29f098a597250adc2a7d9f99acbce3c9e07d37f1c5cfded5df4309738cf613c",
|
||||
"zh:c33607397f9c9302c0cd797c8b7484c9c6cfa09c3489d4b55af17df20b204368",
|
||||
"zh:d519ca364a224110428b390ee06e963a3ec4dfdd1ac816c9f32e647567957cf5",
|
||||
"zh:e4a9c7c0ac31a0192362ef43449390cdf00d2cf6f13061ef730b177eaf00ac45",
|
||||
"zh:f25223c062f274d8f89bb96017e73586030a205bc91cdad266a9954d0def2a23",
|
||||
"zh:fd4dc824ebae2f3a66318df364bec83b88e9a52e7f66b00dafa29a796d9a94ab",
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
terraform {
|
||||
backend "pg" {}
|
||||
required_version = "~> 1.7"
|
||||
|
||||
required_providers {
|
||||
cloudflare = {
|
||||
source = "cloudflare/cloudflare"
|
||||
version = "4.42.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
provider "cloudflare" {
|
||||
api_token = data.terraform_remote_state.api_keys_state.outputs.terraform_key_cloudflare_account
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
data "terraform_remote_state" "api_keys_state" {
|
||||
backend = "pg"
|
||||
|
||||
config = {
|
||||
conn_str = var.tf_state_postgres_conn_str
|
||||
schema_name = "prod_cloudflare_api_keys"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
terraform {
|
||||
source = "."
|
||||
|
||||
extra_arguments custom_vars {
|
||||
commands = get_terraform_commands_that_need_vars()
|
||||
}
|
||||
}
|
||||
|
||||
include "cloudflare" {
|
||||
path = find_in_parent_folders("cloudflare.hcl")
|
||||
}
|
||||
|
||||
include "root" {
|
||||
path = find_in_parent_folders("root.hcl")
|
||||
}
|
||||
|
||||
locals {
|
||||
dist_dir = get_env("DIST_DIR")
|
||||
vmetrics_api_token = get_env("VMETRICS_API_TOKEN")
|
||||
env = get_env("ENVIRONMENT")
|
||||
}
|
||||
|
||||
inputs = {
|
||||
dist_dir = local.dist_dir
|
||||
vmetrics_api_token = local.vmetrics_api_token
|
||||
env = local.env
|
||||
}
|
||||
|
||||
remote_state {
|
||||
backend = "pg"
|
||||
|
||||
config = {
|
||||
conn_str = get_env("TF_STATE_POSTGRES_CONN_STR")
|
||||
schema_name = "${local.env}_cloudflare_data_worker"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
variable "cloudflare_account_id" {}
|
||||
variable "tf_state_postgres_conn_str" {}
|
||||
variable "dist_dir" {}
|
||||
variable "vmetrics_api_token" {}
|
||||
variable "env" {}
|
||||
@@ -0,0 +1,47 @@
|
||||
data "cloudflare_zone" "immich_cloud" {
|
||||
name = "immich.cloud"
|
||||
}
|
||||
|
||||
resource "cloudflare_workers_script" "data_api" {
|
||||
account_id = var.cloudflare_account_id
|
||||
name = "data-api-${var.env}"
|
||||
content = file("${var.dist_dir}/backend/index.js")
|
||||
module = true
|
||||
|
||||
plain_text_binding {
|
||||
name = "ENVIRONMENT"
|
||||
text = var.env
|
||||
}
|
||||
|
||||
secret_text_binding {
|
||||
name = "VMETRICS_API_TOKEN"
|
||||
text = var.vmetrics_api_token
|
||||
}
|
||||
|
||||
compatibility_date = "2024-07-29"
|
||||
compatibility_flags = ["nodejs_compat"]
|
||||
}
|
||||
|
||||
data "http" "data_api_deployments" {
|
||||
depends_on = [cloudflare_workers_script.data_api]
|
||||
url = "https://api.cloudflare.com/client/v4/accounts/${var.cloudflare_account_id}/workers/scripts/${cloudflare_workers_script.data_api.name}/deployments"
|
||||
request_headers = {
|
||||
Authorization = "Bearer ${data.terraform_remote_state.api_keys_state.outputs.terraform_key_cloudflare_account}"
|
||||
}
|
||||
}
|
||||
|
||||
locals {
|
||||
data_api_version_id = split("-", jsondecode(data.http.data_api_deployments.response_body).result.deployments[0].versions[0].version_id)[0]
|
||||
}
|
||||
|
||||
output "data_api_preview_url" {
|
||||
value = "https://${local.data_api_version_id}-${cloudflare_workers_script.data_api.name}.immich.workers.dev"
|
||||
}
|
||||
|
||||
resource "cloudflare_workers_route" "data_api_prod" {
|
||||
count = var.env == "prod" ? 1 : 0
|
||||
pattern = "data.immich.app/api*"
|
||||
script_name = cloudflare_workers_script.data_api.name
|
||||
zone_id = data.cloudflare_zone.immich_cloud.zone_id
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
locals {
|
||||
cloudflare_account_id = get_env("CLOUDFLARE_ACCOUNT_ID")
|
||||
cloudflare_api_token = get_env("CLOUDFLARE_API_TOKEN")
|
||||
}
|
||||
|
||||
inputs = {
|
||||
cloudflare_account_id = local.cloudflare_account_id
|
||||
cloudflare_api_token = local.cloudflare_api_token
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
resource "cloudflare_pages_domain" "immich_app_branch_domain" {
|
||||
account_id = var.cloudflare_account_id
|
||||
project_name = data.terraform_remote_state.cloudflare_account.outputs.static_pages_project_names[var.app_url]
|
||||
project_name = data.terraform_remote_state.cloudflare_account.outputs.static_pages_project_names["data.immich.app"]
|
||||
domain = local.domain
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ resource "cloudflare_record" "immich_app_branch_subdomain" {
|
||||
proxied = true
|
||||
ttl = 1
|
||||
type = "CNAME"
|
||||
value = local.is_main ? data.terraform_remote_state.cloudflare_account.outputs.static_pages_project_subdomains[var.app_url] : "${replace(var.prefix_name, "/\\/|\\./", "-")}.${data.terraform_remote_state.cloudflare_account.outputs.static_pages_project_subdomains[var.app_url]}"
|
||||
content = local.is_main ? data.terraform_remote_state.cloudflare_account.outputs.static_pages_project_subdomains["data.immich.app"] : "${replace(var.prefix_name, "/\\/|\\./", "-")}.${data.terraform_remote_state.cloudflare_account.outputs.static_pages_project_subdomains["data.immich.app"]}"
|
||||
zone_id = data.terraform_remote_state.cloudflare_account.outputs.immich_app_zone_id
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ output "immich_app_branch_subdomain" {
|
||||
}
|
||||
|
||||
output "immich_app_branch_pages_hostname" {
|
||||
value = cloudflare_record.immich_app_branch_subdomain.value
|
||||
value = cloudflare_record.immich_app_branch_subdomain.content
|
||||
}
|
||||
|
||||
output "pages_project_name" {
|
||||
@@ -1,5 +1,5 @@
|
||||
locals {
|
||||
is_main = var.prefix_name == "main"
|
||||
domain_prefix = !local.is_main && contains(["branch", "pr"], var.prefix_event_type) ? "${var.prefix_name}.preview." : ""
|
||||
domain = "${local.domain_prefix}${var.app_url}"
|
||||
domain = "${local.domain_prefix}data.immich.app"
|
||||
}
|
||||
@@ -6,13 +6,20 @@ terraform {
|
||||
}
|
||||
}
|
||||
|
||||
include {
|
||||
path = find_in_parent_folders("state.hcl")
|
||||
include "cloudflare" {
|
||||
path = find_in_parent_folders("cloudflare.hcl")
|
||||
}
|
||||
|
||||
include "root" {
|
||||
path = find_in_parent_folders("root.hcl")
|
||||
}
|
||||
|
||||
locals {
|
||||
prefix_name = get_env("TF_VAR_prefix_name")
|
||||
app_name = replace(get_env("TF_VAR_app_name"), "-", "_")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
paths = ["../backend"]
|
||||
}
|
||||
|
||||
remote_state {
|
||||
@@ -20,6 +27,6 @@ remote_state {
|
||||
|
||||
config = {
|
||||
conn_str = get_env("TF_STATE_POSTGRES_CONN_STR")
|
||||
schema_name = "prod_cloudflare_immich_app_${local.app_name}_${local.prefix_name}"
|
||||
schema_name = "prod_cloudflare_immich_app_data_${local.prefix_name}"
|
||||
}
|
||||
}
|
||||
@@ -3,4 +3,3 @@ variable "tf_state_postgres_conn_str" {}
|
||||
|
||||
variable "prefix_name" {}
|
||||
variable "prefix_event_type" {}
|
||||
variable "app_url" {}
|
||||
@@ -1,7 +1,4 @@
|
||||
locals {
|
||||
cloudflare_account_id = get_env("CLOUDFLARE_ACCOUNT_ID")
|
||||
cloudflare_api_token = get_env("CLOUDFLARE_API_TOKEN")
|
||||
|
||||
tf_state_postgres_conn_str = get_env("TF_STATE_POSTGRES_CONN_STR")
|
||||
}
|
||||
|
||||
@@ -14,7 +11,5 @@ remote_state {
|
||||
}
|
||||
|
||||
inputs = {
|
||||
cloudflare_account_id = local.cloudflare_account_id
|
||||
cloudflare_api_token = local.cloudflare_api_token
|
||||
tf_state_postgres_conn_str = local.tf_state_postgres_conn_str
|
||||
}
|
||||
@@ -4,7 +4,6 @@ node_modules
|
||||
.output
|
||||
.vercel
|
||||
/.svelte-kit
|
||||
/build
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 594 KiB After Width: | Height: | Size: 594 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
@@ -10,6 +10,7 @@ const config = {
|
||||
kit: {
|
||||
adapter: adapter({
|
||||
fallback: 'index.html',
|
||||
pages: '../dist/frontend',
|
||||
}),
|
||||
},
|
||||
};
|
||||