Skip to content

Commit a7440d5

Browse files
mirarifhasananwarulislamjamesgeorge007
authored
fix: maintain incremental orderIndex for collections and requests (hoppscotch#5338)
Co-authored-by: Anwarul Islam <[email protected]> Co-authored-by: jamesgeorge007 <[email protected]>
1 parent 1df781e commit a7440d5

File tree

36 files changed

+1763
-1438
lines changed

36 files changed

+1763
-1438
lines changed

.env.example

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
DATABASE_URL=postgresql://postgres:testpass@hoppscotch-db:5432/hoppscotch
44

55
# Sensitive Data Encryption Key while storing in Database (32 character)
6-
DATA_ENCRYPTION_KEY="data encryption key with 32 char"
6+
DATA_ENCRYPTION_KEY=data encryption key with 32 char
77

88
# Whitelisted origins for the Hoppscotch App.
99
# This list controls which origins can interact with the app through cross-origin comms.
@@ -12,7 +12,7 @@ DATA_ENCRYPTION_KEY="data encryption key with 32 char"
1212
# NOTE: `3200` here refers to the bundle server (port 3200) that provides the bundles,
1313
# NOT where the app runs. The app itself uses the `app://` protocol with dynamic
1414
# bundle names like `app://{bundle-name}/`
15-
WHITELISTED_ORIGINS="http://localhost:3170,http://localhost:3000,http://localhost:3100,app://localhost_3200,app://hoppscotch"
15+
WHITELISTED_ORIGINS=http://localhost:3170,http://localhost:3000,http://localhost:3100,app://localhost_3200,app://hoppscotch
1616

1717
# If true, the client’s IP address is understood as the left-most entry in the X-Forwarded-For header
1818
TRUST_PROXY=false
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
-- Recalculate orderIndex for UserCollection per (parentID)
2+
WITH ordered AS (
3+
SELECT
4+
id,
5+
ROW_NUMBER() OVER (
6+
PARTITION BY "parentID"
7+
ORDER BY "orderIndex" ASC, "createdOn" ASC, id ASC
8+
) AS new_index
9+
FROM "UserCollection"
10+
)
11+
UPDATE "UserCollection" uc
12+
SET "orderIndex" = o.new_index
13+
FROM ordered o
14+
WHERE uc.id = o.id;
15+
16+
-- Recalculate orderIndex for UserRequest per (collectionID)
17+
WITH ordered AS (
18+
SELECT
19+
id,
20+
ROW_NUMBER() OVER (
21+
PARTITION BY "collectionID"
22+
ORDER BY "orderIndex" ASC, "createdOn" ASC, id ASC
23+
) AS new_index
24+
FROM "UserRequest"
25+
)
26+
UPDATE "UserRequest" ur
27+
SET "orderIndex" = o.new_index
28+
FROM ordered o
29+
WHERE ur.id = o.id;
30+
31+
-- Recalculate orderIndex for TeamCollection per (teamID, parentID)
32+
WITH ordered AS (
33+
SELECT
34+
id,
35+
ROW_NUMBER() OVER (
36+
PARTITION BY "teamID", "parentID"
37+
ORDER BY "orderIndex" ASC, "createdOn" ASC, id ASC
38+
) AS new_index
39+
FROM "TeamCollection"
40+
)
41+
UPDATE "TeamCollection" tc
42+
SET "orderIndex" = o.new_index
43+
FROM ordered o
44+
WHERE tc.id = o.id;
45+
46+
-- Recalculate orderIndex for TeamRequest per (teamID, collectionID)
47+
WITH ordered AS (
48+
SELECT
49+
id,
50+
ROW_NUMBER() OVER (
51+
PARTITION BY "teamID", "collectionID"
52+
ORDER BY "orderIndex" ASC, "createdOn" ASC, id ASC
53+
) AS new_index
54+
FROM "TeamRequest"
55+
)
56+
UPDATE "TeamRequest" tr
57+
SET "orderIndex" = o.new_index
58+
FROM ordered o
59+
WHERE tr.id = o.id;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
-- Recreate as DEFERRABLE UNIQUE CONSTRAINTS
2+
3+
ALTER TABLE "TeamCollection"
4+
ADD CONSTRAINT "TeamCollection_teamID_parentID_orderIndex_key"
5+
UNIQUE ("teamID", "parentID", "orderIndex")
6+
DEFERRABLE INITIALLY DEFERRED;
7+
8+
ALTER TABLE "TeamRequest"
9+
ADD CONSTRAINT "TeamRequest_teamID_collectionID_orderIndex_key"
10+
UNIQUE ("teamID", "collectionID", "orderIndex")
11+
DEFERRABLE INITIALLY DEFERRED;
12+
13+
ALTER TABLE "UserCollection"
14+
ADD CONSTRAINT "UserCollection_userUid_parentID_orderIndex_key"
15+
UNIQUE ("userUid", "parentID", "orderIndex")
16+
DEFERRABLE INITIALLY DEFERRED;
17+
18+
ALTER TABLE "UserRequest"
19+
ADD CONSTRAINT "UserRequest_userUid_collectionID_orderIndex_key"
20+
UNIQUE ("userUid", "collectionID", "orderIndex")
21+
DEFERRABLE INITIALLY DEFERRED;

packages/hoppscotch-backend/prisma/schema.prisma

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ model TeamCollection {
5353
orderIndex Int
5454
createdOn DateTime @default(now()) @db.Timestamptz(3)
5555
updatedOn DateTime @updatedAt @db.Timestamptz(3)
56+
57+
@@unique([teamID, parentID, orderIndex])
5658
}
5759

5860
model TeamRequest {
@@ -66,6 +68,8 @@ model TeamRequest {
6668
orderIndex Int
6769
createdOn DateTime @default(now()) @db.Timestamptz(3)
6870
updatedOn DateTime @updatedAt @db.Timestamptz(3)
71+
72+
@@unique([teamID, collectionID, orderIndex])
6973
}
7074

7175
model Shortcode {
@@ -189,6 +193,8 @@ model UserRequest {
189193
orderIndex Int
190194
createdOn DateTime @default(now()) @db.Timestamptz(3)
191195
updatedOn DateTime @updatedAt @db.Timestamptz(3)
196+
197+
@@unique([userUid, collectionID, orderIndex])
192198
}
193199

194200
model UserCollection {
@@ -205,6 +211,8 @@ model UserCollection {
205211
type ReqType
206212
createdOn DateTime @default(now()) @db.Timestamptz(3)
207213
updatedOn DateTime @updatedAt @db.Timestamptz(3)
214+
215+
@@unique([userUid, parentID, orderIndex])
208216
}
209217

210218
enum TeamAccessRole {

packages/hoppscotch-backend/src/errors.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,12 @@ export const TEAM_FB_COLL_PATH_RESOLVE_FAIL = 'team/fb_coll_path_resolve_fail';
188188
*/
189189
export const TEAM_COLL_NOT_FOUND = 'team_coll/collection_not_found';
190190

191+
/**
192+
* Could not find the team in the database
193+
* (TeamCollectionService)
194+
*/
195+
export const TEAM_COLL_CREATION_FAILED = 'team_coll/creation_failed';
196+
191197
/**
192198
* Cannot make parent collection a child of a collection that a child of itself
193199
* (TeamCollectionService)
@@ -650,6 +656,13 @@ export const USER_COLL_NOT_SAME_TYPE = 'user_coll/type_mismatch' as const;
650656
export const USER_COLL_IS_PARENT_COLL =
651657
'user_coll/user_collection_is_parent_coll' as const;
652658

659+
/**
660+
* User Collection Creation Failed
661+
* (UserCollectionService)
662+
*/
663+
export const USER_COLLECTION_CREATION_FAILED =
664+
'user_collection/creation_failed' as const;
665+
653666
/**
654667
* User Collection Re-Ordering Failed
655668
* (UserCollectionService)

packages/hoppscotch-backend/src/infra-config/helper.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export async function loadInfraConfiguration() {
8787

8888
// Prisma throw error if 'Can't reach at database server' OR 'Table does not exist'
8989
// Reason for not throwing error is, we want successful build during 'postinstall' and generate dist files
90-
console.log('Error from loadInfraConfiguration', error);
90+
console.error('Error from loadInfraConfiguration', error);
9191
return { INFRA: {} };
9292
}
9393
}

packages/hoppscotch-backend/src/infra-config/infra-config.service.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import {
4242
SaveOnboardingConfigResponse,
4343
} from './dto/onboarding.dto';
4444
import * as crypto from 'crypto';
45+
import { PrismaError } from 'src/prisma/prisma-error-codes';
4546

4647
@Injectable()
4748
export class InfraConfigService implements OnModuleInit {
@@ -125,14 +126,14 @@ export class InfraConfigService implements OnModuleInit {
125126
stopApp();
126127
}
127128
} catch (error) {
128-
if (error.code === 'P1001') {
129+
if (error.code === PrismaError.DATABASE_UNREACHABLE) {
129130
// Prisma error code for 'Can't reach at database server'
130131
// We're not throwing error here because we want to allow the app to run 'pnpm install'
131-
} else if (error.code === 'P2021') {
132+
} else if (error.code === PrismaError.TABLE_DOES_NOT_EXIST) {
132133
// Prisma error code for 'Table does not exist'
133134
throwErr(DATABASE_TABLE_NOT_EXIST);
134135
} else {
135-
console.log(error);
136+
console.error(error);
136137
throwErr(error);
137138
}
138139
}

packages/hoppscotch-backend/src/mailer/mailer.service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export class MailerService {
5656
context: mailDesc.variables,
5757
});
5858
} catch (error) {
59-
console.log('Error from sendEmail:', error);
59+
console.error('Error from sendEmail:', error);
6060
return throwErr(EMAIL_FAILED);
6161
}
6262
}
@@ -82,7 +82,7 @@ export class MailerService {
8282
});
8383
return res;
8484
} catch (error) {
85-
console.log('Error from sendUserInvitationEmail:', error);
85+
console.error('Error from sendUserInvitationEmail:', error);
8686
return throwErr(EMAIL_FAILED);
8787
}
8888
}

packages/hoppscotch-backend/src/main.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ async function bootstrap() {
8888

8989
if (configService.get('TRUST_PROXY') === 'true') {
9090
console.log('Enabling trust proxy');
91-
9291
app.set('trust proxy', true);
9392
}
9493

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export enum PrismaError {
2+
DATABASE_UNREACHABLE = 'P1001',
3+
TABLE_DOES_NOT_EXIST = 'P2021',
4+
UNIQUE_CONSTRAINT_VIOLATION = 'P2002',
5+
TRANSACTION_TIMEOUT = 'P2028',
6+
TRANSACTION_DEADLOCK = 'P2034', // write conflict or a deadlock
7+
}

0 commit comments

Comments
 (0)