Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] Mentions and Subscription APIs #8554

Merged
merged 32 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
9640a32
feat: initialize mentions module
GloireMutaliko21 Nov 26, 2024
d9a1cb7
feat: add create mention service and and event
GloireMutaliko21 Nov 26, 2024
65bb8f7
feat: publish mention event
GloireMutaliko21 Nov 26, 2024
5124298
feat: mention controller
GloireMutaliko21 Nov 26, 2024
4c9559f
fix(typos): cspell build error
GloireMutaliko21 Nov 26, 2024
9e38d8f
fix: improve code by coderabbit
GloireMutaliko21 Nov 26, 2024
aaabf42
fix: improve mention event naming
GloireMutaliko21 Nov 26, 2024
82763ff
fix: improve entity type based model
GloireMutaliko21 Nov 27, 2024
354bdf4
feat: add subscription model
GloireMutaliko21 Nov 27, 2024
02cc68a
feat: add subscription repositories
GloireMutaliko21 Nov 27, 2024
f2d92a1
feat: subscroption entity
GloireMutaliko21 Nov 27, 2024
d2b73dd
feat: add subscription PostgreSQL migration
GloireMutaliko21 Nov 27, 2024
c1b3600
feat: subscription MySQL and SQLite migrations
GloireMutaliko21 Nov 27, 2024
e9f84d9
fix: wrong folder naming
GloireMutaliko21 Nov 27, 2024
1b42e1e
Merge branch 'feat/subscribe-model-and-entity' into feat/mentions-api
GloireMutaliko21 Nov 27, 2024
c4ab665
feat: create subscription service, command and event
GloireMutaliko21 Nov 27, 2024
e95a214
feat: add subscription controller
GloireMutaliko21 Nov 27, 2024
c1385c2
fix: improve by coderabbit
GloireMutaliko21 Nov 27, 2024
f6c4923
fix: improve mention entity add parent entity fields
GloireMutaliko21 Nov 28, 2024
2a68786
feat: generate Postgre migration
GloireMutaliko21 Nov 28, 2024
d53e238
feat: generate MySQL migration
GloireMutaliko21 Nov 28, 2024
58a1ba3
feat: generate SQLite migration
GloireMutaliko21 Nov 28, 2024
80ccb20
fix: mention subscription event
GloireMutaliko21 Nov 28, 2024
1e1ad3d
feat: subscription handlers
GloireMutaliko21 Nov 28, 2024
c362bd8
feat: mention service improve update operation
GloireMutaliko21 Nov 29, 2024
79abcdd
feat: task mentions and subscription
GloireMutaliko21 Nov 29, 2024
b7203db
fix: hint for future TODO improve
GloireMutaliko21 Nov 29, 2024
2478f90
Merge pull request #8557 from ever-co/fix/improve-mention-entity
GloireMutaliko21 Nov 29, 2024
04e448c
fix: subscribe users after comment
GloireMutaliko21 Nov 30, 2024
8bfc819
fix: enhance code and naming
GloireMutaliko21 Dec 3, 2024
ade2a5a
fix: mention and subscription migration
GloireMutaliko21 Dec 3, 2024
f6d5d77
fix: coderabbit suggestions
GloireMutaliko21 Dec 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions packages/contracts/src/activity-log.model.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
ActorTypeEnum,
BaseEntityEnum,
IBasePerEntityType,
IBasePerTenantAndOrganizationEntityModel,
ID,
JsonData
Expand All @@ -10,9 +10,7 @@ import { IUser } from './user.model';
/**
* Interface representing an activity log entry.
*/
export interface IActivityLog extends IBasePerTenantAndOrganizationEntityModel {
entity: BaseEntityEnum; // Entity / Table name concerned by activity log
entityId: ID; // The ID of the element we are interacting with (a task, an organization, an employee, ...)
export interface IActivityLog extends IBasePerTenantAndOrganizationEntityModel, IBasePerEntityType {
action: ActionTypeEnum;
actorType?: ActorTypeEnum;
description?: string; // A short sentence describing the action performed. (E.g John Doe created this on 22.09.2024)
Expand Down
9 changes: 8 additions & 1 deletion packages/contracts/src/base-entity.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ export interface IBasePerTenantAndOrganizationEntityMutationInput extends Partia
organization?: Partial<IOrganization>; // Allow additional fields from IOrganization
}

// Represents a base structure for generic entities, linking their unique ID with their type.
export interface IBasePerEntityType {
entityId: ID; // Unique ID of the entity
entity: BaseEntityEnum; // The type of the entity, defined in BaseEntityEnum enumeration.
}

// Actor type defines if it's User or system performed some action
export enum ActorTypeEnum {
System = 'System', // System performed the action
Expand Down Expand Up @@ -88,5 +94,6 @@ export enum BaseEntityEnum {
Task = 'Task',
TaskView = 'TaskView',
TaskLinkedIssue = 'TaskLinkedIssue',
User = 'User'
User = 'User',
Comment = 'Comment'
}
15 changes: 7 additions & 8 deletions packages/contracts/src/comment.model.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { ActorTypeEnum, BaseEntityEnum, IBasePerTenantAndOrganizationEntityModel, ID } from './base-entity.model';
import { ActorTypeEnum, IBasePerEntityType, IBasePerTenantAndOrganizationEntityModel, ID } from './base-entity.model';
import { IUser } from './user.model';
import { IEmployee } from './employee.model';
import { IOrganizationTeam } from './organization-team.model';
import { IMentionedUserIds } from './mention.model';

export interface IComment extends IBasePerTenantAndOrganizationEntityModel {
entity: BaseEntityEnum;
entityId: ID; // Indicate the ID of entity record comment related to
export interface IComment extends IBasePerTenantAndOrganizationEntityModel, IBasePerEntityType {
comment: string;
creator?: IUser;
creatorId?: ID; // The comment's user author ID
Expand All @@ -22,15 +21,15 @@ export interface IComment extends IBasePerTenantAndOrganizationEntityModel {
replies?: IComment[];
}

export interface ICommentCreateInput {
export interface ICommentCreateInput extends IBasePerEntityType, IMentionedUserIds {
comment: string;
entity: BaseEntityEnum;
entityId: ID;
parentId?: ID;
members?: IEmployee[];
teams?: IOrganizationTeam[];
}

export interface ICommentUpdateInput extends Partial<Omit<IComment, 'entity' | 'entityId' | 'creatorId' | 'creator'>> {}
export interface ICommentUpdateInput
extends IMentionedUserIds,
Partial<Omit<IComment, 'entity' | 'entityId' | 'creatorId' | 'creator'>> {}

export interface ICommentFindInput extends Pick<IComment, 'entity' | 'entityId'> {}
7 changes: 2 additions & 5 deletions packages/contracts/src/favorite.model.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { BaseEntityEnum, IBasePerTenantAndOrganizationEntityModel, ID } from './base-entity.model';
import { IBasePerEntityType, IBasePerTenantAndOrganizationEntityModel } from './base-entity.model';
import { IEmployeeEntityInput } from './employee.model';

export interface IFavorite extends IBasePerTenantAndOrganizationEntityModel, IEmployeeEntityInput {
entity: BaseEntityEnum;
entityId: ID; // Indicate the ID of entity record marked as favorite
}
export interface IFavorite extends IBasePerTenantAndOrganizationEntityModel, IEmployeeEntityInput, IBasePerEntityType {}

export interface IFavoriteCreateInput extends IFavorite {}
4 changes: 3 additions & 1 deletion packages/contracts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export * from './shared-types';
export * from './skill-entity.model';
export * from './sms.model';
export * from './social-account.model';
export * from './subscription.model';
export * from './tag.model';
export * from './task-estimation.model';
export * from './task-linked-issue.model';
Expand Down Expand Up @@ -147,7 +148,8 @@ export {
IBaseRelationsEntityModel,
ActorTypeEnum,
JsonData,
BaseEntityEnum
BaseEntityEnum,
IBasePerEntityType
} from './base-entity.model';

export * from './proxy.model';
12 changes: 8 additions & 4 deletions packages/contracts/src/mention.model.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { BaseEntityEnum, IBasePerTenantAndOrganizationEntityModel, ID } from './base-entity.model';
import { BaseEntityEnum, IBasePerEntityType, IBasePerTenantAndOrganizationEntityModel, ID } from './base-entity.model';
import { IUser } from './user.model';

export interface IMention extends IBasePerTenantAndOrganizationEntityModel {
entityId: ID;
entity: BaseEntityEnum;
export interface IMention extends IBasePerTenantAndOrganizationEntityModel, IBasePerEntityType {
mentionedUserId: ID;
mentionedUser?: IUser;
mentionById: ID;
mentionBy?: IUser;
parentEntityId?: ID; // E.g : If the mention is in a comment, we need this for subscription and notifications purpose (It could be the task ID concerned by comment, then the user will be subscribed to that task instead of to a comment itself)
parentEntityType?: BaseEntityEnum;
}

export interface IMentionCreateInput extends Omit<IMention, 'mentionBy'> {}

export interface IMentionedUserIds {
mentionIds?: ID[];
GloireMutaliko21 marked this conversation as resolved.
Show resolved Hide resolved
}
6 changes: 2 additions & 4 deletions packages/contracts/src/resource-link.model.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { IURLMetaData } from './timesheet.model';
import { BaseEntityEnum, IBasePerTenantAndOrganizationEntityModel, ID } from './base-entity.model';
import { IBasePerEntityType, IBasePerTenantAndOrganizationEntityModel, ID } from './base-entity.model';
import { IUser } from './user.model';

export interface IResourceLink extends IBasePerTenantAndOrganizationEntityModel {
entity: BaseEntityEnum;
entityId: ID;
export interface IResourceLink extends IBasePerTenantAndOrganizationEntityModel, IBasePerEntityType {
title: string;
url: string;
creator?: IUser;
Expand Down
22 changes: 22 additions & 0 deletions packages/contracts/src/subscription.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { IBasePerEntityType, IBasePerTenantAndOrganizationEntityModel, ID } from './base-entity.model';
import { IUser } from './user.model';

export interface ISubscription extends IBasePerTenantAndOrganizationEntityModel, IBasePerEntityType {
subscriptionType: SubscriptionTypeEnum;
userId: ID;
user?: IUser;
}

export enum SubscriptionTypeEnum {
MANUAL = 'manual',
MENTION = 'mention',
ASSIGNMENT = 'assignment',
COMMENT = 'comment',
CREATED_ENTITY = 'created-entity'
}

export interface ISubscriptionCreateInput
extends Omit<ISubscription, 'user' | 'userId'>,
Partial<Pick<ISubscription, 'userId'>> {}

export interface ISubscriptionFindInput extends Partial<ISubscription> {}
3 changes: 2 additions & 1 deletion packages/contracts/src/task.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { ITaskPriority, TaskPriorityEnum } from './task-priority.model';
import { ITaskSize, TaskSizeEnum } from './task-size.model';
import { IOrganizationProjectModule } from './organization-project-module.model';
import { TaskTypeEnum } from './issue-type.model';
import { IMentionedUserIds } from './mention.model';

export interface ITask
extends IBasePerTenantAndOrganizationEntityModel,
Expand Down Expand Up @@ -75,7 +76,7 @@ export enum TaskParticipantEnum {
TEAMS = 'teams'
}

export type ITaskCreateInput = ITask;
export interface ITaskCreateInput extends ITask, IMentionedUserIds {}

export interface ITaskUpdateInput extends ITaskCreateInput {
id?: string;
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ import { ApiCallLogModule } from './api-call-log/api-call-log.module'; // Global
import { TaskViewModule } from './tasks/views/view.module';
import { ResourceLinkModule } from './resource-link/resource-link.module';
import { MentionModule } from './mention/mention.module';
import { SubscriptionModule } from './subscription/subscription.module';

const { unleashConfig } = environment;

Expand Down Expand Up @@ -453,7 +454,8 @@ if (environment.THROTTLE_ENABLED) {
ApiCallLogModule,
TaskViewModule,
ResourceLinkModule,
MentionModule // Task views Module
MentionModule,
SubscriptionModule // Task views Module
],
controllers: [AppController],
providers: [
Expand Down
51 changes: 42 additions & 9 deletions packages/core/src/comment/comment.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { BadRequestException, Injectable, NotFoundException } from '@nestjs/comm
import { UpdateResult } from 'typeorm';
import { TenantAwareCrudService } from './../core/crud';
import { RequestContext } from '../core/context';
import { IComment, ICommentCreateInput, ICommentUpdateInput, ID } from '@gauzy/contracts';
import { BaseEntityEnum, IComment, ICommentCreateInput, ICommentUpdateInput, ID } from '@gauzy/contracts';
import { UserService } from '../user/user.service';
import { MentionService } from '../mention/mention.service';
import { Comment } from './comment.entity';
import { TypeOrmCommentRepository } from './repository/type-orm.comment.repository';
import { MikroOrmCommentRepository } from './repository/mikro-orm-comment.repository';
Expand All @@ -13,7 +14,8 @@ export class CommentService extends TenantAwareCrudService<Comment> {
constructor(
readonly typeOrmCommentRepository: TypeOrmCommentRepository,
readonly mikroOrmCommentRepository: MikroOrmCommentRepository,
private readonly userService: UserService
private readonly userService: UserService,
private readonly mentionService: MentionService
) {
super(typeOrmCommentRepository, mikroOrmCommentRepository);
}
Expand All @@ -28,20 +30,37 @@ export class CommentService extends TenantAwareCrudService<Comment> {
try {
const userId = RequestContext.currentUserId();
const tenantId = RequestContext.currentTenantId();
const { ...entity } = input;
const { mentionIds = [], ...data } = input;
GloireMutaliko21 marked this conversation as resolved.
Show resolved Hide resolved

// Employee existence validation
const user = await this.userService.findOneByIdString(userId);
if (!user) {
throw new NotFoundException('User not found');
}

// return created comment
return await super.create({
...entity,
// create comment
const comment = await super.create({
...data,
tenantId,
creatorId: user.id
});

// Apply mentions if needed
await Promise.all(
mentionIds.map((mentionedUserId) =>
this.mentionService.publishMention({
entity: BaseEntityEnum.Comment,
entityId: comment.id,
mentionedUserId,
mentionById: user.id,
parentEntityId: comment.entityId,
parentEntityType: comment.entity
})
)
);

// Return created Comment
return comment;
} catch (error) {
console.log(error); // Debug Logging
throw new BadRequestException('Comment post failed', error);
Expand All @@ -50,12 +69,15 @@ export class CommentService extends TenantAwareCrudService<Comment> {

/**
* @description Update comment - Note
* @param {ICommentUpdateInput} input - Data to update comment
* @returns A promise that resolves to the updated comment OR Update result
* @param id - The comment ID to be updated.
* @param {ICommentUpdateInput} input - Data to update comment.
* @returns A promise that resolves to the updated comment OR Update result.
* @memberof CommentService
*/
async update(id: ID, input: ICommentUpdateInput): Promise<IComment | UpdateResult> {
try {
const { mentionIds = [] } = input;

const userId = RequestContext.currentUserId();
const comment = await this.findOneByOptions({
where: {
Expand All @@ -68,10 +90,21 @@ export class CommentService extends TenantAwareCrudService<Comment> {
throw new BadRequestException('Comment not found');
}

return await super.create({
const updatedComment = await super.create({
...input,
id
});

GloireMutaliko21 marked this conversation as resolved.
Show resolved Hide resolved
// Synchronize mentions
await this.mentionService.updateEntityMentions(
BaseEntityEnum.Comment,
id,
mentionIds,
updatedComment.entityId,
updatedComment.entity
);

return updatedComment;
} catch (error) {
console.log(error); // Debug Logging
throw new BadRequestException('Comment update failed', error);
Expand Down
8 changes: 6 additions & 2 deletions packages/core/src/comment/dto/create-comment.dto.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { IntersectionType, OmitType } from '@nestjs/swagger';
import { TenantOrganizationBaseDTO } from './../../core/dto';
import { ICommentCreateInput } from '@gauzy/contracts';
import { TenantOrganizationBaseDTO } from './../../core/dto';
import { MentionedUserIdsDTO } from '../../mention/dto';
import { Comment } from '../comment.entity';

/**
* Create Comment data validation request DTO
*/
export class CreateCommentDTO
extends IntersectionType(TenantOrganizationBaseDTO, OmitType(Comment, ['creatorId', 'creator']))
extends IntersectionType(
TenantOrganizationBaseDTO,
IntersectionType(OmitType(Comment, ['creatorId', 'creator']), MentionedUserIdsDTO)
)
implements ICommentCreateInput {}
2 changes: 1 addition & 1 deletion packages/core/src/comment/dto/update-comment.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ICommentUpdateInput } from '@gauzy/contracts';
import { CreateCommentDTO } from './create-comment.dto';

/**
* Create Comment data validation request DTO
* Update Comment data validation request DTO
*/
export class UpdateCommentDTO
extends PartialType(OmitType(CreateCommentDTO, ['entity', 'entityId']))
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/core/entities/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ import {
Screenshot,
Skill,
SocialAccount,
Subscription,
Tag,
Task,
TaskEstimation,
Expand Down Expand Up @@ -275,6 +276,7 @@ export const coreEntities = [
Screenshot,
Skill,
SocialAccount,
Subscription,
Tag,
Task,
TaskEstimation,
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/core/entities/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ export * from '../../resource-link/resource-link.entity';
export * from '../../role-permission/role-permission.entity';
export * from '../../role/role.entity';
export * from '../../skills/skill.entity';
export * from '../../subscription/subscription.entity';
export * from '../../tags/tag.entity';
export * from '../../tasks/daily-plan/daily-plan.entity';
export * from '../../tasks/estimation/task-estimation.entity';
Expand Down
Loading
Loading