Skip to content

Commit 661bffd

Browse files
authored
Merge pull request #197 from game-node-app/dev
Dev
2 parents bc8da82 + 986f588 commit 661bffd

Some content is hidden

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

41 files changed

+858
-114
lines changed

server_swagger.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

src/app.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import { addTransactionalDataSource } from "typeorm-transactional";
4141
import { DataSource } from "typeorm";
4242
import { AwardsModule } from './awards/awards.module';
4343
import { UserAccountModule } from './user/user-account/user-account.module';
44+
import { PreferredPlatformModule } from './preferred-platform/preferred-platform.module';
4445

4546
/**
4647
* Should only be called after 'ConfigModule' is loaded (e.g. in useFactory)
@@ -198,6 +199,7 @@ function getRedisConfig(target: "cache" | "bullmq" = "cache") {
198199
JournalModule,
199200
AwardsModule,
200201
UserAccountModule,
202+
PreferredPlatformModule,
201203
],
202204
})
203205
export class AppModule implements NestModule {

src/collections/collections-entries/collections-entries.constants.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,3 @@ export enum CollectionEntryStatus {
44
PLANNED = "planned",
55
DROPPED = "dropped",
66
}
7-
8-
export const COLLECTION_ENTRY_ORDERING_GAP = 1000;
9-
10-
export const COLLECTION_ENTRY_ORDERING_NORMALIZATION_THRESHOLD = 1e-6;

src/collections/collections-entries/collections-entries.service.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,14 @@ import { LevelIncreaseActivities } from "../../level/level.constants";
2323
import { CollectionsService } from "../collections.service";
2424
import { FindCollectionEntriesForCollectionIdDto } from "./dto/find-collection-entries-for-collection-id.dto";
2525
import { CollectionEntryToCollection } from "./entities/collection-entry-to-collection.entity";
26-
import {
27-
COLLECTION_ENTRY_ORDERING_GAP,
28-
CollectionEntryStatus,
29-
} from "./collections-entries.constants";
26+
import { CollectionEntryStatus } from "./collections-entries.constants";
3027
import { match } from "ts-pattern";
3128
import { buildGameFilterFindOptions } from "../../game/game-repository/utils/build-game-filter-find-options";
3229
import { Transactional } from "typeorm-transactional";
3330
import { GameRepositoryService } from "../../game/game-repository/game-repository.service";
3431
import { FindRelatedCollectionEntriesResponseDto } from "./dto/find-related-collection-entries.dto";
3532
import { toMap } from "../../utils/toMap";
33+
import { DEFAULT_ORDERING_GAP } from "../../utils/ordering";
3634

3735
@Injectable()
3836
export class CollectionsEntriesService {
@@ -432,7 +430,7 @@ export class CollectionsEntriesService {
432430

433431
for (const collectionId of newCollectionIds) {
434432
const lastOrder = maxOrderMap.get(collectionId) ?? 0;
435-
const newOrder = lastOrder + COLLECTION_ENTRY_ORDERING_GAP;
433+
const newOrder = lastOrder + DEFAULT_ORDERING_GAP;
436434

437435
await this.collectionEntryToCollectionRepository.insert({
438436
collectionEntryId,

src/collections/collections-entries/order/collections-ordering.service.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import { HttpException, HttpStatus, Injectable } from "@nestjs/common";
22
import { InjectRepository } from "@nestjs/typeorm";
33
import { CollectionEntryToCollection } from "../entities/collection-entry-to-collection.entity";
44
import { In, Repository } from "typeorm";
5-
import {
6-
COLLECTION_ENTRY_ORDERING_GAP,
7-
COLLECTION_ENTRY_ORDERING_NORMALIZATION_THRESHOLD,
8-
} from "../collections-entries.constants";
95
import { CollectionEntryUpdateOrderingDto } from "../dto/collection-entry-update-ordering.dto";
6+
import {
7+
DEFAULT_ORDERING_GAP,
8+
DEFAULT_ORDERING_NORMALIZATION_THRESHOLD,
9+
} from "../../../utils/ordering";
1010

1111
/**
1212
* Service responsible for handling collection to collection entry ordering.
@@ -64,10 +64,10 @@ export class CollectionsOrderingService {
6464
newOrder = (before.order + after.order) / 2;
6565
} else if (before) {
6666
// Move to the *end* (after "before")
67-
newOrder = before.order + COLLECTION_ENTRY_ORDERING_GAP;
67+
newOrder = before.order + DEFAULT_ORDERING_GAP;
6868
} else if (after) {
6969
// Move to the *beginning* (before "after")
70-
newOrder = after.order - COLLECTION_ENTRY_ORDERING_GAP;
70+
newOrder = after.order - DEFAULT_ORDERING_GAP;
7171
}
7272

7373
await this.collectionEntryToCollectionRepository.update(
@@ -81,8 +81,7 @@ export class CollectionsOrderingService {
8181
);
8282

8383
if (
84-
Math.abs(newOrder) >
85-
COLLECTION_ENTRY_ORDERING_NORMALIZATION_THRESHOLD ||
84+
Math.abs(newOrder) < DEFAULT_ORDERING_NORMALIZATION_THRESHOLD ||
8685
!Number.isFinite(newOrder)
8786
) {
8887
await this.normalizeCollectionOrdering(collectionId);
@@ -106,7 +105,7 @@ export class CollectionsOrderingService {
106105
});
107106

108107
for (let i = 0; i < entries.length; i++) {
109-
entries[i].order = i + COLLECTION_ENTRY_ORDERING_GAP;
108+
entries[i].order = (i + 1) * DEFAULT_ORDERING_GAP;
110109
}
111110

112111
await this.collectionEntryToCollectionRepository.save(entries);

src/connection/connections.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { PlaytimeModule } from "../playtime/playtime.module";
99
import { PlaytimeWatchModule } from "../playtime/watch/playtime-watch.module";
1010
import { XboxSyncModule } from "../sync/xbox/xbox-sync.module";
1111
import { ConnectionSyncModule } from './connection-sync/connection-sync.module';
12+
import { CollectionsModule } from "../collections/collections.module";
1213

1314
@Module({
1415
imports: [
@@ -18,6 +19,7 @@ import { ConnectionSyncModule } from './connection-sync/connection-sync.module';
1819
PlaytimeModule,
1920
XboxSyncModule,
2021
forwardRef(() => PlaytimeWatchModule),
22+
forwardRef(() => CollectionsModule),
2123
ConnectionSyncModule,
2224
],
2325
providers: [ConnectionsService],

src/connection/connections.service.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { UserConnectionDto } from "./dto/user-connection.dto";
2323
import { HttpStatusCode } from "axios";
2424
import { PlaytimeWatchService } from "../playtime/watch/playtime-watch.service";
2525
import { XboxSyncService } from "../sync/xbox/xbox-sync.service";
26+
import { CollectionsService } from "../collections/collections.service";
2627

2728
const toDto = (userConnection: UserConnection): UserConnectionDto => ({
2829
...userConnection,
@@ -47,6 +48,8 @@ export class ConnectionsService {
4748
private readonly xboxSyncService: XboxSyncService,
4849
@Inject(forwardRef(() => PlaytimeWatchService))
4950
private readonly playtimeWatchService: PlaytimeWatchService,
51+
@Inject(forwardRef(() => CollectionsService))
52+
private readonly collectionsService: CollectionsService,
5053
) {}
5154

5255
public findOneById(id: number) {
@@ -123,6 +126,8 @@ export class ConnectionsService {
123126
userIdentifier,
124127
isImporterEnabled,
125128
isPlaytimeImportEnabled,
129+
isAutoImportEnabled,
130+
autoImportCollectionId,
126131
} = dto;
127132

128133
const possibleExistingConnection = await this.findOneByUserIdAndType(
@@ -172,6 +177,26 @@ export class ConnectionsService {
172177
const finalIsPlaytimeImportEnabled = isPlaytimeImporterViable
173178
? isPlaytimeImportEnabled
174179
: false;
180+
const finalIsAutoImportEnabled = isImporterViable
181+
? isAutoImportEnabled
182+
: false;
183+
184+
// Validate collection exists and belongs to user if autoImportCollectionId is provided
185+
// If auto-import is enabled but no collection specified, games will be imported
186+
// to the user's library without being added to a specific collection
187+
let validatedAutoImportCollectionId: string | null = null;
188+
if (finalIsAutoImportEnabled && autoImportCollectionId) {
189+
const collection = await this.collectionsService.findOneById(
190+
autoImportCollectionId,
191+
);
192+
if (!collection || collection.libraryUserId !== userId) {
193+
throw new HttpException(
194+
"Collection not found or does not belong to user",
195+
HttpStatus.BAD_REQUEST,
196+
);
197+
}
198+
validatedAutoImportCollectionId = autoImportCollectionId;
199+
}
175200

176201
const createdConnection = await this.userConnectionRepository.save({
177202
...possibleExistingConnection,
@@ -182,6 +207,8 @@ export class ConnectionsService {
182207
isImporterViable,
183208
isImporterEnabled: finalIsImporterEnabled,
184209
isPlaytimeImportEnabled: finalIsPlaytimeImportEnabled,
210+
isAutoImportEnabled: finalIsAutoImportEnabled,
211+
autoImportCollectionId: validatedAutoImportCollectionId,
185212
});
186213

187214
this.onConnectionCreate(createdConnection);

src/connection/dto/connection-create.dto.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
IsNotEmpty,
66
IsOptional,
77
IsString,
8+
IsUUID,
89
MinLength,
910
} from "class-validator";
1011

@@ -25,4 +26,10 @@ export class ConnectionCreateDto {
2526
@IsBoolean()
2627
@IsOptional()
2728
isPlaytimeImportEnabled: boolean = true;
29+
@IsBoolean()
30+
@IsOptional()
31+
isAutoImportEnabled: boolean = false;
32+
@IsUUID()
33+
@IsOptional()
34+
autoImportCollectionId?: string | null;
2835
}

src/connection/entity/user-connection.entity.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
} from "typeorm";
88
import { EConnectionType } from "../connections.constants";
99
import { Profile } from "../../profile/entities/profile.entity";
10+
import { Collection } from "../../collections/entities/collection.entity";
1011

1112
@Entity()
1213
@Unique(["profile", "type"])
@@ -45,4 +46,20 @@ export class UserConnection {
4546
default: true,
4647
})
4748
isPlaytimeImportEnabled: boolean;
49+
@Column({
50+
nullable: false,
51+
default: false,
52+
})
53+
isAutoImportEnabled: boolean;
54+
@ManyToOne(() => Collection, {
55+
nullable: true,
56+
onDelete: "SET NULL",
57+
})
58+
autoImportCollection: Collection | null;
59+
@Column({
60+
nullable: true,
61+
type: "varchar",
62+
length: 36,
63+
})
64+
autoImportCollectionId: string | null;
4865
}

src/game/game-repository/game-repository.constants.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,28 @@ enum EGameStatus {
7272
Delisted = 8,
7373
}
7474

75+
/**
76+
* Common abbreviations used across the application for game platforms.
77+
* This enum is not exaustive. Prefer to query the database for the full list of platforms.
78+
* This should only be used to avoid magic strings in the codebase.
79+
*/
80+
enum GamePlatformAbbreviations {
81+
PC = "PC",
82+
// The OG Xbox.
83+
Xbox = "XBOX",
84+
XboxOne = "XONE",
85+
XboxSeriesXS = "Series X|S",
86+
Xbox360 = "X360",
87+
PSVita = "Vita",
88+
PS3 = "PS3",
89+
PS4 = "PS4",
90+
PS5 = "PS5",
91+
Switch = "Switch",
92+
Linux = "Linux",
93+
Android = "Android",
94+
iOS = "iOS",
95+
}
96+
7597
/**
7698
* Where the game data is stored in the GameNode system.
7799
* Helps the clients determine the source of the data.
@@ -99,7 +121,7 @@ const PlatformToIconMap: { [p: string]: string[] } = {
99121
playstation: ["PSVR", "PSVR2"],
100122
x360: ["X360"],
101123
xone: ["XONE"],
102-
xseriesx: ["Series X"],
124+
xseriesx: ["Series X|S"],
103125
xbox: ["XBOX"],
104126
nswitch: ["Switch"],
105127
n64: ["N64"],
@@ -127,5 +149,6 @@ export {
127149
EGameExternalGameCategory,
128150
EGamePlatformCategory,
129151
EGameStorageSource,
152+
GamePlatformAbbreviations,
130153
PlatformToIconMap,
131154
};

0 commit comments

Comments
 (0)