Skip to content

Commit 91e4ec4

Browse files
MoBahgat010shady-2004MarioRaafat
authored
refactor(tweets): refactoring tweets
* refactor(tweets): offset pagination for likes * refactor(tweets): offset pagination for reposts * refactor(tweets): offset pagination for reposts * fix(tweets): some changes * fix(tweets): some changes * fix(tweets): some changes * fix(tweets): some changes * fix(tweets): some changes * fix(tweets): some changes * fix(tweets): some changes * fix(tweets): start changes * fix(tweets): refactor tweets * fix(tweets): extra code cleaning + lent problems * fix(migrations): update schema migrations and disable auto-sync * fix(tweets): final changes * test(tweets): coverage * feat(tweets): optional guard * fix(tweets): package-lock * fix(tweets): package-lock * fix(tweets): package-lock --------- Co-authored-by: shady-2004 <shadyshiko2004@gmail.com> Co-authored-by: Mario Raafat <136023677+MarioRaafat@users.noreply.github.com>
1 parent 3bec40a commit 91e4ec4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+23065
-19234
lines changed

package-lock.json

Lines changed: 17962 additions & 17962 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 133 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -1,133 +1,133 @@
1-
{
2-
"name": "backend",
3-
"version": "0.0.1",
4-
"description": "",
5-
"author": "",
6-
"private": true,
7-
"license": "UNLICENSED",
8-
"scripts": {
9-
"build": "nest build",
10-
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
11-
"start": "nest start",
12-
"start:dev": "nest start --watch",
13-
"start:debug": "nest start --debug --watch",
14-
"start:prod": "node dist/main",
15-
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\"",
16-
"lint:fix": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
17-
"test": "jest",
18-
"test:watch": "jest --watch",
19-
"test:cov": "jest --coverage",
20-
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
21-
"test:e2e": "jest --config ./test/jest-e2e.json",
22-
"migration:generate": "typeorm-ts-node-commonjs migration:generate -d src/databases/data-source.ts",
23-
"migration:run": "typeorm-ts-node-commonjs migration:run -d src/databases/data-source.ts",
24-
"migration:revert": "typeorm-ts-node-commonjs migration:revert -d src/databases/data-source.ts",
25-
"migration:show": "typeorm-ts-node-commonjs migration:show -d src/databases/data-source.ts",
26-
"schema:log": "typeorm-ts-node-commonjs schema:log -d src/databases/data-source.ts",
27-
"prepare": "husky",
28-
"seed": "ts-node -r tsconfig-paths/register src/databases/seeds/scripts/seed.ts"
29-
},
30-
"lint-staged": {
31-
"*.ts": [
32-
"eslint --fix",
33-
"prettier --write"
34-
]
35-
},
36-
"dependencies": {
37-
"@azure/storage-blob": "^12.29.1",
38-
"@google/genai": "^1.28.0",
39-
"@nestjs-modules/ioredis": "^2.0.2",
40-
"@nestjs-modules/mailer": "^2.0.2",
41-
"@nestjs/bull": "^11.0.4",
42-
"@nestjs/azure-storage": "^4.0.0",
43-
"@nestjs/common": "^11.0.1",
44-
"@nestjs/config": "^4.0.2",
45-
"@nestjs/core": "^11.0.1",
46-
"@nestjs/jwt": "^11.0.0",
47-
"@nestjs/mapped-types": "*",
48-
"@nestjs/mongoose": "^11.0.3",
49-
"@nestjs/passport": "^11.0.5",
50-
"@nestjs/platform-express": "^11.0.1",
51-
"@nestjs/swagger": "^11.2.0",
52-
"@nestjs/typeorm": "^11.0.0",
53-
"@types/bull": "^3.15.9",
54-
"@types/multer": "^2.0.0",
55-
"amqplib": "^0.10.9",
56-
"axios": "^1.12.1",
57-
"bcrypt": "^6.0.0",
58-
"bull": "^4.16.5",
59-
"class-transformer": "^0.5.1",
60-
"class-validator": "^0.14.2",
61-
"cookie-parser": "^1.4.7",
62-
"google-auth-library": "^10.4.1",
63-
"ioredis": "^5.7.0",
64-
"mongoose": "^8.19.1",
65-
"multer": "^2.0.2",
66-
"nodemailer": "^7.0.6",
67-
"passport": "^0.7.0",
68-
"passport-facebook": "^3.0.0",
69-
"passport-github2": "^0.1.12",
70-
"passport-google-oauth20": "^2.0.0",
71-
"passport-jwt": "^4.0.1",
72-
"passport-local": "^1.0.0",
73-
"pg": "^8.16.3",
74-
"reflect-metadata": "^0.2.2",
75-
"rxjs": "^7.8.1",
76-
"swagger-ui-express": "^5.0.1",
77-
"typeorm": "^0.3.26",
78-
"xlsx": "^0.18.5"
79-
},
80-
"devDependencies": {
81-
"@eslint/eslintrc": "^3.2.0",
82-
"@eslint/js": "^9.18.0",
83-
"@nestjs/cli": "^11.0.0",
84-
"@nestjs/schematics": "^11.0.0",
85-
"@nestjs/testing": "^11.0.1",
86-
"@types/bcrypt": "^6.0.0",
87-
"@types/cookie-parser": "^1.4.9",
88-
"@types/express": "^5.0.0",
89-
"@types/jest": "^30.0.0",
90-
"@types/multer": "^2.0.0",
91-
"@types/node": "^22.10.7",
92-
"@types/nodemailer": "^7.0.1",
93-
"@types/passport-facebook": "^3.0.3",
94-
"@types/passport-github2": "^1.2.9",
95-
"@types/supertest": "^6.0.2",
96-
"eslint": "^9.18.0",
97-
"eslint-config-prettier": "^10.0.1",
98-
"eslint-plugin-prettier": "^5.2.2",
99-
"globals": "^16.0.0",
100-
"husky": "^9.1.7",
101-
"jest": "^30.0.0",
102-
"lint-staged": "^16.2.4",
103-
"prettier": "^3.4.2",
104-
"source-map-support": "^0.5.21",
105-
"supertest": "^7.0.0",
106-
"ts-jest": "^29.2.5",
107-
"ts-loader": "^9.5.2",
108-
"ts-node": "^10.9.2",
109-
"tsconfig-paths": "^4.2.0",
110-
"typescript": "^5.7.3",
111-
"typescript-eslint": "^8.20.0"
112-
},
113-
"jest": {
114-
"moduleFileExtensions": [
115-
"js",
116-
"json",
117-
"ts"
118-
],
119-
"rootDir": "src",
120-
"testRegex": ".*\\.spec\\.ts$",
121-
"transform": {
122-
"^.+\\.(t|j)s$": "ts-jest"
123-
},
124-
"collectCoverageFrom": [
125-
"**/*.(t|j)s"
126-
],
127-
"coverageDirectory": "../coverage",
128-
"testEnvironment": "node",
129-
"moduleNameMapper": {
130-
"^src/(.*)$": "<rootDir>/$1"
131-
}
132-
}
133-
}
1+
{
2+
"name": "backend",
3+
"version": "0.0.1",
4+
"description": "",
5+
"author": "",
6+
"private": true,
7+
"license": "UNLICENSED",
8+
"scripts": {
9+
"build": "nest build",
10+
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
11+
"start": "nest start",
12+
"start:dev": "nest start --watch",
13+
"start:debug": "nest start --debug --watch",
14+
"start:prod": "node dist/main",
15+
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\"",
16+
"lint:fix": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
17+
"test": "jest",
18+
"test:watch": "jest --watch",
19+
"test:cov": "jest --coverage",
20+
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
21+
"test:e2e": "jest --config ./test/jest-e2e.json",
22+
"migration:generate": "typeorm-ts-node-commonjs migration:generate -d src/databases/data-source.ts",
23+
"migration:run": "typeorm-ts-node-commonjs migration:run -d src/databases/data-source.ts",
24+
"migration:revert": "typeorm-ts-node-commonjs migration:revert -d src/databases/data-source.ts",
25+
"migration:show": "typeorm-ts-node-commonjs migration:show -d src/databases/data-source.ts",
26+
"schema:log": "typeorm-ts-node-commonjs schema:log -d src/databases/data-source.ts",
27+
"prepare": "husky",
28+
"seed": "ts-node -r tsconfig-paths/register src/databases/seeds/scripts/seed.ts"
29+
},
30+
"lint-staged": {
31+
"*.ts": [
32+
"eslint --fix",
33+
"prettier --write"
34+
]
35+
},
36+
"dependencies": {
37+
"@azure/storage-blob": "^12.29.1",
38+
"@google/genai": "^1.28.0",
39+
"@nestjs-modules/ioredis": "^2.0.2",
40+
"@nestjs-modules/mailer": "^2.0.2",
41+
"@nestjs/bull": "^11.0.4",
42+
"@nestjs/azure-storage": "^4.0.0",
43+
"@nestjs/common": "^11.0.1",
44+
"@nestjs/config": "^4.0.2",
45+
"@nestjs/core": "^11.0.1",
46+
"@nestjs/jwt": "^11.0.0",
47+
"@nestjs/mapped-types": "*",
48+
"@nestjs/mongoose": "^11.0.3",
49+
"@nestjs/passport": "^11.0.5",
50+
"@nestjs/platform-express": "^11.0.1",
51+
"@nestjs/swagger": "^11.2.0",
52+
"@nestjs/typeorm": "^11.0.0",
53+
"@types/bull": "^3.15.9",
54+
"@types/multer": "^2.0.0",
55+
"amqplib": "^0.10.9",
56+
"axios": "^1.12.1",
57+
"bcrypt": "^6.0.0",
58+
"bull": "^4.16.5",
59+
"class-transformer": "^0.5.1",
60+
"class-validator": "^0.14.2",
61+
"cookie-parser": "^1.4.7",
62+
"google-auth-library": "^10.4.1",
63+
"ioredis": "^5.7.0",
64+
"mongoose": "^8.19.1",
65+
"multer": "^2.0.2",
66+
"nodemailer": "^7.0.6",
67+
"passport": "^0.7.0",
68+
"passport-facebook": "^3.0.0",
69+
"passport-github2": "^0.1.12",
70+
"passport-google-oauth20": "^2.0.0",
71+
"passport-jwt": "^4.0.1",
72+
"passport-local": "^1.0.0",
73+
"pg": "^8.16.3",
74+
"reflect-metadata": "^0.2.2",
75+
"rxjs": "^7.8.1",
76+
"swagger-ui-express": "^5.0.1",
77+
"typeorm": "^0.3.26",
78+
"xlsx": "^0.18.5"
79+
},
80+
"devDependencies": {
81+
"@eslint/eslintrc": "^3.2.0",
82+
"@eslint/js": "^9.18.0",
83+
"@nestjs/cli": "^11.0.0",
84+
"@nestjs/schematics": "^11.0.0",
85+
"@nestjs/testing": "^11.0.1",
86+
"@types/bcrypt": "^6.0.0",
87+
"@types/cookie-parser": "^1.4.9",
88+
"@types/express": "^5.0.0",
89+
"@types/jest": "^30.0.0",
90+
"@types/multer": "^2.0.0",
91+
"@types/node": "^22.10.7",
92+
"@types/nodemailer": "^7.0.1",
93+
"@types/passport-facebook": "^3.0.3",
94+
"@types/passport-github2": "^1.2.9",
95+
"@types/supertest": "^6.0.2",
96+
"eslint": "^9.18.0",
97+
"eslint-config-prettier": "^10.0.1",
98+
"eslint-plugin-prettier": "^5.2.2",
99+
"globals": "^16.0.0",
100+
"husky": "^9.1.7",
101+
"jest": "^30.0.0",
102+
"lint-staged": "^16.2.4",
103+
"prettier": "^3.4.2",
104+
"source-map-support": "^0.5.21",
105+
"supertest": "^7.0.0",
106+
"ts-jest": "^29.2.5",
107+
"ts-loader": "^9.5.2",
108+
"ts-node": "^10.9.2",
109+
"tsconfig-paths": "^4.2.0",
110+
"typescript": "^5.7.3",
111+
"typescript-eslint": "^8.20.0"
112+
},
113+
"jest": {
114+
"moduleFileExtensions": [
115+
"js",
116+
"json",
117+
"ts"
118+
],
119+
"rootDir": "src",
120+
"testRegex": ".*\\.spec\\.ts$",
121+
"transform": {
122+
"^.+\\.(t|j)s$": "ts-jest"
123+
},
124+
"collectCoverageFrom": [
125+
"**/*.(t|j)s"
126+
],
127+
"coverageDirectory": "../coverage",
128+
"testEnvironment": "node",
129+
"moduleNameMapper": {
130+
"^src/(.*)$": "<rootDir>/$1"
131+
}
132+
}
133+
}

src/databases/data-source.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { User } from '../user/entities/user.entity';
66
import { Verification } from '../verification/entities/verification.entity';
77
import { Category } from '../category/entities';
88
import { Tweet, TweetLike, TweetQuote, TweetReply, TweetRepost } from '../tweets/entities';
9+
import { Hashtag } from '../tweets/entities/hashtags.entity';
10+
import { UserPostsView } from '../tweets/entities/user-posts-view.entity';
911
import { UserBlocks, UserFollows, UserMutes } from '../user/entities';
1012
import { UserInterests } from '../user/entities/user-interests.entity';
1113
config({ path: resolve(__dirname, '../../config/.env') });
@@ -30,10 +32,12 @@ export default new DataSource({
3032
TweetQuote,
3133
Category,
3234
TweetRepost,
35+
Hashtag,
3336
UserBlocks,
3437
UserFollows,
3538
UserInterests,
3639
UserMutes,
40+
UserPostsView,
3741
],
3842
migrations: ['src/migrations/*{.ts,.js}'],
3943
synchronize: false,

src/databases/postgresql.module.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,17 @@ import { TypeOrmModule } from '@nestjs/typeorm';
88
TypeOrmModule.forRootAsync({
99
imports: [ConfigModule],
1010
inject: [ConfigService],
11-
useFactory: async (config_service: ConfigService) => ({
11+
useFactory: (config_service: ConfigService) => ({
1212
type: 'postgres',
1313
host: config_service.get<string>('POSTGRES_HOST'),
1414
username: config_service.get<string>('POSTGRES_USERNAME'),
1515
password: config_service.get<string>('POSTGRES_PASSWORD'),
1616
database: config_service.get<string>('POSTGRES_DB'),
1717
port: config_service.get<number>('POSTGRES_PORT'),
18-
synchronize: false, // Should be false in production
18+
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/migrations/1730356800000-AddConversationIdToTweets.ts

Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,41 +3,46 @@ import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm';
33
export class AddConversationIdToTweets1730356800000 implements MigrationInterface {
44
public async up(query_runner: QueryRunner): Promise<void> {
55
// Add conversation_id column (nullable - only used for replies)
6-
await query_runner.addColumn(
7-
'tweets',
8-
new TableColumn({
9-
name: 'conversation_id',
10-
type: 'uuid',
11-
isNullable: true,
12-
})
13-
);
6+
// Check if column exists first
7+
const has_column = await query_runner.hasColumn('tweets', 'conversation_id');
148

15-
// Set conversation_id for replies based on their parent tweet's conversation_id
16-
// This handles multi-level reply chains
17-
await query_runner.query(`
18-
WITH RECURSIVE reply_chain AS (
19-
-- Base case: direct replies to root tweets
20-
SELECT
21-
tr.reply_tweet_id,
22-
COALESCE(t_parent.conversation_id, tr.original_tweet_id) as conversation_id
23-
FROM tweet_replies tr
24-
LEFT JOIN tweets t_parent ON t_parent.tweet_id = tr.original_tweet_id
25-
WHERE tr.original_tweet_id NOT IN (SELECT reply_tweet_id FROM tweet_replies)
26-
27-
UNION ALL
28-
29-
-- Recursive case: replies to replies
30-
SELECT
31-
tr.reply_tweet_id,
32-
rc.conversation_id
33-
FROM tweet_replies tr
34-
INNER JOIN reply_chain rc ON tr.original_tweet_id = rc.reply_tweet_id
35-
)
36-
UPDATE tweets t
37-
SET conversation_id = rc.conversation_id
38-
FROM reply_chain rc
39-
WHERE t.tweet_id = rc.reply_tweet_id
40-
`);
9+
if (!has_column) {
10+
await query_runner.addColumn(
11+
'tweets',
12+
new TableColumn({
13+
name: 'conversation_id',
14+
type: 'uuid',
15+
isNullable: true,
16+
})
17+
);
18+
19+
// Set conversation_id for replies based on their parent tweet's conversation_id
20+
// This handles multi-level reply chains
21+
await query_runner.query(`
22+
WITH RECURSIVE reply_chain AS (
23+
-- Base case: direct replies to root tweets
24+
SELECT
25+
tr.reply_tweet_id,
26+
COALESCE(t_parent.conversation_id, tr.original_tweet_id) as conversation_id
27+
FROM tweet_replies tr
28+
LEFT JOIN tweets t_parent ON t_parent.tweet_id = tr.original_tweet_id
29+
WHERE tr.original_tweet_id NOT IN (SELECT reply_tweet_id FROM tweet_replies)
30+
31+
UNION ALL
32+
33+
-- Recursive case: replies to replies
34+
SELECT
35+
tr.reply_tweet_id,
36+
rc.conversation_id
37+
FROM tweet_replies tr
38+
INNER JOIN reply_chain rc ON tr.original_tweet_id = rc.reply_tweet_id
39+
)
40+
UPDATE tweets t
41+
SET conversation_id = rc.conversation_id
42+
FROM reply_chain rc
43+
WHERE t.tweet_id = rc.reply_tweet_id
44+
`);
45+
}
4146
}
4247

4348
public async down(query_runner: QueryRunner): Promise<void> {

0 commit comments

Comments
 (0)