Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 15 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,18 @@ DATABASE_URL=file:./dev.db
JWT_SECRET=your-super-secret-jwt-key-change-in-production
# JWT token expiration time (e.g., '7d', '24h', '60m')
JWT_EXPIRATION=7d

# ==============================================
# Notification Service Configuration
# ==============================================
# SMTP configuration for email delivery
SMTP_HOST=localhost
SMTP_PORT=587
SMTP_USER=
SMTP_PASS=
SMTP_FROM=noreply@truthbounty.com

# Notification queue configuration
NOTIFICATION_QUEUE_DELAY=0
NOTIFICATION_MAX_RETRIES=5
NOTIFICATION_RETRY_DELAY=2000
69 changes: 69 additions & 0 deletions src/notifications/dto/create-notification.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import {
IsString,
IsEnum,
IsOptional,
IsObject,
IsArray,
IsDateString,
IsUUID,
} from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import {
NotificationType,
DeliveryChannel,
NotificationPriority,
} from '../enums/notification-type.enum';

export class CreateNotificationDto {
@ApiProperty({ enum: NotificationType })
@IsEnum(NotificationType)
type: NotificationType;

@ApiProperty()
@IsUUID()
userId: string;

@ApiPropertyOptional()
@IsString()
@IsOptional()
walletAddress?: string;

@ApiProperty()
@IsString()
title: string;

@ApiProperty()
@IsString()
body: string;

@ApiPropertyOptional()
@IsObject()
@IsOptional()
data?: Record<string, any>;

@ApiPropertyOptional({ enum: NotificationPriority })
@IsEnum(NotificationPriority)
@IsOptional()
priority?: NotificationPriority;

@ApiPropertyOptional({ enum: DeliveryChannel, isArray: true })
@IsArray()
@IsEnum(DeliveryChannel, { each: true })
@IsOptional()
channels?: DeliveryChannel[];

@ApiPropertyOptional()
@IsDateString()
@IsOptional()
scheduledAt?: string;

@ApiPropertyOptional()
@IsString()
@IsOptional()
templateName?: string;

@ApiPropertyOptional()
@IsObject()
@IsOptional()
templateVariables?: Record<string, string>;
}
83 changes: 83 additions & 0 deletions src/notifications/dto/notification-response.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import {
NotificationType,
NotificationPriority,
DeliveryChannel,
DeliveryStatus,
} from '../enums/notification-type.enum';

export class DeliveryResponseDto {
@ApiProperty()
id: string;

@ApiProperty({ enum: DeliveryChannel })
channel: DeliveryChannel;

@ApiProperty({ enum: DeliveryStatus })
status: DeliveryStatus;

@ApiPropertyOptional()
destination?: string;

@ApiProperty()
retryCount: number;

@ApiPropertyOptional()
failureReason?: string;

@ApiPropertyOptional()
sentAt?: Date;

@ApiPropertyOptional()
deliveredAt?: Date;

@ApiProperty()
createdAt: Date;
}

export class NotificationResponseDto {
@ApiProperty()
id: string;

@ApiProperty()
userId: string;

@ApiPropertyOptional()
walletAddress?: string;

@ApiProperty({ enum: NotificationType })
type: NotificationType;

@ApiProperty()
title: string;

@ApiProperty()
body: string;

@ApiPropertyOptional()
data?: Record<string, any>;

@ApiProperty({ enum: NotificationPriority })
priority: NotificationPriority;

@ApiProperty()
read: boolean;

@ApiPropertyOptional()
readAt?: Date;

@ApiProperty()
status: string;

@ApiPropertyOptional()
scheduledAt?: Date;

@ApiPropertyOptional()
sentAt?: Date;

@ApiProperty()
createdAt: Date;

@ApiPropertyOptional({ type: [DeliveryResponseDto] })
deliveries?: DeliveryResponseDto[];
}
42 changes: 42 additions & 0 deletions src/notifications/dto/query-notifications.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { IsOptional, IsBoolean, IsEnum, IsString, IsInt, Min, Max } from 'class-validator';
import { ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { NotificationType, DeliveryChannel } from '../enums/notification-type.enum';

export class QueryNotificationsDto {
@ApiPropertyOptional()
@IsOptional()
@IsBoolean()
@Type(() => Boolean)
read?: boolean;

@ApiPropertyOptional({ enum: NotificationType })
@IsOptional()
@IsEnum(NotificationType)
type?: NotificationType;

@ApiPropertyOptional({ enum: DeliveryChannel })
@IsOptional()
@IsEnum(DeliveryChannel)
channel?: DeliveryChannel;

@ApiPropertyOptional()
@IsOptional()
@IsString()
status?: string;

@ApiPropertyOptional()
@IsOptional()
@IsInt()
@Min(1)
@Max(100)
@Type(() => Number)
limit?: number;

@ApiPropertyOptional()
@IsOptional()
@IsInt()
@Min(0)
@Type(() => Number)
offset?: number;
}
79 changes: 79 additions & 0 deletions src/notifications/dto/update-notification-preferences.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import {
IsString,
IsEnum,
IsOptional,
IsArray,
IsBoolean,
IsEmail,
IsUrl,
} from 'class-validator';
import { ApiPropertyOptional } from '@nestjs/swagger';
import {
DeliveryChannel,
NotificationFrequency,
} from '../enums/notification-type.enum';

export class UpdateNotificationPreferencesDto {
@ApiPropertyOptional({ enum: DeliveryChannel, isArray: true })
@IsArray()
@IsEnum(DeliveryChannel, { each: true })
@IsOptional()
enabledChannels?: DeliveryChannel[];

@ApiPropertyOptional({ enum: NotificationFrequency })
@IsEnum(NotificationFrequency)
@IsOptional()
frequency?: NotificationFrequency;

@ApiPropertyOptional()
@IsString()
@IsOptional()
quietHoursStart?: string;

@ApiPropertyOptional()
@IsString()
@IsOptional()
quietHoursEnd?: string;

@ApiPropertyOptional({ isArray: true })
@IsArray()
@IsString({ each: true })
@IsOptional()
subscribedCategories?: string[];

@ApiPropertyOptional({ isArray: true })
@IsArray()
@IsString({ each: true })
@IsOptional()
unsubscribedCategories?: string[];

@ApiPropertyOptional()
@IsBoolean()
@IsOptional()
digestEnabled?: boolean;

@ApiPropertyOptional({ enum: NotificationFrequency })
@IsEnum(NotificationFrequency)
@IsOptional()
digestFrequency?: NotificationFrequency;

@ApiPropertyOptional()
@IsEmail()
@IsOptional()
emailAddress?: string;

@ApiPropertyOptional()
@IsUrl()
@IsOptional()
webhookUrl?: string;

@ApiPropertyOptional()
@IsString()
@IsOptional()
pushToken?: string;

@ApiPropertyOptional()
@IsBoolean()
@IsOptional()
notificationsEnabled?: boolean;
}
68 changes: 68 additions & 0 deletions src/notifications/entities/notification-delivery.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
Index,
ManyToOne,
JoinColumn,
} from 'typeorm';
import { Notification } from './notification.entity';
import { DeliveryChannel, DeliveryStatus } from '../enums/notification-type.enum';

@Entity('notification_deliveries')
@Index(['notificationId'])
@Index(['channel'])
@Index(['status'])
@Index(['createdAt'])
export class NotificationDelivery {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column()
notificationId: string;

@ManyToOne(() => Notification, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'notificationId' })
notification: Notification;

@Column({ type: 'varchar', enum: DeliveryChannel })
channel: DeliveryChannel;

@Column({ type: 'varchar', enum: DeliveryStatus, default: DeliveryStatus.PENDING })
status: DeliveryStatus;

@Column({ type: 'text', nullable: true })
destination: string | null;

@Column({ type: 'int', default: 0 })
retryCount: number;

@Column({ type: 'int', default: 0 })
maxRetries: number;

@Column({ type: 'text', nullable: true })
failureReason: string | null;

@Column({ type: 'json', nullable: true })
responseData: Record<string, any>;

@Column({ type: 'datetime', nullable: true })
queuedAt: Date;

@Column({ type: 'datetime', nullable: true })
sentAt: Date;

@Column({ type: 'datetime', nullable: true })
deliveredAt: Date;

@Column({ type: 'datetime', nullable: true })
lastRetryAt: Date;

@CreateDateColumn()
createdAt: Date;

@UpdateDateColumn()
updatedAt: Date;
}
Loading
Loading