Skip to content

Commit 33a302b

Browse files
feat: Cloud-version support with multi-tenant provisioning, agent connectivity, and workspace management (#30)
* feat: zero-connection startup with PostgreSQL SSL support Enable graceful application startup without pre-configured database connections and add SSL support for cloud PostgreSQL deployments. ## Features - **Zero-Connection Startup**: App starts without DB_HOST set, users add connections via UI - **PostgreSQL SSL**: New STORAGE_SSL_CA env var supports file paths or URLs (AWS RDS, GCP, Azure) - **Server Startup Guard**: Frontend waits for backend initialization to prevent race conditions - **SPA Routing**: Fixed client-side routing on page refresh with proper NestJS architecture - **Enhanced UX**: Loading states, error boundaries, and helpful guidance for first-time setup ## Technical Changes - New `waiting` status in health endpoints when no connections configured - SpaFallbackModule with type-safe Fastify handlers and static file whitelist - ServerStartupGuard component with progress indicators and timeout handling - NoConnectionsGuard shows arrow pointing to sidebar connection selector - Proper timeout cleanup to prevent memory leaks - SSL certificate fetching with domain whitelist and 10s timeout ## Migration - Fully backwards compatible - existing DB_HOST configurations work unchanged - Optional: Set STORAGE_SSL_CA for cloud PostgreSQL requiring SSL - API docs now at /docs instead of /api/docs ## Fixes - SPA routing on page refresh in production - Race condition on server startup - Memory leak in polling component - Inconsistent error messaging * feat: zero-connection startup with PostgreSQL SSL support Enable graceful application startup without pre-configured database connections and add SSL support for cloud PostgreSQL deployments. ## Features - **Zero-Connection Startup**: App starts without DB_HOST set, users add connections via UI - **PostgreSQL SSL**: New STORAGE_SSL_CA env var supports file paths or URLs (AWS RDS, GCP, Azure) - **Server Startup Guard**: Frontend waits for backend initialization to prevent race conditions - **SPA Routing**: Fixed client-side routing on page refresh with proper NestJS architecture - **Enhanced UX**: Loading states, error boundaries, and helpful guidance for first-time setup ## Technical Changes - New `waiting` status in health endpoints when no connections configured - SpaFallbackModule with type-safe Fastify handlers and static file whitelist - ServerStartupGuard component with progress indicators and timeout handling - NoConnectionsGuard shows arrow pointing to sidebar connection selector - Proper timeout cleanup to prevent memory leaks - SSL certificate fetching with domain whitelist and 10s timeout ## Migration - Fully backwards compatible - existing DB_HOST configurations work unchanged - Optional: Set STORAGE_SSL_CA for cloud PostgreSQL requiring SSL - API docs now at /docs instead of /api/docs ## Fixes - SPA routing on page refresh in production - Race condition on server startup - Memory leak in polling component - Inconsistent error messaging * fix: address security issues in SPA routing and SSL certificate handling Fix SPA fallback broken by global API prefix, prevent subdomain spoofing in SSL CA domain whitelist, and block insecure HTTP URLs for certificate fetching. * fix: address security issues in SPA routing and SSL certificate handling Fix SPA fallback broken by global API prefix, prevent subdomain spoofing in SSL CA domain whitelist, and block insecure HTTP URLs for certificate fetching. * fix: address security issues in SPA routing and SSL certificate handling Move SPA fallback to Fastify setNotFoundHandler to avoid global prefix conflict, ensure API 404s return JSON not HTML, prevent subdomain spoofing in SSL CA domain validation, and block insecure HTTP URLs for certificate fetching. * fix: address security issues in SPA routing and SSL certificate handling Move SPA fallback to Fastify setNotFoundHandler to avoid global prefix conflict, ensure API 404s return JSON not HTML, prevent subdomain spoofing in SSL CA domain validation, and block insecure HTTP URLs for certificate fetching. * fixed deployed conflict between fastify and nest * fixed deployed conflict between fastify and nest * fix: address security issues in SPA routing and SSL certificate handling Register SPA fallback at Fastify level before NestJS to avoid global prefix conflicts, ensure API 404s return JSON, block insecure HTTP for SSL certificates, prevent subdomain spoofing with proper domain validation, restrict SPA fallback to GET requests only, and update DTOs with 'waiting' status. * fix: address security issues in SPA routing and SSL certificate handling Register SPA fallback at Fastify level before NestJS to avoid global prefix conflicts, ensure API 404s return JSON, block insecure HTTP for SSL certificates, prevent subdomain spoofing with proper domain validation, restrict SPA fallback to GET requests only, and update DTOs with 'waiting' status. * fix: address security issues in SPA routing and SSL certificate handling Register SPA fallback at Fastify level before NestJS to handle client-side routes correctly, ensure missing API routes return JSON 404, block HTTP for SSL certificates and validate only official CA distribution endpoints (AWS RDS PKI, GCP Cloud SQL, DigiCert), prevent subdomain spoofing with proper domain validation, restrict SPA fallback to GET requests only, exclude HEAD from catch-all to preserve Docker healthchecks, and update DTOs with 'waiting' status. * fix: address security issues in SPA routing and SSL certificate handling Register SPA fallback at Fastify level before NestJS to handle client-side routes correctly, ensure missing API routes return JSON 404, block HTTP for SSL certificates and validate only official CA distribution endpoints (AWS RDS PKI, GCP Cloud SQL, DigiCert), prevent subdomain spoofing with proper domain validation, restrict SPA fallback to GET requests only, exclude HEAD from catch-all to preserve Docker healthchecks, and update DTOs with 'waiting' status. * fix: address security issues in SPA routing and SSL certificate handling Register SPA fallback at Fastify level before NestJS to handle client-side routes, ensure missing API routes return JSON 404, block HTTP for SSL certificates and validate only official CA endpoints with proper path boundaries (AWS RDS PKI, GCP Cloud SQL with trailing slash, DigiCert), prevent subdomain spoofing, exclude HEAD from catch-all for Docker healthchecks, consolidate publicPath computation to prevent divergence, and update DTOs with 'waiting' status. * fix: address security issues in SPA routing and SSL certificate handling Register SPA fallback at Fastify level before NestJS to handle client-side routes, ensure missing API routes return JSON 404, block HTTP for SSL certificates and validate only official CA endpoints with proper path boundaries (AWS RDS PKI, GCP Cloud SQL with trailing slash, DigiCert), prevent subdomain spoofing, exclude HEAD from catch-all for Docker healthchecks, consolidate publicPath computation to prevent divergence, and update DTOs with 'waiting' status. * fix: redirect follow behavior * fix: redirect follow behavior * fix: default config conflicting with new checks for the docker version * fix: default config conflicting with new checks for the docker version * version bump * version bump * Initial cloud-version setup and provisioning * cloud-version phase 5A * Phase 5c * Phase 5 - testing and polishing * Workspace management and basic handling * basic agent for VPC connection * graceful degradation and network improvements * added docs about how to connect via agent * build and deploy for the agent * refreshing bug for agent conenctions * terraform added to gitignore * fix: add RuntimeCapabilityTracker to connection registry test providers * fix: add RuntimeCapabilityTracker to remaining test provider * tracking improvements * fix: address bugbot review issues for cloud-version PR - Remove debug || true from isCloudMode prop - Separate try/catch for cluster slot stats to avoid disabling canClusterInfo - Block commands with subcommand restrictions when args are empty - Add .catch() to pool connect handler to prevent unhandled promise rejection - Fix Docker env var names (BETTERDB_TOKEN, BETTERDB_CLOUD_URL) - Fix npx package name to betterdb-agent * fix: move agent token to Authorization header, fix slowlog overlay text - Send agent WebSocket token via Bearer header instead of URL query param - Server falls back to query param for backwards compat with older agents - Show "SLOWLOG/COMMANDLOG" in unavailable overlay instead of always SLOWLOG * fix: prevent shell injection in agent release CI script Use process.env.VERSION instead of shell-interpolated $VERSION in node -e * fix: hide update banner in cloud mode * fix: generate Prisma client before API tests in CI * feat: add deploymentMode field to telemetry payload * version bump1 * build(deps): bump axios from 1.13.2 to 1.13.5 (#21) Bumps [axios](https://github.com/axios/axios) from 1.13.2 to 1.13.5. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](axios/axios@v1.13.2...v1.13.5) --- updated-dependencies: - dependency-name: axios dependency-version: 1.13.5 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * version bumps * fix: include deploymentMode in license_check payload --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1 parent 5ae8ca2 commit 33a302b

File tree

161 files changed

+8271
-260
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

161 files changed

+8271
-260
lines changed
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# Publish BetterDB Agent — Docker image + npm package
2+
#
3+
# Required secrets:
4+
# DOCKERHUB_USERNAME — Docker Hub username
5+
# DOCKERHUB_TOKEN — Docker Hub access token
6+
# NPM_TOKEN — npmjs.com automation token
7+
8+
name: Publish Agent
9+
10+
on:
11+
push:
12+
tags:
13+
- 'agent-v*'
14+
15+
jobs:
16+
publish-npm:
17+
runs-on: ubuntu-latest
18+
permissions:
19+
contents: read
20+
id-token: write
21+
22+
steps:
23+
- name: Checkout
24+
uses: actions/checkout@v4
25+
26+
- name: Setup Node.js
27+
uses: actions/setup-node@v4
28+
with:
29+
node-version: '20'
30+
registry-url: 'https://registry.npmjs.org'
31+
32+
- name: Setup pnpm
33+
uses: pnpm/action-setup@v4
34+
35+
- name: Get pnpm store directory
36+
shell: bash
37+
run: |
38+
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
39+
40+
- name: Setup pnpm cache
41+
uses: actions/cache@v4
42+
with:
43+
path: ${{ env.STORE_PATH }}
44+
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
45+
restore-keys: |
46+
${{ runner.os }}-pnpm-store-
47+
48+
- name: Install dependencies
49+
run: pnpm install --frozen-lockfile
50+
51+
- name: Extract version from tag
52+
id: version
53+
run: |
54+
# agent-v0.1.0 → 0.1.0
55+
echo "version=${GITHUB_REF_NAME#agent-v}" >> $GITHUB_OUTPUT
56+
57+
- name: Update package.json version
58+
env:
59+
VERSION: ${{ steps.version.outputs.version }}
60+
run: |
61+
cd packages/agent
62+
node -e "
63+
const pkg = require('./package.json');
64+
pkg.version = process.env.VERSION;
65+
require('fs').writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\n');
66+
"
67+
68+
- name: Build agent
69+
run: cd packages/agent && pnpm build
70+
71+
- name: Verify build
72+
run: node packages/agent/dist/index.js --help 2>&1 || true
73+
74+
- name: Prepare for publishing
75+
run: |
76+
cd packages/agent
77+
# Remove workspace dependencies before npm publish
78+
node -e "
79+
const pkg = require('./package.json');
80+
if (pkg.dependencies && pkg.dependencies['@betterdb/shared']) {
81+
delete pkg.dependencies['@betterdb/shared'];
82+
}
83+
if (pkg.devDependencies) {
84+
delete pkg.devDependencies;
85+
}
86+
require('fs').writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\n');
87+
"
88+
89+
- name: Publish to npm
90+
env:
91+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
92+
run: cd packages/agent && npm publish --access public
93+
94+
publish-docker:
95+
runs-on: ubuntu-latest
96+
permissions:
97+
contents: read
98+
99+
steps:
100+
- name: Checkout
101+
uses: actions/checkout@v4
102+
103+
- name: Set up QEMU
104+
uses: docker/setup-qemu-action@v3
105+
106+
- name: Set up Docker Buildx
107+
uses: docker/setup-buildx-action@v3
108+
109+
- name: Login to Docker Hub
110+
uses: docker/login-action@v3
111+
with:
112+
username: ${{ secrets.DOCKERHUB_USERNAME }}
113+
password: ${{ secrets.DOCKERHUB_TOKEN }}
114+
115+
- name: Extract version from tag
116+
id: version
117+
run: |
118+
# agent-v0.1.0 → 0.1.0
119+
echo "version=${GITHUB_REF_NAME#agent-v}" >> $GITHUB_OUTPUT
120+
121+
- name: Build and push
122+
uses: docker/build-push-action@v6
123+
with:
124+
context: .
125+
file: ./packages/agent/Dockerfile
126+
platforms: linux/amd64,linux/arm64
127+
push: true
128+
tags: |
129+
betterdb/agent:${{ steps.version.outputs.version }}
130+
betterdb/agent:latest
131+
cache-from: type=gha,scope=agent
132+
cache-to: type=gha,mode=max,scope=agent

.github/workflows/api-tests.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ jobs:
3636

3737
- run: pnpm install --frozen-lockfile
3838

39+
- name: Generate Prisma client
40+
working-directory: proprietary/entitlement
41+
run: pnpm prisma:generate
42+
3943
- name: Run API tests
4044
working-directory: apps/api
4145
run: pnpm test

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,11 @@ pnpm-debug.log*
5959
# OS
6060
.DS_Store
6161
Thumbs.db
62+
63+
# Terraform
64+
.terraform/
65+
*.tfstate
66+
*.tfstate.backup
67+
terraform.tfvars
68+
*.tfvars
69+
!*.tfvars.example

apps/api/package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "api",
3-
"version": "0.5.0",
3+
"version": "0.6.0",
44
"private": true,
55
"scripts": {
66
"dev": "ts-node -r tsconfig-paths/register src/main.ts",
@@ -57,8 +57,10 @@
5757
"better-sqlite3": "^12.5.0",
5858
"class-transformer": "^0.5.1",
5959
"class-validator": "^0.14.3",
60+
"fastify": "^5.7.2",
6061
"hnswlib-node": "^3.0.0",
6162
"iovalkey": "^0.3.3",
63+
"jsonwebtoken": "^9.0.3",
6264
"langchain": "^1.2.7",
6365
"lru-cache": "^11.2.4",
6466
"ollama": "^0.6.3",
@@ -67,16 +69,19 @@
6769
"reflect-metadata": "^0.2.2",
6870
"rxjs": "^7.8.1",
6971
"semver": "^7.7.3",
72+
"ws": "^8.0.0",
7073
"zod": "^4.3.5"
7174
},
7275
"devDependencies": {
7376
"@nestjs/cli": "^11.0.14",
7477
"@nestjs/schematics": "^11.0.3",
7578
"@nestjs/testing": "^11.1.12",
7679
"@types/jest": "^30.0.0",
80+
"@types/jsonwebtoken": "^9.0.10",
7781
"@types/node": "^22.10.5",
7882
"@types/pg": "^8.16.0",
7983
"@types/semver": "^7.7.1",
84+
"@types/ws": "^8.0.0",
8085
"@types/supertest": "^6.0.3",
8186
"@typescript-eslint/eslint-plugin": "^8.19.1",
8287
"@typescript-eslint/parser": "^8.19.1",

apps/api/src/app.module.ts

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,59 +12,85 @@ import { CommandLogAnalyticsModule } from './commandlog-analytics/commandlog-ana
1212
import { PrometheusModule } from './prometheus/prometheus.module';
1313
import { SettingsModule } from './settings/settings.module';
1414
import { WebhooksModule } from './webhooks/webhooks.module';
15+
import { CloudAuthModule } from './auth/cloud-auth.module';
1516

1617
let AiModule: any = null;
1718
let LicenseModule: any = null;
1819
let KeyAnalyticsModule: any = null;
1920
let AnomalyModule: any = null;
2021
let WebhookProModule: any = null;
22+
let AgentModule: any = null;
2123

2224
try {
23-
const module = require('@proprietary/ai/ai.module');
25+
// Use relative path for runtime resolution (tsconfig paths only work at compile time)
26+
const module = require('../../../proprietary/ai/ai.module');
2427
AiModule = module.AiModule;
2528
console.log('[AI] Proprietary module loaded');
2629
} catch {
2730
// Proprietary module not available
2831
}
2932

3033
try {
31-
const licenseModule = require('@proprietary/license/license.module');
34+
const licenseModule = require('../../../proprietary/license/license.module');
3235
LicenseModule = licenseModule.LicenseModule;
3336
console.log('[License] Proprietary module loaded');
3437
} catch {
3538
// Proprietary module not available
3639
}
3740

3841
try {
39-
const keyAnalyticsModule = require('@proprietary/key-analytics/key-analytics.module');
42+
const keyAnalyticsModule = require('../../../proprietary/key-analytics/key-analytics.module');
4043
KeyAnalyticsModule = keyAnalyticsModule.KeyAnalyticsModule;
4144
console.log('[KeyAnalytics] Proprietary module loaded');
4245
} catch {
4346
// Proprietary module not available
4447
}
4548

4649
try {
47-
const anomalyModule = require('@proprietary/anomaly-detection/anomaly.module');
50+
const anomalyModule = require('../../../proprietary/anomaly-detection/anomaly.module');
4851
AnomalyModule = anomalyModule.AnomalyModule;
4952
console.log('[AnomalyDetection] Proprietary module loaded');
5053
} catch {
5154
// Proprietary module not available
5255
}
5356

5457
try {
55-
const webhookProModule = require('@proprietary/webhook-pro');
58+
const webhookProModule = require('../../../proprietary/webhook-pro');
5659
WebhookProModule = webhookProModule.WebhookProModule;
5760
console.log('[WebhookPro] Proprietary module loaded');
5861
} catch {
5962
// Proprietary module not available
6063
}
6164

65+
if (process.env.CLOUD_MODE) {
66+
try {
67+
const agentModule = require('../../../proprietary/agent/agent.module');
68+
AgentModule = agentModule.AgentModule;
69+
console.log('[Agent] Proprietary module loaded');
70+
} catch {
71+
// Proprietary module not available
72+
}
73+
}
74+
75+
// Cloud auth module - uses proprietary implementation in cloud mode
76+
let CloudAuthModuleToUse: any = CloudAuthModule;
77+
if (process.env.CLOUD_MODE) {
78+
try {
79+
const proprietaryCloudAuth = require('../../../proprietary/cloud-auth/cloud-auth.module');
80+
CloudAuthModuleToUse = proprietaryCloudAuth.ProprietaryCloudAuthModule;
81+
console.log('[CloudAuth] Proprietary module loaded');
82+
} catch {
83+
// Proprietary module not available, use OSS no-op
84+
}
85+
}
86+
6287
const baseImports = [
6388
ConfigModule,
6489
ThrottlerModule.forRoot([{
6590
ttl: 60000, // 60 seconds
6691
limit: 10000, // Very high default - endpoint-specific limits provide actual rate limiting
6792
}]),
93+
CloudAuthModuleToUse, // Cloud auth (no-op for self-hosted, proprietary for cloud)
6894
ConnectionsModule, // Must come early - provides ConnectionRegistry globally
6995
HealthModule,
7096
MetricsModule,
@@ -83,6 +109,7 @@ const proprietaryImports = [
83109
AnomalyModule,
84110
WebhookProModule,
85111
AiModule,
112+
AgentModule,
86113
].filter(Boolean);
87114

88115
@Module({

apps/api/src/audit/audit.service.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { SettingsService } from '../settings/settings.service';
77
import { WebhookDispatcherService } from '../webhooks/webhook-dispatcher.service';
88
import { MultiConnectionPoller, ConnectionContext } from '../common/services/multi-connection-poller';
99
import { ConnectionRegistry } from '../connections/connection-registry.service';
10+
import { RuntimeCapabilityTracker } from '../connections/runtime-capability-tracker.service';
1011

1112
@Injectable()
1213
export class AuditService extends MultiConnectionPoller implements OnModuleInit {
@@ -23,6 +24,7 @@ export class AuditService extends MultiConnectionPoller implements OnModuleInit
2324
private readonly settingsService: SettingsService,
2425
@Optional() private readonly webhookDispatcher?: WebhookDispatcherService,
2526
@Optional() @Inject(WEBHOOK_EVENTS_ENTERPRISE_SERVICE) private readonly webhookEventsEnterpriseService?: IWebhookEventsEnterpriseService,
27+
private readonly runtimeCapabilityTracker?: RuntimeCapabilityTracker,
2628
) {
2729
super(connectionRegistry);
2830
}
@@ -50,6 +52,10 @@ export class AuditService extends MultiConnectionPoller implements OnModuleInit
5052
return;
5153
}
5254

55+
if (this.runtimeCapabilityTracker && !this.runtimeCapabilityTracker.isAvailable(ctx.connectionId, 'canAclLog')) {
56+
return;
57+
}
58+
5359
// Get ACL log entries
5460
const aclEntries = await ctx.client.getAclLog(100);
5561

@@ -165,6 +171,10 @@ export class AuditService extends MultiConnectionPoller implements OnModuleInit
165171
this.lastSeenTimestamps.set(ctx.connectionId, latestTimestamp);
166172
this.prometheusService.incrementPollCounter(ctx.connectionId);
167173
} catch (error) {
174+
if (this.runtimeCapabilityTracker?.recordFailure(ctx.connectionId, 'canAclLog', error instanceof Error ? error : String(error))) {
175+
this.logger.warn(`ACL log disabled for ${ctx.connectionName} due to blocked command`);
176+
return;
177+
}
168178
this.logger.error(`Error polling ACL log for ${ctx.connectionName}: ${error instanceof Error ? error.message : 'Unknown error'}`);
169179
throw error;
170180
} finally {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Controller, Get, Query, Res } from '@nestjs/common';
2+
import { FastifyReply } from 'fastify';
3+
4+
@Controller('auth')
5+
export class AuthCallbackController {
6+
@Get('callback')
7+
handleCallback(@Query('token') token: string, @Res() reply: FastifyReply) {
8+
// OSS: no-op, just redirect to root
9+
reply.redirect('/');
10+
}
11+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
2+
3+
/**
4+
* No-op cloud auth guard for self-hosted deployments.
5+
* In cloud mode, the proprietary implementation replaces this.
6+
*/
7+
@Injectable()
8+
export class CloudAuthGuard implements CanActivate {
9+
canActivate(context: ExecutionContext): boolean {
10+
return true; // Self-hosted: no auth required
11+
}
12+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Module, Global } from '@nestjs/common';
2+
import { APP_GUARD } from '@nestjs/core';
3+
import { CloudAuthGuard } from './cloud-auth.guard';
4+
import { AuthCallbackController } from './auth.controller';
5+
6+
@Global()
7+
@Module({
8+
controllers: [AuthCallbackController],
9+
providers: [
10+
{
11+
provide: APP_GUARD,
12+
useClass: CloudAuthGuard,
13+
},
14+
],
15+
})
16+
export class CloudAuthModule { }

apps/api/src/auth/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './cloud-auth.module';
2+
export * from './cloud-auth.guard';
3+
export * from './auth.controller';

0 commit comments

Comments
 (0)