Skip to content

Commit

Permalink
Merge pull request #216 from DestinyItemManager/drop-old-data-import
Browse files Browse the repository at this point in the history
Drop support for importing very old DIM data exports
  • Loading branch information
bhollis authored May 29, 2024
2 parents 328b58a + ebdd4d9 commit d1d0971
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 1,812 deletions.
116 changes: 15 additions & 101 deletions api/routes/import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,12 @@ import { defaultSettings, Settings } from '../shapes/settings.js';
import { badRequest } from '../utils.js';
import { deleteAllData } from './delete-all-data.js';

export interface DimData {
// The last selected platform membership ID
membershipId?: string;
destinyVersion?: DestinyVersion;
// membership IDs of ignored DTR reviewers
ignoredUsers?: readonly string[];
// loadout ids
'loadouts-v3.0'?: readonly string[];
'settings-v1.0'?: Readonly<Partial<Settings>>; // settings

// dimItemInfo-m${account.membershipId}-d${account.destinyVersion}
// [`info.${id}`]
[key: string]: any;
}

export const importHandler = asyncHandler(async (req, res) => {
const { bungieMembershipId } = req.user;
const { id: appId } = req.dimApp;

// Support both old DIM exports and new API exports
const importData = req.body as DimData | ExportResponse;
// Support only new API exports
const importData = req.body as ExportResponse;

const settings = extractSettings(importData);
const loadouts = extractLoadouts(importData);
Expand Down Expand Up @@ -154,112 +139,41 @@ function subtractObject(obj: object | undefined, defaults: object) {
return result;
}

function extractSettings(importData: DimData | ExportResponse): Settings {
return subtractObject(
importData.settings || importData['settings-v1.0'],
defaultSettings,
) as Settings;
function extractSettings(importData: ExportResponse): Settings {
return subtractObject(importData.settings, defaultSettings) as Settings;
}

type PlatformLoadout = Loadout & {
platformMembershipId: string;
destinyVersion: DestinyVersion;
};

function extractLoadouts(importData: DimData | ExportResponse): PlatformLoadout[] {
if (importData.loadouts) {
return importData.loadouts.map((l) => ({
function extractLoadouts(importData: ExportResponse): PlatformLoadout[] {
return (
importData.loadouts?.map((l) => ({
...l.loadout,
platformMembershipId: l.platformMembershipId,
destinyVersion: l.destinyVersion,
}));
}

const ids = importData['loadouts-v3.0'];
if (!ids) {
return [];
}
return ids
.map((id) => importData[id])
.filter(Boolean)
.map((rawLoadout) => ({
platformMembershipId: rawLoadout.membershipId,
destinyVersion: rawLoadout.destinyVersion,
id: rawLoadout.id,
name: rawLoadout.name,
classType: convertLoadoutClassType(rawLoadout.classType),
clearSpace: rawLoadout.clearSpace || false,
equipped: rawLoadout.items
.filter((i) => i.equipped)
.map((item) => ({ id: item.id, hash: item.hash, amount: item.amount })),
unequipped: rawLoadout.items
.filter((i) => !i.equipped)
.map((item) => ({ id: item.id, hash: item.hash, amount: item.amount })),
}));
}

/** Legacy loadout class assignment */
export enum LoadoutClass {
any = -1,
warlock = 0,
titan = 1,
hunter = 2,
}

export const loadoutClassToClassType = {
[LoadoutClass.hunter]: 1,
[LoadoutClass.titan]: 0,
[LoadoutClass.warlock]: 2,
[LoadoutClass.any]: 3,
};

export const classTypeToLoadoutClass = {
1: LoadoutClass.hunter,
0: LoadoutClass.titan,
2: LoadoutClass.warlock,
3: LoadoutClass.any,
};

function convertLoadoutClassType(loadoutClassType: LoadoutClass) {
return loadoutClassToClassType[loadoutClassType ?? LoadoutClass.any];
})) ?? []
);
}

type PlatformItemAnnotation = ItemAnnotation & {
platformMembershipId: string;
destinyVersion: DestinyVersion;
};

function extractItemAnnotations(importData: DimData | ExportResponse): PlatformItemAnnotation[] {
if (importData.tags) {
return importData.tags.map((t) => ({
function extractItemAnnotations(importData: ExportResponse): PlatformItemAnnotation[] {
return (
importData.tags?.map((t) => ({
...t.annotation,
platformMembershipId: t.platformMembershipId,
destinyVersion: t.destinyVersion || 2,
}));
}

const annotations: PlatformItemAnnotation[] = [];
for (const key in importData) {
const match = /^dimItemInfo-m(\d+)-d(1|2)$/.exec(key);
if (match) {
const platformMembershipId = match[1];
const destinyVersion = parseInt(match[2], 10) as DestinyVersion;
for (const id in importData[key]) {
const value = importData[key][id];
annotations.push({
platformMembershipId,
destinyVersion,
id,
tag: value.tag,
notes: value.notes,
});
}
}
}
return annotations;
})) ?? []
);
}

function extractSearches(importData: ExportResponse | DimData): ExportResponse['searches'] {
function extractSearches(importData: ExportResponse): ExportResponse['searches'] {
return (importData.searches || []).filter(
// Filter out pre-filled searches that were never used
(s) => s.search.usageCount > 0,
Expand Down
37 changes: 23 additions & 14 deletions api/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,18 @@ describe('import/export', () => {
const exportResponse = response.body as ExportResponse;

expect(exportResponse.settings.itemSortOrderCustom).toEqual([
'sunset',
'tag',
'rarity',
'primStat',
'season',
'ammoType',
'rarity',
'typeName',
'name',
]);

expect(exportResponse.loadouts.length).toBe(12);
expect(exportResponse.tags.length).toBe(51);
expect(exportResponse.loadouts.length).toBe(37);
expect(exportResponse.tags.length).toBe(592);
});

// TODO: other import formats, validation
Expand All @@ -108,15 +111,18 @@ describe('profile', () => {
const profileResponse = response.body as ProfileResponse;

expect(profileResponse.settings!.itemSortOrderCustom).toEqual([
'sunset',
'tag',
'rarity',
'primStat',
'season',
'ammoType',
'rarity',
'typeName',
'name',
]);
expect(profileResponse.loadouts!.length).toBe(11);
expect(profileResponse.tags!.length).toBe(51);
expect(profileResponse.triumphs!.length).toBe(0);
expect(profileResponse.loadouts!.length).toBe(19);
expect(profileResponse.tags!.length).toBe(592);
expect(profileResponse.triumphs!.length).toBe(30);
});

it('can retrieve only settings, without needing a platform membership ID', async () => {
Expand All @@ -125,9 +131,12 @@ describe('profile', () => {
const profileResponse = response.body as ProfileResponse;

expect(profileResponse.settings!.itemSortOrderCustom).toEqual([
'sunset',
'tag',
'rarity',
'primStat',
'season',
'ammoType',
'rarity',
'typeName',
'name',
]);
Expand All @@ -144,20 +153,20 @@ describe('profile', () => {
const profileResponse = response.body as ProfileResponse;

expect(profileResponse.settings).toBeUndefined();
expect(profileResponse.loadouts!.length).toBe(11);
expect(profileResponse.loadouts!.length).toBe(19);
expect(profileResponse.tags).toBeUndefined();
});

it('can delete all data with /delete_all_data', async () => {
const response = await postRequestAuthed('/delete_all_data').expect(200);

expect(response.body.deleted).toEqual({
itemHashTags: 71,
loadouts: 37,
searches: 205,
settings: 1,
loadouts: 12,
tags: 51,
triumphs: 0,
searches: 0,
itemHashTags: 0,
tags: 592,
triumphs: 30,
});

// Now re-export and make sure it's all gone
Expand Down
Loading

0 comments on commit d1d0971

Please sign in to comment.