@mediaryorg/contracts 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,6 @@ export declare const PROTO_PATH: {
3
3
  readonly USER: string;
4
4
  readonly PROFILE: string;
5
5
  readonly MEDIA: string;
6
- readonly COLLECTION: string;
7
6
  readonly LIBRARY: string;
8
7
  readonly FAVORITE: string;
9
8
  readonly MEDIA_REQUEST: string;
@@ -8,7 +8,6 @@ exports.PROTO_PATH = {
8
8
  USER: (0, path_1.join)(PROTO_DIR, "user/v1/user.proto"),
9
9
  PROFILE: (0, path_1.join)(PROTO_DIR, "profile/v1/profile.proto"),
10
10
  MEDIA: (0, path_1.join)(PROTO_DIR, "media/v1/media.proto"),
11
- COLLECTION: (0, path_1.join)(PROTO_DIR, "collection/v1/collection.proto"),
12
11
  LIBRARY: (0, path_1.join)(PROTO_DIR, "library/v1/library.proto"),
13
12
  FAVORITE: (0, path_1.join)(PROTO_DIR, "favorite/v1/favorite.proto"),
14
13
  MEDIA_REQUEST: (0, path_1.join)(PROTO_DIR, "media_request/v1/media_request.proto"),
@@ -7,6 +7,7 @@
7
7
  /* eslint-disable */
8
8
  import { GrpcMethod, GrpcStreamMethod } from "@nestjs/microservices";
9
9
  import { Observable } from "rxjs";
10
+ import { MediaType } from "../../media/v1/media";
10
11
 
11
12
  export const protobufPackage = "favorite.v1";
12
13
 
@@ -53,7 +54,7 @@ export interface IsFavoriteResponse {
53
54
  export interface ListUserFavoritesRequest {
54
55
  userId: string;
55
56
  search?: string | undefined;
56
- collectionId?: string | undefined;
57
+ type?: MediaType | undefined;
57
58
  page: number;
58
59
  limit: number;
59
60
  sortBy: string;
@@ -7,7 +7,7 @@
7
7
  /* eslint-disable */
8
8
  import { GrpcMethod, GrpcStreamMethod } from "@nestjs/microservices";
9
9
  import { Observable } from "rxjs";
10
- import { Media, MediaSource } from "../../media/v1/media";
10
+ import { Media, MediaSource, MediaType } from "../../media/v1/media";
11
11
 
12
12
  export const protobufPackage = "library.v1";
13
13
 
@@ -69,7 +69,7 @@ export interface AddFromSearchResponse {
69
69
  export interface GetUserLibraryRequest {
70
70
  userId: string;
71
71
  statuses: LibraryItemStatus[];
72
- collectionName?: string | undefined;
72
+ type?: MediaType | undefined;
73
73
  source?: MediaSource | undefined;
74
74
  search?: string | undefined;
75
75
  genres: string[];
@@ -10,20 +10,47 @@ import { Observable } from "rxjs";
10
10
 
11
11
  export const protobufPackage = "media.v1";
12
12
 
13
- /** MediaSource mirrors prisma enum MediaSource. */
13
+ /**
14
+ * Provider the canonical metadata came from. Mirrors search.v1.SearchSource
15
+ * (minus LOCAL, which is not a persisted origin). CUSTOM is reserved for
16
+ * manually added media (media-request flow) with no external provider.
17
+ */
14
18
  export enum MediaSource {
15
19
  MEDIA_SOURCE_UNSPECIFIED = 0,
16
20
  MEDIA_SOURCE_TMDB = 1,
17
21
  MEDIA_SOURCE_GOOGLE_BOOKS = 2,
18
22
  MEDIA_SOURCE_MAL = 3,
19
- MEDIA_SOURCE_CUSTOM = 4,
23
+ MEDIA_SOURCE_ANILIST = 4,
24
+ MEDIA_SOURCE_MANGADEX = 5,
25
+ MEDIA_SOURCE_IGDB = 6,
26
+ MEDIA_SOURCE_RAWG = 7,
27
+ MEDIA_SOURCE_OPEN_LIBRARY = 8,
28
+ MEDIA_SOURCE_CUSTOM = 9,
20
29
  UNRECOGNIZED = -1,
21
30
  }
22
31
 
23
32
  /**
24
- * MediaData is the structured payload kept as JSON in the legacy database.
25
- * `custom_fields_json` contains the type-specific customFields object as a
26
- * JSON-encoded string so callers can extend without breaking the schema.
33
+ * Intrinsic kind of the media entry. Mirrors search.v1.SearchMediaType so a
34
+ * search hit maps 1:1 onto a catalog type. Replaces the legacy Collection
35
+ * concept (the 8 categories are now an attribute of the media itself).
36
+ */
37
+ export enum MediaType {
38
+ MEDIA_TYPE_UNSPECIFIED = 0,
39
+ MEDIA_TYPE_MOVIE = 1,
40
+ MEDIA_TYPE_SERIES = 2,
41
+ MEDIA_TYPE_BOOK = 3,
42
+ MEDIA_TYPE_ANIME = 4,
43
+ MEDIA_TYPE_MANGA = 5,
44
+ MEDIA_TYPE_MANHWA = 6,
45
+ MEDIA_TYPE_GAME = 7,
46
+ MEDIA_TYPE_KDRAMA = 8,
47
+ UNRECOGNIZED = -1,
48
+ }
49
+
50
+ /**
51
+ * Structured metadata kept as a JSON column. `custom_fields_json` carries the
52
+ * type-specific customFields object as a JSON-encoded string so callers can
53
+ * extend it without schema migrations.
27
54
  */
28
55
  export interface MediaData {
29
56
  title: string;
@@ -41,33 +68,27 @@ export interface MediaData {
41
68
 
42
69
  export interface Media {
43
70
  id: string;
71
+ type: MediaType;
44
72
  source: MediaSource;
45
73
  externalId: string;
46
74
  mediaData: MediaData | undefined;
47
75
  searchableTitle: string;
76
+ /** JSON object preserving every known provider id, e.g. {"tmdb":"27205"}. */
48
77
  externalIdsJson: string;
49
- collectionId: string;
78
+ /**
79
+ * Optional reference to the user who first added this entry (user-service).
80
+ * Purely informational; the catalog has no user ownership.
81
+ */
50
82
  addedById: string;
51
83
  createdAt: string;
52
84
  updatedAt: string;
53
85
  }
54
86
 
55
- export interface MediaCounts {
56
- library: number;
57
- reviews: number;
58
- favorites: number;
59
- }
60
-
61
- export interface MediaWithCounts {
62
- media: Media | undefined;
63
- counts: MediaCounts | undefined;
64
- }
65
-
66
87
  export interface CreateMediaRequest {
88
+ type: MediaType;
67
89
  source: MediaSource;
68
90
  externalId: string;
69
91
  mediaData: MediaData | undefined;
70
- collectionId: string;
71
92
  addedById: string;
72
93
  }
73
94
 
@@ -79,16 +100,8 @@ export interface GetMediaByIdRequest {
79
100
  mediaId: string;
80
101
  }
81
102
 
82
- export interface RatingDistributionEntry {
83
- rating: number;
84
- count: number;
85
- }
86
-
87
103
  export interface GetMediaByIdResponse {
88
104
  media: Media | undefined;
89
- counts: MediaCounts | undefined;
90
- averageRating: number;
91
- totalReviews: number;
92
105
  }
93
106
 
94
107
  export interface GetMediaByExternalIdRequest {
@@ -109,27 +122,12 @@ export interface FindDuplicatesResponse {
109
122
  items: Media[];
110
123
  }
111
124
 
112
- export interface GetMediaStatsRequest {
113
- mediaId: string;
114
- }
115
-
116
- export interface MediaStats {
117
- averageRating: number;
118
- totalReviews: number;
119
- ratingDistribution: RatingDistributionEntry[];
120
- }
121
-
122
- export interface GetMediaStatsResponse {
123
- media: Media | undefined;
124
- stats: MediaStats | undefined;
125
- }
126
-
127
125
  export interface UpdateMediaRequest {
128
126
  mediaId: string;
127
+ type?: MediaType | undefined;
129
128
  source?: MediaSource | undefined;
130
129
  externalId?: string | undefined;
131
130
  mediaData?: MediaData | undefined;
132
- collectionId?: string | undefined;
133
131
  }
134
132
 
135
133
  export interface UpdateMediaResponse {
@@ -145,7 +143,7 @@ export interface DeleteMediaResponse {
145
143
 
146
144
  export interface ListMediaRequest {
147
145
  search?: string | undefined;
148
- collectionId?: string | undefined;
146
+ type?: MediaType | undefined;
149
147
  source?: MediaSource | undefined;
150
148
  year?: number | undefined;
151
149
  page: number;
@@ -164,19 +162,20 @@ export interface PaginationMeta {
164
162
  }
165
163
 
166
164
  export interface ListMediaResponse {
167
- data: MediaWithCounts[];
165
+ data: Media[];
168
166
  meta: PaginationMeta | undefined;
169
167
  }
170
168
 
171
169
  export interface EnsureMediaFromSearchRequest {
172
170
  /**
173
171
  * Source identifier coming from search-service results
174
- * ("tmdb", "google_books", "mangadex", ..., or "local" for already-existing)
172
+ * ("tmdb", "google_books", "anilist", "mangadex", ...). "local" means the
173
+ * hit already references a catalog entry by id.
175
174
  */
176
175
  source: string;
177
- /** External id within that source (or media id when source == "local") */
176
+ /** External id within that source (or media id when source == "local"). */
178
177
  externalId: string;
179
- /** Search media type label ("movie"|"series"|"book"|"anime"|"manga"|"manhwa"|"game"|"kdrama") */
178
+ /** Search media-type label ("movie"|"series"|"book"|"anime"|"manga"|"manhwa"|"game"|"kdrama"). */
180
179
  mediaType: string;
181
180
  title: string;
182
181
  subtitle: string;
@@ -184,7 +183,7 @@ export interface EnsureMediaFromSearchRequest {
184
183
  year: string;
185
184
  rating?: number | undefined;
186
185
  genres: string[];
187
- /** User who initiated the action (becomes addedBy on creation). */
186
+ /** User who initiated the action (recorded as added_by_id on creation). */
188
187
  requestedByUserId: string;
189
188
  }
190
189
 
@@ -192,12 +191,20 @@ export interface EnsureMediaFromSearchResponse {
192
191
  media:
193
192
  | Media
194
193
  | undefined;
195
- /** True when the media row was just created, false when reused. */
194
+ /** True when the catalog entry was just created, false when reused. */
196
195
  created: boolean;
197
196
  }
198
197
 
199
198
  export const MEDIA_V1_PACKAGE_NAME = "media.v1";
200
199
 
200
+ /**
201
+ * MediaService owns the global media catalog ("storage"): a deduplicated,
202
+ * user-agnostic set of media entries that are reused across every user's
203
+ * lists/library. Adding media to a search/discover flow first checks this
204
+ * catalog before hitting external providers; removing media from a user's
205
+ * list never deletes it here.
206
+ */
207
+
201
208
  export interface MediaServiceClient {
202
209
  createMedia(request: CreateMediaRequest): Observable<CreateMediaResponse>;
203
210
 
@@ -207,8 +214,6 @@ export interface MediaServiceClient {
207
214
 
208
215
  findDuplicates(request: FindDuplicatesRequest): Observable<FindDuplicatesResponse>;
209
216
 
210
- getMediaStats(request: GetMediaStatsRequest): Observable<GetMediaStatsResponse>;
211
-
212
217
  updateMedia(request: UpdateMediaRequest): Observable<UpdateMediaResponse>;
213
218
 
214
219
  deleteMedia(request: DeleteMediaRequest): Observable<DeleteMediaResponse>;
@@ -216,14 +221,22 @@ export interface MediaServiceClient {
216
221
  listMedia(request: ListMediaRequest): Observable<ListMediaResponse>;
217
222
 
218
223
  /**
219
- * Cross-service: ensure media exists for a search hit.
220
- * Used by library/favorites/recommendation flows so they don't need to write
221
- * into the media database directly.
224
+ * Cross-service: ensure a catalog entry exists for a search/discover hit.
225
+ * Used by library/list/recommendation flows so they don't write into the
226
+ * catalog database directly. Returns the existing entry when found.
222
227
  */
223
228
 
224
229
  ensureMediaFromSearch(request: EnsureMediaFromSearchRequest): Observable<EnsureMediaFromSearchResponse>;
225
230
  }
226
231
 
232
+ /**
233
+ * MediaService owns the global media catalog ("storage"): a deduplicated,
234
+ * user-agnostic set of media entries that are reused across every user's
235
+ * lists/library. Adding media to a search/discover flow first checks this
236
+ * catalog before hitting external providers; removing media from a user's
237
+ * list never deletes it here.
238
+ */
239
+
227
240
  export interface MediaServiceController {
228
241
  createMedia(
229
242
  request: CreateMediaRequest,
@@ -241,10 +254,6 @@ export interface MediaServiceController {
241
254
  request: FindDuplicatesRequest,
242
255
  ): Promise<FindDuplicatesResponse> | Observable<FindDuplicatesResponse> | FindDuplicatesResponse;
243
256
 
244
- getMediaStats(
245
- request: GetMediaStatsRequest,
246
- ): Promise<GetMediaStatsResponse> | Observable<GetMediaStatsResponse> | GetMediaStatsResponse;
247
-
248
257
  updateMedia(
249
258
  request: UpdateMediaRequest,
250
259
  ): Promise<UpdateMediaResponse> | Observable<UpdateMediaResponse> | UpdateMediaResponse;
@@ -256,9 +265,9 @@ export interface MediaServiceController {
256
265
  listMedia(request: ListMediaRequest): Promise<ListMediaResponse> | Observable<ListMediaResponse> | ListMediaResponse;
257
266
 
258
267
  /**
259
- * Cross-service: ensure media exists for a search hit.
260
- * Used by library/favorites/recommendation flows so they don't need to write
261
- * into the media database directly.
268
+ * Cross-service: ensure a catalog entry exists for a search/discover hit.
269
+ * Used by library/list/recommendation flows so they don't write into the
270
+ * catalog database directly. Returns the existing entry when found.
262
271
  */
263
272
 
264
273
  ensureMediaFromSearch(
@@ -273,7 +282,6 @@ export function MediaServiceControllerMethods() {
273
282
  "getMediaById",
274
283
  "getMediaByExternalId",
275
284
  "findDuplicates",
276
- "getMediaStats",
277
285
  "updateMedia",
278
286
  "deleteMedia",
279
287
  "listMedia",
@@ -7,7 +7,7 @@
7
7
  /* eslint-disable */
8
8
  import { GrpcMethod, GrpcStreamMethod } from "@nestjs/microservices";
9
9
  import { Observable } from "rxjs";
10
- import { MediaData, MediaSource } from "../../media/v1/media";
10
+ import { MediaData, MediaSource, MediaType } from "../../media/v1/media";
11
11
 
12
12
  export const protobufPackage = "media_request.v1";
13
13
 
@@ -45,7 +45,7 @@ export interface MediaRequest {
45
45
  moderatedById: string;
46
46
  moderatedBy: MediaRequestUserRef | undefined;
47
47
  approvedMediaId: string;
48
- collectionId: string;
48
+ type: MediaType;
49
49
  createdAt: string;
50
50
  updatedAt: string;
51
51
  }
@@ -55,7 +55,7 @@ export interface CreateMediaRequestRequest {
55
55
  /** Currently only CUSTOM is accepted (TMDB content goes through search flow). */
56
56
  source?: MediaSource | undefined;
57
57
  mediaData: MediaData | undefined;
58
- collectionId: string;
58
+ type: MediaType;
59
59
  comment?: string | undefined;
60
60
  }
61
61
 
@@ -83,7 +83,7 @@ export interface GetMediaRequestByIdResponse {
83
83
  export interface ListMediaRequestsRequest {
84
84
  status?: ModerationStatus | undefined;
85
85
  source?: MediaSource | undefined;
86
- collectionId?: string | undefined;
86
+ type?: MediaType | undefined;
87
87
  search?: string | undefined;
88
88
  requestedById?: string | undefined;
89
89
  moderatedById?: string | undefined;
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@mediaryorg/contracts",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "description": "Protobuf definitions and generated TypeScript types",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
7
  "scripts": {
8
8
  "generate": "npm run generate:win",
9
- "generate:win": "protoc -I .\\proto .\\proto\\auth\\v1\\auth.proto .\\proto\\user\\v1\\user.proto .\\proto\\profile\\v1\\profile.proto .\\proto\\media\\v1\\media.proto .\\proto\\collection\\v1\\collection.proto .\\proto\\library\\v1\\library.proto .\\proto\\favorite\\v1\\favorite.proto .\\proto\\media_request\\v1\\media_request.proto .\\proto\\image\\v1\\image.proto .\\proto\\search\\v1\\search.proto .\\proto\\recommendation\\v1\\recommendation.proto --plugin=protoc-gen-ts_proto=.\\node_modules\\.bin\\protoc-gen-ts_proto.cmd --ts_proto_out=.\\generated --ts_proto_opt=nestJs=true,package=omit",
10
- "generate:ci": "protoc -I ./proto ./proto/auth/v1/auth.proto ./proto/user/v1/user.proto ./proto/profile/v1/profile.proto ./proto/media/v1/media.proto ./proto/collection/v1/collection.proto ./proto/library/v1/library.proto ./proto/favorite/v1/favorite.proto ./proto/media_request/v1/media_request.proto ./proto/image/v1/image.proto ./proto/search/v1/search.proto ./proto/recommendation/v1/recommendation.proto --plugin=protoc-gen-ts_proto=./node_modules/.bin/protoc-gen-ts_proto --ts_proto_out=./generated --ts_proto_opt=nestJs=true,package=omit",
9
+ "generate:win": "protoc -I .\\proto .\\proto\\auth\\v1\\auth.proto .\\proto\\user\\v1\\user.proto .\\proto\\profile\\v1\\profile.proto .\\proto\\media\\v1\\media.proto .\\proto\\library\\v1\\library.proto .\\proto\\favorite\\v1\\favorite.proto .\\proto\\media_request\\v1\\media_request.proto .\\proto\\image\\v1\\image.proto .\\proto\\search\\v1\\search.proto .\\proto\\recommendation\\v1\\recommendation.proto --plugin=protoc-gen-ts_proto=.\\node_modules\\.bin\\protoc-gen-ts_proto.cmd --ts_proto_out=.\\generated --ts_proto_opt=nestJs=true,package=omit",
10
+ "generate:ci": "protoc -I ./proto ./proto/auth/v1/auth.proto ./proto/user/v1/user.proto ./proto/profile/v1/profile.proto ./proto/media/v1/media.proto ./proto/library/v1/library.proto ./proto/favorite/v1/favorite.proto ./proto/media_request/v1/media_request.proto ./proto/image/v1/image.proto ./proto/search/v1/search.proto ./proto/recommendation/v1/recommendation.proto --plugin=protoc-gen-ts_proto=./node_modules/.bin/protoc-gen-ts_proto --ts_proto_out=./generated --ts_proto_opt=nestJs=true,package=omit",
11
11
  "build": "tsc -p tsconfig.build.json"
12
12
  },
13
13
  "files": [
@@ -2,6 +2,8 @@ syntax = "proto3";
2
2
 
3
3
  package favorite.v1;
4
4
 
5
+ import "media/v1/media.proto";
6
+
5
7
  service FavoriteService {
6
8
  rpc CheckMultipleFavorites(CheckMultipleFavoritesRequest) returns (CheckMultipleFavoritesResponse);
7
9
  rpc ToggleFavorite(ToggleFavoriteRequest) returns (ToggleFavoriteResponse);
@@ -48,7 +50,7 @@ message IsFavoriteResponse {
48
50
  message ListUserFavoritesRequest {
49
51
  string user_id = 1;
50
52
  optional string search = 2;
51
- optional string collection_id = 3;
53
+ optional media.v1.MediaType type = 3;
52
54
  int32 page = 4;
53
55
  int32 limit = 5;
54
56
  string sort_by = 6;
@@ -69,7 +69,7 @@ message AddFromSearchResponse {
69
69
  message GetUserLibraryRequest {
70
70
  string user_id = 1;
71
71
  repeated LibraryItemStatus statuses = 2;
72
- optional string collection_name = 3;
72
+ optional media.v1.MediaType type = 3;
73
73
  optional media.v1.MediaSource source = 4;
74
74
  optional string search = 5;
75
75
  repeated string genres = 6;
@@ -2,34 +2,60 @@ syntax = "proto3";
2
2
 
3
3
  package media.v1;
4
4
 
5
+ // MediaService owns the global media catalog ("storage"): a deduplicated,
6
+ // user-agnostic set of media entries that are reused across every user's
7
+ // lists/library. Adding media to a search/discover flow first checks this
8
+ // catalog before hitting external providers; removing media from a user's
9
+ // list never deletes it here.
5
10
  service MediaService {
6
11
  rpc CreateMedia(CreateMediaRequest) returns (CreateMediaResponse);
7
12
  rpc GetMediaById(GetMediaByIdRequest) returns (GetMediaByIdResponse);
8
13
  rpc GetMediaByExternalId(GetMediaByExternalIdRequest) returns (GetMediaByExternalIdResponse);
9
14
  rpc FindDuplicates(FindDuplicatesRequest) returns (FindDuplicatesResponse);
10
- rpc GetMediaStats(GetMediaStatsRequest) returns (GetMediaStatsResponse);
11
15
  rpc UpdateMedia(UpdateMediaRequest) returns (UpdateMediaResponse);
12
16
  rpc DeleteMedia(DeleteMediaRequest) returns (DeleteMediaResponse);
13
17
  rpc ListMedia(ListMediaRequest) returns (ListMediaResponse);
14
18
 
15
- // Cross-service: ensure media exists for a search hit.
16
- // Used by library/favorites/recommendation flows so they don't need to write
17
- // into the media database directly.
19
+ // Cross-service: ensure a catalog entry exists for a search/discover hit.
20
+ // Used by library/list/recommendation flows so they don't write into the
21
+ // catalog database directly. Returns the existing entry when found.
18
22
  rpc EnsureMediaFromSearch(EnsureMediaFromSearchRequest) returns (EnsureMediaFromSearchResponse);
19
23
  }
20
24
 
21
- // MediaSource mirrors prisma enum MediaSource.
25
+ // Provider the canonical metadata came from. Mirrors search.v1.SearchSource
26
+ // (minus LOCAL, which is not a persisted origin). CUSTOM is reserved for
27
+ // manually added media (media-request flow) with no external provider.
22
28
  enum MediaSource {
23
29
  MEDIA_SOURCE_UNSPECIFIED = 0;
24
30
  MEDIA_SOURCE_TMDB = 1;
25
31
  MEDIA_SOURCE_GOOGLE_BOOKS = 2;
26
32
  MEDIA_SOURCE_MAL = 3;
27
- MEDIA_SOURCE_CUSTOM = 4;
28
- }
29
-
30
- // MediaData is the structured payload kept as JSON in the legacy database.
31
- // `custom_fields_json` contains the type-specific customFields object as a
32
- // JSON-encoded string so callers can extend without breaking the schema.
33
+ MEDIA_SOURCE_ANILIST = 4;
34
+ MEDIA_SOURCE_MANGADEX = 5;
35
+ MEDIA_SOURCE_IGDB = 6;
36
+ MEDIA_SOURCE_RAWG = 7;
37
+ MEDIA_SOURCE_OPEN_LIBRARY = 8;
38
+ MEDIA_SOURCE_CUSTOM = 9;
39
+ }
40
+
41
+ // Intrinsic kind of the media entry. Mirrors search.v1.SearchMediaType so a
42
+ // search hit maps 1:1 onto a catalog type. Replaces the legacy Collection
43
+ // concept (the 8 categories are now an attribute of the media itself).
44
+ enum MediaType {
45
+ MEDIA_TYPE_UNSPECIFIED = 0;
46
+ MEDIA_TYPE_MOVIE = 1;
47
+ MEDIA_TYPE_SERIES = 2;
48
+ MEDIA_TYPE_BOOK = 3;
49
+ MEDIA_TYPE_ANIME = 4;
50
+ MEDIA_TYPE_MANGA = 5;
51
+ MEDIA_TYPE_MANHWA = 6;
52
+ MEDIA_TYPE_GAME = 7;
53
+ MEDIA_TYPE_KDRAMA = 8;
54
+ }
55
+
56
+ // Structured metadata kept as a JSON column. `custom_fields_json` carries the
57
+ // type-specific customFields object as a JSON-encoded string so callers can
58
+ // extend it without schema migrations.
33
59
  message MediaData {
34
60
  string title = 1;
35
61
  string original_title = 2;
@@ -46,33 +72,25 @@ message MediaData {
46
72
 
47
73
  message Media {
48
74
  string id = 1;
49
- MediaSource source = 2;
50
- string external_id = 3;
51
- MediaData media_data = 4;
52
- string searchable_title = 5;
53
- string external_ids_json = 6;
54
- string collection_id = 7;
75
+ MediaType type = 2;
76
+ MediaSource source = 3;
77
+ string external_id = 4;
78
+ MediaData media_data = 5;
79
+ string searchable_title = 6;
80
+ // JSON object preserving every known provider id, e.g. {"tmdb":"27205"}.
81
+ string external_ids_json = 7;
82
+ // Optional reference to the user who first added this entry (user-service).
83
+ // Purely informational; the catalog has no user ownership.
55
84
  string added_by_id = 8;
56
85
  string created_at = 9;
57
86
  string updated_at = 10;
58
87
  }
59
88
 
60
- message MediaCounts {
61
- int32 library = 1;
62
- int32 reviews = 2;
63
- int32 favorites = 3;
64
- }
65
-
66
- message MediaWithCounts {
67
- Media media = 1;
68
- MediaCounts counts = 2;
69
- }
70
-
71
89
  message CreateMediaRequest {
72
- MediaSource source = 1;
73
- string external_id = 2;
74
- MediaData media_data = 3;
75
- string collection_id = 4;
90
+ MediaType type = 1;
91
+ MediaSource source = 2;
92
+ string external_id = 3;
93
+ MediaData media_data = 4;
76
94
  string added_by_id = 5;
77
95
  }
78
96
 
@@ -84,16 +102,8 @@ message GetMediaByIdRequest {
84
102
  string media_id = 1;
85
103
  }
86
104
 
87
- message RatingDistributionEntry {
88
- double rating = 1;
89
- int32 count = 2;
90
- }
91
-
92
105
  message GetMediaByIdResponse {
93
106
  Media media = 1;
94
- MediaCounts counts = 2;
95
- double average_rating = 3;
96
- int32 total_reviews = 4;
97
107
  }
98
108
 
99
109
  message GetMediaByExternalIdRequest {
@@ -114,27 +124,12 @@ message FindDuplicatesResponse {
114
124
  repeated Media items = 1;
115
125
  }
116
126
 
117
- message GetMediaStatsRequest {
118
- string media_id = 1;
119
- }
120
-
121
- message MediaStats {
122
- double average_rating = 1;
123
- int32 total_reviews = 2;
124
- repeated RatingDistributionEntry rating_distribution = 3;
125
- }
126
-
127
- message GetMediaStatsResponse {
128
- Media media = 1;
129
- MediaStats stats = 2;
130
- }
131
-
132
127
  message UpdateMediaRequest {
133
128
  string media_id = 1;
134
- optional MediaSource source = 2;
135
- optional string external_id = 3;
136
- optional MediaData media_data = 4;
137
- optional string collection_id = 5;
129
+ optional MediaType type = 2;
130
+ optional MediaSource source = 3;
131
+ optional string external_id = 4;
132
+ optional MediaData media_data = 5;
138
133
  }
139
134
 
140
135
  message UpdateMediaResponse {
@@ -149,7 +144,7 @@ message DeleteMediaResponse {}
149
144
 
150
145
  message ListMediaRequest {
151
146
  optional string search = 1;
152
- optional string collection_id = 2;
147
+ optional MediaType type = 2;
153
148
  optional MediaSource source = 3;
154
149
  optional int32 year = 4;
155
150
  int32 page = 5;
@@ -168,17 +163,18 @@ message PaginationMeta {
168
163
  }
169
164
 
170
165
  message ListMediaResponse {
171
- repeated MediaWithCounts data = 1;
166
+ repeated Media data = 1;
172
167
  PaginationMeta meta = 2;
173
168
  }
174
169
 
175
170
  message EnsureMediaFromSearchRequest {
176
171
  // Source identifier coming from search-service results
177
- // ("tmdb", "google_books", "mangadex", ..., or "local" for already-existing)
172
+ // ("tmdb", "google_books", "anilist", "mangadex", ...). "local" means the
173
+ // hit already references a catalog entry by id.
178
174
  string source = 1;
179
- // External id within that source (or media id when source == "local")
175
+ // External id within that source (or media id when source == "local").
180
176
  string external_id = 2;
181
- // Search media type label ("movie"|"series"|"book"|"anime"|"manga"|"manhwa"|"game"|"kdrama")
177
+ // Search media-type label ("movie"|"series"|"book"|"anime"|"manga"|"manhwa"|"game"|"kdrama").
182
178
  string media_type = 3;
183
179
  string title = 4;
184
180
  string subtitle = 5;
@@ -186,12 +182,12 @@ message EnsureMediaFromSearchRequest {
186
182
  string year = 7;
187
183
  optional double rating = 8;
188
184
  repeated string genres = 9;
189
- // User who initiated the action (becomes addedBy on creation).
185
+ // User who initiated the action (recorded as added_by_id on creation).
190
186
  string requested_by_user_id = 10;
191
187
  }
192
188
 
193
189
  message EnsureMediaFromSearchResponse {
194
190
  Media media = 1;
195
- // True when the media row was just created, false when reused.
191
+ // True when the catalog entry was just created, false when reused.
196
192
  bool created = 2;
197
193
  }
@@ -48,7 +48,7 @@ message MediaRequest {
48
48
  string moderated_by_id = 12;
49
49
  MediaRequestUserRef moderated_by = 13;
50
50
  string approved_media_id = 14;
51
- string collection_id = 15;
51
+ media.v1.MediaType type = 15;
52
52
  string created_at = 16;
53
53
  string updated_at = 17;
54
54
  }
@@ -58,7 +58,7 @@ message CreateMediaRequestRequest {
58
58
  // Currently only CUSTOM is accepted (TMDB content goes through search flow).
59
59
  optional media.v1.MediaSource source = 2;
60
60
  media.v1.MediaData media_data = 3;
61
- string collection_id = 4;
61
+ media.v1.MediaType type = 4;
62
62
  optional string comment = 5;
63
63
  }
64
64
 
@@ -86,7 +86,7 @@ message GetMediaRequestByIdResponse {
86
86
  message ListMediaRequestsRequest {
87
87
  optional ModerationStatus status = 1;
88
88
  optional media.v1.MediaSource source = 2;
89
- optional string collection_id = 3;
89
+ optional media.v1.MediaType type = 3;
90
90
  optional string search = 4;
91
91
  optional string requested_by_id = 5;
92
92
  optional string moderated_by_id = 6;
@@ -1,235 +0,0 @@
1
- // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
- // versions:
3
- // protoc-gen-ts_proto v2.11.6
4
- // protoc v3.21.12
5
- // source: collection/v1/collection.proto
6
-
7
- /* eslint-disable */
8
- import { GrpcMethod, GrpcStreamMethod } from "@nestjs/microservices";
9
- import { Observable } from "rxjs";
10
-
11
- export const protobufPackage = "collection.v1";
12
-
13
- export interface Collection {
14
- id: string;
15
- name: string;
16
- createdAt: string;
17
- updatedAt: string;
18
- /** Number of Media records linked to the collection (returned by list/get). */
19
- mediaCount: number;
20
- }
21
-
22
- export interface CollectionWithUserStatus {
23
- collection: Collection | undefined;
24
- isAdded: boolean;
25
- }
26
-
27
- export interface UserCollection {
28
- id: string;
29
- userId: string;
30
- collectionId: string;
31
- collection: Collection | undefined;
32
- createdAt: string;
33
- updatedAt: string;
34
- }
35
-
36
- export interface ListCollectionsRequest {
37
- }
38
-
39
- export interface ListCollectionsResponse {
40
- items: Collection[];
41
- }
42
-
43
- export interface GetCollectionByIdRequest {
44
- collectionId: string;
45
- }
46
-
47
- export interface GetCollectionByIdResponse {
48
- collection: Collection | undefined;
49
- }
50
-
51
- export interface GetCollectionByNameRequest {
52
- name: string;
53
- }
54
-
55
- export interface GetCollectionByNameResponse {
56
- collection: Collection | undefined;
57
- }
58
-
59
- export interface SeedInitialCollectionsRequest {
60
- }
61
-
62
- export interface SeedInitialCollectionsResponse {
63
- items: Collection[];
64
- }
65
-
66
- export interface GetUserCollectionsRequest {
67
- userId: string;
68
- }
69
-
70
- export interface GetUserCollectionsResponse {
71
- items: Collection[];
72
- }
73
-
74
- export interface GetAllCollectionsWithUserStatusRequest {
75
- userId: string;
76
- }
77
-
78
- export interface GetAllCollectionsWithUserStatusResponse {
79
- items: CollectionWithUserStatus[];
80
- }
81
-
82
- export interface GetAvailableCollectionsRequest {
83
- userId: string;
84
- }
85
-
86
- export interface GetAvailableCollectionsResponse {
87
- items: Collection[];
88
- }
89
-
90
- export interface AddCollectionToUserRequest {
91
- userId: string;
92
- collectionId: string;
93
- }
94
-
95
- export interface AddCollectionToUserResponse {
96
- userCollection: UserCollection | undefined;
97
- }
98
-
99
- export interface RemoveCollectionFromUserRequest {
100
- userId: string;
101
- collectionId: string;
102
- }
103
-
104
- export interface RemoveCollectionFromUserResponse {
105
- collectionId: string;
106
- }
107
-
108
- export interface InitializeDefaultCollectionsRequest {
109
- userId: string;
110
- }
111
-
112
- export interface InitializeDefaultCollectionsResponse {
113
- items: UserCollection[];
114
- }
115
-
116
- export const COLLECTION_V1_PACKAGE_NAME = "collection.v1";
117
-
118
- export interface CollectionServiceClient {
119
- /** Global catalog of media collections (Movies, Series, Books, Manga, Manhwa, Anime, Games, KDramas). */
120
-
121
- listCollections(request: ListCollectionsRequest): Observable<ListCollectionsResponse>;
122
-
123
- getCollectionById(request: GetCollectionByIdRequest): Observable<GetCollectionByIdResponse>;
124
-
125
- getCollectionByName(request: GetCollectionByNameRequest): Observable<GetCollectionByNameResponse>;
126
-
127
- seedInitialCollections(request: SeedInitialCollectionsRequest): Observable<SeedInitialCollectionsResponse>;
128
-
129
- /** Per-user enabled collections. */
130
-
131
- getUserCollections(request: GetUserCollectionsRequest): Observable<GetUserCollectionsResponse>;
132
-
133
- getAllCollectionsWithUserStatus(
134
- request: GetAllCollectionsWithUserStatusRequest,
135
- ): Observable<GetAllCollectionsWithUserStatusResponse>;
136
-
137
- getAvailableCollections(request: GetAvailableCollectionsRequest): Observable<GetAvailableCollectionsResponse>;
138
-
139
- addCollectionToUser(request: AddCollectionToUserRequest): Observable<AddCollectionToUserResponse>;
140
-
141
- removeCollectionFromUser(request: RemoveCollectionFromUserRequest): Observable<RemoveCollectionFromUserResponse>;
142
-
143
- initializeDefaultCollections(
144
- request: InitializeDefaultCollectionsRequest,
145
- ): Observable<InitializeDefaultCollectionsResponse>;
146
- }
147
-
148
- export interface CollectionServiceController {
149
- /** Global catalog of media collections (Movies, Series, Books, Manga, Manhwa, Anime, Games, KDramas). */
150
-
151
- listCollections(
152
- request: ListCollectionsRequest,
153
- ): Promise<ListCollectionsResponse> | Observable<ListCollectionsResponse> | ListCollectionsResponse;
154
-
155
- getCollectionById(
156
- request: GetCollectionByIdRequest,
157
- ): Promise<GetCollectionByIdResponse> | Observable<GetCollectionByIdResponse> | GetCollectionByIdResponse;
158
-
159
- getCollectionByName(
160
- request: GetCollectionByNameRequest,
161
- ): Promise<GetCollectionByNameResponse> | Observable<GetCollectionByNameResponse> | GetCollectionByNameResponse;
162
-
163
- seedInitialCollections(
164
- request: SeedInitialCollectionsRequest,
165
- ):
166
- | Promise<SeedInitialCollectionsResponse>
167
- | Observable<SeedInitialCollectionsResponse>
168
- | SeedInitialCollectionsResponse;
169
-
170
- /** Per-user enabled collections. */
171
-
172
- getUserCollections(
173
- request: GetUserCollectionsRequest,
174
- ): Promise<GetUserCollectionsResponse> | Observable<GetUserCollectionsResponse> | GetUserCollectionsResponse;
175
-
176
- getAllCollectionsWithUserStatus(
177
- request: GetAllCollectionsWithUserStatusRequest,
178
- ):
179
- | Promise<GetAllCollectionsWithUserStatusResponse>
180
- | Observable<GetAllCollectionsWithUserStatusResponse>
181
- | GetAllCollectionsWithUserStatusResponse;
182
-
183
- getAvailableCollections(
184
- request: GetAvailableCollectionsRequest,
185
- ):
186
- | Promise<GetAvailableCollectionsResponse>
187
- | Observable<GetAvailableCollectionsResponse>
188
- | GetAvailableCollectionsResponse;
189
-
190
- addCollectionToUser(
191
- request: AddCollectionToUserRequest,
192
- ): Promise<AddCollectionToUserResponse> | Observable<AddCollectionToUserResponse> | AddCollectionToUserResponse;
193
-
194
- removeCollectionFromUser(
195
- request: RemoveCollectionFromUserRequest,
196
- ):
197
- | Promise<RemoveCollectionFromUserResponse>
198
- | Observable<RemoveCollectionFromUserResponse>
199
- | RemoveCollectionFromUserResponse;
200
-
201
- initializeDefaultCollections(
202
- request: InitializeDefaultCollectionsRequest,
203
- ):
204
- | Promise<InitializeDefaultCollectionsResponse>
205
- | Observable<InitializeDefaultCollectionsResponse>
206
- | InitializeDefaultCollectionsResponse;
207
- }
208
-
209
- export function CollectionServiceControllerMethods() {
210
- return function (constructor: Function) {
211
- const grpcMethods: string[] = [
212
- "listCollections",
213
- "getCollectionById",
214
- "getCollectionByName",
215
- "seedInitialCollections",
216
- "getUserCollections",
217
- "getAllCollectionsWithUserStatus",
218
- "getAvailableCollections",
219
- "addCollectionToUser",
220
- "removeCollectionFromUser",
221
- "initializeDefaultCollections",
222
- ];
223
- for (const method of grpcMethods) {
224
- const descriptor: any = Reflect.getOwnPropertyDescriptor(constructor.prototype, method);
225
- GrpcMethod("CollectionService", method)(constructor.prototype[method], method, descriptor);
226
- }
227
- const grpcStreamMethods: string[] = [];
228
- for (const method of grpcStreamMethods) {
229
- const descriptor: any = Reflect.getOwnPropertyDescriptor(constructor.prototype, method);
230
- GrpcStreamMethod("CollectionService", method)(constructor.prototype[method], method, descriptor);
231
- }
232
- };
233
- }
234
-
235
- export const COLLECTION_SERVICE_NAME = "CollectionService";
@@ -1,120 +0,0 @@
1
- syntax = "proto3";
2
-
3
- package collection.v1;
4
-
5
- service CollectionService {
6
- // Global catalog of media collections (Movies, Series, Books, Manga, Manhwa, Anime, Games, KDramas).
7
- rpc ListCollections(ListCollectionsRequest) returns (ListCollectionsResponse);
8
- rpc GetCollectionById(GetCollectionByIdRequest) returns (GetCollectionByIdResponse);
9
- rpc GetCollectionByName(GetCollectionByNameRequest) returns (GetCollectionByNameResponse);
10
- rpc SeedInitialCollections(SeedInitialCollectionsRequest) returns (SeedInitialCollectionsResponse);
11
-
12
- // Per-user enabled collections.
13
- rpc GetUserCollections(GetUserCollectionsRequest) returns (GetUserCollectionsResponse);
14
- rpc GetAllCollectionsWithUserStatus(GetAllCollectionsWithUserStatusRequest) returns (GetAllCollectionsWithUserStatusResponse);
15
- rpc GetAvailableCollections(GetAvailableCollectionsRequest) returns (GetAvailableCollectionsResponse);
16
- rpc AddCollectionToUser(AddCollectionToUserRequest) returns (AddCollectionToUserResponse);
17
- rpc RemoveCollectionFromUser(RemoveCollectionFromUserRequest) returns (RemoveCollectionFromUserResponse);
18
- rpc InitializeDefaultCollections(InitializeDefaultCollectionsRequest) returns (InitializeDefaultCollectionsResponse);
19
- }
20
-
21
- message Collection {
22
- string id = 1;
23
- string name = 2;
24
- string created_at = 3;
25
- string updated_at = 4;
26
- // Number of Media records linked to the collection (returned by list/get).
27
- int32 media_count = 5;
28
- }
29
-
30
- message CollectionWithUserStatus {
31
- Collection collection = 1;
32
- bool is_added = 2;
33
- }
34
-
35
- message UserCollection {
36
- string id = 1;
37
- string user_id = 2;
38
- string collection_id = 3;
39
- Collection collection = 4;
40
- string created_at = 5;
41
- string updated_at = 6;
42
- }
43
-
44
- message ListCollectionsRequest {}
45
-
46
- message ListCollectionsResponse {
47
- repeated Collection items = 1;
48
- }
49
-
50
- message GetCollectionByIdRequest {
51
- string collection_id = 1;
52
- }
53
-
54
- message GetCollectionByIdResponse {
55
- Collection collection = 1;
56
- }
57
-
58
- message GetCollectionByNameRequest {
59
- string name = 1;
60
- }
61
-
62
- message GetCollectionByNameResponse {
63
- Collection collection = 1;
64
- }
65
-
66
- message SeedInitialCollectionsRequest {}
67
-
68
- message SeedInitialCollectionsResponse {
69
- repeated Collection items = 1;
70
- }
71
-
72
- message GetUserCollectionsRequest {
73
- string user_id = 1;
74
- }
75
-
76
- message GetUserCollectionsResponse {
77
- repeated Collection items = 1;
78
- }
79
-
80
- message GetAllCollectionsWithUserStatusRequest {
81
- string user_id = 1;
82
- }
83
-
84
- message GetAllCollectionsWithUserStatusResponse {
85
- repeated CollectionWithUserStatus items = 1;
86
- }
87
-
88
- message GetAvailableCollectionsRequest {
89
- string user_id = 1;
90
- }
91
-
92
- message GetAvailableCollectionsResponse {
93
- repeated Collection items = 1;
94
- }
95
-
96
- message AddCollectionToUserRequest {
97
- string user_id = 1;
98
- string collection_id = 2;
99
- }
100
-
101
- message AddCollectionToUserResponse {
102
- UserCollection user_collection = 1;
103
- }
104
-
105
- message RemoveCollectionFromUserRequest {
106
- string user_id = 1;
107
- string collection_id = 2;
108
- }
109
-
110
- message RemoveCollectionFromUserResponse {
111
- string collection_id = 1;
112
- }
113
-
114
- message InitializeDefaultCollectionsRequest {
115
- string user_id = 1;
116
- }
117
-
118
- message InitializeDefaultCollectionsResponse {
119
- repeated UserCollection items = 1;
120
- }