telemetry module (#934)

* update lockfile

* fix color check

* telemetry

* complete

* Use interval
This commit is contained in:
Philip Okugbe 2025-03-23 13:12:41 +00:00 committed by GitHub
parent 593f41a050
commit 13039cfacc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 140 additions and 0 deletions

View File

@ -46,6 +46,7 @@
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-fastify": "^11.0.10",
"@nestjs/platform-socket.io": "^11.0.10",
"@nestjs/schedule": "^5.0.1",
"@nestjs/terminus": "^11.0.0",
"@nestjs/websockets": "^11.0.10",
"@node-saml/passport-saml": "^5.0.1",

View File

@ -15,6 +15,7 @@ import { HealthModule } from './integrations/health/health.module';
import { ExportModule } from './integrations/export/export.module';
import { ImportModule } from './integrations/import/import.module';
import { SecurityModule } from './integrations/security/security.module';
import { TelemetryModule } from './integrations/telemetry/telemetry.module';
const enterpriseModules = [];
try {
@ -50,6 +51,7 @@ try {
}),
EventEmitterModule.forRoot(),
SecurityModule,
TelemetryModule,
...enterpriseModules,
],
controllers: [AppController],

View File

@ -182,4 +182,11 @@ export class EnvironmentService {
.toLowerCase();
return isStandalone === 'true';
}
isDisableTelemetry(): boolean {
const disable = this.configService
.get<string>('DISABLE_TELEMETRY', 'false')
.toLowerCase();
return disable === 'true';
}
}

View File

@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { TelemetryService } from './telemetry.service';
import { ScheduleModule } from '@nestjs/schedule';
@Module({
providers: [TelemetryService],
imports: [ScheduleModule.forRoot()],
})
export class TelemetryModule {}

View File

@ -0,0 +1,87 @@
import { Injectable } from '@nestjs/common';
import { Interval, SchedulerRegistry } from '@nestjs/schedule';
import { EnvironmentService } from '../environment/environment.service';
import { InjectKysely } from 'nestjs-kysely';
import { KyselyDB } from '@docmost/db/types/kysely.types';
import { createHmac } from 'node:crypto';
import { WorkspaceRepo } from '@docmost/db/repos/workspace/workspace.repo';
// eslint-disable-next-line @typescript-eslint/no-require-imports
const packageJson = require('./../../../package.json');
@Injectable()
export class TelemetryService {
private readonly ENDPOINT_URL = 'https://tel.docmost.com/api/event';
constructor(
private readonly environmentService: EnvironmentService,
@InjectKysely() private readonly db: KyselyDB,
private readonly workspaceRepo: WorkspaceRepo,
private schedulerRegistry: SchedulerRegistry,
) {}
@Interval('telemetry', 24 * 60 * 60 * 1000)
async sendTelemetry() {
try {
if (
this.environmentService.isDisableTelemetry() ||
this.environmentService.isCloud() ||
this.environmentService.getNodeEnv() !== 'production'
) {
this.schedulerRegistry.deleteInterval('telemetry');
return;
}
const workspace = await this.workspaceRepo.findFirst();
if (!workspace) {
return;
}
const anonymizedHash = createHmac(
'sha256',
this.environmentService.getAppSecret(),
)
.update(workspace.id)
.digest('hex');
const { userCount } = await this.db
.selectFrom('users')
.select((eb) => eb.fn.count('id').as('userCount'))
.executeTakeFirst();
const { pageCount } = await this.db
.selectFrom('pages')
.select((eb) => eb.fn.count('id').as('pageCount'))
.executeTakeFirst();
const { workspaceCount } = await this.db
.selectFrom('workspaces')
.select((eb) => eb.fn.count('id').as('workspaceCount'))
.executeTakeFirst();
const { spaceCount } = await this.db
.selectFrom('spaces')
.select((eb) => eb.fn.count('id').as('spaceCount'))
.executeTakeFirst();
const data = {
instanceId: anonymizedHash,
version: packageJson.version,
userCount,
pageCount,
spaceCount,
workspaceCount,
};
await fetch(this.ENDPOINT_URL, {
method: 'POST',
headers: {
'User-Agent': 'docmost:' + data.version,
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
} catch (err) {
/* empty */
}
}
}

34
pnpm-lock.yaml generated
View File

@ -444,6 +444,9 @@ importers:
'@nestjs/platform-socket.io':
specifier: ^11.0.10
version: 11.0.10(@nestjs/common@11.0.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/websockets@11.0.10)(rxjs@7.8.1)
'@nestjs/schedule':
specifier: ^5.0.1
version: 5.0.1(@nestjs/common@11.0.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.10)
'@nestjs/terminus':
specifier: ^11.0.0
version: 11.0.0(@nestjs/common@11.0.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.10)(reflect-metadata@0.2.2)(rxjs@7.8.1)
@ -2820,6 +2823,12 @@ packages:
'@nestjs/websockets': ^11.0.0
rxjs: ^7.1.0
'@nestjs/schedule@5.0.1':
resolution: {integrity: sha512-kFoel84I4RyS2LNPH6yHYTKxB16tb3auAEciFuc788C3ph6nABkUfzX5IE+unjVaRX+3GuruJwurNepMlHXpQg==}
peerDependencies:
'@nestjs/common': ^10.0.0 || ^11.0.0
'@nestjs/core': ^10.0.0 || ^11.0.0
'@nestjs/schematics@11.0.1':
resolution: {integrity: sha512-PHPAUk4sXkfCxiMacD1JFC+vEyzXjZJRCu1KT2MmG2hrTiMDMk5KtMprro148JUefNuWbVyN0uLTJVSmWVzhoA==}
peerDependencies:
@ -4119,6 +4128,9 @@ packages:
'@types/linkify-it@5.0.0':
resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
'@types/luxon@3.4.2':
resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==}
'@types/markdown-it@14.1.2':
resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==}
@ -5060,6 +5072,9 @@ packages:
resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==}
engines: {node: '>=12.0.0'}
cron@3.5.0:
resolution: {integrity: sha512-0eYZqCnapmxYcV06uktql93wNWdlTmmBFP2iYz+JPVcQqlyFYcn1lFuIk4R54pkOmE7mcldTAPZv6X5XA4Q46A==}
cross-env@7.0.3:
resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==}
engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'}
@ -6822,6 +6837,10 @@ packages:
resolution: {integrity: sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==}
engines: {node: '>=12'}
luxon@3.5.0:
resolution: {integrity: sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==}
engines: {node: '>=12'}
magic-string@0.30.17:
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
@ -11954,6 +11973,12 @@ snapshots:
- supports-color
- utf-8-validate
'@nestjs/schedule@5.0.1(@nestjs/common@11.0.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.10)':
dependencies:
'@nestjs/common': 11.0.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@nestjs/core': 11.0.10(@nestjs/common@11.0.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/websockets@11.0.10)(reflect-metadata@0.2.2)(rxjs@7.8.1)
cron: 3.5.0
'@nestjs/schematics@11.0.1(chokidar@4.0.3)(typescript@5.7.3)':
dependencies:
'@angular-devkit/core': 19.1.7(chokidar@4.0.3)
@ -13331,6 +13356,8 @@ snapshots:
'@types/linkify-it@5.0.0': {}
'@types/luxon@3.4.2': {}
'@types/markdown-it@14.1.2':
dependencies:
'@types/linkify-it': 5.0.0
@ -14486,6 +14513,11 @@ snapshots:
dependencies:
luxon: 3.4.4
cron@3.5.0:
dependencies:
'@types/luxon': 3.4.2
luxon: 3.5.0
cross-env@7.0.3:
dependencies:
cross-spawn: 7.0.3
@ -16688,6 +16720,8 @@ snapshots:
luxon@3.4.4: {}
luxon@3.5.0: {}
magic-string@0.30.17:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.0