Skip to content

Commit

Permalink
feat: validation service for better validation of website options (#298)
Browse files Browse the repository at this point in the history
* feat: validation service

* chore: add more generic validators

* feat: adds validation translations and some more support decorators

* fix: fixes a lot of broken validation parts and some ui issues

* fix: better handling of account population during validation and parallel option operations

* chore: use validation service in post manager
  • Loading branch information
mvdicarlo authored Oct 22, 2024
1 parent a492961 commit ac4d7aa
Show file tree
Hide file tree
Showing 52 changed files with 1,232 additions and 328 deletions.
2 changes: 2 additions & 0 deletions apps/client-server/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { UserSpecifiedWebsiteOptionsModule } from './user-specified-website-opti
import { WebSocketModule } from './web-socket/web-socket.module';
import { WebsiteOptionsModule } from './website-options/website-options.module';
import { WebsitesModule } from './websites/websites.module';
import { ValidationModule } from './validation/validation.module';

@Module({
imports: [
Expand All @@ -43,6 +44,7 @@ import { WebsitesModule } from './websites/websites.module';
UpdateModule,
PostModule,
PostParsersModule,
ValidationModule,
],
controllers: [AppController],
providers: [AppService],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from '@mikro-orm/core';
import {
IPostRecord,
ISubmission,
IWebsitePostRecord,
PostRecordDto,
PostRecordResumeMode,
Expand All @@ -33,7 +34,7 @@ export class PostRecord extends PostyBirbEntity implements IPostRecord {
inversedBy: 'posts',
serializer: (s) => s.id,
})
parent: Rel<Submission>;
parent: Rel<ISubmission>;

@Property({
type: 'date',
Expand Down Expand Up @@ -64,7 +65,7 @@ export class PostRecord extends PostyBirbEntity implements IPostRecord {
>
) {
super();
this.parent = postRecord.parent as unknown as Submission;
this.parent = postRecord.parent;
this.completedAt = postRecord.completedAt;
this.state = postRecord.state;
this.resumeMode = postRecord.resumeMode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
serialize,
} from '@mikro-orm/core';
import {
ISubmission,
IWebsiteFormFields,
IWebsiteOptions,
NULL_ACCOUNT_ID,
Expand All @@ -32,7 +33,7 @@ export class WebsiteOptions<T extends IWebsiteFormFields = IWebsiteFormFields>
nullable: true,
lazy: false,
})
submission: Rel<Submission>;
submission: Rel<ISubmission>;

@Property({ type: 'json', nullable: false })
data: T;
Expand All @@ -51,7 +52,7 @@ export class WebsiteOptions<T extends IWebsiteFormFields = IWebsiteFormFields>

constructor(websiteOptions: Partial<IWebsiteOptions<T>>) {
super();
this.submission = websiteOptions.submission as Submission;
this.submission = websiteOptions.submission;
this.data = websiteOptions.data;
this.account = websiteOptions.account as Account;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Injectable } from '@nestjs/common';
import { TextFieldType } from '@postybirb/form-builder';
import { IWebsiteOptions } from '@postybirb/types';
import { Submission } from '../../database/entities';
import { ISubmission, IWebsiteOptions } from '@postybirb/types';
import { FormGeneratorService } from '../../form-generator/form-generator.service';
import { UnknownWebsite } from '../../websites/website';

Expand All @@ -12,7 +11,7 @@ export class TitleParserService {
constructor(private readonly formGeneratorService: FormGeneratorService) {}

public async parse(
submission: Submission,
submission: ISubmission,
instance: UnknownWebsite,
defaultOptions: IWebsiteOptions,
websiteOptions: IWebsiteOptions
Expand All @@ -22,10 +21,12 @@ export class TitleParserService {
submission.type
)) as unknown as TitleType;
const websiteForm: TitleType =
(await this.formGeneratorService.generateForm({
type: submission.type,
accountId: instance.accountId,
})) as unknown as TitleType;
defaultOptions.id === websiteOptions.id
? defaultForm
: ((await this.formGeneratorService.generateForm({
type: submission.type,
accountId: instance.accountId,
})) as unknown as TitleType);

const title = websiteOptions.data.title ?? defaultOptions.data.title ?? '';
const field: TextFieldType =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Injectable } from '@nestjs/common';
import {
ISubmission,
IWebsiteFormFields,
IWebsiteOptions,
PostData,
} from '@postybirb/types';
import { Submission, WebsiteOptions } from '../database/entities';
import { UnknownWebsite } from '../websites/website';
import { DescriptionParserService } from './parsers/description-parser.service';
import { RatingParser } from './parsers/rating-parser';
Expand All @@ -22,11 +22,11 @@ export class PostParsersService {
) {}

public async parse(
submission: Submission,
submission: ISubmission,
instance: UnknownWebsite,
websiteOptions: IWebsiteOptions
): Promise<PostData<Submission, IWebsiteFormFields>> {
const defaultOptions: WebsiteOptions = submission.options.find(
): Promise<PostData<ISubmission, IWebsiteFormFields>> {
const defaultOptions: IWebsiteOptions = submission.options.find(
(o) => o.isDefault
);
const tags = await this.tagParser.parse(
Expand Down
8 changes: 6 additions & 2 deletions apps/client-server/src/app/post/post-file-resizer.service.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { wrap } from '@mikro-orm/core';
import { Injectable } from '@nestjs/common';
import { Logger } from '@postybirb/logger';
import { FileType, IFileBuffer, ISubmissionFile } from '@postybirb/types';
import {
FileType,
IFileBuffer,
ImageResizeProps,
ISubmissionFile,
} from '@postybirb/types';
import { getFileType } from '@postybirb/utils/file-type';
import type { queueAsPromised } from 'fastq';
import fastq from 'fastq';
import { cpus } from 'os';
import { parse } from 'path';
import { Sharp } from 'sharp';
import { ImageUtil } from '../file/utils/image.util';
import { ImageResizeProps } from './models/image-resize-props';
import { PostingFile, ThumbnailOptions } from './models/posting-file';

type ResizeRequest = {
Expand Down
72 changes: 24 additions & 48 deletions apps/client-server/src/app/post/post-manager.service.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
/* eslint-disable no-param-reassign */
import { EntityDTO, Loaded, wrap } from '@mikro-orm/core';
import { InjectRepository } from '@mikro-orm/nestjs';
import { Inject, Injectable, forwardRef } from '@nestjs/common';
import { forwardRef, Inject, Injectable } from '@nestjs/common';
import { Logger } from '@postybirb/logger';
import {
EntityId,
FileMetadataFields,
FileSubmission,
FileType,
ImageResizeProps,
IPostRecord,
ISubmission,
ISubmissionFile,
IWebsiteFormFields,
IWebsiteOptions,
IWebsitePostRecord,
MessageSubmission,
ModifiedFileDimension,
Expand All @@ -19,7 +23,6 @@ import {
PostResponse,
SubmissionId,
SubmissionType,
ValidationResult,
} from '@postybirb/types';
import { getFileType } from '@postybirb/utils/file-type';
import { chunk } from 'lodash';
Expand All @@ -32,12 +35,12 @@ import {
import { PostyBirbRepository } from '../database/repositories/postybirb-repository';
import { PostParsersService } from '../post-parsers/post-parsers.service';
import { IsTestEnvironment } from '../utils/test.util';
import { ValidationService } from '../validation/validation.service';
import { FileWebsite } from '../websites/models/website-modifiers/file-website';
import { MessageWebsite } from '../websites/models/website-modifiers/message-website';
import { Website } from '../websites/website';
import { WebsiteRegistryService } from '../websites/website-registry.service';
import { CancellableToken } from './models/cancellable-token';
import { ImageResizeProps } from './models/image-resize-props';
import { PostingFile } from './models/posting-file';
import { PostFileResizerService } from './post-file-resizer.service';
import { PostService } from './post.service';
Expand Down Expand Up @@ -69,7 +72,8 @@ export class PostManagerService {
private readonly postService: PostService,
private readonly websiteRegistry: WebsiteRegistryService,
private readonly resizerService: PostFileResizerService,
private readonly postParserService: PostParsersService
private readonly postParserService: PostParsersService,
private readonly validationService: ValidationService
) {
setTimeout(() => this.check(), 60_000);
}
Expand Down Expand Up @@ -212,14 +216,18 @@ export class PostManagerService {
);
}
const data = await this.preparePostData(
submission,
submission as unknown as ISubmission,
instance,
submission.options.find(
(o) => o.account.id === websitePostRecord.account.id
)
) as unknown as IWebsiteOptions
);
const validationResult = await this.validationService.validateSubmission(
submission
);
// TODO - Are there any other 'generic' validations that can be done here?
await this.validatePostData(submission.type, data, instance);
if (validationResult.some((v) => v.errors.length > 0)) {
throw new Error('Submission contains validation errors');
}
await this.attemptToPost(submission, websitePostRecord, instance, data);
} catch (error) {
this.logger
Expand All @@ -237,10 +245,10 @@ export class PostManagerService {
}

private async attemptToPost(
submission: Submission,
submission: ISubmission,
websitePostRecord: IWebsitePostRecord,
instance: Website<unknown>,
data: PostData<Submission, IWebsiteFormFields>
data: PostData<ISubmission, IWebsiteFormFields>
) {
this.cancelToken.throwIfCancelled();
switch (submission.type) {
Expand Down Expand Up @@ -545,9 +553,8 @@ export class PostManagerService {
}

const submission = entity.parent;
const options: WebsiteOptions<never>[] = submission.options.filter(
(o) => !o.isDefault
);
const options: IWebsiteOptions<IWebsiteFormFields>[] =
submission.options.filter((o) => !o.isDefault);
// Only care to create children for those that don't already exist.
const uncreatedOptions = options.filter(
(o) =>
Expand All @@ -556,7 +563,7 @@ export class PostManagerService {
uncreatedOptions.forEach((w) =>
entity.children.add(
this.websitePostRecordRepository.create({
parent: entity,
parent: entity as unknown as IPostRecord,
account: w.account,
})
)
Expand Down Expand Up @@ -604,41 +611,10 @@ export class PostManagerService {
* @return {*} {PostData<Submission, never>}
*/
private preparePostData(
submission: Submission,
submission: ISubmission,
instance: Website<unknown>,
websiteOptions: WebsiteOptions
): Promise<PostData<Submission, IWebsiteFormFields>> {
websiteOptions: IWebsiteOptions
): Promise<PostData<ISubmission, IWebsiteFormFields>> {
return this.postParserService.parse(submission, instance, websiteOptions);
}

/**
* Validates the post data for the given submission type.
* @param {SubmissionType} type
* @param {PostData<Submission, never>} data
* @param {Website<unknown>} instance
*/
private async validatePostData(
type: SubmissionType,
data: PostData<Submission, IWebsiteFormFields>,
instance: Website<unknown>
) {
let result: ValidationResult;
if (type === SubmissionType.FILE) {
result = await (
instance as unknown as FileWebsite<never>
).onValidateFileSubmission(
data as unknown as PostData<FileSubmission, never>
);
} else if (type === SubmissionType.MESSAGE) {
result = await (
instance as unknown as MessageWebsite<never>
).onValidateMessageSubmission(
data as unknown as PostData<MessageSubmission, never>
);
}

if (result?.errors?.length) {
throw new Error('Submission contains validation errors');
}
}
}
2 changes: 2 additions & 0 deletions apps/client-server/src/app/post/post.module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Module } from '@nestjs/common';
import { DatabaseModule } from '../database/database.module';
import { PostParsersModule } from '../post-parsers/post-parsers.module';
import { ValidationModule } from '../validation/validation.module';
import { WebsiteOptionsModule } from '../website-options/website-options.module';
import { WebsiteImplProvider } from '../websites/implementations';
import { WebsitesModule } from '../websites/websites.module';
Expand All @@ -15,6 +16,7 @@ import { PostService } from './post.service';
WebsiteOptionsModule,
WebsitesModule,
PostParsersModule,
ValidationModule,
],
controllers: [PostController],
providers: [
Expand Down
2 changes: 2 additions & 0 deletions apps/client-server/src/app/post/post.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { CreateSubmissionDto } from '../submission/dtos/create-submission.dto';
import { SubmissionService } from '../submission/services/submission.service';
import { SubmissionModule } from '../submission/submission.module';
import { UserSpecifiedWebsiteOptionsModule } from '../user-specified-website-options/user-specified-website-options.module';
import { ValidationModule } from '../validation/validation.module';
import { WebsiteOptionsModule } from '../website-options/website-options.module';
import { WebsitesModule } from '../websites/websites.module';
import { PostFileResizerService } from './post-file-resizer.service';
Expand All @@ -35,6 +36,7 @@ describe('PostService', () => {
UserSpecifiedWebsiteOptionsModule,
PostParsersModule,
PostModule,
ValidationModule,
],
providers: [PostService, PostManagerService, PostFileResizerService],
}).compile();
Expand Down
11 changes: 10 additions & 1 deletion apps/client-server/src/app/post/post.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,16 @@ export class PostService extends PostyBirbService<PostRecord> {
{
completedAt: null,
},
{ orderBy: { createdAt: 'ASC' } }
{
orderBy: { createdAt: 'ASC' },
populate: [
'parent',
'parent.options',
'parent.options.account',
'children',
'children.account',
],
}
);
return entity;
}
Expand Down
Loading

0 comments on commit ac4d7aa

Please sign in to comment.