Skip to content
Merged

Dev #230

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
104 commits
Select commit Hold shift + click to select a range
739b398
fix(search): remove irrelevant posts
Alyaa242 Dec 8, 2025
c2fed33
Fix/notifications (#150)
MoBahgat010 Dec 10, 2025
c8453db
Fix/notifications (#155)
MoBahgat010 Dec 10, 2025
a8eca65
feat(search): add trends to search
Alyaa242 Dec 10, 2025
b5c00f3
fix(search): add is_bookmarked to search response
Alyaa242 Dec 10, 2025
30822b1
fix(search): fix quotes parent tweets
Alyaa242 Dec 10, 2025
fa4f938
fix(search): fix updating quotes and replies bg job
Alyaa242 Dec 10, 2025
08efd86
feat(search): mention suggestions
Alyaa242 Dec 10, 2025
121a748
Fix/notifications (#156)
MoBahgat010 Dec 10, 2025
10b7900
refactor(search): remove unnecessary user index logic
Alyaa242 Dec 10, 2025
f6f4036
Fix/notifications (#157)
MoBahgat010 Dec 10, 2025
4022ad5
Fix/notifications (#158)
MoBahgat010 Dec 10, 2025
70ae42a
Fix/notifications (#159)
MoBahgat010 Dec 10, 2025
1b7b2bc
Fix/notifications (#160)
MoBahgat010 Dec 10, 2025
ea754be
fix(search): remove trigram user indexes
Alyaa242 Dec 10, 2025
c672a2a
fix(trends): use built in function for hashtag extraction
AmiraKhalid04 Dec 10, 2025
568a7aa
fix(hashtags): convert hashtag names to lowercase
AmiraKhalid04 Dec 10, 2025
5486b7c
refactor(search): organize posts search functionalities
Alyaa242 Dec 10, 2025
ed823e5
refactor(search): organize users search functionalities
Alyaa242 Dec 11, 2025
5120430
fix(trends): adjust candidates TTL
AmiraKhalid04 Dec 11, 2025
6246061
Fix/notifications (#163)
MoBahgat010 Dec 11, 2025
5994a10
refactor(trends): remove debugging params
AmiraKhalid04 Dec 11, 2025
6ded9c6
fix(trends): edit hashtag regex
AmiraKhalid04 Dec 11, 2025
2576835
refactor(trends): move cron expression to queue constants file
AmiraKhalid04 Dec 11, 2025
2a13469
Merge branch 'dev' into feat/trends-v3
shady-2004 Dec 11, 2025
acb02b6
fix(dependencies): resolve package lock error
AmiraKhalid04 Dec 11, 2025
7837c2b
refactor(trends): use built in cron expression constants
AmiraKhalid04 Dec 11, 2025
c6097c1
test(trends): fix unit tests
AmiraKhalid04 Dec 11, 2025
5393689
fix(trends): use built in function for hashtag extraction (#162)
AmiraKhalid04 Dec 11, 2025
cd8a70c
fix(trends): make category case insensitive
AmiraKhalid04 Dec 11, 2025
1d27262
Feat/trends v3 (#165)
AmiraKhalid04 Dec 11, 2025
5f798e6
fix(search): fix boosting scores
Alyaa242 Dec 11, 2025
194a601
fix(search): fix arabic queries
Alyaa242 Dec 11, 2025
ab2f2d1
fix(search): fix arabic hashtags
Alyaa242 Dec 11, 2025
cf2f75f
fix(notifications): reply original tweet data (#166)
MoBahgat010 Dec 11, 2025
6854884
Fix/notification response (#167)
MoBahgat010 Dec 11, 2025
2366317
test(search): search users, suggestions unit tests
Alyaa242 Dec 11, 2025
bdef135
fix(search): fix username and media filters
Alyaa242 Dec 11, 2025
3b01051
test(search): update search posts unit tests
Alyaa242 Dec 11, 2025
0c5187b
feat(trends): add fake trends seed
AmiraKhalid04 Dec 11, 2025
a82e8e7
test(trends): add unit tests
AmiraKhalid04 Dec 11, 2025
a0fee77
Merge branch 'dev' into feat/trends-v3
AmiraKhalid04 Dec 11, 2025
f1378b9
test(trends): fix unit tests
AmiraKhalid04 Dec 11, 2025
13e7da6
fix(search): hashtags and parents bugs
MarioRaafat Dec 12, 2025
fb519d0
Fix/notification response (#169)
MoBahgat010 Dec 12, 2025
8d471fa
Fix/notification response (#170)
MoBahgat010 Dec 12, 2025
0c99073
Fix/notification response (#171)
MoBahgat010 Dec 12, 2025
d302de0
feat(trends): fake ones with cron v3
MarioRaafat Dec 12, 2025
160611b
Fix/notification response (#172)
MoBahgat010 Dec 12, 2025
a029416
feat(explore): v2
MarioRaafat Dec 12, 2025
76c46b1
fix(search): return parent user interactions
Alyaa242 Dec 12, 2025
6aa2808
Fix/notification response (#176)
MoBahgat010 Dec 12, 2025
bf2ef28
Fix/notification response (#177)
MoBahgat010 Dec 12, 2025
c89a64b
Fix/notification response (#180)
MoBahgat010 Dec 12, 2025
ffecf7a
Fix/notification response (#181)
MoBahgat010 Dec 12, 2025
4aeee9e
Fix/notification response (#182)
MoBahgat010 Dec 12, 2025
1d18e44
Fix/notification response (#183)
MoBahgat010 Dec 12, 2025
f5f90f3
feat(explore): check for tweets already in redis (#184)
shady-2004 Dec 12, 2025
b0711d7
feat(tweet-summary): delete summary on on tweet update + fix lock (#179)
shady-2004 Dec 12, 2025
3cef382
Feat/explore v2 (#185)
shady-2004 Dec 13, 2025
1412760
fix(tweet): view counter + delete + counters + type + clean service
MarioRaafat Dec 13, 2025
8698656
Fix/notification response (#188)
MoBahgat010 Dec 13, 2025
3f83172
fix(bookmarks): is_bookmarked
shady-2004 Dec 13, 2025
a9fbe72
fix(search): fix minimum should match
Alyaa242 Dec 13, 2025
c33d9ee
fix(trends): fix category condition (#190)
AmiraKhalid04 Dec 13, 2025
528a53a
fix(profile): add reposted_by (#191)
Alyaa242 Dec 13, 2025
550b4c1
fix(clean-job): remove duplicated decrement code (#192)
AmiraKhalid04 Dec 13, 2025
30d894e
Fix/notification response (#193)
MoBahgat010 Dec 13, 2025
716c75d
Fix/profile v4 (#194)
Alyaa242 Dec 13, 2025
035ea10
Fix/hashtag count (#187)
AmiraKhalid04 Dec 13, 2025
ba622e1
fix(notifications): ya rab el 5alaaaaaas (#196)
MoBahgat010 Dec 13, 2025
44a8ecf
fix(profile): fix parent tweet for quotes and replies (#195)
Alyaa242 Dec 13, 2025
a0bf455
fix(notifications): er7mny b2a ya saleh (#197)
MoBahgat010 Dec 13, 2025
c1e5e0a
fix(trends): remove hashtags with count zero (#198)
AmiraKhalid04 Dec 13, 2025
9b464c4
Feat/fake trends (#200)
AmiraKhalid04 Dec 14, 2025
cafbd90
fix(explore): clear endpoint (#202)
shady-2004 Dec 14, 2025
f590ff6
fix(timeline): fix pagination by id in for you
AmiraKhalid04 Dec 15, 2025
ad6394d
Fix/search v4 (#201)
Alyaa242 Dec 15, 2025
cc2dcf6
Feat/fake trends v2 (#203)
AmiraKhalid04 Dec 15, 2025
6a86abe
Feat/fake trends v2 (#204)
AmiraKhalid04 Dec 15, 2025
f2ce18a
fix(timeline): add conversation user id and parent user id to view (#…
AmiraKhalid04 Dec 15, 2025
d9518aa
test(sonar): setup sonar (#206)
shady-2004 Dec 15, 2025
94ef2a8
fix(auth): fix access tokens on delete account (#207)
Alyaa242 Dec 15, 2025
6b98d62
fix(tweets): fix es delete tweets bg job (#208)
Alyaa242 Dec 15, 2025
20dce66
Fix/search v5 (#209)
Alyaa242 Dec 15, 2025
0bbdce4
Fix/search v5 (#210)
Alyaa242 Dec 15, 2025
5cf018d
fix(chat): fix fk constraints (#213)
AmiraKhalid04 Dec 15, 2025
67820a6
Hotfix/notification unit tests (#211)
MoBahgat010 Dec 15, 2025
bb48a94
Test/search (#215)
Alyaa242 Dec 15, 2025
183390b
fix(trend): change trend to be last 24 hrs just for testing purpose (…
AmiraKhalid04 Dec 15, 2025
5722def
Hotfix/notification unit tests (#218)
MoBahgat010 Dec 15, 2025
43e47de
Test/trend (#219)
AmiraKhalid04 Dec 15, 2025
217a936
test(user): add user test cases (#220)
Alyaa242 Dec 15, 2025
c0ff433
Test/tweets (#212)
shady-2004 Dec 15, 2025
a616324
Test/trend (#221)
AmiraKhalid04 Dec 15, 2025
d7560b9
Test/tweets (#222)
shady-2004 Dec 15, 2025
a17d4ba
fix(migrations): clear migrations (#223)
shady-2004 Dec 15, 2025
ab8d3c5
fix(timeline): fix refresh bug
MarioRaafat Dec 15, 2025
02f690b
Hotfix/migrations (#224)
shady-2004 Dec 15, 2025
49f1d6e
fix(search): set suggestions max length (#225)
Alyaa242 Dec 16, 2025
817a1ca
fix(cron): update cron for testing purposes (#226)
AmiraKhalid04 Dec 16, 2025
3eae24c
fix(ci): remove migrations script (#227)
AmiraKhalid04 Dec 16, 2025
0a72e64
fix(timeline): remove reposts from blocked users (#229)
AmiraKhalid04 Dec 16, 2025
374eac4
Hotfix/who to follow (#228)
Alyaa242 Dec 16, 2025
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
352 changes: 176 additions & 176 deletions .github/workflows/deploy-dev.yml

Large diffs are not rendered by default.

Empty file added .scannerwork/.sonar_lock
Empty file.
6 changes: 6 additions & 0 deletions .scannerwork/report-task.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
projectKey=x-backend-replica
serverUrl=http://localhost:9000
serverVersion=25.12.0.117093
dashboardUrl=http://localhost:9000/dashboard?id=x-backend-replica
ceTaskId=7168e6a5-41aa-42a6-a2a2-124c7e9216b7
ceTaskUrl=http://localhost:9000/api/ce/task?id=7168e6a5-41aa-42a6-a2a2-124c7e9216b7
Binary file modified dump.rdb
Binary file not shown.
2,325 changes: 698 additions & 1,627 deletions package-lock.json

Large diffs are not rendered by default.

16 changes: 12 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
"seed": "ts-node -r tsconfig-paths/register src/databases/seeds/scripts/seed.ts",
"es:seed": "ts-node -r tsconfig-paths/register src/elasticsearch/scripts/es-seed.ts",
"es:reset": "ts-node -r tsconfig-paths/register src/elasticsearch/scripts/es-reset.ts",
"generate-encryption-key": "node -r ts-node/register src/shared/services/encryption/generate-encryption-key.ts"
"generate-encryption-key": "node -r ts-node/register src/shared/services/encryption/generate-encryption-key.ts",
"sonar": "npm run test:cov && sonar-scanner"
},
"lint-staged": {
"*.ts": [
Expand All @@ -55,8 +56,8 @@
"@nestjs/mongoose": "^11.0.3",
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.0.1",
"@nestjs/schedule": "^6.0.1",
"@nestjs/platform-socket.io": "^11.1.9",
"@nestjs/schedule": "^6.0.1",
"@nestjs/swagger": "^11.2.0",
"@nestjs/typeorm": "^11.0.0",
"@nestjs/websockets": "^11.1.9",
Expand All @@ -70,7 +71,7 @@
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
"cookie-parser": "^1.4.7",
"firebase-admin": "^13.6.0",
"expo-server-sdk": "^4.0.0",
"fluent-ffmpeg": "^2.1.3",
"google-auth-library": "^10.4.1",
"groq-sdk": "^0.37.0",
Expand All @@ -95,6 +96,7 @@
"socket.io": "^4.8.1",
"swagger-ui-express": "^5.0.1",
"tunnel-ssh": "^5.2.0",
"twitter-text": "^3.1.0",
"typeorm": "^0.3.26",
"xlsx": "^0.18.5"
},
Expand All @@ -115,6 +117,7 @@
"@types/passport-github2": "^1.2.9",
"@types/supertest": "^6.0.2",
"@types/tunnel-ssh": "^5.0.4",
"@types/twitter-text": "^3.1.10",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.2",
Expand All @@ -123,6 +126,7 @@
"jest": "^30.0.0",
"lint-staged": "^16.2.4",
"prettier": "^3.4.2",
"sonarqube-scanner": "^4.3.2",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",
Expand Down Expand Up @@ -164,6 +168,10 @@
"!**/enums/**",
"!**/migrations/**",
"!**/seeds/**",
"!**/*.module.ts",
"!**/*.config.ts",
"!**/config/**",
"!**/constants/**",
"!main.ts",
"!**/*.spec.ts",
"!**/*-key.ts",
Expand All @@ -175,4 +183,4 @@
"^src/(.*)$": "<rootDir>/$1"
}
}
}
}
1 change: 1 addition & 0 deletions simple-socket-test.html
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,7 @@ <h2>Event Logs</h2>
const message = {
content: content,
message_type: messageType,
image_url: "https://yapperdev.blob.core.windows.net/profile-images/test-team-1765575149782-standard.jpg",
};

if (messageType === "reply" && replyToId) {
Expand Down
27 changes: 27 additions & 0 deletions sonar-project.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# SonarQube Configuration
sonar.projectKey=x-backend-replica
sonar.projectName=X Backend Replica
sonar.projectVersion=1.0

# Source code location
sonar.sources=src
sonar.tests=src
sonar.test.inclusions=**/*.spec.ts

# Exclude files from analysis
sonar.exclusions=**/node_modules/**,**/dist/**,**/coverage/**,**/*.spec.ts,**/migrations/**,**/seeds/**,**/databases/**,**/*.swagger.ts

# Exclude infrastructure code from coverage (DTOs, Entities, Modules, Configs)
sonar.coverage.exclusions=**/*.dto.ts,**/*.entity.ts,**/*.module.ts,**/config/**,**/migrations/**,**/seeds/**,**/databases/**,**/*.config.ts,**/constants/**,**/*.interface.ts,**/*.enum.ts

# TypeScript specific settings
sonar.typescript.lcov.reportPaths=coverage/lcov.info

# Encoding
sonar.sourceEncoding=UTF-8

# SonarQube server URL (default local)
sonar.host.url=http://localhost:9000

# Authentication (you'll need to generate a token after SonarQube starts)
sonar.login=squ_3ee91cb3e490cdd73f98c3640cd764b17b18b912
2 changes: 1 addition & 1 deletion src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { Tweet } from './tweets/entities/tweet.entity';
import { UserFollows } from './user/entities/user-follows.entity';
import { TweetLike } from './tweets/entities/tweet-like.entity';
import { TweetReply } from './tweets/entities/tweet-reply.entity';
import { FcmModule } from './fcm/fcm.module';
import { FcmModule } from './expo/expo.module';
import { TrendModule } from './trend/trend.module';
import { ScheduleModule } from '@nestjs/schedule';

Expand Down
1 change: 0 additions & 1 deletion src/app.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,6 @@ export class AppService {
let replies_count = 0;
for (const reply_data of TestDataConstants.TEST_REPLIES) {
const replier = created_users[reply_data.replier_index];
const original_user = created_users[reply_data.original_user_index];
const original_tweet =
all_tweets[reply_data.original_user_index][reply_data.original_tweet_index];

Expand Down
9 changes: 6 additions & 3 deletions src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,8 @@ export class AuthController {
@ApiResponse(google_oauth_swagger.responses.success)
@ApiResponse(google_oauth_swagger.responses.InternalServerError)
@Get('google')
googleLogin() {}
// eslint-disable-next-line @typescript-eslint/no-empty-function
googleLogin() {} // Intentionally empty - GoogleAuthGuard handles the OAuth redirect

@ApiOperation(google_mobile_swagger.operation)
@ApiBody({ type: MobileGoogleAuthDto })
Expand Down Expand Up @@ -505,7 +506,8 @@ export class AuthController {
@ApiResponse(facebook_oauth_swagger.responses.success)
@ApiResponse(facebook_oauth_swagger.responses.InternalServerError)
@Get('facebook')
facebookLogin() {}
// eslint-disable-next-line @typescript-eslint/no-empty-function
facebookLogin() {} // Intentionally empty - FacebookAuthGuard handles the OAuth redirect

@UseGuards(FacebookAuthGuard)
@ApiOperation(facebook_callback_swagger.operation)
Expand Down Expand Up @@ -561,7 +563,8 @@ export class AuthController {
@ApiResponse(github_oauth_swagger.responses.success)
@ApiResponse(github_oauth_swagger.responses.InternalServerError)
@Get('github')
async githubLogin() {}
// eslint-disable-next-line @typescript-eslint/no-empty-function
async githubLogin() {} // Intentionally empty - GitHubAuthGuard handles the OAuth redirect

@ApiOperation(github_mobile_swagger.operation)
@ApiBody({ type: MobileGitHubAuthDto })
Expand Down
16 changes: 8 additions & 8 deletions src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,11 @@ export class AuthService {
const { name, birth_date, email, captcha_token } = dto;

// Verify CAPTCHA first
// try {
// await this.captcha_service.validateCaptcha(captcha_token);
// } catch (error) {
// throw new BadRequestException(ERROR_MESSAGES.CAPTCHA_VERIFICATION_FAILED);
// }
try {
await this.captcha_service.validateCaptcha(captcha_token);
} catch (error) {
throw new BadRequestException(ERROR_MESSAGES.CAPTCHA_VERIFICATION_FAILED);
}

const existing_user = await this.user_repository.findByEmail(email);
if (existing_user) {
Expand Down Expand Up @@ -343,7 +343,7 @@ export class AuthService {
}

async sendResetPasswordEmail(identifier: string) {
const { identifier_type, user_id } = await this.checkIdentifier(identifier);
const { user_id } = await this.checkIdentifier(identifier);
const user = await this.user_repository.findById(user_id);
if (!user) {
throw new NotFoundException(ERROR_MESSAGES.USER_NOT_FOUND);
Expand Down Expand Up @@ -371,7 +371,7 @@ export class AuthService {
}

async verifyResetPasswordOtp(identifier: string, token: string) {
const { identifier_type, user_id } = await this.checkIdentifier(identifier);
const { user_id } = await this.checkIdentifier(identifier);
const is_valid = await this.verification_service.validateOtp(user_id, token, 'password');

if (!is_valid) {
Expand Down Expand Up @@ -412,7 +412,7 @@ export class AuthService {
}

async resetPassword(identifier: string, new_password: string, token: string) {
const { identifier_type, user_id } = await this.checkIdentifier(identifier);
const { user_id } = await this.checkIdentifier(identifier);
const token_data = await this.verification_service.validatePasswordResetToken(token);

if (!token_data) {
Expand Down
30 changes: 28 additions & 2 deletions src/auth/guards/jwt.guard.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,36 @@
import { ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { RedisService } from 'src/redis/redis.service';

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
override canActivate(context: ExecutionContext) {
return super.canActivate(context);
constructor(private readonly redis_service: RedisService) {
super();
}

override async canActivate(context: ExecutionContext) {
const can_activate = await super.canActivate(context);

if (!can_activate) {
return false;
}

const request = context.switchToHttp().getRequest();
const user = request.user;

let is_deleted = false;
if (user) {
try {
is_deleted = await this.redis_service.exists(`deleted_user:${user.id}`);
} catch (error) {
console.warn('Failed to check deleted user in Redis:', error.message);
}
if (is_deleted) {
throw new UnauthorizedException('User account has been deleted');
}
}

return true;
}

override handleRequest(err: any, user: any, info: any) {
Expand Down
2 changes: 1 addition & 1 deletion src/auth/guards/ws-jwt.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface IAuthenticatedSocket extends Socket {

@Injectable()
export class WsJwtGuard implements CanActivate {
constructor(private jwt_service: JwtService) {}
constructor(private readonly jwt_service: JwtService) {}

async canActivate(context: ExecutionContext): Promise<boolean> {
try {
Expand Down
4 changes: 2 additions & 2 deletions src/auth/strategies/facebook.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { FacebookLoginDTO } from '../dto/facebook-login.dto';
@Injectable()
export class FacebookStrategy extends PassportStrategy(Strategy) {
constructor(
private config_service: ConfigService,
private auth_service: AuthService
private readonly config_service: ConfigService,
private readonly auth_service: AuthService
) {
super({
clientID: config_service.get('FACEBOOK_CLIENT_ID') || '',
Expand Down
4 changes: 2 additions & 2 deletions src/auth/strategies/github.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { GitHubUserDto } from '../dto/github-user.dto';
@Injectable()
export class GitHubStrategy extends PassportStrategy(Strategy, 'github') {
constructor(
private config_service: ConfigService,
private auth_service: AuthService
private readonly config_service: ConfigService,
private readonly auth_service: AuthService
) {
super({
clientID: config_service.get('GITHUB_CLIENT_ID') || '',
Expand Down
4 changes: 2 additions & 2 deletions src/auth/strategies/google.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { AuthService } from '../auth.service';
@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy) {
constructor(
private config_service: ConfigService,
private auth_service: AuthService
private readonly config_service: ConfigService,
private readonly auth_service: AuthService
) {
super({
clientID: config_service.get('GOOGLE_CLIENT_ID') || '',
Expand Down
4 changes: 2 additions & 2 deletions src/auth/username.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ export class UsernameService {

private cleanName(name: string): string {
return name
.replace(/[^a-zA-Z0-9]/g, '') // Remove special characters
.replace(/\s+/g, ''); // Remove spaces
.replaceAll(/[^a-zA-Z0-9]/g, '') // Remove special characters
.replaceAll(/\s+/g, ''); // Remove spaces
}

private truncateToMaxLength(str: string): string {
Expand Down
2 changes: 1 addition & 1 deletion src/azure-storage/azure-storage.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export class AzureStorageService implements OnModuleInit {
private blob_service_client: BlobServiceClient;
private profile_image_container_name: string;

constructor(private configService: ConfigService) {}
constructor(private readonly configService: ConfigService) {}

onModuleInit() {
const connection_string = this.configService.get<string>('AZURE_STORAGE_CONNECTION_STRING');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ describe('AiSummaryProcessor', () => {
mock_tweet_summary_repository.save.mockRejectedValue(new Error('Save Error'));

await expect(processor.handleGenerateSummary(mock_job)).rejects.toThrow();
});
}, 10000);

it('should process job data correctly', async () => {
const existing_summary = {
Expand Down
41 changes: 31 additions & 10 deletions src/background-jobs/ai-summary/ai-summary.service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Injectable } from '@nestjs/common';
import { InjectQueue } from '@nestjs/bull';
import type { Queue } from 'bull';
import { BackgroundJobsService } from '../background-jobs';
import { JOB_DELAYS, JOB_NAMES, JOB_PRIORITIES, QUEUE_NAMES } from '../constants/queue.constants';
import type { GenerateTweetSummaryDto } from './ai-summary.dto';
import { QUEUE_NAMES } from '../constants/queue.constants';
import { Injectable, Logger } from '@nestjs/common';
import type { Queue } from 'bull';
import { JOB_DELAYS, JOB_NAMES, JOB_PRIORITIES } from '../constants/queue.constants';
import { GenerateTweetSummaryDto } from './ai-summary.dto';

@Injectable()
export class AiSummaryJobService extends BackgroundJobsService<GenerateTweetSummaryDto> {
Expand All @@ -19,12 +20,32 @@ export class AiSummaryJobService extends BackgroundJobsService<GenerateTweetSumm
);
}

protected getJobId(dto: GenerateTweetSummaryDto): string {
// Unique job per tweet
return `tweet-summary:${dto.tweet_id}`;
}

// Override queueJob to customize cleanup for fixed job_id
async queueGenerateSummary(dto: GenerateTweetSummaryDto) {
return this.queueJob(
dto,
JOB_PRIORITIES.MEDIUM,
JOB_DELAYS.IMMEDIATE,
'Failed to queue AI summary generation:'
);
try {
const job_id = this.getJobId(dto);

const job = await this.queue.add(this.job_name, dto, {
jobId: job_id,
priority: JOB_PRIORITIES.MEDIUM,
delay: JOB_DELAYS.IMMEDIATE,
attempts: 3,
backoff: { type: 'exponential', delay: 2000 },

// Keep job while running to enforce single-job lock
removeOnComplete: false,
removeOnFail: true,
});

return { success: true, job_id: job.id };
} catch (error) {
this.logger.error('Failed to queue AI summary generation:', error);
return { success: false, error: error.message };
}
}
}
Loading