@solidxai/core 0.1.12-beta.1 → 0.1.12-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/constants/media-file-types.d.ts +5 -0
- package/dist/constants/media-file-types.d.ts.map +1 -0
- package/dist/constants/media-file-types.js +27 -0
- package/dist/constants/media-file-types.js.map +1 -0
- package/dist/controllers/media.controller.d.ts +14 -11
- package/dist/controllers/media.controller.d.ts.map +1 -1
- package/dist/controllers/media.controller.js +72 -33
- package/dist/controllers/media.controller.js.map +1 -1
- package/dist/dtos/create-chatter-message.dto.d.ts +1 -0
- package/dist/dtos/create-chatter-message.dto.d.ts.map +1 -1
- package/dist/dtos/create-chatter-message.dto.js +7 -1
- package/dist/dtos/create-chatter-message.dto.js.map +1 -1
- package/dist/dtos/create-media.dto.d.ts +1 -0
- package/dist/dtos/create-media.dto.d.ts.map +1 -1
- package/dist/dtos/create-media.dto.js +7 -1
- package/dist/dtos/create-media.dto.js.map +1 -1
- package/dist/dtos/post-chatter-message.dto.d.ts +1 -0
- package/dist/dtos/post-chatter-message.dto.d.ts.map +1 -1
- package/dist/dtos/post-chatter-message.dto.js +6 -1
- package/dist/dtos/post-chatter-message.dto.js.map +1 -1
- package/dist/dtos/update-chatter-message.dto.d.ts +1 -0
- package/dist/dtos/update-chatter-message.dto.d.ts.map +1 -1
- package/dist/dtos/update-chatter-message.dto.js +8 -1
- package/dist/dtos/update-chatter-message.dto.js.map +1 -1
- package/dist/dtos/update-chatter-note-message.dto.d.ts +1 -0
- package/dist/dtos/update-chatter-note-message.dto.d.ts.map +1 -1
- package/dist/dtos/update-chatter-note-message.dto.js +7 -1
- package/dist/dtos/update-chatter-note-message.dto.js.map +1 -1
- package/dist/dtos/update-media.dto.d.ts +1 -0
- package/dist/dtos/update-media.dto.d.ts.map +1 -1
- package/dist/dtos/update-media.dto.js +7 -1
- package/dist/dtos/update-media.dto.js.map +1 -1
- package/dist/entities/chatter-message.entity.d.ts +1 -0
- package/dist/entities/chatter-message.entity.d.ts.map +1 -1
- package/dist/entities/chatter-message.entity.js +5 -1
- package/dist/entities/chatter-message.entity.js.map +1 -1
- package/dist/entities/media.entity.d.ts +1 -0
- package/dist/entities/media.entity.d.ts.map +1 -1
- package/dist/entities/media.entity.js +9 -1
- package/dist/entities/media.entity.js.map +1 -1
- package/dist/enums/auth-type.enum.d.ts +2 -1
- package/dist/enums/auth-type.enum.d.ts.map +1 -1
- package/dist/enums/auth-type.enum.js +1 -0
- package/dist/enums/auth-type.enum.js.map +1 -1
- package/dist/guards/authentication.guard.d.ts +3 -1
- package/dist/guards/authentication.guard.d.ts.map +1 -1
- package/dist/guards/authentication.guard.js +5 -1
- package/dist/guards/authentication.guard.js.map +1 -1
- package/dist/guards/media-signed-url.guard.d.ts +9 -0
- package/dist/guards/media-signed-url.guard.d.ts.map +1 -0
- package/dist/guards/media-signed-url.guard.js +49 -0
- package/dist/guards/media-signed-url.guard.js.map +1 -0
- package/dist/helpers/field-crud-managers/MediaFieldCrudManager.d.ts.map +1 -1
- package/dist/helpers/field-crud-managers/MediaFieldCrudManager.js +10 -13
- package/dist/helpers/field-crud-managers/MediaFieldCrudManager.js.map +1 -1
- package/dist/interfaces/chatter-mention-notification.interface.d.ts +27 -0
- package/dist/interfaces/chatter-mention-notification.interface.d.ts.map +1 -0
- package/dist/interfaces/chatter-mention-notification.interface.js +3 -0
- package/dist/interfaces/chatter-mention-notification.interface.js.map +1 -0
- package/dist/jobs/chatter-mention-notification-email-queue-handler.service.d.ts +17 -0
- package/dist/jobs/chatter-mention-notification-email-queue-handler.service.d.ts.map +1 -0
- package/dist/jobs/chatter-mention-notification-email-queue-handler.service.js +106 -0
- package/dist/jobs/chatter-mention-notification-email-queue-handler.service.js.map +1 -0
- package/dist/jobs/database/chatter-mention-notification-email-publisher-database.service.d.ts +12 -0
- package/dist/jobs/database/chatter-mention-notification-email-publisher-database.service.d.ts.map +1 -0
- package/dist/jobs/database/chatter-mention-notification-email-publisher-database.service.js +39 -0
- package/dist/jobs/database/chatter-mention-notification-email-publisher-database.service.js.map +1 -0
- package/dist/jobs/database/chatter-mention-notification-email-queue-options-database.d.ts +8 -0
- package/dist/jobs/database/chatter-mention-notification-email-queue-options-database.d.ts.map +1 -0
- package/dist/jobs/database/chatter-mention-notification-email-queue-options-database.js +10 -0
- package/dist/jobs/database/chatter-mention-notification-email-queue-options-database.js.map +1 -0
- package/dist/jobs/database/chatter-mention-notification-email-subscriber-database.service.d.ts +20 -0
- package/dist/jobs/database/chatter-mention-notification-email-subscriber-database.service.d.ts.map +1 -0
- package/dist/jobs/database/chatter-mention-notification-email-subscriber-database.service.js +48 -0
- package/dist/jobs/database/chatter-mention-notification-email-subscriber-database.service.js.map +1 -0
- package/dist/jobs/rabbitmq/chatter-mention-notification-email-publisher.service.d.ts +12 -0
- package/dist/jobs/rabbitmq/chatter-mention-notification-email-publisher.service.d.ts.map +1 -0
- package/dist/jobs/rabbitmq/chatter-mention-notification-email-publisher.service.js +39 -0
- package/dist/jobs/rabbitmq/chatter-mention-notification-email-publisher.service.js.map +1 -0
- package/dist/jobs/rabbitmq/chatter-mention-notification-email-queue-options.d.ts +8 -0
- package/dist/jobs/rabbitmq/chatter-mention-notification-email-queue-options.d.ts.map +1 -0
- package/dist/jobs/rabbitmq/chatter-mention-notification-email-queue-options.js +10 -0
- package/dist/jobs/rabbitmq/chatter-mention-notification-email-queue-options.js.map +1 -0
- package/dist/jobs/rabbitmq/chatter-mention-notification-email-subscriber.service.d.ts +18 -0
- package/dist/jobs/rabbitmq/chatter-mention-notification-email-subscriber.service.d.ts.map +1 -0
- package/dist/jobs/rabbitmq/chatter-mention-notification-email-subscriber.service.js +45 -0
- package/dist/jobs/rabbitmq/chatter-mention-notification-email-subscriber.service.js.map +1 -0
- package/dist/jobs/redis/chatter-mention-notification-email-publisher-redis.service.d.ts +12 -0
- package/dist/jobs/redis/chatter-mention-notification-email-publisher-redis.service.d.ts.map +1 -0
- package/dist/jobs/redis/chatter-mention-notification-email-publisher-redis.service.js +39 -0
- package/dist/jobs/redis/chatter-mention-notification-email-publisher-redis.service.js.map +1 -0
- package/dist/jobs/redis/chatter-mention-notification-email-queue-options-redis.d.ts +8 -0
- package/dist/jobs/redis/chatter-mention-notification-email-queue-options-redis.d.ts.map +1 -0
- package/dist/jobs/redis/chatter-mention-notification-email-queue-options-redis.js +10 -0
- package/dist/jobs/redis/chatter-mention-notification-email-queue-options-redis.js.map +1 -0
- package/dist/jobs/redis/chatter-mention-notification-email-subscriber-redis.service.d.ts +18 -0
- package/dist/jobs/redis/chatter-mention-notification-email-subscriber-redis.service.d.ts.map +1 -0
- package/dist/jobs/redis/chatter-mention-notification-email-subscriber-redis.service.js +45 -0
- package/dist/jobs/redis/chatter-mention-notification-email-subscriber-redis.service.js.map +1 -0
- package/dist/repository/media.repository.d.ts.map +1 -1
- package/dist/repository/media.repository.js +2 -0
- package/dist/repository/media.repository.js.map +1 -1
- package/dist/seeders/seed-data/solid-core-metadata.json +109 -3
- package/dist/services/chatter-message.service.d.ts +7 -1
- package/dist/services/chatter-message.service.d.ts.map +1 -1
- package/dist/services/chatter-message.service.js +85 -4
- package/dist/services/chatter-message.service.js.map +1 -1
- package/dist/services/media-download-url.service.d.ts +21 -0
- package/dist/services/media-download-url.service.d.ts.map +1 -0
- package/dist/services/media-download-url.service.js +67 -0
- package/dist/services/media-download-url.service.js.map +1 -0
- package/dist/services/media-storage.utils.d.ts +27 -0
- package/dist/services/media-storage.utils.d.ts.map +1 -0
- package/dist/services/media-storage.utils.js +82 -0
- package/dist/services/media-storage.utils.js.map +1 -0
- package/dist/services/media.service.d.ts +20 -4
- package/dist/services/media.service.d.ts.map +1 -1
- package/dist/services/media.service.js +157 -55
- package/dist/services/media.service.js.map +1 -1
- package/dist/services/mediaStorageProviders/file-s3-storage-provider.d.ts +4 -4
- package/dist/services/mediaStorageProviders/file-s3-storage-provider.d.ts.map +1 -1
- package/dist/services/mediaStorageProviders/file-s3-storage-provider.js +37 -63
- package/dist/services/mediaStorageProviders/file-s3-storage-provider.js.map +1 -1
- package/dist/services/mediaStorageProviders/file-storage-provider.d.ts +5 -5
- package/dist/services/mediaStorageProviders/file-storage-provider.d.ts.map +1 -1
- package/dist/services/mediaStorageProviders/file-storage-provider.js +37 -38
- package/dist/services/mediaStorageProviders/file-storage-provider.js.map +1 -1
- package/dist/solid-core.module.d.ts.map +1 -1
- package/dist/solid-core.module.js +25 -1
- package/dist/solid-core.module.js.map +1 -1
- package/dist/subscribers/audit.subscriber.js +1 -1
- package/dist/subscribers/audit.subscriber.js.map +1 -1
- package/package.json +1 -1
- package/src/constants/media-file-types.ts +44 -0
- package/src/controllers/media.controller.ts +44 -24
- package/src/dtos/create-chatter-message.dto.ts +5 -0
- package/src/dtos/create-media.dto.ts +8 -3
- package/src/dtos/post-chatter-message.dto.ts +4 -0
- package/src/dtos/update-chatter-message.dto.ts +6 -0
- package/src/dtos/update-chatter-note-message.dto.ts +5 -0
- package/src/dtos/update-media.dto.ts +8 -3
- package/src/entities/chatter-message.entity.ts +3 -0
- package/src/entities/media.entity.ts +4 -1
- package/src/enums/auth-type.enum.ts +1 -0
- package/src/guards/authentication.guard.ts +3 -0
- package/src/guards/media-signed-url.guard.ts +41 -0
- package/src/helpers/field-crud-managers/MediaFieldCrudManager.ts +12 -24
- package/src/interfaces/chatter-mention-notification.interface.ts +29 -0
- package/src/jobs/chatter-mention-notification-email-queue-handler.service.ts +114 -0
- package/src/jobs/database/chatter-mention-notification-email-publisher-database.service.ts +23 -0
- package/src/jobs/database/chatter-mention-notification-email-queue-options-database.ts +9 -0
- package/src/jobs/database/chatter-mention-notification-email-subscriber-database.service.ts +32 -0
- package/src/jobs/rabbitmq/chatter-mention-notification-email-publisher.service.ts +23 -0
- package/src/jobs/rabbitmq/chatter-mention-notification-email-queue-options.ts +9 -0
- package/src/jobs/rabbitmq/chatter-mention-notification-email-subscriber.service.ts +30 -0
- package/src/jobs/redis/chatter-mention-notification-email-publisher-redis.service.ts +23 -0
- package/src/jobs/redis/chatter-mention-notification-email-queue-options-redis.ts +9 -0
- package/src/jobs/redis/chatter-mention-notification-email-subscriber-redis.service.ts +30 -0
- package/src/repository/media.repository.ts +2 -0
- package/src/seeders/seed-data/email-templates/chatter-mention-notification.handlebars.html +200 -0
- package/src/seeders/seed-data/solid-core-metadata.json +109 -3
- package/src/services/chatter-message.service.ts +97 -2
- package/src/services/media-download-url.service.ts +67 -0
- package/src/services/media-storage.utils.ts +79 -0
- package/src/services/media.service.ts +189 -59
- package/src/services/mediaStorageProviders/file-s3-storage-provider.ts +52 -76
- package/src/services/mediaStorageProviders/file-storage-provider.ts +44 -48
- package/src/solid-core.module.ts +35 -2
- package/src/subscribers/audit.subscriber.ts +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ApiProperty } from '@nestjs/swagger';
|
|
2
|
-
import { IsInt } from 'class-validator';
|
|
1
|
+
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
2
|
+
import { IsBoolean, IsInt } from 'class-validator';
|
|
3
3
|
import { IsNotEmpty, IsString, IsOptional } from 'class-validator';
|
|
4
4
|
export class CreateMediaDto {
|
|
5
5
|
@IsNotEmpty()
|
|
@@ -7,6 +7,11 @@ export class CreateMediaDto {
|
|
|
7
7
|
@ApiProperty()
|
|
8
8
|
entityId: number;
|
|
9
9
|
|
|
10
|
+
@IsOptional()
|
|
11
|
+
@IsBoolean()
|
|
12
|
+
@ApiPropertyOptional({ default: true })
|
|
13
|
+
isPublic?: boolean;
|
|
14
|
+
|
|
10
15
|
@IsOptional()
|
|
11
16
|
@IsString()
|
|
12
17
|
@ApiProperty()
|
|
@@ -56,4 +61,4 @@ fieldMetadataId: number;
|
|
|
56
61
|
@IsOptional()
|
|
57
62
|
@ApiProperty()
|
|
58
63
|
fieldMetadataUserKey: string;
|
|
59
|
-
}
|
|
64
|
+
}
|
|
@@ -7,6 +7,11 @@ export class UpdateChatterNoteMessageDto {
|
|
|
7
7
|
@ApiProperty()
|
|
8
8
|
messageBody: string;
|
|
9
9
|
|
|
10
|
+
@IsString()
|
|
11
|
+
@IsOptional()
|
|
12
|
+
@ApiProperty()
|
|
13
|
+
messageBodyMentions?: string;
|
|
14
|
+
|
|
10
15
|
@IsString()
|
|
11
16
|
@IsOptional()
|
|
12
17
|
@ApiProperty({ required: false, description: 'Comma-separated media IDs to remove from this note.' })
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { IsInt,IsOptional, IsNotEmpty, IsString } from 'class-validator';
|
|
2
|
-
import { ApiProperty } from '@nestjs/swagger';
|
|
1
|
+
import { IsBoolean, IsInt,IsOptional, IsNotEmpty, IsString } from 'class-validator';
|
|
2
|
+
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
3
3
|
export class UpdateMediaDto {
|
|
4
4
|
@IsOptional()
|
|
5
5
|
@IsInt()
|
|
@@ -11,6 +11,11 @@ export class UpdateMediaDto {
|
|
|
11
11
|
@ApiProperty()
|
|
12
12
|
entityId: number;
|
|
13
13
|
|
|
14
|
+
@IsOptional()
|
|
15
|
+
@IsBoolean()
|
|
16
|
+
@ApiPropertyOptional({ default: true })
|
|
17
|
+
isPublic?: boolean;
|
|
18
|
+
|
|
14
19
|
@IsOptional()
|
|
15
20
|
@IsString()
|
|
16
21
|
@ApiProperty()
|
|
@@ -60,4 +65,4 @@ fieldMetadataId: number;
|
|
|
60
65
|
@IsOptional()
|
|
61
66
|
@ApiProperty()
|
|
62
67
|
fieldMetadataUserKey: string;
|
|
63
|
-
}
|
|
68
|
+
}
|
|
@@ -18,6 +18,9 @@ export class ChatterMessage extends CommonEntity {
|
|
|
18
18
|
@Column({ nullable: true, ...getColumnType('longText') })
|
|
19
19
|
messageBody: string;
|
|
20
20
|
|
|
21
|
+
@Column({ nullable: true, ...getColumnType('longText') })
|
|
22
|
+
messageBodyMentions: string;
|
|
23
|
+
|
|
21
24
|
@Index()
|
|
22
25
|
@Column({ type: "integer" })
|
|
23
26
|
coModelEntityId: number;
|
|
@@ -9,6 +9,9 @@ export class Media extends CommonEntity {
|
|
|
9
9
|
@Column({ type: "integer" })
|
|
10
10
|
entityId: number;
|
|
11
11
|
|
|
12
|
+
@Column({ name: "is_public", default: true })
|
|
13
|
+
isPublic: boolean = true;
|
|
14
|
+
|
|
12
15
|
@Column({ type: "varchar", nullable: true })
|
|
13
16
|
relativeUri: string;
|
|
14
17
|
|
|
@@ -36,4 +39,4 @@ export class Media extends CommonEntity {
|
|
|
36
39
|
@ManyToOne(() => FieldMetadata, { nullable: false })
|
|
37
40
|
@JoinColumn()
|
|
38
41
|
fieldMetadata: FieldMetadata;
|
|
39
|
-
}
|
|
42
|
+
}
|
|
@@ -9,6 +9,7 @@ import { AUTH_TYPE_KEY } from '../decorators/auth.decorator';
|
|
|
9
9
|
import { AuthType } from '../enums/auth-type.enum';
|
|
10
10
|
import { AccessTokenGuard } from './access-token.guard';
|
|
11
11
|
import { ApiKeyGuard } from './api-key.guard';
|
|
12
|
+
import { MediaSignedUrlGuard } from './media-signed-url.guard';
|
|
12
13
|
import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
|
|
13
14
|
import { PermissionMetadataService } from '../services/permission-metadata.service';
|
|
14
15
|
import { ClsService } from 'nestjs-cls';
|
|
@@ -22,12 +23,14 @@ export class AuthenticationGuard implements CanActivate {
|
|
|
22
23
|
[AuthType.Bearer]: this.accessTokenGuard,
|
|
23
24
|
[AuthType.ApiKey]: this.apiKeyGuard,
|
|
24
25
|
[AuthType.None]: { canActivate: () => true },
|
|
26
|
+
[AuthType.MediaSignedUrl]: this.mediaSignedUrlGuard,
|
|
25
27
|
};
|
|
26
28
|
|
|
27
29
|
constructor(
|
|
28
30
|
private readonly reflector: Reflector,
|
|
29
31
|
private readonly accessTokenGuard: AccessTokenGuard,
|
|
30
32
|
private readonly apiKeyGuard: ApiKeyGuard,
|
|
33
|
+
private readonly mediaSignedUrlGuard: MediaSignedUrlGuard,
|
|
31
34
|
private readonly permissionService: PermissionMetadataService,
|
|
32
35
|
private readonly cls: ClsService,
|
|
33
36
|
) { }
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
|
|
2
|
+
import { Request } from 'express';
|
|
3
|
+
import { MediaDownloadUrlService } from 'src/services/media-download-url.service';
|
|
4
|
+
|
|
5
|
+
@Injectable()
|
|
6
|
+
export class MediaSignedUrlGuard implements CanActivate {
|
|
7
|
+
constructor(private readonly mediaDownloadUrlService: MediaDownloadUrlService) { }
|
|
8
|
+
|
|
9
|
+
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
10
|
+
const request = context.switchToHttp().getRequest<Request>();
|
|
11
|
+
const token = this.extractTokenFromQuery(request);
|
|
12
|
+
if (!token) {
|
|
13
|
+
throw new UnauthorizedException();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let payload: { mediaId: number };
|
|
17
|
+
try {
|
|
18
|
+
payload = await this.mediaDownloadUrlService.verifyDownloadToken(token);
|
|
19
|
+
} catch {
|
|
20
|
+
throw new UnauthorizedException();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const routeMediaId = Number(request.params?.id);
|
|
24
|
+
if (!Number.isFinite(routeMediaId) || payload.mediaId !== routeMediaId) {
|
|
25
|
+
throw new UnauthorizedException();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// No REQUEST_USER_KEY is ever set for token auth (only Bearer/ApiKey guards set it), so
|
|
29
|
+
// the sibling global APP_GUARD PermissionsGuard would otherwise reject this request for
|
|
30
|
+
// lack of a per-user permission grant. Flag it the same way AuthenticationGuard flags
|
|
31
|
+
// Public-role-exempt requests - token possession IS the full authorization decision here.
|
|
32
|
+
request['isListedInPublicRole'] = true;
|
|
33
|
+
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private extractTokenFromQuery(request: Request): string | undefined {
|
|
38
|
+
const token = request.query?.token;
|
|
39
|
+
return typeof token === 'string' ? token : undefined;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FieldCrudManager, ValidationError } from "src/interfaces";
|
|
2
|
+
import { DANGEROUS_EXTENSIONS, EXT_TO_MEDIA_TYPE, MediaCategory } from "src/constants/media-file-types";
|
|
2
3
|
|
|
3
4
|
export enum SolidMediaType {
|
|
4
5
|
mediaSingle = 'mediaSingle',
|
|
@@ -14,7 +15,7 @@ export interface MediaFieldOptions {
|
|
|
14
15
|
isUpdate: boolean | undefined | null;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
type MediaType =
|
|
18
|
+
type MediaType = MediaCategory;
|
|
18
19
|
|
|
19
20
|
const MIME_TO_MEDIA_TYPE: Record<string, MediaType> = {
|
|
20
21
|
// Images
|
|
@@ -25,7 +26,6 @@ const MIME_TO_MEDIA_TYPE: Record<string, MediaType> = {
|
|
|
25
26
|
'image/gif': 'image',
|
|
26
27
|
'image/bmp': 'image',
|
|
27
28
|
'image/tiff': 'image',
|
|
28
|
-
'image/svg+xml': 'image',
|
|
29
29
|
'image/heic': 'image',
|
|
30
30
|
'image/heif': 'image',
|
|
31
31
|
|
|
@@ -72,35 +72,23 @@ const MIME_TO_MEDIA_TYPE: Record<string, MediaType> = {
|
|
|
72
72
|
'application/x-zip-compressed': 'file',
|
|
73
73
|
'application/x-rar-compressed': 'file',
|
|
74
74
|
'application/x-7z-compressed': 'file',
|
|
75
|
-
|
|
76
|
-
// Common binary fallback category
|
|
77
|
-
'application/octet-stream': 'file',
|
|
78
75
|
};
|
|
79
76
|
|
|
80
|
-
const EXT_TO_MEDIA_TYPE: Record<string, MediaType> = {
|
|
81
|
-
// Images
|
|
82
|
-
png: 'image', jpg: 'image', jpeg: 'image', webp: 'image', gif: 'image', bmp: 'image', tiff: 'image', svg: 'image', heic: 'image', heif: 'image',
|
|
83
|
-
|
|
84
|
-
// Audio
|
|
85
|
-
mp3: 'audio', wav: 'audio', ogg: 'audio', aac: 'audio', m4a: 'audio', flac: 'audio',
|
|
86
|
-
|
|
87
|
-
// Video
|
|
88
|
-
mp4: 'video', mov: 'video', avi: 'video', mkv: 'video', mpeg: 'video', mpg: 'video', '3gp': 'video', '3g2': 'video',
|
|
89
|
-
|
|
90
|
-
// Files
|
|
91
|
-
pdf: 'file', txt: 'file', md: 'file', csv: 'file', json: 'file',
|
|
92
|
-
doc: 'file', docx: 'file', xls: 'file', xlsx: 'file', ppt: 'file', pptx: 'file',
|
|
93
|
-
zip: 'file', rar: 'file', '7z': 'file',
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
|
|
97
77
|
export class MediaFieldCrudManager implements FieldCrudManager {
|
|
98
78
|
|
|
99
79
|
constructor(private readonly options: MediaFieldOptions) {
|
|
100
80
|
}
|
|
101
81
|
|
|
102
82
|
private resolveMediaType(mimetype?: string, filename?: string): MediaType | null {
|
|
83
|
+
const ext = (filename || '').split('.').pop()?.toLowerCase();
|
|
84
|
+
if (ext && DANGEROUS_EXTENSIONS.has(ext)) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
103
88
|
const mt = (mimetype || '').toLowerCase().trim();
|
|
89
|
+
if (mt === 'image/svg+xml' || mt === 'text/html' || mt === 'application/xhtml+xml') {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
104
92
|
if (mt && MIME_TO_MEDIA_TYPE[mt]) {
|
|
105
93
|
return MIME_TO_MEDIA_TYPE[mt];
|
|
106
94
|
}
|
|
@@ -110,8 +98,8 @@ export class MediaFieldCrudManager implements FieldCrudManager {
|
|
|
110
98
|
if (mt.startsWith('audio/')) return 'audio';
|
|
111
99
|
if (mt.startsWith('video/')) return 'video';
|
|
112
100
|
|
|
113
|
-
// Fallback to extension if provided
|
|
114
|
-
|
|
101
|
+
// Fallback to extension if provided (also covers generic/unrecognized
|
|
102
|
+
// mimetypes such as application/octet-stream).
|
|
115
103
|
if (ext && EXT_TO_MEDIA_TYPE[ext]) {
|
|
116
104
|
return EXT_TO_MEDIA_TYPE[ext];
|
|
117
105
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface ChatterMentionNotificationMention {
|
|
2
|
+
id?: number;
|
|
3
|
+
username: string;
|
|
4
|
+
displayName: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface ChatterMentionNotificationActor {
|
|
8
|
+
id?: number;
|
|
9
|
+
username?: string;
|
|
10
|
+
email?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ChatterMentionNotificationEntity {
|
|
14
|
+
id: number;
|
|
15
|
+
modelName: string;
|
|
16
|
+
moduleName?: string;
|
|
17
|
+
displayName: string;
|
|
18
|
+
userKey?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ChatterMentionNotificationPayload {
|
|
22
|
+
templateName: string;
|
|
23
|
+
mentions: ChatterMentionNotificationMention[];
|
|
24
|
+
actor: ChatterMentionNotificationActor;
|
|
25
|
+
noteBody: string;
|
|
26
|
+
entity: ChatterMentionNotificationEntity;
|
|
27
|
+
parentEntity: string;
|
|
28
|
+
parentEntityId: number;
|
|
29
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Injectable, Logger } from '@nestjs/common';
|
|
2
|
+
import { MailFactory } from 'src/factories/mail.factory';
|
|
3
|
+
import { ChatterMentionNotificationPayload } from 'src/interfaces/chatter-mention-notification.interface';
|
|
4
|
+
import { QueueMessage } from 'src/interfaces/mq';
|
|
5
|
+
import { UserRepository } from 'src/repository/user.repository';
|
|
6
|
+
import { SettingService } from 'src/services/setting.service';
|
|
7
|
+
import type { SolidCoreSetting } from 'src/services/settings/default-settings-provider.service';
|
|
8
|
+
import { In } from 'typeorm';
|
|
9
|
+
|
|
10
|
+
@Injectable()
|
|
11
|
+
export class ChatterMentionNotificationEmailQueueHandler {
|
|
12
|
+
private readonly logger = new Logger(ChatterMentionNotificationEmailQueueHandler.name);
|
|
13
|
+
|
|
14
|
+
constructor(
|
|
15
|
+
private readonly mailFactory: MailFactory,
|
|
16
|
+
private readonly userRepository: UserRepository,
|
|
17
|
+
private readonly settingService: SettingService,
|
|
18
|
+
) {}
|
|
19
|
+
|
|
20
|
+
private buildAdminRecordFormUrl(frontendAdminBaseUrl: string, moduleName: string, modelName: string, recordId: number) {
|
|
21
|
+
if (!frontendAdminBaseUrl || !moduleName || !modelName || !recordId) return '';
|
|
22
|
+
const normalizedModelName = modelName
|
|
23
|
+
.trim()
|
|
24
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
25
|
+
.replace(/[_\s]+/g, '-')
|
|
26
|
+
.toLowerCase();
|
|
27
|
+
return `${frontendAdminBaseUrl.replace(/\/+$/, '')}/admin/core/${moduleName.trim()}/${normalizedModelName}/form/${recordId}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async handle(message: QueueMessage<ChatterMentionNotificationPayload>) {
|
|
31
|
+
const mailService = this.mailFactory.getMailService();
|
|
32
|
+
const payload = message.payload;
|
|
33
|
+
|
|
34
|
+
if (!payload?.mentions?.length) {
|
|
35
|
+
this.logger.debug('Skipping chatter mention notification because no mentions were provided.');
|
|
36
|
+
return { sent: 0 };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const mentionUserIds = payload.mentions
|
|
40
|
+
.map(mention => Number(mention.id))
|
|
41
|
+
.filter(id => Number.isInteger(id) && id > 0);
|
|
42
|
+
const userIdsToLoad = Array.from(new Set([
|
|
43
|
+
...mentionUserIds,
|
|
44
|
+
...(payload.actor?.id ? [Number(payload.actor.id)] : []),
|
|
45
|
+
]));
|
|
46
|
+
|
|
47
|
+
if (userIdsToLoad.length === 0) {
|
|
48
|
+
this.logger.debug('Skipping chatter mention notification because mention user ids were not provided.');
|
|
49
|
+
return { sent: 0 };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const users = await this.userRepository.find({
|
|
53
|
+
where: { id: In(userIdsToLoad) },
|
|
54
|
+
});
|
|
55
|
+
const usersById = new Map(users.map(user => [Number(user.id), user]));
|
|
56
|
+
const actor = payload.actor?.id ? usersById.get(Number(payload.actor.id)) : null;
|
|
57
|
+
const recipients = Array.from(new Set(mentionUserIds))
|
|
58
|
+
.map(id => {
|
|
59
|
+
const user = usersById.get(id);
|
|
60
|
+
const mention = payload.mentions.find(item => Number(item.id) === id);
|
|
61
|
+
return {
|
|
62
|
+
id,
|
|
63
|
+
username: user?.username || mention?.username || '',
|
|
64
|
+
displayName: user?.fullName || mention?.displayName || user?.username || mention?.username || '',
|
|
65
|
+
email: user?.email || '',
|
|
66
|
+
};
|
|
67
|
+
})
|
|
68
|
+
.filter(recipient => Boolean(recipient.email));
|
|
69
|
+
|
|
70
|
+
if (recipients.length === 0) {
|
|
71
|
+
this.logger.debug('Skipping chatter mention notification because no mentioned users have email addresses.');
|
|
72
|
+
return { sent: 0 };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const frontendAdminBaseUrl = this.settingService.getConfigValue<SolidCoreSetting>('frontendAdminBaseUrl');
|
|
76
|
+
const commonTemplateParams = {
|
|
77
|
+
mentionedByDisplayName: actor?.fullName || payload.actor?.username || payload.actor?.email || 'Someone',
|
|
78
|
+
mentionedByEmail: actor?.email || payload.actor?.email,
|
|
79
|
+
noteBody: payload.noteBody,
|
|
80
|
+
entityDisplayName: payload.entity.displayName,
|
|
81
|
+
entityUserKey: payload.entity.userKey,
|
|
82
|
+
entityFormUrl: this.buildAdminRecordFormUrl(
|
|
83
|
+
frontendAdminBaseUrl,
|
|
84
|
+
payload.entity.moduleName,
|
|
85
|
+
payload.entity.modelName,
|
|
86
|
+
payload.entity.id,
|
|
87
|
+
),
|
|
88
|
+
solidAppName: this.settingService.getConfigValue<SolidCoreSetting>('appTitle'),
|
|
89
|
+
solidAppWebsiteUrl: this.settingService.getConfigValue<SolidCoreSetting>('solidAppWebsiteUrl'),
|
|
90
|
+
companyLogoUrl: this.settingService.getConfigValue<SolidCoreSetting>('companylogo'),
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
let sent = 0;
|
|
94
|
+
for (const recipient of recipients) {
|
|
95
|
+
await mailService.sendEmailUsingTemplate(
|
|
96
|
+
recipient.email,
|
|
97
|
+
payload.templateName,
|
|
98
|
+
{
|
|
99
|
+
...commonTemplateParams,
|
|
100
|
+
mentionedUserDisplayName: recipient.displayName || recipient.username,
|
|
101
|
+
mentionedUserUsername: recipient.username,
|
|
102
|
+
},
|
|
103
|
+
false,
|
|
104
|
+
null,
|
|
105
|
+
null,
|
|
106
|
+
payload.parentEntity,
|
|
107
|
+
payload.parentEntityId,
|
|
108
|
+
);
|
|
109
|
+
sent += 1;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return { sent };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import { ChatterMentionNotificationPayload } from 'src/interfaces/chatter-mention-notification.interface';
|
|
3
|
+
import { QueuesModuleOptions } from 'src/interfaces';
|
|
4
|
+
import { MqMessageQueueService } from 'src/services/mq-message-queue.service';
|
|
5
|
+
import { MqMessageService } from 'src/services/mq-message.service';
|
|
6
|
+
import { DatabasePublisher } from 'src/services/queues/database-publisher.service';
|
|
7
|
+
import queueOptions from './chatter-mention-notification-email-queue-options-database';
|
|
8
|
+
|
|
9
|
+
@Injectable()
|
|
10
|
+
export class ChatterMentionNotificationEmailQueuePublisherDatabase extends DatabasePublisher<ChatterMentionNotificationPayload> {
|
|
11
|
+
constructor(
|
|
12
|
+
protected readonly mqMessageService: MqMessageService,
|
|
13
|
+
protected readonly mqMessageQueueService: MqMessageQueueService,
|
|
14
|
+
) {
|
|
15
|
+
super(mqMessageService, mqMessageQueueService);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
options(): QueuesModuleOptions {
|
|
19
|
+
return {
|
|
20
|
+
...queueOptions
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import { ChatterMentionNotificationEmailQueueHandler } from 'src/jobs/chatter-mention-notification-email-queue-handler.service';
|
|
3
|
+
import { ChatterMentionNotificationPayload } from 'src/interfaces/chatter-mention-notification.interface';
|
|
4
|
+
import { QueuesModuleOptions } from 'src/interfaces';
|
|
5
|
+
import { QueueMessage } from 'src/interfaces/mq';
|
|
6
|
+
import { MqMessageQueueService } from 'src/services/mq-message-queue.service';
|
|
7
|
+
import { MqMessageService } from 'src/services/mq-message.service';
|
|
8
|
+
import { PollerService } from 'src/services/poller.service';
|
|
9
|
+
import { DatabaseSubscriber } from 'src/services/queues/database-subscriber.service';
|
|
10
|
+
import queueOptions from './chatter-mention-notification-email-queue-options-database';
|
|
11
|
+
|
|
12
|
+
@Injectable()
|
|
13
|
+
export class ChatterMentionNotificationEmailQueueSubscriberDatabase extends DatabaseSubscriber<ChatterMentionNotificationPayload> {
|
|
14
|
+
constructor(
|
|
15
|
+
readonly mqMessageService: MqMessageService,
|
|
16
|
+
readonly mqMessageQueueService: MqMessageQueueService,
|
|
17
|
+
readonly poller: PollerService,
|
|
18
|
+
private readonly handler: ChatterMentionNotificationEmailQueueHandler,
|
|
19
|
+
) {
|
|
20
|
+
super(mqMessageService, mqMessageQueueService, poller);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
options(): QueuesModuleOptions {
|
|
24
|
+
return {
|
|
25
|
+
...queueOptions
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
subscribe(message: QueueMessage<ChatterMentionNotificationPayload>) {
|
|
30
|
+
return this.handler.handle(message);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import { ChatterMentionNotificationPayload } from 'src/interfaces/chatter-mention-notification.interface';
|
|
3
|
+
import { QueuesModuleOptions } from 'src/interfaces';
|
|
4
|
+
import { MqMessageQueueService } from 'src/services/mq-message-queue.service';
|
|
5
|
+
import { MqMessageService } from 'src/services/mq-message.service';
|
|
6
|
+
import { RabbitMqPublisher } from 'src/services/queues/rabbitmq-publisher.service';
|
|
7
|
+
import queueOptions from './chatter-mention-notification-email-queue-options';
|
|
8
|
+
|
|
9
|
+
@Injectable()
|
|
10
|
+
export class ChatterMentionNotificationEmailQueuePublisherRabbitmq extends RabbitMqPublisher<ChatterMentionNotificationPayload> {
|
|
11
|
+
constructor(
|
|
12
|
+
protected readonly mqMessageService: MqMessageService,
|
|
13
|
+
protected readonly mqMessageQueueService: MqMessageQueueService,
|
|
14
|
+
) {
|
|
15
|
+
super(mqMessageService, mqMessageQueueService);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
options(): QueuesModuleOptions {
|
|
19
|
+
return {
|
|
20
|
+
...queueOptions
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import { ChatterMentionNotificationEmailQueueHandler } from 'src/jobs/chatter-mention-notification-email-queue-handler.service';
|
|
3
|
+
import { ChatterMentionNotificationPayload } from 'src/interfaces/chatter-mention-notification.interface';
|
|
4
|
+
import { QueuesModuleOptions } from 'src/interfaces';
|
|
5
|
+
import { QueueMessage } from 'src/interfaces/mq';
|
|
6
|
+
import { MqMessageQueueService } from 'src/services/mq-message-queue.service';
|
|
7
|
+
import { MqMessageService } from 'src/services/mq-message.service';
|
|
8
|
+
import { RabbitMqSubscriber } from 'src/services/queues/rabbitmq-subscriber.service';
|
|
9
|
+
import queueOptions from './chatter-mention-notification-email-queue-options';
|
|
10
|
+
|
|
11
|
+
@Injectable()
|
|
12
|
+
export class ChatterMentionNotificationEmailQueueSubscriberRabbitmq extends RabbitMqSubscriber<ChatterMentionNotificationPayload> {
|
|
13
|
+
constructor(
|
|
14
|
+
readonly mqMessageService: MqMessageService,
|
|
15
|
+
readonly mqMessageQueueService: MqMessageQueueService,
|
|
16
|
+
private readonly handler: ChatterMentionNotificationEmailQueueHandler,
|
|
17
|
+
) {
|
|
18
|
+
super(mqMessageService, mqMessageQueueService);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
options(): QueuesModuleOptions {
|
|
22
|
+
return {
|
|
23
|
+
...queueOptions
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
subscribe(message: QueueMessage<ChatterMentionNotificationPayload>) {
|
|
28
|
+
return this.handler.handle(message);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import { ChatterMentionNotificationPayload } from 'src/interfaces/chatter-mention-notification.interface';
|
|
3
|
+
import { QueuesModuleOptions } from 'src/interfaces';
|
|
4
|
+
import { MqMessageQueueService } from 'src/services/mq-message-queue.service';
|
|
5
|
+
import { MqMessageService } from 'src/services/mq-message.service';
|
|
6
|
+
import { RedisPublisher } from 'src/services/queues/redis-publisher.service';
|
|
7
|
+
import queueOptions from './chatter-mention-notification-email-queue-options-redis';
|
|
8
|
+
|
|
9
|
+
@Injectable()
|
|
10
|
+
export class ChatterMentionNotificationEmailQueuePublisherRedis extends RedisPublisher<ChatterMentionNotificationPayload> {
|
|
11
|
+
constructor(
|
|
12
|
+
protected readonly mqMessageService: MqMessageService,
|
|
13
|
+
protected readonly mqMessageQueueService: MqMessageQueueService,
|
|
14
|
+
) {
|
|
15
|
+
super(mqMessageService, mqMessageQueueService);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
options(): QueuesModuleOptions {
|
|
19
|
+
return {
|
|
20
|
+
...queueOptions
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import { ChatterMentionNotificationEmailQueueHandler } from 'src/jobs/chatter-mention-notification-email-queue-handler.service';
|
|
3
|
+
import { ChatterMentionNotificationPayload } from 'src/interfaces/chatter-mention-notification.interface';
|
|
4
|
+
import { QueuesModuleOptions } from 'src/interfaces';
|
|
5
|
+
import { QueueMessage } from 'src/interfaces/mq';
|
|
6
|
+
import { MqMessageQueueService } from 'src/services/mq-message-queue.service';
|
|
7
|
+
import { MqMessageService } from 'src/services/mq-message.service';
|
|
8
|
+
import { RedisSubscriber } from 'src/services/queues/redis-subscriber.service';
|
|
9
|
+
import queueOptions from './chatter-mention-notification-email-queue-options-redis';
|
|
10
|
+
|
|
11
|
+
@Injectable()
|
|
12
|
+
export class ChatterMentionNotificationEmailQueueSubscriberRedis extends RedisSubscriber<ChatterMentionNotificationPayload> {
|
|
13
|
+
constructor(
|
|
14
|
+
readonly mqMessageService: MqMessageService,
|
|
15
|
+
readonly mqMessageQueueService: MqMessageQueueService,
|
|
16
|
+
private readonly handler: ChatterMentionNotificationEmailQueueHandler,
|
|
17
|
+
) {
|
|
18
|
+
super(mqMessageService, mqMessageQueueService);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
options(): QueuesModuleOptions {
|
|
22
|
+
return {
|
|
23
|
+
...queueOptions
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
subscribe(message: QueueMessage<ChatterMentionNotificationPayload>) {
|
|
28
|
+
return this.handler.handle(message);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -7,6 +7,7 @@ import { MediaStorageProviderMetadataRepository } from './media-storage-provider
|
|
|
7
7
|
import { ModelMetadataRepository } from './model-metadata.repository';
|
|
8
8
|
import { SecurityRuleRepository } from './security-rule.repository';
|
|
9
9
|
import { SolidBaseRepository } from './solid-base.repository';
|
|
10
|
+
import { resolveStoredMediaIsPublic } from 'src/services/media-storage.utils';
|
|
10
11
|
|
|
11
12
|
@Injectable()
|
|
12
13
|
export class MediaRepository extends SolidBaseRepository<Media> {
|
|
@@ -44,6 +45,7 @@ export class MediaRepository extends SolidBaseRepository<Media> {
|
|
|
44
45
|
id: createDto['mediaStorageProviderMetadataId']
|
|
45
46
|
},
|
|
46
47
|
});
|
|
48
|
+
createDto['isPublic'] = resolveStoredMediaIsPublic(createDto['isPublic'], createDto['mediaStorageProviderMetadata']);
|
|
47
49
|
const media = this.create(createDto);
|
|
48
50
|
return this.save(media);
|
|
49
51
|
}
|