diff --git a/.github/workflows/infra-deploy.yml b/.github/workflows/infra-deploy.yml index 47976a8b86..0a3e98f61b 100644 --- a/.github/workflows/infra-deploy.yml +++ b/.github/workflows/infra-deploy.yml @@ -207,7 +207,7 @@ jobs: deploy_ok: name: "[Deploy] OK" - needs: [infra_cloud-primary_up, infra_uier_up, infra_workingparty_up, vercel_blog_deploy, vercel_brandgrab_deploy, vercel_slco_deploy, vercel_app_deploy, vercel_web_deploy, vercel_doprocess_deploy, vercel_uidocs_deploy, vercel_uier_deploy, vercel_workingparty_deploy] + needs: [infra_cloud-primary_up, infra_uier_up, infra_workingparty_up, infra_doprocess_up, vercel_blog_deploy, vercel_brandgrab_deploy, vercel_slco_deploy, vercel_app_deploy, vercel_web_deploy, vercel_doprocess_deploy, vercel_uidocs_deploy, vercel_uier_deploy, vercel_workingparty_deploy] runs-on: ubuntu-latest if: always() steps: diff --git a/cloud/src/Signal.Api.Common/Signal.Api.Common.csproj b/cloud/src/Signal.Api.Common/Signal.Api.Common.csproj index f84e95495c..fe09ecd14c 100644 --- a/cloud/src/Signal.Api.Common/Signal.Api.Common.csproj +++ b/cloud/src/Signal.Api.Common/Signal.Api.Common.csproj @@ -1,7 +1,7 @@  - net9.0 + net8.0 enable preview true diff --git a/cloud/src/Signal.Core/Signal.Core.csproj b/cloud/src/Signal.Core/Signal.Core.csproj index 988e9e51e9..cfe6fe2479 100644 --- a/cloud/src/Signal.Core/Signal.Core.csproj +++ b/cloud/src/Signal.Core/Signal.Core.csproj @@ -1,6 +1,6 @@ - net9.0 + net8.0 enable preview true diff --git a/cloud/src/Signal.Infrastructure.AzureDevOps/Signal.Infrastructure.AzureDevOps.csproj b/cloud/src/Signal.Infrastructure.AzureDevOps/Signal.Infrastructure.AzureDevOps.csproj index 9aed10a3aa..d936d5dee2 100644 --- a/cloud/src/Signal.Infrastructure.AzureDevOps/Signal.Infrastructure.AzureDevOps.csproj +++ b/cloud/src/Signal.Infrastructure.AzureDevOps/Signal.Infrastructure.AzureDevOps.csproj @@ -1,7 +1,7 @@ - net9.0 + net8.0 enable false true diff --git a/cloud/src/Signal.Infrastructure.AzureSpeech/Signal.Infrastructure.AzureSpeech.csproj b/cloud/src/Signal.Infrastructure.AzureSpeech/Signal.Infrastructure.AzureSpeech.csproj index 8e35ae368f..0193ef6a79 100644 --- a/cloud/src/Signal.Infrastructure.AzureSpeech/Signal.Infrastructure.AzureSpeech.csproj +++ b/cloud/src/Signal.Infrastructure.AzureSpeech/Signal.Infrastructure.AzureSpeech.csproj @@ -1,7 +1,7 @@ - net9.0 + net8.0 enable false true diff --git a/cloud/src/Signal.Infrastructure.AzureStorage.Tables/Signal.Infrastructure.AzureStorage.Tables.csproj b/cloud/src/Signal.Infrastructure.AzureStorage.Tables/Signal.Infrastructure.AzureStorage.Tables.csproj index 305940d4ba..067693f1c1 100644 --- a/cloud/src/Signal.Infrastructure.AzureStorage.Tables/Signal.Infrastructure.AzureStorage.Tables.csproj +++ b/cloud/src/Signal.Infrastructure.AzureStorage.Tables/Signal.Infrastructure.AzureStorage.Tables.csproj @@ -1,7 +1,7 @@ - net9.0 + net8.0 enable false true diff --git a/cloud/src/Signal.Infrastructure.Secrets/Signal.Infrastructure.Secrets.csproj b/cloud/src/Signal.Infrastructure.Secrets/Signal.Infrastructure.Secrets.csproj index 46a3126411..3d47e1fb79 100644 --- a/cloud/src/Signal.Infrastructure.Secrets/Signal.Infrastructure.Secrets.csproj +++ b/cloud/src/Signal.Infrastructure.Secrets/Signal.Infrastructure.Secrets.csproj @@ -1,7 +1,7 @@ - net9.0 + net8.0 enable false true diff --git a/cloud/src/Signalco.Api.Common.Health/Signalco.Api.Common.Health.csproj b/cloud/src/Signalco.Api.Common.Health/Signalco.Api.Common.Health.csproj index 798f8f30f1..1f0907cce8 100644 --- a/cloud/src/Signalco.Api.Common.Health/Signalco.Api.Common.Health.csproj +++ b/cloud/src/Signalco.Api.Common.Health/Signalco.Api.Common.Health.csproj @@ -1,7 +1,7 @@ - net9.0 + net8.0 enable enable true diff --git a/cloud/src/Signalco.Api.Public.RemoteBrowser/Signalco.Api.Public.RemoteBrowser.csproj b/cloud/src/Signalco.Api.Public.RemoteBrowser/Signalco.Api.Public.RemoteBrowser.csproj index ed46130627..2becc97d60 100644 --- a/cloud/src/Signalco.Api.Public.RemoteBrowser/Signalco.Api.Public.RemoteBrowser.csproj +++ b/cloud/src/Signalco.Api.Public.RemoteBrowser/Signalco.Api.Public.RemoteBrowser.csproj @@ -1,6 +1,6 @@  - net9.0 + net8.0 v4 Exe enable diff --git a/cloud/src/Signalco.Api.Public/Signalco.Api.Public.csproj b/cloud/src/Signalco.Api.Public/Signalco.Api.Public.csproj index c6eb6f797e..1bac2a2ce7 100644 --- a/cloud/src/Signalco.Api.Public/Signalco.Api.Public.csproj +++ b/cloud/src/Signalco.Api.Public/Signalco.Api.Public.csproj @@ -1,6 +1,6 @@  - net9.0 + net8.0 v4 exe 4cf77441-9fec-4ebc-8a60-918e7e2ed7aa diff --git a/cloud/src/Signalco.Api.RemoteBrowser/Signalco.Api.RemoteBrowser.csproj b/cloud/src/Signalco.Api.RemoteBrowser/Signalco.Api.RemoteBrowser.csproj index 2516600a54..579443d75d 100644 --- a/cloud/src/Signalco.Api.RemoteBrowser/Signalco.Api.RemoteBrowser.csproj +++ b/cloud/src/Signalco.Api.RemoteBrowser/Signalco.Api.RemoteBrowser.csproj @@ -1,7 +1,7 @@  - net9.0 + net8.0 enable enable 75b1b62c-8ca6-40ee-a213-e98c0340b5e6 diff --git a/cloud/src/Signalco.Channel.GitHubApp/Signalco.Channel.GitHubApp.csproj b/cloud/src/Signalco.Channel.GitHubApp/Signalco.Channel.GitHubApp.csproj index eb80a64856..2868edb8bd 100644 --- a/cloud/src/Signalco.Channel.GitHubApp/Signalco.Channel.GitHubApp.csproj +++ b/cloud/src/Signalco.Channel.GitHubApp/Signalco.Channel.GitHubApp.csproj @@ -1,6 +1,6 @@  - net9.0 + net8.0 v4 4cf77441-9fec-4ebc-8a60-918e7e2ed7aa exe diff --git a/cloud/src/Signalco.Channel.PhilipsHue/Signalco.Channel.PhilipsHue.csproj b/cloud/src/Signalco.Channel.PhilipsHue/Signalco.Channel.PhilipsHue.csproj index 357c75137b..8f70856ef4 100644 --- a/cloud/src/Signalco.Channel.PhilipsHue/Signalco.Channel.PhilipsHue.csproj +++ b/cloud/src/Signalco.Channel.PhilipsHue/Signalco.Channel.PhilipsHue.csproj @@ -1,6 +1,6 @@ - net9.0 + net8.0 exe v4 enable diff --git a/cloud/src/Signalco.Channel.Samsung/Signalco.Channel.Samsung.csproj b/cloud/src/Signalco.Channel.Samsung/Signalco.Channel.Samsung.csproj index 357c75137b..8f70856ef4 100644 --- a/cloud/src/Signalco.Channel.Samsung/Signalco.Channel.Samsung.csproj +++ b/cloud/src/Signalco.Channel.Samsung/Signalco.Channel.Samsung.csproj @@ -1,6 +1,6 @@ - net9.0 + net8.0 exe v4 enable diff --git a/cloud/src/Signalco.Channel.Slack/Signalco.Channel.Slack.csproj b/cloud/src/Signalco.Channel.Slack/Signalco.Channel.Slack.csproj index 0ae4a1f3a2..0878a25cda 100644 --- a/cloud/src/Signalco.Channel.Slack/Signalco.Channel.Slack.csproj +++ b/cloud/src/Signalco.Channel.Slack/Signalco.Channel.Slack.csproj @@ -1,6 +1,6 @@  - net9.0 + net8.0 exe v4 enable diff --git a/cloud/src/Signalco.Channel.Station/Signalco.Channel.Station.csproj b/cloud/src/Signalco.Channel.Station/Signalco.Channel.Station.csproj index 8570998878..9bc6726524 100644 --- a/cloud/src/Signalco.Channel.Station/Signalco.Channel.Station.csproj +++ b/cloud/src/Signalco.Channel.Station/Signalco.Channel.Station.csproj @@ -1,6 +1,6 @@ - net9.0 + net8.0 exe v4 enable diff --git a/cloud/src/Signalco.Channel.Zigbee2Mqtt/Signalco.Channel.Zigbee2Mqtt.csproj b/cloud/src/Signalco.Channel.Zigbee2Mqtt/Signalco.Channel.Zigbee2Mqtt.csproj index adb9f94002..232cd7daf9 100644 --- a/cloud/src/Signalco.Channel.Zigbee2Mqtt/Signalco.Channel.Zigbee2Mqtt.csproj +++ b/cloud/src/Signalco.Channel.Zigbee2Mqtt/Signalco.Channel.Zigbee2Mqtt.csproj @@ -1,6 +1,6 @@  - net9.0 + net8.0 exe v4 enable diff --git a/cloud/src/Signalco.Channel.iRobot/Signalco.Channel.iRobot.csproj b/cloud/src/Signalco.Channel.iRobot/Signalco.Channel.iRobot.csproj index e4c24e2d6c..82600e84f1 100644 --- a/cloud/src/Signalco.Channel.iRobot/Signalco.Channel.iRobot.csproj +++ b/cloud/src/Signalco.Channel.iRobot/Signalco.Channel.iRobot.csproj @@ -1,6 +1,6 @@ - net9.0 + net8.0 v4 exe enable diff --git a/cloud/src/Signalco.Common.Channel/Signalco.Common.Channel.csproj b/cloud/src/Signalco.Common.Channel/Signalco.Common.Channel.csproj index 2900ec7a42..d80d2f94ce 100644 --- a/cloud/src/Signalco.Common.Channel/Signalco.Common.Channel.csproj +++ b/cloud/src/Signalco.Common.Channel/Signalco.Common.Channel.csproj @@ -1,7 +1,7 @@ - net9.0 + net8.0 enable enable true diff --git a/cloud/src/Signalco.Func.Internal.ContactStateProcessor/Signalco.Func.Internal.ContactStateProcessor.csproj b/cloud/src/Signalco.Func.Internal.ContactStateProcessor/Signalco.Func.Internal.ContactStateProcessor.csproj index 88f09036c7..31cace1d7e 100644 --- a/cloud/src/Signalco.Func.Internal.ContactStateProcessor/Signalco.Func.Internal.ContactStateProcessor.csproj +++ b/cloud/src/Signalco.Func.Internal.ContactStateProcessor/Signalco.Func.Internal.ContactStateProcessor.csproj @@ -1,6 +1,6 @@ - net9.0 + net8.0 exe v4 enable diff --git a/cloud/src/Signalco.Func.Internal.Maintenance/Signalco.Func.Internal.Maintenance.csproj b/cloud/src/Signalco.Func.Internal.Maintenance/Signalco.Func.Internal.Maintenance.csproj index 511cbdab0c..f180e4d130 100644 --- a/cloud/src/Signalco.Func.Internal.Maintenance/Signalco.Func.Internal.Maintenance.csproj +++ b/cloud/src/Signalco.Func.Internal.Maintenance/Signalco.Func.Internal.Maintenance.csproj @@ -1,6 +1,6 @@  - net9.0 + net8.0 exe v4 enable diff --git a/cloud/src/Signalco.Func.Internal.Migration/Signalco.Func.Internal.Migration.csproj b/cloud/src/Signalco.Func.Internal.Migration/Signalco.Func.Internal.Migration.csproj index 06d27e446d..673566c7de 100644 --- a/cloud/src/Signalco.Func.Internal.Migration/Signalco.Func.Internal.Migration.csproj +++ b/cloud/src/Signalco.Func.Internal.Migration/Signalco.Func.Internal.Migration.csproj @@ -1,6 +1,6 @@  - net9.0 + net8.0 exe v4 enable diff --git a/cloud/src/Signalco.Func.Internal.TimeEntityPublic/Signalco.Func.Internal.TimeEntityPublic.csproj b/cloud/src/Signalco.Func.Internal.TimeEntityPublic/Signalco.Func.Internal.TimeEntityPublic.csproj index 9c4a160248..0c2d684db2 100644 --- a/cloud/src/Signalco.Func.Internal.TimeEntityPublic/Signalco.Func.Internal.TimeEntityPublic.csproj +++ b/cloud/src/Signalco.Func.Internal.TimeEntityPublic/Signalco.Func.Internal.TimeEntityPublic.csproj @@ -1,6 +1,6 @@  - net9.0 + net8.0 exe v4 enable diff --git a/cloud/src/Signalco.Func.Internal.UsageProcessor/Signalco.Func.Internal.UsageProcessor.csproj b/cloud/src/Signalco.Func.Internal.UsageProcessor/Signalco.Func.Internal.UsageProcessor.csproj index 0824cf1e8e..27cc894c55 100644 --- a/cloud/src/Signalco.Func.Internal.UsageProcessor/Signalco.Func.Internal.UsageProcessor.csproj +++ b/cloud/src/Signalco.Func.Internal.UsageProcessor/Signalco.Func.Internal.UsageProcessor.csproj @@ -1,6 +1,6 @@  - net9.0 + net8.0 exe v4 enable diff --git a/cloud/src/Signalco.Infrastructure.Processor.Tests/Signalco.Infrastructure.Processor.Tests.csproj b/cloud/src/Signalco.Infrastructure.Processor.Tests/Signalco.Infrastructure.Processor.Tests.csproj index ccaa84fef3..511e071dfc 100644 --- a/cloud/src/Signalco.Infrastructure.Processor.Tests/Signalco.Infrastructure.Processor.Tests.csproj +++ b/cloud/src/Signalco.Infrastructure.Processor.Tests/Signalco.Infrastructure.Processor.Tests.csproj @@ -1,7 +1,7 @@ - net9.0 + net8.0 enable AGPL-3.0-only false diff --git a/cloud/src/Signalco.Infrastructure.Processor/Signalco.Infrastructure.Processor.csproj b/cloud/src/Signalco.Infrastructure.Processor/Signalco.Infrastructure.Processor.csproj index 1e1cd9aea2..ac71e86d0a 100644 --- a/cloud/src/Signalco.Infrastructure.Processor/Signalco.Infrastructure.Processor.csproj +++ b/cloud/src/Signalco.Infrastructure.Processor/Signalco.Infrastructure.Processor.csproj @@ -1,7 +1,7 @@ - net9.0 + net8.0 enable enable false diff --git a/discrete/Signalco.Discrete.Api.Mutex/cloud/Signalco.Discrete.Api.Mutex.csproj b/discrete/Signalco.Discrete.Api.Mutex/cloud/Signalco.Discrete.Api.Mutex.csproj index 1323033c71..54310ad417 100644 --- a/discrete/Signalco.Discrete.Api.Mutex/cloud/Signalco.Discrete.Api.Mutex.csproj +++ b/discrete/Signalco.Discrete.Api.Mutex/cloud/Signalco.Discrete.Api.Mutex.csproj @@ -1,6 +1,6 @@ - net9.0 + net8.0 v4 Exe enable diff --git a/infra/.gitignore b/infra/.gitignore index 96fab4fed3..9b168cc90f 100644 --- a/infra/.gitignore +++ b/infra/.gitignore @@ -1,38 +1,309 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. +# Created by https://www.toptal.com/developers/gitignore/api/webstorm,node,visualstudiocode,macos +# Edit at https://www.toptal.com/developers/gitignore?templates=webstorm,node,visualstudiocode,macos -# Dependencies -node_modules -.pnp -.pnp.js +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Node ### +# 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 -# Local env files +# 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.local .env.development.local .env.test.local .env.production.local +.env.local -# Testing -coverage +# 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 -# Turbo -.turbo +# Docusaurus cache and generated files +.docusaurus -# Vercel -.vercel +# Serverless directories +.serverless/ -# Build Outputs -.next/ +# 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.* + +### Node Patch ### +# Serverless Webpack directories +.webpack/ + +# Optional stylelint cache + +# SvelteKit build / generate output +.svelte-kit + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +### WebStorm ### +# 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/ -build -dist +# mpeltonen/sbt-idea plugin +.idea_modules/ -# Debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* +# JIRA plugin +atlassian-ide-plugin.xml -# Misc -.DS_Store -*.pem +# 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 + +### WebStorm Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +# Azure Toolkit for IntelliJ plugin +# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij +.idea/**/azureSettings.xml + +# End of https://www.toptal.com/developers/gitignore/api/webstorm,node,visualstudiocode,macos \ No newline at end of file diff --git a/infra/apps/cloud-primary/package.json b/infra/apps/cloud-primary/package.json index ab2f0d03cc..c33bcac69b 100644 --- a/infra/apps/cloud-primary/package.json +++ b/infra/apps/cloud-primary/package.json @@ -24,7 +24,7 @@ }, "dependencies": { "@checkly/pulumi": "1.1.4", - "@pulumi/azure-native": "2.72.0", + "@pulumi/azure-native": "2.71.0", "@pulumi/cloudflare": "5.43.0", "@pulumi/command": "1.0.1", "@pulumi/docker": "4.5.7", diff --git a/infra/apps/cloud-primary/src/createChannelFunction.ts b/infra/apps/cloud-primary/src/createChannelFunction.ts index c2e6567579..0f8a23d9af 100644 --- a/infra/apps/cloud-primary/src/createChannelFunction.ts +++ b/infra/apps/cloud-primary/src/createChannelFunction.ts @@ -1,4 +1,4 @@ -import { type StackReference } from '@pulumi/pulumi'; +import { Output, type StackReference } from '@pulumi/pulumi'; import { type ResourceGroup } from '@pulumi/azure-native/resources/index.js'; import { createPublicFunction, assignFunctionCodeAsync } from '@infra/pulumi/azure'; import { apiStatusCheck } from '@infra/pulumi/checkly'; @@ -12,7 +12,8 @@ export async function createChannelFunction( storageAccount: StorageAccount, zipsContainer: BlobContainer, currentStack: StackReference, - protect: boolean): Promise<{ + protect: boolean, + appPlanId: Output): Promise<{ nameLower: string, name: string, } & ReturnType & Awaited>> { @@ -29,6 +30,7 @@ export async function createChannelFunction( corsDomains, currentStack, protect, + appPlanId, ); const codePath = `../../../cloud/src/Signalco.Channel.${channelName}`; diff --git a/infra/apps/cloud-primary/src/createInternalFunctionAsync.ts b/infra/apps/cloud-primary/src/createInternalFunctionAsync.ts index 8a27e66280..6110f07ab1 100644 --- a/infra/apps/cloud-primary/src/createInternalFunctionAsync.ts +++ b/infra/apps/cloud-primary/src/createInternalFunctionAsync.ts @@ -4,6 +4,7 @@ import { publishProjectAsync } from '@infra/pulumi/dotnet'; import { apiStatusCheck } from '@infra/pulumi/checkly'; import { ConfInternalApiCheckInterval } from './config.js'; import { type BlobContainer, type StorageAccount } from '@pulumi/azure-native/storage/index.js'; +import { Output } from '@pulumi/pulumi'; const internalFunctionPrefix = 'cint'; @@ -12,7 +13,8 @@ export async function createInternalFunctionAsync( name: string, storageAccount: StorageAccount, zipsContainer: BlobContainer, - shouldProtect: boolean): Promise<{ + shouldProtect: boolean, + appPlanId?: Output): Promise<{ name: string, shortName: string, } & ReturnType & Awaited>> { @@ -21,7 +23,9 @@ export async function createInternalFunctionAsync( resourceGroup, `int${shortName}`, shouldProtect, - false); + false, + undefined, + appPlanId); const codePath = `../../../cloud/src/Signalco.Func.Internal.${name}`; const publishResult = await publishProjectAsync(codePath); diff --git a/infra/apps/cloud-primary/src/index.ts b/infra/apps/cloud-primary/src/index.ts index f3f46a01b8..52618e0e40 100644 --- a/infra/apps/cloud-primary/src/index.ts +++ b/infra/apps/cloud-primary/src/index.ts @@ -25,6 +25,7 @@ import { Dashboard } from '@checkly/pulumi'; import { nextJsApp, vercelApp } from '@infra/pulumi/vercel'; import { createChannelFunction } from './createChannelFunction.js'; import { createInternalFunctionAsync } from './createInternalFunctionAsync.js'; +import { funcAppPlan } from '../../../packages/pulumi/src/azure/funcAppPlan.js'; /* * NOTE: `parent` configuration is currently disabled for all resources because @@ -51,16 +52,16 @@ const up = async () => { return {}; } else { const shouldProtect = stack === 'production'; - const domainName = `${config.require('domain')}`; - const resourceGroupName = `signalco-cloud-${stack}`; const signalrPrefix = 'sr'; const storagePrefix = 'store'; const keyvaultPrefix = 'kv'; const currentStack = new StackReference(`signalco/${getProject()}/${getStack()}`); - const resourceGroup = new ResourceGroup(resourceGroupName); + const resourceGroup = new ResourceGroup(`signalco-cloud-${stack}`); + + const domainName = `${config.require('domain')}`; const corsDomains = [`app.${domainName}`, `www.${domainName}`, domainName]; const signalr = createSignalR(resourceGroup, signalrPrefix, corsDomains, stack === 'production' ? 'standard1' : 'free', false); @@ -72,6 +73,9 @@ const up = async () => { // Create log workspace const logWorkspace = createLogWorkspace(resourceGroup, 'log'); + // Create functions app plan + const sharedLinuxConsumptionPlan = funcAppPlan(resourceGroup, 'linux-consumption', false); + // Generate public functions const publicApis = [ { name: '', prefix: 'cpub', subDomain: 'api', cors: corsDomains }, @@ -85,8 +89,12 @@ const up = async () => { api.subDomain, api.cors, currentStack, - false); - const apiFuncPublish = await publishProjectAsync(['../../../cloud/src/Signalco.Api.Public', api.name].filter(i => i.length).join('.')); + false, + sharedLinuxConsumptionPlan.plan.id); + const apiFuncPublish = await publishProjectAsync([ + '../../../cloud/src/Signalco.Api.Public', + api.name, + ].filter(i => i.length).join('.')); const apiFuncCode = await assignFunctionCodeAsync( resourceGroup, funcStorage.storageAccount.storageAccount, @@ -106,7 +114,13 @@ const up = async () => { const internalNames = ['UsageProcessor', 'ContactStateProcessor', 'TimeEntityPublic', 'Maintenance', 'Migration']; const internalFuncs = []; for (const funcName of internalNames) { - internalFuncs.push(await createInternalFunctionAsync(resourceGroup, funcName, funcStorage.storageAccount.storageAccount, funcStorage.zipsContainer, false)); + internalFuncs.push(await createInternalFunctionAsync( + resourceGroup, + funcName, + funcStorage.storageAccount.storageAccount, + funcStorage.zipsContainer, + false, + sharedLinuxConsumptionPlan.plan.id)); } // Generate channels functions @@ -117,7 +131,14 @@ const up = async () => { : [...productionChannelNames, ...nextChannelNames]; const channelsFunctions = []; for (const channelName of channelNames) { - channelsFunctions.push(await createChannelFunction(channelName, resourceGroup, funcStorage.storageAccount.storageAccount, funcStorage.zipsContainer, currentStack, false)); + channelsFunctions.push(await createChannelFunction( + channelName, + resourceGroup, + funcStorage.storageAccount.storageAccount, + funcStorage.zipsContainer, + currentStack, + false, + sharedLinuxConsumptionPlan.plan.id)); } // Generate discrete functions @@ -125,6 +146,7 @@ const up = async () => { const discreteFunctions = []; for (const funcName of discreteNames) { const discreteResourceGroup = new ResourceGroup(`signalco-discrete-${stack}-${funcName.toLowerCase()}`); + const discreteLinuxConsumptionPlan = funcAppPlan(discreteResourceGroup, `linux-consumption-${funcName}`, false); const discreteStorage = createFunctionsStorage(discreteResourceGroup, `${funcName.toLowerCase().substring(0, 5)}funcs`, false); const func = createPublicFunction( discreteResourceGroup, @@ -132,7 +154,8 @@ const up = async () => { `${funcName.toLowerCase()}.api`, undefined, currentStack, - false); + false, + discreteLinuxConsumptionPlan.plan.id); const funcPublish = await publishProjectAsync(`../../../discrete/Signalco.Discrete.Api.${funcName}/cloud`); const funcCode = await assignFunctionCodeAsync( discreteResourceGroup, @@ -157,7 +180,7 @@ const up = async () => { const registry = getContainerRegistry(resourceGroupSharedName, containerRegistryName); const appRb = createRemoteBrowser(resourceGroup, 'rb', registry, logWorkspace, false); - // Create general storage and prepare tables + // Create general storage const storage = createStorageAccount(resourceGroup, storagePrefix, shouldProtect); // ACS (Email) diff --git a/infra/apps/uier/Pulumi.next.yaml b/infra/apps/uier/Pulumi.next.yaml index 8da0b9b45f..484539fa7c 100644 --- a/infra/apps/uier/Pulumi.next.yaml +++ b/infra/apps/uier/Pulumi.next.yaml @@ -1,4 +1,10 @@ config: + azure-native:clientId: f72781c2-20ca-47bf-bdfc-a39343fbaede + azure-native:clientSecret: + secure: AAABAKNv1KwWFJpsVdaGYA6F6CcjxQ/stOwVwh99ZQzIA3K/9WD9JOITtbIndAgOdipNfvlmmHASUOjuQsW6tosOXJKaVtgk + azure-native:location: westeurope + azure-native:subscriptionId: d22db5bd-ca9a-447c-9b1b-dc56aca401d7 + azure-native:tenantId: c7837eb3-77ca-413b-8653-c97b08886678 cloudflare:apiToken: secure: AAABAAqPzyAV9mvmavpxCxB+Ws+snM4JK8mNiAxa+WqmzniW5gNmJpHXGgXZI69SkL251Lhhrp43BUrvHnTEbAFf3lG8/Wsl uier:zoneid: diff --git a/infra/apps/uier/Pulumi.production.yaml b/infra/apps/uier/Pulumi.production.yaml index 8c7bf97f11..16410439b6 100644 --- a/infra/apps/uier/Pulumi.production.yaml +++ b/infra/apps/uier/Pulumi.production.yaml @@ -1,4 +1,6 @@ config: + azure-native:location: westeurope + azure-native:subscriptionId: d22db5bd-ca9a-447c-9b1b-dc56aca401d7 cloudflare:apiToken: secure: AAABACGEfMwr2aNFBpAz+WdsfG/AgG31Ktvpe0KVGFCpoiCN8wjhe7NJYY4NJT3o/M+q8x85EOwn3Q9CfljxdPEUq4SPuXxo uier:zoneid: diff --git a/infra/apps/uier/package.json b/infra/apps/uier/package.json index 351f005501..a098e5493d 100644 --- a/infra/apps/uier/package.json +++ b/infra/apps/uier/package.json @@ -27,7 +27,6 @@ "@pulumi/azure-native": "2.72.0", "@pulumi/cloudflare": "5.43.0", "@pulumi/command": "1.0.1", - "@pulumi/docker": "4.5.7", "@pulumi/pulumi": "3.139.0", "@pulumiverse/vercel": "1.14.3" } diff --git a/infra/apps/uier/src/index.ts b/infra/apps/uier/src/index.ts index 78aa051f70..4abb9c13e7 100644 --- a/infra/apps/uier/src/index.ts +++ b/infra/apps/uier/src/index.ts @@ -1,11 +1,15 @@ import { nextJsApp } from '@infra/pulumi/vercel'; import { dnsRecord } from '@infra/pulumi/cloudflare'; -import { ProjectDomain } from '@pulumiverse/vercel'; +import { ProjectDomain, ProjectEnvironmentVariable } from '@pulumiverse/vercel'; import { getStack } from '@pulumi/pulumi'; +import { ResourceGroup } from '@pulumi/azure-native/resources/index.js'; +import { DatabaseAccount, SqlResourceSqlDatabase, SqlResourceSqlContainer, DatabaseAccountOfferType, listDatabaseAccountConnectionStringsOutput } from '@pulumi/azure-native/documentdb/index.js'; +import { createStorageAccount } from '@infra/pulumi/azure'; +import { Profile, Endpoint, SkuName } from '@pulumi/azure-native/cdn/index.js'; const up = async () => { const stack = getStack(); - + const shouldProtect = stack === 'production'; const app = nextJsApp('uier', 'uier', 'web/apps/uier'); // Configure domain name @@ -24,6 +28,91 @@ const up = async () => { dnsRecord('vercel-uier', '@', '76.76.21.21', 'A', false); } } + + // Azure + const resourceGroupName = `uier-${stack}`; + const resourceGroup = new ResourceGroup(resourceGroupName); + + // TODO: Create Static files storage + // const staticFilesStorage = createStorageAccount( + // resourceGroup, + // 'static', + // shouldProtect, + // ); + + // TODO: Create CDN for status files storage + // const cdnProfile = new Profile('uier-staticcndprofile', { + // resourceGroupName: resourceGroup.name, + // sku: { + // name: SkuName.Standard_Microsoft, + // }, + // profileName: 'uier-staticFiles-cdn', + // }); + // new Endpoint('uier-staticFiles', { + // resourceGroupName: resourceGroup.name, + // profileName: cdnProfile.name, + // origins: [ + + // ], + // }); + + // Create an Azure Cosmos DB Account for SQL API + const cosmosAccountName = 'uierdb'; + const databaseName = 'uierdata'; + const cosmosDbAccount = new DatabaseAccount(cosmosAccountName, { + resourceGroupName: resourceGroup.name, + databaseAccountOfferType: DatabaseAccountOfferType.Standard, + capabilities: [ + { + name: 'EnableServerless', // Use specific capabilities if needed + }, + ], + consistencyPolicy: { + defaultConsistencyLevel: 'Session', // Adjust the consistency level as needed + }, + locations: [ + { locationName: 'West Europe' }, + ], + }); + const cosmosPrimaryConnectionString = listDatabaseAccountConnectionStringsOutput({ + resourceGroupName: resourceGroup.name, + accountName: cosmosDbAccount.name, + }).apply(keys => keys.connectionStrings?.at(0)?.connectionString ?? ''); + + // Create a SQL Database within the Cosmos DB Account + const sqlDatabase = new SqlResourceSqlDatabase(databaseName, { + resourceGroupName: resourceGroup.name, + accountName: cosmosDbAccount.name, + resource: { + id: databaseName, + }, + }); + + // Creating the containers inside the database + const containerNames = ['comments']; + containerNames.map((containerName) => + new SqlResourceSqlContainer(`dbcontainer-${containerName}`, { + resourceGroupName: resourceGroup.name, + accountName: cosmosDbAccount.name, + databaseName: sqlDatabase.name, + containerName: containerName, + resource: { + id: containerName, + partitionKey: { + kind: 'Hash', + paths: ['/domain'], + }, + }, + }), + ); + + // Assign app environment variables + new ProjectEnvironmentVariable('vercel-uier-env-cosmos', { + projectId: app.projectId, + key: 'COSMOSDB_CONNECTION_STRING', + value: cosmosPrimaryConnectionString, + targets: stack === 'production' ? ['production'] : ['preview'], + }); }; export default up; \ No newline at end of file diff --git a/infra/infra.code-workspace b/infra/infra.code-workspace new file mode 100644 index 0000000000..7d96cb55b3 --- /dev/null +++ b/infra/infra.code-workspace @@ -0,0 +1,41 @@ +{ + "folders": [ + { + "name": "✨ infra-monorepo", + "path": "." + }, + { + "name": "🚀 cloud-primary", + "path": "apps/cloud-primary" + }, + { + "name": "🚀 uier.io", + "path": "apps/uier" + }, + { + "name": "🚀 doprocess.app", + "path": "apps/doprocess" + }, + { + "name": "🚀 remote-browser", + "path": "apps/remote-browser" + }, + { + "name": "🚀 workingparty.ai", + "path": "apps/workingparty" + }, + { + "name": "📦 @infra/eslint-config", + "path": "packages/eslint-config" + }, + { + "name": "📦 @infra/pulumi", + "path": "packages/pulumi" + }, + { + "name": "📦 @infra/typescript-config", + "path": "packages/typescript-config" + } + ], + "settings": {} +} \ No newline at end of file diff --git a/infra/packages/pulumi/package.json b/infra/packages/pulumi/package.json index ba2e10dd1b..e6e6455f32 100644 --- a/infra/packages/pulumi/package.json +++ b/infra/packages/pulumi/package.json @@ -37,7 +37,7 @@ }, "dependencies": { "@checkly/pulumi": "1.1.4", - "@pulumi/azure-native": "2.72.0", + "@pulumi/azure-native": "2.71.0", "@pulumi/cloudflare": "5.43.0", "@pulumi/command": "1.0.1", "@pulumi/docker": "4.5.7", diff --git a/infra/packages/pulumi/src/azure/assignCustomDomain.ts b/infra/packages/pulumi/src/azure/assignCustomDomain.ts index 63f77fc596..7d8eaf0cde 100644 --- a/infra/packages/pulumi/src/azure/assignCustomDomain.ts +++ b/infra/packages/pulumi/src/azure/assignCustomDomain.ts @@ -1,7 +1,6 @@ import { Config, interpolate, type StackReference, Output } from '@pulumi/pulumi'; import { WebApp, - AppServicePlan, WebAppHostNameBinding, Certificate, HostNameType, @@ -14,7 +13,7 @@ import { dnsRecord } from '../cloudflare/dnsRecord.js'; export function assignCustomDomain( resourceGroup: ResourceGroup, webApp: WebApp, - servicePlan: AppServicePlan, + servicePlanId: Output, namePrefix: string, subDomainName: string, currentStack: StackReference, @@ -56,7 +55,7 @@ export function assignCustomDomain( resourceGroupName: resourceGroup.name, name: `cert-${namePrefix}`, canonicalName: fullDomainName, - serverFarmId: servicePlan.id, + serverFarmId: servicePlanId, }, { protect, }); diff --git a/infra/packages/pulumi/src/azure/createFunction.ts b/infra/packages/pulumi/src/azure/createFunction.ts index c414def624..43bfe0e6dd 100644 --- a/infra/packages/pulumi/src/azure/createFunction.ts +++ b/infra/packages/pulumi/src/azure/createFunction.ts @@ -1,29 +1,22 @@ -import { WebApp, AppServicePlan, SupportedTlsVersions } from '@pulumi/azure-native/web/index.js'; +import { WebApp, SupportedTlsVersions } from '@pulumi/azure-native/web/index.js'; import { ResourceGroup } from '@pulumi/azure-native/resources/index.js'; -import { Input, interpolate } from '@pulumi/pulumi'; +import { Input, interpolate, Output } from '@pulumi/pulumi'; +import { funcAppPlan } from './funcAppPlan.js'; export function createFunction( resourceGroup: ResourceGroup, namePrefix: string, protect: boolean, isPublic: boolean, - cors?: Input[]) { - const plan = new AppServicePlan(`func-appplan-${namePrefix}`, { - resourceGroupName: resourceGroup.name, - kind: 'linux', - reserved: true, - sku: { - name: 'Y1', - tier: 'Dynamic', - }, - }, { - protect, - // parent: resourceGroup - }); + cors?: Input[], + serviceFarmId?: Output) { + const targetServiceFarmId = + serviceFarmId ?? + funcAppPlan(resourceGroup, namePrefix, protect).plan.id; const app = new WebApp(`func-${namePrefix}`, { resourceGroupName: resourceGroup.name, - serverFarmId: plan.id, + serverFarmId: targetServiceFarmId, kind: 'functionapp,linux', containerSize: 1536, dailyMemoryTimeQuota: 500000, @@ -33,13 +26,15 @@ export function createFunction( }, keyVaultReferenceIdentity: 'SystemAssigned', siteConfig: { - linuxFxVersion: 'DOTNET-ISOLATED|9.0', + // TODO: Detect .NET version + linuxFxVersion: 'DOTNET-ISOLATED|8.0', http20Enabled: true, minTlsVersion: SupportedTlsVersions.SupportedTlsVersions_1_2, functionAppScaleLimit: 200, cors: { allowedOrigins: cors ? [ + // TODO: Remove localhost 'https://localhost:3000', // Next.js 'http://localhost:3000', // Next.js 'https://localhost:3001', // Next.js @@ -57,6 +52,6 @@ export function createFunction( return { webApp: app, - servicePlan: plan, + servicePlanId: targetServiceFarmId, }; } diff --git a/infra/packages/pulumi/src/azure/createPublicFunction.ts b/infra/packages/pulumi/src/azure/createPublicFunction.ts index 184b5633ab..871aec355d 100644 --- a/infra/packages/pulumi/src/azure/createPublicFunction.ts +++ b/infra/packages/pulumi/src/azure/createPublicFunction.ts @@ -1,5 +1,5 @@ import { ResourceGroup } from '@pulumi/azure-native/resources/index.js'; -import { type Input, type StackReference } from '@pulumi/pulumi'; +import { Output, type Input, type StackReference } from '@pulumi/pulumi'; import { assignCustomDomain } from './assignCustomDomain.js'; import { createFunction } from './createFunction.js'; @@ -9,10 +9,11 @@ export function createPublicFunction( subDomainName: string, corsDomains: Input[] | undefined, currentStack: StackReference, - protect: boolean) { - const pubFunc = createFunction(resourceGroup, namePrefix, protect, true, corsDomains); + protect: boolean, + appPlanId?: Output) { + const pubFunc = createFunction(resourceGroup, namePrefix, protect, true, corsDomains, appPlanId); const domain = assignCustomDomain( - resourceGroup, pubFunc.webApp, pubFunc.servicePlan, namePrefix, subDomainName, + resourceGroup, pubFunc.webApp, pubFunc.servicePlanId, namePrefix, subDomainName, currentStack, protect); return { diff --git a/infra/packages/pulumi/src/azure/funcAppPlan.ts b/infra/packages/pulumi/src/azure/funcAppPlan.ts new file mode 100644 index 0000000000..9f8ed6d3f2 --- /dev/null +++ b/infra/packages/pulumi/src/azure/funcAppPlan.ts @@ -0,0 +1,25 @@ +import { AppServicePlan } from '@pulumi/azure-native/web/index.js'; +import { ResourceGroup } from '@pulumi/azure-native/resources/index.js'; + +export function funcAppPlan( + resourceGroup: ResourceGroup, + namePrefix: string, + protect: boolean, +) { + const plan = new AppServicePlan(`func-appplan-${namePrefix}`, { + resourceGroupName: resourceGroup.name, + kind: 'linux', + reserved: true, + sku: { + name: 'Y1', + tier: 'Dynamic', + }, + }, { + protect, + // parent: resourceGroup + }); + + return { + plan, + }; +} \ No newline at end of file diff --git a/infra/packages/pulumi/src/dotnet/publishProjectAsync.ts b/infra/packages/pulumi/src/dotnet/publishProjectAsync.ts index 62810bec96..1160380d5c 100644 --- a/infra/packages/pulumi/src/dotnet/publishProjectAsync.ts +++ b/infra/packages/pulumi/src/dotnet/publishProjectAsync.ts @@ -1,7 +1,7 @@ import { run } from '@pulumi/command/local/index.js'; export async function publishProjectAsync(codePath: string) { - const dotnetVersion = 9; + const dotnetVersion = 8; await run({ command: 'dotnet clean', dir: codePath, diff --git a/infra/pnpm-lock.yaml b/infra/pnpm-lock.yaml index d7c55728e0..771436c159 100644 --- a/infra/pnpm-lock.yaml +++ b/infra/pnpm-lock.yaml @@ -24,8 +24,8 @@ importers: specifier: 1.1.4 version: 1.1.4(ts-node@7.0.1)(typescript@5.6.3) '@pulumi/azure-native': - specifier: 2.72.0 - version: 2.72.0(ts-node@7.0.1)(typescript@5.6.3) + specifier: 2.71.0 + version: 2.71.0(ts-node@7.0.1)(typescript@5.6.3) '@pulumi/cloudflare': specifier: 5.43.0 version: 5.43.0(ts-node@7.0.1)(typescript@5.6.3) @@ -191,9 +191,6 @@ importers: '@pulumi/command': specifier: 1.0.1 version: 1.0.1(ts-node@7.0.1)(typescript@5.6.3) - '@pulumi/docker': - specifier: 4.5.7 - version: 4.5.7(ts-node@7.0.1)(typescript@5.6.3) '@pulumi/pulumi': specifier: 3.139.0 version: 3.139.0(ts-node@7.0.1)(typescript@5.6.3) @@ -314,8 +311,8 @@ importers: specifier: 1.1.4 version: 1.1.4(ts-node@7.0.1)(typescript@5.6.3) '@pulumi/azure-native': - specifier: 2.72.0 - version: 2.72.0(ts-node@7.0.1)(typescript@5.6.3) + specifier: 2.71.0 + version: 2.71.0(ts-node@7.0.1)(typescript@5.6.3) '@pulumi/cloudflare': specifier: 5.43.0 version: 5.43.0(ts-node@7.0.1)(typescript@5.6.3) @@ -773,6 +770,9 @@ packages: '@protobufjs/utf8@1.1.0': resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + '@pulumi/azure-native@2.71.0': + resolution: {integrity: sha512-VSDnsLN3kONnrxdVLy9XbPWeKEiZsWoU4czqUHihx6F7MU82H3Gt8Pq6EtJOlMjdK0B991L+g5IPYMd4qLZpcg==} + '@pulumi/azure-native@2.72.0': resolution: {integrity: sha512-UP791cNKSUGPYN4N4OalAehulW5DiajydRXeSAEi/avlv/yznAQ9pjq8J6S+VHalU1+6enq8K3JLmM3l0Q5ZqA==} @@ -3230,6 +3230,15 @@ snapshots: '@protobufjs/utf8@1.1.0': {} + '@pulumi/azure-native@2.71.0(ts-node@7.0.1)(typescript@5.6.3)': + dependencies: + '@pulumi/pulumi': 3.139.0(ts-node@7.0.1)(typescript@5.6.3) + transitivePeerDependencies: + - bluebird + - supports-color + - ts-node + - typescript + '@pulumi/azure-native@2.72.0(ts-node@7.0.1)(typescript@5.6.3)': dependencies: '@pulumi/pulumi': 3.139.0(ts-node@7.0.1)(typescript@5.6.3) diff --git a/station/DEVELOPMENT.md b/station/DEVELOPMENT.md index bd453a04f1..c873c5d2fe 100644 --- a/station/DEVELOPMENT.md +++ b/station/DEVELOPMENT.md @@ -19,7 +19,7 @@ Actions status Requirements: -- [.NET 7 SDK](https://dotnet.microsoft.com/) +- [.NET 9 SDK](https://dotnet.microsoft.com/) Running station diff --git a/web/apps/uier/.env.example b/web/apps/uier/.env.example index e69de29bb2..024f2a56e3 100644 --- a/web/apps/uier/.env.example +++ b/web/apps/uier/.env.example @@ -0,0 +1 @@ +COSMOSDB_CONNECTION_STRING=secret diff --git a/web/apps/uier/app/(rest)/(marketing)/page.tsx b/web/apps/uier/app/(rest)/(marketing)/page.tsx index 2d01a4e4db..353a2cb517 100644 --- a/web/apps/uier/app/(rest)/(marketing)/page.tsx +++ b/web/apps/uier/app/(rest)/(marketing)/page.tsx @@ -20,16 +20,6 @@ const data: SectionData[] = [ header: 'Product', ctas: [ { label: 'uier', href: KnownPages.App }, - { label: 'uier', href: KnownPages.App }, - { label: 'uier', href: KnownPages.App }, - ] - }, - { - header: 'Product', - ctas: [ - { label: 'uier', href: KnownPages.App }, - { label: 'uier', href: KnownPages.App }, - { label: 'uier', href: KnownPages.App }, ] }, { diff --git a/web/apps/uier/app/api/comments/route.ts b/web/apps/uier/app/api/comments/route.ts new file mode 100644 index 0000000000..5c01734b25 --- /dev/null +++ b/web/apps/uier/app/api/comments/route.ts @@ -0,0 +1,33 @@ +import { createComment, getComments, updateComment } from '../../../src/lib/repo/commentsRepo'; + +export async function GET(request: Request) { + const domain = request.headers.get('host'); + if (!domain) + return new Response('Couldn\'t resolve domain', { status: 402 }); + + const comments = await getComments(domain); + return Response.json(comments); +} + +export async function POST(request: Request) { + const domain = request.headers.get('host'); + if (!domain) + return new Response('Couldn\'t resolve domain', { status: 402 }); + + const comment = await request.json() as { + id?: string, + path: string, + position: object, // TODO: Types + thread: object, // TODO: Types + device?: object, // TODO: Types + resolved?: boolean + }; + + if (comment.id) { + await updateComment(domain, comment.id, comment); + return Response.json({ id: comment.id }); + } else { + const id = await createComment({ domain, ...comment }); + return Response.json({ id }, { status: 201 }); + } +} diff --git a/web/apps/uier/next.config.js b/web/apps/uier/next.config.js index 5b3fe83206..3da2a475d1 100644 --- a/web/apps/uier/next.config.js +++ b/web/apps/uier/next.config.js @@ -38,7 +38,7 @@ const nextConfig = { [ knownSecureHeadersExternalUrls.vercel, knownSecureHeadersExternalUrls.googleFonts, - { scriptSrc: 'http://localhost:4005', styleSrc: 'http://localhost:4005' }, + { scriptSrc: 'http://localhost:4005', styleSrc: 'http://localhost:4005', connectSrc: 'http://localhost:4005' }, ] )) }]; diff --git a/web/apps/uier/package.json b/web/apps/uier/package.json index 46fd9d26b8..d136651811 100644 --- a/web/apps/uier/package.json +++ b/web/apps/uier/package.json @@ -17,6 +17,7 @@ "lint": "next lint" }, "dependencies": { + "@azure/cosmos": "4.1.1", "@enterwell/react-hooks": "0.5.0", "@next/env": "15.0.3", "@signalco/cms-components-marketing": "workspace:*", @@ -33,6 +34,7 @@ "@tanstack/react-query-devtools": "5.60.5", "@vercel/analytics": "1.4.0", "classix": "2.2.0", + "nanoid": "5.0.8", "next": "15.0.3", "next-secure-headers": "2.2.0", "next-themes": "0.4.3", diff --git a/web/apps/uier/src/lib/db/client.ts b/web/apps/uier/src/lib/db/client.ts new file mode 100644 index 0000000000..c9880006a3 --- /dev/null +++ b/web/apps/uier/src/lib/db/client.ts @@ -0,0 +1,26 @@ +import { type Container, CosmosClient, type Database } from '@azure/cosmos'; + +let client: CosmosClient | null = null; +let dataDb: Database | null = null; + +let commentsContainer: Container | null = null; + +function cosmosClient() { + if (client == null) { + const connectionString = process.env.COSMOSDB_CONNECTION_STRING; + if (!connectionString) + throw new Error('COSMOSDB_CONNECTION_STRING is not available in the environment. Please set it before using this feature.'); + + client = new CosmosClient(connectionString); + } + + return client; +} + +function cosmosDataDb() { + return dataDb = dataDb ?? cosmosClient().database('uierdata'); +} + +export function cosmosDataContainerComments() { + return commentsContainer = commentsContainer ?? cosmosDataDb().container('comments'); +} diff --git a/web/apps/uier/src/lib/db/schema.ts b/web/apps/uier/src/lib/db/schema.ts new file mode 100644 index 0000000000..096fcaa255 --- /dev/null +++ b/web/apps/uier/src/lib/db/schema.ts @@ -0,0 +1,9 @@ +export type DbComment = { + id: string; + domain: string; + path: string; + position: object, // TODO: Type + thread: object, // TODO: Type + device?: object, // TODO: Type + resolved?: boolean; +}; diff --git a/web/apps/uier/src/lib/repo/commentsRepo.ts b/web/apps/uier/src/lib/repo/commentsRepo.ts new file mode 100644 index 0000000000..33c3f526be --- /dev/null +++ b/web/apps/uier/src/lib/repo/commentsRepo.ts @@ -0,0 +1,37 @@ +import { nanoid } from 'nanoid'; +import { DbComment } from '../db/schema'; +import { cosmosDataContainerComments } from '../db/client'; + +export async function getComments(domain: string) { + return (await cosmosDataContainerComments().items.query({ + query: 'SELECT * FROM c WHERE c.domain = @domain', + parameters: [{ name: '@domain', value: domain }] + }).fetchAll()).resources; +} + +export async function createComment({ domain, path, position, thread, device }: { domain: string, path: string, position: object, thread: object, device?: object }) { + const container = cosmosDataContainerComments(); + const commentId = `comment_${nanoid()}`; + await container.items.create({ + id: commentId, + domain, + path, + position, // TODO: Sanitize + thread, // TODO: Sanitize + device, // TODO: Sanitize + }); + return commentId; +} + +export async function updateComment(domain: string, id: string, comment: { path: string, position: object, thread: object, device?: object, resolved?: boolean }) { + const container = cosmosDataContainerComments(); + await container.item(id, domain).replace({ + id, + domain, + ...comment + }); +} + +export async function deleteComment(domain: string, id: string) { + await cosmosDataContainerComments().item(id, domain).delete(); +} \ No newline at end of file diff --git a/web/apps/web/app/(content)/channels/[channelName]/page.tsx b/web/apps/web/app/(content)/channels/[channelName]/page.tsx index c11d416cb9..012f9d5a05 100644 --- a/web/apps/web/app/(content)/channels/[channelName]/page.tsx +++ b/web/apps/web/app/(content)/channels/[channelName]/page.tsx @@ -10,7 +10,7 @@ import { Breadcrumbs } from '@signalco/ui/Breadcrumbs'; import { channelCategories as channelCategoriesData, channelsData } from '@signalco/data/data'; import { SectionsView } from '@signalco/cms-core/SectionsView'; import { channelsFaq } from '../data'; -import { sectionsComponentRegistry } from '../../../page'; +import { sectionsComponentRegistry } from '../../page'; import { KnownPages } from '../../../../src/knownPages'; import ShareSocial from '../../../../components/pages/ShareSocial'; import CtaSection from '../../../../components/pages/CtaSection'; diff --git a/web/apps/web/app/(content)/channels/page.tsx b/web/apps/web/app/(content)/channels/page.tsx index ddcdd72b47..58526dbc99 100644 --- a/web/apps/web/app/(content)/channels/page.tsx +++ b/web/apps/web/app/(content)/channels/page.tsx @@ -1,7 +1,7 @@ import { Suspense } from 'react'; import { Stack } from '@signalco/ui-primitives/Stack'; import { SectionsView } from '@signalco/cms-core/SectionsView'; -import { sectionsComponentRegistry } from '../../page'; +import { sectionsComponentRegistry } from '../page'; import PageCenterHeader from '../../../components/pages/PageCenterHeader'; import CtaSection from '../../../components/pages/CtaSection'; import ChannelsGallery from '../../../components/channels/ChannelsGallery'; diff --git a/web/apps/web/app/page.tsx b/web/apps/web/app/(content)/page.tsx similarity index 94% rename from web/apps/web/app/page.tsx rename to web/apps/web/app/(content)/page.tsx index d014348851..b1b588fb4e 100644 --- a/web/apps/web/app/page.tsx +++ b/web/apps/web/app/(content)/page.tsx @@ -9,12 +9,12 @@ import { Pricing1 } from '@signalco/cms-components-marketing/Pricing'; import { Heading1 } from '@signalco/cms-components-marketing/Heading'; import { Feature2, Feature3, Feature4 } from '@signalco/cms-components-marketing/Feature'; import { Faq1 } from '@signalco/cms-components-marketing/Faq'; -import { isDeveloper } from '../src/services/EnvProvider'; -import { KnownPages } from '../src/knownPages'; -import { NewsletterSection } from '../components/views/NewsletterSection'; -import DiscoverVisual from '../components/pages/landing/visuals/DiscoverVisual'; -import CtaSection from '../components/pages/CtaSection'; -import SignalcoLogotype from '../components/icons/SignalcoLogotype'; +import { isDeveloper } from '../../src/services/EnvProvider'; +import { KnownPages } from '../../src/knownPages'; +import { NewsletterSection } from '../../components/views/NewsletterSection'; +import DiscoverVisual from '../../components/pages/landing/visuals/DiscoverVisual'; +import CtaSection from '../../components/pages/CtaSection'; +import SignalcoLogotype from '../../components/icons/SignalcoLogotype'; export const sectionsComponentRegistry = { 'Heading1': memo(Heading1), diff --git a/web/apps/web/app/(content)/pricing/page.tsx b/web/apps/web/app/(content)/pricing/page.tsx index 47d9461fae..77fe88b8a0 100644 --- a/web/apps/web/app/(content)/pricing/page.tsx +++ b/web/apps/web/app/(content)/pricing/page.tsx @@ -1,6 +1,6 @@ import { Stack } from '@signalco/ui-primitives/Stack'; import { SectionsView } from '@signalco/cms-core/SectionsView'; -import { sectionPricing, sectionsComponentRegistry } from '../../page'; +import { sectionPricing, sectionsComponentRegistry } from '../page'; import { KnownPages } from '../../../src/knownPages'; import PageCenterHeader from '../../../components/pages/PageCenterHeader'; import CtaSection from '../../../components/pages/CtaSection'; diff --git a/web/apps/web/app/(content)/security/page.tsx b/web/apps/web/app/(content)/security/page.tsx index d3bb6e037f..ee5a45927f 100644 --- a/web/apps/web/app/(content)/security/page.tsx +++ b/web/apps/web/app/(content)/security/page.tsx @@ -4,7 +4,7 @@ import { Card, CardContent } from '@signalco/ui-primitives/Card'; import { Avatar } from '@signalco/ui-primitives/Avatar'; import { Shield, Lock, Euro } from '@signalco/ui-icons'; import { SectionsView } from '@signalco/cms-core/SectionsView'; -import { sectionsComponentRegistry } from '../../page'; +import { sectionsComponentRegistry } from '../page'; import { KnownPages } from '../../../src/knownPages'; import PageCenterHeader from '../../../components/pages/PageCenterHeader'; import CtaSection from '../../../components/pages/CtaSection'; diff --git a/web/packages/cms-components-marketing/src/Feature/Feature4.tsx b/web/packages/cms-components-marketing/src/Feature/Feature4.tsx index b5b8c6efae..003b25992d 100644 --- a/web/packages/cms-components-marketing/src/Feature/Feature4.tsx +++ b/web/packages/cms-components-marketing/src/Feature/Feature4.tsx @@ -1,4 +1,5 @@ import { Stack } from '@signalco/ui-primitives/Stack'; +import { Card } from '@signalco/ui-primitives/Card'; import { type SectionData } from '@signalco/cms-core/SectionData'; import { KeyFeature2 } from '../subcomponents/KeyFeature2'; import { Ctas1 } from '../subcomponents/Ctas1'; @@ -8,7 +9,7 @@ import { Section3 } from '../containers/Section3'; export function Feature4({ tagline, header, description, ctas, asset, features }: SectionData) { return ( -
+ @@ -19,13 +20,13 @@ export function Feature4({ tagline, header, description, ctas, asset, features } )} {features && ( -
+
{features.map((feature, i) => ( ))}
)} -
+
); } diff --git a/web/packages/uier-toolbar/package.json b/web/packages/uier-toolbar/package.json index 084815666d..b198b6a13c 100644 --- a/web/packages/uier-toolbar/package.json +++ b/web/packages/uier-toolbar/package.json @@ -9,7 +9,7 @@ "./index.css": "./dist/index.css" }, "scripts": { - "dev:build": "tsup --watch --env.NODE_ENV development", + "dev:build": "tsup --watch ./src --env.NODE_ENV development", "dev:serve": "pnpm dlx serve dist -l 4005", "dev": "pnpm run /^dev:.*/", "build": "tsup --minify --env.NODE_ENV production", diff --git a/web/packages/uier-toolbar/src/components/Comments.tsx b/web/packages/uier-toolbar/src/components/@types/Comments.tsx similarity index 100% rename from web/packages/uier-toolbar/src/components/Comments.tsx rename to web/packages/uier-toolbar/src/components/@types/Comments.tsx diff --git a/web/packages/uier-toolbar/src/components/CommentBubble.tsx b/web/packages/uier-toolbar/src/components/CommentBubble.tsx index 14df876772..3f24ffbf79 100644 --- a/web/packages/uier-toolbar/src/components/CommentBubble.tsx +++ b/web/packages/uier-toolbar/src/components/CommentBubble.tsx @@ -15,8 +15,8 @@ import { useCommentItemRects } from '../hooks/useCommentItemRects'; import { CommentThread } from './CommentThread'; import { CommentSelectionHighlight } from './CommentSelectionHighlight'; import { CommentsBootstrapperContext } from './CommentsBootstrapperContext'; -import { CommentItem } from './Comments'; import { CommentIcon } from './CommentIcon'; +import { CommentItem } from './@types/Comments'; type CommentBubbleProps = HTMLAttributes & { defaultOpen?: boolean; @@ -45,7 +45,7 @@ export function CommentBubble({ e.preventDefault(); const formData = new FormData(e.currentTarget); e.currentTarget.reset(); - const commentId = await upsert.mutateAsync({ + const { id } = await upsert.mutateAsync({ ...commentItem, thread: { ...commentItem.thread, @@ -60,7 +60,7 @@ export function CommentBubble({ }); if (creating) { - onCreated?.(commentId); + onCreated?.(id); } }; diff --git a/web/packages/uier-toolbar/src/components/CommentPointOverlay.tsx b/web/packages/uier-toolbar/src/components/CommentPointOverlay.tsx index 067fb54088..5e14df35ad 100644 --- a/web/packages/uier-toolbar/src/components/CommentPointOverlay.tsx +++ b/web/packages/uier-toolbar/src/components/CommentPointOverlay.tsx @@ -2,7 +2,7 @@ import { useRef } from 'react'; import { cx } from '@signalco/ui-primitives/cx'; import { getElementSelector } from '@signalco/js'; import { useWindowEvent } from '@signalco/hooks/useWindowEvent'; -import { CommentPoint } from './Comments'; +import { CommentPoint } from './@types/Comments'; type CommentPointOverlayProps = { onPoint: (commentPoint: CommentPoint) => void; diff --git a/web/packages/uier-toolbar/src/components/CommentSelectionHighlight.tsx b/web/packages/uier-toolbar/src/components/CommentSelectionHighlight.tsx index e2396a8b8f..91a87f2bfe 100644 --- a/web/packages/uier-toolbar/src/components/CommentSelectionHighlight.tsx +++ b/web/packages/uier-toolbar/src/components/CommentSelectionHighlight.tsx @@ -3,7 +3,7 @@ import { HTMLAttributes } from 'react'; import { cx } from '@signalco/ui-primitives/cx'; import { useCommentItemRects } from '../hooks/useCommentItemRects'; -import { CommentSelection } from './Comments'; +import { CommentSelection } from './@types/Comments'; type CommentSelectionHighlightProps = HTMLAttributes & { commentSelection: CommentSelection; diff --git a/web/packages/uier-toolbar/src/components/CommentSelectionPopover.tsx b/web/packages/uier-toolbar/src/components/CommentSelectionPopover.tsx index fbf1998adb..da52795fae 100644 --- a/web/packages/uier-toolbar/src/components/CommentSelectionPopover.tsx +++ b/web/packages/uier-toolbar/src/components/CommentSelectionPopover.tsx @@ -6,7 +6,7 @@ import { Button } from '@signalco/ui-primitives/Button'; import { Comment } from '@signalco/ui-icons'; import { orderBy } from '@signalco/js'; import { useResizeObserver } from '@enterwell/react-hooks'; -import { popoverWidth, popoverWindowMargin } from './Comments'; +import { popoverWidth, popoverWindowMargin } from './@types/Comments'; export function CommentSelectionPopover({ rects, onCreate }: { rects: DOMRect[]; onCreate: () => void; }) { const [popoverHeight, setPopoverHeight] = useState(0); diff --git a/web/packages/uier-toolbar/src/components/CommentThread.tsx b/web/packages/uier-toolbar/src/components/CommentThread.tsx index ff6c8400f8..ae275e5b56 100644 --- a/web/packages/uier-toolbar/src/components/CommentThread.tsx +++ b/web/packages/uier-toolbar/src/components/CommentThread.tsx @@ -3,9 +3,9 @@ import { Stack } from '@signalco/ui-primitives/Stack'; import { Divider } from '@signalco/ui-primitives/Divider'; import { Button } from '@signalco/ui-primitives/Button'; import { CommentThreadItem } from './CommentThreadItem'; -import { CommentItem } from './Comments'; +import { CommentItem } from './@types/Comments'; -export function CommentThread({ commentItem, onResolve }: { commentItem: CommentItem; onResolve?: () => void; }) { +export function CommentThread({ commentItem, onResolve }: { commentItem: CommentItem; onResolve: () => void; }) { const [expanded, setExpanded] = useState(false); if (!commentItem.thread.items.length) return null; diff --git a/web/packages/uier-toolbar/src/components/CommentThreadItem.tsx b/web/packages/uier-toolbar/src/components/CommentThreadItem.tsx index 9d7ee9fcb6..8b9e9b464d 100644 --- a/web/packages/uier-toolbar/src/components/CommentThreadItem.tsx +++ b/web/packages/uier-toolbar/src/components/CommentThreadItem.tsx @@ -1,51 +1,15 @@ 'use client'; -import { useContext, useState } from 'react'; import { Typography } from '@signalco/ui-primitives/Typography'; import { Stack } from '@signalco/ui-primitives/Stack'; import { Row } from '@signalco/ui-primitives/Row'; -import { Popper } from '@signalco/ui-primitives/Popper'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@signalco/ui-primitives/Menu'; import { IconButton } from '@signalco/ui-primitives/IconButton'; import { Avatar } from '@signalco/ui-primitives/Avatar'; -import { Check, Desktop, Mobile, MoreHorizontal, Tablet } from '@signalco/ui-icons'; +import { Check, MoreHorizontal } from '@signalco/ui-icons'; import { Timeago } from '@signalco/ui/Timeago'; -import { camelToSentenceCase } from '@signalco/js'; -import { CommentsBootstrapperContext } from './CommentsBootstrapperContext'; -import { CommentItem, CommentItemThreadItem } from './Comments'; - -function DeviceInfo({ device }: { device?: CommentItem['device'] }) { - const { rootElement } = useContext(CommentsBootstrapperContext); - const [open, setOpen] = useState(false); - const deviceSize = device?.size; - const DeviceSizeIcon = deviceSize === 'desktop' - ? Desktop - : (deviceSize === 'tablet' - ? Tablet - : (deviceSize === 'mobile' - ? Mobile - : null)); - - if (!DeviceSizeIcon) return null; - - return ( - setOpen(false)} - container={rootElement} - anchor={( - setOpen(true)} /> - )}> - - {device?.browser && {device.browser}} - {device?.os && {device.os}} - {deviceSize && {camelToSentenceCase(deviceSize)}} - {device?.windowSize && {device.windowSize[0]}x{device.windowSize[1]}} - {device?.pixelRatio && (x{device.pixelRatio})} - - - ); -} +import { DeviceInfo } from './info/DeviceInfo'; +import { CommentItem, CommentItemThreadItem } from './@types/Comments'; export function CommentThreadItem({ comment, threadItem, first, onDone }: { comment: CommentItem, threadItem: CommentItemThreadItem; first?: boolean; onDone?: () => void; }) { const { text } = threadItem; @@ -59,12 +23,14 @@ export function CommentThreadItem({ comment, threadItem, first, onDone }: { comm {avatarFallback} {author} - {first && ( + {/* TODO Enable when we fix device info UI */} + {/* {first && ( - )} - + )} */} + {/* TODO Enable when we have this info */} + {/* - + */} {first && ( diff --git a/web/packages/uier-toolbar/src/components/CommentToolbar.tsx b/web/packages/uier-toolbar/src/components/CommentToolbar.tsx index c89807490a..0aa1e5f851 100644 --- a/web/packages/uier-toolbar/src/components/CommentToolbar.tsx +++ b/web/packages/uier-toolbar/src/components/CommentToolbar.tsx @@ -31,14 +31,15 @@ export function CommentToolbar({ creatingPointComment, onAddPointComment, onShow title="Inbox"> - + {/* TODO Enable when implemented */} + {/* - + */}
); diff --git a/web/packages/uier-toolbar/src/components/CommentsBootstrapper.tsx b/web/packages/uier-toolbar/src/components/CommentsBootstrapper.tsx index 896ad509e1..cf21e8cd1a 100644 --- a/web/packages/uier-toolbar/src/components/CommentsBootstrapper.tsx +++ b/web/packages/uier-toolbar/src/components/CommentsBootstrapper.tsx @@ -1,7 +1,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { CommentsGlobal } from './CommentsGlobal'; import { CommentsBootstrapperContext } from './CommentsBootstrapperContext'; -import { CommentsGlobalProps } from './Comments'; +import { CommentsGlobalProps } from './@types/Comments'; const queryClient = new QueryClient(); diff --git a/web/packages/uier-toolbar/src/components/CommentsGlobal.tsx b/web/packages/uier-toolbar/src/components/CommentsGlobal.tsx index f09eef2cb6..5fc8d8696f 100644 --- a/web/packages/uier-toolbar/src/components/CommentsGlobal.tsx +++ b/web/packages/uier-toolbar/src/components/CommentsGlobal.tsx @@ -8,9 +8,9 @@ import { CommentsSidebar } from './sidebar/CommentsSidebar'; import { CommentToolbar } from './CommentToolbar'; import { CommentSelectionPopover } from './CommentSelectionPopover'; import { CommentSelectionHighlight } from './CommentSelectionHighlight'; -import { CommentsGlobalProps, CommentSelection, CommentPoint, CommentItem } from './Comments'; import { CommentPointOverlay } from './CommentPointOverlay'; import { CommentBubble } from './CommentBubble'; +import { CommentsGlobalProps, CommentSelection, CommentPoint, CommentItem } from './@types/Comments'; export function CommentsGlobal({ reviewParamKey = 'review', @@ -96,15 +96,17 @@ export function CommentsGlobal({ return ( <> - {(creatingComment ? [...(commentItems.data ?? []), creatingComment] : (commentItems.data ?? [])).map((commentItem) => ( - setCreatingComment(undefined)} - onCanceled={() => setCreatingComment(undefined)} - /> - ))} + {(creatingComment ? [...(commentItems.data ?? []), creatingComment] : (commentItems.data ?? [])).map((commentItem) => { + console.log('creating comment id', commentItem.id) + return ( + setCreatingComment(undefined)} + onCanceled={() => setCreatingComment(undefined)} /> + ); + })} {creatingCommentSelection && ( )} diff --git a/web/packages/uier-toolbar/src/components/info/DeviceInfo.tsx b/web/packages/uier-toolbar/src/components/info/DeviceInfo.tsx new file mode 100644 index 0000000000..0ad3f1edae --- /dev/null +++ b/web/packages/uier-toolbar/src/components/info/DeviceInfo.tsx @@ -0,0 +1,43 @@ +'use client'; + +import { useContext, useState } from 'react'; +import { Typography } from '@signalco/ui-primitives/Typography'; +import { Stack } from '@signalco/ui-primitives/Stack'; +import { Popper } from '@signalco/ui-primitives/Popper'; +import { Desktop, Mobile, Tablet } from '@signalco/ui-icons'; +import { camelToSentenceCase } from '@signalco/js'; +import { CommentsBootstrapperContext } from '../CommentsBootstrapperContext'; +import { type CommentItem } from '../@types/Comments'; + +export function DeviceInfo({ device }: { device?: CommentItem['device'] }) { + const { rootElement } = useContext(CommentsBootstrapperContext); + const [open, setOpen] = useState(false); + const deviceSize = device?.size; + const DeviceSizeIcon = deviceSize === 'desktop' + ? Desktop + : (deviceSize === 'tablet' + ? Tablet + : (deviceSize === 'mobile' + ? Mobile + : null)); + + if (!DeviceSizeIcon) return null; + + return ( + setOpen(false)} + container={rootElement} + anchor={( + setOpen(true)} /> + )}> + + {device?.browser && {device.browser}} + {device?.os && {device.os}} + {deviceSize && {camelToSentenceCase(deviceSize)}} + {device?.windowSize && {device.windowSize[0]}x{device.windowSize[1]}} + {device?.pixelRatio && (x{device.pixelRatio})} + + + ); +} \ No newline at end of file diff --git a/web/packages/uier-toolbar/src/components/sidebar/CommentsSidebar.tsx b/web/packages/uier-toolbar/src/components/sidebar/CommentsSidebar.tsx index f3b9002281..46a7345241 100644 --- a/web/packages/uier-toolbar/src/components/sidebar/CommentsSidebar.tsx +++ b/web/packages/uier-toolbar/src/components/sidebar/CommentsSidebar.tsx @@ -3,10 +3,9 @@ import { Stack } from '@signalco/ui-primitives/Stack'; import { Divider } from '@signalco/ui-primitives/Divider'; import { cx } from '@signalco/ui-primitives/cx'; import { Collapse } from '@signalco/ui-primitives/Collapse'; -import { CommentThread } from '../CommentThread'; -import { useComments } from '../../hooks/useComments'; import { CommentsSidebarHeader } from './CommentsSidebarHeader'; import { CommentsSidebarFilter } from './CommentsSidebarFilter'; +import { CommentsSidebarCommentsList } from './CommentsSidebarCommentsList'; import { CommentsFilter } from './CommentsFilter'; export type CommentsSidebarProps = { @@ -14,21 +13,6 @@ export type CommentsSidebarProps = { rootElement?: HTMLElement; }; -function SidebarCommentsList() { - const { query } = useComments(); - const { data: comments } = query; - - return ( - - {comments?.map((comment) => ( -
- -
- ))} -
- ); -} - export function CommentsSidebar({ onClose, rootElement }: CommentsSidebarProps) { const [filterOpen, setFilterOpen] = useState(false); const [filter, setFilter] = useState(); @@ -43,7 +27,7 @@ export function CommentsSidebar({ onClose, rootElement }: CommentsSidebarProps) return (
@@ -55,17 +39,17 @@ export function CommentsSidebar({ onClose, rootElement }: CommentsSidebarProps) onClose={handleClose} />
- -
- -
+ +
+ +
- +
diff --git a/web/packages/uier-toolbar/src/components/sidebar/CommentsSidebarCommentsList.tsx b/web/packages/uier-toolbar/src/components/sidebar/CommentsSidebarCommentsList.tsx new file mode 100644 index 0000000000..604ee0a91c --- /dev/null +++ b/web/packages/uier-toolbar/src/components/sidebar/CommentsSidebarCommentsList.tsx @@ -0,0 +1,29 @@ +import { Stack } from '@signalco/ui-primitives/Stack'; +import { CommentThread } from '../CommentThread'; +import { CommentItem } from '../@types/Comments'; +import { useComments } from '../../hooks/useComments'; + +export function CommentsSidebarCommentsList() { + const { query, upsert } = useComments(); + const { data: comments } = query; + + const handleResolveComment = async (commentItem: CommentItem) => { + await upsert.mutateAsync({ + ...commentItem, + resolved: true + }); + }; + + return ( + + {!comments?.length && ( +
No comments
+ )} + {comments?.map((commentItem) => ( +
+ handleResolveComment(commentItem)} /> +
+ ))} +
+ ); +} \ No newline at end of file diff --git a/web/packages/uier-toolbar/src/components/sidebar/CommentsSidebarHeader.tsx b/web/packages/uier-toolbar/src/components/sidebar/CommentsSidebarHeader.tsx index c6c105cb07..deec6fa40c 100644 --- a/web/packages/uier-toolbar/src/components/sidebar/CommentsSidebarHeader.tsx +++ b/web/packages/uier-toolbar/src/components/sidebar/CommentsSidebarHeader.tsx @@ -8,9 +8,10 @@ export function CommentsSidebarHeader({ filterOpen, onClose, onToggleFilter }: { - + {/* TODO Enable when implemented */} + {/* - + */} diff --git a/web/packages/uier-toolbar/src/hooks/useCommentItemRects.tsx b/web/packages/uier-toolbar/src/hooks/useCommentItemRects.tsx index 3c3cc5d724..198965a6af 100644 --- a/web/packages/uier-toolbar/src/hooks/useCommentItemRects.tsx +++ b/web/packages/uier-toolbar/src/hooks/useCommentItemRects.tsx @@ -3,7 +3,7 @@ import { useEffect, useState } from 'react'; import { arrayMax, orderBy } from '@signalco/js'; import { useWindowRect } from '@signalco/hooks/useWindowRect'; -import { CommentItemPosition } from '../components/Comments'; +import { CommentItemPosition } from '../components/@types/Comments'; export function useCommentItemRects(commentSelection: CommentItemPosition | null | undefined) { diff --git a/web/packages/uier-toolbar/src/hooks/useComments.tsx b/web/packages/uier-toolbar/src/hooks/useComments.tsx index 56867d6116..4d2010f6a5 100644 --- a/web/packages/uier-toolbar/src/hooks/useComments.tsx +++ b/web/packages/uier-toolbar/src/hooks/useComments.tsx @@ -1,51 +1,81 @@ -'use client'; - import { UseMutationResult, UseQueryResult, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { CommentItem } from '../components/Comments'; +import { CommentItem } from '../components/@types/Comments'; + +// TODO: Call API +async function getComments() { + const response = await fetch('https://uier.io/api/comments'); + const comments = await response.json() as CommentItem[]; + return comments.filter(c => !c.resolved); +} + +// TODO: Call API +async function upsertComment(comment: CommentItem) { + const response = await fetch('http://uier.io/api/comments', { + method: 'POST', + body: JSON.stringify({ + id: comment.id, + domain: window.location.host, + path: window.location.pathname + window.location.search, // TODO: Exclude review param from search + position: comment.position, + thread: comment.thread, + device: comment.device, + resolved: comment.resolved + }) + }); + const { id } = await response.json(); + if (!id) { + throw new Error('Failed to create comment'); + } + + return id; +} + +// TODO: Call API +async function deleteComment(id: string) { + // const comments = JSON.parse(localStorage.getItem('comments') ?? '[]') as CommentItem[]; + // const currentComment = comments.find((c: CommentItem) => c.id === id); + // if (currentComment) { + // comments.splice(comments.indexOf(currentComment), 1); + // } + // localStorage.setItem('comments', JSON.stringify(comments)); + throw new Error('Not implemented'); +} export function useComments(): { query: UseQueryResult; - upsert: UseMutationResult; + upsert: UseMutationResult<{ id: string, isNew: boolean }, Error, CommentItem, unknown>; delete: UseMutationResult; } { const client = useQueryClient(); const query = useQuery({ queryKey: ['comments'], - queryFn: () => { - const comments = JSON.parse(localStorage.getItem('comments') ?? '[]') as CommentItem[]; - return comments.filter((c: CommentItem) => !c.resolved); - } + queryFn: getComments }); const mutateUpsert = useMutation({ mutationFn: async (comment: CommentItem) => { - const comments = JSON.parse(localStorage.getItem('comments') ?? '[]') as CommentItem[]; - if (!comment.id) { - comment.id = 'mock' + Math.random() + Date.now(); - } - const currentComment = comments.find((c: CommentItem) => Boolean(comment.id) && c.id === comment.id); - if (currentComment) { - Object.assign(currentComment, comment); + const id = await upsertComment(comment); + console.log('mutated', id, comment.id, comment.id !== id) + return ({ + id, + isNew: comment.id !== id + }); + }, + onSuccess: (id) => { + if (id) { + client.invalidateQueries({ + queryKey: ['comments'] + }); } else { - comments.push(comment); + client.invalidateQueries({ + queryKey: ['comments', id] + }); } - localStorage.setItem('comments', JSON.stringify(comments)); - client.invalidateQueries({ - queryKey: ['comments'] - }); - return comment.id; } }); const mutateDelete = useMutation({ - mutationFn: async (id: string) => { - const comments = JSON.parse(localStorage.getItem('comments') ?? '[]') as CommentItem[]; - const currentComment = comments.find((c: CommentItem) => c.id === id); - if (currentComment) { - comments.splice(comments.indexOf(currentComment), 1); - } - localStorage.setItem('comments', JSON.stringify(comments)); - }, + mutationFn: deleteComment, onSuccess: () => { client.invalidateQueries({ queryKey: ['comments'] diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 0a284e63c4..9aebb9a7be 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -840,6 +840,9 @@ importers: apps/uier: dependencies: + '@azure/cosmos': + specifier: 4.1.1 + version: 4.1.1 '@enterwell/react-hooks': specifier: 0.5.0 version: 0.5.0(react-dom@19.0.0-rc-65a56d0e-20241020(react@19.0.0-rc-65a56d0e-20241020))(react@19.0.0-rc-65a56d0e-20241020) @@ -888,6 +891,9 @@ importers: classix: specifier: 2.2.0 version: 2.2.0 + nanoid: + specifier: 5.0.8 + version: 5.0.8 next: specifier: 15.0.3 version: 15.0.3(@playwright/test@1.48.2)(babel-plugin-react-compiler@19.0.0-beta-8a03594-20241020)(react-dom@19.0.0-rc-65a56d0e-20241020(react@19.0.0-rc-65a56d0e-20241020))(react@19.0.0-rc-65a56d0e-20241020)(sass@1.81.0)