Skip to content

Commit c724af9

Browse files
committed
feat: improve inventory context and uex sync flow
- persist inventory view mode and density across refresh - add org-mode banner and outline for inventory - populate locations table after uex locations sync - register additional seeder repositories
1 parent 7e736db commit c724af9

28 files changed

Lines changed: 1600 additions & 118 deletions

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,40 @@ cd frontend && pnpm dev # Frontend only
175175

176176
**Note**: Redis is optional. The application will fall back to in-memory caching if Redis is unavailable.
177177

178+
### Dependency Health Check
179+
180+
If TypeScript suddenly cannot resolve NestJS/TypeORM/Node types (missing modules, missing `lib.es6.d.ts`, or "Cannot find global type"), re-install dependencies:
181+
182+
```bash
183+
pnpm install
184+
pnpm -C backend install
185+
```
186+
187+
### Clean Reset (Local Dev)
188+
189+
If you want a full clean reset of dependencies and data:
190+
191+
```bash
192+
# Stop containers and drop volumes
193+
docker-compose down -v
194+
195+
# Reinstall dependencies
196+
pnpm install
197+
198+
# Rebuild packages
199+
pnpm build
200+
201+
# Start infrastructure services
202+
docker-compose up -d
203+
204+
# Run migrations and seed data
205+
pnpm --filter backend migration:run
206+
pnpm --filter backend seed
207+
208+
# Start apps
209+
pnpm dev
210+
```
211+
178212
### Building for Production
179213

180214
```bash

backend/docs/uex/locations-sync.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# UEX Locations Sync Relationships
2+
3+
## Overview
4+
5+
UEX location data includes multiple parent references that are not always
6+
planets or moons. Station persists those relationships so sync and location
7+
population can resolve a valid star system and hierarchy path.
8+
9+
## Relationships Captured
10+
11+
- Orbits are stored in `uex_orbits` and linked to star systems.
12+
- Space stations can reference a planet, moon, or orbit.
13+
- POIs can reference a star system, planet, moon, orbit, space station, city,
14+
or outpost.
15+
- Moons can reference either a planet or a star system directly (when
16+
`id_planet` is missing but `id_star_system` is present).
17+
18+
## Sync and Population Notes
19+
20+
- The locations sync order includes `orbits` before planets/moons, so orbit
21+
references can resolve immediately.
22+
- Space stations and POIs resolve their star system from orbit or related
23+
entities when a direct star system ID is missing.
24+
- Location population prefers the most specific parent (station/city/outpost,
25+
then planet/moon, then orbit, then star system) when building hierarchy paths.

backend/src/data-source.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { UexItem } from './modules/uex/entities/uex-item.entity';
1717
import { UexCategory } from './modules/uex/entities/uex-category.entity';
1818
import { UexCompany } from './modules/uex/entities/uex-company.entity';
1919
import { UexStarSystem } from './modules/uex/entities/uex-star-system.entity';
20+
import { UexOrbit } from './modules/uex/entities/uex-orbit.entity';
2021
import { UexPlanet } from './modules/uex/entities/uex-planet.entity';
2122
import { UexMoon } from './modules/uex/entities/uex-moon.entity';
2223
import { UexSpaceStation } from './modules/uex/entities/uex-space-station.entity';
@@ -39,6 +40,8 @@ import { SeedSystemUser1764791795973 } from './migrations/1764791795973-SeedSyst
3940
import { CreateUexBaseTables1764802822073 } from './migrations/1764802822073-CreateUexBaseTables';
4041
import { CreateUexItemsTable1764802975691 } from './migrations/1764802975691-CreateUexItemsTable';
4142
import { CreateUexLocationTables1764803020274 } from './migrations/1764803020274-CreateUexLocationTables';
43+
import { CreateUexOrbitsTable1767050000000 } from './migrations/1767050000000-CreateUexOrbitsTable';
44+
import { AddOrbitRelationsToUexLocations1767051000000 } from './migrations/1767051000000-AddOrbitRelationsToUexLocations';
4245
import { AddUexSyncStateTables1764812815840 } from './migrations/1764812815840-AddUexSyncStateTables';
4346
import { CreateLocationsTable1764949892544 } from './migrations/1764949892544-CreateLocationsTable';
4447
import { CreateUserInventoryItemsTable1764950546163 } from './migrations/1764950546163-CreateUserInventoryItemsTable';
@@ -73,6 +76,7 @@ export const AppDataSource = new DataSource({
7376
UexCategory,
7477
UexCompany,
7578
UexStarSystem,
79+
UexOrbit,
7680
UexPlanet,
7781
UexMoon,
7882
UexSpaceStation,
@@ -101,6 +105,8 @@ export const AppDataSource = new DataSource({
101105
CreateUexBaseTables1764802822073,
102106
CreateUexItemsTable1764802975691,
103107
CreateUexLocationTables1764803020274,
108+
CreateUexOrbitsTable1767050000000,
109+
AddOrbitRelationsToUexLocations1767051000000,
104110
AddUexSyncStateTables1764812815840,
105111

106112
// Locations (depends on games + UEX tables)

backend/src/database/seeds/database-seeder.module.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import { Organization } from '../../modules/organizations/organization.entity';
66
import { User } from '../../modules/users/user.entity';
77
import { UserOrganizationRole } from '../../modules/user-organization-roles/user-organization-role.entity';
88
import { Game } from '../../modules/games/game.entity';
9+
import { OrgInventoryItem } from '../../modules/org-inventory/entities/org-inventory-item.entity';
10+
import { Location } from '../../modules/locations/entities/location.entity';
11+
import { UexItem } from '../../modules/uex/entities/uex-item.entity';
912

1013
@Module({
1114
imports: [
@@ -15,6 +18,9 @@ import { Game } from '../../modules/games/game.entity';
1518
User,
1619
UserOrganizationRole,
1720
Game,
21+
OrgInventoryItem,
22+
Location,
23+
UexItem,
1824
]),
1925
],
2026
providers: [DatabaseSeederService],

backend/src/database/seeds/database-seeder.service.spec.ts

Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,20 @@ describe('DatabaseSeederService', () => {
2525
let loggerWarnSpy: jest.SpyInstance;
2626
let loggerErrorSpy: jest.SpyInstance;
2727

28-
const mockGame = {
28+
const mockGameSc = {
2929
id: 1,
3030
name: 'Star Citizen',
3131
code: 'sc',
3232
description: 'Star Citizen MMO',
3333
active: true,
3434
};
35+
const mockGameSq42 = {
36+
id: 2,
37+
name: 'Squadron 42',
38+
code: 'sq42',
39+
description: 'Squadron 42',
40+
active: true,
41+
};
3542

3643
const mockRole = {
3744
id: 1,
@@ -47,6 +54,20 @@ describe('DatabaseSeederService', () => {
4754
isActive: true,
4855
gameId: 1,
4956
};
57+
const mockOrgTwo = {
58+
id: 2,
59+
name: 'Dreadnought Industries',
60+
description: 'Test org',
61+
isActive: true,
62+
gameId: 1,
63+
};
64+
const mockOrgThree = {
65+
id: 3,
66+
name: 'Frontier Syndicate',
67+
description: 'Test org',
68+
isActive: true,
69+
gameId: 2,
70+
};
5071

5172
const mockUser = {
5273
id: 1,
@@ -55,6 +76,20 @@ describe('DatabaseSeederService', () => {
5576
password: 'hashed',
5677
isActive: true,
5778
};
79+
const mockOrgAdmin = {
80+
id: 2,
81+
username: 'orgadmin',
82+
email: 'orgadmin@example.com',
83+
password: 'hashed',
84+
isActive: true,
85+
};
86+
const mockMember = {
87+
id: 3,
88+
username: 'member1',
89+
email: 'member1@example.com',
90+
password: 'hashed',
91+
isActive: true,
92+
};
5893

5994
const mockUserOrgRole = {
6095
id: 1,
@@ -101,6 +136,7 @@ describe('DatabaseSeederService', () => {
101136
provide: getRepositoryToken(Organization),
102137
useValue: {
103138
findOne: jest.fn(),
139+
find: jest.fn(),
104140
create: jest.fn(),
105141
save: jest.fn(),
106142
},
@@ -109,6 +145,7 @@ describe('DatabaseSeederService', () => {
109145
provide: getRepositoryToken(User),
110146
useValue: {
111147
findOne: jest.fn(),
148+
find: jest.fn(),
112149
create: jest.fn(),
113150
save: jest.fn(),
114151
},
@@ -150,20 +187,19 @@ describe('DatabaseSeederService', () => {
150187

151188
describe('seedAll', () => {
152189
it('should seed all data successfully', async () => {
153-
// Mock games seeding - first call returns null (new game), second call returns the game for org creation
154190
jest
155191
.spyOn(gamesRepository, 'findOne')
156-
.mockResolvedValueOnce(null) // First game check (sc)
157-
.mockResolvedValueOnce(null) // Second game check (sq42)
158-
.mockResolvedValueOnce(mockGame as any) // Get sc game for org creation
159-
.mockResolvedValueOnce(null); // Org check
160-
161-
jest.spyOn(gamesRepository, 'create').mockReturnValue(mockGame as any);
162-
jest.spyOn(gamesRepository, 'save').mockResolvedValue(mockGame as any);
192+
.mockImplementation((query: any) => {
193+
if (query?.where?.code === 'sc') {
194+
return Promise.resolve(mockGameSc as any);
195+
}
196+
if (query?.where?.code === 'sq42') {
197+
return Promise.resolve(mockGameSq42 as any);
198+
}
199+
return Promise.resolve(null);
200+
});
163201

164-
jest.spyOn(rolesRepository, 'findOne').mockResolvedValue(null);
165-
jest.spyOn(rolesRepository, 'create').mockReturnValue(mockRole as any);
166-
jest.spyOn(rolesRepository, 'save').mockResolvedValue(mockRole as any);
202+
jest.spyOn(rolesRepository, 'findOne').mockResolvedValue(mockRole as any);
167203

168204
jest.spyOn(organizationsRepository, 'findOne').mockResolvedValue(null);
169205
jest
@@ -172,10 +208,16 @@ describe('DatabaseSeederService', () => {
172208
jest
173209
.spyOn(organizationsRepository, 'save')
174210
.mockResolvedValue(mockOrganization as any);
211+
jest
212+
.spyOn(organizationsRepository, 'find')
213+
.mockResolvedValue([mockOrganization, mockOrgTwo, mockOrgThree] as any);
175214

176215
jest.spyOn(usersRepository, 'findOne').mockResolvedValue(null);
177216
jest.spyOn(usersRepository, 'create').mockReturnValue(mockUser as any);
178217
jest.spyOn(usersRepository, 'save').mockResolvedValue(mockUser as any);
218+
jest
219+
.spyOn(usersRepository, 'find')
220+
.mockResolvedValue([mockUser, mockOrgAdmin, mockMember] as any);
179221

180222
jest.spyOn(userOrgRolesRepository, 'findOne').mockResolvedValue(null);
181223
jest
@@ -189,12 +231,18 @@ describe('DatabaseSeederService', () => {
189231
});
190232

191233
it('should handle existing data gracefully', async () => {
192-
jest.spyOn(gamesRepository, 'findOne').mockResolvedValue(mockGame as any);
234+
jest
235+
.spyOn(gamesRepository, 'findOne')
236+
.mockResolvedValue(mockGameSc as any);
193237
jest.spyOn(rolesRepository, 'findOne').mockResolvedValue(mockRole as any);
194238
jest
195239
.spyOn(organizationsRepository, 'findOne')
196240
.mockResolvedValue(mockOrganization as any);
241+
jest
242+
.spyOn(organizationsRepository, 'find')
243+
.mockResolvedValue([mockOrganization] as any);
197244
jest.spyOn(usersRepository, 'findOne').mockResolvedValue(mockUser as any);
245+
jest.spyOn(usersRepository, 'find').mockResolvedValue([mockUser] as any);
198246
jest
199247
.spyOn(userOrgRolesRepository, 'findOne')
200248
.mockResolvedValue(mockUserOrgRole as any);

0 commit comments

Comments
 (0)