Upload folder using huggingface_hub (part 10)
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- libraries/nestjs-libraries/src/chat/tools/integration.validation.tool.ts +114 -0
- libraries/nestjs-libraries/src/chat/tools/tool.list.ts +23 -0
- libraries/nestjs-libraries/src/chat/tools/upload.from.url.tool.ts +145 -0
- libraries/nestjs-libraries/src/chat/tools/video.function.tool.ts +56 -0
- libraries/nestjs-libraries/src/chat/validation.schemas.helper.ts +30 -0
- libraries/nestjs-libraries/src/database/prisma/admin-stats/admin-stats.repository.ts +157 -0
- libraries/nestjs-libraries/src/database/prisma/admin-stats/admin-stats.service.ts +14 -0
- libraries/nestjs-libraries/src/database/prisma/agencies/agencies.repository.ts +183 -0
- libraries/nestjs-libraries/src/database/prisma/agencies/agencies.service.ts +217 -0
- libraries/nestjs-libraries/src/database/prisma/announcements/announcements.repository.ts +35 -0
- libraries/nestjs-libraries/src/database/prisma/announcements/announcements.service.ts +20 -0
- libraries/nestjs-libraries/src/database/prisma/autopost/autopost.repository.ts +107 -0
- libraries/nestjs-libraries/src/database/prisma/autopost/autopost.service.ts +371 -0
- libraries/nestjs-libraries/src/database/prisma/database.module.ts +105 -0
- libraries/nestjs-libraries/src/database/prisma/errors/errors.repository.ts +143 -0
- libraries/nestjs-libraries/src/database/prisma/errors/errors.service.ts +21 -0
- libraries/nestjs-libraries/src/database/prisma/integrations/integration.repository.ts +674 -0
- libraries/nestjs-libraries/src/database/prisma/integrations/integration.service.ts +569 -0
- libraries/nestjs-libraries/src/database/prisma/media/media.repository.ts +123 -0
- libraries/nestjs-libraries/src/database/prisma/media/media.service.ts +157 -0
- libraries/nestjs-libraries/src/database/prisma/notifications/notification.service.ts +117 -0
- libraries/nestjs-libraries/src/database/prisma/notifications/notifications.repository.ts +126 -0
- libraries/nestjs-libraries/src/database/prisma/oauth/oauth.repository.ts +252 -0
- libraries/nestjs-libraries/src/database/prisma/oauth/oauth.service.ts +170 -0
- libraries/nestjs-libraries/src/database/prisma/organizations/organization.repository.ts +422 -0
- libraries/nestjs-libraries/src/database/prisma/organizations/organization.service.ts +133 -0
- libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts +923 -0
- libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts +1174 -0
- libraries/nestjs-libraries/src/database/prisma/prisma.service.ts +39 -0
- libraries/nestjs-libraries/src/database/prisma/schema.prisma +970 -0
- libraries/nestjs-libraries/src/database/prisma/sets/sets.repository.ts +58 -0
- libraries/nestjs-libraries/src/database/prisma/sets/sets.service.ts +24 -0
- libraries/nestjs-libraries/src/database/prisma/signatures/signature.repository.ts +55 -0
- libraries/nestjs-libraries/src/database/prisma/signatures/signature.service.ts +28 -0
- libraries/nestjs-libraries/src/database/prisma/subscriptions/pricing.ts +113 -0
- libraries/nestjs-libraries/src/database/prisma/subscriptions/subscription.repository.ts +286 -0
- libraries/nestjs-libraries/src/database/prisma/subscriptions/subscription.service.ts +264 -0
- libraries/nestjs-libraries/src/database/prisma/third-party/third-party.repository.ts +64 -0
- libraries/nestjs-libraries/src/database/prisma/third-party/third-party.service.ts +28 -0
- libraries/nestjs-libraries/src/database/prisma/users/users.repository.ts +177 -0
- libraries/nestjs-libraries/src/database/prisma/users/users.service.ts +54 -0
- libraries/nestjs-libraries/src/database/prisma/webhooks/webhooks.repository.ts +87 -0
- libraries/nestjs-libraries/src/database/prisma/webhooks/webhooks.service.ts +24 -0
- libraries/nestjs-libraries/src/dtos/agencies/create.agency.dto.ts +96 -0
- libraries/nestjs-libraries/src/dtos/analytics/stars.list.dto.ts +15 -0
- libraries/nestjs-libraries/src/dtos/announcements/announcements.dto.ts +16 -0
- libraries/nestjs-libraries/src/dtos/auth/create.org.user.dto.ts +40 -0
- libraries/nestjs-libraries/src/dtos/auth/forgot-return.password.dto.ts +28 -0
- libraries/nestjs-libraries/src/dtos/auth/forgot.password.dto.ts +8 -0
- libraries/nestjs-libraries/src/dtos/auth/login.user.dto.ts +31 -0
libraries/nestjs-libraries/src/chat/tools/integration.validation.tool.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { AgentToolInterface } from '@gitroom/nestjs-libraries/chat/agent.tool.interface';
|
| 2 |
+
import { createTool } from '@mastra/core/tools';
|
| 3 |
+
import { z } from 'zod';
|
| 4 |
+
import { Injectable } from '@nestjs/common';
|
| 5 |
+
import {
|
| 6 |
+
IntegrationManager,
|
| 7 |
+
socialIntegrationList,
|
| 8 |
+
} from '@gitroom/nestjs-libraries/integrations/integration.manager';
|
| 9 |
+
import { getValidationSchemas } from '@gitroom/nestjs-libraries/chat/validation.schemas.helper';
|
| 10 |
+
import { checkAuth } from '@gitroom/nestjs-libraries/chat/auth.context';
|
| 11 |
+
|
| 12 |
+
@Injectable()
|
| 13 |
+
export class IntegrationValidationTool implements AgentToolInterface {
|
| 14 |
+
constructor(private _integrationManager: IntegrationManager) {}
|
| 15 |
+
name = 'integrationSchema';
|
| 16 |
+
|
| 17 |
+
run() {
|
| 18 |
+
return createTool({
|
| 19 |
+
id: 'integrationSchema',
|
| 20 |
+
description: `Everytime we want to schedule a social media post, we need to understand the schema of the integration.
|
| 21 |
+
This tool helps us get the schema of the integration.
|
| 22 |
+
Sometimes we might get a schema back the requires some id, for that, you can get information from 'tools'
|
| 23 |
+
And use the triggerTool function.
|
| 24 |
+
`,
|
| 25 |
+
mcp: {
|
| 26 |
+
annotations: {
|
| 27 |
+
title: 'Get Integration Schema',
|
| 28 |
+
readOnlyHint: true,
|
| 29 |
+
destructiveHint: false,
|
| 30 |
+
idempotentHint: true,
|
| 31 |
+
openWorldHint: false,
|
| 32 |
+
},
|
| 33 |
+
},
|
| 34 |
+
inputSchema: z.object({
|
| 35 |
+
isPremium: z
|
| 36 |
+
.boolean()
|
| 37 |
+
.describe('is this the user premium? if not, set to false'),
|
| 38 |
+
platform: z
|
| 39 |
+
.string()
|
| 40 |
+
.describe(
|
| 41 |
+
`platform identifier (${socialIntegrationList
|
| 42 |
+
.map((p) => p.identifier)
|
| 43 |
+
.join(', ')})`
|
| 44 |
+
),
|
| 45 |
+
}),
|
| 46 |
+
outputSchema: z.object({
|
| 47 |
+
output: z.object({
|
| 48 |
+
rules: z.string(),
|
| 49 |
+
maxLength: z
|
| 50 |
+
.number()
|
| 51 |
+
.describe('The maximum length of a post / comment'),
|
| 52 |
+
settings: z
|
| 53 |
+
.any()
|
| 54 |
+
.describe('List of settings need to be passed to schedule a post'),
|
| 55 |
+
tools: z
|
| 56 |
+
.array(
|
| 57 |
+
z.object({
|
| 58 |
+
description: z.string().describe('Description of the tool'),
|
| 59 |
+
methodName: z
|
| 60 |
+
.string()
|
| 61 |
+
.describe('Method to call to get the information'),
|
| 62 |
+
dataSchema: z
|
| 63 |
+
.array(
|
| 64 |
+
z.object({
|
| 65 |
+
key: z
|
| 66 |
+
.string()
|
| 67 |
+
.describe('Name of the settings key to pass'),
|
| 68 |
+
description: z
|
| 69 |
+
.string()
|
| 70 |
+
.describe('Description of the setting key'),
|
| 71 |
+
type: z.string(),
|
| 72 |
+
})
|
| 73 |
+
)
|
| 74 |
+
.describe(
|
| 75 |
+
'This will be passed to schedulePostTool [output:settings]'
|
| 76 |
+
),
|
| 77 |
+
})
|
| 78 |
+
)
|
| 79 |
+
.describe(
|
| 80 |
+
"Sometimes settings require some id, tags and stuff, if you don't have, trigger the `triggerTool` function from the tools list [output:callable-tools]"
|
| 81 |
+
),
|
| 82 |
+
}),
|
| 83 |
+
}),
|
| 84 |
+
execute: async (inputData, context) => {
|
| 85 |
+
checkAuth(inputData, context);
|
| 86 |
+
const integration = socialIntegrationList.find(
|
| 87 |
+
(p) => p.identifier === inputData.platform
|
| 88 |
+
)!;
|
| 89 |
+
|
| 90 |
+
if (!integration) {
|
| 91 |
+
return {
|
| 92 |
+
output: { rules: '', maxLength: 0, settings: {}, tools: [] },
|
| 93 |
+
};
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
const maxLength = integration.maxLength(inputData.isPremium);
|
| 97 |
+
const schemas = !integration.dto
|
| 98 |
+
? false
|
| 99 |
+
: getValidationSchemas()[integration.dto.name];
|
| 100 |
+
const tools = this._integrationManager.getAllTools();
|
| 101 |
+
const rules = this._integrationManager.getAllRulesDescription();
|
| 102 |
+
|
| 103 |
+
return {
|
| 104 |
+
output: {
|
| 105 |
+
rules: rules[integration.identifier],
|
| 106 |
+
maxLength,
|
| 107 |
+
settings: !schemas ? 'No additional settings required' : schemas,
|
| 108 |
+
tools: tools[integration.identifier],
|
| 109 |
+
},
|
| 110 |
+
};
|
| 111 |
+
},
|
| 112 |
+
});
|
| 113 |
+
}
|
| 114 |
+
}
|
libraries/nestjs-libraries/src/chat/tools/tool.list.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { IntegrationValidationTool } from '@gitroom/nestjs-libraries/chat/tools/integration.validation.tool';
|
| 2 |
+
import { IntegrationTriggerTool } from '@gitroom/nestjs-libraries/chat/tools/integration.trigger.tool';
|
| 3 |
+
import { IntegrationSchedulePostTool } from './integration.schedule.post';
|
| 4 |
+
import { GenerateVideoOptionsTool } from '@gitroom/nestjs-libraries/chat/tools/generate.video.options.tool';
|
| 5 |
+
import { VideoFunctionTool } from '@gitroom/nestjs-libraries/chat/tools/video.function.tool';
|
| 6 |
+
import { GenerateVideoTool } from '@gitroom/nestjs-libraries/chat/tools/generate.video.tool';
|
| 7 |
+
import { GenerateImageTool } from '@gitroom/nestjs-libraries/chat/tools/generate.image.tool';
|
| 8 |
+
import { IntegrationListTool } from '@gitroom/nestjs-libraries/chat/tools/integration.list.tool';
|
| 9 |
+
import { GroupListTool } from '@gitroom/nestjs-libraries/chat/tools/group.list.tool';
|
| 10 |
+
import { UploadFromUrlTool } from '@gitroom/nestjs-libraries/chat/tools/upload.from.url.tool';
|
| 11 |
+
|
| 12 |
+
export const toolList = [
|
| 13 |
+
IntegrationListTool,
|
| 14 |
+
GroupListTool,
|
| 15 |
+
IntegrationValidationTool,
|
| 16 |
+
IntegrationTriggerTool,
|
| 17 |
+
IntegrationSchedulePostTool,
|
| 18 |
+
GenerateVideoOptionsTool,
|
| 19 |
+
VideoFunctionTool,
|
| 20 |
+
GenerateVideoTool,
|
| 21 |
+
GenerateImageTool,
|
| 22 |
+
UploadFromUrlTool,
|
| 23 |
+
];
|
libraries/nestjs-libraries/src/chat/tools/upload.from.url.tool.ts
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { AgentToolInterface } from '@gitroom/nestjs-libraries/chat/agent.tool.interface';
|
| 2 |
+
import { createTool } from '@mastra/core/tools';
|
| 3 |
+
import { z } from 'zod';
|
| 4 |
+
import { Injectable } from '@nestjs/common';
|
| 5 |
+
import { MediaService } from '@gitroom/nestjs-libraries/database/prisma/media/media.service';
|
| 6 |
+
import { UploadFactory } from '@gitroom/nestjs-libraries/upload/upload.factory';
|
| 7 |
+
import { getMaxSize } from '@gitroom/nestjs-libraries/upload/custom.upload.validation';
|
| 8 |
+
import { checkAuth } from '@gitroom/nestjs-libraries/chat/auth.context';
|
| 9 |
+
import { ssrfSafeDispatcher } from '@gitroom/nestjs-libraries/dtos/webhooks/ssrf.safe.dispatcher';
|
| 10 |
+
import { Readable } from 'stream';
|
| 11 |
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
| 12 |
+
const { fromBuffer } = require('file-type');
|
| 13 |
+
|
| 14 |
+
// Same allow-list as the public API /upload-from-url route.
|
| 15 |
+
const ALLOWED_MIME = new Set<string>([
|
| 16 |
+
'image/jpeg',
|
| 17 |
+
'image/png',
|
| 18 |
+
'image/gif',
|
| 19 |
+
'image/webp',
|
| 20 |
+
'image/avif',
|
| 21 |
+
'image/bmp',
|
| 22 |
+
'image/tiff',
|
| 23 |
+
'video/mp4',
|
| 24 |
+
]);
|
| 25 |
+
|
| 26 |
+
@Injectable()
|
| 27 |
+
export class UploadFromUrlTool implements AgentToolInterface {
|
| 28 |
+
private storage = UploadFactory.createStorage();
|
| 29 |
+
|
| 30 |
+
constructor(private _mediaService: MediaService) {}
|
| 31 |
+
name = 'uploadFromUrlTool';
|
| 32 |
+
|
| 33 |
+
run() {
|
| 34 |
+
return createTool({
|
| 35 |
+
id: 'uploadFromUrlTool',
|
| 36 |
+
description: `Upload a remote image or video into the media library from a public URL.
|
| 37 |
+
Use this before scheduling a post when the user provides an external media URL (not already hosted on our domain),
|
| 38 |
+
so the attachment passes the upload-domain validation. Returns the hosted media { id, path } to use as an attachment, or { error } on failure.`,
|
| 39 |
+
mcp: {
|
| 40 |
+
annotations: {
|
| 41 |
+
title: 'Upload Media From URL',
|
| 42 |
+
readOnlyHint: false,
|
| 43 |
+
destructiveHint: false,
|
| 44 |
+
idempotentHint: false,
|
| 45 |
+
openWorldHint: true,
|
| 46 |
+
},
|
| 47 |
+
},
|
| 48 |
+
inputSchema: z.object({
|
| 49 |
+
url: z
|
| 50 |
+
.string()
|
| 51 |
+
.url()
|
| 52 |
+
.describe('The public URL of the image or video to upload'),
|
| 53 |
+
}),
|
| 54 |
+
// Mastra validates a tool's return against this schema, so it must also
|
| 55 |
+
// allow the graceful { error } shape. Fields are optional (rather than
|
| 56 |
+
// wrapping everything in an `output` union) to keep the change minimal:
|
| 57 |
+
// the existing { id, path } success return and the new { error } return
|
| 58 |
+
// both validate without rewriting every return statement.
|
| 59 |
+
outputSchema: z.object({
|
| 60 |
+
id: z.string().optional(),
|
| 61 |
+
path: z.string().optional(),
|
| 62 |
+
error: z.string().optional(),
|
| 63 |
+
}),
|
| 64 |
+
execute: async (inputData, context) => {
|
| 65 |
+
checkAuth(inputData, context);
|
| 66 |
+
try {
|
| 67 |
+
const org = JSON.parse(
|
| 68 |
+
(context?.requestContext as any)?.get('organization') as string
|
| 69 |
+
);
|
| 70 |
+
|
| 71 |
+
const response = await fetch(inputData.url, {
|
| 72 |
+
// @ts-ignore — undici option, not in lib.dom fetch types
|
| 73 |
+
dispatcher: ssrfSafeDispatcher,
|
| 74 |
+
});
|
| 75 |
+
|
| 76 |
+
if (!response.ok) {
|
| 77 |
+
return { error: 'Failed to fetch URL' };
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
// Guard against OOM: bail out before buffering the whole body into
|
| 81 |
+
// memory. Content-Length may be absent or wrong, so we re-check the
|
| 82 |
+
// real size after download too. The type isn't known yet (sniffed
|
| 83 |
+
// below), so the pre-check uses the largest allowed cap (video).
|
| 84 |
+
const maxDownloadSize = getMaxSize('video/mp4');
|
| 85 |
+
const declaredSize = Number(response.headers.get('content-length'));
|
| 86 |
+
if (declaredSize && declaredSize > maxDownloadSize) {
|
| 87 |
+
return {
|
| 88 |
+
error: `File is too large: ${declaredSize} bytes (max ${maxDownloadSize} bytes).`,
|
| 89 |
+
};
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
const buffer = Buffer.from(await response.arrayBuffer());
|
| 93 |
+
const detected = await fromBuffer(buffer);
|
| 94 |
+
if (!detected || !ALLOWED_MIME.has(detected.mime)) {
|
| 95 |
+
return { error: 'Unsupported file type.' };
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
const maxSize = getMaxSize(detected.mime);
|
| 99 |
+
if (buffer.length > maxSize) {
|
| 100 |
+
return {
|
| 101 |
+
error: `File is too large: ${buffer.length} bytes (max ${maxSize} bytes).`,
|
| 102 |
+
};
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
const getFile = await this.storage.uploadFile({
|
| 106 |
+
buffer,
|
| 107 |
+
mimetype: detected.mime,
|
| 108 |
+
size: buffer.length,
|
| 109 |
+
path: '',
|
| 110 |
+
fieldname: '',
|
| 111 |
+
destination: '',
|
| 112 |
+
stream: new Readable(),
|
| 113 |
+
filename: '',
|
| 114 |
+
originalname: `upload.${detected.ext}`,
|
| 115 |
+
encoding: '',
|
| 116 |
+
});
|
| 117 |
+
|
| 118 |
+
return await this._mediaService.saveFile(
|
| 119 |
+
org.id,
|
| 120 |
+
getFile.originalname,
|
| 121 |
+
getFile.path
|
| 122 |
+
);
|
| 123 |
+
} catch (err) {
|
| 124 |
+
// undici's fetch rejects with a generic TypeError('fetch failed')
|
| 125 |
+
// and hides the real reason (DNS, TLS, SSRF block, ...) in
|
| 126 |
+
// err.cause, so surface it for the agent. Error.cause isn't in the
|
| 127 |
+
// es2020 lib typings this repo compiles against, hence the cast.
|
| 128 |
+
const cause =
|
| 129 |
+
err instanceof Error
|
| 130 |
+
? (err as Error & { cause?: unknown }).cause
|
| 131 |
+
: undefined;
|
| 132 |
+
const causeText =
|
| 133 |
+
cause instanceof Error && cause.message
|
| 134 |
+
? ` (${cause.message})`
|
| 135 |
+
: '';
|
| 136 |
+
return {
|
| 137 |
+
error: `Failed to upload media from URL: ${
|
| 138 |
+
err instanceof Error ? err.message : 'Unexpected error'
|
| 139 |
+
}${causeText}`,
|
| 140 |
+
};
|
| 141 |
+
}
|
| 142 |
+
},
|
| 143 |
+
});
|
| 144 |
+
}
|
| 145 |
+
}
|
libraries/nestjs-libraries/src/chat/tools/video.function.tool.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { AgentToolInterface } from '@gitroom/nestjs-libraries/chat/agent.tool.interface';
|
| 2 |
+
import { createTool } from '@mastra/core/tools';
|
| 3 |
+
import { Injectable } from '@nestjs/common';
|
| 4 |
+
import { VideoManager } from '@gitroom/nestjs-libraries/videos/video.manager';
|
| 5 |
+
import z from 'zod';
|
| 6 |
+
import { ModuleRef } from '@nestjs/core';
|
| 7 |
+
import { checkAuth } from '@gitroom/nestjs-libraries/chat/auth.context';
|
| 8 |
+
|
| 9 |
+
@Injectable()
|
| 10 |
+
export class VideoFunctionTool implements AgentToolInterface {
|
| 11 |
+
constructor(
|
| 12 |
+
private _videoManagerService: VideoManager,
|
| 13 |
+
private _moduleRef: ModuleRef
|
| 14 |
+
) {}
|
| 15 |
+
name = 'videoFunctionTool';
|
| 16 |
+
|
| 17 |
+
run() {
|
| 18 |
+
return createTool({
|
| 19 |
+
id: 'videoFunctionTool',
|
| 20 |
+
description: `Sometimes when we want to generate videos we might need to get some additional information like voice_id, etc`,
|
| 21 |
+
mcp: {
|
| 22 |
+
annotations: {
|
| 23 |
+
title: 'Video Function Helper',
|
| 24 |
+
readOnlyHint: true,
|
| 25 |
+
destructiveHint: false,
|
| 26 |
+
idempotentHint: true,
|
| 27 |
+
openWorldHint: false,
|
| 28 |
+
},
|
| 29 |
+
},
|
| 30 |
+
inputSchema: z.object({
|
| 31 |
+
identifier: z.string(),
|
| 32 |
+
functionName: z.string(),
|
| 33 |
+
}),
|
| 34 |
+
outputSchema: z.object({}).passthrough(),
|
| 35 |
+
execute: async (inputData, context) => {
|
| 36 |
+
checkAuth(inputData, context);
|
| 37 |
+
const videos = this._videoManagerService.getAllVideos();
|
| 38 |
+
const findVideo = videos.find(
|
| 39 |
+
(p) =>
|
| 40 |
+
p.identifier === inputData.identifier &&
|
| 41 |
+
p.tools.some((p) => p.functionName === inputData.functionName)
|
| 42 |
+
);
|
| 43 |
+
|
| 44 |
+
if (!findVideo) {
|
| 45 |
+
throw new Error('Function not found');
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
const func = await this._moduleRef
|
| 49 |
+
// @ts-ignore
|
| 50 |
+
.get(findVideo.target, { strict: false })
|
| 51 |
+
[inputData.functionName]();
|
| 52 |
+
return func;
|
| 53 |
+
},
|
| 54 |
+
});
|
| 55 |
+
}
|
| 56 |
+
}
|
libraries/nestjs-libraries/src/chat/validation.schemas.helper.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import {
|
| 2 |
+
validationMetadatasToSchemas,
|
| 3 |
+
targetConstructorToSchema,
|
| 4 |
+
} from 'class-validator-jsonschema';
|
| 5 |
+
import { ValidationTypes } from 'class-validator';
|
| 6 |
+
// @ts-ignore
|
| 7 |
+
import { defaultMetadataStorage } from 'class-transformer/cjs/storage';
|
| 8 |
+
|
| 9 |
+
export function getValidationSchemas() {
|
| 10 |
+
return validationMetadatasToSchemas({
|
| 11 |
+
classTransformerMetadataStorage: defaultMetadataStorage,
|
| 12 |
+
additionalConverters: {
|
| 13 |
+
[ValidationTypes.NESTED_VALIDATION]: (meta, options) => {
|
| 14 |
+
if (typeof meta.target === 'function') {
|
| 15 |
+
const typeMeta = options.classTransformerMetadataStorage
|
| 16 |
+
? options.classTransformerMetadataStorage.findTypeMetadata(
|
| 17 |
+
meta.target,
|
| 18 |
+
meta.propertyName
|
| 19 |
+
)
|
| 20 |
+
: null;
|
| 21 |
+
if (typeMeta) {
|
| 22 |
+
const childType = typeMeta.typeFunction();
|
| 23 |
+
return targetConstructorToSchema(childType, options);
|
| 24 |
+
}
|
| 25 |
+
}
|
| 26 |
+
return {};
|
| 27 |
+
},
|
| 28 |
+
},
|
| 29 |
+
});
|
| 30 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/admin-stats/admin-stats.repository.ts
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';
|
| 2 |
+
import { Injectable } from '@nestjs/common';
|
| 3 |
+
import { Prisma } from '@prisma/client';
|
| 4 |
+
|
| 5 |
+
export interface StatsParams {
|
| 6 |
+
from: Date;
|
| 7 |
+
to: Date;
|
| 8 |
+
unknownOnly?: boolean;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
// Unknown errors are stored as the serialized error payload, e.g.
|
| 12 |
+
// {..."message":"Unknown Error"...}. Matches `message LIKE '%"message":"Unknown Error"%'`.
|
| 13 |
+
const UNKNOWN_ERROR_TOKEN = '"message":"Unknown Error"';
|
| 14 |
+
|
| 15 |
+
interface PerSocial {
|
| 16 |
+
provider: string;
|
| 17 |
+
count: number;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
export interface StatsResponse {
|
| 21 |
+
from: string;
|
| 22 |
+
to: string;
|
| 23 |
+
errors: { total: number; perSocial: PerSocial[] };
|
| 24 |
+
posts: { total: number; perSocial: PerSocial[] };
|
| 25 |
+
connected: { total: number; perSocial: PerSocial[] };
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
const sortDesc = (list: PerSocial[]) =>
|
| 29 |
+
list.sort((a, b) => b.count - a.count || a.provider.localeCompare(b.provider));
|
| 30 |
+
|
| 31 |
+
@Injectable()
|
| 32 |
+
export class AdminStatsRepository {
|
| 33 |
+
constructor(
|
| 34 |
+
private _post: PrismaRepository<'post'>,
|
| 35 |
+
private _integration: PrismaRepository<'integration'>,
|
| 36 |
+
private _errors: PrismaRepository<'errors'>
|
| 37 |
+
) {}
|
| 38 |
+
|
| 39 |
+
private async errorStats(params: StatsParams) {
|
| 40 |
+
const where: Prisma.ErrorsWhereInput = {
|
| 41 |
+
createdAt: { gte: params.from, lte: params.to },
|
| 42 |
+
...(params.unknownOnly
|
| 43 |
+
? { message: { contains: UNKNOWN_ERROR_TOKEN } }
|
| 44 |
+
: {}),
|
| 45 |
+
};
|
| 46 |
+
|
| 47 |
+
const [total, grouped] = await Promise.all([
|
| 48 |
+
this._errors.model.errors.count({ where }),
|
| 49 |
+
this._errors.model.errors.groupBy({
|
| 50 |
+
by: ['platform'],
|
| 51 |
+
where,
|
| 52 |
+
_count: { _all: true },
|
| 53 |
+
}),
|
| 54 |
+
]);
|
| 55 |
+
|
| 56 |
+
return {
|
| 57 |
+
total,
|
| 58 |
+
perSocial: sortDesc(
|
| 59 |
+
grouped.map((g) => ({
|
| 60 |
+
provider: g.platform,
|
| 61 |
+
count: g._count._all,
|
| 62 |
+
}))
|
| 63 |
+
),
|
| 64 |
+
};
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
private async postStats(params: StatsParams) {
|
| 68 |
+
// Only count top-level posts (thread children share a parentPostId) so the
|
| 69 |
+
// numbers match a "post published to a channel" rather than every fragment.
|
| 70 |
+
const where: Prisma.PostWhereInput = {
|
| 71 |
+
state: 'PUBLISHED',
|
| 72 |
+
parentPostId: null,
|
| 73 |
+
deletedAt: null,
|
| 74 |
+
publishDate: { gte: params.from, lte: params.to },
|
| 75 |
+
};
|
| 76 |
+
|
| 77 |
+
const [total, grouped] = await Promise.all([
|
| 78 |
+
this._post.model.post.count({ where }),
|
| 79 |
+
this._post.model.post.groupBy({
|
| 80 |
+
by: ['integrationId'],
|
| 81 |
+
where,
|
| 82 |
+
_count: { _all: true },
|
| 83 |
+
}),
|
| 84 |
+
]);
|
| 85 |
+
|
| 86 |
+
// groupBy can't reach into the integration relation, so resolve the
|
| 87 |
+
// providerIdentifier for the integrations we saw and fold the counts.
|
| 88 |
+
const integrationIds = grouped.map((g) => g.integrationId);
|
| 89 |
+
const integrations = integrationIds.length
|
| 90 |
+
? await this._integration.model.integration.findMany({
|
| 91 |
+
where: { id: { in: integrationIds } },
|
| 92 |
+
select: { id: true, providerIdentifier: true },
|
| 93 |
+
})
|
| 94 |
+
: [];
|
| 95 |
+
const providerById = new Map(
|
| 96 |
+
integrations.map((i) => [i.id, i.providerIdentifier])
|
| 97 |
+
);
|
| 98 |
+
|
| 99 |
+
const byProvider = new Map<string, number>();
|
| 100 |
+
for (const g of grouped) {
|
| 101 |
+
const provider = providerById.get(g.integrationId) || 'unknown';
|
| 102 |
+
byProvider.set(provider, (byProvider.get(provider) || 0) + g._count._all);
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
return {
|
| 106 |
+
total,
|
| 107 |
+
perSocial: sortDesc(
|
| 108 |
+
[...byProvider.entries()].map(([provider, count]) => ({
|
| 109 |
+
provider,
|
| 110 |
+
count,
|
| 111 |
+
}))
|
| 112 |
+
),
|
| 113 |
+
};
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
private async connectedStats(params: StatsParams) {
|
| 117 |
+
const where: Prisma.IntegrationWhereInput = {
|
| 118 |
+
deletedAt: null,
|
| 119 |
+
createdAt: { gte: params.from, lte: params.to },
|
| 120 |
+
};
|
| 121 |
+
|
| 122 |
+
const [total, grouped] = await Promise.all([
|
| 123 |
+
this._integration.model.integration.count({ where }),
|
| 124 |
+
this._integration.model.integration.groupBy({
|
| 125 |
+
by: ['providerIdentifier'],
|
| 126 |
+
where,
|
| 127 |
+
_count: { _all: true },
|
| 128 |
+
}),
|
| 129 |
+
]);
|
| 130 |
+
|
| 131 |
+
return {
|
| 132 |
+
total,
|
| 133 |
+
perSocial: sortDesc(
|
| 134 |
+
grouped.map((g) => ({
|
| 135 |
+
provider: g.providerIdentifier,
|
| 136 |
+
count: g._count._all,
|
| 137 |
+
}))
|
| 138 |
+
),
|
| 139 |
+
};
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
async getStats(params: StatsParams): Promise<StatsResponse> {
|
| 143 |
+
const [errors, posts, connected] = await Promise.all([
|
| 144 |
+
this.errorStats(params),
|
| 145 |
+
this.postStats(params),
|
| 146 |
+
this.connectedStats(params),
|
| 147 |
+
]);
|
| 148 |
+
|
| 149 |
+
return {
|
| 150 |
+
from: params.from.toISOString(),
|
| 151 |
+
to: params.to.toISOString(),
|
| 152 |
+
errors,
|
| 153 |
+
posts,
|
| 154 |
+
connected,
|
| 155 |
+
};
|
| 156 |
+
}
|
| 157 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/admin-stats/admin-stats.service.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Injectable } from '@nestjs/common';
|
| 2 |
+
import {
|
| 3 |
+
AdminStatsRepository,
|
| 4 |
+
StatsParams,
|
| 5 |
+
} from '@gitroom/nestjs-libraries/database/prisma/admin-stats/admin-stats.repository';
|
| 6 |
+
|
| 7 |
+
@Injectable()
|
| 8 |
+
export class AdminStatsService {
|
| 9 |
+
constructor(private _adminStatsRepository: AdminStatsRepository) {}
|
| 10 |
+
|
| 11 |
+
getStats(params: StatsParams) {
|
| 12 |
+
return this._adminStatsRepository.getStats(params);
|
| 13 |
+
}
|
| 14 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/agencies/agencies.repository.ts
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';
|
| 2 |
+
import { Injectable } from '@nestjs/common';
|
| 3 |
+
import { User } from '@prisma/client';
|
| 4 |
+
import { CreateAgencyDto } from '@gitroom/nestjs-libraries/dtos/agencies/create.agency.dto';
|
| 5 |
+
|
| 6 |
+
@Injectable()
|
| 7 |
+
export class AgenciesRepository {
|
| 8 |
+
constructor(
|
| 9 |
+
private _socialMediaAgencies: PrismaRepository<'socialMediaAgency'>,
|
| 10 |
+
private _socialMediaAgenciesNiche: PrismaRepository<'socialMediaAgencyNiche'>
|
| 11 |
+
) {}
|
| 12 |
+
|
| 13 |
+
getAllAgencies() {
|
| 14 |
+
return this._socialMediaAgencies.model.socialMediaAgency.findMany({
|
| 15 |
+
where: {
|
| 16 |
+
deletedAt: null,
|
| 17 |
+
approved: true,
|
| 18 |
+
},
|
| 19 |
+
include: {
|
| 20 |
+
logo: true,
|
| 21 |
+
niches: true,
|
| 22 |
+
},
|
| 23 |
+
orderBy: {
|
| 24 |
+
createdAt: 'desc',
|
| 25 |
+
},
|
| 26 |
+
});
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
getCount() {
|
| 30 |
+
return this._socialMediaAgencies.model.socialMediaAgency.count({
|
| 31 |
+
where: {
|
| 32 |
+
deletedAt: null,
|
| 33 |
+
approved: true,
|
| 34 |
+
},
|
| 35 |
+
});
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
getAllAgenciesSlug() {
|
| 39 |
+
return this._socialMediaAgencies.model.socialMediaAgency.findMany({
|
| 40 |
+
where: {
|
| 41 |
+
deletedAt: null,
|
| 42 |
+
approved: true,
|
| 43 |
+
},
|
| 44 |
+
select: {
|
| 45 |
+
slug: true,
|
| 46 |
+
},
|
| 47 |
+
});
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
approveOrDecline(action: string, id: string) {
|
| 51 |
+
return this._socialMediaAgencies.model.socialMediaAgency.update({
|
| 52 |
+
where: {
|
| 53 |
+
id,
|
| 54 |
+
},
|
| 55 |
+
data: {
|
| 56 |
+
approved: action === 'approve',
|
| 57 |
+
},
|
| 58 |
+
});
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
getAgencyById(id: string) {
|
| 62 |
+
return this._socialMediaAgencies.model.socialMediaAgency.findFirst({
|
| 63 |
+
where: {
|
| 64 |
+
id,
|
| 65 |
+
deletedAt: null,
|
| 66 |
+
approved: true,
|
| 67 |
+
},
|
| 68 |
+
include: {
|
| 69 |
+
logo: true,
|
| 70 |
+
niches: true,
|
| 71 |
+
user: true,
|
| 72 |
+
},
|
| 73 |
+
});
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
getAgencyInformation(agency: string) {
|
| 77 |
+
return this._socialMediaAgencies.model.socialMediaAgency.findFirst({
|
| 78 |
+
where: {
|
| 79 |
+
slug: agency,
|
| 80 |
+
deletedAt: null,
|
| 81 |
+
approved: true,
|
| 82 |
+
},
|
| 83 |
+
include: {
|
| 84 |
+
logo: true,
|
| 85 |
+
niches: true,
|
| 86 |
+
},
|
| 87 |
+
});
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
getAgencyByUser(user: User) {
|
| 91 |
+
return this._socialMediaAgencies.model.socialMediaAgency.findFirst({
|
| 92 |
+
where: {
|
| 93 |
+
userId: user.id,
|
| 94 |
+
deletedAt: null,
|
| 95 |
+
},
|
| 96 |
+
include: {
|
| 97 |
+
logo: true,
|
| 98 |
+
niches: true,
|
| 99 |
+
},
|
| 100 |
+
});
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
async createAgency(user: User, body: CreateAgencyDto) {
|
| 104 |
+
const insertAgency =
|
| 105 |
+
await this._socialMediaAgencies.model.socialMediaAgency.upsert({
|
| 106 |
+
where: {
|
| 107 |
+
userId: user.id,
|
| 108 |
+
},
|
| 109 |
+
update: {
|
| 110 |
+
userId: user.id,
|
| 111 |
+
name: body.name,
|
| 112 |
+
website: body.website,
|
| 113 |
+
facebook: body.facebook,
|
| 114 |
+
instagram: body.instagram,
|
| 115 |
+
twitter: body.twitter,
|
| 116 |
+
linkedIn: body.linkedIn,
|
| 117 |
+
youtube: body.youtube,
|
| 118 |
+
tiktok: body.tiktok,
|
| 119 |
+
logoId: body.logo.id,
|
| 120 |
+
shortDescription: body.shortDescription,
|
| 121 |
+
description: body.description,
|
| 122 |
+
approved: false,
|
| 123 |
+
},
|
| 124 |
+
create: {
|
| 125 |
+
userId: user.id,
|
| 126 |
+
name: body.name,
|
| 127 |
+
website: body.website,
|
| 128 |
+
facebook: body.facebook,
|
| 129 |
+
instagram: body.instagram,
|
| 130 |
+
twitter: body.twitter,
|
| 131 |
+
linkedIn: body.linkedIn,
|
| 132 |
+
youtube: body.youtube,
|
| 133 |
+
tiktok: body.tiktok,
|
| 134 |
+
logoId: body.logo.id,
|
| 135 |
+
shortDescription: body.shortDescription,
|
| 136 |
+
description: body.description,
|
| 137 |
+
slug: body.name.toLowerCase().replace(/ /g, '-'),
|
| 138 |
+
approved: false,
|
| 139 |
+
},
|
| 140 |
+
select: {
|
| 141 |
+
id: true,
|
| 142 |
+
},
|
| 143 |
+
});
|
| 144 |
+
|
| 145 |
+
await this._socialMediaAgenciesNiche.model.socialMediaAgencyNiche.deleteMany(
|
| 146 |
+
{
|
| 147 |
+
where: {
|
| 148 |
+
agencyId: insertAgency.id,
|
| 149 |
+
niche: {
|
| 150 |
+
notIn: body.niches,
|
| 151 |
+
},
|
| 152 |
+
},
|
| 153 |
+
}
|
| 154 |
+
);
|
| 155 |
+
|
| 156 |
+
const currentNiche =
|
| 157 |
+
await this._socialMediaAgenciesNiche.model.socialMediaAgencyNiche.findMany(
|
| 158 |
+
{
|
| 159 |
+
where: {
|
| 160 |
+
agencyId: insertAgency.id,
|
| 161 |
+
},
|
| 162 |
+
select: {
|
| 163 |
+
niche: true,
|
| 164 |
+
},
|
| 165 |
+
}
|
| 166 |
+
);
|
| 167 |
+
|
| 168 |
+
const addNewNiche = body.niches.filter(
|
| 169 |
+
(n) => !currentNiche.some((c) => c.niche === n)
|
| 170 |
+
);
|
| 171 |
+
|
| 172 |
+
await this._socialMediaAgenciesNiche.model.socialMediaAgencyNiche.createMany(
|
| 173 |
+
{
|
| 174 |
+
data: addNewNiche.map((n) => ({
|
| 175 |
+
agencyId: insertAgency.id,
|
| 176 |
+
niche: n,
|
| 177 |
+
})),
|
| 178 |
+
}
|
| 179 |
+
);
|
| 180 |
+
|
| 181 |
+
return insertAgency;
|
| 182 |
+
}
|
| 183 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/agencies/agencies.service.ts
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Injectable } from '@nestjs/common';
|
| 2 |
+
import { AgenciesRepository } from '@gitroom/nestjs-libraries/database/prisma/agencies/agencies.repository';
|
| 3 |
+
import { User } from '@prisma/client';
|
| 4 |
+
import { CreateAgencyDto } from '@gitroom/nestjs-libraries/dtos/agencies/create.agency.dto';
|
| 5 |
+
import { NotificationService } from '@gitroom/nestjs-libraries/database/prisma/notifications/notification.service';
|
| 6 |
+
|
| 7 |
+
@Injectable()
|
| 8 |
+
export class AgenciesService {
|
| 9 |
+
constructor(
|
| 10 |
+
private _agenciesRepository: AgenciesRepository,
|
| 11 |
+
private _notificationService: NotificationService
|
| 12 |
+
) {}
|
| 13 |
+
getAgencyByUser(user: User) {
|
| 14 |
+
return this._agenciesRepository.getAgencyByUser(user);
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
getCount() {
|
| 18 |
+
return this._agenciesRepository.getCount();
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
getAllAgencies() {
|
| 22 |
+
return this._agenciesRepository.getAllAgencies();
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
getAllAgenciesSlug() {
|
| 26 |
+
return this._agenciesRepository.getAllAgenciesSlug();
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
getAgencyInformation(agency: string) {
|
| 30 |
+
return this._agenciesRepository.getAgencyInformation(agency);
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
async approveOrDecline(email: string, action: string, id: string) {
|
| 34 |
+
await this._agenciesRepository.approveOrDecline(action, id);
|
| 35 |
+
const agency = await this._agenciesRepository.getAgencyById(id);
|
| 36 |
+
|
| 37 |
+
if (action === 'approve') {
|
| 38 |
+
await this._notificationService.sendEmail(
|
| 39 |
+
agency?.user?.email!,
|
| 40 |
+
'Your Agency has been approved and added to Postiz 🚀',
|
| 41 |
+
`
|
| 42 |
+
<html lang="en">
|
| 43 |
+
|
| 44 |
+
<head>
|
| 45 |
+
<meta charset="UTF-8">
|
| 46 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 47 |
+
<title>Your Agency has been approved and added to Postiz 🚀</title>
|
| 48 |
+
</head>
|
| 49 |
+
|
| 50 |
+
<body style="font-family: Arial, sans-serif; margin: 0; padding: 0;">
|
| 51 |
+
Hi there, <br /><br />
|
| 52 |
+
Your agency ${agency?.name} has been added to Postiz!<br />
|
| 53 |
+
You can <a href="https://postiz.com/agencies/${agency?.slug}">check it here</a><br />
|
| 54 |
+
It will appear on the main agency of Postiz in the next 24 hours.<br /><br />
|
| 55 |
+
</body>
|
| 56 |
+
</html>`
|
| 57 |
+
);
|
| 58 |
+
|
| 59 |
+
return;
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
await this._notificationService.sendEmail(
|
| 63 |
+
agency?.user?.email!,
|
| 64 |
+
'Your Agency has been declined 😔',
|
| 65 |
+
`
|
| 66 |
+
<html lang="en">
|
| 67 |
+
|
| 68 |
+
<head>
|
| 69 |
+
<meta charset="UTF-8">
|
| 70 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 71 |
+
<title>Your Agency has been declined</title>
|
| 72 |
+
</head>
|
| 73 |
+
|
| 74 |
+
<body style="font-family: Arial, sans-serif; margin: 0; padding: 0;">
|
| 75 |
+
Hi there, <br /><br />
|
| 76 |
+
Your agency ${agency?.name} has been declined to Postiz!<br />
|
| 77 |
+
If you think we have made a mistake, please reply to this email and let us know
|
| 78 |
+
</body>
|
| 79 |
+
</html>`
|
| 80 |
+
);
|
| 81 |
+
|
| 82 |
+
return;
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
async createAgency(user: User, body: CreateAgencyDto) {
|
| 86 |
+
const agency = await this._agenciesRepository.createAgency(user, body);
|
| 87 |
+
await this._notificationService.sendEmail(
|
| 88 |
+
'nevo@postiz.com',
|
| 89 |
+
'New agency created',
|
| 90 |
+
`
|
| 91 |
+
<html lang="en">
|
| 92 |
+
|
| 93 |
+
<head>
|
| 94 |
+
<meta charset="UTF-8">
|
| 95 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 96 |
+
<title>Email Template</title>
|
| 97 |
+
</head>
|
| 98 |
+
|
| 99 |
+
<body style="font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f4f4f4;">
|
| 100 |
+
<table align="center" cellpadding="0" cellspacing="0" style="max-width: 600px; width: 100%; background-color: #ffffff; margin-top: 20px; border: 1px solid #ddd;">
|
| 101 |
+
<tr>
|
| 102 |
+
<td style="padding: 0 20px 20px 20px; text-align: center;">
|
| 103 |
+
<!-- Website -->
|
| 104 |
+
<a href="${
|
| 105 |
+
body.website
|
| 106 |
+
}" style="text-decoration: none; color: #007bff;">${
|
| 107 |
+
body.website
|
| 108 |
+
}</a>
|
| 109 |
+
</td>
|
| 110 |
+
</tr>
|
| 111 |
+
<tr>
|
| 112 |
+
<td style="padding: 20px; text-align: center;">
|
| 113 |
+
<!-- Social Media Links -->
|
| 114 |
+
<p style="margin: 10px 0; font-size: 16px;">
|
| 115 |
+
Social Medias:
|
| 116 |
+
<a href="${
|
| 117 |
+
body.facebook
|
| 118 |
+
}" style="margin: 0 10px; text-decoration: none; color: #007bff;">${
|
| 119 |
+
body.facebook
|
| 120 |
+
}</a><br />
|
| 121 |
+
<a href="${
|
| 122 |
+
body.instagram
|
| 123 |
+
}" style="margin: 0 10px; text-decoration: none; color: #007bff;">${
|
| 124 |
+
body.instagram
|
| 125 |
+
}</a><br />
|
| 126 |
+
<a href="${
|
| 127 |
+
body.twitter
|
| 128 |
+
}" style="margin: 0 10px; text-decoration: none; color: #007bff;">${
|
| 129 |
+
body.twitter
|
| 130 |
+
}</a><br />
|
| 131 |
+
<a href="${
|
| 132 |
+
body.linkedIn
|
| 133 |
+
}" style="margin: 0 10px; text-decoration: none; color: #007bff;">${
|
| 134 |
+
body.linkedIn
|
| 135 |
+
}</a><br />
|
| 136 |
+
<a href="${
|
| 137 |
+
body.youtube
|
| 138 |
+
}" style="margin: 0 10px; text-decoration: none; color: #007bff;">${
|
| 139 |
+
body.youtube
|
| 140 |
+
}</a><br />
|
| 141 |
+
<a href="${
|
| 142 |
+
body.tiktok
|
| 143 |
+
}" style="margin: 0 10px; text-decoration: none; color: #007bff;">${
|
| 144 |
+
body.tiktok
|
| 145 |
+
}</a>
|
| 146 |
+
</p>
|
| 147 |
+
</td>
|
| 148 |
+
</tr>
|
| 149 |
+
<tr>
|
| 150 |
+
<td style="padding: 20px;">
|
| 151 |
+
<!-- Short Description -->
|
| 152 |
+
<h2 style="text-align: center; color: #333;">Logo</h2>
|
| 153 |
+
<p style="text-align: center; color: #555; font-size: 16px;">
|
| 154 |
+
<img src="${body.logo.path}" width="60" height="60" />
|
| 155 |
+
</p>
|
| 156 |
+
</td>
|
| 157 |
+
</tr>
|
| 158 |
+
<tr>
|
| 159 |
+
<td style="padding: 20px;">
|
| 160 |
+
<!-- Short Description -->
|
| 161 |
+
<h2 style="text-align: center; color: #333;">Name</h2>
|
| 162 |
+
<p style="text-align: center; color: #555; font-size: 16px;">${
|
| 163 |
+
body.name
|
| 164 |
+
}</p>
|
| 165 |
+
</td>
|
| 166 |
+
</tr>
|
| 167 |
+
<tr>
|
| 168 |
+
<td style="padding: 20px;">
|
| 169 |
+
<!-- Short Description -->
|
| 170 |
+
<h2 style="text-align: center; color: #333;">Short Description</h2>
|
| 171 |
+
<p style="text-align: center; color: #555; font-size: 16px;">${
|
| 172 |
+
body.shortDescription
|
| 173 |
+
}</p>
|
| 174 |
+
</td>
|
| 175 |
+
</tr>
|
| 176 |
+
<tr>
|
| 177 |
+
<td style="padding: 20px;">
|
| 178 |
+
<!-- Description -->
|
| 179 |
+
<h2 style="text-align: center; color: #333;">Description</h2>
|
| 180 |
+
<p style="text-align: center; color: #555; font-size: 16px;">${
|
| 181 |
+
body.description
|
| 182 |
+
}</p>
|
| 183 |
+
</td>
|
| 184 |
+
</tr>
|
| 185 |
+
<tr>
|
| 186 |
+
<td style="padding: 20px;">
|
| 187 |
+
<!-- Niches -->
|
| 188 |
+
<h2 style="text-align: center; color: #333;">Niches</h2>
|
| 189 |
+
<p style="text-align: center; color: #555; font-size: 16px;">${body.niches.join(
|
| 190 |
+
','
|
| 191 |
+
)}</p>
|
| 192 |
+
</td>
|
| 193 |
+
</tr>
|
| 194 |
+
<tr>
|
| 195 |
+
<td style="padding: 20px; text-align: center; background-color: #000;">
|
| 196 |
+
<a href="https://postiz.com/agencies/action/approve/${
|
| 197 |
+
agency.id
|
| 198 |
+
}" style="margin: 0 10px; text-decoration: none; color: #007bff;">To approve click here</a><br /><br /><br />
|
| 199 |
+
<a href="https://postiz.com/agencies/action/decline/${
|
| 200 |
+
agency.id
|
| 201 |
+
}" style="margin: 0 10px; text-decoration: none; color: #007bff;">To decline click here</a><br /><br /><br />
|
| 202 |
+
</td>
|
| 203 |
+
</tr>
|
| 204 |
+
<tr>
|
| 205 |
+
<td style="padding: 20px; text-align: center; background-color: #f4f4f4;">
|
| 206 |
+
<p style="color: #777; font-size: 14px;">© 2024 Your Gitroom Limited All rights reserved.</p>
|
| 207 |
+
</td>
|
| 208 |
+
</tr>
|
| 209 |
+
</table>
|
| 210 |
+
</body>
|
| 211 |
+
|
| 212 |
+
</html>
|
| 213 |
+
`
|
| 214 |
+
);
|
| 215 |
+
return agency;
|
| 216 |
+
}
|
| 217 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/announcements/announcements.repository.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';
|
| 2 |
+
import { Injectable } from '@nestjs/common';
|
| 3 |
+
import { AnnouncementDto } from '@gitroom/nestjs-libraries/dtos/announcements/announcements.dto';
|
| 4 |
+
import { AnnouncementColor } from '@prisma/client';
|
| 5 |
+
|
| 6 |
+
@Injectable()
|
| 7 |
+
export class AnnouncementsRepository {
|
| 8 |
+
constructor(private _announcements: PrismaRepository<'announcement'>) {}
|
| 9 |
+
|
| 10 |
+
getAnnouncements() {
|
| 11 |
+
return this._announcements.model.announcement.findMany({
|
| 12 |
+
orderBy: {
|
| 13 |
+
createdAt: 'desc',
|
| 14 |
+
},
|
| 15 |
+
});
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
createAnnouncement(body: AnnouncementDto) {
|
| 19 |
+
return this._announcements.model.announcement.create({
|
| 20 |
+
data: {
|
| 21 |
+
title: body.title,
|
| 22 |
+
description: body.description,
|
| 23 |
+
color: (body.color as AnnouncementColor) || AnnouncementColor.INFO,
|
| 24 |
+
},
|
| 25 |
+
});
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
deleteAnnouncement(id: string) {
|
| 29 |
+
return this._announcements.model.announcement.delete({
|
| 30 |
+
where: {
|
| 31 |
+
id,
|
| 32 |
+
},
|
| 33 |
+
});
|
| 34 |
+
}
|
| 35 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/announcements/announcements.service.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Injectable } from '@nestjs/common';
|
| 2 |
+
import { AnnouncementsRepository } from '@gitroom/nestjs-libraries/database/prisma/announcements/announcements.repository';
|
| 3 |
+
import { AnnouncementDto } from '@gitroom/nestjs-libraries/dtos/announcements/announcements.dto';
|
| 4 |
+
|
| 5 |
+
@Injectable()
|
| 6 |
+
export class AnnouncementsService {
|
| 7 |
+
constructor(private _announcementsRepository: AnnouncementsRepository) {}
|
| 8 |
+
|
| 9 |
+
getAnnouncements() {
|
| 10 |
+
return this._announcementsRepository.getAnnouncements();
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
createAnnouncement(body: AnnouncementDto) {
|
| 14 |
+
return this._announcementsRepository.createAnnouncement(body);
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
deleteAnnouncement(id: string) {
|
| 18 |
+
return this._announcementsRepository.deleteAnnouncement(id);
|
| 19 |
+
}
|
| 20 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/autopost/autopost.repository.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';
|
| 2 |
+
import { Injectable } from '@nestjs/common';
|
| 3 |
+
import { v4 as uuidv4 } from 'uuid';
|
| 4 |
+
import { AutopostDto } from '@gitroom/nestjs-libraries/dtos/autopost/autopost.dto';
|
| 5 |
+
|
| 6 |
+
@Injectable()
|
| 7 |
+
export class AutopostRepository {
|
| 8 |
+
constructor(private _autoPost: PrismaRepository<'autoPost'>) {}
|
| 9 |
+
|
| 10 |
+
getTotal(orgId: string) {
|
| 11 |
+
return this._autoPost.model.autoPost.count({
|
| 12 |
+
where: {
|
| 13 |
+
organizationId: orgId,
|
| 14 |
+
deletedAt: null,
|
| 15 |
+
},
|
| 16 |
+
});
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
getAutoposts(orgId: string) {
|
| 20 |
+
return this._autoPost.model.autoPost.findMany({
|
| 21 |
+
where: {
|
| 22 |
+
organizationId: orgId,
|
| 23 |
+
deletedAt: null,
|
| 24 |
+
},
|
| 25 |
+
});
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
deleteAutopost(orgId: string, id: string) {
|
| 29 |
+
return this._autoPost.model.autoPost.update({
|
| 30 |
+
where: {
|
| 31 |
+
id,
|
| 32 |
+
organizationId: orgId,
|
| 33 |
+
},
|
| 34 |
+
data: {
|
| 35 |
+
deletedAt: new Date(),
|
| 36 |
+
},
|
| 37 |
+
});
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
getAutopost(id: string) {
|
| 41 |
+
return this._autoPost.model.autoPost.findUnique({
|
| 42 |
+
where: {
|
| 43 |
+
id,
|
| 44 |
+
deletedAt: null,
|
| 45 |
+
},
|
| 46 |
+
});
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
updateUrl(id: string, url: string) {
|
| 50 |
+
return this._autoPost.model.autoPost.update({
|
| 51 |
+
where: {
|
| 52 |
+
id,
|
| 53 |
+
},
|
| 54 |
+
data: {
|
| 55 |
+
lastUrl: url,
|
| 56 |
+
},
|
| 57 |
+
});
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
changeActive(orgId: string, id: string, active: boolean) {
|
| 61 |
+
return this._autoPost.model.autoPost.update({
|
| 62 |
+
where: {
|
| 63 |
+
id,
|
| 64 |
+
organizationId: orgId,
|
| 65 |
+
},
|
| 66 |
+
data: {
|
| 67 |
+
active,
|
| 68 |
+
},
|
| 69 |
+
});
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
async createAutopost(orgId: string, body: AutopostDto, id?: string) {
|
| 73 |
+
const { id: newId, active } = await this._autoPost.model.autoPost.upsert({
|
| 74 |
+
where: {
|
| 75 |
+
id: id || uuidv4(),
|
| 76 |
+
organizationId: orgId,
|
| 77 |
+
},
|
| 78 |
+
create: {
|
| 79 |
+
organizationId: orgId,
|
| 80 |
+
url: body.url,
|
| 81 |
+
title: body.title,
|
| 82 |
+
integrations: JSON.stringify(body.integrations),
|
| 83 |
+
active: body.active,
|
| 84 |
+
content: body.content,
|
| 85 |
+
generateContent: body.generateContent,
|
| 86 |
+
addPicture: body.addPicture,
|
| 87 |
+
syncLast: body.syncLast,
|
| 88 |
+
onSlot: body.onSlot,
|
| 89 |
+
lastUrl: body.lastUrl,
|
| 90 |
+
},
|
| 91 |
+
update: {
|
| 92 |
+
url: body.url,
|
| 93 |
+
title: body.title,
|
| 94 |
+
integrations: JSON.stringify(body.integrations),
|
| 95 |
+
active: body.active,
|
| 96 |
+
content: body.content,
|
| 97 |
+
generateContent: body.generateContent,
|
| 98 |
+
addPicture: body.addPicture,
|
| 99 |
+
syncLast: body.syncLast,
|
| 100 |
+
onSlot: body.onSlot,
|
| 101 |
+
lastUrl: body.lastUrl,
|
| 102 |
+
},
|
| 103 |
+
});
|
| 104 |
+
|
| 105 |
+
return { id: newId, active };
|
| 106 |
+
}
|
| 107 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/autopost/autopost.service.ts
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Injectable } from '@nestjs/common';
|
| 2 |
+
import { AutopostRepository } from '@gitroom/nestjs-libraries/database/prisma/autopost/autopost.repository';
|
| 3 |
+
import { AutopostDto } from '@gitroom/nestjs-libraries/dtos/autopost/autopost.dto';
|
| 4 |
+
import dayjs from 'dayjs';
|
| 5 |
+
import { END, START, StateGraph } from '@langchain/langgraph';
|
| 6 |
+
import { AutoPost, Integration } from '@prisma/client';
|
| 7 |
+
import { BaseMessage } from '@langchain/core/messages';
|
| 8 |
+
import striptags from 'striptags';
|
| 9 |
+
import { ChatOpenAI, DallEAPIWrapper } from '@langchain/openai';
|
| 10 |
+
import { JSDOM } from 'jsdom';
|
| 11 |
+
import { z } from 'zod';
|
| 12 |
+
import { ChatPromptTemplate } from '@langchain/core/prompts';
|
| 13 |
+
import { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.service';
|
| 14 |
+
import Parser from 'rss-parser';
|
| 15 |
+
import { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';
|
| 16 |
+
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
|
| 17 |
+
import { TemporalService } from 'nestjs-temporal-core';
|
| 18 |
+
import { TypedSearchAttributes } from '@temporalio/common';
|
| 19 |
+
import {
|
| 20 |
+
organizationId,
|
| 21 |
+
} from '@gitroom/nestjs-libraries/temporal/temporal.search.attribute';
|
| 22 |
+
const parser = new Parser();
|
| 23 |
+
|
| 24 |
+
interface WorkflowChannelsState {
|
| 25 |
+
messages: BaseMessage[];
|
| 26 |
+
integrations: Integration[];
|
| 27 |
+
body: AutoPost;
|
| 28 |
+
description: string;
|
| 29 |
+
image: string;
|
| 30 |
+
id: string;
|
| 31 |
+
load: {
|
| 32 |
+
date: string;
|
| 33 |
+
url: string;
|
| 34 |
+
description: string;
|
| 35 |
+
};
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
const model = new ChatOpenAI({
|
| 39 |
+
apiKey: process.env.OPENAI_API_KEY || 'sk-proj-',
|
| 40 |
+
model: 'gpt-4.1',
|
| 41 |
+
temperature: 0.7,
|
| 42 |
+
});
|
| 43 |
+
|
| 44 |
+
const dalle = new DallEAPIWrapper({
|
| 45 |
+
apiKey: process.env.OPENAI_API_KEY || 'sk-proj-',
|
| 46 |
+
model: 'chatgpt-image-latest',
|
| 47 |
+
});
|
| 48 |
+
|
| 49 |
+
const generateContent = z.object({
|
| 50 |
+
socialMediaPostContent: z
|
| 51 |
+
.string()
|
| 52 |
+
.describe('Content for social media posts max 120 chars'),
|
| 53 |
+
});
|
| 54 |
+
|
| 55 |
+
const dallePrompt = z.object({
|
| 56 |
+
generatedTextToBeSentToDallE: z
|
| 57 |
+
.string()
|
| 58 |
+
.describe('Generated prompt from description to be sent to DallE'),
|
| 59 |
+
});
|
| 60 |
+
|
| 61 |
+
@Injectable()
|
| 62 |
+
export class AutopostService {
|
| 63 |
+
constructor(
|
| 64 |
+
private _autopostsRepository: AutopostRepository,
|
| 65 |
+
private _temporalService: TemporalService,
|
| 66 |
+
private _integrationService: IntegrationService,
|
| 67 |
+
private _postsService: PostsService
|
| 68 |
+
) {}
|
| 69 |
+
|
| 70 |
+
async stopAll(org: string) {
|
| 71 |
+
const getAll = (await this.getAutoposts(org)).filter((f) => f.active);
|
| 72 |
+
for (const autopost of getAll) {
|
| 73 |
+
await this.changeActive(org, autopost.id, false);
|
| 74 |
+
}
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
getAutoposts(orgId: string) {
|
| 78 |
+
return this._autopostsRepository.getAutoposts(orgId);
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
async createAutopost(orgId: string, body: AutopostDto, id?: string) {
|
| 82 |
+
const data = await this._autopostsRepository.createAutopost(
|
| 83 |
+
orgId,
|
| 84 |
+
body,
|
| 85 |
+
id
|
| 86 |
+
);
|
| 87 |
+
|
| 88 |
+
await this.processCron(body.active, orgId, data.id);
|
| 89 |
+
|
| 90 |
+
return data;
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
async changeActive(orgId: string, id: string, active: boolean) {
|
| 94 |
+
const data = await this._autopostsRepository.changeActive(
|
| 95 |
+
orgId,
|
| 96 |
+
id,
|
| 97 |
+
active
|
| 98 |
+
);
|
| 99 |
+
await this.processCron(active, orgId, id);
|
| 100 |
+
return data;
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
async processCron(active: boolean, orgId: string, id: string) {
|
| 104 |
+
if (active) {
|
| 105 |
+
try {
|
| 106 |
+
return this._temporalService.client
|
| 107 |
+
.getRawClient()
|
| 108 |
+
?.workflow.start('autoPostWorkflow', {
|
| 109 |
+
workflowId: `autopost-${id}`,
|
| 110 |
+
taskQueue: 'main',
|
| 111 |
+
args: [{ id, immediately: true }],
|
| 112 |
+
typedSearchAttributes: new TypedSearchAttributes([
|
| 113 |
+
{
|
| 114 |
+
key: organizationId,
|
| 115 |
+
value: orgId,
|
| 116 |
+
},
|
| 117 |
+
]),
|
| 118 |
+
});
|
| 119 |
+
} catch (err) {}
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
try {
|
| 123 |
+
return await this._temporalService.terminateWorkflow(`autopost-${id}`);
|
| 124 |
+
} catch (err) {
|
| 125 |
+
return false;
|
| 126 |
+
}
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
async deleteAutopost(orgId: string, id: string) {
|
| 130 |
+
const data = await this._autopostsRepository.deleteAutopost(orgId, id);
|
| 131 |
+
await this.processCron(false, orgId, id);
|
| 132 |
+
return data;
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
async loadXML(url: string) {
|
| 136 |
+
try {
|
| 137 |
+
const { items } = await parser.parseURL(url);
|
| 138 |
+
const findLast = items.reduce(
|
| 139 |
+
(all: any, current: any) => {
|
| 140 |
+
if (dayjs(current.pubDate).isAfter(all.pubDate)) {
|
| 141 |
+
return current;
|
| 142 |
+
}
|
| 143 |
+
return all;
|
| 144 |
+
},
|
| 145 |
+
{ pubDate: dayjs().subtract(100, 'years') }
|
| 146 |
+
);
|
| 147 |
+
|
| 148 |
+
return {
|
| 149 |
+
success: true,
|
| 150 |
+
date: findLast.pubDate,
|
| 151 |
+
url: findLast.link,
|
| 152 |
+
description: striptags(
|
| 153 |
+
findLast?.['content:encoded'] ||
|
| 154 |
+
findLast?.content ||
|
| 155 |
+
findLast?.description ||
|
| 156 |
+
''
|
| 157 |
+
)
|
| 158 |
+
.replace(/\n/g, ' ')
|
| 159 |
+
.trim(),
|
| 160 |
+
};
|
| 161 |
+
} catch (err) {
|
| 162 |
+
/** sent **/
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
return { success: false };
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
static state = () =>
|
| 169 |
+
new StateGraph<WorkflowChannelsState>({
|
| 170 |
+
channels: {
|
| 171 |
+
messages: {
|
| 172 |
+
reducer: (currentState, updateValue) =>
|
| 173 |
+
currentState.concat(updateValue),
|
| 174 |
+
default: () => [],
|
| 175 |
+
},
|
| 176 |
+
body: null,
|
| 177 |
+
description: null,
|
| 178 |
+
load: null,
|
| 179 |
+
image: null,
|
| 180 |
+
integrations: null,
|
| 181 |
+
id: null,
|
| 182 |
+
},
|
| 183 |
+
});
|
| 184 |
+
|
| 185 |
+
async loadUrl(url: string) {
|
| 186 |
+
try {
|
| 187 |
+
const loadDom = new JSDOM(await (await fetch(url)).text());
|
| 188 |
+
loadDom.window.document
|
| 189 |
+
.querySelectorAll('script')
|
| 190 |
+
.forEach((s) => s.remove());
|
| 191 |
+
loadDom.window.document
|
| 192 |
+
.querySelectorAll('style')
|
| 193 |
+
.forEach((s) => s.remove());
|
| 194 |
+
// remove all html, script and styles
|
| 195 |
+
return striptags(loadDom.window.document.body.innerHTML);
|
| 196 |
+
} catch (err) {
|
| 197 |
+
return '';
|
| 198 |
+
}
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
async generateDescription(state: WorkflowChannelsState) {
|
| 202 |
+
if (!state.body.generateContent) {
|
| 203 |
+
return {
|
| 204 |
+
...state,
|
| 205 |
+
description: state.body.content,
|
| 206 |
+
};
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
const description =
|
| 210 |
+
state.load.description || (await this.loadUrl(state.load.url));
|
| 211 |
+
if (!description) {
|
| 212 |
+
return {
|
| 213 |
+
...state,
|
| 214 |
+
description: '',
|
| 215 |
+
};
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
const structuredOutput = model.withStructuredOutput(generateContent);
|
| 219 |
+
const { socialMediaPostContent } = await ChatPromptTemplate.fromTemplate(
|
| 220 |
+
`
|
| 221 |
+
You are an assistant that gets raw 'description' of a content and generate a social media post content.
|
| 222 |
+
Rules:
|
| 223 |
+
- Maximum 100 chars
|
| 224 |
+
- Try to make it a short as possible to fit any social media
|
| 225 |
+
- Add line breaks between sentences (\\n)
|
| 226 |
+
- Don't add hashtags
|
| 227 |
+
- Add emojis when needed
|
| 228 |
+
|
| 229 |
+
'description':
|
| 230 |
+
{content}
|
| 231 |
+
`
|
| 232 |
+
)
|
| 233 |
+
.pipe(structuredOutput)
|
| 234 |
+
.invoke({
|
| 235 |
+
content: description,
|
| 236 |
+
});
|
| 237 |
+
|
| 238 |
+
return {
|
| 239 |
+
...state,
|
| 240 |
+
description: socialMediaPostContent,
|
| 241 |
+
};
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
async generatePicture(state: WorkflowChannelsState) {
|
| 245 |
+
const structuredOutput = model.withStructuredOutput(dallePrompt);
|
| 246 |
+
const { generatedTextToBeSentToDallE } =
|
| 247 |
+
await ChatPromptTemplate.fromTemplate(
|
| 248 |
+
`
|
| 249 |
+
You are an assistant that gets description and generate a prompt that will be sent to DallE to generate pictures.
|
| 250 |
+
|
| 251 |
+
content:
|
| 252 |
+
{content}
|
| 253 |
+
`
|
| 254 |
+
)
|
| 255 |
+
.pipe(structuredOutput)
|
| 256 |
+
.invoke({
|
| 257 |
+
content: state.load.description || state.description,
|
| 258 |
+
});
|
| 259 |
+
|
| 260 |
+
const image = await dalle.invoke(generatedTextToBeSentToDallE);
|
| 261 |
+
|
| 262 |
+
return { ...state, image };
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
async schedulePost(state: WorkflowChannelsState) {
|
| 266 |
+
const nextTime = await this._postsService.findFreeDateTime(
|
| 267 |
+
state.integrations[0].organizationId
|
| 268 |
+
);
|
| 269 |
+
|
| 270 |
+
await this._postsService.createPost(state.integrations[0].organizationId, {
|
| 271 |
+
date: nextTime + 'Z',
|
| 272 |
+
order: makeId(10),
|
| 273 |
+
shortLink: false,
|
| 274 |
+
type: 'draft',
|
| 275 |
+
tags: [],
|
| 276 |
+
posts: state.integrations.map((i) => ({
|
| 277 |
+
settings: {
|
| 278 |
+
__type: i.providerIdentifier as any,
|
| 279 |
+
title: '',
|
| 280 |
+
tags: [],
|
| 281 |
+
subreddit: [],
|
| 282 |
+
},
|
| 283 |
+
group: makeId(10),
|
| 284 |
+
integration: { id: i.id },
|
| 285 |
+
value: [
|
| 286 |
+
{
|
| 287 |
+
id: makeId(10),
|
| 288 |
+
delay: 0,
|
| 289 |
+
content:
|
| 290 |
+
state.description.replace(/\n/g, '\n\n') +
|
| 291 |
+
'\n\n' +
|
| 292 |
+
state.load.url,
|
| 293 |
+
image: !state.image
|
| 294 |
+
? []
|
| 295 |
+
: [
|
| 296 |
+
{
|
| 297 |
+
id: makeId(10),
|
| 298 |
+
name: makeId(10),
|
| 299 |
+
path: state.image,
|
| 300 |
+
organizationId: state.integrations[0].organizationId,
|
| 301 |
+
},
|
| 302 |
+
],
|
| 303 |
+
},
|
| 304 |
+
],
|
| 305 |
+
})),
|
| 306 |
+
}, 'AUTOPOST');
|
| 307 |
+
}
|
| 308 |
+
|
| 309 |
+
async updateUrl(state: WorkflowChannelsState) {
|
| 310 |
+
await this._autopostsRepository.updateUrl(state.id, state.load.url);
|
| 311 |
+
}
|
| 312 |
+
|
| 313 |
+
async startAutopost(id: string) {
|
| 314 |
+
const getPost = await this._autopostsRepository.getAutopost(id);
|
| 315 |
+
if (!getPost || !getPost.active) {
|
| 316 |
+
return;
|
| 317 |
+
}
|
| 318 |
+
|
| 319 |
+
const load = await this.loadXML(getPost.url);
|
| 320 |
+
if (!load.success || load.url === getPost.lastUrl) {
|
| 321 |
+
return;
|
| 322 |
+
}
|
| 323 |
+
|
| 324 |
+
const integrations = await this._integrationService.getIntegrationsList(
|
| 325 |
+
getPost.organizationId
|
| 326 |
+
);
|
| 327 |
+
|
| 328 |
+
const parseIntegrations = JSON.parse(getPost.integrations || '[]') || [];
|
| 329 |
+
const neededIntegrations = integrations.filter((i) =>
|
| 330 |
+
parseIntegrations.some((ii: any) => ii.id === i.id)
|
| 331 |
+
);
|
| 332 |
+
|
| 333 |
+
const integrationsToSend =
|
| 334 |
+
parseIntegrations.length === 0 ? integrations : neededIntegrations;
|
| 335 |
+
if (integrationsToSend.length === 0) {
|
| 336 |
+
return;
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
const state = AutopostService.state();
|
| 340 |
+
const workflow = state
|
| 341 |
+
.addNode('generate-description', this.generateDescription.bind(this))
|
| 342 |
+
.addNode('generate-picture', this.generatePicture.bind(this))
|
| 343 |
+
.addNode('schedule-post', this.schedulePost.bind(this))
|
| 344 |
+
.addNode('update-url', this.updateUrl.bind(this))
|
| 345 |
+
.addEdge(START, 'generate-description')
|
| 346 |
+
.addConditionalEdges(
|
| 347 |
+
'generate-description',
|
| 348 |
+
(state: WorkflowChannelsState) => {
|
| 349 |
+
if (!state.description) {
|
| 350 |
+
return 'schedule-post';
|
| 351 |
+
}
|
| 352 |
+
if (state.body.addPicture) {
|
| 353 |
+
return 'generate-picture';
|
| 354 |
+
}
|
| 355 |
+
return 'schedule-post';
|
| 356 |
+
}
|
| 357 |
+
)
|
| 358 |
+
.addEdge('generate-picture', 'schedule-post')
|
| 359 |
+
.addEdge('schedule-post', 'update-url')
|
| 360 |
+
.addEdge('update-url', END);
|
| 361 |
+
|
| 362 |
+
const app = workflow.compile();
|
| 363 |
+
await app.invoke({
|
| 364 |
+
messages: [],
|
| 365 |
+
id,
|
| 366 |
+
body: getPost,
|
| 367 |
+
load,
|
| 368 |
+
integrations: integrationsToSend,
|
| 369 |
+
});
|
| 370 |
+
}
|
| 371 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/database.module.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Global, Module } from '@nestjs/common';
|
| 2 |
+
import { PrismaRepository, PrismaService, PrismaTransaction } from './prisma.service';
|
| 3 |
+
import { OrganizationRepository } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.repository';
|
| 4 |
+
import { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service';
|
| 5 |
+
import { UsersService } from '@gitroom/nestjs-libraries/database/prisma/users/users.service';
|
| 6 |
+
import { UsersRepository } from '@gitroom/nestjs-libraries/database/prisma/users/users.repository';
|
| 7 |
+
import { SubscriptionService } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.service';
|
| 8 |
+
import { SubscriptionRepository } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.repository';
|
| 9 |
+
import { NotificationService } from '@gitroom/nestjs-libraries/database/prisma/notifications/notification.service';
|
| 10 |
+
import { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';
|
| 11 |
+
import { IntegrationRepository } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.repository';
|
| 12 |
+
import { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.service';
|
| 13 |
+
import { PostsRepository } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.repository';
|
| 14 |
+
import { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integration.manager';
|
| 15 |
+
import { MediaService } from '@gitroom/nestjs-libraries/database/prisma/media/media.service';
|
| 16 |
+
import { MediaRepository } from '@gitroom/nestjs-libraries/database/prisma/media/media.repository';
|
| 17 |
+
import { NotificationsRepository } from '@gitroom/nestjs-libraries/database/prisma/notifications/notifications.repository';
|
| 18 |
+
import { EmailService } from '@gitroom/nestjs-libraries/services/email.service';
|
| 19 |
+
import { StripeService } from '@gitroom/nestjs-libraries/services/stripe.service';
|
| 20 |
+
import { ExtractContentService } from '@gitroom/nestjs-libraries/openai/extract.content.service';
|
| 21 |
+
import { OpenaiService } from '@gitroom/nestjs-libraries/openai/openai.service';
|
| 22 |
+
import { AgenciesService } from '@gitroom/nestjs-libraries/database/prisma/agencies/agencies.service';
|
| 23 |
+
import { AgenciesRepository } from '@gitroom/nestjs-libraries/database/prisma/agencies/agencies.repository';
|
| 24 |
+
import { TrackService } from '@gitroom/nestjs-libraries/track/track.service';
|
| 25 |
+
import { ShortLinkService } from '@gitroom/nestjs-libraries/short-linking/short.link.service';
|
| 26 |
+
import { WebhooksRepository } from '@gitroom/nestjs-libraries/database/prisma/webhooks/webhooks.repository';
|
| 27 |
+
import { WebhooksService } from '@gitroom/nestjs-libraries/database/prisma/webhooks/webhooks.service';
|
| 28 |
+
import { SignatureRepository } from '@gitroom/nestjs-libraries/database/prisma/signatures/signature.repository';
|
| 29 |
+
import { SignatureService } from '@gitroom/nestjs-libraries/database/prisma/signatures/signature.service';
|
| 30 |
+
import { AutopostRepository } from '@gitroom/nestjs-libraries/database/prisma/autopost/autopost.repository';
|
| 31 |
+
import { AutopostService } from '@gitroom/nestjs-libraries/database/prisma/autopost/autopost.service';
|
| 32 |
+
import { SetsService } from '@gitroom/nestjs-libraries/database/prisma/sets/sets.service';
|
| 33 |
+
import { SetsRepository } from '@gitroom/nestjs-libraries/database/prisma/sets/sets.repository';
|
| 34 |
+
import { ThirdPartyRepository } from '@gitroom/nestjs-libraries/database/prisma/third-party/third-party.repository';
|
| 35 |
+
import { ThirdPartyService } from '@gitroom/nestjs-libraries/database/prisma/third-party/third-party.service';
|
| 36 |
+
import { VideoManager } from '@gitroom/nestjs-libraries/videos/video.manager';
|
| 37 |
+
import { FalService } from '@gitroom/nestjs-libraries/openai/fal.service';
|
| 38 |
+
import { RefreshIntegrationService } from '@gitroom/nestjs-libraries/integrations/refresh.integration.service';
|
| 39 |
+
import { OAuthRepository } from '@gitroom/nestjs-libraries/database/prisma/oauth/oauth.repository';
|
| 40 |
+
import { OAuthService } from '@gitroom/nestjs-libraries/database/prisma/oauth/oauth.service';
|
| 41 |
+
import { AnnouncementsRepository } from '@gitroom/nestjs-libraries/database/prisma/announcements/announcements.repository';
|
| 42 |
+
import { AnnouncementsService } from '@gitroom/nestjs-libraries/database/prisma/announcements/announcements.service';
|
| 43 |
+
import { ErrorsRepository } from '@gitroom/nestjs-libraries/database/prisma/errors/errors.repository';
|
| 44 |
+
import { ErrorsService } from '@gitroom/nestjs-libraries/database/prisma/errors/errors.service';
|
| 45 |
+
import { AdminStatsRepository } from '@gitroom/nestjs-libraries/database/prisma/admin-stats/admin-stats.repository';
|
| 46 |
+
import { AdminStatsService } from '@gitroom/nestjs-libraries/database/prisma/admin-stats/admin-stats.service';
|
| 47 |
+
|
| 48 |
+
@Global()
|
| 49 |
+
@Module({
|
| 50 |
+
imports: [],
|
| 51 |
+
controllers: [],
|
| 52 |
+
providers: [
|
| 53 |
+
PrismaService,
|
| 54 |
+
PrismaRepository,
|
| 55 |
+
PrismaTransaction,
|
| 56 |
+
UsersService,
|
| 57 |
+
UsersRepository,
|
| 58 |
+
OrganizationService,
|
| 59 |
+
OrganizationRepository,
|
| 60 |
+
SubscriptionService,
|
| 61 |
+
SubscriptionRepository,
|
| 62 |
+
NotificationService,
|
| 63 |
+
NotificationsRepository,
|
| 64 |
+
WebhooksRepository,
|
| 65 |
+
WebhooksService,
|
| 66 |
+
IntegrationService,
|
| 67 |
+
IntegrationRepository,
|
| 68 |
+
PostsService,
|
| 69 |
+
PostsRepository,
|
| 70 |
+
StripeService,
|
| 71 |
+
SignatureRepository,
|
| 72 |
+
AutopostRepository,
|
| 73 |
+
AutopostService,
|
| 74 |
+
SignatureService,
|
| 75 |
+
MediaService,
|
| 76 |
+
MediaRepository,
|
| 77 |
+
AgenciesService,
|
| 78 |
+
AgenciesRepository,
|
| 79 |
+
IntegrationManager,
|
| 80 |
+
RefreshIntegrationService,
|
| 81 |
+
ExtractContentService,
|
| 82 |
+
OpenaiService,
|
| 83 |
+
FalService,
|
| 84 |
+
EmailService,
|
| 85 |
+
TrackService,
|
| 86 |
+
ShortLinkService,
|
| 87 |
+
SetsService,
|
| 88 |
+
SetsRepository,
|
| 89 |
+
ThirdPartyRepository,
|
| 90 |
+
ThirdPartyService,
|
| 91 |
+
OAuthRepository,
|
| 92 |
+
OAuthService,
|
| 93 |
+
VideoManager,
|
| 94 |
+
AnnouncementsRepository,
|
| 95 |
+
AnnouncementsService,
|
| 96 |
+
ErrorsRepository,
|
| 97 |
+
ErrorsService,
|
| 98 |
+
AdminStatsRepository,
|
| 99 |
+
AdminStatsService,
|
| 100 |
+
],
|
| 101 |
+
get exports() {
|
| 102 |
+
return this.providers;
|
| 103 |
+
},
|
| 104 |
+
})
|
| 105 |
+
export class DatabaseModule {}
|
libraries/nestjs-libraries/src/database/prisma/errors/errors.repository.ts
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';
|
| 2 |
+
import { Injectable } from '@nestjs/common';
|
| 3 |
+
|
| 4 |
+
const UNKNOWN_TOKEN = 'An unknown error occurred';
|
| 5 |
+
|
| 6 |
+
interface ListErrorsParams {
|
| 7 |
+
page?: number;
|
| 8 |
+
limit?: number;
|
| 9 |
+
platform?: string;
|
| 10 |
+
email?: string;
|
| 11 |
+
unknownFirst?: boolean;
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
@Injectable()
|
| 15 |
+
export class ErrorsRepository {
|
| 16 |
+
constructor(private _errors: PrismaRepository<'errors'>) {}
|
| 17 |
+
|
| 18 |
+
private buildWhere(params: ListErrorsParams) {
|
| 19 |
+
const where: any = {};
|
| 20 |
+
if (params.platform) {
|
| 21 |
+
where.platform = params.platform;
|
| 22 |
+
}
|
| 23 |
+
if (params.email) {
|
| 24 |
+
where.organization = {
|
| 25 |
+
users: {
|
| 26 |
+
some: {
|
| 27 |
+
user: {
|
| 28 |
+
email: { contains: params.email, mode: 'insensitive' },
|
| 29 |
+
},
|
| 30 |
+
},
|
| 31 |
+
},
|
| 32 |
+
};
|
| 33 |
+
}
|
| 34 |
+
return where;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
private get include() {
|
| 38 |
+
return {
|
| 39 |
+
organization: {
|
| 40 |
+
select: {
|
| 41 |
+
id: true,
|
| 42 |
+
name: true,
|
| 43 |
+
users: {
|
| 44 |
+
select: {
|
| 45 |
+
user: { select: { id: true, email: true, name: true } },
|
| 46 |
+
},
|
| 47 |
+
},
|
| 48 |
+
},
|
| 49 |
+
},
|
| 50 |
+
post: { select: { id: true, content: true } },
|
| 51 |
+
} as const;
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
async listPlatforms() {
|
| 55 |
+
const rows = await this._errors.model.errors.findMany({
|
| 56 |
+
distinct: ['platform'],
|
| 57 |
+
select: { platform: true },
|
| 58 |
+
orderBy: { platform: 'asc' },
|
| 59 |
+
});
|
| 60 |
+
return rows.map((r) => r.platform);
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
async listErrors(params: ListErrorsParams) {
|
| 64 |
+
const page = Math.max(0, params.page || 0);
|
| 65 |
+
const limit = Math.min(Math.max(1, params.limit || 20), 100);
|
| 66 |
+
const skip = page * limit;
|
| 67 |
+
const where = this.buildWhere(params);
|
| 68 |
+
const include = this.include;
|
| 69 |
+
|
| 70 |
+
if (!params.unknownFirst) {
|
| 71 |
+
const [items, total] = await Promise.all([
|
| 72 |
+
this._errors.model.errors.findMany({
|
| 73 |
+
where,
|
| 74 |
+
orderBy: { createdAt: 'desc' },
|
| 75 |
+
skip,
|
| 76 |
+
take: limit,
|
| 77 |
+
include,
|
| 78 |
+
}),
|
| 79 |
+
this._errors.model.errors.count({ where }),
|
| 80 |
+
]);
|
| 81 |
+
return {
|
| 82 |
+
items,
|
| 83 |
+
total,
|
| 84 |
+
page,
|
| 85 |
+
limit,
|
| 86 |
+
hasMore: skip + items.length < total,
|
| 87 |
+
};
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
const unknownWhere = { ...where, message: { contains: UNKNOWN_TOKEN } };
|
| 91 |
+
const knownWhere = {
|
| 92 |
+
...where,
|
| 93 |
+
NOT: { message: { contains: UNKNOWN_TOKEN } },
|
| 94 |
+
};
|
| 95 |
+
|
| 96 |
+
const [unknownTotal, knownTotal] = await Promise.all([
|
| 97 |
+
this._errors.model.errors.count({ where: unknownWhere }),
|
| 98 |
+
this._errors.model.errors.count({ where: knownWhere }),
|
| 99 |
+
]);
|
| 100 |
+
|
| 101 |
+
let unknownItems: any[] = [];
|
| 102 |
+
let knownItems: any[] = [];
|
| 103 |
+
|
| 104 |
+
if (skip < unknownTotal) {
|
| 105 |
+
const takeUnknown = Math.min(unknownTotal - skip, limit);
|
| 106 |
+
unknownItems = await this._errors.model.errors.findMany({
|
| 107 |
+
where: unknownWhere,
|
| 108 |
+
orderBy: { createdAt: 'desc' },
|
| 109 |
+
skip,
|
| 110 |
+
take: takeUnknown,
|
| 111 |
+
include,
|
| 112 |
+
});
|
| 113 |
+
const remaining = limit - unknownItems.length;
|
| 114 |
+
if (remaining > 0) {
|
| 115 |
+
knownItems = await this._errors.model.errors.findMany({
|
| 116 |
+
where: knownWhere,
|
| 117 |
+
orderBy: { createdAt: 'desc' },
|
| 118 |
+
skip: 0,
|
| 119 |
+
take: remaining,
|
| 120 |
+
include,
|
| 121 |
+
});
|
| 122 |
+
}
|
| 123 |
+
} else {
|
| 124 |
+
knownItems = await this._errors.model.errors.findMany({
|
| 125 |
+
where: knownWhere,
|
| 126 |
+
orderBy: { createdAt: 'desc' },
|
| 127 |
+
skip: skip - unknownTotal,
|
| 128 |
+
take: limit,
|
| 129 |
+
include,
|
| 130 |
+
});
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
const items = [...unknownItems, ...knownItems];
|
| 134 |
+
const total = unknownTotal + knownTotal;
|
| 135 |
+
return {
|
| 136 |
+
items,
|
| 137 |
+
total,
|
| 138 |
+
page,
|
| 139 |
+
limit,
|
| 140 |
+
hasMore: skip + items.length < total,
|
| 141 |
+
};
|
| 142 |
+
}
|
| 143 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/errors/errors.service.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Injectable } from '@nestjs/common';
|
| 2 |
+
import { ErrorsRepository } from '@gitroom/nestjs-libraries/database/prisma/errors/errors.repository';
|
| 3 |
+
|
| 4 |
+
@Injectable()
|
| 5 |
+
export class ErrorsService {
|
| 6 |
+
constructor(private _errorsRepository: ErrorsRepository) {}
|
| 7 |
+
|
| 8 |
+
listErrors(params: {
|
| 9 |
+
page?: number;
|
| 10 |
+
limit?: number;
|
| 11 |
+
platform?: string;
|
| 12 |
+
email?: string;
|
| 13 |
+
unknownFirst?: boolean;
|
| 14 |
+
}) {
|
| 15 |
+
return this._errorsRepository.listErrors(params);
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
listPlatforms() {
|
| 19 |
+
return this._errorsRepository.listPlatforms();
|
| 20 |
+
}
|
| 21 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/integrations/integration.repository.ts
ADDED
|
@@ -0,0 +1,674 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';
|
| 2 |
+
import { Injectable } from '@nestjs/common';
|
| 3 |
+
import dayjs from 'dayjs';
|
| 4 |
+
import { Integration } from '@prisma/client';
|
| 5 |
+
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
|
| 6 |
+
import { IntegrationTimeDto } from '@gitroom/nestjs-libraries/dtos/integrations/integration.time.dto';
|
| 7 |
+
import { UploadFactory } from '@gitroom/nestjs-libraries/upload/upload.factory';
|
| 8 |
+
import { PlugDto } from '@gitroom/nestjs-libraries/dtos/plugs/plug.dto';
|
| 9 |
+
|
| 10 |
+
@Injectable()
|
| 11 |
+
export class IntegrationRepository {
|
| 12 |
+
private storage = UploadFactory.createStorage();
|
| 13 |
+
constructor(
|
| 14 |
+
private _integration: PrismaRepository<'integration'>,
|
| 15 |
+
private _posts: PrismaRepository<'post'>,
|
| 16 |
+
private _plugs: PrismaRepository<'plugs'>,
|
| 17 |
+
private _exisingPlugData: PrismaRepository<'exisingPlugData'>,
|
| 18 |
+
private _customers: PrismaRepository<'customer'>,
|
| 19 |
+
private _mentions: PrismaRepository<'mentions'>
|
| 20 |
+
) {}
|
| 21 |
+
|
| 22 |
+
getMentions(platform: string, q: string) {
|
| 23 |
+
return this._mentions.model.mentions.findMany({
|
| 24 |
+
where: {
|
| 25 |
+
platform,
|
| 26 |
+
OR: [
|
| 27 |
+
{
|
| 28 |
+
name: {
|
| 29 |
+
contains: q,
|
| 30 |
+
mode: 'insensitive',
|
| 31 |
+
},
|
| 32 |
+
},
|
| 33 |
+
{
|
| 34 |
+
username: {
|
| 35 |
+
contains: q,
|
| 36 |
+
mode: 'insensitive',
|
| 37 |
+
},
|
| 38 |
+
},
|
| 39 |
+
],
|
| 40 |
+
},
|
| 41 |
+
orderBy: {
|
| 42 |
+
name: 'asc',
|
| 43 |
+
},
|
| 44 |
+
take: 100,
|
| 45 |
+
select: {
|
| 46 |
+
name: true,
|
| 47 |
+
username: true,
|
| 48 |
+
image: true,
|
| 49 |
+
},
|
| 50 |
+
});
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
insertMentions(
|
| 54 |
+
platform: string,
|
| 55 |
+
mentions: { name: string; username: string; image: string }[]
|
| 56 |
+
) {
|
| 57 |
+
if (mentions.length === 0) {
|
| 58 |
+
return [] as any[];
|
| 59 |
+
}
|
| 60 |
+
return this._mentions.model.mentions.createMany({
|
| 61 |
+
data: mentions.map((mention) => ({
|
| 62 |
+
platform,
|
| 63 |
+
name: mention.name,
|
| 64 |
+
username: mention.username,
|
| 65 |
+
image: mention.image,
|
| 66 |
+
})),
|
| 67 |
+
skipDuplicates: true,
|
| 68 |
+
});
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
async checkPreviousConnections(org: string, id: string) {
|
| 72 |
+
const findIt = await this._integration.model.integration.findMany({
|
| 73 |
+
where: {
|
| 74 |
+
rootInternalId: id,
|
| 75 |
+
},
|
| 76 |
+
select: {
|
| 77 |
+
organizationId: true,
|
| 78 |
+
id: true,
|
| 79 |
+
},
|
| 80 |
+
});
|
| 81 |
+
|
| 82 |
+
if (findIt.some((f) => f.organizationId === org)) {
|
| 83 |
+
return false;
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
return findIt.length > 0;
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
updateProviderSettings(org: string, id: string, settings: string) {
|
| 90 |
+
return this._integration.model.integration.update({
|
| 91 |
+
where: {
|
| 92 |
+
id,
|
| 93 |
+
organizationId: org,
|
| 94 |
+
},
|
| 95 |
+
data: {
|
| 96 |
+
additionalSettings: settings,
|
| 97 |
+
},
|
| 98 |
+
});
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
async setTimes(org: string, id: string, times: IntegrationTimeDto) {
|
| 102 |
+
return this._integration.model.integration.update({
|
| 103 |
+
select: {
|
| 104 |
+
id: true,
|
| 105 |
+
},
|
| 106 |
+
where: {
|
| 107 |
+
id,
|
| 108 |
+
organizationId: org,
|
| 109 |
+
},
|
| 110 |
+
data: {
|
| 111 |
+
postingTimes: JSON.stringify(times.time),
|
| 112 |
+
},
|
| 113 |
+
});
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
getPlug(plugId: string) {
|
| 117 |
+
return this._plugs.model.plugs.findFirst({
|
| 118 |
+
where: {
|
| 119 |
+
id: plugId,
|
| 120 |
+
},
|
| 121 |
+
include: {
|
| 122 |
+
integration: true,
|
| 123 |
+
},
|
| 124 |
+
});
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
async getPlugs(orgId: string, integrationId: string) {
|
| 128 |
+
return this._plugs.model.plugs.findMany({
|
| 129 |
+
where: {
|
| 130 |
+
integrationId,
|
| 131 |
+
organizationId: orgId,
|
| 132 |
+
activated: true,
|
| 133 |
+
},
|
| 134 |
+
include: {
|
| 135 |
+
integration: {
|
| 136 |
+
select: {
|
| 137 |
+
id: true,
|
| 138 |
+
providerIdentifier: true,
|
| 139 |
+
},
|
| 140 |
+
},
|
| 141 |
+
},
|
| 142 |
+
});
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
async updateIntegration(id: string, params: Partial<Integration>) {
|
| 146 |
+
if (
|
| 147 |
+
params.picture &&
|
| 148 |
+
(params.picture.indexOf(process.env.CLOUDFLARE_BUCKET_URL!) === -1 ||
|
| 149 |
+
params.picture.indexOf(process.env.FRONTEND_URL!) === -1)
|
| 150 |
+
) {
|
| 151 |
+
params.picture = await this.storage.uploadSimple(params.picture);
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
const existing = await this._integration.model.integration.findUnique({
|
| 155 |
+
where: {
|
| 156 |
+
organizationId_internalId: {
|
| 157 |
+
organizationId: params.organizationId!,
|
| 158 |
+
internalId: params.internalId,
|
| 159 |
+
},
|
| 160 |
+
},
|
| 161 |
+
});
|
| 162 |
+
|
| 163 |
+
if (existing) {
|
| 164 |
+
await this._posts.model.post.updateMany({
|
| 165 |
+
where: {
|
| 166 |
+
integrationId: id,
|
| 167 |
+
},
|
| 168 |
+
data: {
|
| 169 |
+
deletedAt: new Date(),
|
| 170 |
+
},
|
| 171 |
+
});
|
| 172 |
+
|
| 173 |
+
await this._integration.model.integration.update({
|
| 174 |
+
where: {
|
| 175 |
+
id,
|
| 176 |
+
},
|
| 177 |
+
data: {
|
| 178 |
+
internalId: `deleted_${params.internalId}_${makeId(10)}`,
|
| 179 |
+
deletedAt: new Date(),
|
| 180 |
+
},
|
| 181 |
+
});
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
return this._integration.model.integration.update({
|
| 185 |
+
where: {
|
| 186 |
+
...(existing ? { id: existing.id } : { id }),
|
| 187 |
+
},
|
| 188 |
+
data: {
|
| 189 |
+
...params,
|
| 190 |
+
disabled: false,
|
| 191 |
+
deletedAt: null,
|
| 192 |
+
},
|
| 193 |
+
});
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
disconnectChannel(org: string, id: string) {
|
| 197 |
+
return this._integration.model.integration.update({
|
| 198 |
+
where: {
|
| 199 |
+
id,
|
| 200 |
+
organizationId: org,
|
| 201 |
+
},
|
| 202 |
+
data: {
|
| 203 |
+
refreshNeeded: true,
|
| 204 |
+
},
|
| 205 |
+
});
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
async createOrUpdateIntegration(
|
| 209 |
+
additionalSettings:
|
| 210 |
+
| {
|
| 211 |
+
title: string;
|
| 212 |
+
description: string;
|
| 213 |
+
type: 'checkbox' | 'text' | 'textarea';
|
| 214 |
+
value: any;
|
| 215 |
+
regex?: string;
|
| 216 |
+
}[]
|
| 217 |
+
| undefined,
|
| 218 |
+
oneTimeToken: boolean,
|
| 219 |
+
org: string,
|
| 220 |
+
name: string,
|
| 221 |
+
picture: string | undefined,
|
| 222 |
+
type: 'article' | 'social',
|
| 223 |
+
internalId: string,
|
| 224 |
+
provider: string,
|
| 225 |
+
token: string,
|
| 226 |
+
refreshToken = '',
|
| 227 |
+
expiresIn = 999999999,
|
| 228 |
+
username?: string,
|
| 229 |
+
isBetweenSteps = false,
|
| 230 |
+
refresh?: string,
|
| 231 |
+
timezone?: number,
|
| 232 |
+
customInstanceDetails?: string
|
| 233 |
+
) {
|
| 234 |
+
const postTimes = timezone
|
| 235 |
+
? {
|
| 236 |
+
postingTimes: JSON.stringify([
|
| 237 |
+
{ time: 560 - timezone },
|
| 238 |
+
{ time: 850 - timezone },
|
| 239 |
+
{ time: 1140 - timezone },
|
| 240 |
+
]),
|
| 241 |
+
}
|
| 242 |
+
: {};
|
| 243 |
+
const upsert = await this._integration.model.integration.upsert({
|
| 244 |
+
where: {
|
| 245 |
+
organizationId_internalId: {
|
| 246 |
+
internalId,
|
| 247 |
+
organizationId: org,
|
| 248 |
+
},
|
| 249 |
+
},
|
| 250 |
+
create: {
|
| 251 |
+
type: type as any,
|
| 252 |
+
name,
|
| 253 |
+
providerIdentifier: provider,
|
| 254 |
+
token,
|
| 255 |
+
profile: username,
|
| 256 |
+
...(picture ? { picture } : {}),
|
| 257 |
+
inBetweenSteps: isBetweenSteps,
|
| 258 |
+
refreshToken,
|
| 259 |
+
...(expiresIn
|
| 260 |
+
? { tokenExpiration: new Date(Date.now() + expiresIn * 1000) }
|
| 261 |
+
: {}),
|
| 262 |
+
internalId,
|
| 263 |
+
...postTimes,
|
| 264 |
+
organizationId: org,
|
| 265 |
+
refreshNeeded: false,
|
| 266 |
+
rootInternalId: internalId,
|
| 267 |
+
...(customInstanceDetails ? { customInstanceDetails } : {}),
|
| 268 |
+
additionalSettings: additionalSettings
|
| 269 |
+
? JSON.stringify(additionalSettings)
|
| 270 |
+
: '[]',
|
| 271 |
+
},
|
| 272 |
+
update: {
|
| 273 |
+
...(additionalSettings
|
| 274 |
+
? { additionalSettings: JSON.stringify(additionalSettings) }
|
| 275 |
+
: {}),
|
| 276 |
+
...(customInstanceDetails ? { customInstanceDetails } : {}),
|
| 277 |
+
type: type as any,
|
| 278 |
+
...(!refresh
|
| 279 |
+
? {
|
| 280 |
+
inBetweenSteps: isBetweenSteps,
|
| 281 |
+
}
|
| 282 |
+
: {}),
|
| 283 |
+
...(picture ? { picture } : {}),
|
| 284 |
+
profile: username,
|
| 285 |
+
providerIdentifier: provider,
|
| 286 |
+
token,
|
| 287 |
+
refreshToken,
|
| 288 |
+
...(expiresIn
|
| 289 |
+
? { tokenExpiration: new Date(Date.now() + expiresIn * 1000) }
|
| 290 |
+
: {}),
|
| 291 |
+
internalId,
|
| 292 |
+
organizationId: org,
|
| 293 |
+
deletedAt: null,
|
| 294 |
+
refreshNeeded: false,
|
| 295 |
+
},
|
| 296 |
+
});
|
| 297 |
+
|
| 298 |
+
if (oneTimeToken) {
|
| 299 |
+
const rootId =
|
| 300 |
+
(
|
| 301 |
+
await this._integration.model.integration.findFirst({
|
| 302 |
+
where: {
|
| 303 |
+
organizationId: org,
|
| 304 |
+
internalId: internalId,
|
| 305 |
+
},
|
| 306 |
+
})
|
| 307 |
+
)?.rootInternalId || internalId;
|
| 308 |
+
|
| 309 |
+
await this._integration.model.integration.updateMany({
|
| 310 |
+
where: {
|
| 311 |
+
id: {
|
| 312 |
+
not: upsert.id,
|
| 313 |
+
},
|
| 314 |
+
rootInternalId: rootId,
|
| 315 |
+
},
|
| 316 |
+
data: {
|
| 317 |
+
token,
|
| 318 |
+
refreshToken,
|
| 319 |
+
refreshNeeded: false,
|
| 320 |
+
...(expiresIn
|
| 321 |
+
? { tokenExpiration: new Date(Date.now() + expiresIn * 1000) }
|
| 322 |
+
: {}),
|
| 323 |
+
},
|
| 324 |
+
});
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
return upsert;
|
| 328 |
+
}
|
| 329 |
+
|
| 330 |
+
needsToBeRefreshed() {
|
| 331 |
+
return this._integration.model.integration.findMany({
|
| 332 |
+
where: {
|
| 333 |
+
tokenExpiration: {
|
| 334 |
+
lte: dayjs().add(1, 'day').toDate(),
|
| 335 |
+
},
|
| 336 |
+
inBetweenSteps: false,
|
| 337 |
+
deletedAt: null,
|
| 338 |
+
refreshNeeded: false,
|
| 339 |
+
},
|
| 340 |
+
});
|
| 341 |
+
}
|
| 342 |
+
|
| 343 |
+
async setBetweenRefreshSteps(id: string) {
|
| 344 |
+
return this._integration.model.integration.update({
|
| 345 |
+
where: {
|
| 346 |
+
id,
|
| 347 |
+
},
|
| 348 |
+
data: {
|
| 349 |
+
inBetweenSteps: true,
|
| 350 |
+
},
|
| 351 |
+
});
|
| 352 |
+
}
|
| 353 |
+
refreshNeeded(org: string, id: string) {
|
| 354 |
+
return this._integration.model.integration.update({
|
| 355 |
+
where: {
|
| 356 |
+
id,
|
| 357 |
+
organizationId: org,
|
| 358 |
+
},
|
| 359 |
+
data: {
|
| 360 |
+
refreshNeeded: true,
|
| 361 |
+
},
|
| 362 |
+
});
|
| 363 |
+
}
|
| 364 |
+
|
| 365 |
+
updateNameAndUrl(id: string, name: string, url: string) {
|
| 366 |
+
return this._integration.model.integration.update({
|
| 367 |
+
where: {
|
| 368 |
+
id,
|
| 369 |
+
},
|
| 370 |
+
data: {
|
| 371 |
+
...(name ? { name } : {}),
|
| 372 |
+
...(url ? { picture: url } : {}),
|
| 373 |
+
},
|
| 374 |
+
});
|
| 375 |
+
}
|
| 376 |
+
|
| 377 |
+
getIntegrationById(org: string, id: string) {
|
| 378 |
+
return this._integration.model.integration.findFirst({
|
| 379 |
+
where: {
|
| 380 |
+
organizationId: org,
|
| 381 |
+
id,
|
| 382 |
+
},
|
| 383 |
+
});
|
| 384 |
+
}
|
| 385 |
+
|
| 386 |
+
async getIntegrationForOrder(
|
| 387 |
+
id: string,
|
| 388 |
+
order: string,
|
| 389 |
+
user: string,
|
| 390 |
+
org: string
|
| 391 |
+
) {
|
| 392 |
+
const integration = await this._posts.model.post.findFirst({
|
| 393 |
+
where: {
|
| 394 |
+
integrationId: id,
|
| 395 |
+
submittedForOrder: {
|
| 396 |
+
id: order,
|
| 397 |
+
messageGroup: {
|
| 398 |
+
OR: [
|
| 399 |
+
{ sellerId: user },
|
| 400 |
+
{ buyerId: user },
|
| 401 |
+
{ buyerOrganizationId: org },
|
| 402 |
+
],
|
| 403 |
+
},
|
| 404 |
+
},
|
| 405 |
+
},
|
| 406 |
+
select: {
|
| 407 |
+
integration: {
|
| 408 |
+
select: {
|
| 409 |
+
id: true,
|
| 410 |
+
name: true,
|
| 411 |
+
picture: true,
|
| 412 |
+
inBetweenSteps: true,
|
| 413 |
+
providerIdentifier: true,
|
| 414 |
+
},
|
| 415 |
+
},
|
| 416 |
+
},
|
| 417 |
+
});
|
| 418 |
+
|
| 419 |
+
return integration?.integration;
|
| 420 |
+
}
|
| 421 |
+
|
| 422 |
+
async updateOnCustomerName(org: string, id: string, name: string) {
|
| 423 |
+
const customer = !name
|
| 424 |
+
? undefined
|
| 425 |
+
: (await this._customers.model.customer.findFirst({
|
| 426 |
+
where: {
|
| 427 |
+
orgId: org,
|
| 428 |
+
name,
|
| 429 |
+
},
|
| 430 |
+
})) ||
|
| 431 |
+
(await this._customers.model.customer.create({
|
| 432 |
+
data: {
|
| 433 |
+
name,
|
| 434 |
+
orgId: org,
|
| 435 |
+
},
|
| 436 |
+
}));
|
| 437 |
+
|
| 438 |
+
return this._integration.model.integration.update({
|
| 439 |
+
where: {
|
| 440 |
+
id,
|
| 441 |
+
organizationId: org,
|
| 442 |
+
},
|
| 443 |
+
data: {
|
| 444 |
+
customer: !customer
|
| 445 |
+
? { disconnect: true }
|
| 446 |
+
: {
|
| 447 |
+
connect: {
|
| 448 |
+
id: customer.id,
|
| 449 |
+
},
|
| 450 |
+
},
|
| 451 |
+
},
|
| 452 |
+
});
|
| 453 |
+
}
|
| 454 |
+
|
| 455 |
+
updateIntegrationGroup(org: string, id: string, group: string) {
|
| 456 |
+
return this._integration.model.integration.update({
|
| 457 |
+
where: {
|
| 458 |
+
id,
|
| 459 |
+
organizationId: org,
|
| 460 |
+
},
|
| 461 |
+
data: !group
|
| 462 |
+
? {
|
| 463 |
+
customer: {
|
| 464 |
+
disconnect: true,
|
| 465 |
+
},
|
| 466 |
+
}
|
| 467 |
+
: {
|
| 468 |
+
customer: {
|
| 469 |
+
connect: {
|
| 470 |
+
id: group,
|
| 471 |
+
},
|
| 472 |
+
},
|
| 473 |
+
},
|
| 474 |
+
});
|
| 475 |
+
}
|
| 476 |
+
|
| 477 |
+
customers(orgId: string) {
|
| 478 |
+
return this._customers.model.customer.findMany({
|
| 479 |
+
where: {
|
| 480 |
+
orgId,
|
| 481 |
+
deletedAt: null,
|
| 482 |
+
},
|
| 483 |
+
});
|
| 484 |
+
}
|
| 485 |
+
|
| 486 |
+
getIntegrationsList(org: string) {
|
| 487 |
+
return this._integration.model.integration.findMany({
|
| 488 |
+
where: {
|
| 489 |
+
organizationId: org,
|
| 490 |
+
deletedAt: null,
|
| 491 |
+
},
|
| 492 |
+
include: {
|
| 493 |
+
customer: true,
|
| 494 |
+
},
|
| 495 |
+
});
|
| 496 |
+
}
|
| 497 |
+
|
| 498 |
+
async disableChannel(org: string, id: string) {
|
| 499 |
+
await this._integration.model.integration.update({
|
| 500 |
+
where: {
|
| 501 |
+
id,
|
| 502 |
+
organizationId: org,
|
| 503 |
+
},
|
| 504 |
+
data: {
|
| 505 |
+
disabled: true,
|
| 506 |
+
},
|
| 507 |
+
});
|
| 508 |
+
}
|
| 509 |
+
|
| 510 |
+
async enableChannel(org: string, id: string) {
|
| 511 |
+
await this._integration.model.integration.update({
|
| 512 |
+
where: {
|
| 513 |
+
id,
|
| 514 |
+
organizationId: org,
|
| 515 |
+
},
|
| 516 |
+
data: {
|
| 517 |
+
disabled: false,
|
| 518 |
+
},
|
| 519 |
+
});
|
| 520 |
+
}
|
| 521 |
+
|
| 522 |
+
getPostsForChannel(org: string, id: string) {
|
| 523 |
+
return this._posts.model.post.groupBy({
|
| 524 |
+
by: ['group'],
|
| 525 |
+
where: {
|
| 526 |
+
organizationId: org,
|
| 527 |
+
integrationId: id,
|
| 528 |
+
deletedAt: null,
|
| 529 |
+
},
|
| 530 |
+
});
|
| 531 |
+
}
|
| 532 |
+
|
| 533 |
+
deleteChannel(org: string, id: string) {
|
| 534 |
+
return this._integration.model.integration.update({
|
| 535 |
+
where: {
|
| 536 |
+
id,
|
| 537 |
+
organizationId: org,
|
| 538 |
+
},
|
| 539 |
+
data: {
|
| 540 |
+
deletedAt: new Date(),
|
| 541 |
+
},
|
| 542 |
+
});
|
| 543 |
+
}
|
| 544 |
+
|
| 545 |
+
async checkForDeletedOnceAndUpdate(org: string, page: string) {
|
| 546 |
+
return this._integration.model.integration.updateMany({
|
| 547 |
+
where: {
|
| 548 |
+
organizationId: org,
|
| 549 |
+
internalId: page,
|
| 550 |
+
deletedAt: {
|
| 551 |
+
not: null,
|
| 552 |
+
},
|
| 553 |
+
},
|
| 554 |
+
data: {
|
| 555 |
+
internalId: makeId(10),
|
| 556 |
+
},
|
| 557 |
+
});
|
| 558 |
+
}
|
| 559 |
+
|
| 560 |
+
async disableIntegrations(org: string, totalChannels: number) {
|
| 561 |
+
const getChannels = await this._integration.model.integration.findMany({
|
| 562 |
+
where: {
|
| 563 |
+
organizationId: org,
|
| 564 |
+
disabled: false,
|
| 565 |
+
deletedAt: null,
|
| 566 |
+
},
|
| 567 |
+
take: totalChannels,
|
| 568 |
+
select: {
|
| 569 |
+
id: true,
|
| 570 |
+
},
|
| 571 |
+
});
|
| 572 |
+
|
| 573 |
+
for (const channel of getChannels) {
|
| 574 |
+
await this._integration.model.integration.update({
|
| 575 |
+
where: {
|
| 576 |
+
id: channel.id,
|
| 577 |
+
},
|
| 578 |
+
data: {
|
| 579 |
+
disabled: true,
|
| 580 |
+
},
|
| 581 |
+
});
|
| 582 |
+
}
|
| 583 |
+
}
|
| 584 |
+
|
| 585 |
+
getPlugsByIntegrationId(org: string, id: string) {
|
| 586 |
+
return this._plugs.model.plugs.findMany({
|
| 587 |
+
where: {
|
| 588 |
+
organizationId: org,
|
| 589 |
+
integrationId: id,
|
| 590 |
+
},
|
| 591 |
+
});
|
| 592 |
+
}
|
| 593 |
+
|
| 594 |
+
createOrUpdatePlug(org: string, integrationId: string, body: PlugDto) {
|
| 595 |
+
return this._plugs.model.plugs.upsert({
|
| 596 |
+
where: {
|
| 597 |
+
organizationId: org,
|
| 598 |
+
plugFunction_integrationId: {
|
| 599 |
+
integrationId,
|
| 600 |
+
plugFunction: body.func,
|
| 601 |
+
},
|
| 602 |
+
},
|
| 603 |
+
create: {
|
| 604 |
+
integrationId,
|
| 605 |
+
organizationId: org,
|
| 606 |
+
plugFunction: body.func,
|
| 607 |
+
data: JSON.stringify(body.fields),
|
| 608 |
+
activated: true,
|
| 609 |
+
},
|
| 610 |
+
update: {
|
| 611 |
+
data: JSON.stringify(body.fields),
|
| 612 |
+
},
|
| 613 |
+
select: {
|
| 614 |
+
activated: true,
|
| 615 |
+
},
|
| 616 |
+
});
|
| 617 |
+
}
|
| 618 |
+
|
| 619 |
+
changePlugActivation(orgId: string, plugId: string, status: boolean) {
|
| 620 |
+
return this._plugs.model.plugs.update({
|
| 621 |
+
where: {
|
| 622 |
+
organizationId: orgId,
|
| 623 |
+
id: plugId,
|
| 624 |
+
},
|
| 625 |
+
data: {
|
| 626 |
+
activated: !!status,
|
| 627 |
+
},
|
| 628 |
+
});
|
| 629 |
+
}
|
| 630 |
+
|
| 631 |
+
async loadExisingData(
|
| 632 |
+
methodName: string,
|
| 633 |
+
integrationId: string,
|
| 634 |
+
id: string[]
|
| 635 |
+
) {
|
| 636 |
+
return this._exisingPlugData.model.exisingPlugData.findMany({
|
| 637 |
+
where: {
|
| 638 |
+
integrationId,
|
| 639 |
+
methodName,
|
| 640 |
+
value: {
|
| 641 |
+
in: id,
|
| 642 |
+
},
|
| 643 |
+
},
|
| 644 |
+
});
|
| 645 |
+
}
|
| 646 |
+
|
| 647 |
+
async saveExisingData(
|
| 648 |
+
methodName: string,
|
| 649 |
+
integrationId: string,
|
| 650 |
+
value: string[]
|
| 651 |
+
) {
|
| 652 |
+
return this._exisingPlugData.model.exisingPlugData.createMany({
|
| 653 |
+
data: value.map((p) => ({
|
| 654 |
+
integrationId,
|
| 655 |
+
methodName,
|
| 656 |
+
value: p,
|
| 657 |
+
})),
|
| 658 |
+
});
|
| 659 |
+
}
|
| 660 |
+
|
| 661 |
+
async getPostingTimes(orgId: string, integrationsId?: string) {
|
| 662 |
+
return this._integration.model.integration.findMany({
|
| 663 |
+
where: {
|
| 664 |
+
...(integrationsId ? { id: integrationsId } : {}),
|
| 665 |
+
organizationId: orgId,
|
| 666 |
+
disabled: false,
|
| 667 |
+
deletedAt: null,
|
| 668 |
+
},
|
| 669 |
+
select: {
|
| 670 |
+
postingTimes: true,
|
| 671 |
+
},
|
| 672 |
+
});
|
| 673 |
+
}
|
| 674 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/integrations/integration.service.ts
ADDED
|
@@ -0,0 +1,569 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import {
|
| 2 |
+
forwardRef,
|
| 3 |
+
HttpException,
|
| 4 |
+
HttpStatus,
|
| 5 |
+
Inject,
|
| 6 |
+
Injectable,
|
| 7 |
+
} from '@nestjs/common';
|
| 8 |
+
import { IntegrationRepository } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.repository';
|
| 9 |
+
import { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integration.manager';
|
| 10 |
+
import {
|
| 11 |
+
AnalyticsData,
|
| 12 |
+
SocialProvider,
|
| 13 |
+
} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';
|
| 14 |
+
import { Integration, Organization } from '@prisma/client';
|
| 15 |
+
import { NotificationService } from '@gitroom/nestjs-libraries/database/prisma/notifications/notification.service';
|
| 16 |
+
import dayjs from 'dayjs';
|
| 17 |
+
import { timer } from '@gitroom/helpers/utils/timer';
|
| 18 |
+
import { ioRedis } from '@gitroom/nestjs-libraries/redis/redis.service';
|
| 19 |
+
import { RefreshToken } from '@gitroom/nestjs-libraries/integrations/social.abstract';
|
| 20 |
+
import { IntegrationTimeDto } from '@gitroom/nestjs-libraries/dtos/integrations/integration.time.dto';
|
| 21 |
+
import { UploadFactory } from '@gitroom/nestjs-libraries/upload/upload.factory';
|
| 22 |
+
import { PlugDto } from '@gitroom/nestjs-libraries/dtos/plugs/plug.dto';
|
| 23 |
+
import { difference, uniq } from 'lodash';
|
| 24 |
+
import utc from 'dayjs/plugin/utc';
|
| 25 |
+
import { AutopostRepository } from '@gitroom/nestjs-libraries/database/prisma/autopost/autopost.repository';
|
| 26 |
+
import { RefreshIntegrationService } from '@gitroom/nestjs-libraries/integrations/refresh.integration.service';
|
| 27 |
+
import { TemporalService } from 'nestjs-temporal-core';
|
| 28 |
+
|
| 29 |
+
dayjs.extend(utc);
|
| 30 |
+
|
| 31 |
+
@Injectable()
|
| 32 |
+
export class IntegrationService {
|
| 33 |
+
private storage = UploadFactory.createStorage();
|
| 34 |
+
constructor(
|
| 35 |
+
private _integrationRepository: IntegrationRepository,
|
| 36 |
+
private _autopostsRepository: AutopostRepository,
|
| 37 |
+
private _integrationManager: IntegrationManager,
|
| 38 |
+
private _notificationService: NotificationService,
|
| 39 |
+
@Inject(forwardRef(() => RefreshIntegrationService))
|
| 40 |
+
private _refreshIntegrationService: RefreshIntegrationService,
|
| 41 |
+
private _temporalService: TemporalService
|
| 42 |
+
) {}
|
| 43 |
+
|
| 44 |
+
async changeActiveCron(orgId: string) {
|
| 45 |
+
const data = await this._autopostsRepository.getAutoposts(orgId);
|
| 46 |
+
|
| 47 |
+
for (const item of data.filter((f) => f.active)) {
|
| 48 |
+
try {
|
| 49 |
+
await this._temporalService.terminateWorkflow(`autopost-${item.id}`);
|
| 50 |
+
} catch (err) {}
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
return true;
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
getMentions(platform: string, q: string) {
|
| 57 |
+
return this._integrationRepository.getMentions(platform, q);
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
insertMentions(
|
| 61 |
+
platform: string,
|
| 62 |
+
mentions: { name: string; username: string; image: string }[]
|
| 63 |
+
) {
|
| 64 |
+
return this._integrationRepository.insertMentions(platform, mentions);
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
async setTimes(
|
| 68 |
+
orgId: string,
|
| 69 |
+
integrationId: string,
|
| 70 |
+
times: IntegrationTimeDto
|
| 71 |
+
) {
|
| 72 |
+
return this._integrationRepository.setTimes(orgId, integrationId, times);
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
updateProviderSettings(org: string, id: string, additionalSettings: string) {
|
| 76 |
+
return this._integrationRepository.updateProviderSettings(
|
| 77 |
+
org,
|
| 78 |
+
id,
|
| 79 |
+
additionalSettings
|
| 80 |
+
);
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
checkPreviousConnections(org: string, id: string) {
|
| 84 |
+
return this._integrationRepository.checkPreviousConnections(org, id);
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
async createOrUpdateIntegration(
|
| 88 |
+
additionalSettings:
|
| 89 |
+
| {
|
| 90 |
+
title: string;
|
| 91 |
+
description: string;
|
| 92 |
+
type: 'checkbox' | 'text' | 'textarea';
|
| 93 |
+
value: any;
|
| 94 |
+
regex?: string;
|
| 95 |
+
}[]
|
| 96 |
+
| undefined,
|
| 97 |
+
oneTimeToken: boolean,
|
| 98 |
+
org: string,
|
| 99 |
+
name: string,
|
| 100 |
+
picture: string | undefined,
|
| 101 |
+
type: 'article' | 'social',
|
| 102 |
+
internalId: string,
|
| 103 |
+
provider: string,
|
| 104 |
+
token: string,
|
| 105 |
+
refreshToken = '',
|
| 106 |
+
expiresIn?: number,
|
| 107 |
+
username?: string,
|
| 108 |
+
isBetweenSteps = false,
|
| 109 |
+
refresh?: string,
|
| 110 |
+
timezone?: number,
|
| 111 |
+
customInstanceDetails?: string
|
| 112 |
+
) {
|
| 113 |
+
const uploadedPicture = picture
|
| 114 |
+
? picture?.indexOf('imagedelivery.net') > -1
|
| 115 |
+
? picture
|
| 116 |
+
: await this.storage.uploadSimple(picture)
|
| 117 |
+
: undefined;
|
| 118 |
+
|
| 119 |
+
return this._integrationRepository.createOrUpdateIntegration(
|
| 120 |
+
additionalSettings,
|
| 121 |
+
oneTimeToken,
|
| 122 |
+
org,
|
| 123 |
+
name,
|
| 124 |
+
uploadedPicture,
|
| 125 |
+
type,
|
| 126 |
+
internalId,
|
| 127 |
+
provider,
|
| 128 |
+
token,
|
| 129 |
+
refreshToken,
|
| 130 |
+
expiresIn,
|
| 131 |
+
username,
|
| 132 |
+
isBetweenSteps,
|
| 133 |
+
refresh,
|
| 134 |
+
timezone,
|
| 135 |
+
customInstanceDetails
|
| 136 |
+
);
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
updateIntegrationGroup(org: string, id: string, group: string) {
|
| 140 |
+
return this._integrationRepository.updateIntegrationGroup(org, id, group);
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
updateOnCustomerName(org: string, id: string, name: string) {
|
| 144 |
+
return this._integrationRepository.updateOnCustomerName(org, id, name);
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
getIntegrationsList(org: string) {
|
| 148 |
+
return this._integrationRepository.getIntegrationsList(org);
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
getIntegrationForOrder(id: string, order: string, user: string, org: string) {
|
| 152 |
+
return this._integrationRepository.getIntegrationForOrder(
|
| 153 |
+
id,
|
| 154 |
+
order,
|
| 155 |
+
user,
|
| 156 |
+
org
|
| 157 |
+
);
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
updateNameAndUrl(id: string, name: string, url: string) {
|
| 161 |
+
return this._integrationRepository.updateNameAndUrl(id, name, url);
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
getIntegrationById(org: string, id: string) {
|
| 165 |
+
return this._integrationRepository.getIntegrationById(org, id);
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
async refreshToken(provider: SocialProvider, refresh: string) {
|
| 169 |
+
try {
|
| 170 |
+
const { refreshToken, accessToken, expiresIn } =
|
| 171 |
+
await provider.refreshToken(refresh);
|
| 172 |
+
|
| 173 |
+
if (!refreshToken || !accessToken || !expiresIn) {
|
| 174 |
+
return false;
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
return { refreshToken, accessToken, expiresIn };
|
| 178 |
+
} catch (e) {
|
| 179 |
+
return false;
|
| 180 |
+
}
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
async disconnectChannel(orgId: string, integration: Integration) {
|
| 184 |
+
await this._integrationRepository.disconnectChannel(orgId, integration.id);
|
| 185 |
+
await this.informAboutRefreshError(orgId, integration);
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
async informAboutRefreshError(
|
| 189 |
+
orgId: string,
|
| 190 |
+
integration: Integration,
|
| 191 |
+
err = ''
|
| 192 |
+
) {
|
| 193 |
+
await this._notificationService.inAppNotification(
|
| 194 |
+
orgId,
|
| 195 |
+
`Could not refresh your ${integration.providerIdentifier} channel ${err}`,
|
| 196 |
+
`Could not refresh your ${integration.providerIdentifier} channel ${err}. Please go back to the system and connect it again ${process.env.FRONTEND_URL}/launches`,
|
| 197 |
+
true,
|
| 198 |
+
false,
|
| 199 |
+
'info'
|
| 200 |
+
);
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
async refreshNeeded(org: string, id: string) {
|
| 204 |
+
return this._integrationRepository.refreshNeeded(org, id);
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
async setBetweenRefreshSteps(id: string) {
|
| 208 |
+
return this._integrationRepository.setBetweenRefreshSteps(id);
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
async refreshTokens() {
|
| 212 |
+
const integrations = await this._integrationRepository.needsToBeRefreshed();
|
| 213 |
+
for (const integration of integrations) {
|
| 214 |
+
const provider = this._integrationManager.getSocialIntegration(
|
| 215 |
+
integration.providerIdentifier
|
| 216 |
+
);
|
| 217 |
+
|
| 218 |
+
const data = await this.refreshToken(provider, integration.refreshToken!);
|
| 219 |
+
|
| 220 |
+
if (!data) {
|
| 221 |
+
await this.informAboutRefreshError(
|
| 222 |
+
integration.organizationId,
|
| 223 |
+
integration
|
| 224 |
+
);
|
| 225 |
+
await this._integrationRepository.refreshNeeded(
|
| 226 |
+
integration.organizationId,
|
| 227 |
+
integration.id
|
| 228 |
+
);
|
| 229 |
+
return;
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
const { refreshToken, accessToken, expiresIn } = data;
|
| 233 |
+
|
| 234 |
+
await this.createOrUpdateIntegration(
|
| 235 |
+
undefined,
|
| 236 |
+
!!provider.oneTimeToken,
|
| 237 |
+
integration.organizationId,
|
| 238 |
+
integration.name,
|
| 239 |
+
undefined,
|
| 240 |
+
'social',
|
| 241 |
+
integration.internalId,
|
| 242 |
+
integration.providerIdentifier,
|
| 243 |
+
accessToken,
|
| 244 |
+
refreshToken,
|
| 245 |
+
expiresIn
|
| 246 |
+
);
|
| 247 |
+
}
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
async disableChannel(org: string, id: string) {
|
| 251 |
+
return this._integrationRepository.disableChannel(org, id);
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
async enableChannel(org: string, totalChannels: number, id: string) {
|
| 255 |
+
const integrations = (
|
| 256 |
+
await this._integrationRepository.getIntegrationsList(org)
|
| 257 |
+
).filter((f) => !f.disabled);
|
| 258 |
+
if (
|
| 259 |
+
!!process.env.STRIPE_PUBLISHABLE_KEY &&
|
| 260 |
+
integrations.length >= totalChannels
|
| 261 |
+
) {
|
| 262 |
+
throw new Error('You have reached the maximum number of channels');
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
return this._integrationRepository.enableChannel(org, id);
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
+
async getPostsForChannel(org: string, id: string) {
|
| 269 |
+
return this._integrationRepository.getPostsForChannel(org, id);
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
async deleteChannel(org: string, id: string) {
|
| 273 |
+
return this._integrationRepository.deleteChannel(org, id);
|
| 274 |
+
}
|
| 275 |
+
|
| 276 |
+
async disableIntegrations(org: string, totalChannels: number) {
|
| 277 |
+
return this._integrationRepository.disableIntegrations(org, totalChannels);
|
| 278 |
+
}
|
| 279 |
+
|
| 280 |
+
async checkForDeletedOnceAndUpdate(org: string, page: string) {
|
| 281 |
+
return this._integrationRepository.checkForDeletedOnceAndUpdate(org, page);
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
async saveProviderPage(org: string, id: string, data: any) {
|
| 285 |
+
const getIntegration = await this._integrationRepository.getIntegrationById(
|
| 286 |
+
org,
|
| 287 |
+
id
|
| 288 |
+
);
|
| 289 |
+
if (!getIntegration) {
|
| 290 |
+
throw new HttpException('Integration not found', HttpStatus.NOT_FOUND);
|
| 291 |
+
}
|
| 292 |
+
if (!getIntegration.inBetweenSteps) {
|
| 293 |
+
throw new HttpException('Invalid request', HttpStatus.BAD_REQUEST);
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
+
const provider = this._integrationManager.getSocialIntegration(
|
| 297 |
+
getIntegration.providerIdentifier
|
| 298 |
+
);
|
| 299 |
+
|
| 300 |
+
if (!provider.fetchPageInformation) {
|
| 301 |
+
throw new HttpException(
|
| 302 |
+
'Provider does not support page selection',
|
| 303 |
+
HttpStatus.BAD_REQUEST
|
| 304 |
+
);
|
| 305 |
+
}
|
| 306 |
+
|
| 307 |
+
const getIntegrationInformation = await provider.fetchPageInformation(
|
| 308 |
+
getIntegration.token,
|
| 309 |
+
data
|
| 310 |
+
);
|
| 311 |
+
|
| 312 |
+
await this.checkForDeletedOnceAndUpdate(
|
| 313 |
+
org,
|
| 314 |
+
String(getIntegrationInformation.id)
|
| 315 |
+
);
|
| 316 |
+
await this._integrationRepository.updateIntegration(id, {
|
| 317 |
+
picture: getIntegrationInformation.picture,
|
| 318 |
+
internalId: String(getIntegrationInformation.id),
|
| 319 |
+
organizationId: org,
|
| 320 |
+
name: getIntegrationInformation.name,
|
| 321 |
+
inBetweenSteps: false,
|
| 322 |
+
token: getIntegrationInformation.access_token,
|
| 323 |
+
profile: getIntegrationInformation.username,
|
| 324 |
+
});
|
| 325 |
+
|
| 326 |
+
return { success: true };
|
| 327 |
+
}
|
| 328 |
+
|
| 329 |
+
async checkAnalytics(
|
| 330 |
+
org: Organization,
|
| 331 |
+
integration: string,
|
| 332 |
+
date: string,
|
| 333 |
+
forceRefresh = false
|
| 334 |
+
): Promise<AnalyticsData[]> {
|
| 335 |
+
const getIntegration = await this.getIntegrationById(org.id, integration);
|
| 336 |
+
|
| 337 |
+
if (!getIntegration) {
|
| 338 |
+
throw new Error('Invalid integration');
|
| 339 |
+
}
|
| 340 |
+
|
| 341 |
+
if (getIntegration.type !== 'social') {
|
| 342 |
+
return [];
|
| 343 |
+
}
|
| 344 |
+
|
| 345 |
+
const integrationProvider = this._integrationManager.getSocialIntegration(
|
| 346 |
+
getIntegration.providerIdentifier
|
| 347 |
+
);
|
| 348 |
+
|
| 349 |
+
if (
|
| 350 |
+
dayjs(getIntegration?.tokenExpiration).isBefore(dayjs()) ||
|
| 351 |
+
forceRefresh
|
| 352 |
+
) {
|
| 353 |
+
const data = await this._refreshIntegrationService.refresh(
|
| 354 |
+
getIntegration
|
| 355 |
+
);
|
| 356 |
+
if (!data) {
|
| 357 |
+
return [];
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
const { accessToken } = data;
|
| 361 |
+
|
| 362 |
+
if (accessToken) {
|
| 363 |
+
getIntegration.token = accessToken;
|
| 364 |
+
|
| 365 |
+
if (integrationProvider.refreshWait) {
|
| 366 |
+
await timer(10000);
|
| 367 |
+
}
|
| 368 |
+
} else {
|
| 369 |
+
await this.disconnectChannel(org.id, getIntegration);
|
| 370 |
+
return [];
|
| 371 |
+
}
|
| 372 |
+
}
|
| 373 |
+
|
| 374 |
+
const getIntegrationData = await ioRedis.get(
|
| 375 |
+
`integration:${org.id}:${integration}:${date}`
|
| 376 |
+
);
|
| 377 |
+
if (getIntegrationData) {
|
| 378 |
+
return JSON.parse(getIntegrationData);
|
| 379 |
+
}
|
| 380 |
+
|
| 381 |
+
if (integrationProvider.analytics) {
|
| 382 |
+
try {
|
| 383 |
+
const loadAnalytics = await integrationProvider.analytics(
|
| 384 |
+
getIntegration.internalId,
|
| 385 |
+
getIntegration.token,
|
| 386 |
+
+date
|
| 387 |
+
);
|
| 388 |
+
await ioRedis.set(
|
| 389 |
+
`integration:${org.id}:${integration}:${date}`,
|
| 390 |
+
JSON.stringify(loadAnalytics),
|
| 391 |
+
'EX',
|
| 392 |
+
!process.env.NODE_ENV || process.env.NODE_ENV === 'development'
|
| 393 |
+
? 1
|
| 394 |
+
: 3600
|
| 395 |
+
);
|
| 396 |
+
return loadAnalytics;
|
| 397 |
+
} catch (e) {
|
| 398 |
+
if (e instanceof RefreshToken) {
|
| 399 |
+
return this.checkAnalytics(org, integration, date, true);
|
| 400 |
+
}
|
| 401 |
+
}
|
| 402 |
+
}
|
| 403 |
+
|
| 404 |
+
return [];
|
| 405 |
+
}
|
| 406 |
+
|
| 407 |
+
customers(orgId: string) {
|
| 408 |
+
return this._integrationRepository.customers(orgId);
|
| 409 |
+
}
|
| 410 |
+
|
| 411 |
+
getPlugsByIntegrationId(org: string, integrationId: string) {
|
| 412 |
+
return this._integrationRepository.getPlugsByIntegrationId(
|
| 413 |
+
org,
|
| 414 |
+
integrationId
|
| 415 |
+
);
|
| 416 |
+
}
|
| 417 |
+
|
| 418 |
+
async processInternalPlug(
|
| 419 |
+
data: {
|
| 420 |
+
post: string;
|
| 421 |
+
originalIntegration: string;
|
| 422 |
+
integration: string;
|
| 423 |
+
plugName: string;
|
| 424 |
+
orgId: string;
|
| 425 |
+
delay: number;
|
| 426 |
+
information: any;
|
| 427 |
+
},
|
| 428 |
+
forceRefresh = false
|
| 429 |
+
): Promise<any> {
|
| 430 |
+
const originalIntegration =
|
| 431 |
+
await this._integrationRepository.getIntegrationById(
|
| 432 |
+
data.orgId,
|
| 433 |
+
data.originalIntegration
|
| 434 |
+
);
|
| 435 |
+
|
| 436 |
+
const getIntegration = await this._integrationRepository.getIntegrationById(
|
| 437 |
+
data.orgId,
|
| 438 |
+
data.integration
|
| 439 |
+
);
|
| 440 |
+
|
| 441 |
+
if (!getIntegration || !originalIntegration) {
|
| 442 |
+
return;
|
| 443 |
+
}
|
| 444 |
+
|
| 445 |
+
const getAllInternalPlugs = this._integrationManager
|
| 446 |
+
.getInternalPlugs(getIntegration.providerIdentifier)
|
| 447 |
+
.internalPlugs.find((p: any) => p.identifier === data.plugName);
|
| 448 |
+
|
| 449 |
+
if (!getAllInternalPlugs) {
|
| 450 |
+
return;
|
| 451 |
+
}
|
| 452 |
+
|
| 453 |
+
const getSocialIntegration = this._integrationManager.getSocialIntegration(
|
| 454 |
+
getIntegration.providerIdentifier
|
| 455 |
+
);
|
| 456 |
+
|
| 457 |
+
// @ts-ignore
|
| 458 |
+
await getSocialIntegration?.[getAllInternalPlugs.methodName]?.(
|
| 459 |
+
getIntegration,
|
| 460 |
+
originalIntegration,
|
| 461 |
+
data.post,
|
| 462 |
+
data.information
|
| 463 |
+
);
|
| 464 |
+
|
| 465 |
+
return;
|
| 466 |
+
}
|
| 467 |
+
|
| 468 |
+
async processPlugs(data: {
|
| 469 |
+
plugId: string;
|
| 470 |
+
postId: string;
|
| 471 |
+
delay: number;
|
| 472 |
+
totalRuns: number;
|
| 473 |
+
currentRun: number;
|
| 474 |
+
}) {
|
| 475 |
+
const getPlugById = await this._integrationRepository.getPlug(data.plugId);
|
| 476 |
+
if (!getPlugById) {
|
| 477 |
+
return true;
|
| 478 |
+
}
|
| 479 |
+
|
| 480 |
+
const integration = this._integrationManager.getSocialIntegration(
|
| 481 |
+
getPlugById.integration.providerIdentifier
|
| 482 |
+
);
|
| 483 |
+
|
| 484 |
+
// @ts-ignore
|
| 485 |
+
const process = await integration[getPlugById.plugFunction](
|
| 486 |
+
getPlugById.integration,
|
| 487 |
+
data.postId,
|
| 488 |
+
JSON.parse(getPlugById.data).reduce((all: any, current: any) => {
|
| 489 |
+
all[current.name] = current.value;
|
| 490 |
+
return all;
|
| 491 |
+
}, {})
|
| 492 |
+
);
|
| 493 |
+
|
| 494 |
+
if (process) {
|
| 495 |
+
return true;
|
| 496 |
+
}
|
| 497 |
+
|
| 498 |
+
if (data.totalRuns === data.currentRun) {
|
| 499 |
+
return true;
|
| 500 |
+
}
|
| 501 |
+
|
| 502 |
+
return false;
|
| 503 |
+
}
|
| 504 |
+
|
| 505 |
+
async createOrUpdatePlug(
|
| 506 |
+
orgId: string,
|
| 507 |
+
integrationId: string,
|
| 508 |
+
body: PlugDto
|
| 509 |
+
) {
|
| 510 |
+
const { activated } = await this._integrationRepository.createOrUpdatePlug(
|
| 511 |
+
orgId,
|
| 512 |
+
integrationId,
|
| 513 |
+
body
|
| 514 |
+
);
|
| 515 |
+
|
| 516 |
+
return {
|
| 517 |
+
activated,
|
| 518 |
+
};
|
| 519 |
+
}
|
| 520 |
+
|
| 521 |
+
async changePlugActivation(orgId: string, plugId: string, status: boolean) {
|
| 522 |
+
const { id, integrationId, plugFunction } =
|
| 523 |
+
await this._integrationRepository.changePlugActivation(
|
| 524 |
+
orgId,
|
| 525 |
+
plugId,
|
| 526 |
+
status
|
| 527 |
+
);
|
| 528 |
+
|
| 529 |
+
return { id };
|
| 530 |
+
}
|
| 531 |
+
|
| 532 |
+
async getPlugs(orgId: string, integrationId: string) {
|
| 533 |
+
return this._integrationRepository.getPlugs(orgId, integrationId);
|
| 534 |
+
}
|
| 535 |
+
|
| 536 |
+
async loadExisingData(
|
| 537 |
+
methodName: string,
|
| 538 |
+
integrationId: string,
|
| 539 |
+
id: string[]
|
| 540 |
+
) {
|
| 541 |
+
const exisingData = await this._integrationRepository.loadExisingData(
|
| 542 |
+
methodName,
|
| 543 |
+
integrationId,
|
| 544 |
+
id
|
| 545 |
+
);
|
| 546 |
+
const loadOnlyIds = exisingData.map((p) => p.value);
|
| 547 |
+
return difference(id, loadOnlyIds);
|
| 548 |
+
}
|
| 549 |
+
|
| 550 |
+
async findFreeDateTime(
|
| 551 |
+
orgId: string,
|
| 552 |
+
integrationsId?: string
|
| 553 |
+
): Promise<number[]> {
|
| 554 |
+
const findTimes = await this._integrationRepository.getPostingTimes(
|
| 555 |
+
orgId,
|
| 556 |
+
integrationsId
|
| 557 |
+
);
|
| 558 |
+
return uniq(
|
| 559 |
+
findTimes.reduce((all: any, current: any) => {
|
| 560 |
+
return [
|
| 561 |
+
...all,
|
| 562 |
+
...JSON.parse(current.postingTimes).map(
|
| 563 |
+
(p: { time: number }) => p.time
|
| 564 |
+
),
|
| 565 |
+
];
|
| 566 |
+
}, [] as number[])
|
| 567 |
+
);
|
| 568 |
+
}
|
| 569 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/media/media.repository.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';
|
| 2 |
+
import { Injectable } from '@nestjs/common';
|
| 3 |
+
import { SaveMediaInformationDto } from '@gitroom/nestjs-libraries/dtos/media/save.media.information.dto';
|
| 4 |
+
|
| 5 |
+
@Injectable()
|
| 6 |
+
export class MediaRepository {
|
| 7 |
+
constructor(private _media: PrismaRepository<'media'>) {}
|
| 8 |
+
|
| 9 |
+
saveFile(org: string, fileName: string, filePath: string, originalName?: string) {
|
| 10 |
+
return this._media.model.media.create({
|
| 11 |
+
data: {
|
| 12 |
+
organization: {
|
| 13 |
+
connect: {
|
| 14 |
+
id: org,
|
| 15 |
+
},
|
| 16 |
+
},
|
| 17 |
+
name: fileName,
|
| 18 |
+
path: filePath,
|
| 19 |
+
originalName: originalName || null,
|
| 20 |
+
},
|
| 21 |
+
select: {
|
| 22 |
+
id: true,
|
| 23 |
+
name: true,
|
| 24 |
+
originalName: true,
|
| 25 |
+
path: true,
|
| 26 |
+
thumbnail: true,
|
| 27 |
+
alt: true,
|
| 28 |
+
},
|
| 29 |
+
});
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
getMediaById(id: string) {
|
| 33 |
+
return this._media.model.media.findUnique({
|
| 34 |
+
where: {
|
| 35 |
+
id,
|
| 36 |
+
},
|
| 37 |
+
});
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
deleteMedia(org: string, id: string) {
|
| 41 |
+
return this._media.model.media.update({
|
| 42 |
+
where: {
|
| 43 |
+
id,
|
| 44 |
+
organizationId: org,
|
| 45 |
+
},
|
| 46 |
+
data: {
|
| 47 |
+
deletedAt: new Date(),
|
| 48 |
+
},
|
| 49 |
+
});
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
saveMediaInformation(org: string, data: SaveMediaInformationDto) {
|
| 53 |
+
return this._media.model.media.update({
|
| 54 |
+
where: {
|
| 55 |
+
id: data.id,
|
| 56 |
+
organizationId: org,
|
| 57 |
+
},
|
| 58 |
+
data: {
|
| 59 |
+
alt: data.alt,
|
| 60 |
+
thumbnail: data.thumbnail,
|
| 61 |
+
thumbnailTimestamp: data.thumbnailTimestamp,
|
| 62 |
+
},
|
| 63 |
+
select: {
|
| 64 |
+
id: true,
|
| 65 |
+
name: true,
|
| 66 |
+
originalName: true,
|
| 67 |
+
alt: true,
|
| 68 |
+
thumbnail: true,
|
| 69 |
+
path: true,
|
| 70 |
+
thumbnailTimestamp: true,
|
| 71 |
+
},
|
| 72 |
+
});
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
async getMedia(org: string, page: number, search?: string) {
|
| 76 |
+
const pageNum = (page || 1) - 1;
|
| 77 |
+
const trimmedSearch = search?.trim();
|
| 78 |
+
const searchFilter = trimmedSearch
|
| 79 |
+
? {
|
| 80 |
+
originalName: {
|
| 81 |
+
contains: trimmedSearch,
|
| 82 |
+
mode: 'insensitive' as const,
|
| 83 |
+
},
|
| 84 |
+
}
|
| 85 |
+
: {};
|
| 86 |
+
const query = {
|
| 87 |
+
where: {
|
| 88 |
+
organization: {
|
| 89 |
+
id: org,
|
| 90 |
+
},
|
| 91 |
+
deletedAt: null,
|
| 92 |
+
...searchFilter,
|
| 93 |
+
},
|
| 94 |
+
};
|
| 95 |
+
const pages = Math.ceil((await this._media.model.media.count(query)) / 18);
|
| 96 |
+
const results = await this._media.model.media.findMany({
|
| 97 |
+
where: {
|
| 98 |
+
organizationId: org,
|
| 99 |
+
deletedAt: null,
|
| 100 |
+
...searchFilter,
|
| 101 |
+
},
|
| 102 |
+
orderBy: {
|
| 103 |
+
createdAt: 'desc',
|
| 104 |
+
},
|
| 105 |
+
select: {
|
| 106 |
+
id: true,
|
| 107 |
+
name: true,
|
| 108 |
+
originalName: true,
|
| 109 |
+
path: true,
|
| 110 |
+
thumbnail: true,
|
| 111 |
+
alt: true,
|
| 112 |
+
thumbnailTimestamp: true,
|
| 113 |
+
},
|
| 114 |
+
skip: pageNum * 18,
|
| 115 |
+
take: 18,
|
| 116 |
+
});
|
| 117 |
+
|
| 118 |
+
return {
|
| 119 |
+
pages,
|
| 120 |
+
results,
|
| 121 |
+
};
|
| 122 |
+
}
|
| 123 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/media/media.service.ts
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { HttpException, Injectable } from '@nestjs/common';
|
| 2 |
+
import { MediaRepository } from '@gitroom/nestjs-libraries/database/prisma/media/media.repository';
|
| 3 |
+
import { OpenaiService } from '@gitroom/nestjs-libraries/openai/openai.service';
|
| 4 |
+
import { generationError } from '@gitroom/nestjs-libraries/openai/generation.error';
|
| 5 |
+
import { SubscriptionService } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.service';
|
| 6 |
+
import { Organization } from '@prisma/client';
|
| 7 |
+
import { SaveMediaInformationDto } from '@gitroom/nestjs-libraries/dtos/media/save.media.information.dto';
|
| 8 |
+
import { VideoManager } from '@gitroom/nestjs-libraries/videos/video.manager';
|
| 9 |
+
import { VideoDto } from '@gitroom/nestjs-libraries/dtos/videos/video.dto';
|
| 10 |
+
import { UploadFactory } from '@gitroom/nestjs-libraries/upload/upload.factory';
|
| 11 |
+
import {
|
| 12 |
+
AuthorizationActions,
|
| 13 |
+
Sections,
|
| 14 |
+
SubscriptionException,
|
| 15 |
+
} from '@gitroom/backend/services/auth/permissions/permission.exception.class';
|
| 16 |
+
|
| 17 |
+
@Injectable()
|
| 18 |
+
export class MediaService {
|
| 19 |
+
private storage = UploadFactory.createStorage();
|
| 20 |
+
|
| 21 |
+
constructor(
|
| 22 |
+
private _mediaRepository: MediaRepository,
|
| 23 |
+
private _openAi: OpenaiService,
|
| 24 |
+
private _subscriptionService: SubscriptionService,
|
| 25 |
+
private _videoManager: VideoManager
|
| 26 |
+
) {}
|
| 27 |
+
|
| 28 |
+
async deleteMedia(org: string, id: string) {
|
| 29 |
+
return this._mediaRepository.deleteMedia(org, id);
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
getMediaById(id: string) {
|
| 33 |
+
return this._mediaRepository.getMediaById(id);
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
async generateImage(
|
| 37 |
+
prompt: string,
|
| 38 |
+
org: Organization,
|
| 39 |
+
generatePromptFirst?: boolean
|
| 40 |
+
) {
|
| 41 |
+
try {
|
| 42 |
+
const generating = await this._subscriptionService.useCredit(
|
| 43 |
+
org,
|
| 44 |
+
'ai_images',
|
| 45 |
+
async () => {
|
| 46 |
+
if (generatePromptFirst) {
|
| 47 |
+
prompt = await this._openAi.generatePromptForPicture(prompt);
|
| 48 |
+
console.log('Prompt:', prompt);
|
| 49 |
+
}
|
| 50 |
+
return this._openAi.generateImage(prompt);
|
| 51 |
+
}
|
| 52 |
+
);
|
| 53 |
+
|
| 54 |
+
return generating;
|
| 55 |
+
} catch (err) {
|
| 56 |
+
throw generationError(err);
|
| 57 |
+
}
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
saveFile(org: string, fileName: string, filePath: string, originalName?: string) {
|
| 61 |
+
return this._mediaRepository.saveFile(org, fileName, filePath, originalName);
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
getMedia(org: string, page: number, search?: string) {
|
| 65 |
+
return this._mediaRepository.getMedia(org, page, search);
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
saveMediaInformation(org: string, data: SaveMediaInformationDto) {
|
| 69 |
+
return this._mediaRepository.saveMediaInformation(org, data);
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
getVideoOptions() {
|
| 73 |
+
return this._videoManager.getAllVideos();
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
async generateVideoAllowed(org: Organization, type: string) {
|
| 77 |
+
const video = this._videoManager.getVideoByName(type);
|
| 78 |
+
if (!video) {
|
| 79 |
+
throw new Error(`Video type ${type} not found`);
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
if (!video.trial && org.isTrailing) {
|
| 83 |
+
throw new HttpException('This video is not available in trial mode', 406);
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
return true;
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
async generateVideo(org: Organization, body: VideoDto) {
|
| 90 |
+
try {
|
| 91 |
+
const totalCredits = await this._subscriptionService.checkCredits(
|
| 92 |
+
org,
|
| 93 |
+
'ai_videos'
|
| 94 |
+
);
|
| 95 |
+
|
| 96 |
+
if (totalCredits.credits <= 0) {
|
| 97 |
+
throw new SubscriptionException({
|
| 98 |
+
action: AuthorizationActions.Create,
|
| 99 |
+
section: Sections.VIDEOS_PER_MONTH,
|
| 100 |
+
});
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
const video = this._videoManager.getVideoByName(body.type);
|
| 104 |
+
if (!video) {
|
| 105 |
+
throw new Error(`Video type ${body.type} not found`);
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
if (!video.trial && org.isTrailing) {
|
| 109 |
+
throw new HttpException(
|
| 110 |
+
'This video is not available in trial mode',
|
| 111 |
+
406
|
| 112 |
+
);
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
console.log(body.customParams);
|
| 116 |
+
await video.instance.processAndValidate(body.customParams);
|
| 117 |
+
console.log('no err');
|
| 118 |
+
|
| 119 |
+
return await this._subscriptionService.useCredit(
|
| 120 |
+
org,
|
| 121 |
+
'ai_videos',
|
| 122 |
+
async () => {
|
| 123 |
+
const loadedData = await video.instance.process(
|
| 124 |
+
body.output,
|
| 125 |
+
body.customParams
|
| 126 |
+
);
|
| 127 |
+
|
| 128 |
+
const file = await this.storage.uploadSimple(loadedData);
|
| 129 |
+
return this.saveFile(org.id, file.split('/').pop(), file);
|
| 130 |
+
}
|
| 131 |
+
);
|
| 132 |
+
} catch (err) {
|
| 133 |
+
throw generationError(err);
|
| 134 |
+
}
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
async videoFunction(identifier: string, functionName: string, body: any) {
|
| 138 |
+
const video = this._videoManager.getVideoByName(identifier);
|
| 139 |
+
if (!video) {
|
| 140 |
+
throw new Error(`Video with identifier ${identifier} not found`);
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
// @ts-ignore
|
| 144 |
+
const functionToCall = video.instance[functionName];
|
| 145 |
+
if (
|
| 146 |
+
typeof functionToCall !== 'function' ||
|
| 147 |
+
this._videoManager.checkAvailableVideoFunction(functionToCall)
|
| 148 |
+
) {
|
| 149 |
+
throw new HttpException(
|
| 150 |
+
`Function ${functionName} not found on video instance`,
|
| 151 |
+
400
|
| 152 |
+
);
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
return functionToCall(body);
|
| 156 |
+
}
|
| 157 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/notifications/notification.service.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Injectable } from '@nestjs/common';
|
| 2 |
+
import { NotificationsRepository } from '@gitroom/nestjs-libraries/database/prisma/notifications/notifications.repository';
|
| 3 |
+
import { EmailService } from '@gitroom/nestjs-libraries/services/email.service';
|
| 4 |
+
import { OrganizationRepository } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.repository';
|
| 5 |
+
import { TemporalService } from 'nestjs-temporal-core';
|
| 6 |
+
import { TypedSearchAttributes } from '@temporalio/common';
|
| 7 |
+
import { organizationId } from '@gitroom/nestjs-libraries/temporal/temporal.search.attribute';
|
| 8 |
+
|
| 9 |
+
export type NotificationType = 'success' | 'fail' | 'info';
|
| 10 |
+
|
| 11 |
+
@Injectable()
|
| 12 |
+
export class NotificationService {
|
| 13 |
+
constructor(
|
| 14 |
+
private _notificationRepository: NotificationsRepository,
|
| 15 |
+
private _emailService: EmailService,
|
| 16 |
+
private _organizationRepository: OrganizationRepository,
|
| 17 |
+
private _temporalService: TemporalService
|
| 18 |
+
) {}
|
| 19 |
+
|
| 20 |
+
getMainPageCount(organizationId: string, userId: string) {
|
| 21 |
+
return this._notificationRepository.getMainPageCount(
|
| 22 |
+
organizationId,
|
| 23 |
+
userId
|
| 24 |
+
);
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
getNotificationsPaginated(organizationId: string, page: number) {
|
| 28 |
+
return this._notificationRepository.getNotificationsPaginated(
|
| 29 |
+
organizationId,
|
| 30 |
+
page
|
| 31 |
+
);
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
getNotifications(organizationId: string, userId: string) {
|
| 35 |
+
return this._notificationRepository.getNotifications(
|
| 36 |
+
organizationId,
|
| 37 |
+
userId
|
| 38 |
+
);
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
async inAppNotification(
|
| 42 |
+
orgId: string,
|
| 43 |
+
subject: string,
|
| 44 |
+
message: string,
|
| 45 |
+
sendEmail = false,
|
| 46 |
+
digest = false,
|
| 47 |
+
type: NotificationType = 'success'
|
| 48 |
+
) {
|
| 49 |
+
await this._notificationRepository.createNotification(orgId, message);
|
| 50 |
+
if (!sendEmail) {
|
| 51 |
+
return;
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
if (digest) {
|
| 55 |
+
try {
|
| 56 |
+
await this._temporalService.client
|
| 57 |
+
.getRawClient()
|
| 58 |
+
?.workflow.signalWithStart('digestEmailWorkflow', {
|
| 59 |
+
workflowId: 'digest_email_workflow_' + orgId,
|
| 60 |
+
signal: 'email',
|
| 61 |
+
signalArgs: [
|
| 62 |
+
[
|
| 63 |
+
{
|
| 64 |
+
title: subject,
|
| 65 |
+
message,
|
| 66 |
+
type,
|
| 67 |
+
},
|
| 68 |
+
],
|
| 69 |
+
],
|
| 70 |
+
taskQueue: 'main',
|
| 71 |
+
workflowIdConflictPolicy: 'USE_EXISTING',
|
| 72 |
+
args: [{ organizationId: orgId }],
|
| 73 |
+
typedSearchAttributes: new TypedSearchAttributes([
|
| 74 |
+
{
|
| 75 |
+
key: organizationId,
|
| 76 |
+
value: orgId,
|
| 77 |
+
},
|
| 78 |
+
]),
|
| 79 |
+
});
|
| 80 |
+
} catch (err) {}
|
| 81 |
+
|
| 82 |
+
return;
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
await this.sendEmailsToOrg(orgId, subject, message, type);
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
async sendEmailsToOrg(
|
| 89 |
+
orgId: string,
|
| 90 |
+
subject: string,
|
| 91 |
+
message: string,
|
| 92 |
+
type?: NotificationType
|
| 93 |
+
) {
|
| 94 |
+
const userOrg = await this._organizationRepository.getAllUsersOrgs(orgId);
|
| 95 |
+
for (const user of userOrg?.users || []) {
|
| 96 |
+
// 'info' type is always sent regardless of preferences
|
| 97 |
+
if (type !== 'info') {
|
| 98 |
+
// Filter users based on their email preferences
|
| 99 |
+
if (type === 'success' && !user.user.sendSuccessEmails) {
|
| 100 |
+
continue;
|
| 101 |
+
}
|
| 102 |
+
if (type === 'fail' && !user.user.sendFailureEmails) {
|
| 103 |
+
continue;
|
| 104 |
+
}
|
| 105 |
+
}
|
| 106 |
+
await this.sendEmail(user.user.email, subject, message);
|
| 107 |
+
}
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
async sendEmail(to: string, subject: string, html: string, replyTo?: string) {
|
| 111 |
+
await this._emailService.sendEmail(to, subject, html, 'top', replyTo);
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
hasEmailProvider() {
|
| 115 |
+
return this._emailService.hasProvider();
|
| 116 |
+
}
|
| 117 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/notifications/notifications.repository.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';
|
| 2 |
+
import { Injectable } from '@nestjs/common';
|
| 3 |
+
|
| 4 |
+
@Injectable()
|
| 5 |
+
export class NotificationsRepository {
|
| 6 |
+
constructor(
|
| 7 |
+
private _notifications: PrismaRepository<'notifications'>,
|
| 8 |
+
private _user: PrismaRepository<'user'>
|
| 9 |
+
) {}
|
| 10 |
+
|
| 11 |
+
getLastReadNotification(userId: string) {
|
| 12 |
+
return this._user.model.user.findFirst({
|
| 13 |
+
where: {
|
| 14 |
+
id: userId,
|
| 15 |
+
},
|
| 16 |
+
select: {
|
| 17 |
+
lastReadNotifications: true,
|
| 18 |
+
},
|
| 19 |
+
});
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
async getMainPageCount(organizationId: string, userId: string) {
|
| 23 |
+
const { lastReadNotifications } = (await this.getLastReadNotification(
|
| 24 |
+
userId
|
| 25 |
+
))!;
|
| 26 |
+
|
| 27 |
+
return {
|
| 28 |
+
total: await this._notifications.model.notifications.count({
|
| 29 |
+
where: {
|
| 30 |
+
organizationId,
|
| 31 |
+
createdAt: {
|
| 32 |
+
gt: lastReadNotifications!,
|
| 33 |
+
},
|
| 34 |
+
},
|
| 35 |
+
}),
|
| 36 |
+
};
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
async createNotification(organizationId: string, content: string) {
|
| 40 |
+
await this._notifications.model.notifications.create({
|
| 41 |
+
data: {
|
| 42 |
+
organizationId,
|
| 43 |
+
content,
|
| 44 |
+
},
|
| 45 |
+
});
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
async getNotificationsSince(organizationId: string, since: string) {
|
| 49 |
+
return this._notifications.model.notifications.findMany({
|
| 50 |
+
where: {
|
| 51 |
+
organizationId,
|
| 52 |
+
createdAt: {
|
| 53 |
+
gte: new Date(since),
|
| 54 |
+
},
|
| 55 |
+
},
|
| 56 |
+
});
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
async getNotificationsPaginated(organizationId: string, page: number) {
|
| 60 |
+
const limit = 100;
|
| 61 |
+
const skip = page * limit;
|
| 62 |
+
|
| 63 |
+
const where = {
|
| 64 |
+
organizationId,
|
| 65 |
+
deletedAt: null as Date | null,
|
| 66 |
+
};
|
| 67 |
+
|
| 68 |
+
const [notifications, total] = await Promise.all([
|
| 69 |
+
this._notifications.model.notifications.findMany({
|
| 70 |
+
where,
|
| 71 |
+
orderBy: {
|
| 72 |
+
createdAt: 'desc',
|
| 73 |
+
},
|
| 74 |
+
skip,
|
| 75 |
+
take: limit,
|
| 76 |
+
select: {
|
| 77 |
+
id: true,
|
| 78 |
+
content: true,
|
| 79 |
+
link: true,
|
| 80 |
+
createdAt: true,
|
| 81 |
+
},
|
| 82 |
+
}),
|
| 83 |
+
this._notifications.model.notifications.count({ where }),
|
| 84 |
+
]);
|
| 85 |
+
|
| 86 |
+
return {
|
| 87 |
+
notifications,
|
| 88 |
+
total,
|
| 89 |
+
page,
|
| 90 |
+
limit,
|
| 91 |
+
hasMore: skip + notifications.length < total,
|
| 92 |
+
};
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
async getNotifications(organizationId: string, userId: string) {
|
| 96 |
+
const { lastReadNotifications } = (await this.getLastReadNotification(
|
| 97 |
+
userId
|
| 98 |
+
))!;
|
| 99 |
+
|
| 100 |
+
await this._user.model.user.update({
|
| 101 |
+
where: {
|
| 102 |
+
id: userId,
|
| 103 |
+
},
|
| 104 |
+
data: {
|
| 105 |
+
lastReadNotifications: new Date(),
|
| 106 |
+
},
|
| 107 |
+
});
|
| 108 |
+
|
| 109 |
+
return {
|
| 110 |
+
lastReadNotifications,
|
| 111 |
+
notifications: await this._notifications.model.notifications.findMany({
|
| 112 |
+
orderBy: {
|
| 113 |
+
createdAt: 'desc',
|
| 114 |
+
},
|
| 115 |
+
take: 10,
|
| 116 |
+
where: {
|
| 117 |
+
organizationId,
|
| 118 |
+
},
|
| 119 |
+
select: {
|
| 120 |
+
createdAt: true,
|
| 121 |
+
content: true,
|
| 122 |
+
},
|
| 123 |
+
}),
|
| 124 |
+
};
|
| 125 |
+
}
|
| 126 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/oauth/oauth.repository.ts
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Injectable } from '@nestjs/common';
|
| 2 |
+
import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';
|
| 3 |
+
|
| 4 |
+
@Injectable()
|
| 5 |
+
export class OAuthRepository {
|
| 6 |
+
constructor(
|
| 7 |
+
private _oauthApp: PrismaRepository<'oAuthApp'>,
|
| 8 |
+
private _oauthAuth: PrismaRepository<'oAuthAuthorization'>
|
| 9 |
+
) {}
|
| 10 |
+
|
| 11 |
+
getAppByOrgId(orgId: string) {
|
| 12 |
+
return this._oauthApp.model.oAuthApp.findFirst({
|
| 13 |
+
where: {
|
| 14 |
+
organizationId: orgId,
|
| 15 |
+
deletedAt: null,
|
| 16 |
+
},
|
| 17 |
+
include: {
|
| 18 |
+
picture: true,
|
| 19 |
+
},
|
| 20 |
+
});
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
getAppByClientId(clientId: string) {
|
| 24 |
+
return this._oauthApp.model.oAuthApp.findFirst({
|
| 25 |
+
where: {
|
| 26 |
+
clientId,
|
| 27 |
+
deletedAt: null,
|
| 28 |
+
},
|
| 29 |
+
include: {
|
| 30 |
+
picture: true,
|
| 31 |
+
},
|
| 32 |
+
});
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
createApp(
|
| 36 |
+
orgId: string,
|
| 37 |
+
data: {
|
| 38 |
+
name: string;
|
| 39 |
+
description?: string;
|
| 40 |
+
pictureId?: string;
|
| 41 |
+
redirectUrl: string;
|
| 42 |
+
clientId: string;
|
| 43 |
+
clientSecret: string;
|
| 44 |
+
}
|
| 45 |
+
) {
|
| 46 |
+
return this._oauthApp.model.oAuthApp.create({
|
| 47 |
+
data: {
|
| 48 |
+
organizationId: orgId,
|
| 49 |
+
name: data.name,
|
| 50 |
+
description: data.description,
|
| 51 |
+
pictureId: data.pictureId,
|
| 52 |
+
redirectUrl: data.redirectUrl,
|
| 53 |
+
clientId: data.clientId,
|
| 54 |
+
clientSecret: data.clientSecret,
|
| 55 |
+
},
|
| 56 |
+
include: {
|
| 57 |
+
picture: true,
|
| 58 |
+
},
|
| 59 |
+
});
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
async updateApp(
|
| 63 |
+
orgId: string,
|
| 64 |
+
data: {
|
| 65 |
+
name?: string;
|
| 66 |
+
description?: string;
|
| 67 |
+
pictureId?: string;
|
| 68 |
+
redirectUrl?: string;
|
| 69 |
+
}
|
| 70 |
+
) {
|
| 71 |
+
const app = await this._oauthApp.model.oAuthApp.findFirst({
|
| 72 |
+
where: {
|
| 73 |
+
organizationId: orgId,
|
| 74 |
+
deletedAt: null,
|
| 75 |
+
},
|
| 76 |
+
});
|
| 77 |
+
if (!app) {
|
| 78 |
+
return null;
|
| 79 |
+
}
|
| 80 |
+
return this._oauthApp.model.oAuthApp.update({
|
| 81 |
+
where: { id: app.id },
|
| 82 |
+
data,
|
| 83 |
+
include: {
|
| 84 |
+
picture: true,
|
| 85 |
+
},
|
| 86 |
+
});
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
async deleteApp(orgId: string) {
|
| 90 |
+
const app = await this._oauthApp.model.oAuthApp.findFirst({
|
| 91 |
+
where: {
|
| 92 |
+
organizationId: orgId,
|
| 93 |
+
deletedAt: null,
|
| 94 |
+
},
|
| 95 |
+
});
|
| 96 |
+
if (!app) {
|
| 97 |
+
return null;
|
| 98 |
+
}
|
| 99 |
+
return this._oauthApp.model.oAuthApp.update({
|
| 100 |
+
where: { id: app.id },
|
| 101 |
+
data: {
|
| 102 |
+
deletedAt: new Date(),
|
| 103 |
+
},
|
| 104 |
+
});
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
async updateClientSecret(orgId: string, newSecret: string) {
|
| 108 |
+
const app = await this._oauthApp.model.oAuthApp.findFirst({
|
| 109 |
+
where: {
|
| 110 |
+
organizationId: orgId,
|
| 111 |
+
deletedAt: null,
|
| 112 |
+
},
|
| 113 |
+
});
|
| 114 |
+
if (!app) {
|
| 115 |
+
return null;
|
| 116 |
+
}
|
| 117 |
+
return this._oauthApp.model.oAuthApp.update({
|
| 118 |
+
where: { id: app.id },
|
| 119 |
+
data: {
|
| 120 |
+
clientSecret: newSecret,
|
| 121 |
+
},
|
| 122 |
+
});
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
createAuthorization(data: {
|
| 126 |
+
oauthAppId: string;
|
| 127 |
+
userId: string;
|
| 128 |
+
organizationId: string;
|
| 129 |
+
authorizationCode: string;
|
| 130 |
+
codeExpiresAt: Date;
|
| 131 |
+
}) {
|
| 132 |
+
return this._oauthAuth.model.oAuthAuthorization.upsert({
|
| 133 |
+
where: {
|
| 134 |
+
oauthAppId_userId_organizationId: {
|
| 135 |
+
oauthAppId: data.oauthAppId,
|
| 136 |
+
userId: data.userId,
|
| 137 |
+
organizationId: data.organizationId,
|
| 138 |
+
},
|
| 139 |
+
},
|
| 140 |
+
create: {
|
| 141 |
+
oauthAppId: data.oauthAppId,
|
| 142 |
+
userId: data.userId,
|
| 143 |
+
organizationId: data.organizationId,
|
| 144 |
+
authorizationCode: data.authorizationCode,
|
| 145 |
+
codeExpiresAt: data.codeExpiresAt,
|
| 146 |
+
},
|
| 147 |
+
update: {
|
| 148 |
+
authorizationCode: data.authorizationCode,
|
| 149 |
+
codeExpiresAt: data.codeExpiresAt,
|
| 150 |
+
accessToken: null,
|
| 151 |
+
revokedAt: null,
|
| 152 |
+
},
|
| 153 |
+
});
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
findByCode(encryptedCode: string) {
|
| 157 |
+
return this._oauthAuth.model.oAuthAuthorization.findFirst({
|
| 158 |
+
where: {
|
| 159 |
+
authorizationCode: encryptedCode,
|
| 160 |
+
revokedAt: null,
|
| 161 |
+
},
|
| 162 |
+
});
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
exchangeCodeForToken(id: string, encryptedToken: string) {
|
| 166 |
+
return this._oauthAuth.model.oAuthAuthorization.update({
|
| 167 |
+
where: { id },
|
| 168 |
+
select: {
|
| 169 |
+
organizationId: true,
|
| 170 |
+
organization: {
|
| 171 |
+
select: {
|
| 172 |
+
paymentId: true,
|
| 173 |
+
}
|
| 174 |
+
}
|
| 175 |
+
},
|
| 176 |
+
data: {
|
| 177 |
+
accessToken: encryptedToken,
|
| 178 |
+
authorizationCode: null,
|
| 179 |
+
codeExpiresAt: null,
|
| 180 |
+
},
|
| 181 |
+
});
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
findByAccessToken(encryptedToken: string) {
|
| 185 |
+
return this._oauthAuth.model.oAuthAuthorization.findFirst({
|
| 186 |
+
where: {
|
| 187 |
+
accessToken: encryptedToken,
|
| 188 |
+
revokedAt: null,
|
| 189 |
+
},
|
| 190 |
+
include: {
|
| 191 |
+
organization: {
|
| 192 |
+
include: {
|
| 193 |
+
subscription: {
|
| 194 |
+
select: {
|
| 195 |
+
subscriptionTier: true,
|
| 196 |
+
totalChannels: true,
|
| 197 |
+
isLifetime: true,
|
| 198 |
+
},
|
| 199 |
+
},
|
| 200 |
+
},
|
| 201 |
+
},
|
| 202 |
+
user: {
|
| 203 |
+
select: { id: true },
|
| 204 |
+
},
|
| 205 |
+
},
|
| 206 |
+
});
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
getApprovedApps(userId: string) {
|
| 210 |
+
return this._oauthAuth.model.oAuthAuthorization.findMany({
|
| 211 |
+
where: {
|
| 212 |
+
userId,
|
| 213 |
+
revokedAt: null,
|
| 214 |
+
accessToken: { not: null },
|
| 215 |
+
},
|
| 216 |
+
include: {
|
| 217 |
+
oauthApp: {
|
| 218 |
+
include: {
|
| 219 |
+
picture: true,
|
| 220 |
+
},
|
| 221 |
+
},
|
| 222 |
+
},
|
| 223 |
+
orderBy: {
|
| 224 |
+
createdAt: 'desc',
|
| 225 |
+
},
|
| 226 |
+
});
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
revokeAuthorization(userId: string, authId: string) {
|
| 230 |
+
return this._oauthAuth.model.oAuthAuthorization.update({
|
| 231 |
+
where: {
|
| 232 |
+
id: authId,
|
| 233 |
+
userId,
|
| 234 |
+
},
|
| 235 |
+
data: {
|
| 236 |
+
revokedAt: new Date(),
|
| 237 |
+
},
|
| 238 |
+
});
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
revokeAllForApp(oauthAppId: string) {
|
| 242 |
+
return this._oauthAuth.model.oAuthAuthorization.updateMany({
|
| 243 |
+
where: {
|
| 244 |
+
oauthAppId,
|
| 245 |
+
revokedAt: null,
|
| 246 |
+
},
|
| 247 |
+
data: {
|
| 248 |
+
revokedAt: new Date(),
|
| 249 |
+
},
|
| 250 |
+
});
|
| 251 |
+
}
|
| 252 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/oauth/oauth.service.ts
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
| 2 |
+
import { OAuthRepository } from '@gitroom/nestjs-libraries/database/prisma/oauth/oauth.repository';
|
| 3 |
+
import { CreateOAuthAppDto } from '@gitroom/nestjs-libraries/dtos/oauth/create-oauth-app.dto';
|
| 4 |
+
import { UpdateOAuthAppDto } from '@gitroom/nestjs-libraries/dtos/oauth/update-oauth-app.dto';
|
| 5 |
+
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
|
| 6 |
+
import { AuthService } from '@gitroom/helpers/auth/auth.service';
|
| 7 |
+
|
| 8 |
+
@Injectable()
|
| 9 |
+
export class OAuthService {
|
| 10 |
+
constructor(private _oauthRepository: OAuthRepository) {}
|
| 11 |
+
|
| 12 |
+
async getApp(orgId: string) {
|
| 13 |
+
const app = await this._oauthRepository.getAppByOrgId(orgId);
|
| 14 |
+
if (!app) return false;
|
| 15 |
+
const { clientSecret, ...rest } = app;
|
| 16 |
+
return rest;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
async createApp(orgId: string, dto: CreateOAuthAppDto) {
|
| 20 |
+
const existing = await this._oauthRepository.getAppByOrgId(orgId);
|
| 21 |
+
if (existing) {
|
| 22 |
+
throw new HttpException(
|
| 23 |
+
'You can only have one OAuth application per organization',
|
| 24 |
+
HttpStatus.BAD_REQUEST
|
| 25 |
+
);
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
const clientId = 'pca_' + makeId(32);
|
| 29 |
+
const clientSecret = 'pcs_' + makeId(48);
|
| 30 |
+
const encryptedSecret = AuthService.fixedEncryption(clientSecret);
|
| 31 |
+
|
| 32 |
+
const app = await this._oauthRepository.createApp(orgId, {
|
| 33 |
+
name: dto.name,
|
| 34 |
+
description: dto.description,
|
| 35 |
+
pictureId: dto.pictureId,
|
| 36 |
+
redirectUrl: dto.redirectUrl,
|
| 37 |
+
clientId,
|
| 38 |
+
clientSecret: encryptedSecret,
|
| 39 |
+
});
|
| 40 |
+
|
| 41 |
+
return { ...app, clientSecret };
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
async updateApp(orgId: string, dto: UpdateOAuthAppDto) {
|
| 45 |
+
return this._oauthRepository.updateApp(orgId, {
|
| 46 |
+
...(dto.name && { name: dto.name }),
|
| 47 |
+
...(dto.description !== undefined && { description: dto.description }),
|
| 48 |
+
...(dto.pictureId !== undefined && { pictureId: dto.pictureId }),
|
| 49 |
+
...(dto.redirectUrl && { redirectUrl: dto.redirectUrl }),
|
| 50 |
+
});
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
async deleteApp(orgId: string) {
|
| 54 |
+
const app = await this._oauthRepository.getAppByOrgId(orgId);
|
| 55 |
+
if (!app) {
|
| 56 |
+
throw new HttpException('No OAuth app found', HttpStatus.NOT_FOUND);
|
| 57 |
+
}
|
| 58 |
+
await this._oauthRepository.revokeAllForApp(app.id);
|
| 59 |
+
await this._oauthRepository.deleteApp(orgId);
|
| 60 |
+
return { success: true };
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
async rotateSecret(orgId: string) {
|
| 64 |
+
const app = await this._oauthRepository.getAppByOrgId(orgId);
|
| 65 |
+
if (!app) {
|
| 66 |
+
throw new HttpException('No OAuth app found', HttpStatus.NOT_FOUND);
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
const newSecret = 'pcs_' + makeId(48);
|
| 70 |
+
const encrypted = AuthService.fixedEncryption(newSecret);
|
| 71 |
+
await this._oauthRepository.updateClientSecret(orgId, encrypted);
|
| 72 |
+
return { clientSecret: newSecret };
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
async validateAuthorizationRequest(clientId: string) {
|
| 76 |
+
const app = await this._oauthRepository.getAppByClientId(clientId);
|
| 77 |
+
if (!app) {
|
| 78 |
+
throw new HttpException('Invalid client_id', HttpStatus.BAD_REQUEST);
|
| 79 |
+
}
|
| 80 |
+
return app;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
async createAuthorizationCode(
|
| 84 |
+
oauthAppId: string,
|
| 85 |
+
userId: string,
|
| 86 |
+
organizationId: string
|
| 87 |
+
) {
|
| 88 |
+
const code = makeId(32);
|
| 89 |
+
const encryptedCode = AuthService.fixedEncryption(code);
|
| 90 |
+
const codeExpiresAt = new Date(Date.now() + 10 * 60 * 1000);
|
| 91 |
+
|
| 92 |
+
await this._oauthRepository.createAuthorization({
|
| 93 |
+
oauthAppId,
|
| 94 |
+
userId,
|
| 95 |
+
organizationId,
|
| 96 |
+
authorizationCode: encryptedCode,
|
| 97 |
+
codeExpiresAt,
|
| 98 |
+
});
|
| 99 |
+
|
| 100 |
+
return code;
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
async exchangeCodeForToken(
|
| 104 |
+
code: string,
|
| 105 |
+
clientId: string,
|
| 106 |
+
clientSecret: string
|
| 107 |
+
) {
|
| 108 |
+
const app = await this._oauthRepository.getAppByClientId(clientId);
|
| 109 |
+
if (!app) {
|
| 110 |
+
throw new HttpException(
|
| 111 |
+
{ error: 'invalid_client' },
|
| 112 |
+
HttpStatus.UNAUTHORIZED
|
| 113 |
+
);
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
if (app.clientSecret !== AuthService.fixedEncryption(clientSecret)) {
|
| 117 |
+
throw new HttpException(
|
| 118 |
+
{ error: 'invalid_client' },
|
| 119 |
+
HttpStatus.UNAUTHORIZED
|
| 120 |
+
);
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
const encryptedCode = AuthService.fixedEncryption(code);
|
| 124 |
+
const auth = await this._oauthRepository.findByCode(encryptedCode);
|
| 125 |
+
if (!auth || auth.oauthAppId !== app.id) {
|
| 126 |
+
throw new HttpException(
|
| 127 |
+
{ error: 'invalid_grant' },
|
| 128 |
+
HttpStatus.BAD_REQUEST
|
| 129 |
+
);
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
if (!auth.codeExpiresAt || new Date() > auth.codeExpiresAt) {
|
| 133 |
+
throw new HttpException(
|
| 134 |
+
{ error: 'invalid_grant', error_description: 'Code has expired' },
|
| 135 |
+
HttpStatus.BAD_REQUEST
|
| 136 |
+
);
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
const token = 'pos_' + makeId(40);
|
| 140 |
+
const encryptedToken = AuthService.fixedEncryption(token);
|
| 141 |
+
const {
|
| 142 |
+
organizationId,
|
| 143 |
+
organization: { paymentId },
|
| 144 |
+
} = await this._oauthRepository.exchangeCodeForToken(
|
| 145 |
+
auth.id,
|
| 146 |
+
encryptedToken
|
| 147 |
+
);
|
| 148 |
+
|
| 149 |
+
return {
|
| 150 |
+
id: organizationId,
|
| 151 |
+
cus: paymentId,
|
| 152 |
+
access_token: token,
|
| 153 |
+
token_type: 'bearer',
|
| 154 |
+
};
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
async getOrgByOAuthToken(token: string) {
|
| 158 |
+
const encrypted = AuthService.fixedEncryption(token);
|
| 159 |
+
return this._oauthRepository.findByAccessToken(encrypted);
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
async getApprovedApps(userId: string) {
|
| 163 |
+
return this._oauthRepository.getApprovedApps(userId);
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
async revokeApp(userId: string, authId: string) {
|
| 167 |
+
await this._oauthRepository.revokeAuthorization(userId, authId);
|
| 168 |
+
return { success: true };
|
| 169 |
+
}
|
| 170 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/organizations/organization.repository.ts
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';
|
| 2 |
+
import { Role, ShortLinkPreference, SubscriptionTier } from '@prisma/client';
|
| 3 |
+
import { Injectable } from '@nestjs/common';
|
| 4 |
+
import { AuthService } from '@gitroom/helpers/auth/auth.service';
|
| 5 |
+
import { CreateOrgUserDto } from '@gitroom/nestjs-libraries/dtos/auth/create.org.user.dto';
|
| 6 |
+
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
|
| 7 |
+
|
| 8 |
+
@Injectable()
|
| 9 |
+
export class OrganizationRepository {
|
| 10 |
+
constructor(
|
| 11 |
+
private _organization: PrismaRepository<'organization'>,
|
| 12 |
+
private _userOrg: PrismaRepository<'userOrganization'>,
|
| 13 |
+
private _user: PrismaRepository<'user'>
|
| 14 |
+
) {}
|
| 15 |
+
|
| 16 |
+
createMaxUser(id: string, name: string, saasName: string, email: string) {
|
| 17 |
+
return this._organization.model.organization.create({
|
| 18 |
+
select: {
|
| 19 |
+
id: true,
|
| 20 |
+
apiKey: true,
|
| 21 |
+
},
|
| 22 |
+
data: {
|
| 23 |
+
name: name ? `${name}###${id}` : `Unnamed User###${id}`,
|
| 24 |
+
apiKey: AuthService.fixedEncryption(makeId(20)),
|
| 25 |
+
isTrailing: false,
|
| 26 |
+
subscription: {
|
| 27 |
+
create: {
|
| 28 |
+
totalChannels: 1000000,
|
| 29 |
+
subscriptionTier: 'ULTIMATE',
|
| 30 |
+
isLifetime: true,
|
| 31 |
+
period: 'YEARLY',
|
| 32 |
+
},
|
| 33 |
+
},
|
| 34 |
+
users: {
|
| 35 |
+
create: {
|
| 36 |
+
role: Role.SUPERADMIN,
|
| 37 |
+
user: {
|
| 38 |
+
create: {
|
| 39 |
+
activated: true,
|
| 40 |
+
email: email
|
| 41 |
+
? email.split('@').join(`+${saasName}@`)
|
| 42 |
+
: `${saasName}+` + makeId(10) + '@postiz.com',
|
| 43 |
+
name: name ? `${name}###${id}` : `Unnamed User###${id}`,
|
| 44 |
+
providerName: 'LOCAL',
|
| 45 |
+
password: AuthService.hashPassword(makeId(500)),
|
| 46 |
+
timezone: 0,
|
| 47 |
+
},
|
| 48 |
+
},
|
| 49 |
+
},
|
| 50 |
+
},
|
| 51 |
+
},
|
| 52 |
+
});
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
getOrgByApiKey(api: string) {
|
| 56 |
+
return this._organization.model.organization.findFirst({
|
| 57 |
+
where: {
|
| 58 |
+
apiKey: api,
|
| 59 |
+
},
|
| 60 |
+
include: {
|
| 61 |
+
subscription: {
|
| 62 |
+
select: {
|
| 63 |
+
subscriptionTier: true,
|
| 64 |
+
totalChannels: true,
|
| 65 |
+
isLifetime: true,
|
| 66 |
+
},
|
| 67 |
+
},
|
| 68 |
+
},
|
| 69 |
+
});
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
getCount() {
|
| 73 |
+
return this._organization.model.organization.count();
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
getUserOrg(id: string) {
|
| 77 |
+
return this._userOrg.model.userOrganization.findFirst({
|
| 78 |
+
where: {
|
| 79 |
+
id,
|
| 80 |
+
},
|
| 81 |
+
select: {
|
| 82 |
+
user: true,
|
| 83 |
+
organization: {
|
| 84 |
+
include: {
|
| 85 |
+
users: {
|
| 86 |
+
select: {
|
| 87 |
+
id: true,
|
| 88 |
+
disabled: true,
|
| 89 |
+
role: true,
|
| 90 |
+
userId: true,
|
| 91 |
+
},
|
| 92 |
+
},
|
| 93 |
+
subscription: {
|
| 94 |
+
select: {
|
| 95 |
+
subscriptionTier: true,
|
| 96 |
+
totalChannels: true,
|
| 97 |
+
isLifetime: true,
|
| 98 |
+
},
|
| 99 |
+
},
|
| 100 |
+
},
|
| 101 |
+
},
|
| 102 |
+
},
|
| 103 |
+
});
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
getImpersonateUser(name: string) {
|
| 107 |
+
return this._userOrg.model.userOrganization.findMany({
|
| 108 |
+
where: {
|
| 109 |
+
OR: [
|
| 110 |
+
{
|
| 111 |
+
organizationId: {
|
| 112 |
+
contains: name,
|
| 113 |
+
},
|
| 114 |
+
},
|
| 115 |
+
{
|
| 116 |
+
user: {
|
| 117 |
+
OR: [
|
| 118 |
+
{
|
| 119 |
+
name: {
|
| 120 |
+
contains: name,
|
| 121 |
+
},
|
| 122 |
+
},
|
| 123 |
+
{
|
| 124 |
+
email: {
|
| 125 |
+
contains: name,
|
| 126 |
+
},
|
| 127 |
+
},
|
| 128 |
+
{
|
| 129 |
+
id: {
|
| 130 |
+
contains: name,
|
| 131 |
+
},
|
| 132 |
+
},
|
| 133 |
+
],
|
| 134 |
+
},
|
| 135 |
+
},
|
| 136 |
+
],
|
| 137 |
+
},
|
| 138 |
+
select: {
|
| 139 |
+
id: true,
|
| 140 |
+
organization: {
|
| 141 |
+
select: {
|
| 142 |
+
id: true,
|
| 143 |
+
},
|
| 144 |
+
},
|
| 145 |
+
user: {
|
| 146 |
+
select: {
|
| 147 |
+
id: true,
|
| 148 |
+
name: true,
|
| 149 |
+
email: true,
|
| 150 |
+
},
|
| 151 |
+
},
|
| 152 |
+
},
|
| 153 |
+
});
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
updateApiKey(orgId: string) {
|
| 157 |
+
return this._organization.model.organization.update({
|
| 158 |
+
where: {
|
| 159 |
+
id: orgId,
|
| 160 |
+
},
|
| 161 |
+
data: {
|
| 162 |
+
apiKey: AuthService.fixedEncryption(makeId(20)),
|
| 163 |
+
},
|
| 164 |
+
});
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
async getOrgsByUserId(userId: string) {
|
| 168 |
+
return this._organization.model.organization.findMany({
|
| 169 |
+
where: {
|
| 170 |
+
users: {
|
| 171 |
+
some: {
|
| 172 |
+
userId,
|
| 173 |
+
},
|
| 174 |
+
},
|
| 175 |
+
},
|
| 176 |
+
include: {
|
| 177 |
+
users: {
|
| 178 |
+
where: {
|
| 179 |
+
userId,
|
| 180 |
+
},
|
| 181 |
+
select: {
|
| 182 |
+
disabled: true,
|
| 183 |
+
role: true,
|
| 184 |
+
},
|
| 185 |
+
},
|
| 186 |
+
subscription: {
|
| 187 |
+
select: {
|
| 188 |
+
subscriptionTier: true,
|
| 189 |
+
totalChannels: true,
|
| 190 |
+
isLifetime: true,
|
| 191 |
+
createdAt: true,
|
| 192 |
+
},
|
| 193 |
+
},
|
| 194 |
+
},
|
| 195 |
+
});
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
async getOrgById(id: string) {
|
| 199 |
+
return this._organization.model.organization.findUnique({
|
| 200 |
+
where: {
|
| 201 |
+
id,
|
| 202 |
+
},
|
| 203 |
+
});
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
async addUserToOrg(
|
| 207 |
+
userId: string,
|
| 208 |
+
id: string,
|
| 209 |
+
orgId: string,
|
| 210 |
+
role: 'USER' | 'ADMIN'
|
| 211 |
+
) {
|
| 212 |
+
const checkIfInviteExists = await this._user.model.user.findFirst({
|
| 213 |
+
where: {
|
| 214 |
+
inviteId: id,
|
| 215 |
+
},
|
| 216 |
+
});
|
| 217 |
+
|
| 218 |
+
if (checkIfInviteExists) {
|
| 219 |
+
return false;
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
const checkForSubscription =
|
| 223 |
+
await this._organization.model.organization.findFirst({
|
| 224 |
+
where: {
|
| 225 |
+
id: orgId,
|
| 226 |
+
},
|
| 227 |
+
select: {
|
| 228 |
+
subscription: true,
|
| 229 |
+
},
|
| 230 |
+
});
|
| 231 |
+
|
| 232 |
+
if (
|
| 233 |
+
process.env.STRIPE_PUBLISHABLE_KEY &&
|
| 234 |
+
checkForSubscription?.subscription?.subscriptionTier ===
|
| 235 |
+
SubscriptionTier.STANDARD
|
| 236 |
+
) {
|
| 237 |
+
return false;
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
const create = await this._userOrg.model.userOrganization.create({
|
| 241 |
+
data: {
|
| 242 |
+
role,
|
| 243 |
+
userId,
|
| 244 |
+
organizationId: orgId,
|
| 245 |
+
},
|
| 246 |
+
});
|
| 247 |
+
|
| 248 |
+
await this._user.model.user.update({
|
| 249 |
+
where: {
|
| 250 |
+
id: userId,
|
| 251 |
+
},
|
| 252 |
+
data: {
|
| 253 |
+
inviteId: id,
|
| 254 |
+
},
|
| 255 |
+
});
|
| 256 |
+
|
| 257 |
+
return create;
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
async createOrgAndUser(
|
| 261 |
+
body: Omit<CreateOrgUserDto, 'providerToken'> & { providerId?: string },
|
| 262 |
+
hasEmail: boolean,
|
| 263 |
+
ip: string,
|
| 264 |
+
userAgent: string
|
| 265 |
+
) {
|
| 266 |
+
return this._organization.model.organization.create({
|
| 267 |
+
data: {
|
| 268 |
+
name: body.company,
|
| 269 |
+
apiKey: AuthService.fixedEncryption(makeId(20)),
|
| 270 |
+
allowTrial: true,
|
| 271 |
+
isTrailing: true,
|
| 272 |
+
users: {
|
| 273 |
+
create: {
|
| 274 |
+
role: Role.SUPERADMIN,
|
| 275 |
+
user: {
|
| 276 |
+
create: {
|
| 277 |
+
activated: body.provider !== 'LOCAL' || !hasEmail,
|
| 278 |
+
email: body.email,
|
| 279 |
+
password: body.password
|
| 280 |
+
? AuthService.hashPassword(body.password)
|
| 281 |
+
: '',
|
| 282 |
+
providerName: body.provider,
|
| 283 |
+
providerId: body.providerId || '',
|
| 284 |
+
timezone: 0,
|
| 285 |
+
ip,
|
| 286 |
+
agent: userAgent,
|
| 287 |
+
},
|
| 288 |
+
},
|
| 289 |
+
},
|
| 290 |
+
},
|
| 291 |
+
},
|
| 292 |
+
select: {
|
| 293 |
+
id: true,
|
| 294 |
+
users: {
|
| 295 |
+
select: {
|
| 296 |
+
user: true,
|
| 297 |
+
},
|
| 298 |
+
},
|
| 299 |
+
},
|
| 300 |
+
});
|
| 301 |
+
}
|
| 302 |
+
|
| 303 |
+
getOrgByCustomerId(customerId: string) {
|
| 304 |
+
return this._organization.model.organization.findFirst({
|
| 305 |
+
where: {
|
| 306 |
+
paymentId: customerId,
|
| 307 |
+
},
|
| 308 |
+
});
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
async setStreak(organizationId: string, type: 'start' | 'end') {
|
| 312 |
+
try {
|
| 313 |
+
await this._organization.model.organization.update({
|
| 314 |
+
where: {
|
| 315 |
+
id: organizationId,
|
| 316 |
+
...(type === 'start'
|
| 317 |
+
? {
|
| 318 |
+
streakSince: null,
|
| 319 |
+
}
|
| 320 |
+
: {}),
|
| 321 |
+
},
|
| 322 |
+
data: {
|
| 323 |
+
...(type === 'end' ? { streakSince: null } : {}),
|
| 324 |
+
...(type === 'start' ? { streakSince: new Date() } : {}),
|
| 325 |
+
},
|
| 326 |
+
});
|
| 327 |
+
} catch (err) {}
|
| 328 |
+
}
|
| 329 |
+
|
| 330 |
+
async getTeam(orgId: string) {
|
| 331 |
+
return this._organization.model.organization.findUnique({
|
| 332 |
+
where: {
|
| 333 |
+
id: orgId,
|
| 334 |
+
},
|
| 335 |
+
select: {
|
| 336 |
+
users: {
|
| 337 |
+
select: {
|
| 338 |
+
role: true,
|
| 339 |
+
user: {
|
| 340 |
+
select: {
|
| 341 |
+
email: true,
|
| 342 |
+
id: true,
|
| 343 |
+
sendSuccessEmails: true,
|
| 344 |
+
sendFailureEmails: true,
|
| 345 |
+
sendStreakEmails: true,
|
| 346 |
+
},
|
| 347 |
+
},
|
| 348 |
+
},
|
| 349 |
+
},
|
| 350 |
+
},
|
| 351 |
+
});
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
getAllUsersOrgs(orgId: string) {
|
| 355 |
+
return this._organization.model.organization.findUnique({
|
| 356 |
+
where: {
|
| 357 |
+
id: orgId,
|
| 358 |
+
},
|
| 359 |
+
select: {
|
| 360 |
+
users: {
|
| 361 |
+
select: {
|
| 362 |
+
user: {
|
| 363 |
+
select: {
|
| 364 |
+
email: true,
|
| 365 |
+
id: true,
|
| 366 |
+
sendSuccessEmails: true,
|
| 367 |
+
sendFailureEmails: true,
|
| 368 |
+
},
|
| 369 |
+
},
|
| 370 |
+
},
|
| 371 |
+
},
|
| 372 |
+
},
|
| 373 |
+
});
|
| 374 |
+
}
|
| 375 |
+
|
| 376 |
+
async deleteTeamMember(orgId: string, userId: string) {
|
| 377 |
+
return this._userOrg.model.userOrganization.delete({
|
| 378 |
+
where: {
|
| 379 |
+
userId_organizationId: {
|
| 380 |
+
userId,
|
| 381 |
+
organizationId: orgId,
|
| 382 |
+
},
|
| 383 |
+
},
|
| 384 |
+
});
|
| 385 |
+
}
|
| 386 |
+
|
| 387 |
+
disableOrEnableNonSuperAdminUsers(orgId: string, disable: boolean) {
|
| 388 |
+
return this._userOrg.model.userOrganization.updateMany({
|
| 389 |
+
where: {
|
| 390 |
+
organizationId: orgId,
|
| 391 |
+
role: {
|
| 392 |
+
not: Role.SUPERADMIN,
|
| 393 |
+
},
|
| 394 |
+
},
|
| 395 |
+
data: {
|
| 396 |
+
disabled: disable,
|
| 397 |
+
},
|
| 398 |
+
});
|
| 399 |
+
}
|
| 400 |
+
|
| 401 |
+
getShortlinkPreference(orgId: string) {
|
| 402 |
+
return this._organization.model.organization.findUnique({
|
| 403 |
+
where: {
|
| 404 |
+
id: orgId,
|
| 405 |
+
},
|
| 406 |
+
select: {
|
| 407 |
+
shortlink: true,
|
| 408 |
+
},
|
| 409 |
+
});
|
| 410 |
+
}
|
| 411 |
+
|
| 412 |
+
updateShortlinkPreference(orgId: string, shortlink: ShortLinkPreference) {
|
| 413 |
+
return this._organization.model.organization.update({
|
| 414 |
+
where: {
|
| 415 |
+
id: orgId,
|
| 416 |
+
},
|
| 417 |
+
data: {
|
| 418 |
+
shortlink,
|
| 419 |
+
},
|
| 420 |
+
});
|
| 421 |
+
}
|
| 422 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/organizations/organization.service.ts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { CreateOrgUserDto } from '@gitroom/nestjs-libraries/dtos/auth/create.org.user.dto';
|
| 2 |
+
import { Injectable } from '@nestjs/common';
|
| 3 |
+
import { OrganizationRepository } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.repository';
|
| 4 |
+
import { NotificationService } from '@gitroom/nestjs-libraries/database/prisma/notifications/notification.service';
|
| 5 |
+
import { AddTeamMemberDto } from '@gitroom/nestjs-libraries/dtos/settings/add.team.member.dto';
|
| 6 |
+
import { AuthService } from '@gitroom/helpers/auth/auth.service';
|
| 7 |
+
import dayjs from 'dayjs';
|
| 8 |
+
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
|
| 9 |
+
import { Organization, ShortLinkPreference } from '@prisma/client';
|
| 10 |
+
import { AutopostService } from '@gitroom/nestjs-libraries/database/prisma/autopost/autopost.service';
|
| 11 |
+
|
| 12 |
+
@Injectable()
|
| 13 |
+
export class OrganizationService {
|
| 14 |
+
constructor(
|
| 15 |
+
private _organizationRepository: OrganizationRepository,
|
| 16 |
+
private _notificationsService: NotificationService
|
| 17 |
+
) {}
|
| 18 |
+
async createOrgAndUser(
|
| 19 |
+
body: Omit<CreateOrgUserDto, 'providerToken'> & { providerId?: string },
|
| 20 |
+
ip: string,
|
| 21 |
+
userAgent: string
|
| 22 |
+
) {
|
| 23 |
+
return this._organizationRepository.createOrgAndUser(
|
| 24 |
+
body,
|
| 25 |
+
this._notificationsService.hasEmailProvider(),
|
| 26 |
+
ip,
|
| 27 |
+
userAgent
|
| 28 |
+
);
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
async getCount() {
|
| 32 |
+
return this._organizationRepository.getCount();
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
async createMaxUser(id: string, name: string, saasName: string, email: string) {
|
| 36 |
+
return this._organizationRepository.createMaxUser(id, name, saasName, email);
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
addUserToOrg(
|
| 40 |
+
userId: string,
|
| 41 |
+
id: string,
|
| 42 |
+
orgId: string,
|
| 43 |
+
role: 'USER' | 'ADMIN'
|
| 44 |
+
) {
|
| 45 |
+
return this._organizationRepository.addUserToOrg(userId, id, orgId, role);
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
getOrgById(id: string) {
|
| 49 |
+
return this._organizationRepository.getOrgById(id);
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
getOrgByApiKey(api: string) {
|
| 53 |
+
return this._organizationRepository.getOrgByApiKey(api);
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
getUserOrg(id: string) {
|
| 57 |
+
return this._organizationRepository.getUserOrg(id);
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
getOrgsByUserId(userId: string) {
|
| 61 |
+
return this._organizationRepository.getOrgsByUserId(userId);
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
updateApiKey(orgId: string) {
|
| 65 |
+
return this._organizationRepository.updateApiKey(orgId);
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
getTeam(orgId: string) {
|
| 69 |
+
return this._organizationRepository.getTeam(orgId);
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
async setStreak(organizationId: string, type: 'start' | 'end') {
|
| 73 |
+
return this._organizationRepository.setStreak(organizationId, type);
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
getOrgByCustomerId(customerId: string) {
|
| 77 |
+
return this._organizationRepository.getOrgByCustomerId(customerId);
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
async inviteTeamMember(orgId: string, body: AddTeamMemberDto) {
|
| 81 |
+
const timeLimit = dayjs().add(2, 'day').format('YYYY-MM-DD HH:mm:ss');
|
| 82 |
+
const id = makeId(5);
|
| 83 |
+
const url =
|
| 84 |
+
process.env.FRONTEND_URL +
|
| 85 |
+
`/?org=${AuthService.signJWT({ ...body, orgId, timeLimit, id })}`;
|
| 86 |
+
if (body.sendEmail) {
|
| 87 |
+
await this._notificationsService.sendEmail(
|
| 88 |
+
body.email,
|
| 89 |
+
'You have been invited to join an organization',
|
| 90 |
+
`You have been invited to join an organization. Click <a href="${url}">here</a> to join.<br />The link will expire in 2 days.`
|
| 91 |
+
);
|
| 92 |
+
}
|
| 93 |
+
return { url };
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
async deleteTeamMember(org: Organization, userId: string) {
|
| 97 |
+
const userOrgs = await this._organizationRepository.getOrgsByUserId(userId);
|
| 98 |
+
const findOrgToDelete = userOrgs.find((orgUser) => orgUser.id === org.id);
|
| 99 |
+
if (!findOrgToDelete) {
|
| 100 |
+
throw new Error('User is not part of this organization');
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
// @ts-ignore
|
| 104 |
+
const myRole = org.users[0].role;
|
| 105 |
+
const userRole = findOrgToDelete.users[0].role;
|
| 106 |
+
const myLevel = myRole === 'USER' ? 0 : myRole === 'ADMIN' ? 1 : 2;
|
| 107 |
+
const userLevel = userRole === 'USER' ? 0 : userRole === 'ADMIN' ? 1 : 2;
|
| 108 |
+
|
| 109 |
+
if (myLevel < userLevel) {
|
| 110 |
+
throw new Error('You do not have permission to delete this user');
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
return this._organizationRepository.deleteTeamMember(org.id, userId);
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
disableOrEnableNonSuperAdminUsers(orgId: string, disable: boolean) {
|
| 117 |
+
return this._organizationRepository.disableOrEnableNonSuperAdminUsers(
|
| 118 |
+
orgId,
|
| 119 |
+
disable
|
| 120 |
+
);
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
getShortlinkPreference(orgId: string) {
|
| 124 |
+
return this._organizationRepository.getShortlinkPreference(orgId);
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
updateShortlinkPreference(orgId: string, shortlink: ShortLinkPreference) {
|
| 128 |
+
return this._organizationRepository.updateShortlinkPreference(
|
| 129 |
+
orgId,
|
| 130 |
+
shortlink
|
| 131 |
+
);
|
| 132 |
+
}
|
| 133 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts
ADDED
|
@@ -0,0 +1,923 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';
|
| 2 |
+
import { Injectable } from '@nestjs/common';
|
| 3 |
+
import { Post as PostBody } from '@gitroom/nestjs-libraries/dtos/posts/create.post.dto';
|
| 4 |
+
import {
|
| 5 |
+
APPROVED_SUBMIT_FOR_ORDER,
|
| 6 |
+
CreationMethod,
|
| 7 |
+
Post,
|
| 8 |
+
State,
|
| 9 |
+
} from '@prisma/client';
|
| 10 |
+
import { GetPostsDto } from '@gitroom/nestjs-libraries/dtos/posts/get.posts.dto';
|
| 11 |
+
import { GetPostsListDto } from '@gitroom/nestjs-libraries/dtos/posts/get.posts.list.dto';
|
| 12 |
+
import dayjs from 'dayjs';
|
| 13 |
+
import isoWeek from 'dayjs/plugin/isoWeek';
|
| 14 |
+
import weekOfYear from 'dayjs/plugin/weekOfYear';
|
| 15 |
+
import isSameOrAfter from 'dayjs/plugin/isSameOrAfter';
|
| 16 |
+
import utc from 'dayjs/plugin/utc';
|
| 17 |
+
import { v4 as uuidv4 } from 'uuid';
|
| 18 |
+
import { CreateTagDto } from '@gitroom/nestjs-libraries/dtos/posts/create.tag.dto';
|
| 19 |
+
|
| 20 |
+
dayjs.extend(isoWeek);
|
| 21 |
+
dayjs.extend(weekOfYear);
|
| 22 |
+
dayjs.extend(isSameOrAfter);
|
| 23 |
+
dayjs.extend(utc);
|
| 24 |
+
|
| 25 |
+
@Injectable()
|
| 26 |
+
export class PostsRepository {
|
| 27 |
+
constructor(
|
| 28 |
+
private _post: PrismaRepository<'post'>,
|
| 29 |
+
private _popularPosts: PrismaRepository<'popularPosts'>,
|
| 30 |
+
private _comments: PrismaRepository<'comments'>,
|
| 31 |
+
private _tags: PrismaRepository<'tags'>,
|
| 32 |
+
private _tagsPosts: PrismaRepository<'tagsPosts'>,
|
| 33 |
+
private _errors: PrismaRepository<'errors'>
|
| 34 |
+
) {}
|
| 35 |
+
|
| 36 |
+
searchForMissingThreeHoursPosts() {
|
| 37 |
+
return this._post.model.post.findMany({
|
| 38 |
+
where: {
|
| 39 |
+
integration: {
|
| 40 |
+
refreshNeeded: false,
|
| 41 |
+
inBetweenSteps: false,
|
| 42 |
+
disabled: false,
|
| 43 |
+
deletedAt: null,
|
| 44 |
+
},
|
| 45 |
+
publishDate: {
|
| 46 |
+
gte: dayjs.utc().subtract(2, 'day').toDate(),
|
| 47 |
+
lt: dayjs.utc().toDate(),
|
| 48 |
+
},
|
| 49 |
+
state: 'QUEUE',
|
| 50 |
+
deletedAt: null,
|
| 51 |
+
parentPostId: null,
|
| 52 |
+
},
|
| 53 |
+
select: {
|
| 54 |
+
id: true,
|
| 55 |
+
organizationId: true,
|
| 56 |
+
integration: {
|
| 57 |
+
select: {
|
| 58 |
+
providerIdentifier: true,
|
| 59 |
+
},
|
| 60 |
+
},
|
| 61 |
+
publishDate: true,
|
| 62 |
+
},
|
| 63 |
+
});
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
getOldPosts(orgId: string, date: string) {
|
| 67 |
+
return this._post.model.post.findMany({
|
| 68 |
+
where: {
|
| 69 |
+
integration: {
|
| 70 |
+
refreshNeeded: false,
|
| 71 |
+
inBetweenSteps: false,
|
| 72 |
+
disabled: false,
|
| 73 |
+
},
|
| 74 |
+
organizationId: orgId,
|
| 75 |
+
publishDate: {
|
| 76 |
+
lte: dayjs(date).toDate(),
|
| 77 |
+
},
|
| 78 |
+
deletedAt: null,
|
| 79 |
+
parentPostId: null,
|
| 80 |
+
},
|
| 81 |
+
orderBy: {
|
| 82 |
+
publishDate: 'desc',
|
| 83 |
+
},
|
| 84 |
+
select: {
|
| 85 |
+
id: true,
|
| 86 |
+
content: true,
|
| 87 |
+
publishDate: true,
|
| 88 |
+
releaseURL: true,
|
| 89 |
+
state: true,
|
| 90 |
+
integration: {
|
| 91 |
+
select: {
|
| 92 |
+
id: true,
|
| 93 |
+
name: true,
|
| 94 |
+
providerIdentifier: true,
|
| 95 |
+
picture: true,
|
| 96 |
+
type: true,
|
| 97 |
+
},
|
| 98 |
+
},
|
| 99 |
+
},
|
| 100 |
+
});
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
updateImages(id: string, images: string) {
|
| 104 |
+
return this._post.model.post.update({
|
| 105 |
+
where: {
|
| 106 |
+
id,
|
| 107 |
+
},
|
| 108 |
+
data: {
|
| 109 |
+
image: images,
|
| 110 |
+
},
|
| 111 |
+
});
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
getPostUrls(orgId: string, ids: string[]) {
|
| 115 |
+
return this._post.model.post.findMany({
|
| 116 |
+
where: {
|
| 117 |
+
organizationId: orgId,
|
| 118 |
+
id: {
|
| 119 |
+
in: ids,
|
| 120 |
+
},
|
| 121 |
+
},
|
| 122 |
+
select: {
|
| 123 |
+
id: true,
|
| 124 |
+
releaseURL: true,
|
| 125 |
+
},
|
| 126 |
+
});
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
async getPosts(orgId: string, query: GetPostsDto) {
|
| 130 |
+
// Use the provided start and end dates directly
|
| 131 |
+
const startDate = dayjs.utc(query.startDate).toDate();
|
| 132 |
+
const endDate = dayjs.utc(query.endDate).toDate();
|
| 133 |
+
|
| 134 |
+
const list = await this._post.model.post.findMany({
|
| 135 |
+
where: {
|
| 136 |
+
AND: [
|
| 137 |
+
{
|
| 138 |
+
OR: [
|
| 139 |
+
{
|
| 140 |
+
organizationId: orgId,
|
| 141 |
+
},
|
| 142 |
+
],
|
| 143 |
+
},
|
| 144 |
+
{
|
| 145 |
+
OR: [
|
| 146 |
+
{
|
| 147 |
+
publishDate: {
|
| 148 |
+
gte: startDate,
|
| 149 |
+
lte: endDate,
|
| 150 |
+
},
|
| 151 |
+
},
|
| 152 |
+
{
|
| 153 |
+
intervalInDays: {
|
| 154 |
+
not: null,
|
| 155 |
+
},
|
| 156 |
+
},
|
| 157 |
+
],
|
| 158 |
+
},
|
| 159 |
+
],
|
| 160 |
+
integration: {
|
| 161 |
+
deletedAt: null,
|
| 162 |
+
organizationId: orgId,
|
| 163 |
+
},
|
| 164 |
+
deletedAt: null,
|
| 165 |
+
parentPostId: null,
|
| 166 |
+
...(query.customer
|
| 167 |
+
? {
|
| 168 |
+
integration: {
|
| 169 |
+
customerId: query.customer,
|
| 170 |
+
},
|
| 171 |
+
}
|
| 172 |
+
: {}),
|
| 173 |
+
},
|
| 174 |
+
select: {
|
| 175 |
+
id: true,
|
| 176 |
+
content: true,
|
| 177 |
+
publishDate: true,
|
| 178 |
+
releaseURL: true,
|
| 179 |
+
releaseId: true,
|
| 180 |
+
state: true,
|
| 181 |
+
intervalInDays: true,
|
| 182 |
+
group: true,
|
| 183 |
+
creationMethod: true,
|
| 184 |
+
tags: {
|
| 185 |
+
select: {
|
| 186 |
+
tag: true,
|
| 187 |
+
},
|
| 188 |
+
},
|
| 189 |
+
integration: {
|
| 190 |
+
select: {
|
| 191 |
+
id: true,
|
| 192 |
+
providerIdentifier: true,
|
| 193 |
+
name: true,
|
| 194 |
+
picture: true,
|
| 195 |
+
},
|
| 196 |
+
},
|
| 197 |
+
},
|
| 198 |
+
});
|
| 199 |
+
|
| 200 |
+
return list.reduce((all, post) => {
|
| 201 |
+
if (!post.intervalInDays) {
|
| 202 |
+
return [...all, post];
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
const addMorePosts = [];
|
| 206 |
+
let startingDate = dayjs.utc(post.publishDate);
|
| 207 |
+
while (dayjs.utc(endDate).isSameOrAfter(startingDate)) {
|
| 208 |
+
if (dayjs(startingDate).isSameOrAfter(dayjs.utc(post.publishDate))) {
|
| 209 |
+
addMorePosts.push({
|
| 210 |
+
...post,
|
| 211 |
+
publishDate: startingDate.toDate(),
|
| 212 |
+
actualDate: post.publishDate,
|
| 213 |
+
});
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
startingDate = startingDate.add(post.intervalInDays, 'days');
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
return [...all, ...addMorePosts];
|
| 220 |
+
}, [] as any[]);
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
async getPostsList(orgId: string, query: GetPostsListDto) {
|
| 224 |
+
const page = query.page || 0;
|
| 225 |
+
const limit = query.limit || 20;
|
| 226 |
+
const skip = page * limit;
|
| 227 |
+
|
| 228 |
+
const stateFilter = query.state || 'all';
|
| 229 |
+
const stateAndDate =
|
| 230 |
+
stateFilter === 'scheduled'
|
| 231 |
+
? {
|
| 232 |
+
state: State.QUEUE,
|
| 233 |
+
}
|
| 234 |
+
: stateFilter === 'draft'
|
| 235 |
+
? { state: State.DRAFT }
|
| 236 |
+
: stateFilter === 'published'
|
| 237 |
+
? { state: State.PUBLISHED }
|
| 238 |
+
: {
|
| 239 |
+
state: {
|
| 240 |
+
in: [State.QUEUE, State.DRAFT, State.PUBLISHED, State.ERROR],
|
| 241 |
+
},
|
| 242 |
+
};
|
| 243 |
+
|
| 244 |
+
const orderDirection: 'asc' | 'desc' =
|
| 245 |
+
stateFilter === 'published' ? 'desc' : 'asc';
|
| 246 |
+
|
| 247 |
+
const where = {
|
| 248 |
+
AND: [
|
| 249 |
+
{
|
| 250 |
+
OR: [
|
| 251 |
+
{
|
| 252 |
+
organizationId: orgId,
|
| 253 |
+
},
|
| 254 |
+
],
|
| 255 |
+
},
|
| 256 |
+
],
|
| 257 |
+
...stateAndDate,
|
| 258 |
+
// Published posts were already posted (publishDate in the past), so fetch
|
| 259 |
+
// all of them; everything else stays upcoming. Ordering handles the rest.
|
| 260 |
+
...(stateFilter === 'published'
|
| 261 |
+
? {}
|
| 262 |
+
: { publishDate: { gte: dayjs.utc().toDate() } }),
|
| 263 |
+
deletedAt: null as Date | null,
|
| 264 |
+
parentPostId: null as string | null,
|
| 265 |
+
intervalInDays: null as number | null,
|
| 266 |
+
|
| 267 |
+
integration: {
|
| 268 |
+
deletedAt: null as any,
|
| 269 |
+
organizationId: orgId,
|
| 270 |
+
...(query.customer
|
| 271 |
+
? {
|
| 272 |
+
customerId: query.customer,
|
| 273 |
+
}
|
| 274 |
+
: {}),
|
| 275 |
+
},
|
| 276 |
+
};
|
| 277 |
+
|
| 278 |
+
const [posts, total] = await Promise.all([
|
| 279 |
+
this._post.model.post.findMany({
|
| 280 |
+
where,
|
| 281 |
+
skip,
|
| 282 |
+
take: limit,
|
| 283 |
+
orderBy: {
|
| 284 |
+
publishDate: orderDirection,
|
| 285 |
+
},
|
| 286 |
+
select: {
|
| 287 |
+
id: true,
|
| 288 |
+
content: true,
|
| 289 |
+
publishDate: true,
|
| 290 |
+
releaseURL: true,
|
| 291 |
+
releaseId: true,
|
| 292 |
+
state: true,
|
| 293 |
+
intervalInDays: true,
|
| 294 |
+
group: true,
|
| 295 |
+
creationMethod: true,
|
| 296 |
+
tags: {
|
| 297 |
+
select: {
|
| 298 |
+
tag: true,
|
| 299 |
+
},
|
| 300 |
+
},
|
| 301 |
+
integration: {
|
| 302 |
+
select: {
|
| 303 |
+
id: true,
|
| 304 |
+
providerIdentifier: true,
|
| 305 |
+
name: true,
|
| 306 |
+
picture: true,
|
| 307 |
+
},
|
| 308 |
+
},
|
| 309 |
+
},
|
| 310 |
+
}),
|
| 311 |
+
this._post.model.post.count({ where }),
|
| 312 |
+
]);
|
| 313 |
+
|
| 314 |
+
return {
|
| 315 |
+
posts,
|
| 316 |
+
total,
|
| 317 |
+
page,
|
| 318 |
+
limit,
|
| 319 |
+
hasMore: skip + posts.length < total,
|
| 320 |
+
};
|
| 321 |
+
}
|
| 322 |
+
|
| 323 |
+
async deletePost(orgId: string, group: string) {
|
| 324 |
+
await this._post.model.post.updateMany({
|
| 325 |
+
where: {
|
| 326 |
+
organizationId: orgId,
|
| 327 |
+
group,
|
| 328 |
+
},
|
| 329 |
+
data: {
|
| 330 |
+
deletedAt: new Date(),
|
| 331 |
+
},
|
| 332 |
+
});
|
| 333 |
+
|
| 334 |
+
return this._post.model.post.findFirst({
|
| 335 |
+
where: {
|
| 336 |
+
organizationId: orgId,
|
| 337 |
+
group,
|
| 338 |
+
parentPostId: null,
|
| 339 |
+
},
|
| 340 |
+
select: {
|
| 341 |
+
id: true,
|
| 342 |
+
},
|
| 343 |
+
});
|
| 344 |
+
}
|
| 345 |
+
|
| 346 |
+
getPostsByGroup(orgId: string, group: string) {
|
| 347 |
+
return this._post.model.post.findMany({
|
| 348 |
+
where: {
|
| 349 |
+
group,
|
| 350 |
+
...(orgId ? { organizationId: orgId } : {}),
|
| 351 |
+
deletedAt: null,
|
| 352 |
+
},
|
| 353 |
+
include: {
|
| 354 |
+
integration: true,
|
| 355 |
+
tags: {
|
| 356 |
+
select: {
|
| 357 |
+
tag: true,
|
| 358 |
+
},
|
| 359 |
+
},
|
| 360 |
+
},
|
| 361 |
+
});
|
| 362 |
+
}
|
| 363 |
+
|
| 364 |
+
getPost(
|
| 365 |
+
id: string,
|
| 366 |
+
includeIntegration = false,
|
| 367 |
+
orgId?: string,
|
| 368 |
+
isFirst?: boolean
|
| 369 |
+
) {
|
| 370 |
+
return this._post.model.post.findUnique({
|
| 371 |
+
where: {
|
| 372 |
+
id,
|
| 373 |
+
...(orgId ? { organizationId: orgId } : {}),
|
| 374 |
+
deletedAt: null,
|
| 375 |
+
},
|
| 376 |
+
include: {
|
| 377 |
+
...(includeIntegration
|
| 378 |
+
? {
|
| 379 |
+
integration: true,
|
| 380 |
+
tags: {
|
| 381 |
+
select: {
|
| 382 |
+
tag: true,
|
| 383 |
+
},
|
| 384 |
+
},
|
| 385 |
+
}
|
| 386 |
+
: {}),
|
| 387 |
+
childrenPost: true,
|
| 388 |
+
},
|
| 389 |
+
});
|
| 390 |
+
}
|
| 391 |
+
|
| 392 |
+
updatePost(id: string, postId: string, releaseURL: string) {
|
| 393 |
+
return this._post.model.post.update({
|
| 394 |
+
where: {
|
| 395 |
+
id,
|
| 396 |
+
},
|
| 397 |
+
data: {
|
| 398 |
+
state: 'PUBLISHED',
|
| 399 |
+
releaseURL,
|
| 400 |
+
releaseId: postId,
|
| 401 |
+
},
|
| 402 |
+
});
|
| 403 |
+
}
|
| 404 |
+
|
| 405 |
+
updateReleaseId(id: string, orgId: string, releaseId: string) {
|
| 406 |
+
return this._post.model.post.update({
|
| 407 |
+
where: {
|
| 408 |
+
id,
|
| 409 |
+
organizationId: orgId,
|
| 410 |
+
releaseId: 'missing',
|
| 411 |
+
},
|
| 412 |
+
data: {
|
| 413 |
+
releaseId: String(releaseId),
|
| 414 |
+
},
|
| 415 |
+
});
|
| 416 |
+
}
|
| 417 |
+
|
| 418 |
+
async changeState(id: string, state: State, err?: any, body?: any) {
|
| 419 |
+
const update = await this._post.model.post.update({
|
| 420 |
+
where: {
|
| 421 |
+
id,
|
| 422 |
+
},
|
| 423 |
+
data: {
|
| 424 |
+
state,
|
| 425 |
+
...(err
|
| 426 |
+
? { error: typeof err === 'string' ? err : JSON.stringify(err) }
|
| 427 |
+
: {}),
|
| 428 |
+
},
|
| 429 |
+
include: {
|
| 430 |
+
integration: {
|
| 431 |
+
select: {
|
| 432 |
+
providerIdentifier: true,
|
| 433 |
+
},
|
| 434 |
+
},
|
| 435 |
+
},
|
| 436 |
+
});
|
| 437 |
+
|
| 438 |
+
if (state === 'ERROR' && err && body) {
|
| 439 |
+
try {
|
| 440 |
+
await this._errors.model.errors.create({
|
| 441 |
+
data: {
|
| 442 |
+
message: typeof err === 'string' ? err : JSON.stringify(err),
|
| 443 |
+
organizationId: update.organizationId,
|
| 444 |
+
platform: update.integration.providerIdentifier,
|
| 445 |
+
postId: update.id,
|
| 446 |
+
body: typeof body === 'string' ? body : JSON.stringify(body),
|
| 447 |
+
},
|
| 448 |
+
});
|
| 449 |
+
} catch (err) {}
|
| 450 |
+
}
|
| 451 |
+
|
| 452 |
+
return update;
|
| 453 |
+
}
|
| 454 |
+
|
| 455 |
+
getErrorsByPostIds(postIds: string[]) {
|
| 456 |
+
return this._errors.model.errors.findMany({
|
| 457 |
+
where: {
|
| 458 |
+
postId: { in: postIds },
|
| 459 |
+
},
|
| 460 |
+
orderBy: { createdAt: 'desc' },
|
| 461 |
+
});
|
| 462 |
+
}
|
| 463 |
+
|
| 464 |
+
async changeDate(
|
| 465 |
+
orgId: string,
|
| 466 |
+
id: string,
|
| 467 |
+
date: string,
|
| 468 |
+
isDraft: boolean,
|
| 469 |
+
action: 'schedule' | 'update' = 'schedule'
|
| 470 |
+
) {
|
| 471 |
+
return this._post.model.post.update({
|
| 472 |
+
where: {
|
| 473 |
+
organizationId: orgId,
|
| 474 |
+
id,
|
| 475 |
+
},
|
| 476 |
+
data: {
|
| 477 |
+
publishDate: dayjs(date).toDate(),
|
| 478 |
+
// schedule: set state to QUEUE (or DRAFT if it was a draft)
|
| 479 |
+
// update: don't change the state
|
| 480 |
+
...(action === 'schedule'
|
| 481 |
+
? {
|
| 482 |
+
state: isDraft ? 'DRAFT' : 'QUEUE',
|
| 483 |
+
releaseId: null,
|
| 484 |
+
releaseURL: null,
|
| 485 |
+
}
|
| 486 |
+
: {}),
|
| 487 |
+
},
|
| 488 |
+
});
|
| 489 |
+
}
|
| 490 |
+
|
| 491 |
+
countPostsFromDay(orgId: string, date: Date) {
|
| 492 |
+
return this._post.model.post.count({
|
| 493 |
+
where: {
|
| 494 |
+
organizationId: orgId,
|
| 495 |
+
publishDate: {
|
| 496 |
+
gte: date,
|
| 497 |
+
},
|
| 498 |
+
OR: [
|
| 499 |
+
{
|
| 500 |
+
deletedAt: null,
|
| 501 |
+
state: {
|
| 502 |
+
in: ['QUEUE'],
|
| 503 |
+
},
|
| 504 |
+
},
|
| 505 |
+
{
|
| 506 |
+
state: 'PUBLISHED',
|
| 507 |
+
},
|
| 508 |
+
],
|
| 509 |
+
},
|
| 510 |
+
});
|
| 511 |
+
}
|
| 512 |
+
|
| 513 |
+
async createOrUpdatePost(
|
| 514 |
+
state: 'draft' | 'schedule' | 'now' | 'update',
|
| 515 |
+
orgId: string,
|
| 516 |
+
date: string,
|
| 517 |
+
body: PostBody,
|
| 518 |
+
tags: { value: string; label: string }[],
|
| 519 |
+
creationMethod: CreationMethod,
|
| 520 |
+
inter?: number
|
| 521 |
+
) {
|
| 522 |
+
const posts: Post[] = [];
|
| 523 |
+
const uuid = uuidv4();
|
| 524 |
+
|
| 525 |
+
for (const value of body.value) {
|
| 526 |
+
const updateData = (type: 'create' | 'update') => ({
|
| 527 |
+
publishDate: dayjs(date).toDate(),
|
| 528 |
+
integration: {
|
| 529 |
+
connect: {
|
| 530 |
+
id: body.integration.id,
|
| 531 |
+
organizationId: orgId,
|
| 532 |
+
},
|
| 533 |
+
},
|
| 534 |
+
...(posts?.[posts.length - 1]?.id
|
| 535 |
+
? {
|
| 536 |
+
parentPost: {
|
| 537 |
+
connect: {
|
| 538 |
+
id: posts[posts.length - 1]?.id,
|
| 539 |
+
},
|
| 540 |
+
},
|
| 541 |
+
}
|
| 542 |
+
: type === 'update'
|
| 543 |
+
? {
|
| 544 |
+
parentPost: {
|
| 545 |
+
disconnect: true,
|
| 546 |
+
},
|
| 547 |
+
}
|
| 548 |
+
: {}),
|
| 549 |
+
content: value.content,
|
| 550 |
+
delay: value.delay || 0,
|
| 551 |
+
group: uuid,
|
| 552 |
+
intervalInDays: inter ? +inter : null,
|
| 553 |
+
approvedSubmitForOrder: APPROVED_SUBMIT_FOR_ORDER.NO,
|
| 554 |
+
...(type === 'create' ? { creationMethod } : {}),
|
| 555 |
+
...(state === 'update'
|
| 556 |
+
? {}
|
| 557 |
+
: {
|
| 558 |
+
state:
|
| 559 |
+
state === 'draft' ? ('DRAFT' as const) : ('QUEUE' as const),
|
| 560 |
+
}),
|
| 561 |
+
image: JSON.stringify(value.image),
|
| 562 |
+
settings: JSON.stringify(body.settings),
|
| 563 |
+
organization: {
|
| 564 |
+
connect: {
|
| 565 |
+
id: orgId,
|
| 566 |
+
},
|
| 567 |
+
},
|
| 568 |
+
});
|
| 569 |
+
|
| 570 |
+
posts.push(
|
| 571 |
+
await this._post.model.post.upsert({
|
| 572 |
+
where: {
|
| 573 |
+
id: value.id || uuidv4(),
|
| 574 |
+
},
|
| 575 |
+
create: { ...updateData('create') },
|
| 576 |
+
update: {
|
| 577 |
+
...updateData('update'),
|
| 578 |
+
lastMessage: {
|
| 579 |
+
disconnect: true,
|
| 580 |
+
},
|
| 581 |
+
submittedForOrder: {
|
| 582 |
+
disconnect: true,
|
| 583 |
+
},
|
| 584 |
+
},
|
| 585 |
+
})
|
| 586 |
+
);
|
| 587 |
+
|
| 588 |
+
if (posts.length === 1) {
|
| 589 |
+
await this._tagsPosts.model.tagsPosts.deleteMany({
|
| 590 |
+
where: {
|
| 591 |
+
post: {
|
| 592 |
+
id: posts[0].id,
|
| 593 |
+
},
|
| 594 |
+
},
|
| 595 |
+
});
|
| 596 |
+
|
| 597 |
+
if (tags.length) {
|
| 598 |
+
const tagsList = await this._tags.model.tags.findMany({
|
| 599 |
+
where: {
|
| 600 |
+
orgId: orgId,
|
| 601 |
+
name: {
|
| 602 |
+
in: tags.map((tag) => tag.label).filter((f) => f),
|
| 603 |
+
},
|
| 604 |
+
},
|
| 605 |
+
});
|
| 606 |
+
|
| 607 |
+
if (tagsList.length) {
|
| 608 |
+
await this._post.model.post.update({
|
| 609 |
+
where: {
|
| 610 |
+
id: posts[posts.length - 1].id,
|
| 611 |
+
},
|
| 612 |
+
data: {
|
| 613 |
+
tags: {
|
| 614 |
+
createMany: {
|
| 615 |
+
data: tagsList.map((tag) => ({
|
| 616 |
+
tagId: tag.id,
|
| 617 |
+
})),
|
| 618 |
+
},
|
| 619 |
+
},
|
| 620 |
+
},
|
| 621 |
+
});
|
| 622 |
+
}
|
| 623 |
+
}
|
| 624 |
+
}
|
| 625 |
+
}
|
| 626 |
+
|
| 627 |
+
const previousPost = body.group
|
| 628 |
+
? (
|
| 629 |
+
await this._post.model.post.findFirst({
|
| 630 |
+
where: {
|
| 631 |
+
group: body.group,
|
| 632 |
+
deletedAt: null,
|
| 633 |
+
parentPostId: null,
|
| 634 |
+
},
|
| 635 |
+
select: {
|
| 636 |
+
id: true,
|
| 637 |
+
},
|
| 638 |
+
})
|
| 639 |
+
)?.id!
|
| 640 |
+
: undefined;
|
| 641 |
+
|
| 642 |
+
if (body.group) {
|
| 643 |
+
await this._post.model.post.updateMany({
|
| 644 |
+
where: {
|
| 645 |
+
group: body.group,
|
| 646 |
+
deletedAt: null,
|
| 647 |
+
},
|
| 648 |
+
data: {
|
| 649 |
+
parentPostId: null,
|
| 650 |
+
deletedAt: new Date(),
|
| 651 |
+
},
|
| 652 |
+
});
|
| 653 |
+
}
|
| 654 |
+
|
| 655 |
+
return { previousPost, posts };
|
| 656 |
+
}
|
| 657 |
+
|
| 658 |
+
async submit(id: string, order: string, buyerOrganizationId: string) {
|
| 659 |
+
return this._post.model.post.update({
|
| 660 |
+
where: {
|
| 661 |
+
id,
|
| 662 |
+
},
|
| 663 |
+
data: {
|
| 664 |
+
submittedForOrderId: order,
|
| 665 |
+
approvedSubmitForOrder: 'WAITING_CONFIRMATION',
|
| 666 |
+
submittedForOrganizationId: buyerOrganizationId,
|
| 667 |
+
},
|
| 668 |
+
select: {
|
| 669 |
+
id: true,
|
| 670 |
+
description: true,
|
| 671 |
+
submittedForOrder: {
|
| 672 |
+
select: {
|
| 673 |
+
messageGroupId: true,
|
| 674 |
+
},
|
| 675 |
+
},
|
| 676 |
+
},
|
| 677 |
+
});
|
| 678 |
+
}
|
| 679 |
+
|
| 680 |
+
updateMessage(id: string, messageId: string) {
|
| 681 |
+
return this._post.model.post.update({
|
| 682 |
+
where: {
|
| 683 |
+
id,
|
| 684 |
+
},
|
| 685 |
+
data: {
|
| 686 |
+
lastMessageId: messageId,
|
| 687 |
+
},
|
| 688 |
+
});
|
| 689 |
+
}
|
| 690 |
+
|
| 691 |
+
getPostById(id: string, org?: string) {
|
| 692 |
+
return this._post.model.post.findUnique({
|
| 693 |
+
where: {
|
| 694 |
+
id,
|
| 695 |
+
...(org ? { organizationId: org } : {}),
|
| 696 |
+
},
|
| 697 |
+
include: {
|
| 698 |
+
integration: true,
|
| 699 |
+
submittedForOrder: {
|
| 700 |
+
include: {
|
| 701 |
+
posts: {
|
| 702 |
+
where: {
|
| 703 |
+
state: 'PUBLISHED',
|
| 704 |
+
},
|
| 705 |
+
},
|
| 706 |
+
ordersItems: true,
|
| 707 |
+
seller: {
|
| 708 |
+
select: {
|
| 709 |
+
id: true,
|
| 710 |
+
account: true,
|
| 711 |
+
},
|
| 712 |
+
},
|
| 713 |
+
},
|
| 714 |
+
},
|
| 715 |
+
},
|
| 716 |
+
});
|
| 717 |
+
}
|
| 718 |
+
|
| 719 |
+
findAllExistingCategories() {
|
| 720 |
+
return this._popularPosts.model.popularPosts.findMany({
|
| 721 |
+
select: {
|
| 722 |
+
category: true,
|
| 723 |
+
},
|
| 724 |
+
distinct: ['category'],
|
| 725 |
+
});
|
| 726 |
+
}
|
| 727 |
+
|
| 728 |
+
findAllExistingTopicsOfCategory(category: string) {
|
| 729 |
+
return this._popularPosts.model.popularPosts.findMany({
|
| 730 |
+
where: {
|
| 731 |
+
category,
|
| 732 |
+
},
|
| 733 |
+
select: {
|
| 734 |
+
topic: true,
|
| 735 |
+
},
|
| 736 |
+
distinct: ['topic'],
|
| 737 |
+
});
|
| 738 |
+
}
|
| 739 |
+
|
| 740 |
+
findPopularPosts(category: string, topic?: string) {
|
| 741 |
+
return this._popularPosts.model.popularPosts.findMany({
|
| 742 |
+
where: {
|
| 743 |
+
category,
|
| 744 |
+
...(topic ? { topic } : {}),
|
| 745 |
+
},
|
| 746 |
+
select: {
|
| 747 |
+
content: true,
|
| 748 |
+
hook: true,
|
| 749 |
+
},
|
| 750 |
+
});
|
| 751 |
+
}
|
| 752 |
+
|
| 753 |
+
createPopularPosts(post: {
|
| 754 |
+
category: string;
|
| 755 |
+
topic: string;
|
| 756 |
+
content: string;
|
| 757 |
+
hook: string;
|
| 758 |
+
}) {
|
| 759 |
+
return this._popularPosts.model.popularPosts.create({
|
| 760 |
+
data: {
|
| 761 |
+
category: 'category',
|
| 762 |
+
topic: 'topic',
|
| 763 |
+
content: 'content',
|
| 764 |
+
hook: 'hook',
|
| 765 |
+
},
|
| 766 |
+
});
|
| 767 |
+
}
|
| 768 |
+
|
| 769 |
+
async getPostsCountsByDates(
|
| 770 |
+
orgId: string,
|
| 771 |
+
times: number[],
|
| 772 |
+
date: dayjs.Dayjs
|
| 773 |
+
) {
|
| 774 |
+
const dates = await this._post.model.post.findMany({
|
| 775 |
+
where: {
|
| 776 |
+
deletedAt: null,
|
| 777 |
+
organizationId: orgId,
|
| 778 |
+
publishDate: {
|
| 779 |
+
in: times.map((time) => {
|
| 780 |
+
return date.clone().add(time, 'minutes').toDate();
|
| 781 |
+
}),
|
| 782 |
+
},
|
| 783 |
+
},
|
| 784 |
+
});
|
| 785 |
+
|
| 786 |
+
return times.filter(
|
| 787 |
+
(time) =>
|
| 788 |
+
date.clone().add(time, 'minutes').isAfter(dayjs.utc()) &&
|
| 789 |
+
!dates.find((dateFind) => {
|
| 790 |
+
return (
|
| 791 |
+
dayjs
|
| 792 |
+
.utc(dateFind.publishDate)
|
| 793 |
+
.diff(date.clone().startOf('day'), 'minutes') == time
|
| 794 |
+
);
|
| 795 |
+
})
|
| 796 |
+
);
|
| 797 |
+
}
|
| 798 |
+
|
| 799 |
+
async getComments(postId: string) {
|
| 800 |
+
return this._comments.model.comments.findMany({
|
| 801 |
+
where: {
|
| 802 |
+
postId,
|
| 803 |
+
},
|
| 804 |
+
orderBy: {
|
| 805 |
+
createdAt: 'asc',
|
| 806 |
+
},
|
| 807 |
+
});
|
| 808 |
+
}
|
| 809 |
+
|
| 810 |
+
async getTags(orgId: string) {
|
| 811 |
+
return this._tags.model.tags.findMany({
|
| 812 |
+
where: {
|
| 813 |
+
orgId,
|
| 814 |
+
deletedAt: null,
|
| 815 |
+
},
|
| 816 |
+
});
|
| 817 |
+
}
|
| 818 |
+
|
| 819 |
+
createTag(orgId: string, body: CreateTagDto) {
|
| 820 |
+
return this._tags.model.tags.create({
|
| 821 |
+
data: {
|
| 822 |
+
orgId,
|
| 823 |
+
name: body.name,
|
| 824 |
+
color: body.color,
|
| 825 |
+
},
|
| 826 |
+
});
|
| 827 |
+
}
|
| 828 |
+
|
| 829 |
+
editTag(id: string, orgId: string, body: CreateTagDto) {
|
| 830 |
+
return this._tags.model.tags.update({
|
| 831 |
+
where: {
|
| 832 |
+
id,
|
| 833 |
+
},
|
| 834 |
+
data: {
|
| 835 |
+
name: body.name,
|
| 836 |
+
color: body.color,
|
| 837 |
+
},
|
| 838 |
+
});
|
| 839 |
+
}
|
| 840 |
+
|
| 841 |
+
deleteTag(id: string, orgId: string) {
|
| 842 |
+
return this._tags.model.tags.update({
|
| 843 |
+
where: {
|
| 844 |
+
id,
|
| 845 |
+
orgId,
|
| 846 |
+
},
|
| 847 |
+
data: {
|
| 848 |
+
deletedAt: new Date(),
|
| 849 |
+
},
|
| 850 |
+
});
|
| 851 |
+
}
|
| 852 |
+
|
| 853 |
+
createComment(
|
| 854 |
+
orgId: string,
|
| 855 |
+
userId: string,
|
| 856 |
+
postId: string,
|
| 857 |
+
content: string
|
| 858 |
+
) {
|
| 859 |
+
return this._comments.model.comments.create({
|
| 860 |
+
data: {
|
| 861 |
+
organizationId: orgId,
|
| 862 |
+
userId,
|
| 863 |
+
postId,
|
| 864 |
+
content,
|
| 865 |
+
},
|
| 866 |
+
});
|
| 867 |
+
}
|
| 868 |
+
|
| 869 |
+
async getPostByForWebhookId(postId: string) {
|
| 870 |
+
return this._post.model.post.findMany({
|
| 871 |
+
where: {
|
| 872 |
+
id: postId,
|
| 873 |
+
deletedAt: null,
|
| 874 |
+
parentPostId: null,
|
| 875 |
+
},
|
| 876 |
+
select: {
|
| 877 |
+
id: true,
|
| 878 |
+
content: true,
|
| 879 |
+
publishDate: true,
|
| 880 |
+
releaseURL: true,
|
| 881 |
+
state: true,
|
| 882 |
+
integration: {
|
| 883 |
+
select: {
|
| 884 |
+
id: true,
|
| 885 |
+
name: true,
|
| 886 |
+
providerIdentifier: true,
|
| 887 |
+
picture: true,
|
| 888 |
+
type: true,
|
| 889 |
+
},
|
| 890 |
+
},
|
| 891 |
+
},
|
| 892 |
+
});
|
| 893 |
+
}
|
| 894 |
+
|
| 895 |
+
async getPostsSince(orgId: string, since: string) {
|
| 896 |
+
return this._post.model.post.findMany({
|
| 897 |
+
where: {
|
| 898 |
+
organizationId: orgId,
|
| 899 |
+
publishDate: {
|
| 900 |
+
gte: new Date(since),
|
| 901 |
+
},
|
| 902 |
+
deletedAt: null,
|
| 903 |
+
parentPostId: null,
|
| 904 |
+
},
|
| 905 |
+
select: {
|
| 906 |
+
id: true,
|
| 907 |
+
content: true,
|
| 908 |
+
publishDate: true,
|
| 909 |
+
releaseURL: true,
|
| 910 |
+
state: true,
|
| 911 |
+
integration: {
|
| 912 |
+
select: {
|
| 913 |
+
id: true,
|
| 914 |
+
name: true,
|
| 915 |
+
providerIdentifier: true,
|
| 916 |
+
picture: true,
|
| 917 |
+
type: true,
|
| 918 |
+
},
|
| 919 |
+
},
|
| 920 |
+
},
|
| 921 |
+
});
|
| 922 |
+
}
|
| 923 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts
ADDED
|
@@ -0,0 +1,1174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import {
|
| 2 |
+
BadRequestException,
|
| 3 |
+
Injectable,
|
| 4 |
+
ValidationPipe,
|
| 5 |
+
} from '@nestjs/common';
|
| 6 |
+
import { PostsRepository } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.repository';
|
| 7 |
+
import { CreatePostDto } from '@gitroom/nestjs-libraries/dtos/posts/create.post.dto';
|
| 8 |
+
import dayjs from 'dayjs';
|
| 9 |
+
import { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integration.manager';
|
| 10 |
+
import {
|
| 11 |
+
Integration,
|
| 12 |
+
Post,
|
| 13 |
+
Media,
|
| 14 |
+
From,
|
| 15 |
+
CreationMethod,
|
| 16 |
+
State,
|
| 17 |
+
} from '@prisma/client';
|
| 18 |
+
import { GetPostsDto } from '@gitroom/nestjs-libraries/dtos/posts/get.posts.dto';
|
| 19 |
+
import { GetPostsListDto } from '@gitroom/nestjs-libraries/dtos/posts/get.posts.list.dto';
|
| 20 |
+
import { shuffle } from 'lodash';
|
| 21 |
+
import { CreateGeneratedPostsDto } from '@gitroom/nestjs-libraries/dtos/generator/create.generated.posts.dto';
|
| 22 |
+
import { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';
|
| 23 |
+
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
|
| 24 |
+
import utc from 'dayjs/plugin/utc';
|
| 25 |
+
import { MediaService } from '@gitroom/nestjs-libraries/database/prisma/media/media.service';
|
| 26 |
+
import { ShortLinkService } from '@gitroom/nestjs-libraries/short-linking/short.link.service';
|
| 27 |
+
import { CreateTagDto } from '@gitroom/nestjs-libraries/dtos/posts/create.tag.dto';
|
| 28 |
+
import {
|
| 29 |
+
minifyPostsList,
|
| 30 |
+
minifyPosts,
|
| 31 |
+
} from '@gitroom/helpers/utils/posts.list.minify';
|
| 32 |
+
import axios from 'axios';
|
| 33 |
+
import sharp from 'sharp';
|
| 34 |
+
import { UploadFactory } from '@gitroom/nestjs-libraries/upload/upload.factory';
|
| 35 |
+
import { Readable } from 'stream';
|
| 36 |
+
import { OpenaiService } from '@gitroom/nestjs-libraries/openai/openai.service';
|
| 37 |
+
dayjs.extend(utc);
|
| 38 |
+
import * as Sentry from '@sentry/nestjs';
|
| 39 |
+
import { TemporalService } from 'nestjs-temporal-core';
|
| 40 |
+
import { TypedSearchAttributes } from '@temporalio/common';
|
| 41 |
+
import {
|
| 42 |
+
organizationId,
|
| 43 |
+
postId as postIdSearchParam,
|
| 44 |
+
} from '@gitroom/nestjs-libraries/temporal/temporal.search.attribute';
|
| 45 |
+
import { AnalyticsData } from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';
|
| 46 |
+
import { timer } from '@gitroom/helpers/utils/timer';
|
| 47 |
+
import { ioRedis } from '@gitroom/nestjs-libraries/redis/redis.service';
|
| 48 |
+
import { RefreshToken } from '@gitroom/nestjs-libraries/integrations/social.abstract';
|
| 49 |
+
import { RefreshIntegrationService } from '@gitroom/nestjs-libraries/integrations/refresh.integration.service';
|
| 50 |
+
import { hasExtension } from '@gitroom/helpers/utils/has.extension';
|
| 51 |
+
import { stripLinks } from '@gitroom/helpers/utils/strip.links';
|
| 52 |
+
import { validate } from 'class-validator';
|
| 53 |
+
import { plainToInstance } from 'class-transformer';
|
| 54 |
+
import { stripHtmlValidation } from '@gitroom/helpers/utils/strip.html.validation';
|
| 55 |
+
import { weightedLength } from '@gitroom/helpers/utils/count.length';
|
| 56 |
+
|
| 57 |
+
type PostWithConditionals = Post & {
|
| 58 |
+
integration?: Integration;
|
| 59 |
+
childrenPost: Post[];
|
| 60 |
+
};
|
| 61 |
+
|
| 62 |
+
@Injectable()
|
| 63 |
+
export class PostsService {
|
| 64 |
+
private storage = UploadFactory.createStorage();
|
| 65 |
+
constructor(
|
| 66 |
+
private _postRepository: PostsRepository,
|
| 67 |
+
private _integrationManager: IntegrationManager,
|
| 68 |
+
private _integrationService: IntegrationService,
|
| 69 |
+
private _mediaService: MediaService,
|
| 70 |
+
private _shortLinkService: ShortLinkService,
|
| 71 |
+
private _openaiService: OpenaiService,
|
| 72 |
+
private _temporalService: TemporalService,
|
| 73 |
+
private _refreshIntegrationService: RefreshIntegrationService
|
| 74 |
+
) {}
|
| 75 |
+
|
| 76 |
+
searchForMissingThreeHoursPosts() {
|
| 77 |
+
return this._postRepository.searchForMissingThreeHoursPosts();
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
updatePost(id: string, postId: string, releaseURL: string) {
|
| 81 |
+
return this._postRepository.updatePost(id, postId, releaseURL);
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
async getMissingContent(
|
| 85 |
+
orgId: string,
|
| 86 |
+
postId: string,
|
| 87 |
+
forceRefresh = false
|
| 88 |
+
): Promise<{ id: string; url: string }[]> {
|
| 89 |
+
const post = await this._postRepository.getPostById(postId, orgId);
|
| 90 |
+
if (!post || post.releaseId !== 'missing') {
|
| 91 |
+
return [];
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
const integrationProvider = this._integrationManager.getSocialIntegration(
|
| 95 |
+
post.integration.providerIdentifier
|
| 96 |
+
);
|
| 97 |
+
|
| 98 |
+
if (!integrationProvider.missing) {
|
| 99 |
+
return [];
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
const getIntegration = post.integration!;
|
| 103 |
+
|
| 104 |
+
if (
|
| 105 |
+
dayjs(getIntegration?.tokenExpiration).isBefore(dayjs()) ||
|
| 106 |
+
forceRefresh
|
| 107 |
+
) {
|
| 108 |
+
const data = await this._refreshIntegrationService.refresh(
|
| 109 |
+
getIntegration
|
| 110 |
+
);
|
| 111 |
+
if (!data) {
|
| 112 |
+
return [];
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
const { accessToken } = data;
|
| 116 |
+
|
| 117 |
+
if (accessToken) {
|
| 118 |
+
getIntegration.token = accessToken;
|
| 119 |
+
|
| 120 |
+
if (integrationProvider.refreshWait) {
|
| 121 |
+
await timer(10000);
|
| 122 |
+
}
|
| 123 |
+
} else {
|
| 124 |
+
await this._integrationService.disconnectChannel(orgId, getIntegration);
|
| 125 |
+
return [];
|
| 126 |
+
}
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
try {
|
| 130 |
+
return await integrationProvider.missing(
|
| 131 |
+
getIntegration.internalId,
|
| 132 |
+
getIntegration.token
|
| 133 |
+
);
|
| 134 |
+
} catch (e) {
|
| 135 |
+
console.log(e);
|
| 136 |
+
if (e instanceof RefreshToken) {
|
| 137 |
+
return this.getMissingContent(orgId, postId, true);
|
| 138 |
+
}
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
return [];
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
async getPostById(postId: string, orgId: string) {
|
| 145 |
+
return this._postRepository.getPostById(postId, orgId);
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
async updateReleaseId(orgId: string, postId: string, releaseId: string) {
|
| 149 |
+
return this._postRepository.updateReleaseId(postId, orgId, releaseId);
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
async checkPostAnalytics(
|
| 153 |
+
orgId: string,
|
| 154 |
+
postId: string,
|
| 155 |
+
date: number,
|
| 156 |
+
forceRefresh = false
|
| 157 |
+
): Promise<AnalyticsData[] | { missing: true }> {
|
| 158 |
+
const post = await this._postRepository.getPostById(postId, orgId);
|
| 159 |
+
if (!post || !post.releaseId) {
|
| 160 |
+
return [];
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
if (post.releaseId === 'missing') {
|
| 164 |
+
return { missing: true };
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
const integrationProvider = this._integrationManager.getSocialIntegration(
|
| 168 |
+
post.integration.providerIdentifier
|
| 169 |
+
);
|
| 170 |
+
|
| 171 |
+
if (!integrationProvider.postAnalytics) {
|
| 172 |
+
return [];
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
const getIntegration = post.integration!;
|
| 176 |
+
|
| 177 |
+
if (
|
| 178 |
+
dayjs(getIntegration?.tokenExpiration).isBefore(dayjs()) ||
|
| 179 |
+
forceRefresh
|
| 180 |
+
) {
|
| 181 |
+
const data = await this._refreshIntegrationService.refresh(
|
| 182 |
+
getIntegration
|
| 183 |
+
);
|
| 184 |
+
if (!data) {
|
| 185 |
+
return [];
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
const { accessToken } = data;
|
| 189 |
+
|
| 190 |
+
if (accessToken) {
|
| 191 |
+
getIntegration.token = accessToken;
|
| 192 |
+
|
| 193 |
+
if (integrationProvider.refreshWait) {
|
| 194 |
+
await timer(10000);
|
| 195 |
+
}
|
| 196 |
+
} else {
|
| 197 |
+
await this._integrationService.disconnectChannel(orgId, getIntegration);
|
| 198 |
+
return [];
|
| 199 |
+
}
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
// const getIntegrationData = await ioRedis.get(
|
| 203 |
+
// `integration:${orgId}:${post.id}:${date}`
|
| 204 |
+
// );
|
| 205 |
+
// if (getIntegrationData) {
|
| 206 |
+
// return JSON.parse(getIntegrationData);
|
| 207 |
+
// }
|
| 208 |
+
|
| 209 |
+
try {
|
| 210 |
+
const loadAnalytics = await integrationProvider.postAnalytics(
|
| 211 |
+
getIntegration.internalId,
|
| 212 |
+
getIntegration.token,
|
| 213 |
+
post.releaseId,
|
| 214 |
+
date
|
| 215 |
+
);
|
| 216 |
+
await ioRedis.set(
|
| 217 |
+
`integration:${orgId}:${post.id}:${date}`,
|
| 218 |
+
JSON.stringify(loadAnalytics),
|
| 219 |
+
'EX',
|
| 220 |
+
!process.env.NODE_ENV || process.env.NODE_ENV === 'development'
|
| 221 |
+
? 1
|
| 222 |
+
: 3600
|
| 223 |
+
);
|
| 224 |
+
return loadAnalytics;
|
| 225 |
+
} catch (e) {
|
| 226 |
+
console.log(e);
|
| 227 |
+
if (e instanceof RefreshToken) {
|
| 228 |
+
return this.checkPostAnalytics(orgId, postId, date, true);
|
| 229 |
+
}
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
return [];
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
async getStatistics(orgId: string, id: string) {
|
| 236 |
+
const getPost = await this.getPostsRecursively(id, true, orgId, true);
|
| 237 |
+
const content = getPost.map((p) => p.content);
|
| 238 |
+
const shortLinksTracking = await this._shortLinkService.getStatistics(
|
| 239 |
+
content
|
| 240 |
+
);
|
| 241 |
+
|
| 242 |
+
return {
|
| 243 |
+
clicks: shortLinksTracking,
|
| 244 |
+
};
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
async mapTypeToPost(
|
| 248 |
+
body: CreatePostDto,
|
| 249 |
+
organization: string,
|
| 250 |
+
replaceDraft: boolean = false
|
| 251 |
+
): Promise<CreatePostDto> {
|
| 252 |
+
if (!body?.posts?.every((p) => p?.integration?.id)) {
|
| 253 |
+
throw new BadRequestException('All posts must have an integration id');
|
| 254 |
+
}
|
| 255 |
+
|
| 256 |
+
const mappedValues = {
|
| 257 |
+
...body,
|
| 258 |
+
type: replaceDraft ? 'schedule' : body?.type,
|
| 259 |
+
posts: await Promise.all(
|
| 260 |
+
body?.posts?.map(async (post) => {
|
| 261 |
+
const integration = await this._integrationService.getIntegrationById(
|
| 262 |
+
organization,
|
| 263 |
+
post.integration.id
|
| 264 |
+
);
|
| 265 |
+
|
| 266 |
+
if (!integration) {
|
| 267 |
+
throw new BadRequestException(
|
| 268 |
+
`Integration with id ${post.integration.id} not found`
|
| 269 |
+
);
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
return {
|
| 273 |
+
type: replaceDraft ? 'schedule' : body?.type,
|
| 274 |
+
...post,
|
| 275 |
+
settings: {
|
| 276 |
+
...(post.settings || ({} as any)),
|
| 277 |
+
__type: integration.providerIdentifier,
|
| 278 |
+
},
|
| 279 |
+
};
|
| 280 |
+
}) || []
|
| 281 |
+
),
|
| 282 |
+
};
|
| 283 |
+
|
| 284 |
+
const validationPipe = new ValidationPipe({
|
| 285 |
+
skipMissingProperties: false,
|
| 286 |
+
transform: true,
|
| 287 |
+
transformOptions: {
|
| 288 |
+
enableImplicitConversion: true,
|
| 289 |
+
},
|
| 290 |
+
});
|
| 291 |
+
|
| 292 |
+
return await validationPipe.transform(mappedValues, {
|
| 293 |
+
type: 'body',
|
| 294 |
+
metatype: CreatePostDto,
|
| 295 |
+
});
|
| 296 |
+
}
|
| 297 |
+
|
| 298 |
+
async getPostsRecursively(
|
| 299 |
+
id: string,
|
| 300 |
+
includeIntegration = false,
|
| 301 |
+
orgId?: string,
|
| 302 |
+
isFirst?: boolean
|
| 303 |
+
): Promise<PostWithConditionals[]> {
|
| 304 |
+
const post = await this._postRepository.getPost(
|
| 305 |
+
id,
|
| 306 |
+
includeIntegration,
|
| 307 |
+
orgId,
|
| 308 |
+
isFirst
|
| 309 |
+
);
|
| 310 |
+
|
| 311 |
+
if (!post) {
|
| 312 |
+
return [];
|
| 313 |
+
}
|
| 314 |
+
|
| 315 |
+
return [
|
| 316 |
+
post!,
|
| 317 |
+
...(post?.childrenPost?.length
|
| 318 |
+
? await this.getPostsRecursively(
|
| 319 |
+
post?.childrenPost?.[0]?.id,
|
| 320 |
+
false,
|
| 321 |
+
orgId,
|
| 322 |
+
false
|
| 323 |
+
)
|
| 324 |
+
: []),
|
| 325 |
+
];
|
| 326 |
+
}
|
| 327 |
+
|
| 328 |
+
async getPosts(orgId: string, query: GetPostsDto) {
|
| 329 |
+
return this._postRepository.getPosts(orgId, query);
|
| 330 |
+
}
|
| 331 |
+
|
| 332 |
+
async getPostsMinified(orgId: string, query: GetPostsDto) {
|
| 333 |
+
return minifyPosts({
|
| 334 |
+
posts: await this._postRepository.getPosts(orgId, query),
|
| 335 |
+
});
|
| 336 |
+
}
|
| 337 |
+
|
| 338 |
+
async getPostsList(orgId: string, query: GetPostsListDto) {
|
| 339 |
+
return minifyPostsList(
|
| 340 |
+
await this._postRepository.getPostsList(orgId, query)
|
| 341 |
+
);
|
| 342 |
+
}
|
| 343 |
+
|
| 344 |
+
async updateMedia(id: string, imagesList: any[], convertToJPEG = false) {
|
| 345 |
+
try {
|
| 346 |
+
let imageUpdateNeeded = false;
|
| 347 |
+
const getImageList = await Promise.all(
|
| 348 |
+
(
|
| 349 |
+
await Promise.all(
|
| 350 |
+
(imagesList || []).map(async (p: any) => {
|
| 351 |
+
if (!p.path && p.id) {
|
| 352 |
+
imageUpdateNeeded = true;
|
| 353 |
+
return this._mediaService.getMediaById(p.id);
|
| 354 |
+
}
|
| 355 |
+
|
| 356 |
+
return p;
|
| 357 |
+
})
|
| 358 |
+
)
|
| 359 |
+
)
|
| 360 |
+
.map((m) => {
|
| 361 |
+
return {
|
| 362 |
+
...m,
|
| 363 |
+
url:
|
| 364 |
+
m.path.indexOf('http') === -1
|
| 365 |
+
? process.env.FRONTEND_URL +
|
| 366 |
+
'/' +
|
| 367 |
+
process.env.NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY +
|
| 368 |
+
m.path
|
| 369 |
+
: m.path,
|
| 370 |
+
type: 'image',
|
| 371 |
+
path:
|
| 372 |
+
m.path.indexOf('http') === -1
|
| 373 |
+
? process.env.UPLOAD_DIRECTORY + m.path
|
| 374 |
+
: m.path,
|
| 375 |
+
};
|
| 376 |
+
})
|
| 377 |
+
.map(async (m) => {
|
| 378 |
+
if (!convertToJPEG) {
|
| 379 |
+
return m;
|
| 380 |
+
}
|
| 381 |
+
|
| 382 |
+
if (hasExtension(m.path, 'png')) {
|
| 383 |
+
imageUpdateNeeded = true;
|
| 384 |
+
const response = await axios.get(m.url, {
|
| 385 |
+
responseType: 'arraybuffer',
|
| 386 |
+
});
|
| 387 |
+
|
| 388 |
+
const imageBuffer = Buffer.from(response.data);
|
| 389 |
+
|
| 390 |
+
// Use sharp to get the metadata of the image
|
| 391 |
+
const buffer = await sharp(imageBuffer)
|
| 392 |
+
.jpeg({ quality: 100 })
|
| 393 |
+
.toBuffer();
|
| 394 |
+
|
| 395 |
+
const { path, originalname } = await this.storage.uploadFile({
|
| 396 |
+
buffer,
|
| 397 |
+
mimetype: 'image/jpeg',
|
| 398 |
+
size: buffer.length,
|
| 399 |
+
path: '',
|
| 400 |
+
fieldname: '',
|
| 401 |
+
destination: '',
|
| 402 |
+
stream: new Readable(),
|
| 403 |
+
filename: '',
|
| 404 |
+
originalname: '',
|
| 405 |
+
encoding: '',
|
| 406 |
+
});
|
| 407 |
+
|
| 408 |
+
return {
|
| 409 |
+
...m,
|
| 410 |
+
name: originalname,
|
| 411 |
+
url:
|
| 412 |
+
path.indexOf('http') === -1
|
| 413 |
+
? process.env.FRONTEND_URL +
|
| 414 |
+
'/' +
|
| 415 |
+
process.env.NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY +
|
| 416 |
+
path
|
| 417 |
+
: path,
|
| 418 |
+
type: 'image',
|
| 419 |
+
path:
|
| 420 |
+
path.indexOf('http') === -1
|
| 421 |
+
? process.env.UPLOAD_DIRECTORY + path
|
| 422 |
+
: path,
|
| 423 |
+
};
|
| 424 |
+
}
|
| 425 |
+
|
| 426 |
+
return m;
|
| 427 |
+
})
|
| 428 |
+
);
|
| 429 |
+
|
| 430 |
+
if (imageUpdateNeeded) {
|
| 431 |
+
await this._postRepository.updateImages(
|
| 432 |
+
id,
|
| 433 |
+
JSON.stringify(getImageList)
|
| 434 |
+
);
|
| 435 |
+
}
|
| 436 |
+
|
| 437 |
+
return getImageList;
|
| 438 |
+
} catch (err: any) {
|
| 439 |
+
return imagesList;
|
| 440 |
+
}
|
| 441 |
+
}
|
| 442 |
+
|
| 443 |
+
async getPostGroupDebugExport(orgId: string, group: string) {
|
| 444 |
+
const loadAll = await this._postRepository.getPostsByGroup(orgId, group);
|
| 445 |
+
const errors = await this._postRepository.getErrorsByPostIds(
|
| 446 |
+
loadAll.map((p) => p.id)
|
| 447 |
+
);
|
| 448 |
+
const posts = this.arrangePostsByGroup(loadAll, undefined);
|
| 449 |
+
const rootPost = posts[0] as any;
|
| 450 |
+
|
| 451 |
+
return {
|
| 452 |
+
type: 'draft' as const,
|
| 453 |
+
shortLink: false,
|
| 454 |
+
date: rootPost.publishDate.toISOString(),
|
| 455 |
+
tags:
|
| 456 |
+
rootPost.tags?.map((t: any) => ({
|
| 457 |
+
value: t.tag.id,
|
| 458 |
+
label: t.tag.name,
|
| 459 |
+
})) || [],
|
| 460 |
+
posts: [
|
| 461 |
+
{
|
| 462 |
+
integration: { id: 'REPLACE_WITH_LOCAL_INTEGRATION_ID' },
|
| 463 |
+
group: rootPost.group,
|
| 464 |
+
settings: JSON.parse(rootPost.settings || '{}'),
|
| 465 |
+
value: posts.map((post) => ({
|
| 466 |
+
content: post.content,
|
| 467 |
+
image: JSON.parse(post.image || '[]'),
|
| 468 |
+
delay: post.delay || 0,
|
| 469 |
+
})),
|
| 470 |
+
},
|
| 471 |
+
],
|
| 472 |
+
_debug: {
|
| 473 |
+
providerIdentifier: rootPost.integration?.providerIdentifier,
|
| 474 |
+
providerName: rootPost.integration?.name,
|
| 475 |
+
state: rootPost.state,
|
| 476 |
+
error: rootPost.error,
|
| 477 |
+
errors: errors.map((e) => ({
|
| 478 |
+
message: e.message,
|
| 479 |
+
platform: e.platform,
|
| 480 |
+
body: e.body,
|
| 481 |
+
createdAt: e.createdAt,
|
| 482 |
+
})),
|
| 483 |
+
originalGroup: group,
|
| 484 |
+
originalPublishDate: rootPost.publishDate,
|
| 485 |
+
exportedAt: new Date().toISOString(),
|
| 486 |
+
},
|
| 487 |
+
};
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
+
async getPostsByGroup(orgId: string, group: string) {
|
| 491 |
+
const convertToJPEG = false;
|
| 492 |
+
const loadAll = await this._postRepository.getPostsByGroup(orgId, group);
|
| 493 |
+
const posts = this.arrangePostsByGroup(loadAll, undefined);
|
| 494 |
+
|
| 495 |
+
return {
|
| 496 |
+
group: posts?.[0]?.group,
|
| 497 |
+
posts: await Promise.all(
|
| 498 |
+
(posts || []).map(async (post) => ({
|
| 499 |
+
...post,
|
| 500 |
+
image: await this.updateMedia(
|
| 501 |
+
post.id,
|
| 502 |
+
JSON.parse(post.image || '[]'),
|
| 503 |
+
convertToJPEG
|
| 504 |
+
),
|
| 505 |
+
}))
|
| 506 |
+
),
|
| 507 |
+
integrationPicture: posts[0]?.integration?.picture,
|
| 508 |
+
integration: posts[0].integrationId,
|
| 509 |
+
settings: JSON.parse(posts[0].settings || '{}'),
|
| 510 |
+
};
|
| 511 |
+
}
|
| 512 |
+
|
| 513 |
+
arrangePostsByGroup(all: any, parent?: string): PostWithConditionals[] {
|
| 514 |
+
const findAll = all
|
| 515 |
+
.filter((p: any) =>
|
| 516 |
+
!parent ? !p.parentPostId : p.parentPostId === parent
|
| 517 |
+
)
|
| 518 |
+
.map(({ integration, ...all }: any) => ({
|
| 519 |
+
...all,
|
| 520 |
+
...(!parent ? { integration } : {}),
|
| 521 |
+
}));
|
| 522 |
+
|
| 523 |
+
return [
|
| 524 |
+
...findAll,
|
| 525 |
+
...(findAll.length
|
| 526 |
+
? findAll.flatMap((p: any) => this.arrangePostsByGroup(all, p.id))
|
| 527 |
+
: []),
|
| 528 |
+
];
|
| 529 |
+
}
|
| 530 |
+
|
| 531 |
+
async getPost(orgId: string, id: string, convertToJPEG = false) {
|
| 532 |
+
const posts = await this.getPostsRecursively(id, true, orgId, true);
|
| 533 |
+
const list = {
|
| 534 |
+
group: posts?.[0]?.group,
|
| 535 |
+
posts: await Promise.all(
|
| 536 |
+
(posts || []).map(async (post) => ({
|
| 537 |
+
...post,
|
| 538 |
+
image: await this.updateMedia(
|
| 539 |
+
post.id,
|
| 540 |
+
JSON.parse(post.image || '[]'),
|
| 541 |
+
convertToJPEG
|
| 542 |
+
),
|
| 543 |
+
}))
|
| 544 |
+
),
|
| 545 |
+
integrationPicture: posts[0]?.integration?.picture,
|
| 546 |
+
integration: posts[0].integrationId,
|
| 547 |
+
settings: JSON.parse(posts[0].settings || '{}'),
|
| 548 |
+
};
|
| 549 |
+
|
| 550 |
+
return list;
|
| 551 |
+
}
|
| 552 |
+
|
| 553 |
+
async getOldPosts(orgId: string, date: string) {
|
| 554 |
+
return this._postRepository.getOldPosts(orgId, date);
|
| 555 |
+
}
|
| 556 |
+
|
| 557 |
+
public async updateTags(orgId: string, post: Post[]): Promise<Post[]> {
|
| 558 |
+
const plainText = JSON.stringify(post);
|
| 559 |
+
const extract = Array.from(
|
| 560 |
+
plainText.match(/\(post:[a-zA-Z0-9-_]+\)/g) || []
|
| 561 |
+
);
|
| 562 |
+
if (!extract.length) {
|
| 563 |
+
return post;
|
| 564 |
+
}
|
| 565 |
+
|
| 566 |
+
const ids = (extract || []).map((e) =>
|
| 567 |
+
e.replace('(post:', '').replace(')', '')
|
| 568 |
+
);
|
| 569 |
+
const urls = await this._postRepository.getPostUrls(orgId, ids);
|
| 570 |
+
const newPlainText = ids.reduce((acc, value) => {
|
| 571 |
+
const findUrl = urls?.find?.((u) => u.id === value)?.releaseURL || '';
|
| 572 |
+
return acc.replace(
|
| 573 |
+
new RegExp(`\\(post:${value}\\)`, 'g'),
|
| 574 |
+
findUrl.split(',')[0]
|
| 575 |
+
);
|
| 576 |
+
}, plainText);
|
| 577 |
+
|
| 578 |
+
return this.updateTags(orgId, JSON.parse(newPlainText) as Post[]);
|
| 579 |
+
}
|
| 580 |
+
|
| 581 |
+
public async checkInternalPlug(
|
| 582 |
+
integration: Integration,
|
| 583 |
+
orgId: string,
|
| 584 |
+
id: string,
|
| 585 |
+
settings: any
|
| 586 |
+
) {
|
| 587 |
+
const plugs = Object.entries(settings).filter(([key]) => {
|
| 588 |
+
return key.indexOf('plug-') > -1;
|
| 589 |
+
});
|
| 590 |
+
|
| 591 |
+
if (plugs.length === 0) {
|
| 592 |
+
return [];
|
| 593 |
+
}
|
| 594 |
+
|
| 595 |
+
const parsePlugs = plugs.reduce((all, [key, value]) => {
|
| 596 |
+
const [_, name, identifier] = key.split('--');
|
| 597 |
+
all[name] = all[name] || { name };
|
| 598 |
+
all[name][identifier] = value;
|
| 599 |
+
return all;
|
| 600 |
+
}, {} as any);
|
| 601 |
+
|
| 602 |
+
const list: {
|
| 603 |
+
name: string;
|
| 604 |
+
integrations: { id: string }[];
|
| 605 |
+
delay: string;
|
| 606 |
+
active: boolean;
|
| 607 |
+
}[] = Object.values(parsePlugs);
|
| 608 |
+
|
| 609 |
+
return (list || []).flatMap((trigger) => {
|
| 610 |
+
return (trigger?.integrations || []).flatMap((int) => ({
|
| 611 |
+
type: 'internal-plug',
|
| 612 |
+
post: id,
|
| 613 |
+
originalIntegration: integration.id,
|
| 614 |
+
integration: int.id,
|
| 615 |
+
plugName: trigger.name,
|
| 616 |
+
orgId: orgId,
|
| 617 |
+
delay: +trigger.delay,
|
| 618 |
+
information: trigger,
|
| 619 |
+
}));
|
| 620 |
+
});
|
| 621 |
+
}
|
| 622 |
+
|
| 623 |
+
public async checkPlugs(
|
| 624 |
+
orgId: string,
|
| 625 |
+
providerName: string,
|
| 626 |
+
integrationId: string
|
| 627 |
+
) {
|
| 628 |
+
const loadAllPlugs = this._integrationManager.getAllPlugs();
|
| 629 |
+
const getPlugs = await this._integrationService.getPlugs(
|
| 630 |
+
orgId,
|
| 631 |
+
integrationId
|
| 632 |
+
);
|
| 633 |
+
|
| 634 |
+
const currentPlug = loadAllPlugs.find((p) => p.identifier === providerName);
|
| 635 |
+
|
| 636 |
+
return getPlugs
|
| 637 |
+
.filter((plug) => {
|
| 638 |
+
return currentPlug?.plugs?.some(
|
| 639 |
+
(p: any) => p.methodName === plug.plugFunction
|
| 640 |
+
);
|
| 641 |
+
})
|
| 642 |
+
.map((plug) => {
|
| 643 |
+
const runPlug = currentPlug?.plugs?.find(
|
| 644 |
+
(p: any) => p.methodName === plug.plugFunction
|
| 645 |
+
)!;
|
| 646 |
+
return {
|
| 647 |
+
type: 'global',
|
| 648 |
+
plugId: plug.id,
|
| 649 |
+
delay: runPlug.runEveryMilliseconds,
|
| 650 |
+
totalRuns: runPlug.totalRuns,
|
| 651 |
+
};
|
| 652 |
+
});
|
| 653 |
+
}
|
| 654 |
+
|
| 655 |
+
async deletePost(orgId: string, group: string) {
|
| 656 |
+
const post = await this._postRepository.deletePost(orgId, group);
|
| 657 |
+
|
| 658 |
+
if (post?.id) {
|
| 659 |
+
try {
|
| 660 |
+
const workflows = this._temporalService.client
|
| 661 |
+
.getRawClient()
|
| 662 |
+
?.workflow.list({
|
| 663 |
+
query: `postId="${post.id}" AND ExecutionStatus="Running"`,
|
| 664 |
+
});
|
| 665 |
+
|
| 666 |
+
for await (const executionInfo of workflows) {
|
| 667 |
+
try {
|
| 668 |
+
const workflow =
|
| 669 |
+
await this._temporalService.client.getWorkflowHandle(
|
| 670 |
+
executionInfo.workflowId
|
| 671 |
+
);
|
| 672 |
+
if (
|
| 673 |
+
workflow &&
|
| 674 |
+
(await workflow.describe()).status.name !== 'TERMINATED'
|
| 675 |
+
) {
|
| 676 |
+
await workflow.terminate();
|
| 677 |
+
}
|
| 678 |
+
} catch (err) {}
|
| 679 |
+
}
|
| 680 |
+
} catch (err) {}
|
| 681 |
+
}
|
| 682 |
+
|
| 683 |
+
return { error: true };
|
| 684 |
+
}
|
| 685 |
+
|
| 686 |
+
async countPostsFromDay(orgId: string, date: Date) {
|
| 687 |
+
return this._postRepository.countPostsFromDay(orgId, date);
|
| 688 |
+
}
|
| 689 |
+
|
| 690 |
+
getPostByForWebhookId(id: string) {
|
| 691 |
+
return this._postRepository.getPostByForWebhookId(id);
|
| 692 |
+
}
|
| 693 |
+
|
| 694 |
+
async startWorkflow(
|
| 695 |
+
taskQueue: string,
|
| 696 |
+
postId: string,
|
| 697 |
+
orgId: string,
|
| 698 |
+
state: State
|
| 699 |
+
) {
|
| 700 |
+
try {
|
| 701 |
+
const workflows = this._temporalService.client
|
| 702 |
+
.getRawClient()
|
| 703 |
+
?.workflow.list({
|
| 704 |
+
query: `postId="${postId}" AND ExecutionStatus="Running"`,
|
| 705 |
+
});
|
| 706 |
+
|
| 707 |
+
for await (const executionInfo of workflows) {
|
| 708 |
+
try {
|
| 709 |
+
const workflow = await this._temporalService.client.getWorkflowHandle(
|
| 710 |
+
executionInfo.workflowId
|
| 711 |
+
);
|
| 712 |
+
if (
|
| 713 |
+
workflow &&
|
| 714 |
+
(await workflow.describe()).status.name !== 'TERMINATED'
|
| 715 |
+
) {
|
| 716 |
+
await workflow.terminate();
|
| 717 |
+
}
|
| 718 |
+
} catch (err) {}
|
| 719 |
+
}
|
| 720 |
+
} catch (err) {}
|
| 721 |
+
|
| 722 |
+
if (state === 'DRAFT') {
|
| 723 |
+
return;
|
| 724 |
+
}
|
| 725 |
+
|
| 726 |
+
try {
|
| 727 |
+
await this._temporalService.client
|
| 728 |
+
.getRawClient()
|
| 729 |
+
?.workflow.start('postWorkflowV105', {
|
| 730 |
+
workflowId: `post_${postId}`,
|
| 731 |
+
taskQueue: 'main',
|
| 732 |
+
workflowIdConflictPolicy: 'TERMINATE_EXISTING',
|
| 733 |
+
args: [
|
| 734 |
+
{
|
| 735 |
+
taskQueue: taskQueue,
|
| 736 |
+
postId: postId,
|
| 737 |
+
organizationId: orgId,
|
| 738 |
+
},
|
| 739 |
+
],
|
| 740 |
+
typedSearchAttributes: new TypedSearchAttributes([
|
| 741 |
+
{
|
| 742 |
+
key: postIdSearchParam,
|
| 743 |
+
value: postId,
|
| 744 |
+
},
|
| 745 |
+
{
|
| 746 |
+
key: organizationId,
|
| 747 |
+
value: orgId,
|
| 748 |
+
},
|
| 749 |
+
]),
|
| 750 |
+
});
|
| 751 |
+
} catch (err) {}
|
| 752 |
+
}
|
| 753 |
+
|
| 754 |
+
/**
|
| 755 |
+
* Server-side validation that used to live on the client (`checkValidity` +
|
| 756 |
+
* the manage modal loop). Runs the provider's settings DTO validation, the
|
| 757 |
+
* provider `checkValidity` (media rules) and the empty-content / too-long
|
| 758 |
+
* character checks. Returns one result per post so the frontend can show the
|
| 759 |
+
* same toasts it did before — and so `/posts` can refuse to create invalid
|
| 760 |
+
* posts.
|
| 761 |
+
*/
|
| 762 |
+
async validatePosts(
|
| 763 |
+
orgId: string,
|
| 764 |
+
posts: Array<{
|
| 765 |
+
integration: { id: string };
|
| 766 |
+
value: Array<{
|
| 767 |
+
content?: string;
|
| 768 |
+
image?: Array<{ path: string; thumbnail?: string }>;
|
| 769 |
+
}>;
|
| 770 |
+
settings?: any;
|
| 771 |
+
}>
|
| 772 |
+
) {
|
| 773 |
+
return Promise.all(
|
| 774 |
+
(posts || []).map(async (post) => {
|
| 775 |
+
const integration = await this._integrationService.getIntegrationById(
|
| 776 |
+
orgId,
|
| 777 |
+
post?.integration?.id
|
| 778 |
+
);
|
| 779 |
+
|
| 780 |
+
if (!integration) {
|
| 781 |
+
throw new BadRequestException(
|
| 782 |
+
`Integration with id ${post?.integration?.id} not found`
|
| 783 |
+
);
|
| 784 |
+
}
|
| 785 |
+
|
| 786 |
+
const provider = this._integrationManager.getSocialIntegration(
|
| 787 |
+
integration.providerIdentifier
|
| 788 |
+
);
|
| 789 |
+
|
| 790 |
+
let additionalSettings: any[] = [];
|
| 791 |
+
try {
|
| 792 |
+
additionalSettings = JSON.parse(
|
| 793 |
+
integration.additionalSettings || '[]'
|
| 794 |
+
);
|
| 795 |
+
} catch {
|
| 796 |
+
additionalSettings = [];
|
| 797 |
+
}
|
| 798 |
+
|
| 799 |
+
const settings = post.settings || {};
|
| 800 |
+
const media = (post.value || []).map((p) => p.image || []);
|
| 801 |
+
|
| 802 |
+
// Settings DTO validation — mirrors the client `form.trigger()`.
|
| 803 |
+
let valid = true;
|
| 804 |
+
let settingsError = '';
|
| 805 |
+
if (provider?.dto) {
|
| 806 |
+
const instance = plainToInstance(provider.dto, settings, {
|
| 807 |
+
enableImplicitConversion: false,
|
| 808 |
+
});
|
| 809 |
+
const validationErrors = await validate(instance as object, {
|
| 810 |
+
skipMissingProperties: false,
|
| 811 |
+
});
|
| 812 |
+
settingsError = this.firstValidationError(validationErrors);
|
| 813 |
+
valid = validationErrors.length === 0;
|
| 814 |
+
}
|
| 815 |
+
|
| 816 |
+
// Provider-specific media validation (the old client `checkValidity`).
|
| 817 |
+
let errors: string | true = true;
|
| 818 |
+
try {
|
| 819 |
+
errors = await provider.checkValidity(
|
| 820 |
+
media,
|
| 821 |
+
settings,
|
| 822 |
+
additionalSettings
|
| 823 |
+
);
|
| 824 |
+
} catch (err: any) {
|
| 825 |
+
errors = err?.message || 'Invalid media';
|
| 826 |
+
}
|
| 827 |
+
|
| 828 |
+
const maximumCharacters = provider.maxLength(additionalSettings);
|
| 829 |
+
const isX = integration.providerIdentifier === 'x';
|
| 830 |
+
|
| 831 |
+
const emptyContent = (post.value || []).some((a) => {
|
| 832 |
+
const strip = stripHtmlValidation('normal', a.content || '', true);
|
| 833 |
+
const length = isX ? weightedLength(strip) : strip.length;
|
| 834 |
+
return length === 0 && (a.image || []).length === 0;
|
| 835 |
+
});
|
| 836 |
+
|
| 837 |
+
const tooLong = (post.value || []).some((a) => {
|
| 838 |
+
const strip = stripHtmlValidation('normal', a.content || '', true);
|
| 839 |
+
const weighted = isX ? weightedLength(strip) : strip.length;
|
| 840 |
+
const totalCharacters =
|
| 841 |
+
weighted > strip.length ? weighted : strip.length;
|
| 842 |
+
return totalCharacters > (maximumCharacters || 1000000);
|
| 843 |
+
});
|
| 844 |
+
|
| 845 |
+
return {
|
| 846 |
+
id: integration.id,
|
| 847 |
+
identifier: integration.providerIdentifier,
|
| 848 |
+
name: integration.name,
|
| 849 |
+
valid,
|
| 850 |
+
settingsError,
|
| 851 |
+
errors,
|
| 852 |
+
emptyContent,
|
| 853 |
+
tooLong,
|
| 854 |
+
maximumCharacters,
|
| 855 |
+
};
|
| 856 |
+
})
|
| 857 |
+
);
|
| 858 |
+
}
|
| 859 |
+
|
| 860 |
+
/** Returns the first class-validator message (incl. nested children), or ''. */
|
| 861 |
+
private firstValidationError(errors: any[]): string {
|
| 862 |
+
for (const e of errors || []) {
|
| 863 |
+
if (e?.constraints) {
|
| 864 |
+
return Object.values(e.constraints as Record<string, string>)[0] || '';
|
| 865 |
+
}
|
| 866 |
+
const child = e?.children?.length
|
| 867 |
+
? this.firstValidationError(e.children)
|
| 868 |
+
: '';
|
| 869 |
+
if (child) {
|
| 870 |
+
return child;
|
| 871 |
+
}
|
| 872 |
+
}
|
| 873 |
+
return '';
|
| 874 |
+
}
|
| 875 |
+
|
| 876 |
+
async createPost(
|
| 877 |
+
orgId: string,
|
| 878 |
+
body: CreatePostDto,
|
| 879 |
+
creationMethod: CreationMethod
|
| 880 |
+
): Promise<any[]> {
|
| 881 |
+
const postList = [];
|
| 882 |
+
for (const post of body.posts) {
|
| 883 |
+
const provider = this._integrationManager.getSocialIntegration(
|
| 884 |
+
(post.settings as any)?.__type
|
| 885 |
+
);
|
| 886 |
+
const removeLinks = !!provider?.stripLinks?.();
|
| 887 |
+
|
| 888 |
+
const messages = (post.value || []).map((p) => p.content);
|
| 889 |
+
// No point shortlinking links on platforms that strip them out anyway
|
| 890 |
+
const updateContent =
|
| 891 |
+
!body.shortLink || removeLinks
|
| 892 |
+
? messages
|
| 893 |
+
: await this._shortLinkService.convertTextToShortLinks(
|
| 894 |
+
orgId,
|
| 895 |
+
messages
|
| 896 |
+
);
|
| 897 |
+
|
| 898 |
+
post.value = (post.value || []).map((p, i) => ({
|
| 899 |
+
...p,
|
| 900 |
+
content: removeLinks ? stripLinks(updateContent[i]) : updateContent[i],
|
| 901 |
+
}));
|
| 902 |
+
|
| 903 |
+
const { posts } = await this._postRepository.createOrUpdatePost(
|
| 904 |
+
body.type,
|
| 905 |
+
orgId,
|
| 906 |
+
body.type === 'now' ? dayjs().format('YYYY-MM-DDTHH:mm:00') : body.date,
|
| 907 |
+
post,
|
| 908 |
+
body.tags,
|
| 909 |
+
creationMethod,
|
| 910 |
+
body.inter
|
| 911 |
+
);
|
| 912 |
+
|
| 913 |
+
if (!posts?.length) {
|
| 914 |
+
return [] as any[];
|
| 915 |
+
}
|
| 916 |
+
|
| 917 |
+
if (body.type !== 'update') {
|
| 918 |
+
this.startWorkflow(
|
| 919 |
+
post.settings.__type.split('-')[0].toLowerCase(),
|
| 920 |
+
posts[0].id,
|
| 921 |
+
orgId,
|
| 922 |
+
posts[0].state
|
| 923 |
+
).catch((err) => {});
|
| 924 |
+
}
|
| 925 |
+
|
| 926 |
+
Sentry.metrics.count('post_created', 1);
|
| 927 |
+
postList.push({
|
| 928 |
+
postId: posts[0].id,
|
| 929 |
+
integration: post.integration.id,
|
| 930 |
+
});
|
| 931 |
+
}
|
| 932 |
+
|
| 933 |
+
return postList;
|
| 934 |
+
}
|
| 935 |
+
|
| 936 |
+
async separatePosts(content: string, len: number) {
|
| 937 |
+
return this._openaiService.separatePosts(content, len);
|
| 938 |
+
}
|
| 939 |
+
|
| 940 |
+
async changeState(id: string, state: State, err?: any, body?: any) {
|
| 941 |
+
return this._postRepository.changeState(id, state, err, body);
|
| 942 |
+
}
|
| 943 |
+
|
| 944 |
+
async changePostStatus(
|
| 945 |
+
orgId: string,
|
| 946 |
+
id: string,
|
| 947 |
+
status: 'draft' | 'schedule'
|
| 948 |
+
) {
|
| 949 |
+
const getPostById = await this._postRepository.getPostById(id, orgId);
|
| 950 |
+
if (!getPostById) {
|
| 951 |
+
throw new BadRequestException('Post not found');
|
| 952 |
+
}
|
| 953 |
+
|
| 954 |
+
const state: State = status === 'draft' ? 'DRAFT' : 'QUEUE';
|
| 955 |
+
await this._postRepository.changeState(id, state);
|
| 956 |
+
|
| 957 |
+
try {
|
| 958 |
+
await this.startWorkflow(
|
| 959 |
+
getPostById.integration.providerIdentifier.split('-')[0].toLowerCase(),
|
| 960 |
+
getPostById.id,
|
| 961 |
+
orgId,
|
| 962 |
+
state
|
| 963 |
+
);
|
| 964 |
+
} catch (err) {}
|
| 965 |
+
|
| 966 |
+
return { id, state };
|
| 967 |
+
}
|
| 968 |
+
|
| 969 |
+
async changeDate(
|
| 970 |
+
orgId: string,
|
| 971 |
+
id: string,
|
| 972 |
+
date: string,
|
| 973 |
+
action: 'schedule' | 'update' = 'schedule'
|
| 974 |
+
) {
|
| 975 |
+
const getPostById = await this._postRepository.getPostById(id, orgId);
|
| 976 |
+
|
| 977 |
+
// schedule: Set status to QUEUE and change date (reschedule the post)
|
| 978 |
+
// update: Just change the date without changing the status
|
| 979 |
+
const newDate = await this._postRepository.changeDate(
|
| 980 |
+
orgId,
|
| 981 |
+
id,
|
| 982 |
+
date,
|
| 983 |
+
getPostById.state === 'DRAFT',
|
| 984 |
+
action
|
| 985 |
+
);
|
| 986 |
+
|
| 987 |
+
if (action === 'schedule') {
|
| 988 |
+
try {
|
| 989 |
+
await this.startWorkflow(
|
| 990 |
+
getPostById.integration.providerIdentifier
|
| 991 |
+
.split('-')[0]
|
| 992 |
+
.toLowerCase(),
|
| 993 |
+
getPostById.id,
|
| 994 |
+
orgId,
|
| 995 |
+
getPostById.state === 'DRAFT' ? 'DRAFT' : 'QUEUE'
|
| 996 |
+
);
|
| 997 |
+
} catch (err) {}
|
| 998 |
+
}
|
| 999 |
+
|
| 1000 |
+
return newDate;
|
| 1001 |
+
}
|
| 1002 |
+
|
| 1003 |
+
async generatePostsDraft(orgId: string, body: CreateGeneratedPostsDto) {
|
| 1004 |
+
const getAllIntegrations = (
|
| 1005 |
+
await this._integrationService.getIntegrationsList(orgId)
|
| 1006 |
+
).filter((f) => !f.disabled && f.providerIdentifier !== 'reddit');
|
| 1007 |
+
|
| 1008 |
+
// const posts = chunk(body.posts, getAllIntegrations.length);
|
| 1009 |
+
const allDates = dayjs()
|
| 1010 |
+
.isoWeek(body.week)
|
| 1011 |
+
.year(body.year)
|
| 1012 |
+
.startOf('isoWeek');
|
| 1013 |
+
|
| 1014 |
+
const dates = [...new Array(7)].map((_, i) => {
|
| 1015 |
+
return allDates.add(i, 'day').format('YYYY-MM-DD');
|
| 1016 |
+
});
|
| 1017 |
+
|
| 1018 |
+
const findTime = (): string => {
|
| 1019 |
+
const totalMinutes = Math.floor(Math.random() * 144) * 10;
|
| 1020 |
+
|
| 1021 |
+
// Convert total minutes to hours and minutes
|
| 1022 |
+
const hours = Math.floor(totalMinutes / 60);
|
| 1023 |
+
const minutes = totalMinutes % 60;
|
| 1024 |
+
|
| 1025 |
+
// Format hours and minutes to always be two digits
|
| 1026 |
+
const formattedHours = hours.toString().padStart(2, '0');
|
| 1027 |
+
const formattedMinutes = minutes.toString().padStart(2, '0');
|
| 1028 |
+
const randomDate =
|
| 1029 |
+
shuffle(dates)[0] + 'T' + `${formattedHours}:${formattedMinutes}:00`;
|
| 1030 |
+
|
| 1031 |
+
if (dayjs(randomDate).isBefore(dayjs())) {
|
| 1032 |
+
return findTime();
|
| 1033 |
+
}
|
| 1034 |
+
|
| 1035 |
+
return randomDate;
|
| 1036 |
+
};
|
| 1037 |
+
|
| 1038 |
+
for (const integration of getAllIntegrations) {
|
| 1039 |
+
for (const toPost of body.posts) {
|
| 1040 |
+
const group = makeId(10);
|
| 1041 |
+
const randomDate = findTime();
|
| 1042 |
+
|
| 1043 |
+
await this.createPost(
|
| 1044 |
+
orgId,
|
| 1045 |
+
{
|
| 1046 |
+
type: 'draft',
|
| 1047 |
+
date: randomDate,
|
| 1048 |
+
order: '',
|
| 1049 |
+
shortLink: false,
|
| 1050 |
+
tags: [],
|
| 1051 |
+
posts: [
|
| 1052 |
+
{
|
| 1053 |
+
group,
|
| 1054 |
+
integration: {
|
| 1055 |
+
id: integration.id,
|
| 1056 |
+
},
|
| 1057 |
+
settings: {
|
| 1058 |
+
__type: integration.providerIdentifier as any,
|
| 1059 |
+
title: '',
|
| 1060 |
+
tags: [],
|
| 1061 |
+
subreddit: [],
|
| 1062 |
+
},
|
| 1063 |
+
value: [
|
| 1064 |
+
...toPost.list.map((l) => ({
|
| 1065 |
+
id: '',
|
| 1066 |
+
content: l.post,
|
| 1067 |
+
delay: 0,
|
| 1068 |
+
image: [],
|
| 1069 |
+
})),
|
| 1070 |
+
{
|
| 1071 |
+
id: '',
|
| 1072 |
+
delay: 0,
|
| 1073 |
+
content: `Check out the full story here:\n${
|
| 1074 |
+
body.postId || body.url
|
| 1075 |
+
}`,
|
| 1076 |
+
image: [],
|
| 1077 |
+
},
|
| 1078 |
+
],
|
| 1079 |
+
},
|
| 1080 |
+
],
|
| 1081 |
+
},
|
| 1082 |
+
'WEB'
|
| 1083 |
+
);
|
| 1084 |
+
}
|
| 1085 |
+
}
|
| 1086 |
+
}
|
| 1087 |
+
|
| 1088 |
+
findAllExistingCategories() {
|
| 1089 |
+
return this._postRepository.findAllExistingCategories();
|
| 1090 |
+
}
|
| 1091 |
+
|
| 1092 |
+
findAllExistingTopicsOfCategory(category: string) {
|
| 1093 |
+
return this._postRepository.findAllExistingTopicsOfCategory(category);
|
| 1094 |
+
}
|
| 1095 |
+
|
| 1096 |
+
findPopularPosts(category: string, topic?: string) {
|
| 1097 |
+
return this._postRepository.findPopularPosts(category, topic);
|
| 1098 |
+
}
|
| 1099 |
+
|
| 1100 |
+
async findFreeDateTime(orgId: string, integrationId?: string) {
|
| 1101 |
+
const findTimes = await this._integrationService.findFreeDateTime(
|
| 1102 |
+
orgId,
|
| 1103 |
+
integrationId
|
| 1104 |
+
);
|
| 1105 |
+
return this.findFreeDateTimeRecursive(
|
| 1106 |
+
orgId,
|
| 1107 |
+
findTimes,
|
| 1108 |
+
dayjs.utc().startOf('day')
|
| 1109 |
+
);
|
| 1110 |
+
}
|
| 1111 |
+
|
| 1112 |
+
async createPopularPosts(post: {
|
| 1113 |
+
category: string;
|
| 1114 |
+
topic: string;
|
| 1115 |
+
content: string;
|
| 1116 |
+
hook: string;
|
| 1117 |
+
}) {
|
| 1118 |
+
return this._postRepository.createPopularPosts(post);
|
| 1119 |
+
}
|
| 1120 |
+
|
| 1121 |
+
private async findFreeDateTimeRecursive(
|
| 1122 |
+
orgId: string,
|
| 1123 |
+
times: number[],
|
| 1124 |
+
date: dayjs.Dayjs
|
| 1125 |
+
): Promise<string> {
|
| 1126 |
+
const list = await this._postRepository.getPostsCountsByDates(
|
| 1127 |
+
orgId,
|
| 1128 |
+
times,
|
| 1129 |
+
date
|
| 1130 |
+
);
|
| 1131 |
+
|
| 1132 |
+
if (!list.length) {
|
| 1133 |
+
return this.findFreeDateTimeRecursive(orgId, times, date.add(1, 'day'));
|
| 1134 |
+
}
|
| 1135 |
+
|
| 1136 |
+
const num = list.reduce<null | number>((prev, curr) => {
|
| 1137 |
+
if (prev === null || prev > curr) {
|
| 1138 |
+
return curr;
|
| 1139 |
+
}
|
| 1140 |
+
return prev;
|
| 1141 |
+
}, null) as number;
|
| 1142 |
+
|
| 1143 |
+
return date.clone().add(num, 'minutes').format('YYYY-MM-DDTHH:mm:00');
|
| 1144 |
+
}
|
| 1145 |
+
|
| 1146 |
+
getComments(postId: string) {
|
| 1147 |
+
return this._postRepository.getComments(postId);
|
| 1148 |
+
}
|
| 1149 |
+
|
| 1150 |
+
getTags(orgId: string) {
|
| 1151 |
+
return this._postRepository.getTags(orgId);
|
| 1152 |
+
}
|
| 1153 |
+
|
| 1154 |
+
createTag(orgId: string, body: CreateTagDto) {
|
| 1155 |
+
return this._postRepository.createTag(orgId, body);
|
| 1156 |
+
}
|
| 1157 |
+
|
| 1158 |
+
editTag(id: string, orgId: string, body: CreateTagDto) {
|
| 1159 |
+
return this._postRepository.editTag(id, orgId, body);
|
| 1160 |
+
}
|
| 1161 |
+
|
| 1162 |
+
deleteTag(id: string, orgId: string) {
|
| 1163 |
+
return this._postRepository.deleteTag(id, orgId);
|
| 1164 |
+
}
|
| 1165 |
+
|
| 1166 |
+
createComment(
|
| 1167 |
+
orgId: string,
|
| 1168 |
+
userId: string,
|
| 1169 |
+
postId: string,
|
| 1170 |
+
comment: string
|
| 1171 |
+
) {
|
| 1172 |
+
return this._postRepository.createComment(orgId, userId, postId, comment);
|
| 1173 |
+
}
|
| 1174 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/prisma.service.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
|
| 2 |
+
import { PrismaClient } from '@prisma/client';
|
| 3 |
+
|
| 4 |
+
@Injectable()
|
| 5 |
+
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
|
| 6 |
+
constructor() {
|
| 7 |
+
super({
|
| 8 |
+
log: [
|
| 9 |
+
{
|
| 10 |
+
emit: 'event',
|
| 11 |
+
level: 'query',
|
| 12 |
+
},
|
| 13 |
+
],
|
| 14 |
+
});
|
| 15 |
+
}
|
| 16 |
+
async onModuleInit() {
|
| 17 |
+
await this.$connect();
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
async onModuleDestroy() {
|
| 21 |
+
await this.$disconnect();
|
| 22 |
+
}
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
@Injectable()
|
| 26 |
+
export class PrismaRepository<T extends keyof PrismaService> {
|
| 27 |
+
public model: Pick<PrismaService, T>;
|
| 28 |
+
constructor(private _prismaService: PrismaService) {
|
| 29 |
+
this.model = this._prismaService;
|
| 30 |
+
}
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
@Injectable()
|
| 34 |
+
export class PrismaTransaction {
|
| 35 |
+
public model: Pick<PrismaService, '$transaction'>;
|
| 36 |
+
constructor(private _prismaService: PrismaService) {
|
| 37 |
+
this.model = this._prismaService;
|
| 38 |
+
}
|
| 39 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/schema.prisma
ADDED
|
@@ -0,0 +1,970 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
generator client {
|
| 2 |
+
provider = "prisma-client-js"
|
| 3 |
+
runtime = "nodejs"
|
| 4 |
+
}
|
| 5 |
+
|
| 6 |
+
datasource db {
|
| 7 |
+
provider = "postgresql"
|
| 8 |
+
url = env("DATABASE_URL")
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
model Organization {
|
| 12 |
+
id String @id @default(uuid())
|
| 13 |
+
name String
|
| 14 |
+
description String?
|
| 15 |
+
apiKey String?
|
| 16 |
+
paymentId String?
|
| 17 |
+
streakSince DateTime?
|
| 18 |
+
createdAt DateTime @default(now())
|
| 19 |
+
updatedAt DateTime @updatedAt
|
| 20 |
+
allowTrial Boolean @default(false)
|
| 21 |
+
isTrailing Boolean @default(false)
|
| 22 |
+
shortlink ShortLinkPreference @default(ASK)
|
| 23 |
+
autoPost AutoPost[]
|
| 24 |
+
Comments Comments[]
|
| 25 |
+
credits Credits[]
|
| 26 |
+
customers Customer[]
|
| 27 |
+
errors Errors[]
|
| 28 |
+
github GitHub[]
|
| 29 |
+
Integration Integration[]
|
| 30 |
+
media Media[]
|
| 31 |
+
buyerOrganization MessagesGroup[]
|
| 32 |
+
notifications Notifications[]
|
| 33 |
+
plugs Plugs[]
|
| 34 |
+
post Post[] @relation("organization")
|
| 35 |
+
submittedPost Post[] @relation("submittedForOrg")
|
| 36 |
+
sets Sets[]
|
| 37 |
+
signatures Signatures[]
|
| 38 |
+
subscription Subscription?
|
| 39 |
+
tags Tags[]
|
| 40 |
+
thirdParty ThirdParty[]
|
| 41 |
+
usedCodes UsedCodes[]
|
| 42 |
+
users UserOrganization[]
|
| 43 |
+
webhooks Webhooks[]
|
| 44 |
+
oauthApp OAuthApp[]
|
| 45 |
+
oauthAuthorizations OAuthAuthorization[]
|
| 46 |
+
|
| 47 |
+
@@index([apiKey])
|
| 48 |
+
@@index([streakSince])
|
| 49 |
+
@@index([paymentId])
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
model Tags {
|
| 53 |
+
id String @id @default(uuid())
|
| 54 |
+
name String
|
| 55 |
+
color String
|
| 56 |
+
orgId String
|
| 57 |
+
deletedAt DateTime?
|
| 58 |
+
createdAt DateTime @default(now())
|
| 59 |
+
updatedAt DateTime @updatedAt
|
| 60 |
+
organization Organization @relation(fields: [orgId], references: [id])
|
| 61 |
+
posts TagsPosts[]
|
| 62 |
+
|
| 63 |
+
@@index([orgId])
|
| 64 |
+
@@index([deletedAt])
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
model TagsPosts {
|
| 68 |
+
postId String
|
| 69 |
+
tagId String
|
| 70 |
+
createdAt DateTime @default(now())
|
| 71 |
+
updatedAt DateTime @updatedAt
|
| 72 |
+
post Post @relation(fields: [postId], references: [id])
|
| 73 |
+
tag Tags @relation(fields: [tagId], references: [id])
|
| 74 |
+
|
| 75 |
+
@@id([postId, tagId])
|
| 76 |
+
@@unique([postId, tagId])
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
model User {
|
| 80 |
+
id String @id @default(uuid())
|
| 81 |
+
email String
|
| 82 |
+
password String?
|
| 83 |
+
providerName Provider
|
| 84 |
+
name String?
|
| 85 |
+
lastName String?
|
| 86 |
+
isSuperAdmin Boolean @default(false)
|
| 87 |
+
bio String?
|
| 88 |
+
audience Int @default(0)
|
| 89 |
+
pictureId String?
|
| 90 |
+
providerId String?
|
| 91 |
+
timezone Int
|
| 92 |
+
createdAt DateTime @default(now())
|
| 93 |
+
updatedAt DateTime @updatedAt
|
| 94 |
+
lastReadNotifications DateTime @default(now())
|
| 95 |
+
inviteId String?
|
| 96 |
+
activated Boolean @default(true)
|
| 97 |
+
account String?
|
| 98 |
+
connectedAccount Boolean @default(false)
|
| 99 |
+
lastOnline DateTime @default(now())
|
| 100 |
+
ip String?
|
| 101 |
+
agent String?
|
| 102 |
+
comments Comments[]
|
| 103 |
+
items ItemUser[]
|
| 104 |
+
groupBuyer MessagesGroup[] @relation("groupBuyer")
|
| 105 |
+
groupSeller MessagesGroup[] @relation("groupSeller")
|
| 106 |
+
orderBuyer Orders[] @relation("orderBuyer")
|
| 107 |
+
orderSeller Orders[] @relation("orderSeller")
|
| 108 |
+
payoutProblems PayoutProblems[]
|
| 109 |
+
agencies SocialMediaAgency?
|
| 110 |
+
picture Media? @relation(fields: [pictureId], references: [id])
|
| 111 |
+
organizations UserOrganization[]
|
| 112 |
+
sendSuccessEmails Boolean @default(true)
|
| 113 |
+
sendFailureEmails Boolean @default(true)
|
| 114 |
+
sendStreakEmails Boolean @default(true)
|
| 115 |
+
oauthAuthorizations OAuthAuthorization[]
|
| 116 |
+
|
| 117 |
+
@@unique([email, providerName])
|
| 118 |
+
@@index([lastReadNotifications])
|
| 119 |
+
@@index([inviteId])
|
| 120 |
+
@@index([account])
|
| 121 |
+
@@index([lastOnline])
|
| 122 |
+
@@index([pictureId])
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
model UsedCodes {
|
| 126 |
+
id String @id @default(uuid())
|
| 127 |
+
code String
|
| 128 |
+
orgId String
|
| 129 |
+
createdAt DateTime @default(now())
|
| 130 |
+
updatedAt DateTime @updatedAt
|
| 131 |
+
organization Organization @relation(fields: [orgId], references: [id])
|
| 132 |
+
|
| 133 |
+
@@index([code])
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
model UserOrganization {
|
| 137 |
+
id String @id @default(uuid())
|
| 138 |
+
userId String
|
| 139 |
+
organizationId String
|
| 140 |
+
disabled Boolean @default(false)
|
| 141 |
+
role Role @default(USER)
|
| 142 |
+
createdAt DateTime @default(now())
|
| 143 |
+
updatedAt DateTime @updatedAt
|
| 144 |
+
organization Organization @relation(fields: [organizationId], references: [id])
|
| 145 |
+
user User @relation(fields: [userId], references: [id])
|
| 146 |
+
|
| 147 |
+
@@unique([userId, organizationId])
|
| 148 |
+
@@index([disabled])
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
model GitHub {
|
| 152 |
+
id String @id @default(uuid())
|
| 153 |
+
login String?
|
| 154 |
+
name String?
|
| 155 |
+
token String
|
| 156 |
+
jobId String?
|
| 157 |
+
organizationId String
|
| 158 |
+
createdAt DateTime @default(now())
|
| 159 |
+
updatedAt DateTime @updatedAt
|
| 160 |
+
organization Organization @relation(fields: [organizationId], references: [id])
|
| 161 |
+
|
| 162 |
+
@@index([login])
|
| 163 |
+
@@index([organizationId])
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
model Trending {
|
| 167 |
+
id String @id @default(uuid())
|
| 168 |
+
trendingList String
|
| 169 |
+
language String? @unique
|
| 170 |
+
hash String
|
| 171 |
+
date DateTime
|
| 172 |
+
createdAt DateTime @default(now())
|
| 173 |
+
updatedAt DateTime @updatedAt
|
| 174 |
+
|
| 175 |
+
@@index([hash])
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
model TrendingLog {
|
| 179 |
+
id String @id @default(uuid())
|
| 180 |
+
language String?
|
| 181 |
+
date DateTime
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
model ItemUser {
|
| 185 |
+
id String @id @default(uuid())
|
| 186 |
+
userId String
|
| 187 |
+
key String
|
| 188 |
+
user User @relation(fields: [userId], references: [id])
|
| 189 |
+
|
| 190 |
+
@@unique([userId, key])
|
| 191 |
+
@@index([userId])
|
| 192 |
+
@@index([key])
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
model Star {
|
| 196 |
+
id String @id @default(uuid())
|
| 197 |
+
stars Int
|
| 198 |
+
totalStars Int
|
| 199 |
+
forks Int
|
| 200 |
+
totalForks Int
|
| 201 |
+
login String
|
| 202 |
+
date DateTime @default(now()) @db.Date
|
| 203 |
+
createdAt DateTime @default(now())
|
| 204 |
+
updatedAt DateTime @updatedAt
|
| 205 |
+
|
| 206 |
+
@@unique([login, date])
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
model Media {
|
| 210 |
+
id String @id @default(uuid())
|
| 211 |
+
name String
|
| 212 |
+
originalName String?
|
| 213 |
+
path String
|
| 214 |
+
organizationId String
|
| 215 |
+
createdAt DateTime @default(now())
|
| 216 |
+
updatedAt DateTime @updatedAt
|
| 217 |
+
deletedAt DateTime?
|
| 218 |
+
fileSize Int @default(0)
|
| 219 |
+
type String @default("image")
|
| 220 |
+
thumbnail String?
|
| 221 |
+
alt String?
|
| 222 |
+
thumbnailTimestamp Int?
|
| 223 |
+
organization Organization @relation(fields: [organizationId], references: [id])
|
| 224 |
+
agencies SocialMediaAgency[]
|
| 225 |
+
userPicture User[]
|
| 226 |
+
oauthApps OAuthApp[]
|
| 227 |
+
|
| 228 |
+
@@index([name])
|
| 229 |
+
@@index([organizationId])
|
| 230 |
+
@@index([type])
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
model SocialMediaAgency {
|
| 234 |
+
id String @id @default(uuid())
|
| 235 |
+
userId String @unique
|
| 236 |
+
name String
|
| 237 |
+
logoId String?
|
| 238 |
+
website String?
|
| 239 |
+
slug String?
|
| 240 |
+
facebook String?
|
| 241 |
+
instagram String?
|
| 242 |
+
twitter String?
|
| 243 |
+
linkedIn String?
|
| 244 |
+
youtube String?
|
| 245 |
+
tiktok String?
|
| 246 |
+
otherSocialMedia String?
|
| 247 |
+
shortDescription String
|
| 248 |
+
description String
|
| 249 |
+
approved Boolean @default(false)
|
| 250 |
+
createdAt DateTime @default(now())
|
| 251 |
+
updatedAt DateTime @updatedAt
|
| 252 |
+
deletedAt DateTime?
|
| 253 |
+
logo Media? @relation(fields: [logoId], references: [id])
|
| 254 |
+
user User @relation(fields: [userId], references: [id])
|
| 255 |
+
niches SocialMediaAgencyNiche[]
|
| 256 |
+
|
| 257 |
+
@@index([userId])
|
| 258 |
+
@@index([deletedAt])
|
| 259 |
+
@@index([id])
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
model SocialMediaAgencyNiche {
|
| 263 |
+
agencyId String
|
| 264 |
+
niche String
|
| 265 |
+
agency SocialMediaAgency @relation(fields: [agencyId], references: [id])
|
| 266 |
+
|
| 267 |
+
@@id([agencyId, niche])
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
model Credits {
|
| 271 |
+
id String @id @default(uuid())
|
| 272 |
+
organizationId String
|
| 273 |
+
credits Int
|
| 274 |
+
createdAt DateTime @default(now())
|
| 275 |
+
updatedAt DateTime @updatedAt
|
| 276 |
+
type String @default("ai_images")
|
| 277 |
+
organization Organization @relation(fields: [organizationId], references: [id])
|
| 278 |
+
|
| 279 |
+
@@index([organizationId])
|
| 280 |
+
@@index([createdAt])
|
| 281 |
+
}
|
| 282 |
+
|
| 283 |
+
model Subscription {
|
| 284 |
+
id String @id @default(cuid())
|
| 285 |
+
organizationId String @unique
|
| 286 |
+
subscriptionTier SubscriptionTier
|
| 287 |
+
identifier String?
|
| 288 |
+
cancelAt DateTime?
|
| 289 |
+
period Period
|
| 290 |
+
totalChannels Int
|
| 291 |
+
isLifetime Boolean @default(false)
|
| 292 |
+
createdAt DateTime @default(now())
|
| 293 |
+
updatedAt DateTime @updatedAt
|
| 294 |
+
deletedAt DateTime?
|
| 295 |
+
organization Organization @relation(fields: [organizationId], references: [id])
|
| 296 |
+
|
| 297 |
+
@@index([organizationId])
|
| 298 |
+
@@index([deletedAt])
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
model Customer {
|
| 302 |
+
id String @id @default(uuid())
|
| 303 |
+
name String
|
| 304 |
+
orgId String
|
| 305 |
+
createdAt DateTime @default(now())
|
| 306 |
+
updatedAt DateTime @updatedAt
|
| 307 |
+
deletedAt DateTime?
|
| 308 |
+
organization Organization @relation(fields: [orgId], references: [id])
|
| 309 |
+
integrations Integration[]
|
| 310 |
+
|
| 311 |
+
@@unique([orgId, name, deletedAt])
|
| 312 |
+
}
|
| 313 |
+
|
| 314 |
+
model Integration {
|
| 315 |
+
id String @id @default(cuid())
|
| 316 |
+
internalId String
|
| 317 |
+
organizationId String
|
| 318 |
+
name String
|
| 319 |
+
picture String?
|
| 320 |
+
providerIdentifier String
|
| 321 |
+
type String
|
| 322 |
+
token String
|
| 323 |
+
disabled Boolean @default(false)
|
| 324 |
+
tokenExpiration DateTime?
|
| 325 |
+
refreshToken String?
|
| 326 |
+
profile String?
|
| 327 |
+
deletedAt DateTime?
|
| 328 |
+
createdAt DateTime @default(now())
|
| 329 |
+
updatedAt DateTime? @updatedAt
|
| 330 |
+
inBetweenSteps Boolean @default(false)
|
| 331 |
+
refreshNeeded Boolean @default(false)
|
| 332 |
+
postingTimes String @default("[{\"time\":120}, {\"time\":400}, {\"time\":700}]")
|
| 333 |
+
customInstanceDetails String?
|
| 334 |
+
customerId String?
|
| 335 |
+
rootInternalId String?
|
| 336 |
+
additionalSettings String? @default("[]")
|
| 337 |
+
exisingPlugData ExisingPlugData[]
|
| 338 |
+
customer Customer? @relation(fields: [customerId], references: [id])
|
| 339 |
+
organization Organization @relation(fields: [organizationId], references: [id])
|
| 340 |
+
webhooks IntegrationsWebhooks[]
|
| 341 |
+
orderItems OrderItems[]
|
| 342 |
+
plugs Plugs[]
|
| 343 |
+
posts Post[]
|
| 344 |
+
|
| 345 |
+
@@unique([organizationId, internalId])
|
| 346 |
+
@@index([rootInternalId])
|
| 347 |
+
@@index([organizationId])
|
| 348 |
+
@@index([providerIdentifier])
|
| 349 |
+
@@index([updatedAt])
|
| 350 |
+
@@index([createdAt])
|
| 351 |
+
@@index([deletedAt])
|
| 352 |
+
@@index([customerId])
|
| 353 |
+
@@index([inBetweenSteps])
|
| 354 |
+
@@index([refreshNeeded])
|
| 355 |
+
@@index([disabled])
|
| 356 |
+
}
|
| 357 |
+
|
| 358 |
+
model Signatures {
|
| 359 |
+
id String @id @default(uuid())
|
| 360 |
+
organizationId String
|
| 361 |
+
content String
|
| 362 |
+
autoAdd Boolean
|
| 363 |
+
createdAt DateTime @default(now())
|
| 364 |
+
updatedAt DateTime @updatedAt
|
| 365 |
+
deletedAt DateTime?
|
| 366 |
+
organization Organization @relation(fields: [organizationId], references: [id])
|
| 367 |
+
|
| 368 |
+
@@index([createdAt])
|
| 369 |
+
@@index([organizationId])
|
| 370 |
+
@@index([deletedAt])
|
| 371 |
+
}
|
| 372 |
+
|
| 373 |
+
model Comments {
|
| 374 |
+
id String @id @default(uuid())
|
| 375 |
+
content String
|
| 376 |
+
organizationId String
|
| 377 |
+
postId String
|
| 378 |
+
userId String
|
| 379 |
+
createdAt DateTime @default(now())
|
| 380 |
+
updatedAt DateTime @updatedAt
|
| 381 |
+
deletedAt DateTime?
|
| 382 |
+
organization Organization @relation(fields: [organizationId], references: [id])
|
| 383 |
+
post Post @relation(fields: [postId], references: [id])
|
| 384 |
+
user User @relation(fields: [userId], references: [id])
|
| 385 |
+
|
| 386 |
+
@@index([createdAt])
|
| 387 |
+
@@index([organizationId])
|
| 388 |
+
@@index([userId])
|
| 389 |
+
@@index([postId])
|
| 390 |
+
@@index([deletedAt])
|
| 391 |
+
}
|
| 392 |
+
|
| 393 |
+
model Post {
|
| 394 |
+
id String @id @default(cuid())
|
| 395 |
+
state State @default(QUEUE)
|
| 396 |
+
publishDate DateTime
|
| 397 |
+
organizationId String
|
| 398 |
+
integrationId String
|
| 399 |
+
content String
|
| 400 |
+
delay Int @default(0)
|
| 401 |
+
group String
|
| 402 |
+
title String?
|
| 403 |
+
description String?
|
| 404 |
+
parentPostId String?
|
| 405 |
+
releaseId String?
|
| 406 |
+
releaseURL String?
|
| 407 |
+
settings String?
|
| 408 |
+
image String?
|
| 409 |
+
submittedForOrderId String?
|
| 410 |
+
submittedForOrganizationId String?
|
| 411 |
+
approvedSubmitForOrder APPROVED_SUBMIT_FOR_ORDER @default(NO)
|
| 412 |
+
creationMethod CreationMethod @default(UNKNOWN)
|
| 413 |
+
lastMessageId String?
|
| 414 |
+
intervalInDays Int?
|
| 415 |
+
error String?
|
| 416 |
+
createdAt DateTime @default(now())
|
| 417 |
+
updatedAt DateTime @updatedAt
|
| 418 |
+
deletedAt DateTime?
|
| 419 |
+
comments Comments[]
|
| 420 |
+
errors Errors[]
|
| 421 |
+
payoutProblems PayoutProblems[]
|
| 422 |
+
integration Integration @relation(fields: [integrationId], references: [id])
|
| 423 |
+
lastMessage Messages? @relation(fields: [lastMessageId], references: [id])
|
| 424 |
+
organization Organization @relation("organization", fields: [organizationId], references: [id])
|
| 425 |
+
parentPost Post? @relation("parentPostId", fields: [parentPostId], references: [id])
|
| 426 |
+
childrenPost Post[] @relation("parentPostId")
|
| 427 |
+
submittedForOrder Orders? @relation(fields: [submittedForOrderId], references: [id])
|
| 428 |
+
submittedForOrganization Organization? @relation("submittedForOrg", fields: [submittedForOrganizationId], references: [id])
|
| 429 |
+
tags TagsPosts[]
|
| 430 |
+
|
| 431 |
+
@@index([group])
|
| 432 |
+
@@index([deletedAt])
|
| 433 |
+
@@index([publishDate])
|
| 434 |
+
@@index([state])
|
| 435 |
+
@@index([organizationId])
|
| 436 |
+
@@index([parentPostId])
|
| 437 |
+
@@index([submittedForOrderId])
|
| 438 |
+
@@index([intervalInDays])
|
| 439 |
+
@@index([approvedSubmitForOrder])
|
| 440 |
+
@@index([creationMethod])
|
| 441 |
+
@@index([lastMessageId])
|
| 442 |
+
@@index([createdAt])
|
| 443 |
+
@@index([updatedAt])
|
| 444 |
+
@@index([releaseURL])
|
| 445 |
+
@@index([integrationId])
|
| 446 |
+
}
|
| 447 |
+
|
| 448 |
+
model Notifications {
|
| 449 |
+
id String @id @default(uuid())
|
| 450 |
+
organizationId String
|
| 451 |
+
content String
|
| 452 |
+
link String?
|
| 453 |
+
createdAt DateTime @default(now())
|
| 454 |
+
updatedAt DateTime @updatedAt
|
| 455 |
+
deletedAt DateTime?
|
| 456 |
+
organization Organization @relation(fields: [organizationId], references: [id])
|
| 457 |
+
|
| 458 |
+
@@index([createdAt])
|
| 459 |
+
@@index([organizationId])
|
| 460 |
+
@@index([deletedAt])
|
| 461 |
+
}
|
| 462 |
+
|
| 463 |
+
model MessagesGroup {
|
| 464 |
+
id String @id @default(uuid())
|
| 465 |
+
buyerOrganizationId String
|
| 466 |
+
buyerId String
|
| 467 |
+
sellerId String
|
| 468 |
+
createdAt DateTime @default(now())
|
| 469 |
+
updatedAt DateTime @updatedAt
|
| 470 |
+
messages Messages[]
|
| 471 |
+
buyer User @relation("groupBuyer", fields: [buyerId], references: [id])
|
| 472 |
+
buyerOrganization Organization @relation(fields: [buyerOrganizationId], references: [id])
|
| 473 |
+
seller User @relation("groupSeller", fields: [sellerId], references: [id])
|
| 474 |
+
orders Orders[]
|
| 475 |
+
|
| 476 |
+
@@unique([buyerId, sellerId])
|
| 477 |
+
@@index([createdAt])
|
| 478 |
+
@@index([updatedAt])
|
| 479 |
+
@@index([buyerOrganizationId])
|
| 480 |
+
}
|
| 481 |
+
|
| 482 |
+
model PayoutProblems {
|
| 483 |
+
id String @id @default(uuid())
|
| 484 |
+
status String
|
| 485 |
+
orderId String
|
| 486 |
+
userId String
|
| 487 |
+
postId String?
|
| 488 |
+
amount Int
|
| 489 |
+
createdAt DateTime @default(now())
|
| 490 |
+
updatedAt DateTime @updatedAt
|
| 491 |
+
order Orders @relation(fields: [orderId], references: [id])
|
| 492 |
+
post Post? @relation(fields: [postId], references: [id])
|
| 493 |
+
user User @relation(fields: [userId], references: [id])
|
| 494 |
+
}
|
| 495 |
+
|
| 496 |
+
model Orders {
|
| 497 |
+
id String @id @default(uuid())
|
| 498 |
+
buyerId String
|
| 499 |
+
sellerId String
|
| 500 |
+
status OrderStatus
|
| 501 |
+
messageGroupId String
|
| 502 |
+
captureId String?
|
| 503 |
+
createdAt DateTime @default(now())
|
| 504 |
+
updatedAt DateTime @updatedAt
|
| 505 |
+
ordersItems OrderItems[]
|
| 506 |
+
buyer User @relation("orderBuyer", fields: [buyerId], references: [id])
|
| 507 |
+
messageGroup MessagesGroup @relation(fields: [messageGroupId], references: [id])
|
| 508 |
+
seller User @relation("orderSeller", fields: [sellerId], references: [id])
|
| 509 |
+
payoutProblems PayoutProblems[]
|
| 510 |
+
posts Post[]
|
| 511 |
+
|
| 512 |
+
@@index([buyerId])
|
| 513 |
+
@@index([sellerId])
|
| 514 |
+
@@index([updatedAt])
|
| 515 |
+
@@index([createdAt])
|
| 516 |
+
@@index([messageGroupId])
|
| 517 |
+
}
|
| 518 |
+
|
| 519 |
+
model OrderItems {
|
| 520 |
+
id String @id @default(uuid())
|
| 521 |
+
orderId String
|
| 522 |
+
integrationId String
|
| 523 |
+
quantity Int
|
| 524 |
+
price Int
|
| 525 |
+
integration Integration @relation(fields: [integrationId], references: [id])
|
| 526 |
+
order Orders @relation(fields: [orderId], references: [id])
|
| 527 |
+
|
| 528 |
+
@@index([orderId])
|
| 529 |
+
@@index([integrationId])
|
| 530 |
+
}
|
| 531 |
+
|
| 532 |
+
model Messages {
|
| 533 |
+
id String @id @default(uuid())
|
| 534 |
+
from From
|
| 535 |
+
content String?
|
| 536 |
+
groupId String
|
| 537 |
+
special String?
|
| 538 |
+
createdAt DateTime @default(now())
|
| 539 |
+
updatedAt DateTime @updatedAt
|
| 540 |
+
deletedAt DateTime?
|
| 541 |
+
group MessagesGroup @relation(fields: [groupId], references: [id])
|
| 542 |
+
posts Post[]
|
| 543 |
+
|
| 544 |
+
@@index([groupId])
|
| 545 |
+
@@index([createdAt])
|
| 546 |
+
@@index([deletedAt])
|
| 547 |
+
}
|
| 548 |
+
|
| 549 |
+
model Plugs {
|
| 550 |
+
id String @id @default(uuid())
|
| 551 |
+
organizationId String
|
| 552 |
+
plugFunction String
|
| 553 |
+
data String
|
| 554 |
+
integrationId String
|
| 555 |
+
activated Boolean @default(true)
|
| 556 |
+
integration Integration @relation(fields: [integrationId], references: [id])
|
| 557 |
+
organization Organization @relation(fields: [organizationId], references: [id])
|
| 558 |
+
|
| 559 |
+
@@unique([plugFunction, integrationId])
|
| 560 |
+
@@index([organizationId])
|
| 561 |
+
}
|
| 562 |
+
|
| 563 |
+
model ExisingPlugData {
|
| 564 |
+
id String @id @default(uuid())
|
| 565 |
+
integrationId String
|
| 566 |
+
methodName String
|
| 567 |
+
value String
|
| 568 |
+
integration Integration @relation(fields: [integrationId], references: [id])
|
| 569 |
+
|
| 570 |
+
@@unique([integrationId, methodName, value])
|
| 571 |
+
}
|
| 572 |
+
|
| 573 |
+
model PopularPosts {
|
| 574 |
+
id String @id @default(uuid())
|
| 575 |
+
category String
|
| 576 |
+
topic String
|
| 577 |
+
content String
|
| 578 |
+
hook String
|
| 579 |
+
createdAt DateTime @default(now())
|
| 580 |
+
updatedAt DateTime @updatedAt
|
| 581 |
+
}
|
| 582 |
+
|
| 583 |
+
model IntegrationsWebhooks {
|
| 584 |
+
integrationId String
|
| 585 |
+
webhookId String
|
| 586 |
+
integration Integration @relation(fields: [integrationId], references: [id])
|
| 587 |
+
webhook Webhooks @relation(fields: [webhookId], references: [id])
|
| 588 |
+
|
| 589 |
+
@@id([integrationId, webhookId])
|
| 590 |
+
@@unique([integrationId, webhookId])
|
| 591 |
+
@@index([integrationId])
|
| 592 |
+
@@index([webhookId])
|
| 593 |
+
}
|
| 594 |
+
|
| 595 |
+
model Webhooks {
|
| 596 |
+
id String @id @default(uuid())
|
| 597 |
+
name String
|
| 598 |
+
organizationId String
|
| 599 |
+
url String
|
| 600 |
+
deletedAt DateTime?
|
| 601 |
+
createdAt DateTime @default(now())
|
| 602 |
+
updatedAt DateTime @updatedAt
|
| 603 |
+
integrations IntegrationsWebhooks[]
|
| 604 |
+
organization Organization @relation(fields: [organizationId], references: [id])
|
| 605 |
+
|
| 606 |
+
@@index([organizationId])
|
| 607 |
+
@@index([deletedAt])
|
| 608 |
+
}
|
| 609 |
+
|
| 610 |
+
model AutoPost {
|
| 611 |
+
id String @id @default(uuid())
|
| 612 |
+
organizationId String
|
| 613 |
+
title String
|
| 614 |
+
content String?
|
| 615 |
+
onSlot Boolean
|
| 616 |
+
syncLast Boolean
|
| 617 |
+
url String
|
| 618 |
+
lastUrl String
|
| 619 |
+
active Boolean
|
| 620 |
+
addPicture Boolean
|
| 621 |
+
generateContent Boolean
|
| 622 |
+
integrations String
|
| 623 |
+
deletedAt DateTime?
|
| 624 |
+
createdAt DateTime @default(now())
|
| 625 |
+
updatedAt DateTime @updatedAt
|
| 626 |
+
organization Organization @relation(fields: [organizationId], references: [id])
|
| 627 |
+
|
| 628 |
+
@@index([deletedAt])
|
| 629 |
+
}
|
| 630 |
+
|
| 631 |
+
model Sets {
|
| 632 |
+
id String @id @default(uuid())
|
| 633 |
+
organizationId String
|
| 634 |
+
name String
|
| 635 |
+
content String
|
| 636 |
+
createdAt DateTime @default(now())
|
| 637 |
+
updatedAt DateTime @updatedAt
|
| 638 |
+
organization Organization @relation(fields: [organizationId], references: [id])
|
| 639 |
+
|
| 640 |
+
@@index([organizationId])
|
| 641 |
+
}
|
| 642 |
+
|
| 643 |
+
model ThirdParty {
|
| 644 |
+
id String @id @default(uuid())
|
| 645 |
+
organizationId String
|
| 646 |
+
identifier String
|
| 647 |
+
name String
|
| 648 |
+
internalId String
|
| 649 |
+
apiKey String
|
| 650 |
+
createdAt DateTime @default(now())
|
| 651 |
+
updatedAt DateTime @updatedAt
|
| 652 |
+
deletedAt DateTime?
|
| 653 |
+
organization Organization @relation(fields: [organizationId], references: [id])
|
| 654 |
+
|
| 655 |
+
@@unique([organizationId, internalId])
|
| 656 |
+
@@index([organizationId])
|
| 657 |
+
@@index([deletedAt])
|
| 658 |
+
}
|
| 659 |
+
|
| 660 |
+
model Errors {
|
| 661 |
+
id String @id @default(uuid())
|
| 662 |
+
message String
|
| 663 |
+
platform String
|
| 664 |
+
organizationId String
|
| 665 |
+
createdAt DateTime @default(now())
|
| 666 |
+
updatedAt DateTime @updatedAt
|
| 667 |
+
postId String
|
| 668 |
+
body String @default("{}")
|
| 669 |
+
organization Organization @relation(fields: [organizationId], references: [id])
|
| 670 |
+
post Post @relation(fields: [postId], references: [id])
|
| 671 |
+
|
| 672 |
+
@@index([organizationId])
|
| 673 |
+
@@index([createdAt])
|
| 674 |
+
}
|
| 675 |
+
|
| 676 |
+
model Mentions {
|
| 677 |
+
name String
|
| 678 |
+
username String
|
| 679 |
+
platform String
|
| 680 |
+
createdAt DateTime @default(now())
|
| 681 |
+
updatedAt DateTime @updatedAt
|
| 682 |
+
image String
|
| 683 |
+
|
| 684 |
+
@@id([name, username, platform, image])
|
| 685 |
+
@@index([createdAt])
|
| 686 |
+
}
|
| 687 |
+
|
| 688 |
+
/// The underlying table does not contain a valid unique identifier and can therefore currently not be handled by Prisma Client.
|
| 689 |
+
model mastra_ai_spans {
|
| 690 |
+
traceId String
|
| 691 |
+
spanId String
|
| 692 |
+
parentSpanId String?
|
| 693 |
+
name String
|
| 694 |
+
scope Json?
|
| 695 |
+
spanType String
|
| 696 |
+
attributes Json?
|
| 697 |
+
metadata Json?
|
| 698 |
+
links Json?
|
| 699 |
+
input Json?
|
| 700 |
+
output Json?
|
| 701 |
+
error Json?
|
| 702 |
+
startedAt DateTime @db.Timestamp(6)
|
| 703 |
+
endedAt DateTime? @db.Timestamp(6)
|
| 704 |
+
createdAt DateTime @db.Timestamp(6)
|
| 705 |
+
updatedAt DateTime? @db.Timestamp(6)
|
| 706 |
+
isEvent Boolean
|
| 707 |
+
startedAtZ DateTime? @default(now()) @db.Timestamptz(6)
|
| 708 |
+
endedAtZ DateTime? @default(now()) @db.Timestamptz(6)
|
| 709 |
+
createdAtZ DateTime? @default(now()) @db.Timestamptz(6)
|
| 710 |
+
updatedAtZ DateTime? @default(now()) @db.Timestamptz(6)
|
| 711 |
+
|
| 712 |
+
@@index([name], map: "public_mastra_ai_spans_name_idx")
|
| 713 |
+
@@index([parentSpanId, startedAt(sort: Desc)], map: "public_mastra_ai_spans_parentspanid_startedat_idx")
|
| 714 |
+
@@index([spanType, startedAt(sort: Desc)], map: "public_mastra_ai_spans_spantype_startedat_idx")
|
| 715 |
+
@@index([traceId, startedAt(sort: Desc)], map: "public_mastra_ai_spans_traceid_startedat_idx")
|
| 716 |
+
@@ignore
|
| 717 |
+
}
|
| 718 |
+
|
| 719 |
+
/// The underlying table does not contain a valid unique identifier and can therefore currently not be handled by Prisma Client.
|
| 720 |
+
model mastra_evals {
|
| 721 |
+
input String
|
| 722 |
+
output String
|
| 723 |
+
result Json
|
| 724 |
+
agent_name String
|
| 725 |
+
metric_name String
|
| 726 |
+
instructions String
|
| 727 |
+
test_info Json?
|
| 728 |
+
global_run_id String
|
| 729 |
+
run_id String
|
| 730 |
+
created_at DateTime @db.Timestamp(6)
|
| 731 |
+
createdAt DateTime? @db.Timestamp(6)
|
| 732 |
+
created_atZ DateTime? @default(now()) @db.Timestamptz(6)
|
| 733 |
+
createdAtZ DateTime? @default(now()) @db.Timestamptz(6)
|
| 734 |
+
|
| 735 |
+
@@index([agent_name, created_at(sort: Desc)], map: "public_mastra_evals_agent_name_created_at_idx")
|
| 736 |
+
@@ignore
|
| 737 |
+
}
|
| 738 |
+
|
| 739 |
+
model mastra_messages {
|
| 740 |
+
id String @id
|
| 741 |
+
thread_id String
|
| 742 |
+
content String
|
| 743 |
+
role String
|
| 744 |
+
type String
|
| 745 |
+
createdAt DateTime @db.Timestamp(6)
|
| 746 |
+
resourceId String?
|
| 747 |
+
createdAtZ DateTime? @default(now()) @db.Timestamptz(6)
|
| 748 |
+
|
| 749 |
+
@@index([thread_id, createdAt(sort: Desc)], map: "public_mastra_messages_thread_id_createdat_idx")
|
| 750 |
+
}
|
| 751 |
+
|
| 752 |
+
model mastra_resources {
|
| 753 |
+
id String @id
|
| 754 |
+
workingMemory String?
|
| 755 |
+
metadata Json?
|
| 756 |
+
createdAt DateTime @db.Timestamp(6)
|
| 757 |
+
updatedAt DateTime @db.Timestamp(6)
|
| 758 |
+
createdAtZ DateTime? @default(now()) @db.Timestamptz(6)
|
| 759 |
+
updatedAtZ DateTime? @default(now()) @db.Timestamptz(6)
|
| 760 |
+
}
|
| 761 |
+
|
| 762 |
+
model mastra_scorers {
|
| 763 |
+
id String @id
|
| 764 |
+
scorerId String
|
| 765 |
+
traceId String?
|
| 766 |
+
runId String
|
| 767 |
+
scorer Json
|
| 768 |
+
preprocessStepResult Json?
|
| 769 |
+
extractStepResult Json?
|
| 770 |
+
analyzeStepResult Json?
|
| 771 |
+
score Float
|
| 772 |
+
reason String?
|
| 773 |
+
metadata Json?
|
| 774 |
+
preprocessPrompt String?
|
| 775 |
+
extractPrompt String?
|
| 776 |
+
generateScorePrompt String?
|
| 777 |
+
generateReasonPrompt String?
|
| 778 |
+
analyzePrompt String?
|
| 779 |
+
reasonPrompt String?
|
| 780 |
+
input Json
|
| 781 |
+
output Json
|
| 782 |
+
additionalContext Json?
|
| 783 |
+
runtimeContext Json?
|
| 784 |
+
entityType String?
|
| 785 |
+
entity Json?
|
| 786 |
+
entityId String?
|
| 787 |
+
source String
|
| 788 |
+
resourceId String?
|
| 789 |
+
threadId String?
|
| 790 |
+
createdAt DateTime @db.Timestamp(6)
|
| 791 |
+
updatedAt DateTime @db.Timestamp(6)
|
| 792 |
+
createdAtZ DateTime? @default(now()) @db.Timestamptz(6)
|
| 793 |
+
updatedAtZ DateTime? @default(now()) @db.Timestamptz(6)
|
| 794 |
+
spanId String?
|
| 795 |
+
|
| 796 |
+
@@index([traceId, spanId, createdAt(sort: Desc)], map: "public_mastra_scores_trace_id_span_id_created_at_idx")
|
| 797 |
+
}
|
| 798 |
+
|
| 799 |
+
model mastra_threads {
|
| 800 |
+
id String @id
|
| 801 |
+
resourceId String
|
| 802 |
+
title String
|
| 803 |
+
metadata String?
|
| 804 |
+
createdAt DateTime @db.Timestamp(6)
|
| 805 |
+
updatedAt DateTime @db.Timestamp(6)
|
| 806 |
+
createdAtZ DateTime? @default(now()) @db.Timestamptz(6)
|
| 807 |
+
updatedAtZ DateTime? @default(now()) @db.Timestamptz(6)
|
| 808 |
+
|
| 809 |
+
@@index([resourceId, createdAt(sort: Desc)], map: "public_mastra_threads_resourceid_createdat_idx")
|
| 810 |
+
}
|
| 811 |
+
|
| 812 |
+
model mastra_traces {
|
| 813 |
+
id String @id
|
| 814 |
+
parentSpanId String?
|
| 815 |
+
name String
|
| 816 |
+
traceId String
|
| 817 |
+
scope String
|
| 818 |
+
kind Int
|
| 819 |
+
attributes Json?
|
| 820 |
+
status Json?
|
| 821 |
+
events Json?
|
| 822 |
+
links Json?
|
| 823 |
+
other String?
|
| 824 |
+
startTime BigInt
|
| 825 |
+
endTime BigInt
|
| 826 |
+
createdAt DateTime @db.Timestamp(6)
|
| 827 |
+
createdAtZ DateTime? @default(now()) @db.Timestamptz(6)
|
| 828 |
+
|
| 829 |
+
@@index([name, startTime(sort: Desc)], map: "public_mastra_traces_name_starttime_idx")
|
| 830 |
+
}
|
| 831 |
+
|
| 832 |
+
model mastra_workflow_snapshot {
|
| 833 |
+
workflow_name String
|
| 834 |
+
run_id String
|
| 835 |
+
resourceId String?
|
| 836 |
+
snapshot String
|
| 837 |
+
createdAt DateTime @db.Timestamp(6)
|
| 838 |
+
updatedAt DateTime @db.Timestamp(6)
|
| 839 |
+
createdAtZ DateTime? @default(now()) @db.Timestamptz(6)
|
| 840 |
+
updatedAtZ DateTime? @default(now()) @db.Timestamptz(6)
|
| 841 |
+
|
| 842 |
+
@@unique([workflow_name, run_id], map: "public_mastra_workflow_snapshot_workflow_name_run_id_key")
|
| 843 |
+
}
|
| 844 |
+
|
| 845 |
+
model OAuthApp {
|
| 846 |
+
id String @id @default(uuid())
|
| 847 |
+
organizationId String
|
| 848 |
+
name String
|
| 849 |
+
description String?
|
| 850 |
+
pictureId String?
|
| 851 |
+
redirectUrl String
|
| 852 |
+
clientId String @unique
|
| 853 |
+
clientSecret String
|
| 854 |
+
createdAt DateTime @default(now())
|
| 855 |
+
updatedAt DateTime @updatedAt
|
| 856 |
+
deletedAt DateTime?
|
| 857 |
+
organization Organization @relation(fields: [organizationId], references: [id])
|
| 858 |
+
picture Media? @relation(fields: [pictureId], references: [id])
|
| 859 |
+
authorizations OAuthAuthorization[]
|
| 860 |
+
|
| 861 |
+
@@unique([organizationId, deletedAt])
|
| 862 |
+
@@index([clientId])
|
| 863 |
+
@@index([organizationId])
|
| 864 |
+
@@index([deletedAt])
|
| 865 |
+
}
|
| 866 |
+
|
| 867 |
+
model OAuthAuthorization {
|
| 868 |
+
id String @id @default(uuid())
|
| 869 |
+
oauthAppId String
|
| 870 |
+
userId String
|
| 871 |
+
organizationId String
|
| 872 |
+
accessToken String?
|
| 873 |
+
authorizationCode String?
|
| 874 |
+
codeExpiresAt DateTime?
|
| 875 |
+
revokedAt DateTime?
|
| 876 |
+
createdAt DateTime @default(now())
|
| 877 |
+
updatedAt DateTime @updatedAt
|
| 878 |
+
oauthApp OAuthApp @relation(fields: [oauthAppId], references: [id])
|
| 879 |
+
user User @relation(fields: [userId], references: [id])
|
| 880 |
+
organization Organization @relation(fields: [organizationId], references: [id])
|
| 881 |
+
|
| 882 |
+
@@unique([oauthAppId, userId, organizationId])
|
| 883 |
+
@@index([accessToken])
|
| 884 |
+
@@index([authorizationCode])
|
| 885 |
+
@@index([oauthAppId])
|
| 886 |
+
@@index([userId])
|
| 887 |
+
@@index([organizationId])
|
| 888 |
+
@@index([revokedAt])
|
| 889 |
+
}
|
| 890 |
+
|
| 891 |
+
enum OrderStatus {
|
| 892 |
+
PENDING
|
| 893 |
+
ACCEPTED
|
| 894 |
+
CANCELED
|
| 895 |
+
COMPLETED
|
| 896 |
+
}
|
| 897 |
+
|
| 898 |
+
enum From {
|
| 899 |
+
BUYER
|
| 900 |
+
SELLER
|
| 901 |
+
}
|
| 902 |
+
|
| 903 |
+
enum State {
|
| 904 |
+
QUEUE
|
| 905 |
+
PUBLISHED
|
| 906 |
+
ERROR
|
| 907 |
+
DRAFT
|
| 908 |
+
}
|
| 909 |
+
|
| 910 |
+
enum SubscriptionTier {
|
| 911 |
+
STANDARD
|
| 912 |
+
PRO
|
| 913 |
+
TEAM
|
| 914 |
+
ULTIMATE
|
| 915 |
+
}
|
| 916 |
+
|
| 917 |
+
enum Period {
|
| 918 |
+
MONTHLY
|
| 919 |
+
YEARLY
|
| 920 |
+
}
|
| 921 |
+
|
| 922 |
+
enum Provider {
|
| 923 |
+
LOCAL
|
| 924 |
+
GITHUB
|
| 925 |
+
GOOGLE
|
| 926 |
+
FARCASTER
|
| 927 |
+
WALLET
|
| 928 |
+
GENERIC
|
| 929 |
+
}
|
| 930 |
+
|
| 931 |
+
enum Role {
|
| 932 |
+
SUPERADMIN
|
| 933 |
+
ADMIN
|
| 934 |
+
USER
|
| 935 |
+
}
|
| 936 |
+
|
| 937 |
+
enum APPROVED_SUBMIT_FOR_ORDER {
|
| 938 |
+
NO
|
| 939 |
+
WAITING_CONFIRMATION
|
| 940 |
+
YES
|
| 941 |
+
}
|
| 942 |
+
|
| 943 |
+
enum CreationMethod {
|
| 944 |
+
UNKNOWN
|
| 945 |
+
WEB
|
| 946 |
+
MCP
|
| 947 |
+
API
|
| 948 |
+
AUTOPOST
|
| 949 |
+
CLI
|
| 950 |
+
}
|
| 951 |
+
|
| 952 |
+
enum ShortLinkPreference {
|
| 953 |
+
ASK
|
| 954 |
+
YES
|
| 955 |
+
NO
|
| 956 |
+
}
|
| 957 |
+
|
| 958 |
+
enum AnnouncementColor {
|
| 959 |
+
INFO
|
| 960 |
+
WARNING
|
| 961 |
+
ERROR
|
| 962 |
+
}
|
| 963 |
+
|
| 964 |
+
model Announcement {
|
| 965 |
+
id String @id @default(uuid())
|
| 966 |
+
title String
|
| 967 |
+
description String
|
| 968 |
+
color AnnouncementColor @default(INFO)
|
| 969 |
+
createdAt DateTime @default(now())
|
| 970 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/sets/sets.repository.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';
|
| 2 |
+
import { Injectable } from '@nestjs/common';
|
| 3 |
+
import { SetsDto } from '@gitroom/nestjs-libraries/dtos/sets/sets.dto';
|
| 4 |
+
import { v4 as uuidv4 } from 'uuid';
|
| 5 |
+
|
| 6 |
+
@Injectable()
|
| 7 |
+
export class SetsRepository {
|
| 8 |
+
constructor(private _sets: PrismaRepository<'sets'>) {}
|
| 9 |
+
|
| 10 |
+
getTotal(orgId: string) {
|
| 11 |
+
return this._sets.model.sets.count({
|
| 12 |
+
where: {
|
| 13 |
+
organizationId: orgId,
|
| 14 |
+
},
|
| 15 |
+
});
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
getSets(orgId: string) {
|
| 19 |
+
return this._sets.model.sets.findMany({
|
| 20 |
+
where: {
|
| 21 |
+
organizationId: orgId,
|
| 22 |
+
},
|
| 23 |
+
orderBy: {
|
| 24 |
+
createdAt: 'desc',
|
| 25 |
+
},
|
| 26 |
+
});
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
deleteSet(orgId: string, id: string) {
|
| 30 |
+
return this._sets.model.sets.delete({
|
| 31 |
+
where: {
|
| 32 |
+
id,
|
| 33 |
+
organizationId: orgId,
|
| 34 |
+
},
|
| 35 |
+
});
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
async createSet(orgId: string, body: SetsDto) {
|
| 39 |
+
const { id } = await this._sets.model.sets.upsert({
|
| 40 |
+
where: {
|
| 41 |
+
id: body.id || uuidv4(),
|
| 42 |
+
organizationId: orgId,
|
| 43 |
+
},
|
| 44 |
+
create: {
|
| 45 |
+
id: body.id || uuidv4(),
|
| 46 |
+
organizationId: orgId,
|
| 47 |
+
name: body.name,
|
| 48 |
+
content: body.content,
|
| 49 |
+
},
|
| 50 |
+
update: {
|
| 51 |
+
name: body.name,
|
| 52 |
+
content: body.content,
|
| 53 |
+
},
|
| 54 |
+
});
|
| 55 |
+
|
| 56 |
+
return { id };
|
| 57 |
+
}
|
| 58 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/sets/sets.service.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Injectable } from '@nestjs/common';
|
| 2 |
+
import { SetsRepository } from '@gitroom/nestjs-libraries/database/prisma/sets/sets.repository';
|
| 3 |
+
import { SetsDto } from '@gitroom/nestjs-libraries/dtos/sets/sets.dto';
|
| 4 |
+
|
| 5 |
+
@Injectable()
|
| 6 |
+
export class SetsService {
|
| 7 |
+
constructor(private _setsRepository: SetsRepository) {}
|
| 8 |
+
|
| 9 |
+
getTotal(orgId: string) {
|
| 10 |
+
return this._setsRepository.getTotal(orgId);
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
getSets(orgId: string) {
|
| 14 |
+
return this._setsRepository.getSets(orgId);
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
createSet(orgId: string, body: SetsDto) {
|
| 18 |
+
return this._setsRepository.createSet(orgId, body);
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
deleteSet(orgId: string, id: string) {
|
| 22 |
+
return this._setsRepository.deleteSet(orgId, id);
|
| 23 |
+
}
|
| 24 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/signatures/signature.repository.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';
|
| 2 |
+
import { Injectable } from '@nestjs/common';
|
| 3 |
+
import { v4 as uuidv4 } from 'uuid';
|
| 4 |
+
import { SignatureDto } from '@gitroom/nestjs-libraries/dtos/signature/signature.dto';
|
| 5 |
+
|
| 6 |
+
@Injectable()
|
| 7 |
+
export class SignatureRepository {
|
| 8 |
+
constructor(private _signatures: PrismaRepository<'signatures'>) {}
|
| 9 |
+
|
| 10 |
+
getSignaturesByOrgId(orgId: string) {
|
| 11 |
+
return this._signatures.model.signatures.findMany({
|
| 12 |
+
where: { organizationId: orgId, deletedAt: null },
|
| 13 |
+
});
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
getDefaultSignature(orgId: string) {
|
| 17 |
+
return this._signatures.model.signatures.findFirst({
|
| 18 |
+
where: { organizationId: orgId, autoAdd: true, deletedAt: null },
|
| 19 |
+
});
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
async createOrUpdateSignature(
|
| 23 |
+
orgId: string,
|
| 24 |
+
signature: SignatureDto,
|
| 25 |
+
id?: string
|
| 26 |
+
) {
|
| 27 |
+
const values = {
|
| 28 |
+
organizationId: orgId,
|
| 29 |
+
content: signature.content,
|
| 30 |
+
autoAdd: signature.autoAdd,
|
| 31 |
+
};
|
| 32 |
+
|
| 33 |
+
const { id: updatedId } = await this._signatures.model.signatures.upsert({
|
| 34 |
+
where: { id: id || uuidv4(), organizationId: orgId },
|
| 35 |
+
update: values,
|
| 36 |
+
create: values,
|
| 37 |
+
});
|
| 38 |
+
|
| 39 |
+
if (values.autoAdd) {
|
| 40 |
+
await this._signatures.model.signatures.updateMany({
|
| 41 |
+
where: { organizationId: orgId, id: { not: updatedId } },
|
| 42 |
+
data: { autoAdd: false },
|
| 43 |
+
});
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
return { id: updatedId };
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
deleteSignature(orgId: string, id: string) {
|
| 50 |
+
return this._signatures.model.signatures.update({
|
| 51 |
+
where: { id, organizationId: orgId },
|
| 52 |
+
data: { deletedAt: new Date() },
|
| 53 |
+
});
|
| 54 |
+
}
|
| 55 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/signatures/signature.service.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Injectable } from '@nestjs/common';
|
| 2 |
+
import { SignatureRepository } from '@gitroom/nestjs-libraries/database/prisma/signatures/signature.repository';
|
| 3 |
+
import { SignatureDto } from '@gitroom/nestjs-libraries/dtos/signature/signature.dto';
|
| 4 |
+
|
| 5 |
+
@Injectable()
|
| 6 |
+
export class SignatureService {
|
| 7 |
+
constructor(private _signatureRepository: SignatureRepository) {}
|
| 8 |
+
|
| 9 |
+
getSignaturesByOrgId(orgId: string) {
|
| 10 |
+
return this._signatureRepository.getSignaturesByOrgId(orgId);
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
getDefaultSignature(orgId: string) {
|
| 14 |
+
return this._signatureRepository.getDefaultSignature(orgId);
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
createOrUpdateSignature(orgId: string, signature: SignatureDto, id?: string) {
|
| 18 |
+
return this._signatureRepository.createOrUpdateSignature(
|
| 19 |
+
orgId,
|
| 20 |
+
signature,
|
| 21 |
+
id
|
| 22 |
+
);
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
deleteSignature(orgId: string, id: string) {
|
| 26 |
+
return this._signatureRepository.deleteSignature(orgId, id);
|
| 27 |
+
}
|
| 28 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/subscriptions/pricing.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export interface PricingInnerInterface {
|
| 2 |
+
current: string;
|
| 3 |
+
month_price: number;
|
| 4 |
+
year_price: number;
|
| 5 |
+
channel?: number;
|
| 6 |
+
posts_per_month: number;
|
| 7 |
+
team_members: boolean;
|
| 8 |
+
community_features: boolean;
|
| 9 |
+
featured_by_gitroom: boolean;
|
| 10 |
+
ai: boolean;
|
| 11 |
+
import_from_channels: boolean;
|
| 12 |
+
image_generator?: boolean;
|
| 13 |
+
image_generation_count: number;
|
| 14 |
+
generate_videos: number;
|
| 15 |
+
public_api: boolean;
|
| 16 |
+
webhooks: number;
|
| 17 |
+
autoPost: boolean;
|
| 18 |
+
}
|
| 19 |
+
export interface PricingInterface {
|
| 20 |
+
[key: string]: PricingInnerInterface;
|
| 21 |
+
}
|
| 22 |
+
export const pricing: PricingInterface = {
|
| 23 |
+
FREE: {
|
| 24 |
+
current: 'FREE',
|
| 25 |
+
month_price: 0,
|
| 26 |
+
year_price: 0,
|
| 27 |
+
channel: 0,
|
| 28 |
+
image_generation_count: 0,
|
| 29 |
+
posts_per_month: 0,
|
| 30 |
+
team_members: false,
|
| 31 |
+
community_features: false,
|
| 32 |
+
featured_by_gitroom: false,
|
| 33 |
+
ai: false,
|
| 34 |
+
import_from_channels: false,
|
| 35 |
+
image_generator: false,
|
| 36 |
+
public_api: false,
|
| 37 |
+
webhooks: 0,
|
| 38 |
+
autoPost: false,
|
| 39 |
+
generate_videos: 0,
|
| 40 |
+
},
|
| 41 |
+
STANDARD: {
|
| 42 |
+
current: 'STANDARD',
|
| 43 |
+
month_price: 29,
|
| 44 |
+
year_price: 278,
|
| 45 |
+
channel: 5,
|
| 46 |
+
posts_per_month: 400,
|
| 47 |
+
image_generation_count: 20,
|
| 48 |
+
team_members: false,
|
| 49 |
+
ai: true,
|
| 50 |
+
community_features: false,
|
| 51 |
+
featured_by_gitroom: false,
|
| 52 |
+
import_from_channels: true,
|
| 53 |
+
image_generator: false,
|
| 54 |
+
public_api: true,
|
| 55 |
+
webhooks: 2,
|
| 56 |
+
autoPost: false,
|
| 57 |
+
generate_videos: 3,
|
| 58 |
+
},
|
| 59 |
+
TEAM: {
|
| 60 |
+
current: 'TEAM',
|
| 61 |
+
month_price: 39,
|
| 62 |
+
year_price: 374,
|
| 63 |
+
channel: 10,
|
| 64 |
+
posts_per_month: 1000000,
|
| 65 |
+
image_generation_count: 100,
|
| 66 |
+
community_features: true,
|
| 67 |
+
team_members: true,
|
| 68 |
+
featured_by_gitroom: true,
|
| 69 |
+
ai: true,
|
| 70 |
+
import_from_channels: true,
|
| 71 |
+
image_generator: true,
|
| 72 |
+
public_api: true,
|
| 73 |
+
webhooks: 10,
|
| 74 |
+
autoPost: true,
|
| 75 |
+
generate_videos: 10,
|
| 76 |
+
},
|
| 77 |
+
PRO: {
|
| 78 |
+
current: 'PRO',
|
| 79 |
+
month_price: 49,
|
| 80 |
+
year_price: 470,
|
| 81 |
+
channel: 30,
|
| 82 |
+
posts_per_month: 1000000,
|
| 83 |
+
image_generation_count: 300,
|
| 84 |
+
community_features: true,
|
| 85 |
+
team_members: true,
|
| 86 |
+
featured_by_gitroom: true,
|
| 87 |
+
ai: true,
|
| 88 |
+
import_from_channels: true,
|
| 89 |
+
image_generator: true,
|
| 90 |
+
public_api: true,
|
| 91 |
+
webhooks: 30,
|
| 92 |
+
autoPost: true,
|
| 93 |
+
generate_videos: 30,
|
| 94 |
+
},
|
| 95 |
+
ULTIMATE: {
|
| 96 |
+
current: 'ULTIMATE',
|
| 97 |
+
month_price: 99,
|
| 98 |
+
year_price: 950,
|
| 99 |
+
channel: 100,
|
| 100 |
+
posts_per_month: 1000000,
|
| 101 |
+
image_generation_count: 500,
|
| 102 |
+
community_features: true,
|
| 103 |
+
team_members: true,
|
| 104 |
+
featured_by_gitroom: true,
|
| 105 |
+
ai: true,
|
| 106 |
+
import_from_channels: true,
|
| 107 |
+
image_generator: true,
|
| 108 |
+
public_api: true,
|
| 109 |
+
webhooks: 10000,
|
| 110 |
+
autoPost: true,
|
| 111 |
+
generate_videos: 60,
|
| 112 |
+
},
|
| 113 |
+
};
|
libraries/nestjs-libraries/src/database/prisma/subscriptions/subscription.repository.ts
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Injectable } from '@nestjs/common';
|
| 2 |
+
import {
|
| 3 |
+
PrismaRepository,
|
| 4 |
+
PrismaTransaction,
|
| 5 |
+
} from '@gitroom/nestjs-libraries/database/prisma/prisma.service';
|
| 6 |
+
import dayjs from 'dayjs';
|
| 7 |
+
import { Organization } from '@prisma/client';
|
| 8 |
+
|
| 9 |
+
@Injectable()
|
| 10 |
+
export class SubscriptionRepository {
|
| 11 |
+
constructor(
|
| 12 |
+
private readonly _subscription: PrismaRepository<'subscription'>,
|
| 13 |
+
private readonly _organization: PrismaRepository<'organization'>,
|
| 14 |
+
private readonly _user: PrismaRepository<'user'>,
|
| 15 |
+
private readonly _credits: PrismaRepository<'credits'>,
|
| 16 |
+
private _usedCodes: PrismaRepository<'usedCodes'>
|
| 17 |
+
) {}
|
| 18 |
+
|
| 19 |
+
getUserAccount(userId: string) {
|
| 20 |
+
return this._user.model.user.findFirst({
|
| 21 |
+
where: {
|
| 22 |
+
id: userId,
|
| 23 |
+
},
|
| 24 |
+
select: {
|
| 25 |
+
account: true,
|
| 26 |
+
connectedAccount: true,
|
| 27 |
+
},
|
| 28 |
+
});
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
getCode(code: string) {
|
| 32 |
+
return this._usedCodes.model.usedCodes.findFirst({
|
| 33 |
+
where: {
|
| 34 |
+
code,
|
| 35 |
+
},
|
| 36 |
+
});
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
updateAccount(userId: string, account: string) {
|
| 40 |
+
return this._user.model.user.update({
|
| 41 |
+
where: {
|
| 42 |
+
id: userId,
|
| 43 |
+
},
|
| 44 |
+
data: {
|
| 45 |
+
account,
|
| 46 |
+
},
|
| 47 |
+
});
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
getSubscriptionByOrganizationId(organizationId: string) {
|
| 51 |
+
return this._subscription.model.subscription.findFirst({
|
| 52 |
+
where: {
|
| 53 |
+
organizationId,
|
| 54 |
+
deletedAt: null,
|
| 55 |
+
},
|
| 56 |
+
});
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
updateConnectedStatus(account: string, accountCharges: boolean) {
|
| 60 |
+
return this._user.model.user.updateMany({
|
| 61 |
+
where: {
|
| 62 |
+
account,
|
| 63 |
+
},
|
| 64 |
+
data: {
|
| 65 |
+
connectedAccount: accountCharges,
|
| 66 |
+
},
|
| 67 |
+
});
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
getCustomerIdByOrgId(organizationId: string) {
|
| 71 |
+
return this._organization.model.organization.findFirst({
|
| 72 |
+
where: {
|
| 73 |
+
id: organizationId,
|
| 74 |
+
},
|
| 75 |
+
select: {
|
| 76 |
+
paymentId: true,
|
| 77 |
+
},
|
| 78 |
+
});
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
checkSubscription(organizationId: string, subscriptionId: string) {
|
| 82 |
+
return this._subscription.model.subscription.findFirst({
|
| 83 |
+
where: {
|
| 84 |
+
organizationId,
|
| 85 |
+
identifier: subscriptionId,
|
| 86 |
+
deletedAt: null,
|
| 87 |
+
},
|
| 88 |
+
});
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
deleteSubscriptionByCustomerId(customerId: string) {
|
| 92 |
+
return this._subscription.model.subscription.deleteMany({
|
| 93 |
+
where: {
|
| 94 |
+
organization: {
|
| 95 |
+
paymentId: customerId,
|
| 96 |
+
},
|
| 97 |
+
},
|
| 98 |
+
});
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
updateCustomerId(organizationId: string, customerId: string) {
|
| 102 |
+
return this._organization.model.organization.update({
|
| 103 |
+
where: {
|
| 104 |
+
id: organizationId,
|
| 105 |
+
},
|
| 106 |
+
data: {
|
| 107 |
+
paymentId: customerId,
|
| 108 |
+
},
|
| 109 |
+
});
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
async getSubscriptionByOrgId(orgId: string) {
|
| 113 |
+
return this._subscription.model.subscription.findFirst({
|
| 114 |
+
where: {
|
| 115 |
+
organizationId: orgId,
|
| 116 |
+
},
|
| 117 |
+
});
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
async getSubscriptionByCustomerId(customerId: string) {
|
| 121 |
+
return this._subscription.model.subscription.findFirst({
|
| 122 |
+
where: {
|
| 123 |
+
organization: {
|
| 124 |
+
paymentId: customerId,
|
| 125 |
+
},
|
| 126 |
+
},
|
| 127 |
+
});
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
async getOrganizationByCustomerId(customerId: string) {
|
| 131 |
+
return this._organization.model.organization.findFirst({
|
| 132 |
+
where: {
|
| 133 |
+
paymentId: customerId,
|
| 134 |
+
},
|
| 135 |
+
});
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
async createOrUpdateSubscription(
|
| 139 |
+
isTrailing: boolean,
|
| 140 |
+
identifier: string,
|
| 141 |
+
customerId: string,
|
| 142 |
+
totalChannels: number,
|
| 143 |
+
billing: 'STANDARD' | 'TEAM' | 'PRO' | 'ULTIMATE',
|
| 144 |
+
period: 'MONTHLY' | 'YEARLY',
|
| 145 |
+
cancelAt: number | null,
|
| 146 |
+
code?: string,
|
| 147 |
+
org?: { id: string }
|
| 148 |
+
) {
|
| 149 |
+
const findOrg =
|
| 150 |
+
org || (await this.getOrganizationByCustomerId(customerId))!;
|
| 151 |
+
|
| 152 |
+
if (!findOrg) {
|
| 153 |
+
return;
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
await this._subscription.model.subscription.upsert({
|
| 157 |
+
where: {
|
| 158 |
+
organizationId: findOrg.id,
|
| 159 |
+
...(!code
|
| 160 |
+
? {
|
| 161 |
+
organization: {
|
| 162 |
+
paymentId: customerId,
|
| 163 |
+
},
|
| 164 |
+
}
|
| 165 |
+
: {}),
|
| 166 |
+
},
|
| 167 |
+
update: {
|
| 168 |
+
subscriptionTier: billing,
|
| 169 |
+
totalChannels,
|
| 170 |
+
period,
|
| 171 |
+
identifier,
|
| 172 |
+
isLifetime: !!code,
|
| 173 |
+
cancelAt: cancelAt ? new Date(cancelAt * 1000) : null,
|
| 174 |
+
deletedAt: null,
|
| 175 |
+
},
|
| 176 |
+
create: {
|
| 177 |
+
organizationId: findOrg.id,
|
| 178 |
+
subscriptionTier: billing,
|
| 179 |
+
isLifetime: !!code,
|
| 180 |
+
totalChannels,
|
| 181 |
+
period,
|
| 182 |
+
cancelAt: cancelAt ? new Date(cancelAt * 1000) : null,
|
| 183 |
+
identifier,
|
| 184 |
+
deletedAt: null,
|
| 185 |
+
},
|
| 186 |
+
});
|
| 187 |
+
|
| 188 |
+
await this._organization.model.organization.update({
|
| 189 |
+
where: {
|
| 190 |
+
id: findOrg.id,
|
| 191 |
+
},
|
| 192 |
+
data: {
|
| 193 |
+
isTrailing,
|
| 194 |
+
allowTrial: false,
|
| 195 |
+
},
|
| 196 |
+
});
|
| 197 |
+
|
| 198 |
+
if (code) {
|
| 199 |
+
await this._usedCodes.model.usedCodes.create({
|
| 200 |
+
data: {
|
| 201 |
+
code,
|
| 202 |
+
orgId: findOrg.id,
|
| 203 |
+
},
|
| 204 |
+
});
|
| 205 |
+
}
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
getSubscriptionByIdentifier(identifier: string) {
|
| 209 |
+
return this._subscription.model.subscription.findFirst({
|
| 210 |
+
where: {
|
| 211 |
+
identifier,
|
| 212 |
+
deletedAt: null,
|
| 213 |
+
},
|
| 214 |
+
include: {
|
| 215 |
+
organization: true,
|
| 216 |
+
},
|
| 217 |
+
});
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
getSubscription(organizationId: string) {
|
| 221 |
+
return this._subscription.model.subscription.findFirst({
|
| 222 |
+
where: {
|
| 223 |
+
organizationId,
|
| 224 |
+
deletedAt: null,
|
| 225 |
+
},
|
| 226 |
+
});
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
async getCreditsFrom(
|
| 230 |
+
organizationId: string,
|
| 231 |
+
from: dayjs.Dayjs,
|
| 232 |
+
type = 'ai_images'
|
| 233 |
+
) {
|
| 234 |
+
const load = await this._credits.model.credits.groupBy({
|
| 235 |
+
by: ['organizationId'],
|
| 236 |
+
where: {
|
| 237 |
+
organizationId,
|
| 238 |
+
type,
|
| 239 |
+
createdAt: {
|
| 240 |
+
gte: from.toDate(),
|
| 241 |
+
},
|
| 242 |
+
},
|
| 243 |
+
_sum: {
|
| 244 |
+
credits: true,
|
| 245 |
+
},
|
| 246 |
+
});
|
| 247 |
+
|
| 248 |
+
return load?.[0]?._sum?.credits || 0;
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
async useCredit<T>(
|
| 252 |
+
org: Organization,
|
| 253 |
+
type = 'ai_images',
|
| 254 |
+
func: () => Promise<T>
|
| 255 |
+
) {
|
| 256 |
+
const data = await this._credits.model.credits.create({
|
| 257 |
+
data: {
|
| 258 |
+
organizationId: org.id,
|
| 259 |
+
credits: 1,
|
| 260 |
+
type,
|
| 261 |
+
},
|
| 262 |
+
});
|
| 263 |
+
|
| 264 |
+
try {
|
| 265 |
+
return await func();
|
| 266 |
+
} catch (err) {
|
| 267 |
+
await this._credits.model.credits.delete({
|
| 268 |
+
where: {
|
| 269 |
+
id: data.id,
|
| 270 |
+
},
|
| 271 |
+
});
|
| 272 |
+
throw err;
|
| 273 |
+
}
|
| 274 |
+
}
|
| 275 |
+
|
| 276 |
+
setCustomerId(orgId: string, customerId: string) {
|
| 277 |
+
return this._organization.model.organization.update({
|
| 278 |
+
where: {
|
| 279 |
+
id: orgId,
|
| 280 |
+
},
|
| 281 |
+
data: {
|
| 282 |
+
paymentId: customerId,
|
| 283 |
+
},
|
| 284 |
+
});
|
| 285 |
+
}
|
| 286 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/subscriptions/subscription.service.ts
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Injectable } from '@nestjs/common';
|
| 2 |
+
import { pricing } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/pricing';
|
| 3 |
+
import { SubscriptionRepository } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.repository';
|
| 4 |
+
import { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';
|
| 5 |
+
import { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service';
|
| 6 |
+
import { Organization } from '@prisma/client';
|
| 7 |
+
import dayjs from 'dayjs';
|
| 8 |
+
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
|
| 9 |
+
|
| 10 |
+
@Injectable()
|
| 11 |
+
export class SubscriptionService {
|
| 12 |
+
constructor(
|
| 13 |
+
private readonly _subscriptionRepository: SubscriptionRepository,
|
| 14 |
+
private readonly _integrationService: IntegrationService,
|
| 15 |
+
private readonly _organizationService: OrganizationService
|
| 16 |
+
) {}
|
| 17 |
+
|
| 18 |
+
getSubscriptionByOrganizationId(organizationId: string) {
|
| 19 |
+
return this._subscriptionRepository.getSubscriptionByOrganizationId(
|
| 20 |
+
organizationId
|
| 21 |
+
);
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
useCredit<T>(
|
| 25 |
+
organization: Organization,
|
| 26 |
+
type = 'ai_images',
|
| 27 |
+
func: () => Promise<T>
|
| 28 |
+
): Promise<T> {
|
| 29 |
+
return this._subscriptionRepository.useCredit(organization, type, func);
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
getCode(code: string) {
|
| 33 |
+
return this._subscriptionRepository.getCode(code);
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
async deleteSubscription(customerId: string) {
|
| 37 |
+
await this.modifySubscription(
|
| 38 |
+
customerId,
|
| 39 |
+
pricing.FREE.channel || 0,
|
| 40 |
+
'FREE'
|
| 41 |
+
);
|
| 42 |
+
return this._subscriptionRepository.deleteSubscriptionByCustomerId(
|
| 43 |
+
customerId
|
| 44 |
+
);
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
updateCustomerId(organizationId: string, customerId: string) {
|
| 48 |
+
return this._subscriptionRepository.updateCustomerId(
|
| 49 |
+
organizationId,
|
| 50 |
+
customerId
|
| 51 |
+
);
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
async checkSubscription(organizationId: string, subscriptionId: string) {
|
| 55 |
+
return await this._subscriptionRepository.checkSubscription(
|
| 56 |
+
organizationId,
|
| 57 |
+
subscriptionId
|
| 58 |
+
);
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
async modifySubscriptionByOrg(
|
| 62 |
+
organizationId: string,
|
| 63 |
+
totalChannels: number,
|
| 64 |
+
billing: 'FREE' | 'STANDARD' | 'TEAM' | 'PRO' | 'ULTIMATE'
|
| 65 |
+
) {
|
| 66 |
+
if (!organizationId) {
|
| 67 |
+
return false;
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
const getCurrentSubscription =
|
| 71 |
+
(await this._subscriptionRepository.getSubscriptionByOrgId(
|
| 72 |
+
organizationId
|
| 73 |
+
))!;
|
| 74 |
+
|
| 75 |
+
const from = pricing[getCurrentSubscription?.subscriptionTier || 'FREE'];
|
| 76 |
+
const to = pricing[billing];
|
| 77 |
+
|
| 78 |
+
const currentTotalChannels = (
|
| 79 |
+
await this._integrationService.getIntegrationsList(organizationId)
|
| 80 |
+
).filter((f) => !f.disabled);
|
| 81 |
+
|
| 82 |
+
if (currentTotalChannels.length > totalChannels) {
|
| 83 |
+
await this._integrationService.disableIntegrations(
|
| 84 |
+
organizationId,
|
| 85 |
+
currentTotalChannels.length - totalChannels
|
| 86 |
+
);
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
if (from.team_members && !to.team_members) {
|
| 90 |
+
await this._organizationService.disableOrEnableNonSuperAdminUsers(
|
| 91 |
+
organizationId,
|
| 92 |
+
true
|
| 93 |
+
);
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
if (!from.team_members && to.team_members) {
|
| 97 |
+
await this._organizationService.disableOrEnableNonSuperAdminUsers(
|
| 98 |
+
organizationId,
|
| 99 |
+
false
|
| 100 |
+
);
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
if (billing === 'FREE') {
|
| 104 |
+
await this._integrationService.changeActiveCron(organizationId);
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
return true;
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
async modifySubscription(
|
| 111 |
+
customerId: string,
|
| 112 |
+
totalChannels: number,
|
| 113 |
+
billing: 'FREE' | 'STANDARD' | 'TEAM' | 'PRO' | 'ULTIMATE'
|
| 114 |
+
) {
|
| 115 |
+
if (!customerId) {
|
| 116 |
+
return false;
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
const getOrgByCustomerId =
|
| 120 |
+
await this._subscriptionRepository.getOrganizationByCustomerId(
|
| 121 |
+
customerId
|
| 122 |
+
);
|
| 123 |
+
|
| 124 |
+
const getCurrentSubscription =
|
| 125 |
+
(await this._subscriptionRepository.getSubscriptionByCustomerId(
|
| 126 |
+
customerId
|
| 127 |
+
))!;
|
| 128 |
+
|
| 129 |
+
if (
|
| 130 |
+
!getOrgByCustomerId ||
|
| 131 |
+
(getCurrentSubscription && getCurrentSubscription?.isLifetime)
|
| 132 |
+
) {
|
| 133 |
+
return false;
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
const from = pricing[getCurrentSubscription?.subscriptionTier || 'FREE'];
|
| 137 |
+
const to = pricing[billing];
|
| 138 |
+
|
| 139 |
+
const currentTotalChannels = (
|
| 140 |
+
await this._integrationService.getIntegrationsList(
|
| 141 |
+
getOrgByCustomerId?.id!
|
| 142 |
+
)
|
| 143 |
+
).filter((f) => !f.disabled);
|
| 144 |
+
|
| 145 |
+
if (currentTotalChannels.length > totalChannels) {
|
| 146 |
+
await this._integrationService.disableIntegrations(
|
| 147 |
+
getOrgByCustomerId?.id!,
|
| 148 |
+
currentTotalChannels.length - totalChannels
|
| 149 |
+
);
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
if (from.team_members && !to.team_members) {
|
| 153 |
+
await this._organizationService.disableOrEnableNonSuperAdminUsers(
|
| 154 |
+
getOrgByCustomerId?.id!,
|
| 155 |
+
true
|
| 156 |
+
);
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
if (!from.team_members && to.team_members) {
|
| 160 |
+
await this._organizationService.disableOrEnableNonSuperAdminUsers(
|
| 161 |
+
getOrgByCustomerId?.id!,
|
| 162 |
+
false
|
| 163 |
+
);
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
if (billing === 'FREE') {
|
| 167 |
+
await this._integrationService.changeActiveCron(getOrgByCustomerId?.id!);
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
return true;
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
async createOrUpdateSubscription(
|
| 174 |
+
isTrailing: boolean,
|
| 175 |
+
identifier: string,
|
| 176 |
+
customerId: string,
|
| 177 |
+
totalChannels: number,
|
| 178 |
+
billing: 'STANDARD' | 'TEAM' | 'PRO' | 'ULTIMATE',
|
| 179 |
+
period: 'MONTHLY' | 'YEARLY',
|
| 180 |
+
cancelAt: number | null,
|
| 181 |
+
code?: string,
|
| 182 |
+
org?: string
|
| 183 |
+
) {
|
| 184 |
+
if (!code) {
|
| 185 |
+
try {
|
| 186 |
+
const load = await this.modifySubscription(
|
| 187 |
+
customerId,
|
| 188 |
+
totalChannels,
|
| 189 |
+
billing
|
| 190 |
+
);
|
| 191 |
+
if (!load) {
|
| 192 |
+
return {};
|
| 193 |
+
}
|
| 194 |
+
} catch (e) {
|
| 195 |
+
return {};
|
| 196 |
+
}
|
| 197 |
+
}
|
| 198 |
+
return this._subscriptionRepository.createOrUpdateSubscription(
|
| 199 |
+
isTrailing,
|
| 200 |
+
identifier,
|
| 201 |
+
customerId,
|
| 202 |
+
totalChannels,
|
| 203 |
+
billing,
|
| 204 |
+
period,
|
| 205 |
+
cancelAt,
|
| 206 |
+
code,
|
| 207 |
+
org ? { id: org } : undefined
|
| 208 |
+
);
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
getSubscriptionByIdentifier(identifier: string) {
|
| 212 |
+
return this._subscriptionRepository.getSubscriptionByIdentifier(identifier);
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
async getSubscription(organizationId: string) {
|
| 216 |
+
return this._subscriptionRepository.getSubscription(organizationId);
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
async checkCredits(organization: Organization, checkType = 'ai_images') {
|
| 220 |
+
// @ts-ignore
|
| 221 |
+
const type = organization?.subscription?.subscriptionTier || 'FREE';
|
| 222 |
+
|
| 223 |
+
if (type === 'FREE') {
|
| 224 |
+
return { credits: 0 };
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
// @ts-ignore
|
| 228 |
+
let date = dayjs(organization.subscription.createdAt);
|
| 229 |
+
while (date.isBefore(dayjs())) {
|
| 230 |
+
date = date.add(1, 'month');
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
const checkFromMonth = date.subtract(1, 'month');
|
| 234 |
+
const imageGenerationCount =
|
| 235 |
+
checkType === 'ai_images'
|
| 236 |
+
? pricing[type].image_generation_count
|
| 237 |
+
: pricing[type].generate_videos;
|
| 238 |
+
|
| 239 |
+
const totalUse = await this._subscriptionRepository.getCreditsFrom(
|
| 240 |
+
organization.id,
|
| 241 |
+
checkFromMonth,
|
| 242 |
+
checkType
|
| 243 |
+
);
|
| 244 |
+
|
| 245 |
+
return {
|
| 246 |
+
credits: imageGenerationCount - totalUse,
|
| 247 |
+
};
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
async addSubscription(orgId: string, userId: string, subscription: any) {
|
| 251 |
+
await this._subscriptionRepository.setCustomerId(orgId, userId);
|
| 252 |
+
return this.createOrUpdateSubscription(
|
| 253 |
+
false,
|
| 254 |
+
makeId(5),
|
| 255 |
+
userId,
|
| 256 |
+
pricing[subscription].channel!,
|
| 257 |
+
subscription,
|
| 258 |
+
'MONTHLY',
|
| 259 |
+
null,
|
| 260 |
+
undefined,
|
| 261 |
+
orgId
|
| 262 |
+
);
|
| 263 |
+
}
|
| 264 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/third-party/third-party.repository.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';
|
| 2 |
+
import { Injectable } from '@nestjs/common';
|
| 3 |
+
import { AuthService } from '@gitroom/helpers/auth/auth.service';
|
| 4 |
+
|
| 5 |
+
@Injectable()
|
| 6 |
+
export class ThirdPartyRepository {
|
| 7 |
+
constructor(private _thirdParty: PrismaRepository<'thirdParty'>) {}
|
| 8 |
+
|
| 9 |
+
getAllThirdPartiesByOrganization(org: string) {
|
| 10 |
+
return this._thirdParty.model.thirdParty.findMany({
|
| 11 |
+
where: { organizationId: org, deletedAt: null },
|
| 12 |
+
select: {
|
| 13 |
+
id: true,
|
| 14 |
+
name: true,
|
| 15 |
+
identifier: true,
|
| 16 |
+
},
|
| 17 |
+
});
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
deleteIntegration(org: string, id: string) {
|
| 21 |
+
return this._thirdParty.model.thirdParty.update({
|
| 22 |
+
where: { id, organizationId: org },
|
| 23 |
+
data: { deletedAt: new Date() },
|
| 24 |
+
});
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
getIntegrationById(org: string, id: string) {
|
| 28 |
+
return this._thirdParty.model.thirdParty.findFirst({
|
| 29 |
+
where: { id, organizationId: org, deletedAt: null },
|
| 30 |
+
});
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
saveIntegration(
|
| 34 |
+
org: string,
|
| 35 |
+
identifier: string,
|
| 36 |
+
apiKey: string,
|
| 37 |
+
data: { name: string; username: string; id: string }
|
| 38 |
+
) {
|
| 39 |
+
return this._thirdParty.model.thirdParty.upsert({
|
| 40 |
+
where: {
|
| 41 |
+
organizationId_internalId: {
|
| 42 |
+
internalId: data.id,
|
| 43 |
+
organizationId: org,
|
| 44 |
+
},
|
| 45 |
+
},
|
| 46 |
+
create: {
|
| 47 |
+
organizationId: org,
|
| 48 |
+
name: data.name,
|
| 49 |
+
internalId: data.id,
|
| 50 |
+
identifier,
|
| 51 |
+
apiKey: AuthService.fixedEncryption(apiKey),
|
| 52 |
+
deletedAt: null,
|
| 53 |
+
},
|
| 54 |
+
update: {
|
| 55 |
+
organizationId: org,
|
| 56 |
+
name: data.name,
|
| 57 |
+
internalId: data.id,
|
| 58 |
+
identifier,
|
| 59 |
+
apiKey: AuthService.fixedEncryption(apiKey),
|
| 60 |
+
deletedAt: null,
|
| 61 |
+
},
|
| 62 |
+
});
|
| 63 |
+
}
|
| 64 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/third-party/third-party.service.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Injectable } from '@nestjs/common';
|
| 2 |
+
import { ThirdPartyRepository } from '@gitroom/nestjs-libraries/database/prisma/third-party/third-party.repository';
|
| 3 |
+
|
| 4 |
+
@Injectable()
|
| 5 |
+
export class ThirdPartyService {
|
| 6 |
+
constructor(private _thirdPartyRepository: ThirdPartyRepository) {}
|
| 7 |
+
|
| 8 |
+
getAllThirdPartiesByOrganization(org: string) {
|
| 9 |
+
return this._thirdPartyRepository.getAllThirdPartiesByOrganization(org);
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
deleteIntegration(org: string, id: string) {
|
| 13 |
+
return this._thirdPartyRepository.deleteIntegration(org, id);
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
getIntegrationById(org: string, id: string) {
|
| 17 |
+
return this._thirdPartyRepository.getIntegrationById(org, id);
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
saveIntegration(
|
| 21 |
+
org: string,
|
| 22 |
+
identifier: string,
|
| 23 |
+
apiKey: string,
|
| 24 |
+
data: { name: string; username: string; id: string }
|
| 25 |
+
) {
|
| 26 |
+
return this._thirdPartyRepository.saveIntegration(org, identifier, apiKey, data);
|
| 27 |
+
}
|
| 28 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/users/users.repository.ts
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';
|
| 2 |
+
import { Injectable } from '@nestjs/common';
|
| 3 |
+
import { Provider } from '@prisma/client';
|
| 4 |
+
import { AuthService } from '@gitroom/helpers/auth/auth.service';
|
| 5 |
+
import { UserDetailDto } from '@gitroom/nestjs-libraries/dtos/users/user.details.dto';
|
| 6 |
+
import { EmailNotificationsDto } from '@gitroom/nestjs-libraries/dtos/users/email-notifications.dto';
|
| 7 |
+
|
| 8 |
+
@Injectable()
|
| 9 |
+
export class UsersRepository {
|
| 10 |
+
constructor(private _user: PrismaRepository<'user'>) {}
|
| 11 |
+
|
| 12 |
+
getImpersonateUser(name: string) {
|
| 13 |
+
return this._user.model.user.findMany({
|
| 14 |
+
where: {
|
| 15 |
+
OR: [
|
| 16 |
+
{
|
| 17 |
+
name: {
|
| 18 |
+
contains: name,
|
| 19 |
+
},
|
| 20 |
+
},
|
| 21 |
+
{
|
| 22 |
+
email: {
|
| 23 |
+
contains: name,
|
| 24 |
+
},
|
| 25 |
+
},
|
| 26 |
+
{
|
| 27 |
+
id: {
|
| 28 |
+
contains: name,
|
| 29 |
+
},
|
| 30 |
+
},
|
| 31 |
+
],
|
| 32 |
+
},
|
| 33 |
+
select: {
|
| 34 |
+
id: true,
|
| 35 |
+
name: true,
|
| 36 |
+
email: true,
|
| 37 |
+
},
|
| 38 |
+
take: 10,
|
| 39 |
+
});
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
getUserById(id: string) {
|
| 43 |
+
return this._user.model.user.findFirst({
|
| 44 |
+
where: {
|
| 45 |
+
id,
|
| 46 |
+
},
|
| 47 |
+
});
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
getUserByEmail(email: string) {
|
| 51 |
+
return this._user.model.user.findFirst({
|
| 52 |
+
where: {
|
| 53 |
+
email,
|
| 54 |
+
providerName: Provider.LOCAL,
|
| 55 |
+
},
|
| 56 |
+
include: {
|
| 57 |
+
picture: {
|
| 58 |
+
select: {
|
| 59 |
+
id: true,
|
| 60 |
+
path: true,
|
| 61 |
+
},
|
| 62 |
+
},
|
| 63 |
+
},
|
| 64 |
+
});
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
activateUser(id: string) {
|
| 68 |
+
return this._user.model.user.update({
|
| 69 |
+
where: {
|
| 70 |
+
id,
|
| 71 |
+
},
|
| 72 |
+
data: {
|
| 73 |
+
activated: true,
|
| 74 |
+
},
|
| 75 |
+
});
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
getUserByProvider(providerId: string, provider: Provider) {
|
| 79 |
+
return this._user.model.user.findFirst({
|
| 80 |
+
where: {
|
| 81 |
+
providerId,
|
| 82 |
+
providerName: provider,
|
| 83 |
+
},
|
| 84 |
+
});
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
updatePassword(id: string, password: string) {
|
| 88 |
+
return this._user.model.user.update({
|
| 89 |
+
where: {
|
| 90 |
+
id,
|
| 91 |
+
providerName: Provider.LOCAL,
|
| 92 |
+
},
|
| 93 |
+
data: {
|
| 94 |
+
password: AuthService.hashPassword(password),
|
| 95 |
+
},
|
| 96 |
+
});
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
changeAudienceSize(userId: string, audience: number) {
|
| 100 |
+
return this._user.model.user.update({
|
| 101 |
+
where: {
|
| 102 |
+
id: userId,
|
| 103 |
+
},
|
| 104 |
+
data: {
|
| 105 |
+
audience,
|
| 106 |
+
},
|
| 107 |
+
});
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
async getPersonal(userId: string) {
|
| 111 |
+
const user = await this._user.model.user.findUnique({
|
| 112 |
+
where: {
|
| 113 |
+
id: userId,
|
| 114 |
+
},
|
| 115 |
+
select: {
|
| 116 |
+
id: true,
|
| 117 |
+
name: true,
|
| 118 |
+
bio: true,
|
| 119 |
+
picture: {
|
| 120 |
+
select: {
|
| 121 |
+
id: true,
|
| 122 |
+
path: true,
|
| 123 |
+
},
|
| 124 |
+
},
|
| 125 |
+
},
|
| 126 |
+
});
|
| 127 |
+
|
| 128 |
+
return user;
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
async changePersonal(userId: string, body: UserDetailDto) {
|
| 132 |
+
await this._user.model.user.update({
|
| 133 |
+
where: {
|
| 134 |
+
id: userId,
|
| 135 |
+
},
|
| 136 |
+
data: {
|
| 137 |
+
name: body.fullname,
|
| 138 |
+
bio: body.bio,
|
| 139 |
+
picture: body.picture
|
| 140 |
+
? {
|
| 141 |
+
connect: {
|
| 142 |
+
id: body.picture.id,
|
| 143 |
+
},
|
| 144 |
+
}
|
| 145 |
+
: {
|
| 146 |
+
disconnect: true,
|
| 147 |
+
},
|
| 148 |
+
},
|
| 149 |
+
});
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
async getEmailNotifications(userId: string) {
|
| 153 |
+
return this._user.model.user.findUnique({
|
| 154 |
+
where: {
|
| 155 |
+
id: userId,
|
| 156 |
+
},
|
| 157 |
+
select: {
|
| 158 |
+
sendSuccessEmails: true,
|
| 159 |
+
sendFailureEmails: true,
|
| 160 |
+
sendStreakEmails: true,
|
| 161 |
+
},
|
| 162 |
+
});
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
async updateEmailNotifications(userId: string, body: EmailNotificationsDto) {
|
| 166 |
+
await this._user.model.user.update({
|
| 167 |
+
where: {
|
| 168 |
+
id: userId,
|
| 169 |
+
},
|
| 170 |
+
data: {
|
| 171 |
+
sendSuccessEmails: body.sendSuccessEmails,
|
| 172 |
+
sendFailureEmails: body.sendFailureEmails,
|
| 173 |
+
sendStreakEmails: body.sendStreakEmails,
|
| 174 |
+
},
|
| 175 |
+
});
|
| 176 |
+
}
|
| 177 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/users/users.service.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Injectable } from '@nestjs/common';
|
| 2 |
+
import { UsersRepository } from '@gitroom/nestjs-libraries/database/prisma/users/users.repository';
|
| 3 |
+
import { Provider } from '@prisma/client';
|
| 4 |
+
import { UserDetailDto } from '@gitroom/nestjs-libraries/dtos/users/user.details.dto';
|
| 5 |
+
import { EmailNotificationsDto } from '@gitroom/nestjs-libraries/dtos/users/email-notifications.dto';
|
| 6 |
+
import { OrganizationRepository } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.repository';
|
| 7 |
+
|
| 8 |
+
@Injectable()
|
| 9 |
+
export class UsersService {
|
| 10 |
+
constructor(
|
| 11 |
+
private _usersRepository: UsersRepository,
|
| 12 |
+
private _organizationRepository: OrganizationRepository
|
| 13 |
+
) {}
|
| 14 |
+
|
| 15 |
+
getUserByEmail(email: string) {
|
| 16 |
+
return this._usersRepository.getUserByEmail(email);
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
getUserById(id: string) {
|
| 20 |
+
return this._usersRepository.getUserById(id);
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
getImpersonateUser(name: string) {
|
| 24 |
+
return this._organizationRepository.getImpersonateUser(name);
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
getUserByProvider(providerId: string, provider: Provider) {
|
| 28 |
+
return this._usersRepository.getUserByProvider(providerId, provider);
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
activateUser(id: string) {
|
| 32 |
+
return this._usersRepository.activateUser(id);
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
updatePassword(id: string, password: string) {
|
| 36 |
+
return this._usersRepository.updatePassword(id, password);
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
getPersonal(userId: string) {
|
| 40 |
+
return this._usersRepository.getPersonal(userId);
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
changePersonal(userId: string, body: UserDetailDto) {
|
| 44 |
+
return this._usersRepository.changePersonal(userId, body);
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
getEmailNotifications(userId: string) {
|
| 48 |
+
return this._usersRepository.getEmailNotifications(userId);
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
updateEmailNotifications(userId: string, body: EmailNotificationsDto) {
|
| 52 |
+
return this._usersRepository.updateEmailNotifications(userId, body);
|
| 53 |
+
}
|
| 54 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/webhooks/webhooks.repository.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';
|
| 2 |
+
import { Injectable } from '@nestjs/common';
|
| 3 |
+
import { WebhooksDto } from '@gitroom/nestjs-libraries/dtos/webhooks/webhooks.dto';
|
| 4 |
+
import { v4 as uuidv4 } from 'uuid';
|
| 5 |
+
|
| 6 |
+
@Injectable()
|
| 7 |
+
export class WebhooksRepository {
|
| 8 |
+
constructor(private _webhooks: PrismaRepository<'webhooks'>) {}
|
| 9 |
+
|
| 10 |
+
getTotal(orgId: string) {
|
| 11 |
+
return this._webhooks.model.webhooks.count({
|
| 12 |
+
where: {
|
| 13 |
+
organizationId: orgId,
|
| 14 |
+
deletedAt: null,
|
| 15 |
+
},
|
| 16 |
+
});
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
getWebhooks(orgId: string) {
|
| 20 |
+
return this._webhooks.model.webhooks.findMany({
|
| 21 |
+
where: {
|
| 22 |
+
organizationId: orgId,
|
| 23 |
+
deletedAt: null,
|
| 24 |
+
},
|
| 25 |
+
include: {
|
| 26 |
+
integrations: {
|
| 27 |
+
select: {
|
| 28 |
+
integration: {
|
| 29 |
+
select: {
|
| 30 |
+
id: true,
|
| 31 |
+
picture: true,
|
| 32 |
+
name: true,
|
| 33 |
+
},
|
| 34 |
+
},
|
| 35 |
+
},
|
| 36 |
+
},
|
| 37 |
+
},
|
| 38 |
+
});
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
deleteWebhook(orgId: string, id: string) {
|
| 42 |
+
return this._webhooks.model.webhooks.update({
|
| 43 |
+
where: {
|
| 44 |
+
id,
|
| 45 |
+
organizationId: orgId,
|
| 46 |
+
},
|
| 47 |
+
data: {
|
| 48 |
+
deletedAt: new Date(),
|
| 49 |
+
},
|
| 50 |
+
});
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
async createWebhook(orgId: string, body: WebhooksDto) {
|
| 54 |
+
const { id } = await this._webhooks.model.webhooks.upsert({
|
| 55 |
+
where: {
|
| 56 |
+
id: body.id || uuidv4(),
|
| 57 |
+
organizationId: orgId,
|
| 58 |
+
},
|
| 59 |
+
create: {
|
| 60 |
+
organizationId: orgId,
|
| 61 |
+
url: body.url,
|
| 62 |
+
name: body.name,
|
| 63 |
+
},
|
| 64 |
+
update: {
|
| 65 |
+
url: body.url,
|
| 66 |
+
name: body.name,
|
| 67 |
+
},
|
| 68 |
+
});
|
| 69 |
+
|
| 70 |
+
await this._webhooks.model.webhooks.update({
|
| 71 |
+
where: {
|
| 72 |
+
id,
|
| 73 |
+
organizationId: orgId,
|
| 74 |
+
},
|
| 75 |
+
data: {
|
| 76 |
+
integrations: {
|
| 77 |
+
deleteMany: {},
|
| 78 |
+
create: body.integrations.map((integration) => ({
|
| 79 |
+
integrationId: integration.id,
|
| 80 |
+
})),
|
| 81 |
+
},
|
| 82 |
+
},
|
| 83 |
+
});
|
| 84 |
+
|
| 85 |
+
return { id };
|
| 86 |
+
}
|
| 87 |
+
}
|
libraries/nestjs-libraries/src/database/prisma/webhooks/webhooks.service.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Injectable } from '@nestjs/common';
|
| 2 |
+
import { WebhooksRepository } from '@gitroom/nestjs-libraries/database/prisma/webhooks/webhooks.repository';
|
| 3 |
+
import { WebhooksDto } from '@gitroom/nestjs-libraries/dtos/webhooks/webhooks.dto';
|
| 4 |
+
|
| 5 |
+
@Injectable()
|
| 6 |
+
export class WebhooksService {
|
| 7 |
+
constructor(private _webhooksRepository: WebhooksRepository) {}
|
| 8 |
+
|
| 9 |
+
getTotal(orgId: string) {
|
| 10 |
+
return this._webhooksRepository.getTotal(orgId);
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
getWebhooks(orgId: string) {
|
| 14 |
+
return this._webhooksRepository.getWebhooks(orgId);
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
createWebhook(orgId: string, body: WebhooksDto) {
|
| 18 |
+
return this._webhooksRepository.createWebhook(orgId, body);
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
deleteWebhook(orgId: string, id: string) {
|
| 22 |
+
return this._webhooksRepository.deleteWebhook(orgId, id);
|
| 23 |
+
}
|
| 24 |
+
}
|
libraries/nestjs-libraries/src/dtos/agencies/create.agency.dto.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import {
|
| 2 |
+
ArrayMaxSize,
|
| 3 |
+
ArrayMinSize,
|
| 4 |
+
IsDefined,
|
| 5 |
+
IsIn,
|
| 6 |
+
IsOptional,
|
| 7 |
+
IsString,
|
| 8 |
+
IsUrl,
|
| 9 |
+
MinLength,
|
| 10 |
+
ValidateIf,
|
| 11 |
+
} from 'class-validator';
|
| 12 |
+
import { Type } from 'class-transformer';
|
| 13 |
+
|
| 14 |
+
export class CreateAgencyLogoDto {
|
| 15 |
+
@IsString()
|
| 16 |
+
@IsDefined()
|
| 17 |
+
id: string;
|
| 18 |
+
|
| 19 |
+
path: string;
|
| 20 |
+
}
|
| 21 |
+
export class CreateAgencyDto {
|
| 22 |
+
@IsString()
|
| 23 |
+
@MinLength(3)
|
| 24 |
+
name: string;
|
| 25 |
+
|
| 26 |
+
@IsUrl()
|
| 27 |
+
@IsDefined()
|
| 28 |
+
website: string;
|
| 29 |
+
|
| 30 |
+
@IsUrl()
|
| 31 |
+
@ValidateIf((o) => o.facebook)
|
| 32 |
+
facebook: string;
|
| 33 |
+
|
| 34 |
+
@IsString()
|
| 35 |
+
@IsOptional()
|
| 36 |
+
instagram: string;
|
| 37 |
+
|
| 38 |
+
@IsString()
|
| 39 |
+
@IsOptional()
|
| 40 |
+
twitter: string;
|
| 41 |
+
|
| 42 |
+
@IsUrl()
|
| 43 |
+
@ValidateIf((o) => o.linkedIn)
|
| 44 |
+
linkedIn: string;
|
| 45 |
+
|
| 46 |
+
@IsUrl()
|
| 47 |
+
@ValidateIf((o) => o.youtube)
|
| 48 |
+
youtube: string;
|
| 49 |
+
|
| 50 |
+
@IsString()
|
| 51 |
+
@IsOptional()
|
| 52 |
+
tiktok: string;
|
| 53 |
+
|
| 54 |
+
@Type(() => CreateAgencyLogoDto)
|
| 55 |
+
logo: CreateAgencyLogoDto;
|
| 56 |
+
|
| 57 |
+
@IsString()
|
| 58 |
+
shortDescription: string;
|
| 59 |
+
|
| 60 |
+
@IsString()
|
| 61 |
+
description: string;
|
| 62 |
+
|
| 63 |
+
@IsString({
|
| 64 |
+
each: true,
|
| 65 |
+
})
|
| 66 |
+
@ArrayMinSize(1)
|
| 67 |
+
@ArrayMaxSize(3)
|
| 68 |
+
@IsIn(
|
| 69 |
+
[
|
| 70 |
+
'Real Estate',
|
| 71 |
+
'Fashion',
|
| 72 |
+
'Health and Fitness',
|
| 73 |
+
'Beauty',
|
| 74 |
+
'Travel',
|
| 75 |
+
'Food',
|
| 76 |
+
'Tech',
|
| 77 |
+
'Gaming',
|
| 78 |
+
'Parenting',
|
| 79 |
+
'Education',
|
| 80 |
+
'Business',
|
| 81 |
+
'Finance',
|
| 82 |
+
'DIY',
|
| 83 |
+
'Pets',
|
| 84 |
+
'Lifestyle',
|
| 85 |
+
'Sports',
|
| 86 |
+
'Entertainment',
|
| 87 |
+
'Art',
|
| 88 |
+
'Photography',
|
| 89 |
+
'Sustainability',
|
| 90 |
+
],
|
| 91 |
+
{
|
| 92 |
+
each: true,
|
| 93 |
+
}
|
| 94 |
+
)
|
| 95 |
+
niches: string[];
|
| 96 |
+
}
|
libraries/nestjs-libraries/src/dtos/analytics/stars.list.dto.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { IsDefined, IsIn, IsNumber, IsOptional } from 'class-validator';
|
| 2 |
+
|
| 3 |
+
export class StarsListDto {
|
| 4 |
+
@IsNumber()
|
| 5 |
+
@IsDefined()
|
| 6 |
+
page: number;
|
| 7 |
+
|
| 8 |
+
@IsOptional()
|
| 9 |
+
@IsIn(['login', 'totalStars', 'stars', 'date', 'forks', 'totalForks'])
|
| 10 |
+
key: 'login' | 'date' | 'stars' | 'totalStars';
|
| 11 |
+
|
| 12 |
+
@IsOptional()
|
| 13 |
+
@IsIn(['desc', 'asc'])
|
| 14 |
+
state: 'desc' | 'asc';
|
| 15 |
+
}
|
libraries/nestjs-libraries/src/dtos/announcements/announcements.dto.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { IsDefined, IsIn, IsOptional, IsString } from 'class-validator';
|
| 2 |
+
|
| 3 |
+
export class AnnouncementDto {
|
| 4 |
+
@IsString()
|
| 5 |
+
@IsDefined()
|
| 6 |
+
title: string;
|
| 7 |
+
|
| 8 |
+
@IsString()
|
| 9 |
+
@IsDefined()
|
| 10 |
+
description: string;
|
| 11 |
+
|
| 12 |
+
@IsOptional()
|
| 13 |
+
@IsString()
|
| 14 |
+
@IsIn(['INFO', 'WARNING', 'ERROR'])
|
| 15 |
+
color?: string;
|
| 16 |
+
}
|
libraries/nestjs-libraries/src/dtos/auth/create.org.user.dto.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import {
|
| 2 |
+
IsDefined,
|
| 3 |
+
IsEmail,
|
| 4 |
+
IsString,
|
| 5 |
+
MaxLength,
|
| 6 |
+
MinLength,
|
| 7 |
+
ValidateIf,
|
| 8 |
+
} from 'class-validator';
|
| 9 |
+
import { Provider } from '@prisma/client';
|
| 10 |
+
|
| 11 |
+
export class CreateOrgUserDto {
|
| 12 |
+
@IsString()
|
| 13 |
+
@MinLength(3)
|
| 14 |
+
@MaxLength(64)
|
| 15 |
+
@IsDefined()
|
| 16 |
+
@ValidateIf((o) => !o.providerToken)
|
| 17 |
+
password: string;
|
| 18 |
+
|
| 19 |
+
@IsString()
|
| 20 |
+
@IsDefined()
|
| 21 |
+
provider: Provider;
|
| 22 |
+
|
| 23 |
+
@IsString()
|
| 24 |
+
@IsDefined()
|
| 25 |
+
@ValidateIf((o) => !o.password)
|
| 26 |
+
providerToken: string;
|
| 27 |
+
|
| 28 |
+
@IsEmail()
|
| 29 |
+
@IsDefined()
|
| 30 |
+
@ValidateIf((o) => !o.providerToken)
|
| 31 |
+
email: string;
|
| 32 |
+
|
| 33 |
+
@IsString()
|
| 34 |
+
@IsDefined()
|
| 35 |
+
@MinLength(3)
|
| 36 |
+
@MaxLength(128)
|
| 37 |
+
company: string;
|
| 38 |
+
|
| 39 |
+
datafast_visitor_id: string;
|
| 40 |
+
}
|
libraries/nestjs-libraries/src/dtos/auth/forgot-return.password.dto.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import {
|
| 2 |
+
IsDefined,
|
| 3 |
+
IsIn,
|
| 4 |
+
IsString,
|
| 5 |
+
MinLength,
|
| 6 |
+
ValidateIf,
|
| 7 |
+
} from 'class-validator';
|
| 8 |
+
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
|
| 9 |
+
|
| 10 |
+
export class ForgotReturnPasswordDto {
|
| 11 |
+
@IsString()
|
| 12 |
+
@IsDefined()
|
| 13 |
+
@MinLength(3)
|
| 14 |
+
password: string;
|
| 15 |
+
|
| 16 |
+
@IsString()
|
| 17 |
+
@IsDefined()
|
| 18 |
+
@IsIn([makeId(10)], {
|
| 19 |
+
message: 'Passwords do not match',
|
| 20 |
+
})
|
| 21 |
+
@ValidateIf((o) => o.password !== o.repeatPassword)
|
| 22 |
+
repeatPassword: string;
|
| 23 |
+
|
| 24 |
+
@IsString()
|
| 25 |
+
@IsDefined()
|
| 26 |
+
@MinLength(5)
|
| 27 |
+
token: string;
|
| 28 |
+
}
|
libraries/nestjs-libraries/src/dtos/auth/forgot.password.dto.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { IsDefined, IsEmail, IsString } from 'class-validator';
|
| 2 |
+
|
| 3 |
+
export class ForgotPasswordDto {
|
| 4 |
+
@IsString()
|
| 5 |
+
@IsDefined()
|
| 6 |
+
@IsEmail()
|
| 7 |
+
email: string;
|
| 8 |
+
}
|
libraries/nestjs-libraries/src/dtos/auth/login.user.dto.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import {
|
| 2 |
+
IsDefined,
|
| 3 |
+
IsEmail,
|
| 4 |
+
IsString,
|
| 5 |
+
MinLength,
|
| 6 |
+
ValidateIf,
|
| 7 |
+
} from 'class-validator';
|
| 8 |
+
import { Provider } from '@prisma/client';
|
| 9 |
+
|
| 10 |
+
export class LoginUserDto {
|
| 11 |
+
@IsString()
|
| 12 |
+
@IsDefined()
|
| 13 |
+
@ValidateIf((o) => !o.providerToken)
|
| 14 |
+
@MinLength(3)
|
| 15 |
+
password: string;
|
| 16 |
+
|
| 17 |
+
@IsString()
|
| 18 |
+
@IsDefined()
|
| 19 |
+
provider: Provider;
|
| 20 |
+
|
| 21 |
+
@IsString()
|
| 22 |
+
@IsDefined()
|
| 23 |
+
@ValidateIf((o) => !o.password)
|
| 24 |
+
providerToken: string;
|
| 25 |
+
|
| 26 |
+
@IsEmail()
|
| 27 |
+
@IsDefined()
|
| 28 |
+
email: string;
|
| 29 |
+
|
| 30 |
+
datafast_visitor_id: string;
|
| 31 |
+
}
|