A personal media tracker built on the AT Protocol opnshelf.xyz
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: change to xyz.opnshelf

+105 -106
+1 -1
README.md
··· 189 189 190 190 ## Architecture 191 191 192 - Users track movies which are stored as AT Protocol records in their personal data repository (`app.opnshelf.movie` lexicon). The backend subscribes to the AT Protocol firehose via TAP to index public records, enabling discovery and social features while users maintain ownership of their data. 192 + Users track movies which are stored as AT Protocol records in their personal data repository (`xyz.opnshelf.movie` lexicon). The backend subscribes to the AT Protocol firehose via TAP to index public records, enabling discovery and social features while users maintain ownership of their data. 193 193 194 194 ### Database Schema 195 195
+4 -4
backend/lexicons.json
··· 2 2 "version": 1, 3 3 "lexicons": [ 4 4 { 5 - "id": "app.opnshelf.movie", 5 + "id": "xyz.opnshelf.movie", 6 6 "path": "lexicons/app/opnshelf/movie.json", 7 7 "local": true 8 8 }, 9 9 { 10 - "id": "app.opnshelf.episode", 10 + "id": "xyz.opnshelf.episode", 11 11 "path": "lexicons/app/opnshelf/episode.json", 12 12 "local": true 13 13 }, 14 14 { 15 - "id": "app.opnshelf.list", 15 + "id": "xyz.opnshelf.list", 16 16 "path": "lexicons/app/opnshelf/list.json", 17 17 "local": true 18 18 }, 19 19 { 20 - "id": "app.opnshelf.listItem", 20 + "id": "xyz.opnshelf.listItem", 21 21 "path": "lexicons/app/opnshelf/listItem.json", 22 22 "local": true 23 23 }
+6 -6
backend/src/auth/auth.service.spec.ts
··· 302 302 client_uri: "http://127.0.0.1:3001", 303 303 redirect_uris: ["http://127.0.0.1:3001/auth/callback"], 304 304 scope: 305 - "atproto repo:app.opnshelf.movie repo:app.opnshelf.episode repo:app.opnshelf.list repo:app.opnshelf.listItem rpc:app.bsky.actor.getProfile?aud=did:web:api.bsky.app%23bsky_appview", 305 + "atproto repo:xyz.opnshelf.movie repo:xyz.opnshelf.episode repo:xyz.opnshelf.list repo:xyz.opnshelf.listItem rpc:app.bsky.actor.getProfile?aud=did:web:api.bsky.app%23bsky_appview", 306 306 grant_types: ["authorization_code", "refresh_token"], 307 307 response_types: ["code"], 308 308 application_type: "native", ··· 370 370 371 371 expect(client.authorize).toHaveBeenCalledWith("user.bsky.social", { 372 372 scope: 373 - "atproto repo:app.opnshelf.movie repo:app.opnshelf.episode repo:app.opnshelf.list repo:app.opnshelf.listItem rpc:app.bsky.actor.getProfile?aud=did:web:api.bsky.app%23bsky_appview", 373 + "atproto repo:xyz.opnshelf.movie repo:xyz.opnshelf.episode repo:xyz.opnshelf.list repo:xyz.opnshelf.listItem rpc:app.bsky.actor.getProfile?aud=did:web:api.bsky.app%23bsky_appview", 374 374 }); 375 375 expect(result).toBe(mockUrl.toString()); 376 376 }); ··· 470 470 471 471 // The OAUTH_SCOPE constant should include: 472 472 // - atproto: base AT Protocol access 473 - // - repo:app.opnshelf.movie: write movie records 474 - // - repo:app.opnshelf.list: write list records 475 - // - repo:app.opnshelf.listItem: write list item records 473 + // - repo:xyz.opnshelf.movie: write movie records 474 + // - repo:xyz.opnshelf.list: write list records 475 + // - repo:xyz.opnshelf.listItem: write list item records 476 476 // - rpc:app.bsky.actor.getProfile: fetch user profiles via Bluesky AppView 477 477 expect(authServiceModule.OAUTH_SCOPE).toBe( 478 - "atproto repo:app.opnshelf.movie repo:app.opnshelf.episode repo:app.opnshelf.list repo:app.opnshelf.listItem rpc:app.bsky.actor.getProfile?aud=did:web:api.bsky.app%23bsky_appview", 478 + "atproto repo:xyz.opnshelf.movie repo:xyz.opnshelf.episode repo:xyz.opnshelf.list repo:xyz.opnshelf.listItem rpc:app.bsky.actor.getProfile?aud=did:web:api.bsky.app%23bsky_appview", 479 479 ); 480 480 }); 481 481
+1 -1
backend/src/auth/auth.service.ts
··· 11 11 const BLUESKY_PUBLIC_API = "https://public.api.bsky.app/xrpc"; 12 12 13 13 export const OAUTH_SCOPE = 14 - "atproto repo:app.opnshelf.movie repo:app.opnshelf.episode repo:app.opnshelf.list repo:app.opnshelf.listItem rpc:app.bsky.actor.getProfile?aud=did:web:api.bsky.app%23bsky_appview"; 14 + "atproto repo:xyz.opnshelf.movie repo:xyz.opnshelf.episode repo:xyz.opnshelf.list repo:xyz.opnshelf.listItem rpc:app.bsky.actor.getProfile?aud=did:web:api.bsky.app%23bsky_appview"; 15 15 16 16 @Injectable() 17 17 export class AuthService implements OnModuleInit {
+10 -10
backend/src/ingester/ingester.service.spec.ts
··· 183 183 return recordHandler; 184 184 }; 185 185 186 - it("should upsert tracked movie for app.opnshelf.movie create", async () => { 186 + it("should upsert tracked movie for xyz.opnshelf.movie create", async () => { 187 187 const recordHandler = setupRecordHandler(); 188 188 mockPrismaService.user.findUnique.mockResolvedValue({ 189 189 did: "did:plc:abc123", ··· 196 196 action: "create", 197 197 did: "did:plc:abc123", 198 198 rev: "rev123", 199 - collection: "app.opnshelf.movie", 199 + collection: "xyz.opnshelf.movie", 200 200 rkey: "movie-123", 201 201 record: { 202 - $type: "app.opnshelf.movie", 202 + $type: "xyz.opnshelf.movie", 203 203 movieId: "123", 204 204 source: "tmdb", 205 205 watchedAt: "2024-01-15T10:00:00Z", ··· 220 220 ); 221 221 }); 222 222 223 - it("should upsert tracked episode for app.opnshelf.episode create", async () => { 223 + it("should upsert tracked episode for xyz.opnshelf.episode create", async () => { 224 224 const recordHandler = setupRecordHandler(); 225 225 mockPrismaService.user.findUnique.mockResolvedValue({ 226 226 did: "did:plc:abc123", ··· 233 233 action: "create", 234 234 did: "did:plc:abc123", 235 235 rev: "rev124", 236 - collection: "app.opnshelf.episode", 236 + collection: "xyz.opnshelf.episode", 237 237 rkey: "episode-456-1-1", 238 238 record: { 239 - $type: "app.opnshelf.episode", 239 + $type: "xyz.opnshelf.episode", 240 240 showId: "456", 241 241 seasonNumber: 1, 242 242 episodeNumber: 1, ··· 261 261 ); 262 262 }); 263 263 264 - it("should delete tracked episode on app.opnshelf.episode delete", async () => { 264 + it("should delete tracked episode on xyz.opnshelf.episode delete", async () => { 265 265 const recordHandler = setupRecordHandler(); 266 266 267 267 await recordHandler({ ··· 270 270 action: "delete", 271 271 did: "did:plc:abc123", 272 272 rev: "rev125", 273 - collection: "app.opnshelf.episode", 273 + collection: "xyz.opnshelf.episode", 274 274 rkey: "episode-456-1-1", 275 275 live: true, 276 276 }); ··· 292 292 action: "create", 293 293 did: "did:plc:abc123", 294 294 rev: "rev126", 295 - collection: "app.opnshelf.listItem", 295 + collection: "xyz.opnshelf.listItem", 296 296 rkey: "item-1", 297 297 record: { 298 - $type: "app.opnshelf.listItem", 298 + $type: "xyz.opnshelf.listItem", 299 299 listRkey: "watchlist", 300 300 mediaType: "show", 301 301 mediaId: "456",
+8 -8
backend/src/ingester/ingester.service.ts
··· 14 14 import { 15 15 $nsid as LIST_COLLECTION, 16 16 main as listSchema, 17 - } from "../lexicons/app/opnshelf/list"; 18 - import type { Main as ListRecord } from "../lexicons/app/opnshelf/list.defs"; 17 + } from "../lexicons/xyz/opnshelf/list"; 18 + import type { Main as ListRecord } from "../lexicons/xyz/opnshelf/list.defs"; 19 19 import { 20 20 $nsid as LIST_ITEM_COLLECTION, 21 21 main as listItemSchema, 22 - } from "../lexicons/app/opnshelf/listItem"; 23 - import type { Main as ListItemRecord } from "../lexicons/app/opnshelf/listItem.defs"; 22 + } from "../lexicons/xyz/opnshelf/listItem"; 23 + import type { Main as ListItemRecord } from "../lexicons/xyz/opnshelf/listItem.defs"; 24 24 import { 25 25 $nsid as EPISODE_COLLECTION, 26 26 main as episodeSchema, 27 - } from "../lexicons/app/opnshelf/episode"; 28 - import type { Main as EpisodeRecord } from "../lexicons/app/opnshelf/episode.defs"; 27 + } from "../lexicons/xyz/opnshelf/episode"; 28 + import type { Main as EpisodeRecord } from "../lexicons/xyz/opnshelf/episode.defs"; 29 29 import { 30 30 $nsid as MOVIE_COLLECTION, 31 31 main as movieSchema, 32 - } from "../lexicons/app/opnshelf/movie"; 33 - import type { Main as MovieRecord } from "../lexicons/app/opnshelf/movie.defs"; 32 + } from "../lexicons/xyz/opnshelf/movie"; 33 + import type { Main as MovieRecord } from "../lexicons/xyz/opnshelf/movie.defs"; 34 34 import { ListsService } from "../lists/lists.service"; 35 35 import { MoviesService } from "../movies/movies.service"; 36 36 import { PrismaService } from "../prisma/prisma.service";
-5
backend/src/lexicons/app.ts
··· 1 - /* 2 - * THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT. 3 - */ 4 - 5 - export * as opnshelf from './app/opnshelf.js'
backend/src/lexicons/app/opnshelf.ts backend/src/lexicons/xyz/opnshelf.ts
+2 -2
backend/src/lexicons/app/opnshelf/episode.defs.ts backend/src/lexicons/xyz/opnshelf/episode.defs.ts
··· 4 4 5 5 import { l } from '@atproto/lex' 6 6 7 - const $nsid = 'app.opnshelf.episode' 7 + const $nsid = 'xyz.opnshelf.episode' 8 8 9 9 export { $nsid } 10 10 11 11 /** A tracked show episode record for OpnShelf */ 12 12 type Main = { 13 - $type: 'app.opnshelf.episode' 13 + $type: 'xyz.opnshelf.episode' 14 14 15 15 /** 16 16 * TMDB show ID
backend/src/lexicons/app/opnshelf/episode.ts backend/src/lexicons/xyz/opnshelf/episode.ts
+2 -2
backend/src/lexicons/app/opnshelf/list.defs.ts backend/src/lexicons/xyz/opnshelf/list.defs.ts
··· 4 4 5 5 import { l } from '@atproto/lex' 6 6 7 - const $nsid = 'app.opnshelf.list' 7 + const $nsid = 'xyz.opnshelf.list' 8 8 9 9 export { $nsid } 10 10 11 11 /** A movie list for OpnShelf */ 12 12 type Main = { 13 - $type: 'app.opnshelf.list' 13 + $type: 'xyz.opnshelf.list' 14 14 15 15 /** 16 16 * Display name of the list
backend/src/lexicons/app/opnshelf/list.ts backend/src/lexicons/xyz/opnshelf/list.ts
+2 -2
backend/src/lexicons/app/opnshelf/listItem.defs.ts backend/src/lexicons/xyz/opnshelf/listItem.defs.ts
··· 4 4 5 5 import { l } from '@atproto/lex' 6 6 7 - const $nsid = 'app.opnshelf.listItem' 7 + const $nsid = 'xyz.opnshelf.listItem' 8 8 9 9 export { $nsid } 10 10 11 11 /** A media item in a list for OpnShelf */ 12 12 type Main = { 13 - $type: 'app.opnshelf.listItem' 13 + $type: 'xyz.opnshelf.listItem' 14 14 15 15 /** 16 16 * Rkey of the parent list
backend/src/lexicons/app/opnshelf/listItem.ts backend/src/lexicons/xyz/opnshelf/listItem.ts
+2 -2
backend/src/lexicons/app/opnshelf/movie.defs.ts backend/src/lexicons/xyz/opnshelf/movie.defs.ts
··· 4 4 5 5 import { l } from '@atproto/lex' 6 6 7 - const $nsid = 'app.opnshelf.movie' 7 + const $nsid = 'xyz.opnshelf.movie' 8 8 9 9 export { $nsid } 10 10 11 11 /** A tracked movie record for OpnShelf */ 12 12 type Main = { 13 - $type: 'app.opnshelf.movie' 13 + $type: 'xyz.opnshelf.movie' 14 14 15 15 /** 16 16 * TMDB movie ID
backend/src/lexicons/app/opnshelf/movie.ts backend/src/lexicons/xyz/opnshelf/movie.ts
+5
backend/src/lexicons/xyz.ts
··· 1 + /* 2 + * THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT. 3 + */ 4 + 5 + export * as opnshelf from './xyz/opnshelf.js'
+13 -13
backend/src/lists/lists.service.spec.ts
··· 25 25 }, 26 26 })); 27 27 28 - jest.mock("../lexicons/app/opnshelf/list", () => ({ 28 + jest.mock("../lexicons/xyz/opnshelf/list", () => ({ 29 29 main: { 30 30 build: jest.fn((data: Record<string, unknown>) => ({ 31 - $type: "app.opnshelf.list", 31 + $type: "xyz.opnshelf.list", 32 32 ...data, 33 33 })), 34 34 }, 35 - $nsid: "app.opnshelf.list", 35 + $nsid: "xyz.opnshelf.list", 36 36 })); 37 37 38 - jest.mock("../lexicons/app/opnshelf/listItem", () => ({ 38 + jest.mock("../lexicons/xyz/opnshelf/listItem", () => ({ 39 39 main: { 40 40 build: jest.fn((data: Record<string, unknown>) => ({ 41 - $type: "app.opnshelf.listItem", 41 + $type: "xyz.opnshelf.listItem", 42 42 ...data, 43 43 })), 44 44 }, 45 - $nsid: "app.opnshelf.listItem", 45 + $nsid: "xyz.opnshelf.listItem", 46 46 })); 47 47 48 48 import { MoviesService } from "../movies/movies.service"; ··· 172 172 mockPrismaService.listItem.count.mockResolvedValue(0); 173 173 mockPutRecord.mockResolvedValue({ 174 174 data: { 175 - uri: "at://did:plc:abc123/app.opnshelf.listItem/testtid123", 175 + uri: "at://did:plc:abc123/xyz.opnshelf.listItem/testtid123", 176 176 cid: "cid123", 177 177 }, 178 178 }); ··· 230 230 mockPrismaService.listItem.count.mockResolvedValue(1); 231 231 mockPutRecord.mockResolvedValue({ 232 232 data: { 233 - uri: "at://did:plc:abc123/app.opnshelf.listItem/testtid123", 233 + uri: "at://did:plc:abc123/xyz.opnshelf.listItem/testtid123", 234 234 cid: "cid123", 235 235 }, 236 236 }); ··· 301 301 302 302 expect(mockDeleteRecord).toHaveBeenCalledWith({ 303 303 repo: "did:plc:abc123", 304 - collection: "app.opnshelf.listItem", 304 + collection: "xyz.opnshelf.listItem", 305 305 rkey: "item-abc", 306 306 }); 307 307 expect(mockPrismaService.listItem.delete).toHaveBeenCalledWith({ ··· 316 316 mockMoviesService.getMovieByTMDBId.mockResolvedValue({ movieId: "123" }); 317 317 318 318 await service.indexListItemRecord( 319 - "at://did:plc:abc123/app.opnshelf.listItem/testtid123", 319 + "at://did:plc:abc123/xyz.opnshelf.listItem/testtid123", 320 320 "cid123", 321 321 "testtid123", 322 322 "did:plc:abc123", 323 323 { 324 - $type: "app.opnshelf.listItem", 324 + $type: "xyz.opnshelf.listItem", 325 325 listRkey: "watchlist-abc123", 326 326 mediaType: "movie", 327 327 mediaId: "123", ··· 347 347 mockShowsService.getShowByTMDBId.mockResolvedValue({ showId: "456" }); 348 348 349 349 await service.indexListItemRecord( 350 - "at://did:plc:abc123/app.opnshelf.listItem/testtid123", 350 + "at://did:plc:abc123/xyz.opnshelf.listItem/testtid123", 351 351 "cid123", 352 352 "testtid123", 353 353 "did:plc:abc123", 354 354 { 355 - $type: "app.opnshelf.listItem", 355 + $type: "xyz.opnshelf.listItem", 356 356 listRkey: "favorites-abc123", 357 357 mediaType: "show", 358 358 mediaId: "456",
+4 -4
backend/src/lists/lists.service.ts
··· 4 4 import { 5 5 $nsid as LIST_COLLECTION, 6 6 main as listSchema, 7 - } from "../lexicons/app/opnshelf/list"; 8 - import type { Main as ListRecord } from "../lexicons/app/opnshelf/list.defs"; 7 + } from "../lexicons/xyz/opnshelf/list"; 8 + import type { Main as ListRecord } from "../lexicons/xyz/opnshelf/list.defs"; 9 9 import { 10 10 $nsid as LIST_ITEM_COLLECTION, 11 11 main as listItemSchema, 12 - } from "../lexicons/app/opnshelf/listItem"; 13 - import type { Main as ListItemRecord } from "../lexicons/app/opnshelf/listItem.defs"; 12 + } from "../lexicons/xyz/opnshelf/listItem"; 13 + import type { Main as ListItemRecord } from "../lexicons/xyz/opnshelf/listItem.defs"; 14 14 import { MoviesService } from "../movies/movies.service"; 15 15 import { PrismaService } from "../prisma/prisma.service"; 16 16 import { ShowsService } from "../shows/shows.service";
+8 -8
backend/src/movies/movies.controller.spec.ts
··· 243 243 session: { did: "did:plc:abc123" }, 244 244 }; 245 245 const mockMarkWatchedResult = { 246 - uri: "at://did:plc:abc123/app.opnshelf.movie/movie-456-1234567890", 246 + uri: "at://did:plc:abc123/xyz.opnshelf.movie/movie-456-1234567890", 247 247 cid: "cid456", 248 248 rkey: "movie-456-1234567890", 249 249 record: { ··· 252 252 }; 253 253 const mockTrackedMovie = { 254 254 id: "tracked-1", 255 - uri: "at://did:plc:abc123/app.opnshelf.movie/movie-456-1234567890", 255 + uri: "at://did:plc:abc123/xyz.opnshelf.movie/movie-456-1234567890", 256 256 rkey: "movie-456-1234567890", 257 257 cid: "cid456", 258 258 userDid: "did:plc:abc123", ··· 278 278 undefined, 279 279 ); 280 280 expect(mockMoviesService.indexTrackedMovie).toHaveBeenCalledWith( 281 - "at://did:plc:abc123/app.opnshelf.movie/movie-456-1234567890", 281 + "at://did:plc:abc123/xyz.opnshelf.movie/movie-456-1234567890", 282 282 "cid456", 283 283 "movie-456-1234567890", 284 284 "did:plc:abc123", ··· 295 295 }; 296 296 const customDate = "2024-01-10T08:30:00Z"; 297 297 const mockMarkWatchedResult = { 298 - uri: "at://did:plc:abc123/app.opnshelf.movie/movie-456-1234567890", 298 + uri: "at://did:plc:abc123/xyz.opnshelf.movie/movie-456-1234567890", 299 299 cid: "cid456", 300 300 rkey: "movie-456-1234567890", 301 301 record: { ··· 304 304 }; 305 305 const mockTrackedMovie = { 306 306 id: "tracked-1", 307 - uri: "at://did:plc:abc123/app.opnshelf.movie/movie-456-1234567890", 307 + uri: "at://did:plc:abc123/xyz.opnshelf.movie/movie-456-1234567890", 308 308 rkey: "movie-456-1234567890", 309 309 cid: "cid456", 310 310 userDid: "did:plc:abc123", ··· 338 338 session: { did: "did:plc:abc123" }, 339 339 }; 340 340 const mockMarkWatchedResult = { 341 - uri: "at://did:plc:abc123/app.opnshelf.movie/movie-789-1234567890", 341 + uri: "at://did:plc:abc123/xyz.opnshelf.movie/movie-789-1234567890", 342 342 cid: "cid789", 343 343 rkey: "movie-789-1234567890", 344 344 record: { ··· 355 355 const result = await controller.markWatched("789", undefined, req); 356 356 357 357 expect(result).toEqual({ 358 - uri: "at://did:plc:abc123/app.opnshelf.movie/movie-789-1234567890", 358 + uri: "at://did:plc:abc123/xyz.opnshelf.movie/movie-789-1234567890", 359 359 cid: "cid789", 360 360 rkey: "movie-789-1234567890", 361 361 movieId: "789", ··· 369 369 session: { did: "did:plc:abc123" }, 370 370 }; 371 371 const mockMarkWatchedResult = { 372 - uri: "at://did:plc:abc123/app.opnshelf.movie/test-rkey", 372 + uri: "at://did:plc:abc123/xyz.opnshelf.movie/test-rkey", 373 373 cid: "test-cid", 374 374 rkey: "test-rkey", 375 375 record: {
+15 -15
backend/src/movies/movies.service.spec.ts
··· 24 24 25 25 // Mock lexicon module 26 26 const mockValidateMovieRecord = jest.fn(); 27 - jest.mock("../lexicons/app/opnshelf/movie", () => ({ 27 + jest.mock("../lexicons/xyz/opnshelf/movie", () => ({ 28 28 main: { 29 29 build: jest.fn((data: Record<string, unknown>) => ({ 30 - $type: "app.opnshelf.movie", 30 + $type: "xyz.opnshelf.movie", 31 31 ...data, 32 32 })), 33 33 }, 34 - $nsid: "app.opnshelf.movie", 34 + $nsid: "xyz.opnshelf.movie", 35 35 $validate: mockValidateMovieRecord, 36 36 })); 37 37 ··· 503 503 const mockSession = { did: "did:plc:abc123" }; 504 504 const mockPutRecordResponse = { 505 505 data: { 506 - uri: "at://did:plc:abc123/app.opnshelf.movie/movie-123-1234567890", 506 + uri: "at://did:plc:abc123/xyz.opnshelf.movie/movie-123-1234567890", 507 507 cid: "cid123", 508 508 }, 509 509 }; ··· 518 518 519 519 expect(mockPutRecord).toHaveBeenCalledWith({ 520 520 repo: "did:plc:abc123", 521 - collection: "app.opnshelf.movie", 521 + collection: "xyz.opnshelf.movie", 522 522 rkey: expect.stringMatching(/^[a-z0-9]+$/), 523 523 record: expect.objectContaining({ 524 - $type: "app.opnshelf.movie", 524 + $type: "xyz.opnshelf.movie", 525 525 movieId: "123", 526 526 source: "tmdb", 527 527 }), ··· 529 529 }); 530 530 expect(result.rkey).toMatch(/^[a-z0-9]+$/); 531 531 expect(result.record).toMatchObject({ 532 - $type: "app.opnshelf.movie", 532 + $type: "xyz.opnshelf.movie", 533 533 movieId: "123", 534 534 source: "tmdb", 535 535 }); ··· 540 540 const customDate = "2024-01-15T10:30:00Z"; 541 541 const mockPutRecordResponse = { 542 542 data: { 543 - uri: "at://did:plc:abc123/app.opnshelf.movie/movie-123-1234567890", 543 + uri: "at://did:plc:abc123/xyz.opnshelf.movie/movie-123-1234567890", 544 544 cid: "cid123", 545 545 }, 546 546 }; ··· 594 594 }); 595 595 expect(mockDeleteRecord).toHaveBeenCalledWith({ 596 596 repo: "did:plc:abc123", 597 - collection: "app.opnshelf.movie", 597 + collection: "xyz.opnshelf.movie", 598 598 rkey: "movie-123-1234567890", 599 599 }); 600 600 expect(result).toEqual({ ··· 628 628 expect(mockDeleteRecord).toHaveBeenCalledTimes(2); 629 629 expect(mockDeleteRecord).toHaveBeenNthCalledWith(1, { 630 630 repo: "did:plc:abc123", 631 - collection: "app.opnshelf.movie", 631 + collection: "xyz.opnshelf.movie", 632 632 rkey: "movie-123-1234567890", 633 633 }); 634 634 expect(mockDeleteRecord).toHaveBeenNthCalledWith(2, { 635 635 repo: "did:plc:abc123", 636 - collection: "app.opnshelf.movie", 636 + collection: "xyz.opnshelf.movie", 637 637 rkey: "movie-123-1234567880", 638 638 }); 639 639 expect(result).toEqual({ ··· 680 680 }; 681 681 const mockTrackedMovie = { 682 682 id: "tracked-1", 683 - uri: "at://did:plc:abc123/app.opnshelf.movie/movie-123-1234567890", 683 + uri: "at://did:plc:abc123/xyz.opnshelf.movie/movie-123-1234567890", 684 684 rkey: "movie-123-1234567890", 685 685 cid: "cid123", 686 686 userDid: "did:plc:abc123", ··· 698 698 mockPrismaService.trackedMovie.create.mockResolvedValue(mockTrackedMovie); 699 699 700 700 const result = await service.indexTrackedMovie( 701 - "at://did:plc:abc123/app.opnshelf.movie/movie-123-1234567890", 701 + "at://did:plc:abc123/xyz.opnshelf.movie/movie-123-1234567890", 702 702 "cid123", 703 703 "movie-123-1234567890", 704 704 "did:plc:abc123", ··· 712 712 expect(mockPrismaService.movie.upsert).toHaveBeenCalled(); 713 713 expect(mockPrismaService.trackedMovie.create).toHaveBeenCalledWith({ 714 714 data: expect.objectContaining({ 715 - uri: "at://did:plc:abc123/app.opnshelf.movie/movie-123-1234567890", 715 + uri: "at://did:plc:abc123/xyz.opnshelf.movie/movie-123-1234567890", 716 716 rkey: "movie-123-1234567890", 717 717 cid: "cid123", 718 718 userDid: "did:plc:abc123", ··· 732 732 733 733 await expect( 734 734 service.indexTrackedMovie( 735 - "at://did:plc:abc123/app.opnshelf.movie/movie-123-1234567890", 735 + "at://did:plc:abc123/xyz.opnshelf.movie/movie-123-1234567890", 736 736 "cid123", 737 737 "movie-123-1234567890", 738 738 "did:plc:abc123",
+3 -3
backend/src/movies/movies.service.ts
··· 5 5 import { 6 6 $nsid as COLLECTION, 7 7 main as movieSchema, 8 - } from "../lexicons/app/opnshelf/movie"; 9 - import type { Main as MovieRecord } from "../lexicons/app/opnshelf/movie.defs"; 8 + } from "../lexicons/xyz/opnshelf/movie"; 9 + import type { Main as MovieRecord } from "../lexicons/xyz/opnshelf/movie.defs"; 10 10 import { PrismaService } from "../prisma/prisma.service"; 11 11 import { ColorExtractionService } from "./color-extraction.service"; 12 12 ··· 346 346 collection: COLLECTION, 347 347 rkey, 348 348 record, 349 - validate: false, // PDS may not have app.opnshelf.movie lexicon 349 + validate: false, // PDS may not have xyz.opnshelf.movie lexicon 350 350 }); 351 351 352 352 this.logger.log(
+1 -1
backend/src/shows/shows.controller.spec.ts
··· 91 91 session: { did: "did:plc:abc123" }, 92 92 }; 93 93 const mockMarkResult = { 94 - uri: "at://did:plc:abc123/app.opnshelf.episode/abc", 94 + uri: "at://did:plc:abc123/xyz.opnshelf.episode/abc", 95 95 cid: "cid1", 96 96 rkey: "rk1", 97 97 record: { watchedAt: "2024-01-01T00:00:00Z" },
+7 -7
backend/src/shows/shows.service.spec.ts
··· 20 20 })), 21 21 })); 22 22 23 - jest.mock("../lexicons/app/opnshelf/episode", () => ({ 23 + jest.mock("../lexicons/xyz/opnshelf/episode", () => ({ 24 24 main: { 25 25 build: jest.fn((data: Record<string, unknown>) => ({ 26 - $type: "app.opnshelf.episode", 26 + $type: "xyz.opnshelf.episode", 27 27 ...data, 28 28 })), 29 29 }, 30 - $nsid: "app.opnshelf.episode", 30 + $nsid: "xyz.opnshelf.episode", 31 31 })); 32 32 33 33 import { PrismaService } from "../prisma/prisma.service"; ··· 137 137 const mockSession = { did: "did:plc:abc123" }; 138 138 mockPutRecord.mockResolvedValue({ 139 139 data: { 140 - uri: "at://did:plc:abc123/app.opnshelf.episode/abc", 140 + uri: "at://did:plc:abc123/xyz.opnshelf.episode/abc", 141 141 cid: "cid123", 142 142 }, 143 143 }); ··· 152 152 153 153 expect(mockPutRecord).toHaveBeenCalledWith( 154 154 expect.objectContaining({ 155 - collection: "app.opnshelf.episode", 155 + collection: "xyz.opnshelf.episode", 156 156 record: expect.objectContaining({ 157 - $type: "app.opnshelf.episode", 157 + $type: "xyz.opnshelf.episode", 158 158 showId: "123", 159 159 seasonNumber: 1, 160 160 episodeNumber: 2, ··· 182 182 183 183 expect(mockDeleteRecord).toHaveBeenCalledWith({ 184 184 repo: "did:plc:abc123", 185 - collection: "app.opnshelf.episode", 185 + collection: "xyz.opnshelf.episode", 186 186 rkey: "rk1", 187 187 }); 188 188 });
+2 -2
backend/src/shows/shows.service.ts
··· 5 5 import { 6 6 $nsid as COLLECTION, 7 7 main as episodeSchema, 8 - } from "../lexicons/app/opnshelf/episode"; 9 - import type { Main as EpisodeRecord } from "../lexicons/app/opnshelf/episode.defs"; 8 + } from "../lexicons/xyz/opnshelf/episode"; 9 + import type { Main as EpisodeRecord } from "../lexicons/xyz/opnshelf/episode.defs"; 10 10 import { ColorExtractionService } from "../movies/color-extraction.service"; 11 11 import { PrismaService } from "../prisma/prisma.service"; 12 12
+4 -5
backend/src/users/users.service.ts
··· 1 1 import { Agent } from "@atproto/api"; 2 2 import { Injectable, Logger, NotFoundException } from "@nestjs/common"; 3 - import { $nsid as EPISODE_COLLECTION } from "../lexicons/app/opnshelf/episode"; 4 - import { $nsid as LIST_COLLECTION } from "../lexicons/app/opnshelf/list"; 5 - import { $nsid as LIST_ITEM_COLLECTION } from "../lexicons/app/opnshelf/listItem"; 3 + import { $nsid as EPISODE_COLLECTION } from "../lexicons/xyz/opnshelf/episode"; 4 + import { $nsid as LIST_COLLECTION } from "../lexicons/xyz/opnshelf/list"; 5 + import { $nsid as LIST_ITEM_COLLECTION } from "../lexicons/xyz/opnshelf/listItem"; 6 + import { $nsid as MOVIE_COLLECTION } from "../lexicons/xyz/opnshelf/movie"; 6 7 import { PrismaService } from "../prisma/prisma.service"; 7 8 import type { 8 9 UpdateUserSettingsDto, 9 10 UserSettingsDto, 10 11 } from "./dto/user-settings.dto"; 11 - 12 - const MOVIE_COLLECTION = "app.opnshelf.movie"; 13 12 14 13 interface ATSession { 15 14 did: string;
+1 -1
docker-compose.yml
··· 6 6 - "2480:2480" 7 7 environment: 8 8 - TAP_ADMIN_PASSWORD=y29d6b572f17af0f150cd4b480bec85cf 9 - - TAP_COLLECTION_FILTERS=app.opnshelf.* 9 + - TAP_COLLECTION_FILTERS=xyz.opnshelf.* 10 10 volumes: 11 11 - tap-data:/data 12 12 depends_on:
+1 -1
lexicons/app/opnshelf/episode.json
··· 1 1 { 2 2 "lexicon": 1, 3 - "id": "app.opnshelf.episode", 3 + "id": "xyz.opnshelf.episode", 4 4 "defs": { 5 5 "main": { 6 6 "type": "record",
+1 -1
lexicons/app/opnshelf/list.json
··· 1 1 { 2 2 "lexicon": 1, 3 - "id": "app.opnshelf.list", 3 + "id": "xyz.opnshelf.list", 4 4 "defs": { 5 5 "main": { 6 6 "type": "record",
+1 -1
lexicons/app/opnshelf/listItem.json
··· 1 1 { 2 2 "lexicon": 1, 3 - "id": "app.opnshelf.listItem", 3 + "id": "xyz.opnshelf.listItem", 4 4 "defs": { 5 5 "main": { 6 6 "type": "record",
+1 -1
lexicons/app/opnshelf/movie.json
··· 1 1 { 2 2 "lexicon": 1, 3 - "id": "app.opnshelf.movie", 3 + "id": "xyz.opnshelf.movie", 4 4 "defs": { 5 5 "main": { 6 6 "type": "record",