Demo using Slices Network GraphQL Relay API to make a teal.fm client

add daily, weekly, monthly tops

+329 -55
schema.graphql
··· 29 29 joinedViaStarterPack: JSON 30 30 labels: JSON 31 31 pinnedPost: JSON 32 - appBskyFeedPostgate(limit: Int): [AppBskyFeedPostgate!]! 33 - appBskyFeedThreadgate(limit: Int): [AppBskyFeedThreadgate!]! 34 32 appBskyActorProfile: AppBskyActorProfile 35 - fmTealAlphaFeedPlay(limit: Int): [FmTealAlphaFeedPlay!]! 36 33 appBskyFeedPostgates(limit: Int): [AppBskyFeedPostgate!]! 34 + appBskyFeedPostgatesCount: Int! 37 35 appBskyFeedThreadgates(limit: Int): [AppBskyFeedThreadgate!]! 36 + appBskyFeedThreadgatesCount: Int! 38 37 appBskyActorProfiles(limit: Int): [AppBskyActorProfile!]! 38 + appBskyActorProfilesCount: Int! 39 39 fmTealAlphaFeedPlays(limit: Int): [FmTealAlphaFeedPlay!]! 40 + fmTealAlphaFeedPlaysCount: Int! 40 41 } 41 42 42 43 type AppBskyActorProfileAggregated { ··· 63 64 cursor: String! 64 65 } 65 66 67 + enum AppBskyActorProfileGroupByField { 68 + indexedAt 69 + avatar 70 + banner 71 + createdAt 72 + description 73 + displayName 74 + joinedViaStarterPack 75 + labels 76 + pinnedPost 77 + } 78 + 79 + input AppBskyActorProfileWhereInput { 80 + indexedAt: DateTimeFilter 81 + uri: StringFilter 82 + cid: StringFilter 83 + did: StringFilter 84 + collection: StringFilter 85 + actorHandle: StringFilter 86 + avatar: StringFilter 87 + banner: StringFilter 88 + createdAt: StringFilter 89 + description: StringFilter 90 + displayName: StringFilter 91 + joinedViaStarterPack: StringFilter 92 + labels: StringFilter 93 + pinnedPost: StringFilter 94 + } 95 + 66 96 type AppBskyEmbedExternal { 67 97 uri: String! 68 98 cid: String! ··· 70 100 indexedAt: String! 71 101 actorHandle: String 72 102 external: JSON! 73 - appBskyFeedPostgate(limit: Int): [AppBskyFeedPostgate!]! 74 - appBskyFeedThreadgate(limit: Int): [AppBskyFeedThreadgate!]! 75 103 appBskyActorProfile: AppBskyActorProfile 76 - fmTealAlphaFeedPlay(limit: Int): [FmTealAlphaFeedPlay!]! 77 104 appBskyFeedPostgates(limit: Int): [AppBskyFeedPostgate!]! 105 + appBskyFeedPostgatesCount: Int! 78 106 appBskyFeedThreadgates(limit: Int): [AppBskyFeedThreadgate!]! 107 + appBskyFeedThreadgatesCount: Int! 79 108 appBskyActorProfiles(limit: Int): [AppBskyActorProfile!]! 109 + appBskyActorProfilesCount: Int! 80 110 fmTealAlphaFeedPlays(limit: Int): [FmTealAlphaFeedPlay!]! 111 + fmTealAlphaFeedPlaysCount: Int! 81 112 } 82 113 83 114 type AppBskyEmbedExternalAggregated { ··· 97 128 cursor: String! 98 129 } 99 130 131 + enum AppBskyEmbedExternalGroupByField { 132 + indexedAt 133 + external 134 + } 135 + 136 + input AppBskyEmbedExternalWhereInput { 137 + indexedAt: DateTimeFilter 138 + uri: StringFilter 139 + cid: StringFilter 140 + did: StringFilter 141 + collection: StringFilter 142 + actorHandle: StringFilter 143 + external: StringFilter 144 + } 145 + 100 146 type AppBskyEmbedImages { 101 147 uri: String! 102 148 cid: String! ··· 104 150 indexedAt: String! 105 151 actorHandle: String 106 152 images: JSON! 107 - appBskyFeedPostgate(limit: Int): [AppBskyFeedPostgate!]! 108 - appBskyFeedThreadgate(limit: Int): [AppBskyFeedThreadgate!]! 109 153 appBskyActorProfile: AppBskyActorProfile 110 - fmTealAlphaFeedPlay(limit: Int): [FmTealAlphaFeedPlay!]! 111 154 appBskyFeedPostgates(limit: Int): [AppBskyFeedPostgate!]! 155 + appBskyFeedPostgatesCount: Int! 112 156 appBskyFeedThreadgates(limit: Int): [AppBskyFeedThreadgate!]! 157 + appBskyFeedThreadgatesCount: Int! 113 158 appBskyActorProfiles(limit: Int): [AppBskyActorProfile!]! 159 + appBskyActorProfilesCount: Int! 114 160 fmTealAlphaFeedPlays(limit: Int): [FmTealAlphaFeedPlay!]! 161 + fmTealAlphaFeedPlaysCount: Int! 115 162 } 116 163 117 164 type AppBskyEmbedImagesAggregated { ··· 131 178 cursor: String! 132 179 } 133 180 181 + enum AppBskyEmbedImagesGroupByField { 182 + indexedAt 183 + images 184 + } 185 + 186 + input AppBskyEmbedImagesWhereInput { 187 + indexedAt: DateTimeFilter 188 + uri: StringFilter 189 + cid: StringFilter 190 + did: StringFilter 191 + collection: StringFilter 192 + actorHandle: StringFilter 193 + images: StringFilter 194 + } 195 + 134 196 type AppBskyEmbedRecord { 135 197 uri: String! 136 198 cid: String! ··· 138 200 indexedAt: String! 139 201 actorHandle: String 140 202 record: JSON! 141 - appBskyFeedPostgate(limit: Int): [AppBskyFeedPostgate!]! 142 - appBskyFeedThreadgate(limit: Int): [AppBskyFeedThreadgate!]! 143 203 appBskyActorProfile: AppBskyActorProfile 144 - fmTealAlphaFeedPlay(limit: Int): [FmTealAlphaFeedPlay!]! 145 204 appBskyFeedPostgates(limit: Int): [AppBskyFeedPostgate!]! 205 + appBskyFeedPostgatesCount: Int! 146 206 appBskyFeedThreadgates(limit: Int): [AppBskyFeedThreadgate!]! 207 + appBskyFeedThreadgatesCount: Int! 147 208 appBskyActorProfiles(limit: Int): [AppBskyActorProfile!]! 209 + appBskyActorProfilesCount: Int! 148 210 fmTealAlphaFeedPlays(limit: Int): [FmTealAlphaFeedPlay!]! 211 + fmTealAlphaFeedPlaysCount: Int! 149 212 } 150 213 151 214 type AppBskyEmbedRecordAggregated { ··· 165 228 cursor: String! 166 229 } 167 230 231 + enum AppBskyEmbedRecordGroupByField { 232 + indexedAt 233 + record 234 + } 235 + 236 + input AppBskyEmbedRecordWhereInput { 237 + indexedAt: DateTimeFilter 238 + uri: StringFilter 239 + cid: StringFilter 240 + did: StringFilter 241 + collection: StringFilter 242 + actorHandle: StringFilter 243 + record: StringFilter 244 + } 245 + 168 246 type AppBskyEmbedRecordWithMedia { 169 247 uri: String! 170 248 cid: String! ··· 173 251 actorHandle: String 174 252 media: JSON! 175 253 record: JSON! 176 - appBskyFeedPostgate(limit: Int): [AppBskyFeedPostgate!]! 177 - appBskyFeedThreadgate(limit: Int): [AppBskyFeedThreadgate!]! 178 254 appBskyActorProfile: AppBskyActorProfile 179 - fmTealAlphaFeedPlay(limit: Int): [FmTealAlphaFeedPlay!]! 180 255 appBskyFeedPostgates(limit: Int): [AppBskyFeedPostgate!]! 256 + appBskyFeedPostgatesCount: Int! 181 257 appBskyFeedThreadgates(limit: Int): [AppBskyFeedThreadgate!]! 258 + appBskyFeedThreadgatesCount: Int! 182 259 appBskyActorProfiles(limit: Int): [AppBskyActorProfile!]! 260 + appBskyActorProfilesCount: Int! 183 261 fmTealAlphaFeedPlays(limit: Int): [FmTealAlphaFeedPlay!]! 262 + fmTealAlphaFeedPlaysCount: Int! 184 263 } 185 264 186 265 type AppBskyEmbedRecordWithMediaAggregated { ··· 201 280 cursor: String! 202 281 } 203 282 283 + enum AppBskyEmbedRecordWithMediaGroupByField { 284 + indexedAt 285 + media 286 + record 287 + } 288 + 289 + input AppBskyEmbedRecordWithMediaWhereInput { 290 + indexedAt: DateTimeFilter 291 + uri: StringFilter 292 + cid: StringFilter 293 + did: StringFilter 294 + collection: StringFilter 295 + actorHandle: StringFilter 296 + media: StringFilter 297 + record: StringFilter 298 + } 299 + 204 300 type AppBskyEmbedVideo { 205 301 uri: String! 206 302 cid: String! ··· 211 307 aspectRatio: JSON 212 308 captions: JSON 213 309 video: Blob! 214 - appBskyFeedPostgate(limit: Int): [AppBskyFeedPostgate!]! 215 - appBskyFeedThreadgate(limit: Int): [AppBskyFeedThreadgate!]! 216 310 appBskyActorProfile: AppBskyActorProfile 217 - fmTealAlphaFeedPlay(limit: Int): [FmTealAlphaFeedPlay!]! 218 311 appBskyFeedPostgates(limit: Int): [AppBskyFeedPostgate!]! 312 + appBskyFeedPostgatesCount: Int! 219 313 appBskyFeedThreadgates(limit: Int): [AppBskyFeedThreadgate!]! 314 + appBskyFeedThreadgatesCount: Int! 220 315 appBskyActorProfiles(limit: Int): [AppBskyActorProfile!]! 316 + appBskyActorProfilesCount: Int! 221 317 fmTealAlphaFeedPlays(limit: Int): [FmTealAlphaFeedPlay!]! 318 + fmTealAlphaFeedPlaysCount: Int! 222 319 } 223 320 224 321 type AppBskyEmbedVideoAggregated { ··· 241 338 cursor: String! 242 339 } 243 340 341 + enum AppBskyEmbedVideoGroupByField { 342 + indexedAt 343 + alt 344 + aspectRatio 345 + captions 346 + video 347 + } 348 + 349 + input AppBskyEmbedVideoWhereInput { 350 + indexedAt: DateTimeFilter 351 + uri: StringFilter 352 + cid: StringFilter 353 + did: StringFilter 354 + collection: StringFilter 355 + actorHandle: StringFilter 356 + alt: StringFilter 357 + aspectRatio: StringFilter 358 + captions: StringFilter 359 + video: StringFilter 360 + } 361 + 244 362 type AppBskyFeedPostgate { 245 363 uri: String! 246 364 cid: String! ··· 251 369 detachedEmbeddingUris: [String] 252 370 embeddingRules: JSON 253 371 post: String! 254 - appBskyFeedPostgate(limit: Int): [AppBskyFeedPostgate!]! 255 - appBskyFeedThreadgate(limit: Int): [AppBskyFeedThreadgate!]! 372 + appBskyFeedPostgate: AppBskyFeedPostgate 373 + appBskyFeedThreadgate: AppBskyFeedThreadgate 256 374 appBskyActorProfile: AppBskyActorProfile 257 - fmTealAlphaFeedPlay(limit: Int): [FmTealAlphaFeedPlay!]! 375 + fmTealAlphaFeedPlay: FmTealAlphaFeedPlay 258 376 appBskyFeedPostgates(limit: Int): [AppBskyFeedPostgate!]! 377 + appBskyFeedPostgatesCount: Int! 259 378 appBskyFeedThreadgates(limit: Int): [AppBskyFeedThreadgate!]! 379 + appBskyFeedThreadgatesCount: Int! 260 380 appBskyActorProfiles(limit: Int): [AppBskyActorProfile!]! 381 + appBskyActorProfilesCount: Int! 261 382 fmTealAlphaFeedPlays(limit: Int): [FmTealAlphaFeedPlay!]! 383 + fmTealAlphaFeedPlaysCount: Int! 262 384 } 263 385 264 386 type AppBskyFeedPostgateAggregated { ··· 281 403 cursor: String! 282 404 } 283 405 406 + enum AppBskyFeedPostgateGroupByField { 407 + indexedAt 408 + createdAt 409 + detachedEmbeddingUris 410 + embeddingRules 411 + post 412 + } 413 + 414 + input AppBskyFeedPostgateWhereInput { 415 + indexedAt: DateTimeFilter 416 + uri: StringFilter 417 + cid: StringFilter 418 + did: StringFilter 419 + collection: StringFilter 420 + actorHandle: StringFilter 421 + createdAt: StringFilter 422 + detachedEmbeddingUris: StringFilter 423 + embeddingRules: StringFilter 424 + post: StringFilter 425 + } 426 + 284 427 type AppBskyFeedThreadgate { 285 428 uri: String! 286 429 cid: String! ··· 291 434 createdAt: String! 292 435 hiddenReplies: [String] 293 436 post: String! 294 - appBskyFeedPostgate(limit: Int): [AppBskyFeedPostgate!]! 295 - appBskyFeedThreadgate(limit: Int): [AppBskyFeedThreadgate!]! 437 + appBskyFeedPostgate: AppBskyFeedPostgate 438 + appBskyFeedThreadgate: AppBskyFeedThreadgate 296 439 appBskyActorProfile: AppBskyActorProfile 297 - fmTealAlphaFeedPlay(limit: Int): [FmTealAlphaFeedPlay!]! 440 + fmTealAlphaFeedPlay: FmTealAlphaFeedPlay 298 441 appBskyFeedPostgates(limit: Int): [AppBskyFeedPostgate!]! 442 + appBskyFeedPostgatesCount: Int! 299 443 appBskyFeedThreadgates(limit: Int): [AppBskyFeedThreadgate!]! 444 + appBskyFeedThreadgatesCount: Int! 300 445 appBskyActorProfiles(limit: Int): [AppBskyActorProfile!]! 446 + appBskyActorProfilesCount: Int! 301 447 fmTealAlphaFeedPlays(limit: Int): [FmTealAlphaFeedPlay!]! 448 + fmTealAlphaFeedPlaysCount: Int! 302 449 } 303 450 304 451 type AppBskyFeedThreadgateAggregated { ··· 319 466 type AppBskyFeedThreadgateEdge { 320 467 node: AppBskyFeedThreadgate! 321 468 cursor: String! 469 + } 470 + 471 + enum AppBskyFeedThreadgateGroupByField { 472 + indexedAt 473 + allow 474 + createdAt 475 + hiddenReplies 476 + post 477 + } 478 + 479 + input AppBskyFeedThreadgateWhereInput { 480 + indexedAt: DateTimeFilter 481 + uri: StringFilter 482 + cid: StringFilter 483 + did: StringFilter 484 + collection: StringFilter 485 + actorHandle: StringFilter 486 + allow: StringFilter 487 + createdAt: StringFilter 488 + hiddenReplies: StringFilter 489 + post: StringFilter 322 490 } 323 491 324 492 type AppBskyRichtextFacet { ··· 329 497 actorHandle: String 330 498 features: JSON! 331 499 index: JSON! 332 - appBskyFeedPostgate(limit: Int): [AppBskyFeedPostgate!]! 333 - appBskyFeedThreadgate(limit: Int): [AppBskyFeedThreadgate!]! 334 500 appBskyActorProfile: AppBskyActorProfile 335 - fmTealAlphaFeedPlay(limit: Int): [FmTealAlphaFeedPlay!]! 336 501 appBskyFeedPostgates(limit: Int): [AppBskyFeedPostgate!]! 502 + appBskyFeedPostgatesCount: Int! 337 503 appBskyFeedThreadgates(limit: Int): [AppBskyFeedThreadgate!]! 504 + appBskyFeedThreadgatesCount: Int! 338 505 appBskyActorProfiles(limit: Int): [AppBskyActorProfile!]! 506 + appBskyActorProfilesCount: Int! 339 507 fmTealAlphaFeedPlays(limit: Int): [FmTealAlphaFeedPlay!]! 508 + fmTealAlphaFeedPlaysCount: Int! 340 509 } 341 510 342 511 type AppBskyRichtextFacetAggregated { ··· 355 524 type AppBskyRichtextFacetEdge { 356 525 node: AppBskyRichtextFacet! 357 526 cursor: String! 527 + } 528 + 529 + enum AppBskyRichtextFacetGroupByField { 530 + indexedAt 531 + features 532 + index 533 + } 534 + 535 + input AppBskyRichtextFacetWhereInput { 536 + indexedAt: DateTimeFilter 537 + uri: StringFilter 538 + cid: StringFilter 539 + did: StringFilter 540 + collection: StringFilter 541 + actorHandle: StringFilter 542 + features: StringFilter 543 + index: StringFilter 358 544 } 359 545 360 546 type Blob { ··· 374 560 actorHandle: String 375 561 cid: String! 376 562 uri: String! 377 - appBskyFeedPostgate(limit: Int): [AppBskyFeedPostgate!]! 378 - appBskyFeedThreadgate(limit: Int): [AppBskyFeedThreadgate!]! 563 + appBskyFeedPostgate: AppBskyFeedPostgate 564 + appBskyFeedThreadgate: AppBskyFeedThreadgate 379 565 appBskyActorProfile: AppBskyActorProfile 380 - fmTealAlphaFeedPlay(limit: Int): [FmTealAlphaFeedPlay!]! 566 + fmTealAlphaFeedPlay: FmTealAlphaFeedPlay 381 567 appBskyFeedPostgates(limit: Int): [AppBskyFeedPostgate!]! 568 + appBskyFeedPostgatesCount: Int! 382 569 appBskyFeedThreadgates(limit: Int): [AppBskyFeedThreadgate!]! 570 + appBskyFeedThreadgatesCount: Int! 383 571 appBskyActorProfiles(limit: Int): [AppBskyActorProfile!]! 572 + appBskyActorProfilesCount: Int! 384 573 fmTealAlphaFeedPlays(limit: Int): [FmTealAlphaFeedPlay!]! 574 + fmTealAlphaFeedPlaysCount: Int! 385 575 } 386 576 387 577 type ComAtprotoRepoStrongRefAggregated { ··· 402 592 cursor: String! 403 593 } 404 594 595 + enum ComAtprotoRepoStrongRefGroupByField { 596 + indexedAt 597 + cid 598 + uri 599 + } 600 + 601 + input ComAtprotoRepoStrongRefWhereInput { 602 + indexedAt: DateTimeFilter 603 + did: StringFilter 604 + collection: StringFilter 605 + actorHandle: StringFilter 606 + cid: StringFilter 607 + uri: StringFilter 608 + } 609 + 610 + input DateTimeFilter { 611 + eq: String 612 + gt: String 613 + gte: String 614 + lt: String 615 + lte: String 616 + } 617 + 405 618 type FmTealAlphaFeedPlay { 406 619 uri: String! 407 620 cid: String! ··· 422 635 submissionClientAgent: String 423 636 trackMbId: String 424 637 trackName: String! 425 - appBskyFeedPostgate(limit: Int): [AppBskyFeedPostgate!]! 426 - appBskyFeedThreadgate(limit: Int): [AppBskyFeedThreadgate!]! 427 638 appBskyActorProfile: AppBskyActorProfile 428 - fmTealAlphaFeedPlay(limit: Int): [FmTealAlphaFeedPlay!]! 429 639 appBskyFeedPostgates(limit: Int): [AppBskyFeedPostgate!]! 640 + appBskyFeedPostgatesCount: Int! 430 641 appBskyFeedThreadgates(limit: Int): [AppBskyFeedThreadgate!]! 642 + appBskyFeedThreadgatesCount: Int! 431 643 appBskyActorProfiles(limit: Int): [AppBskyActorProfile!]! 644 + appBskyActorProfilesCount: Int! 432 645 fmTealAlphaFeedPlays(limit: Int): [FmTealAlphaFeedPlay!]! 646 + fmTealAlphaFeedPlaysCount: Int! 433 647 } 434 648 435 649 type FmTealAlphaFeedPlayAggregated { ··· 462 676 cursor: String! 463 677 } 464 678 679 + enum FmTealAlphaFeedPlayGroupByField { 680 + indexedAt 681 + artistMbIds 682 + artistNames 683 + artists 684 + duration 685 + isrc 686 + musicServiceBaseDomain 687 + originUrl 688 + playedTime 689 + recordingMbId 690 + releaseMbId 691 + releaseName 692 + submissionClientAgent 693 + trackMbId 694 + trackName 695 + } 696 + 697 + input FmTealAlphaFeedPlayWhereInput { 698 + indexedAt: DateTimeFilter 699 + uri: StringFilter 700 + cid: StringFilter 701 + did: StringFilter 702 + collection: StringFilter 703 + actorHandle: StringFilter 704 + artistMbIds: StringFilter 705 + artistNames: StringFilter 706 + artists: StringFilter 707 + duration: IntFilter 708 + isrc: StringFilter 709 + musicServiceBaseDomain: StringFilter 710 + originUrl: StringFilter 711 + playedTime: StringFilter 712 + recordingMbId: StringFilter 713 + releaseMbId: StringFilter 714 + releaseName: StringFilter 715 + submissionClientAgent: StringFilter 716 + trackMbId: StringFilter 717 + trackName: StringFilter 718 + } 719 + 720 + input IntFilter { 721 + eq: Int 722 + in: [Int] 723 + gt: Int 724 + gte: Int 725 + lt: Int 726 + lte: Int 727 + } 728 + 465 729 scalar JSON 466 730 467 731 type Mutation { ··· 478 742 479 743 type Query { 480 744 """Query app.bsky.embed.record records""" 481 - appBskyEmbedRecords(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: JSON): AppBskyEmbedRecordConnection! 745 + appBskyEmbedRecords(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: AppBskyEmbedRecordWhereInput): AppBskyEmbedRecordConnection! 482 746 483 747 """ 484 748 Aggregated query for app.bsky.embed.record records with GROUP BY support 485 749 """ 486 - appBskyEmbedRecordsAggregated(groupBy: [String!]!, where: JSON, orderBy: AggregationOrderBy, limit: Int): [AppBskyEmbedRecordAggregated!]! 750 + appBskyEmbedRecordsAggregated(groupBy: [AppBskyEmbedRecordGroupByField!], where: AppBskyEmbedRecordWhereInput, orderBy: AggregationOrderBy, limit: Int): [AppBskyEmbedRecordAggregated!]! 487 751 488 752 """Query app.bsky.embed.images records""" 489 - appBskyEmbedImageses(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: JSON): AppBskyEmbedImagesConnection! 753 + appBskyEmbedImageses(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: AppBskyEmbedImagesWhereInput): AppBskyEmbedImagesConnection! 490 754 491 755 """ 492 756 Aggregated query for app.bsky.embed.images records with GROUP BY support 493 757 """ 494 - appBskyEmbedImagesesAggregated(groupBy: [String!]!, where: JSON, orderBy: AggregationOrderBy, limit: Int): [AppBskyEmbedImagesAggregated!]! 758 + appBskyEmbedImagesesAggregated(groupBy: [AppBskyEmbedImagesGroupByField!], where: AppBskyEmbedImagesWhereInput, orderBy: AggregationOrderBy, limit: Int): [AppBskyEmbedImagesAggregated!]! 495 759 496 760 """Query app.bsky.embed.video records""" 497 - appBskyEmbedVideos(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: JSON): AppBskyEmbedVideoConnection! 761 + appBskyEmbedVideos(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: AppBskyEmbedVideoWhereInput): AppBskyEmbedVideoConnection! 498 762 499 763 """ 500 764 Aggregated query for app.bsky.embed.video records with GROUP BY support 501 765 """ 502 - appBskyEmbedVideosAggregated(groupBy: [String!]!, where: JSON, orderBy: AggregationOrderBy, limit: Int): [AppBskyEmbedVideoAggregated!]! 766 + appBskyEmbedVideosAggregated(groupBy: [AppBskyEmbedVideoGroupByField!], where: AppBskyEmbedVideoWhereInput, orderBy: AggregationOrderBy, limit: Int): [AppBskyEmbedVideoAggregated!]! 503 767 504 768 """Query app.bsky.embed.recordWithMedia records""" 505 - appBskyEmbedRecordWithMedias(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: JSON): AppBskyEmbedRecordWithMediaConnection! 769 + appBskyEmbedRecordWithMedias(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: AppBskyEmbedRecordWithMediaWhereInput): AppBskyEmbedRecordWithMediaConnection! 506 770 507 771 """ 508 772 Aggregated query for app.bsky.embed.recordWithMedia records with GROUP BY support 509 773 """ 510 - appBskyEmbedRecordWithMediasAggregated(groupBy: [String!]!, where: JSON, orderBy: AggregationOrderBy, limit: Int): [AppBskyEmbedRecordWithMediaAggregated!]! 774 + appBskyEmbedRecordWithMediasAggregated(groupBy: [AppBskyEmbedRecordWithMediaGroupByField!], where: AppBskyEmbedRecordWithMediaWhereInput, orderBy: AggregationOrderBy, limit: Int): [AppBskyEmbedRecordWithMediaAggregated!]! 511 775 512 776 """Query app.bsky.embed.external records""" 513 - appBskyEmbedExternals(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: JSON): AppBskyEmbedExternalConnection! 777 + appBskyEmbedExternals(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: AppBskyEmbedExternalWhereInput): AppBskyEmbedExternalConnection! 514 778 515 779 """ 516 780 Aggregated query for app.bsky.embed.external records with GROUP BY support 517 781 """ 518 - appBskyEmbedExternalsAggregated(groupBy: [String!]!, where: JSON, orderBy: AggregationOrderBy, limit: Int): [AppBskyEmbedExternalAggregated!]! 782 + appBskyEmbedExternalsAggregated(groupBy: [AppBskyEmbedExternalGroupByField!], where: AppBskyEmbedExternalWhereInput, orderBy: AggregationOrderBy, limit: Int): [AppBskyEmbedExternalAggregated!]! 519 783 520 784 """Query app.bsky.feed.postgate records""" 521 - appBskyFeedPostgates(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: JSON): AppBskyFeedPostgateConnection! 785 + appBskyFeedPostgates(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: AppBskyFeedPostgateWhereInput): AppBskyFeedPostgateConnection! 522 786 523 787 """ 524 788 Aggregated query for app.bsky.feed.postgate records with GROUP BY support 525 789 """ 526 - appBskyFeedPostgatesAggregated(groupBy: [String!]!, where: JSON, orderBy: AggregationOrderBy, limit: Int): [AppBskyFeedPostgateAggregated!]! 790 + appBskyFeedPostgatesAggregated(groupBy: [AppBskyFeedPostgateGroupByField!], where: AppBskyFeedPostgateWhereInput, orderBy: AggregationOrderBy, limit: Int): [AppBskyFeedPostgateAggregated!]! 527 791 528 792 """Query app.bsky.feed.threadgate records""" 529 - appBskyFeedThreadgates(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: JSON): AppBskyFeedThreadgateConnection! 793 + appBskyFeedThreadgates(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: AppBskyFeedThreadgateWhereInput): AppBskyFeedThreadgateConnection! 530 794 531 795 """ 532 796 Aggregated query for app.bsky.feed.threadgate records with GROUP BY support 533 797 """ 534 - appBskyFeedThreadgatesAggregated(groupBy: [String!]!, where: JSON, orderBy: AggregationOrderBy, limit: Int): [AppBskyFeedThreadgateAggregated!]! 798 + appBskyFeedThreadgatesAggregated(groupBy: [AppBskyFeedThreadgateGroupByField!], where: AppBskyFeedThreadgateWhereInput, orderBy: AggregationOrderBy, limit: Int): [AppBskyFeedThreadgateAggregated!]! 535 799 536 800 """Query app.bsky.richtext.facet records""" 537 - appBskyRichtextFacets(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: JSON): AppBskyRichtextFacetConnection! 801 + appBskyRichtextFacets(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: AppBskyRichtextFacetWhereInput): AppBskyRichtextFacetConnection! 538 802 539 803 """ 540 804 Aggregated query for app.bsky.richtext.facet records with GROUP BY support 541 805 """ 542 - appBskyRichtextFacetsAggregated(groupBy: [String!]!, where: JSON, orderBy: AggregationOrderBy, limit: Int): [AppBskyRichtextFacetAggregated!]! 806 + appBskyRichtextFacetsAggregated(groupBy: [AppBskyRichtextFacetGroupByField!], where: AppBskyRichtextFacetWhereInput, orderBy: AggregationOrderBy, limit: Int): [AppBskyRichtextFacetAggregated!]! 543 807 544 808 """Query app.bsky.actor.profile records""" 545 - appBskyActorProfiles(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: JSON): AppBskyActorProfileConnection! 809 + appBskyActorProfiles(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: AppBskyActorProfileWhereInput): AppBskyActorProfileConnection! 546 810 547 811 """ 548 812 Aggregated query for app.bsky.actor.profile records with GROUP BY support 549 813 """ 550 - appBskyActorProfilesAggregated(groupBy: [String!]!, where: JSON, orderBy: AggregationOrderBy, limit: Int): [AppBskyActorProfileAggregated!]! 814 + appBskyActorProfilesAggregated(groupBy: [AppBskyActorProfileGroupByField!], where: AppBskyActorProfileWhereInput, orderBy: AggregationOrderBy, limit: Int): [AppBskyActorProfileAggregated!]! 551 815 552 816 """Query com.atproto.repo.strongRef records""" 553 - comAtprotoRepoStrongRefs(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: JSON): ComAtprotoRepoStrongRefConnection! 817 + comAtprotoRepoStrongRefs(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: ComAtprotoRepoStrongRefWhereInput): ComAtprotoRepoStrongRefConnection! 554 818 555 819 """ 556 820 Aggregated query for com.atproto.repo.strongRef records with GROUP BY support 557 821 """ 558 - comAtprotoRepoStrongRefsAggregated(groupBy: [String!]!, where: JSON, orderBy: AggregationOrderBy, limit: Int): [ComAtprotoRepoStrongRefAggregated!]! 822 + comAtprotoRepoStrongRefsAggregated(groupBy: [ComAtprotoRepoStrongRefGroupByField!], where: ComAtprotoRepoStrongRefWhereInput, orderBy: AggregationOrderBy, limit: Int): [ComAtprotoRepoStrongRefAggregated!]! 559 823 560 824 """Query fm.teal.alpha.feed.play records""" 561 - fmTealAlphaFeedPlays(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: JSON): FmTealAlphaFeedPlayConnection! 825 + fmTealAlphaFeedPlays(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: FmTealAlphaFeedPlayWhereInput): FmTealAlphaFeedPlayConnection! 562 826 563 827 """ 564 828 Aggregated query for fm.teal.alpha.feed.play records with GROUP BY support 565 829 """ 566 - fmTealAlphaFeedPlaysAggregated(groupBy: [String!]!, where: JSON, orderBy: AggregationOrderBy, limit: Int): [FmTealAlphaFeedPlayAggregated!]! 830 + fmTealAlphaFeedPlaysAggregated(groupBy: [FmTealAlphaFeedPlayGroupByField!], where: FmTealAlphaFeedPlayWhereInput, orderBy: AggregationOrderBy, limit: Int): [FmTealAlphaFeedPlayAggregated!]! 567 831 } 568 832 569 833 enum SortDirection { ··· 574 838 input SortField { 575 839 field: String! 576 840 direction: SortDirection! 841 + } 842 + 843 + input StringFilter { 844 + eq: String 845 + in: [String] 846 + contains: String 847 + gt: String 848 + gte: String 849 + lt: String 850 + lte: String 577 851 } 578 852 579 853 type Subscription {
+115 -19
src/Layout.tsx
··· 6 6 7 7 export default function Layout({ children }: LayoutProps) { 8 8 const location = useLocation(); 9 + const isTracksPage = location.pathname.startsWith("/tracks"); 10 + const isAlbumsPage = location.pathname.startsWith("/albums"); 9 11 10 12 return ( 11 13 <div className="min-h-screen bg-zinc-950 text-zinc-300 font-mono"> 12 14 <div className="max-w-4xl mx-auto px-6 py-12"> 13 - <div className="mb-12 flex items-end justify-between border-b border-zinc-800 pb-6"> 14 - <div> 15 - <h1 className="text-xs font-medium uppercase tracking-wider text-zinc-500">Listening History</h1> 16 - <p className="text-xs text-zinc-600 mt-1">fm.teal.alpha.feed.play</p> 15 + <div className="mb-4 border-b border-zinc-800 pb-4"> 16 + <div className="flex items-end justify-between"> 17 + <div> 18 + <h1 className="text-xs font-medium uppercase tracking-wider text-zinc-500">Listening History</h1> 19 + <p className="text-xs text-zinc-600 mt-1">fm.teal.alpha.feed.play</p> 20 + </div> 21 + 22 + <div className="flex gap-4 text-xs"> 23 + <Link 24 + to="/" 25 + className={`px-2 py-1 transition-colors ${ 26 + location.pathname === "/" 27 + ? "text-zinc-400" 28 + : "text-zinc-500 hover:text-zinc-300" 29 + }`} 30 + > 31 + Recent 32 + </Link> 33 + <Link 34 + to="/tracks" 35 + className={`px-2 py-1 transition-colors ${ 36 + isTracksPage 37 + ? "text-zinc-400" 38 + : "text-zinc-500 hover:text-zinc-300" 39 + }`} 40 + > 41 + Top Tracks 42 + </Link> 43 + <Link 44 + to="/albums" 45 + className={`px-2 py-1 transition-colors ${ 46 + isAlbumsPage 47 + ? "text-zinc-400" 48 + : "text-zinc-500 hover:text-zinc-300" 49 + }`} 50 + > 51 + Top Albums 52 + </Link> 53 + </div> 17 54 </div> 55 + </div> 18 56 19 - <div className="flex gap-4 text-xs"> 57 + {isTracksPage && ( 58 + <div className="flex gap-3 text-xs mb-8 pb-4 border-b border-zinc-800"> 20 59 <Link 21 - to="/" 60 + to="/tracks" 22 61 className={`px-2 py-1 transition-colors ${ 23 - location.pathname === "/" 24 - ? "text-zinc-400" 25 - : "text-zinc-500 hover:text-zinc-300" 62 + location.pathname === "/tracks" 63 + ? "text-zinc-300" 64 + : "text-zinc-600 hover:text-zinc-400" 26 65 }`} 27 66 > 28 - Recent 67 + All Time 29 68 </Link> 30 69 <Link 31 - to="/tracks" 70 + to="/tracks/daily" 32 71 className={`px-2 py-1 transition-colors ${ 33 - location.pathname === "/tracks" 34 - ? "text-zinc-400" 35 - : "text-zinc-500 hover:text-zinc-300" 72 + location.pathname === "/tracks/daily" 73 + ? "text-zinc-300" 74 + : "text-zinc-600 hover:text-zinc-400" 36 75 }`} 37 76 > 38 - Top Tracks 77 + Daily 78 + </Link> 79 + <Link 80 + to="/tracks/weekly" 81 + className={`px-2 py-1 transition-colors ${ 82 + location.pathname === "/tracks/weekly" 83 + ? "text-zinc-300" 84 + : "text-zinc-600 hover:text-zinc-400" 85 + }`} 86 + > 87 + Weekly 39 88 </Link> 40 89 <Link 90 + to="/tracks/monthly" 91 + className={`px-2 py-1 transition-colors ${ 92 + location.pathname === "/tracks/monthly" 93 + ? "text-zinc-300" 94 + : "text-zinc-600 hover:text-zinc-400" 95 + }`} 96 + > 97 + Monthly 98 + </Link> 99 + </div> 100 + )} 101 + 102 + {isAlbumsPage && ( 103 + <div className="flex gap-3 text-xs mb-8 pb-4 border-b border-zinc-800"> 104 + <Link 41 105 to="/albums" 42 106 className={`px-2 py-1 transition-colors ${ 43 107 location.pathname === "/albums" 44 - ? "text-zinc-400" 45 - : "text-zinc-500 hover:text-zinc-300" 108 + ? "text-zinc-300" 109 + : "text-zinc-600 hover:text-zinc-400" 110 + }`} 111 + > 112 + All Time 113 + </Link> 114 + <Link 115 + to="/albums/daily" 116 + className={`px-2 py-1 transition-colors ${ 117 + location.pathname === "/albums/daily" 118 + ? "text-zinc-300" 119 + : "text-zinc-600 hover:text-zinc-400" 120 + }`} 121 + > 122 + Daily 123 + </Link> 124 + <Link 125 + to="/albums/weekly" 126 + className={`px-2 py-1 transition-colors ${ 127 + location.pathname === "/albums/weekly" 128 + ? "text-zinc-300" 129 + : "text-zinc-600 hover:text-zinc-400" 130 + }`} 131 + > 132 + Weekly 133 + </Link> 134 + <Link 135 + to="/albums/monthly" 136 + className={`px-2 py-1 transition-colors ${ 137 + location.pathname === "/albums/monthly" 138 + ? "text-zinc-300" 139 + : "text-zinc-600 hover:text-zinc-400" 46 140 }`} 47 141 > 48 - Top Albums 142 + Monthly 49 143 </Link> 50 144 </div> 51 - </div> 145 + )} 146 + 147 + {!isTracksPage && !isAlbumsPage && <div className="mb-8"></div>} 52 148 53 149 {children} 54 150 </div>
+2 -2
src/Profile.tsx
··· 10 10 11 11 const queryData = useLazyLoadQuery<ProfileQueryType>( 12 12 graphql` 13 - query ProfileQuery($where: JSON!) { 13 + query ProfileQuery($where: FmTealAlphaFeedPlayWhereInput!) { 14 14 ...Profile_plays @arguments(where: $where) 15 15 } 16 16 `, ··· 29 29 @argumentDefinitions( 30 30 cursor: { type: "String" } 31 31 count: { type: "Int", defaultValue: 20 } 32 - where: { type: "JSON!" } 32 + where: { type: "FmTealAlphaFeedPlayWhereInput!" } 33 33 ) { 34 34 fmTealAlphaFeedPlays( 35 35 first: $count
+10 -3
src/TopAlbums.tsx
··· 1 1 import { graphql, useLazyLoadQuery } from "react-relay"; 2 + import { useParams } from "react-router-dom"; 2 3 import type { TopAlbumsQuery } from "./__generated__/TopAlbumsQuery.graphql"; 3 4 import AlbumItem from "./AlbumItem"; 4 5 import Layout from "./Layout"; 6 + import { useDateRangeFilter } from "./useDateRangeFilter"; 5 7 6 8 export default function TopAlbums() { 9 + const { period } = useParams<{ period?: string }>(); 10 + const queryVariables = useDateRangeFilter(period); 11 + 7 12 const data = useLazyLoadQuery<TopAlbumsQuery>( 8 13 graphql` 9 - query TopAlbumsQuery { 14 + query TopAlbumsQuery($where: FmTealAlphaFeedPlayWhereInput) { 10 15 fmTealAlphaFeedPlaysAggregated( 11 - groupBy: ["releaseMbId", "releaseName", "artists"] 16 + groupBy: [releaseMbId, releaseName, artists] 12 17 orderBy: { count: desc } 13 18 limit: 100 19 + where: $where 14 20 ) { 15 21 releaseMbId 16 22 releaseName ··· 19 25 } 20 26 } 21 27 `, 22 - {} 28 + queryVariables, 29 + { fetchKey: period || "all", fetchPolicy: "store-or-network" } 23 30 ); 24 31 25 32 const albums = [...(data.fmTealAlphaFeedPlaysAggregated || [])];
+10 -3
src/TopTracks.tsx
··· 1 1 import { graphql, useLazyLoadQuery } from "react-relay"; 2 + import { useParams } from "react-router-dom"; 2 3 import type { TopTracksQuery } from "./__generated__/TopTracksQuery.graphql"; 3 4 import TopTrackItem from "./TopTrackItem"; 4 5 import Layout from "./Layout"; 6 + import { useDateRangeFilter } from "./useDateRangeFilter"; 5 7 6 8 export default function TopTracks() { 9 + const { period } = useParams<{ period?: string }>(); 10 + const queryVariables = useDateRangeFilter(period); 11 + 7 12 const data = useLazyLoadQuery<TopTracksQuery>( 8 13 graphql` 9 - query TopTracksQuery { 14 + query TopTracksQuery($where: FmTealAlphaFeedPlayWhereInput) { 10 15 fmTealAlphaFeedPlaysAggregated( 11 - groupBy: ["trackName", "releaseMbId", "artists"] 16 + groupBy: [trackName, releaseMbId, artists] 12 17 orderBy: { count: desc } 13 18 limit: 50 19 + where: $where 14 20 ) { 15 21 trackName 16 22 releaseMbId ··· 19 25 } 20 26 } 21 27 `, 22 - {} 28 + queryVariables, 29 + { fetchKey: period || "all", fetchPolicy: "store-or-network" } 23 30 ); 24 31 25 32 const tracks = data.fmTealAlphaFeedPlaysAggregated || [];
+51 -5
src/__generated__/ProfilePaginationQuery.graphql.ts
··· 1 1 /** 2 - * @generated SignedSource<<a103b609fd81848d14264d18e76ece37>> 2 + * @generated SignedSource<<9824b6fa6724ec81721b89464e18ee4f>> 3 3 * @lightSyntaxTransform 4 4 * @nogrep 5 5 */ ··· 10 10 11 11 import { ConcreteRequest } from 'relay-runtime'; 12 12 import { FragmentRefs } from "relay-runtime"; 13 + export type FmTealAlphaFeedPlayWhereInput = { 14 + actorHandle?: StringFilter | null | undefined; 15 + artistMbIds?: StringFilter | null | undefined; 16 + artistNames?: StringFilter | null | undefined; 17 + artists?: StringFilter | null | undefined; 18 + cid?: StringFilter | null | undefined; 19 + collection?: StringFilter | null | undefined; 20 + did?: StringFilter | null | undefined; 21 + duration?: IntFilter | null | undefined; 22 + indexedAt?: DateTimeFilter | null | undefined; 23 + isrc?: StringFilter | null | undefined; 24 + musicServiceBaseDomain?: StringFilter | null | undefined; 25 + originUrl?: StringFilter | null | undefined; 26 + playedTime?: StringFilter | null | undefined; 27 + recordingMbId?: StringFilter | null | undefined; 28 + releaseMbId?: StringFilter | null | undefined; 29 + releaseName?: StringFilter | null | undefined; 30 + submissionClientAgent?: StringFilter | null | undefined; 31 + trackMbId?: StringFilter | null | undefined; 32 + trackName?: StringFilter | null | undefined; 33 + uri?: StringFilter | null | undefined; 34 + }; 35 + export type DateTimeFilter = { 36 + eq?: string | null | undefined; 37 + gt?: string | null | undefined; 38 + gte?: string | null | undefined; 39 + lt?: string | null | undefined; 40 + lte?: string | null | undefined; 41 + }; 42 + export type StringFilter = { 43 + contains?: string | null | undefined; 44 + eq?: string | null | undefined; 45 + gt?: string | null | undefined; 46 + gte?: string | null | undefined; 47 + in?: ReadonlyArray<string | null | undefined> | null | undefined; 48 + lt?: string | null | undefined; 49 + lte?: string | null | undefined; 50 + }; 51 + export type IntFilter = { 52 + eq?: number | null | undefined; 53 + gt?: number | null | undefined; 54 + gte?: number | null | undefined; 55 + in?: ReadonlyArray<number | null | undefined> | null | undefined; 56 + lt?: number | null | undefined; 57 + lte?: number | null | undefined; 58 + }; 13 59 export type ProfilePaginationQuery$variables = { 14 60 count?: number | null | undefined; 15 61 cursor?: string | null | undefined; 16 - where: any; 62 + where: FmTealAlphaFeedPlayWhereInput; 17 63 }; 18 64 export type ProfilePaginationQuery$data = { 19 65 readonly " $fragmentSpreads": FragmentRefs<"Profile_plays">; ··· 295 341 ] 296 342 }, 297 343 "params": { 298 - "cacheID": "b6789cc8edc0cedfd5de86eadfba8b9e", 344 + "cacheID": "6d52a3e02fe71c3ad54ec2006fc2ac45", 299 345 "id": null, 300 346 "metadata": {}, 301 347 "name": "ProfilePaginationQuery", 302 348 "operationKind": "query", 303 - "text": "query ProfilePaginationQuery(\n $count: Int = 20\n $cursor: String\n $where: JSON!\n) {\n ...Profile_plays_mjR8k\n}\n\nfragment Profile_plays_mjR8k on Query {\n fmTealAlphaFeedPlays(first: $count, after: $cursor, sortBy: [{field: \"playedTime\", direction: desc}], where: $where) {\n totalCount\n edges {\n node {\n ...TrackItem_play\n actorHandle\n appBskyActorProfile {\n displayName\n description\n avatar {\n url(preset: \"avatar\")\n }\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n\nfragment TrackItem_play on FmTealAlphaFeedPlay {\n trackName\n playedTime\n artists\n releaseName\n releaseMbId\n actorHandle\n musicServiceBaseDomain\n appBskyActorProfile {\n displayName\n }\n}\n" 349 + "text": "query ProfilePaginationQuery(\n $count: Int = 20\n $cursor: String\n $where: FmTealAlphaFeedPlayWhereInput!\n) {\n ...Profile_plays_mjR8k\n}\n\nfragment Profile_plays_mjR8k on Query {\n fmTealAlphaFeedPlays(first: $count, after: $cursor, sortBy: [{field: \"playedTime\", direction: desc}], where: $where) {\n totalCount\n edges {\n node {\n ...TrackItem_play\n actorHandle\n appBskyActorProfile {\n displayName\n description\n avatar {\n url(preset: \"avatar\")\n }\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n\nfragment TrackItem_play on FmTealAlphaFeedPlay {\n trackName\n playedTime\n artists\n releaseName\n releaseMbId\n actorHandle\n musicServiceBaseDomain\n appBskyActorProfile {\n displayName\n }\n}\n" 304 350 } 305 351 }; 306 352 })(); 307 353 308 - (node as any).hash = "474168bb0d13417c1b7067c09a82f7a2"; 354 + (node as any).hash = "86bf47e8cb24c938b0b5d7ad6f5cb916"; 309 355 310 356 export default node;
+51 -5
src/__generated__/ProfileQuery.graphql.ts
··· 1 1 /** 2 - * @generated SignedSource<<706478869815c293b78f1ace532431f2>> 2 + * @generated SignedSource<<0920331f4eccd3551cbc3ca8646596f0>> 3 3 * @lightSyntaxTransform 4 4 * @nogrep 5 5 */ ··· 10 10 11 11 import { ConcreteRequest } from 'relay-runtime'; 12 12 import { FragmentRefs } from "relay-runtime"; 13 + export type FmTealAlphaFeedPlayWhereInput = { 14 + actorHandle?: StringFilter | null | undefined; 15 + artistMbIds?: StringFilter | null | undefined; 16 + artistNames?: StringFilter | null | undefined; 17 + artists?: StringFilter | null | undefined; 18 + cid?: StringFilter | null | undefined; 19 + collection?: StringFilter | null | undefined; 20 + did?: StringFilter | null | undefined; 21 + duration?: IntFilter | null | undefined; 22 + indexedAt?: DateTimeFilter | null | undefined; 23 + isrc?: StringFilter | null | undefined; 24 + musicServiceBaseDomain?: StringFilter | null | undefined; 25 + originUrl?: StringFilter | null | undefined; 26 + playedTime?: StringFilter | null | undefined; 27 + recordingMbId?: StringFilter | null | undefined; 28 + releaseMbId?: StringFilter | null | undefined; 29 + releaseName?: StringFilter | null | undefined; 30 + submissionClientAgent?: StringFilter | null | undefined; 31 + trackMbId?: StringFilter | null | undefined; 32 + trackName?: StringFilter | null | undefined; 33 + uri?: StringFilter | null | undefined; 34 + }; 35 + export type DateTimeFilter = { 36 + eq?: string | null | undefined; 37 + gt?: string | null | undefined; 38 + gte?: string | null | undefined; 39 + lt?: string | null | undefined; 40 + lte?: string | null | undefined; 41 + }; 42 + export type StringFilter = { 43 + contains?: string | null | undefined; 44 + eq?: string | null | undefined; 45 + gt?: string | null | undefined; 46 + gte?: string | null | undefined; 47 + in?: ReadonlyArray<string | null | undefined> | null | undefined; 48 + lt?: string | null | undefined; 49 + lte?: string | null | undefined; 50 + }; 51 + export type IntFilter = { 52 + eq?: number | null | undefined; 53 + gt?: number | null | undefined; 54 + gte?: number | null | undefined; 55 + in?: ReadonlyArray<number | null | undefined> | null | undefined; 56 + lt?: number | null | undefined; 57 + lte?: number | null | undefined; 58 + }; 13 59 export type ProfileQuery$variables = { 14 - where: any; 60 + where: FmTealAlphaFeedPlayWhereInput; 15 61 }; 16 62 export type ProfileQuery$data = { 17 63 readonly " $fragmentSpreads": FragmentRefs<"Profile_plays">; ··· 268 314 ] 269 315 }, 270 316 "params": { 271 - "cacheID": "f6cf8b245658eb5d028de7fafcc457c9", 317 + "cacheID": "0592d86db07d88ab11657ea4cd107231", 272 318 "id": null, 273 319 "metadata": {}, 274 320 "name": "ProfileQuery", 275 321 "operationKind": "query", 276 - "text": "query ProfileQuery(\n $where: JSON!\n) {\n ...Profile_plays_3FC4Qo\n}\n\nfragment Profile_plays_3FC4Qo on Query {\n fmTealAlphaFeedPlays(first: 20, sortBy: [{field: \"playedTime\", direction: desc}], where: $where) {\n totalCount\n edges {\n node {\n ...TrackItem_play\n actorHandle\n appBskyActorProfile {\n displayName\n description\n avatar {\n url(preset: \"avatar\")\n }\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n\nfragment TrackItem_play on FmTealAlphaFeedPlay {\n trackName\n playedTime\n artists\n releaseName\n releaseMbId\n actorHandle\n musicServiceBaseDomain\n appBskyActorProfile {\n displayName\n }\n}\n" 322 + "text": "query ProfileQuery(\n $where: FmTealAlphaFeedPlayWhereInput!\n) {\n ...Profile_plays_3FC4Qo\n}\n\nfragment Profile_plays_3FC4Qo on Query {\n fmTealAlphaFeedPlays(first: 20, sortBy: [{field: \"playedTime\", direction: desc}], where: $where) {\n totalCount\n edges {\n node {\n ...TrackItem_play\n actorHandle\n appBskyActorProfile {\n displayName\n description\n avatar {\n url(preset: \"avatar\")\n }\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n\nfragment TrackItem_play on FmTealAlphaFeedPlay {\n trackName\n playedTime\n artists\n releaseName\n releaseMbId\n actorHandle\n musicServiceBaseDomain\n appBskyActorProfile {\n displayName\n }\n}\n" 277 323 } 278 324 }; 279 325 })(); 280 326 281 - (node as any).hash = "267039e382b3b95a739ff3cdced3211e"; 327 + (node as any).hash = "4a0ecbad0ab4453246cdcdd753b1f84f"; 282 328 283 329 export default node;
+2 -2
src/__generated__/Profile_plays.graphql.ts
··· 1 1 /** 2 - * @generated SignedSource<<bd63c9e74b05810076b60ad0e51cb230>> 2 + * @generated SignedSource<<0e69127350e3dc66273c2ea60929dc92>> 3 3 * @lightSyntaxTransform 4 4 * @nogrep 5 5 */ ··· 245 245 }; 246 246 })(); 247 247 248 - (node as any).hash = "474168bb0d13417c1b7067c09a82f7a2"; 248 + (node as any).hash = "86bf47e8cb24c938b0b5d7ad6f5cb916"; 249 249 250 250 export default node;
+70 -10
src/__generated__/TopAlbumsQuery.graphql.ts
··· 1 1 /** 2 - * @generated SignedSource<<e2bf0d16ddd996a8b44b47387dd220b3>> 2 + * @generated SignedSource<<8cf8cec6835334168002a2939635c9d5>> 3 3 * @lightSyntaxTransform 4 4 * @nogrep 5 5 */ ··· 9 9 // @ts-nocheck 10 10 11 11 import { ConcreteRequest } from 'relay-runtime'; 12 - export type TopAlbumsQuery$variables = Record<PropertyKey, never>; 12 + export type FmTealAlphaFeedPlayWhereInput = { 13 + actorHandle?: StringFilter | null | undefined; 14 + artistMbIds?: StringFilter | null | undefined; 15 + artistNames?: StringFilter | null | undefined; 16 + artists?: StringFilter | null | undefined; 17 + cid?: StringFilter | null | undefined; 18 + collection?: StringFilter | null | undefined; 19 + did?: StringFilter | null | undefined; 20 + duration?: IntFilter | null | undefined; 21 + indexedAt?: DateTimeFilter | null | undefined; 22 + isrc?: StringFilter | null | undefined; 23 + musicServiceBaseDomain?: StringFilter | null | undefined; 24 + originUrl?: StringFilter | null | undefined; 25 + playedTime?: StringFilter | null | undefined; 26 + recordingMbId?: StringFilter | null | undefined; 27 + releaseMbId?: StringFilter | null | undefined; 28 + releaseName?: StringFilter | null | undefined; 29 + submissionClientAgent?: StringFilter | null | undefined; 30 + trackMbId?: StringFilter | null | undefined; 31 + trackName?: StringFilter | null | undefined; 32 + uri?: StringFilter | null | undefined; 33 + }; 34 + export type DateTimeFilter = { 35 + eq?: string | null | undefined; 36 + gt?: string | null | undefined; 37 + gte?: string | null | undefined; 38 + lt?: string | null | undefined; 39 + lte?: string | null | undefined; 40 + }; 41 + export type StringFilter = { 42 + contains?: string | null | undefined; 43 + eq?: string | null | undefined; 44 + gt?: string | null | undefined; 45 + gte?: string | null | undefined; 46 + in?: ReadonlyArray<string | null | undefined> | null | undefined; 47 + lt?: string | null | undefined; 48 + lte?: string | null | undefined; 49 + }; 50 + export type IntFilter = { 51 + eq?: number | null | undefined; 52 + gt?: number | null | undefined; 53 + gte?: number | null | undefined; 54 + in?: ReadonlyArray<number | null | undefined> | null | undefined; 55 + lt?: number | null | undefined; 56 + lte?: number | null | undefined; 57 + }; 58 + export type TopAlbumsQuery$variables = { 59 + where?: FmTealAlphaFeedPlayWhereInput | null | undefined; 60 + }; 13 61 export type TopAlbumsQuery$data = { 14 62 readonly fmTealAlphaFeedPlaysAggregated: ReadonlyArray<{ 15 63 readonly artists: any | null | undefined; ··· 26 74 const node: ConcreteRequest = (function(){ 27 75 var v0 = [ 28 76 { 77 + "defaultValue": null, 78 + "kind": "LocalArgument", 79 + "name": "where" 80 + } 81 + ], 82 + v1 = [ 83 + { 29 84 "alias": null, 30 85 "args": [ 31 86 { ··· 48 103 "value": { 49 104 "count": "desc" 50 105 } 106 + }, 107 + { 108 + "kind": "Variable", 109 + "name": "where", 110 + "variableName": "where" 51 111 } 52 112 ], 53 113 "concreteType": "FmTealAlphaFeedPlayAggregated", ··· 84 144 "storageKey": null 85 145 } 86 146 ], 87 - "storageKey": "fmTealAlphaFeedPlaysAggregated(groupBy:[\"releaseMbId\",\"releaseName\",\"artists\"],limit:100,orderBy:{\"count\":\"desc\"})" 147 + "storageKey": null 88 148 } 89 149 ]; 90 150 return { 91 151 "fragment": { 92 - "argumentDefinitions": [], 152 + "argumentDefinitions": (v0/*: any*/), 93 153 "kind": "Fragment", 94 154 "metadata": null, 95 155 "name": "TopAlbumsQuery", 96 - "selections": (v0/*: any*/), 156 + "selections": (v1/*: any*/), 97 157 "type": "Query", 98 158 "abstractKey": null 99 159 }, 100 160 "kind": "Request", 101 161 "operation": { 102 - "argumentDefinitions": [], 162 + "argumentDefinitions": (v0/*: any*/), 103 163 "kind": "Operation", 104 164 "name": "TopAlbumsQuery", 105 - "selections": (v0/*: any*/) 165 + "selections": (v1/*: any*/) 106 166 }, 107 167 "params": { 108 - "cacheID": "65b42ff33b8a5de6eb4785e764ae70fc", 168 + "cacheID": "bb227295300710370e7e5c2492532c01", 109 169 "id": null, 110 170 "metadata": {}, 111 171 "name": "TopAlbumsQuery", 112 172 "operationKind": "query", 113 - "text": "query TopAlbumsQuery {\n fmTealAlphaFeedPlaysAggregated(groupBy: [\"releaseMbId\", \"releaseName\", \"artists\"], orderBy: {count: desc}, limit: 100) {\n releaseMbId\n releaseName\n artists\n count\n }\n}\n" 173 + "text": "query TopAlbumsQuery(\n $where: FmTealAlphaFeedPlayWhereInput\n) {\n fmTealAlphaFeedPlaysAggregated(groupBy: [releaseMbId, releaseName, artists], orderBy: {count: desc}, limit: 100, where: $where) {\n releaseMbId\n releaseName\n artists\n count\n }\n}\n" 114 174 } 115 175 }; 116 176 })(); 117 177 118 - (node as any).hash = "b5748a3a4af3140d3cff228e7462f73d"; 178 + (node as any).hash = "6e30827615eb8acfde3c0c80598b6627"; 119 179 120 180 export default node;
+70 -10
src/__generated__/TopTracksQuery.graphql.ts
··· 1 1 /** 2 - * @generated SignedSource<<58e8aa653524405ace1405d28bd8f19e>> 2 + * @generated SignedSource<<2c2f4cf7a049eff39002109dffd04288>> 3 3 * @lightSyntaxTransform 4 4 * @nogrep 5 5 */ ··· 9 9 // @ts-nocheck 10 10 11 11 import { ConcreteRequest } from 'relay-runtime'; 12 - export type TopTracksQuery$variables = Record<PropertyKey, never>; 12 + export type FmTealAlphaFeedPlayWhereInput = { 13 + actorHandle?: StringFilter | null | undefined; 14 + artistMbIds?: StringFilter | null | undefined; 15 + artistNames?: StringFilter | null | undefined; 16 + artists?: StringFilter | null | undefined; 17 + cid?: StringFilter | null | undefined; 18 + collection?: StringFilter | null | undefined; 19 + did?: StringFilter | null | undefined; 20 + duration?: IntFilter | null | undefined; 21 + indexedAt?: DateTimeFilter | null | undefined; 22 + isrc?: StringFilter | null | undefined; 23 + musicServiceBaseDomain?: StringFilter | null | undefined; 24 + originUrl?: StringFilter | null | undefined; 25 + playedTime?: StringFilter | null | undefined; 26 + recordingMbId?: StringFilter | null | undefined; 27 + releaseMbId?: StringFilter | null | undefined; 28 + releaseName?: StringFilter | null | undefined; 29 + submissionClientAgent?: StringFilter | null | undefined; 30 + trackMbId?: StringFilter | null | undefined; 31 + trackName?: StringFilter | null | undefined; 32 + uri?: StringFilter | null | undefined; 33 + }; 34 + export type DateTimeFilter = { 35 + eq?: string | null | undefined; 36 + gt?: string | null | undefined; 37 + gte?: string | null | undefined; 38 + lt?: string | null | undefined; 39 + lte?: string | null | undefined; 40 + }; 41 + export type StringFilter = { 42 + contains?: string | null | undefined; 43 + eq?: string | null | undefined; 44 + gt?: string | null | undefined; 45 + gte?: string | null | undefined; 46 + in?: ReadonlyArray<string | null | undefined> | null | undefined; 47 + lt?: string | null | undefined; 48 + lte?: string | null | undefined; 49 + }; 50 + export type IntFilter = { 51 + eq?: number | null | undefined; 52 + gt?: number | null | undefined; 53 + gte?: number | null | undefined; 54 + in?: ReadonlyArray<number | null | undefined> | null | undefined; 55 + lt?: number | null | undefined; 56 + lte?: number | null | undefined; 57 + }; 58 + export type TopTracksQuery$variables = { 59 + where?: FmTealAlphaFeedPlayWhereInput | null | undefined; 60 + }; 13 61 export type TopTracksQuery$data = { 14 62 readonly fmTealAlphaFeedPlaysAggregated: ReadonlyArray<{ 15 63 readonly artists: any | null | undefined; ··· 26 74 const node: ConcreteRequest = (function(){ 27 75 var v0 = [ 28 76 { 77 + "defaultValue": null, 78 + "kind": "LocalArgument", 79 + "name": "where" 80 + } 81 + ], 82 + v1 = [ 83 + { 29 84 "alias": null, 30 85 "args": [ 31 86 { ··· 48 103 "value": { 49 104 "count": "desc" 50 105 } 106 + }, 107 + { 108 + "kind": "Variable", 109 + "name": "where", 110 + "variableName": "where" 51 111 } 52 112 ], 53 113 "concreteType": "FmTealAlphaFeedPlayAggregated", ··· 84 144 "storageKey": null 85 145 } 86 146 ], 87 - "storageKey": "fmTealAlphaFeedPlaysAggregated(groupBy:[\"trackName\",\"releaseMbId\",\"artists\"],limit:50,orderBy:{\"count\":\"desc\"})" 147 + "storageKey": null 88 148 } 89 149 ]; 90 150 return { 91 151 "fragment": { 92 - "argumentDefinitions": [], 152 + "argumentDefinitions": (v0/*: any*/), 93 153 "kind": "Fragment", 94 154 "metadata": null, 95 155 "name": "TopTracksQuery", 96 - "selections": (v0/*: any*/), 156 + "selections": (v1/*: any*/), 97 157 "type": "Query", 98 158 "abstractKey": null 99 159 }, 100 160 "kind": "Request", 101 161 "operation": { 102 - "argumentDefinitions": [], 162 + "argumentDefinitions": (v0/*: any*/), 103 163 "kind": "Operation", 104 164 "name": "TopTracksQuery", 105 - "selections": (v0/*: any*/) 165 + "selections": (v1/*: any*/) 106 166 }, 107 167 "params": { 108 - "cacheID": "61e9f7886dfe9eaeb599b939f2d636e5", 168 + "cacheID": "cbf23694acc55e8dfbe9296500193932", 109 169 "id": null, 110 170 "metadata": {}, 111 171 "name": "TopTracksQuery", 112 172 "operationKind": "query", 113 - "text": "query TopTracksQuery {\n fmTealAlphaFeedPlaysAggregated(groupBy: [\"trackName\", \"releaseMbId\", \"artists\"], orderBy: {count: desc}, limit: 50) {\n trackName\n releaseMbId\n artists\n count\n }\n}\n" 173 + "text": "query TopTracksQuery(\n $where: FmTealAlphaFeedPlayWhereInput\n) {\n fmTealAlphaFeedPlaysAggregated(groupBy: [trackName, releaseMbId, artists], orderBy: {count: desc}, limit: 50, where: $where) {\n trackName\n releaseMbId\n artists\n count\n }\n}\n" 114 174 } 115 175 }; 116 176 })(); 117 177 118 - (node as any).hash = "536f8ddb64daa09017abff121d7ea8ce"; 178 + (node as any).hash = "6b649e9c39df41d4ce995e69e6dc6f35"; 119 179 120 180 export default node;
+4 -2
src/main.tsx
··· 19 19 import { createClient } from "graphql-ws"; 20 20 21 21 const HTTP_ENDPOINT = 22 - "https://api.slices.network/graphql?slice=at://did:plc:fpruhuo22xkm5o7ttr2ktxdo/network.slices.slice/3m257yljpbg2a"; 22 + "http://localhost:3000/graphql?slice=at://did:plc:fpruhuo22xkm5o7ttr2ktxdo/network.slices.slice/3m257yljpbg2a"; 23 23 24 24 const WS_ENDPOINT = 25 - "wss://api.slices.network/graphql/ws?slice=at://did:plc:fpruhuo22xkm5o7ttr2ktxdo/network.slices.slice/3m257yljpbg2a"; 25 + "ws://localhost:3000/graphql/ws?slice=at://did:plc:fpruhuo22xkm5o7ttr2ktxdo/network.slices.slice/3m257yljpbg2a"; 26 26 27 27 const fetchGraphQL: FetchFunction = async (request, variables) => { 28 28 const resp = await fetch(HTTP_ENDPOINT, { ··· 102 102 <Routes> 103 103 <Route path="/" element={<App />} /> 104 104 <Route path="/tracks" element={<TopTracks />} /> 105 + <Route path="/tracks/:period" element={<TopTracks />} /> 105 106 <Route path="/albums" element={<TopAlbums />} /> 107 + <Route path="/albums/:period" element={<TopAlbums />} /> 106 108 <Route path="/profile/:handle" element={<Profile />} /> 107 109 </Routes> 108 110 </Suspense>
+31
src/useDateRangeFilter.ts
··· 1 + import { useMemo } from "react"; 2 + 3 + export function useDateRangeFilter(period: string | undefined) { 4 + return useMemo(() => { 5 + if (!period || period === "all") { 6 + return { where: undefined }; 7 + } 8 + 9 + // Round to start of current day to keep the timestamp stable 10 + const now = new Date(); 11 + now.setHours(0, 0, 0, 0); 12 + 13 + let daysAgo = 0; 14 + switch (period) { 15 + case "daily": 16 + daysAgo = 1; 17 + break; 18 + case "weekly": 19 + daysAgo = 7; 20 + break; 21 + case "monthly": 22 + daysAgo = 30; 23 + break; 24 + default: 25 + return { where: undefined }; 26 + } 27 + 28 + const startDate = new Date(now.getTime() - daysAgo * 24 * 60 * 60 * 1000); 29 + return { where: { playedTime: { gte: startDate.toISOString() } } }; 30 + }, [period]); 31 + }