Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
CarsonF committed Oct 22, 2024
2 parents 873a509 + a1ece6a commit 14a4d36
Show file tree
Hide file tree
Showing 46 changed files with 1,847 additions and 587 deletions.
10 changes: 5 additions & 5 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,6 @@ const oldRestrictedImports = [
importNames: ['Dictionary', 'SafeDictionary'],
message: 'Use a type with strict keys instead',
},
{
name: 'express-serve-static-core',
importNames: ['Dictionary'],
message: 'Use a type with strict keys instead',
},
];

/** @type {import('@seedcompany/eslint-plugin').ImportRestriction[]} */
Expand Down Expand Up @@ -88,6 +83,11 @@ const restrictedImports = [
path: '@nestjs/common',
replacement: { importName: 'HttpMiddleware', path: '~/core/http' },
},
{
importNames: ['RouteConfig', 'RouteConstraints'],
path: '@nestjs/platform-fastify',
replacement: { path: '~/core/http' },
},
];

const namingConvention = [
Expand Down
35 changes: 35 additions & 0 deletions dbschema/migrations/00008-m146fzf.edgeql

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions dbschema/migrations/00009-m17teec.edgeql

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 34 additions & 0 deletions dbschema/notifications.esdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
module default {
abstract type Notification extending Mixin::Audited {
readAt := .currentRecipient.readAt;
unread := not exists .currentRecipient.readAt;
single currentRecipient := assert_single((
select .recipients filter .user = global currentUser
));
recipients := .<notification[is Notification::Recipient];
}
}

module Notification {
type Recipient {
required notification: default::Notification {
on target delete delete source;
};
required user: default::User {
on target delete delete source;
};

readAt: datetime;
}

type System extending default::Notification {
required message: str;
}
abstract type Comment extending default::Notification {
required comment: Comments::Comment {
on target delete delete source;
};
}
type CommentViaMention extending Comment;
type CommentViaMembership extending Comment;
}
17 changes: 11 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,21 @@
"dependencies": {
"@apollo/server": "^4.9.5",
"@apollo/subgraph": "^2.5.6",
"@as-integrations/fastify": "^2.1.1",
"@aws-sdk/client-s3": "^3.440.0",
"@aws-sdk/s3-request-presigner": "^3.440.0",
"@faker-js/faker": "^8.2.0",
"@fastify/compress": "^7.0.3",
"@fastify/cookie": "^9.4.0",
"@fastify/cors": "^9.0.1",
"@ffprobe-installer/ffprobe": "^2.1.2",
"@golevelup/nestjs-discovery": "^4.0.0",
"@leeoniya/ufuzzy": "^1.0.11",
"@nestjs/apollo": "^12.0.9",
"@nestjs/common": "^10.2.7",
"@nestjs/core": "^10.2.7",
"@nestjs/graphql": "^12.0.9",
"@nestjs/platform-express": "^10.2.7",
"@nestjs/platform-fastify": "^10.4.3",
"@patarapolw/prettyprint": "^1.0.3",
"@seedcompany/cache": "^2.0.0",
"@seedcompany/common": ">=0.13.1 <1",
Expand All @@ -59,16 +63,16 @@
"cli-table3": "^0.6.3",
"clipanion": "^4.0.0-rc.3",
"common-tags": "^1.8.2",
"cookie-parser": "^1.4.6",
"cypher-query-builder": "patch:cypher-query-builder@npm%3A6.0.4#~/.yarn/patches/cypher-query-builder-npm-6.0.4-e8707a5e8e.patch",
"dotenv": "^16.3.1",
"dotenv-expand": "^10.0.0",
"edgedb": "^1.6.0-canary.20240827T111834",
"execa": "^8.0.1",
"express": "^4.18.2",
"extensionless": "^1.7.0",
"fast-safe-stringify": "^2.1.1",
"fastest-levenshtein": "^1.0.16",
"fastify": "^4.28.1",
"fastify-raw-body": "^4.3.0",
"file-type": "^18.6.0",
"glob": "^10.3.10",
"got": "^14.3.0",
Expand Down Expand Up @@ -116,9 +120,6 @@
"@seedcompany/eslint-plugin": "^3.4.1",
"@tsconfig/strictest": "^2.0.2",
"@types/common-tags": "^1.8.3",
"@types/cookie-parser": "^1.4.5",
"@types/express": "^4.17.20",
"@types/express-serve-static-core": "^4.17.39",
"@types/ffprobe": "^1.1.7",
"@types/graphql-upload": "^16.0.4",
"@types/jest": "^29.5.7",
Expand Down Expand Up @@ -154,9 +155,13 @@
"neo4j-driver-bolt-connection@npm:5.20.0": "patch:neo4j-driver-bolt-connection@npm%3A5.20.0#~/.yarn/patches/neo4j-driver-bolt-connection-npm-5.20.0-1f7809f435.patch",
"neo4j-driver-core@npm:5.20.0": "patch:neo4j-driver-core@npm%3A5.20.0#~/.yarn/patches/neo4j-driver-core-npm-5.20.0-99216f6938.patch",
"@apollo/server-plugin-landing-page-graphql-playground": "npm:empty-npm-package@*",
"@apollo/server/express": "npm:empty-npm-package@*",
"@nestjs/cli/fork-ts-checker-webpack-plugin": "npm:empty-npm-package@*",
"@nestjs/cli/webpack": "npm:empty-npm-package@*",
"@nestjs/cli/typescript": "^5.1.6",
"@types/express": "npm:@types/stack-trace@*",
"@types/express-serve-static-core": "npm:@types/stack-trace@*",
"@types/koa": "npm:@types/stack-trace@*",
"subscriptions-transport-ws": "npm:empty-npm-package@*"
},
"dependenciesMeta": {
Expand Down
2 changes: 2 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { FilmModule } from './components/film/film.module';
import { FundingAccountModule } from './components/funding-account/funding-account.module';
import { LanguageModule } from './components/language/language.module';
import { LocationModule } from './components/location/location.module';
import { SystemNotificationModule } from './components/notification-system/system-notification.module';
import { NotificationModule } from './components/notifications/notification.module';
import { OrganizationModule } from './components/organization/organization.module';
import { PartnerModule } from './components/partner/partner.module';
Expand Down Expand Up @@ -91,6 +92,7 @@ if (process.env.NODE_ENV !== 'production') {
PromptsModule,
PnpExtractionResultModule,
NotificationModule,
SystemNotificationModule,
],
})
export class AppModule {}
Expand Down
13 changes: 8 additions & 5 deletions src/common/markdown.scalar.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { CustomScalar, Scalar } from '@nestjs/graphql';
import { GraphQLError, Kind, ValueNode } from 'graphql';

@Scalar('InlineMarkdown')
export class InlineMarkdownScalar
implements CustomScalar<string, string | null>
{
description = 'A string that holds inline Markdown formatted text';
@Scalar('Markdown')
export class MarkdownScalar implements CustomScalar<string, string | null> {
description = 'A string that holds Markdown formatted text';

parseLiteral(ast: ValueNode): string | null {
if (ast.kind !== Kind.STRING) {
Expand All @@ -22,3 +20,8 @@ export class InlineMarkdownScalar
return value;
}
}

@Scalar('InlineMarkdown')
export class InlineMarkdownScalar extends MarkdownScalar {
description = 'A string that holds inline Markdown formatted text';
}
3 changes: 2 additions & 1 deletion src/common/scalars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { CustomScalar } from '@nestjs/graphql';
import { GraphQLScalarType } from 'graphql';
import UploadScalar from 'graphql-upload/GraphQLUpload.mjs';
import { DateScalar, DateTimeScalar } from './luxon.graphql';
import { InlineMarkdownScalar } from './markdown.scalar';
import { InlineMarkdownScalar, MarkdownScalar } from './markdown.scalar';
import { RichTextScalar } from './rich-text.scalar';
import { UrlScalar } from './url.field';

Expand All @@ -16,5 +16,6 @@ export const getRegisteredScalars = (): Scalar[] => [
RichTextScalar,
UploadScalar,
UrlScalar,
MarkdownScalar,
InlineMarkdownScalar,
];
4 changes: 2 additions & 2 deletions src/components/authentication/current-user.provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,14 @@ export class EdgeDBCurrentUserProvider
const { request, session$ } =
GqlExecutionContext.create(context).getContext();
if (request) {
const optionsHolder = this.optionsHolderByRequest.get(request)!;
const optionsHolder = this.optionsHolderByRequest.get(request.raw)!;
session$.subscribe((session) => {
this.applyToOptions(session, optionsHolder);
});
}
} else if (type === 'http') {
const request = context.switchToHttp().getRequest();
const optionsHolder = this.optionsHolderByRequest.get(request)!;
const optionsHolder = this.optionsHolderByRequest.get(request.raw)!;
this.applyToOptions(request.session, optionsHolder);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Policy } from '../util';

@Policy('all', (r) => [
// Technically, we want only when the Commentable is readable.
// I think this is sufficient for practical use at this point in time.
...[r.CommentThread, r.Comment].flatMap((it) => it.read.create),
// This shouldn't be needed, but it is. children() needs rewrite.
r.Commentable.children((c) => c.commentThreads.read.create),
])
export class EveryoneCanCommentPolicy {}
1 change: 1 addition & 0 deletions src/components/authorization/policies/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@ export * from './by-feature/progress-report-media-owner.policy';
export * from './by-feature/project-change-requests-beta.policy';
export * from './by-feature/read-util-objects.policy';
export * from './by-feature/user-can-edit-self.policy';
export * from './by-feature/everyone-can-comment.policy';
export * from './by-feature/user-can-manage-own-comments.policy';
export * from './by-feature/new-progress-reports-beta.policy';
2 changes: 2 additions & 0 deletions src/components/comments/comment.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ import { CommentResolver } from './comment.resolver';
import { CommentService } from './comment.service';
import { CommentableResolver } from './commentable.resolver';
import { CreateCommentResolver } from './create-comment.resolver';
import { CommentViaMentionNotificationModule } from './mention-notification/comment-via-mention-notification.module';

@Module({
imports: [
forwardRef(() => UserModule),
forwardRef(() => AuthorizationModule),
CommentViaMentionNotificationModule,
],
providers: [
CreateCommentResolver,
Expand Down
20 changes: 18 additions & 2 deletions src/components/comments/comment.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Injectable } from '@nestjs/common';
import { difference } from 'lodash';
import {
ID,
InvalidIdForTypeException,
Expand Down Expand Up @@ -26,6 +27,7 @@ import {
CreateCommentInput,
UpdateCommentInput,
} from './dto';
import { CommentViaMentionNotificationService } from './mention-notification/comment-via-mention-notification.service';

type CommentableRef = ID | BaseNode | Commentable;

Expand All @@ -36,6 +38,7 @@ export class CommentService {
private readonly privileges: Privileges,
private readonly resources: ResourceLoader,
private readonly resourcesHost: ResourcesHost,
private readonly mentionNotificationService: CommentViaMentionNotificationService,
) {}

async create(input: CreateCommentInput, session: Session) {
Expand All @@ -45,12 +48,13 @@ export class CommentService {
);
perms.verifyCan('create');

let dto;
try {
const result = await this.repo.create(input, session);
if (!result) {
throw new ServerException('Failed to create comment');
}
return await this.readOne(result.id, session);
dto = await this.repo.readOne(result.id);
} catch (exception) {
if (
input.threadId &&
Expand All @@ -64,6 +68,11 @@ export class CommentService {

throw new ServerException('Failed to create comment', exception);
}

const mentionees = this.mentionNotificationService.extract(dto);
await this.mentionNotificationService.notify(mentionees, dto);

return this.secureComment(dto, session);
}

async getPermissionsFromResource(resource: CommentableRef, session: Session) {
Expand Down Expand Up @@ -134,7 +143,14 @@ export class CommentService {
this.privileges.for(session, Comment, object).verifyChanges(changes);
await this.repo.update(object, changes);

return await this.readOne(input.id, session);
const updated = await this.repo.readOne(object.id);

const prevMentionees = this.mentionNotificationService.extract(object);
const nowMentionees = this.mentionNotificationService.extract(updated);
const newMentionees = difference(prevMentionees, nowMentionees);
await this.mentionNotificationService.notify(newMentionees, updated);

return this.secureComment(updated, session);
}

async delete(id: ID, session: Session): Promise<void> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ObjectType } from '@nestjs/graphql';
import { keys as keysOf } from 'ts-transformer-keys';
import { SecuredProps } from '~/common';
import { e } from '~/core/edgedb';
import { LinkTo, RegisterResource } from '~/core/resources';
import { Notification } from '../../notifications';

@RegisterResource({ db: e.Notification.CommentViaMention })
@ObjectType({
implements: [Notification],
})
export class CommentViaMentionNotification extends Notification {
static readonly Props = keysOf<CommentViaMentionNotification>();
static readonly SecuredProps =
keysOf<SecuredProps<CommentViaMentionNotification>>();

readonly comment: LinkTo<'Comment'>;
}

declare module '~/core/resources/map' {
interface ResourceMap {
CommentMentionedNotification: typeof CommentViaMentionNotification;
}
interface ResourceDBMap {
CommentMentionedNotification: typeof e.Notification.CommentViaMention;
}
}
Loading

0 comments on commit 14a4d36

Please sign in to comment.