Skip to content

Commit e8a8036

Browse files
feat(timeline): for you
* refactor(timeline): refactor following timeline query * refactor(timeline): refactor for you timeline query * refactor(timeline): remove unneeded functions * feat(timeline): add candidates source * feat(timeline): add get user interests * feat(timeline): add simple feature extractor class * feat(tweet-category): add get categories by tweet ids * feat(timeline): add basic rule based ranker * feat(following-query): refactor timeline queries * feat(timeline-queries): attach reply response to timeline query * fix(timeline-query): remove unnecessary joins * feat(reply-query): add show_more_replies boolean * fix(timeline): add template for for-you response * fix(query): attach following flags to user response dto * feat(reply-query): add parent tweet and conversation sub queries * fix(reply-query): remove deubgging code * fix(reply-query): attach missing interaction fields * refactor(tweet-response): remove unneeded fields --------- Co-authored-by: Mario Raafat <136023677+MarioRaafat@users.noreply.github.com>
1 parent a65c92a commit e8a8036

21 files changed

+1030
-338
lines changed

src/databases/data-source.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { Hashtag } from '../tweets/entities/hashtags.entity';
1111
import { UserPostsView } from '../tweets/entities/user-posts-view.entity';
1212
import { UserBlocks, UserFollows, UserMutes } from '../user/entities';
1313
import { UserInterests } from '../user/entities/user-interests.entity';
14+
import { TweetCategory } from 'src/tweets/entities/tweet-category.entity';
1415
config({ path: resolve(__dirname, '../../config/.env') });
1516
const config_service = new ConfigService();
1617

@@ -40,6 +41,7 @@ export default new DataSource({
4041
UserInterests,
4142
UserMutes,
4243
UserPostsView,
44+
TweetCategory,
4345
],
4446
migrations: ['src/migrations/*{.ts,.js}'],
4547
synchronize: false,

src/databases/postgresql.module.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ import { TypeOrmModule } from '@nestjs/typeorm';
1717
port: config_service.get<number>('POSTGRES_PORT'),
1818
synchronize: false, // Using migrations instead
1919
autoLoadEntities: true,
20-
// logging: ['query'],
21-
// logger: 'advanced-console',
20+
logging: ['query'],
21+
logger: 'advanced-console',
2222
}),
2323
}),
2424
],

src/main.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ async function bootstrap() {
1212
transform: true,
1313
transformOptions: {
1414
exposeDefaultValues: true,
15-
enableImplicitConversion: true, // should be removed
1615
},
1716
})
1817
);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { MigrationInterface, QueryRunner } from 'typeorm';
2+
3+
export class AddTweetCategories1763498108364 implements MigrationInterface {
4+
name = 'AddTweetCategories1763498108364';
5+
6+
public async up(query_runner: QueryRunner): Promise<void> {
7+
await query_runner.query(
8+
`CREATE TABLE "tweet_categories" ("tweet_id" uuid NOT NULL, "category_id" smallint NOT NULL, "percentage" integer NOT NULL DEFAULT '0', CONSTRAINT "PK_971fcf9897bf9c05a94df12b9b8" PRIMARY KEY ("tweet_id", "category_id"))`
9+
);
10+
await query_runner.query(
11+
`CREATE INDEX "IDX_CATEGORY" ON "tweet_categories" ("category_id") `
12+
);
13+
await query_runner.query(
14+
`ALTER TABLE "tweet_categories" ADD CONSTRAINT "FK_2b4ee6b36f719addb2be60efdd3" FOREIGN KEY ("category_id") REFERENCES "category"("id") ON DELETE CASCADE ON UPDATE NO ACTION`
15+
);
16+
await query_runner.query(
17+
`ALTER TABLE "tweet_categories" ADD CONSTRAINT "FK_c5786e021a586ae7fd93ee65c0e" FOREIGN KEY ("tweet_id") REFERENCES "tweets"("tweet_id") ON DELETE CASCADE ON UPDATE NO ACTION`
18+
);
19+
}
20+
21+
public async down(query_runner: QueryRunner): Promise<void> {
22+
await query_runner.query(
23+
`ALTER TABLE "tweet_categories" DROP CONSTRAINT "FK_c5786e021a586ae7fd93ee65c0e"`
24+
);
25+
await query_runner.query(
26+
`ALTER TABLE "tweet_categories" DROP CONSTRAINT "FK_2b4ee6b36f719addb2be60efdd3"`
27+
);
28+
await query_runner.query(`DROP INDEX "public"."IDX_CATEGORY"`);
29+
await query_runner.query(`DROP TABLE "tweet_categories"`);
30+
}
31+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { TweetResponseDTO } from 'src/tweets/dto/tweet-response.dto';
2+
3+
export class ScoredCandidateDTO {
4+
tweet: TweetResponseDTO;
5+
6+
// === Numeric features
7+
recency_score: number; // 0–1 (1 = just posted)
8+
relevance_score: number; // 0–100 (interests + similarity)
9+
engagement_score: number; // (likes + 3×retweets + ...)
10+
media_boost: number; // 0, 10, 15
11+
credibility_boost: number; // verified? follower ratio?
12+
diversity_penalty: number; // negative if same author/topic
13+
location_boost: number; // 0 or 20 if same country/city
14+
virality_score: number; // trending velocity
15+
16+
// source: string;
17+
// will be added
18+
author_similarity?: number;
19+
_final_score?: number;
20+
}

src/timeline/dto/timeline-pagination.dto.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ApiProperty } from '@nestjs/swagger';
2+
import { Type } from 'class-transformer';
23
import { IsBoolean, IsInt, IsOptional, IsString, Max, MaxLength, Min, MIN } from 'class-validator';
34
import { STRING_MAX_LENGTH } from 'src/constants/variables';
45

@@ -14,6 +15,7 @@ export class TimelinePaginationDto {
1415
@IsOptional()
1516
@IsInt()
1617
@Min(1)
18+
@Type(() => Number)
1719
@Max(100)
1820
limit?: number = 20;
1921

@@ -25,7 +27,7 @@ export class TimelinePaginationDto {
2527
@IsOptional()
2628
@IsString()
2729
@MaxLength(STRING_MAX_LENGTH)
28-
cursor?: string | null;
30+
cursor?: string;
2931

3032
@IsOptional()
3133
@ApiProperty({
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Injectable } from '@nestjs/common';
2+
import { TweetsRepository } from 'src/tweets/tweets.repository';
3+
import { DataSource } from 'typeorm';
4+
5+
@Injectable()
6+
export class InNetworkCandidateSource {
7+
constructor(private tweets_repository: TweetsRepository) {}
8+
9+
async getCandidates(user_id: string, limit: number = 100) {
10+
const result = await this.tweets_repository.getFollowingTweets(
11+
user_id,
12+
undefined,
13+
limit,
14+
48
15+
);
16+
17+
//TODO: Response from all candidates
18+
19+
return result.data.map((tweet) => ({
20+
...tweet,
21+
source: 'in_network',
22+
}));
23+
}
24+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { Injectable } from '@nestjs/common';
2+
import { InjectRepository } from '@nestjs/typeorm';
3+
import { Tweet } from 'src/tweets/entities';
4+
import { TweetsRepository } from 'src/tweets/tweets.repository';
5+
import { UserInterests } from 'src/user/entities/user-interests.entity';
6+
import { Repository } from 'typeorm';
7+
8+
@Injectable()
9+
export class InterestsCandidateSource {
10+
constructor(
11+
@InjectRepository(UserInterests)
12+
private user_intersets_repository: Repository<UserInterests>,
13+
private readonly tweet_repository: TweetsRepository
14+
) {}
15+
16+
async getCandidates(user_id: string, limit: number = 100) {
17+
// Get user top 10 topics
18+
19+
const user_intersets = await this.user_intersets_repository.find({
20+
where: { user_id: user_id },
21+
order: { score: 'DESC' },
22+
take: 10,
23+
select: ['category_id', 'score'],
24+
});
25+
26+
if (user_intersets.length === 0) return [];
27+
28+
// Get tweets by categories ids
29+
30+
const category_ids = user_intersets.map((interest) => interest.category_id);
31+
32+
const tweets = await this.tweet_repository.getRecentTweetsByCategoryIds(
33+
category_ids,
34+
user_id,
35+
{
36+
limit,
37+
since_hours_ago: 48,
38+
}
39+
);
40+
41+
// TODO: Sort by percentage
42+
43+
return {
44+
tweets,
45+
source: 'interests',
46+
};
47+
}
48+
}

src/timeline/services/foryou/canditate-sources/out-network-source.ts

Whitespace-only changes.

src/timeline/services/foryou/canditate-sources/trending-source.ts

Whitespace-only changes.

0 commit comments

Comments
 (0)