service.proto raw

   1  syntax = "proto3";
   2  package orlydb.v1;
   3  option go_package = "next.orly.dev/pkg/proto/orlydb/v1;orlydbv1";
   4  
   5  import "orlydb/v1/types.proto";
   6  
   7  // DatabaseService provides gRPC access to the ORLY database
   8  service DatabaseService {
   9    // === Lifecycle Methods ===
  10  
  11    // GetPath returns the database file path
  12    rpc GetPath(Empty) returns (PathResponse);
  13  
  14    // Sync flushes buffers to disk
  15    rpc Sync(Empty) returns (Empty);
  16  
  17    // Ready returns whether the database is ready to serve requests
  18    rpc Ready(Empty) returns (ReadyResponse);
  19  
  20    // SetLogLevel sets the database log level
  21    rpc SetLogLevel(SetLogLevelRequest) returns (Empty);
  22  
  23    // === Event Storage ===
  24  
  25    // SaveEvent stores an event, returns whether it already existed
  26    rpc SaveEvent(SaveEventRequest) returns (SaveEventResponse);
  27  
  28    // GetSerialsFromFilter gets serial numbers matching a filter
  29    rpc GetSerialsFromFilter(GetSerialsFromFilterRequest) returns (SerialList);
  30  
  31    // WouldReplaceEvent checks if an event would replace existing ones
  32    rpc WouldReplaceEvent(WouldReplaceEventRequest) returns (WouldReplaceEventResponse);
  33  
  34    // === Event Queries (Streaming for large results) ===
  35  
  36    // QueryEvents queries events matching a filter (with deletion filtering)
  37    rpc QueryEvents(QueryEventsRequest) returns (stream EventBatch);
  38  
  39    // QueryAllVersions queries all versions of replaceable events
  40    rpc QueryAllVersions(QueryEventsRequest) returns (stream EventBatch);
  41  
  42    // QueryEventsWithOptions provides full control over query behavior
  43    rpc QueryEventsWithOptions(QueryEventsWithOptionsRequest) returns (stream EventBatch);
  44  
  45    // QueryDeleteEventsByTargetId queries deletion events for a target
  46    rpc QueryDeleteEventsByTargetId(QueryDeleteEventsByTargetIdRequest) returns (stream EventBatch);
  47  
  48    // QueryForSerials queries just serial numbers matching a filter
  49    rpc QueryForSerials(QueryEventsRequest) returns (SerialList);
  50  
  51    // QueryForIds queries ID/Pubkey/Timestamp triplets
  52    rpc QueryForIds(QueryEventsRequest) returns (IdPkTsList);
  53  
  54    // CountEvents counts events matching a filter
  55    rpc CountEvents(QueryEventsRequest) returns (CountEventsResponse);
  56  
  57    // === Event Retrieval by Serial ===
  58  
  59    // FetchEventBySerial fetches a single event by serial
  60    rpc FetchEventBySerial(FetchEventBySerialRequest) returns (FetchEventBySerialResponse);
  61  
  62    // FetchEventsBySerials fetches multiple events by serial (batch)
  63    rpc FetchEventsBySerials(FetchEventsBySerialRequest) returns (EventMap);
  64  
  65    // GetSerialById gets the serial for a single event ID
  66    rpc GetSerialById(GetSerialByIdRequest) returns (GetSerialByIdResponse);
  67  
  68    // GetSerialsByIds gets serials for multiple event IDs
  69    rpc GetSerialsByIds(GetSerialsByIdsRequest) returns (SerialMap);
  70  
  71    // GetSerialsByRange gets serials within an index range
  72    rpc GetSerialsByRange(GetSerialsByRangeRequest) returns (SerialList);
  73  
  74    // GetFullIdPubkeyBySerial gets full ID/Pubkey/Timestamp for a serial
  75    rpc GetFullIdPubkeyBySerial(GetFullIdPubkeyBySerialRequest) returns (IdPkTs);
  76  
  77    // GetFullIdPubkeyBySerials gets full data for multiple serials
  78    rpc GetFullIdPubkeyBySerials(GetFullIdPubkeyBySerialsRequest) returns (IdPkTsList);
  79  
  80    // === Event Deletion ===
  81  
  82    // DeleteEvent deletes an event by ID
  83    rpc DeleteEvent(DeleteEventRequest) returns (Empty);
  84  
  85    // DeleteEventBySerial deletes an event by serial
  86    rpc DeleteEventBySerial(DeleteEventBySerialRequest) returns (Empty);
  87  
  88    // DeleteExpired deletes events with expired expiration tags
  89    rpc DeleteExpired(Empty) returns (Empty);
  90  
  91    // ProcessDelete processes a deletion event (NIP-09)
  92    rpc ProcessDelete(ProcessDeleteRequest) returns (Empty);
  93  
  94    // CheckForDeleted checks if an event was deleted
  95    rpc CheckForDeleted(CheckForDeletedRequest) returns (Empty);
  96  
  97    // === Import/Export (Streaming) ===
  98  
  99    // Import imports events from a stream
 100    rpc Import(stream ImportChunk) returns (ImportResponse);
 101  
 102    // Export exports events to a stream
 103    rpc Export(ExportRequest) returns (stream ExportChunk);
 104  
 105    // ImportEventsFromStrings imports events from JSON strings
 106    rpc ImportEventsFromStrings(ImportEventsFromStringsRequest) returns (ImportResponse);
 107  
 108    // === Relay Identity ===
 109  
 110    // GetRelayIdentitySecret gets the relay's secret key
 111    rpc GetRelayIdentitySecret(Empty) returns (GetRelayIdentitySecretResponse);
 112  
 113    // SetRelayIdentitySecret sets the relay's secret key
 114    rpc SetRelayIdentitySecret(SetRelayIdentitySecretRequest) returns (Empty);
 115  
 116    // GetOrCreateRelayIdentitySecret gets or creates the relay's secret key
 117    rpc GetOrCreateRelayIdentitySecret(Empty) returns (GetRelayIdentitySecretResponse);
 118  
 119    // === Markers (Key-Value Storage) ===
 120  
 121    // SetMarker sets a metadata marker
 122    rpc SetMarker(SetMarkerRequest) returns (Empty);
 123  
 124    // GetMarker gets a metadata marker
 125    rpc GetMarker(GetMarkerRequest) returns (GetMarkerResponse);
 126  
 127    // HasMarker checks if a marker exists
 128    rpc HasMarker(HasMarkerRequest) returns (HasMarkerResponse);
 129  
 130    // DeleteMarker deletes a marker
 131    rpc DeleteMarker(DeleteMarkerRequest) returns (Empty);
 132  
 133    // === Subscriptions (Payment-Based Access) ===
 134  
 135    // GetSubscription gets subscription info for a pubkey
 136    rpc GetSubscription(GetSubscriptionRequest) returns (Subscription);
 137  
 138    // IsSubscriptionActive checks if a subscription is active
 139    rpc IsSubscriptionActive(IsSubscriptionActiveRequest) returns (IsSubscriptionActiveResponse);
 140  
 141    // ExtendSubscription extends a subscription by days
 142    rpc ExtendSubscription(ExtendSubscriptionRequest) returns (Empty);
 143  
 144    // RecordPayment records a payment
 145    rpc RecordPayment(RecordPaymentRequest) returns (Empty);
 146  
 147    // GetPaymentHistory gets payment history for a pubkey
 148    rpc GetPaymentHistory(GetPaymentHistoryRequest) returns (PaymentList);
 149  
 150    // ExtendBlossomSubscription extends blossom storage subscription
 151    rpc ExtendBlossomSubscription(ExtendBlossomSubscriptionRequest) returns (Empty);
 152  
 153    // GetBlossomStorageQuota gets blossom storage quota
 154    rpc GetBlossomStorageQuota(GetBlossomStorageQuotaRequest) returns (GetBlossomStorageQuotaResponse);
 155  
 156    // IsFirstTimeUser checks if this is a first-time user
 157    rpc IsFirstTimeUser(IsFirstTimeUserRequest) returns (IsFirstTimeUserResponse);
 158  
 159    // === Paid ACL (Lightning Payment-Gated Access) ===
 160  
 161    // SavePaidSubscription saves or updates a paid subscription
 162    rpc SavePaidSubscription(PaidSubscriptionMsg) returns (Empty);
 163  
 164    // GetPaidSubscription gets a paid subscription by pubkey hex
 165    rpc GetPaidSubscription(GetPaidSubscriptionRequest) returns (PaidSubscriptionMsg);
 166  
 167    // DeletePaidSubscription deletes a paid subscription
 168    rpc DeletePaidSubscription(DeletePaidSubscriptionRequest) returns (Empty);
 169  
 170    // ListPaidSubscriptions lists all paid subscriptions
 171    rpc ListPaidSubscriptions(Empty) returns (PaidSubscriptionList);
 172  
 173    // ClaimAlias claims an email alias for a pubkey
 174    rpc ClaimAlias(ClaimAliasRequest) returns (Empty);
 175  
 176    // GetAliasByPubkey gets the alias for a pubkey
 177    rpc GetAliasByPubkey(GetAliasByPubkeyRequest) returns (AliasResponse);
 178  
 179    // GetPubkeyByAlias gets the pubkey for an alias
 180    rpc GetPubkeyByAlias(GetPubkeyByAliasRequest) returns (PubkeyResponse);
 181  
 182    // IsAliasTaken checks if an alias is taken
 183    rpc IsAliasTaken(IsAliasTakenRequest) returns (IsAliasTakenResponse);
 184  
 185    // === NIP-43 Invite-Based ACL ===
 186  
 187    // AddNIP43Member adds a member via invite code
 188    rpc AddNIP43Member(AddNIP43MemberRequest) returns (Empty);
 189  
 190    // RemoveNIP43Member removes a member
 191    rpc RemoveNIP43Member(RemoveNIP43MemberRequest) returns (Empty);
 192  
 193    // IsNIP43Member checks if a pubkey is a member
 194    rpc IsNIP43Member(IsNIP43MemberRequest) returns (IsNIP43MemberResponse);
 195  
 196    // GetNIP43Membership gets membership details
 197    rpc GetNIP43Membership(GetNIP43MembershipRequest) returns (NIP43Membership);
 198  
 199    // GetAllNIP43Members gets all members
 200    rpc GetAllNIP43Members(Empty) returns (PubkeyList);
 201  
 202    // StoreInviteCode stores an invite code with expiration
 203    rpc StoreInviteCode(StoreInviteCodeRequest) returns (Empty);
 204  
 205    // ValidateInviteCode validates an invite code
 206    rpc ValidateInviteCode(ValidateInviteCodeRequest) returns (ValidateInviteCodeResponse);
 207  
 208    // DeleteInviteCode deletes an invite code
 209    rpc DeleteInviteCode(DeleteInviteCodeRequest) returns (Empty);
 210  
 211    // PublishNIP43MembershipEvent publishes a membership event
 212    rpc PublishNIP43MembershipEvent(PublishNIP43MembershipEventRequest) returns (Empty);
 213  
 214    // === Query Cache ===
 215  
 216    // GetCachedJSON gets cached JSON for a filter
 217    rpc GetCachedJSON(GetCachedJSONRequest) returns (GetCachedJSONResponse);
 218  
 219    // CacheMarshaledJSON caches JSON for a filter
 220    rpc CacheMarshaledJSON(CacheMarshaledJSONRequest) returns (Empty);
 221  
 222    // GetCachedEvents gets cached events for a filter
 223    rpc GetCachedEvents(GetCachedEventsRequest) returns (GetCachedEventsResponse);
 224  
 225    // CacheEvents caches events for a filter
 226    rpc CacheEvents(CacheEventsRequest) returns (Empty);
 227  
 228    // InvalidateQueryCache invalidates the entire query cache
 229    rpc InvalidateQueryCache(Empty) returns (Empty);
 230  
 231    // === Access Tracking ===
 232  
 233    // RecordEventAccess records an access to an event
 234    rpc RecordEventAccess(RecordEventAccessRequest) returns (Empty);
 235  
 236    // GetEventAccessInfo gets access info for an event
 237    rpc GetEventAccessInfo(GetEventAccessInfoRequest) returns (GetEventAccessInfoResponse);
 238  
 239    // GetLeastAccessedEvents gets least accessed events for GC
 240    rpc GetLeastAccessedEvents(GetLeastAccessedEventsRequest) returns (SerialList);
 241  
 242    // === Blob Storage (Blossom) ===
 243  
 244    // SaveBlob stores a blob with its metadata
 245    rpc SaveBlob(SaveBlobRequest) returns (Empty);
 246  
 247    // SaveBlobMetadata stores only metadata for a blob whose file is already on disk
 248    rpc SaveBlobMetadata(SaveBlobMetadataRequest) returns (Empty);
 249  
 250    // GetBlob retrieves blob data and metadata
 251    rpc GetBlob(GetBlobRequest) returns (GetBlobResponse);
 252  
 253    // HasBlob checks if a blob exists
 254    rpc HasBlob(HasBlobRequest) returns (HasBlobResponse);
 255  
 256    // DeleteBlob deletes a blob
 257    rpc DeleteBlob(DeleteBlobRequest) returns (Empty);
 258  
 259    // ListBlobs lists blobs for a pubkey
 260    rpc ListBlobs(ListBlobsRequest) returns (ListBlobsResponse);
 261  
 262    // GetBlobMetadata gets only metadata for a blob
 263    rpc GetBlobMetadata(GetBlobMetadataRequest) returns (BlobMetadata);
 264  
 265    // GetTotalBlobStorageUsed gets total storage used by a pubkey in MB
 266    rpc GetTotalBlobStorageUsed(GetTotalBlobStorageUsedRequest) returns (GetTotalBlobStorageUsedResponse);
 267  
 268    // SaveBlobReport stores a report for a blob (BUD-09)
 269    rpc SaveBlobReport(SaveBlobReportRequest) returns (Empty);
 270  
 271    // ListAllBlobUserStats gets storage statistics for all users
 272    rpc ListAllBlobUserStats(Empty) returns (ListAllBlobUserStatsResponse);
 273  
 274    // ReconcileBlobMetadata scans the blossom directory and creates metadata for orphan blobs
 275    rpc ReconcileBlobMetadata(Empty) returns (ReconcileBlobMetadataResponse);
 276  
 277    // ListAllBlobs returns all blob descriptors (for admin thumbnail generation)
 278    rpc ListAllBlobs(Empty) returns (ListBlobsResponse);
 279  
 280    // GetThumbnail retrieves a cached thumbnail by key
 281    rpc GetThumbnail(GetThumbnailRequest) returns (GetThumbnailResponse);
 282  
 283    // SaveThumbnail stores a cached thumbnail
 284    rpc SaveThumbnail(SaveThumbnailRequest) returns (Empty);
 285  
 286    // === Utility ===
 287  
 288    // EventIdsBySerial gets event IDs by serial range
 289    rpc EventIdsBySerial(EventIdsBySerialRequest) returns (EventIdsBySerialResponse);
 290  
 291    // RunMigrations runs database migrations
 292    rpc RunMigrations(Empty) returns (Empty);
 293  
 294    // === Cypher Query Proxy (Neo4j only) ===
 295  
 296    // ExecuteCypherRead executes a read-only Cypher query against Neo4j
 297    rpc ExecuteCypherRead(CypherReadRequest) returns (CypherReadResponse);
 298  }
 299  
 300  // === Request/Response Messages ===
 301  
 302  // Lifecycle
 303  message PathResponse {
 304    string path = 1;
 305  }
 306  
 307  message ReadyResponse {
 308    bool ready = 1;
 309  }
 310  
 311  message SetLogLevelRequest {
 312    string level = 1;
 313  }
 314  
 315  // Event Storage
 316  message SaveEventRequest {
 317    Event event = 1;
 318  }
 319  
 320  message SaveEventResponse {
 321    bool exists = 1;
 322  }
 323  
 324  message GetSerialsFromFilterRequest {
 325    Filter filter = 1;
 326  }
 327  
 328  message WouldReplaceEventRequest {
 329    Event event = 1;
 330  }
 331  
 332  message WouldReplaceEventResponse {
 333    bool would_replace = 1;
 334    repeated uint64 replaced_serials = 2;
 335  }
 336  
 337  // Event Queries
 338  message QueryEventsRequest {
 339    Filter filter = 1;
 340  }
 341  
 342  message QueryEventsWithOptionsRequest {
 343    Filter filter = 1;
 344    bool include_delete_events = 2;
 345    bool show_all_versions = 3;
 346  }
 347  
 348  message QueryDeleteEventsByTargetIdRequest {
 349    bytes target_event_id = 1;
 350  }
 351  
 352  message CountEventsResponse {
 353    int32 count = 1;
 354    bool approximate = 2;
 355  }
 356  
 357  // Event Retrieval
 358  message FetchEventBySerialRequest {
 359    uint64 serial = 1;
 360  }
 361  
 362  message FetchEventBySerialResponse {
 363    Event event = 1;
 364    bool found = 2;
 365  }
 366  
 367  message FetchEventsBySerialRequest {
 368    repeated uint64 serials = 1;
 369  }
 370  
 371  message GetSerialByIdRequest {
 372    bytes id = 1;
 373  }
 374  
 375  message GetSerialByIdResponse {
 376    uint64 serial = 1;
 377    bool found = 2;
 378  }
 379  
 380  message GetSerialsByIdsRequest {
 381    repeated bytes ids = 1;
 382  }
 383  
 384  message GetSerialsByRangeRequest {
 385    Range range = 1;
 386  }
 387  
 388  message GetFullIdPubkeyBySerialRequest {
 389    uint64 serial = 1;
 390  }
 391  
 392  message GetFullIdPubkeyBySerialsRequest {
 393    repeated uint64 serials = 1;
 394  }
 395  
 396  // Event Deletion
 397  message DeleteEventRequest {
 398    bytes event_id = 1;
 399  }
 400  
 401  message DeleteEventBySerialRequest {
 402    uint64 serial = 1;
 403    Event event = 2;
 404  }
 405  
 406  message ProcessDeleteRequest {
 407    Event event = 1;
 408    repeated bytes admins = 2;
 409  }
 410  
 411  message CheckForDeletedRequest {
 412    Event event = 1;
 413    repeated bytes admins = 2;
 414  }
 415  
 416  // Import/Export
 417  message ImportChunk {
 418    bytes data = 1;
 419  }
 420  
 421  message ImportResponse {
 422    int64 events_imported = 1;
 423    int64 events_skipped = 2;
 424  }
 425  
 426  message ExportRequest {
 427    repeated bytes pubkeys = 1;
 428  }
 429  
 430  message ExportChunk {
 431    bytes data = 1;
 432  }
 433  
 434  message ImportEventsFromStringsRequest {
 435    repeated string event_jsons = 1;
 436    bool check_policy = 2;
 437  }
 438  
 439  // Relay Identity
 440  message GetRelayIdentitySecretResponse {
 441    bytes secret_key = 1;
 442  }
 443  
 444  message SetRelayIdentitySecretRequest {
 445    bytes secret_key = 1;
 446  }
 447  
 448  // Markers
 449  message SetMarkerRequest {
 450    string key = 1;
 451    bytes value = 2;
 452  }
 453  
 454  message GetMarkerRequest {
 455    string key = 1;
 456  }
 457  
 458  message GetMarkerResponse {
 459    bytes value = 1;
 460    bool found = 2;
 461  }
 462  
 463  message HasMarkerRequest {
 464    string key = 1;
 465  }
 466  
 467  message HasMarkerResponse {
 468    bool exists = 1;
 469  }
 470  
 471  message DeleteMarkerRequest {
 472    string key = 1;
 473  }
 474  
 475  // Subscriptions
 476  message GetSubscriptionRequest {
 477    bytes pubkey = 1;
 478  }
 479  
 480  message IsSubscriptionActiveRequest {
 481    bytes pubkey = 1;
 482  }
 483  
 484  message IsSubscriptionActiveResponse {
 485    bool active = 1;
 486  }
 487  
 488  message ExtendSubscriptionRequest {
 489    bytes pubkey = 1;
 490    int32 days = 2;
 491  }
 492  
 493  message RecordPaymentRequest {
 494    bytes pubkey = 1;
 495    int64 amount = 2;
 496    string invoice = 3;
 497    string preimage = 4;
 498  }
 499  
 500  message GetPaymentHistoryRequest {
 501    bytes pubkey = 1;
 502  }
 503  
 504  message ExtendBlossomSubscriptionRequest {
 505    bytes pubkey = 1;
 506    string tier = 2;
 507    int64 storage_mb = 3;
 508    int32 days_extended = 4;
 509  }
 510  
 511  message GetBlossomStorageQuotaRequest {
 512    bytes pubkey = 1;
 513  }
 514  
 515  message GetBlossomStorageQuotaResponse {
 516    int64 quota_mb = 1;
 517  }
 518  
 519  message IsFirstTimeUserRequest {
 520    bytes pubkey = 1;
 521  }
 522  
 523  message IsFirstTimeUserResponse {
 524    bool first_time = 1;
 525  }
 526  
 527  // NIP-43
 528  message AddNIP43MemberRequest {
 529    bytes pubkey = 1;
 530    string invite_code = 2;
 531  }
 532  
 533  message RemoveNIP43MemberRequest {
 534    bytes pubkey = 1;
 535  }
 536  
 537  message IsNIP43MemberRequest {
 538    bytes pubkey = 1;
 539  }
 540  
 541  message IsNIP43MemberResponse {
 542    bool is_member = 1;
 543  }
 544  
 545  message GetNIP43MembershipRequest {
 546    bytes pubkey = 1;
 547  }
 548  
 549  message StoreInviteCodeRequest {
 550    string code = 1;
 551    int64 expires_at = 2;
 552  }
 553  
 554  message ValidateInviteCodeRequest {
 555    string code = 1;
 556  }
 557  
 558  message ValidateInviteCodeResponse {
 559    bool valid = 1;
 560  }
 561  
 562  message DeleteInviteCodeRequest {
 563    string code = 1;
 564  }
 565  
 566  message PublishNIP43MembershipEventRequest {
 567    int32 kind = 1;
 568    bytes pubkey = 2;
 569  }
 570  
 571  // Query Cache
 572  message GetCachedJSONRequest {
 573    Filter filter = 1;
 574  }
 575  
 576  message GetCachedJSONResponse {
 577    repeated bytes json_items = 1;
 578    bool found = 2;
 579  }
 580  
 581  message CacheMarshaledJSONRequest {
 582    Filter filter = 1;
 583    repeated bytes json_items = 2;
 584  }
 585  
 586  message GetCachedEventsRequest {
 587    Filter filter = 1;
 588  }
 589  
 590  message GetCachedEventsResponse {
 591    repeated Event events = 1;
 592    bool found = 2;
 593  }
 594  
 595  message CacheEventsRequest {
 596    Filter filter = 1;
 597    repeated Event events = 2;
 598  }
 599  
 600  // Access Tracking
 601  message RecordEventAccessRequest {
 602    uint64 serial = 1;
 603    string connection_id = 2;
 604  }
 605  
 606  message GetEventAccessInfoRequest {
 607    uint64 serial = 1;
 608  }
 609  
 610  message GetEventAccessInfoResponse {
 611    int64 last_access = 1;
 612    uint32 access_count = 2;
 613  }
 614  
 615  message GetLeastAccessedEventsRequest {
 616    int32 limit = 1;
 617    int64 min_age_sec = 2;
 618  }
 619  
 620  // Utility
 621  message EventIdsBySerialRequest {
 622    uint64 start = 1;
 623    int32 count = 2;
 624  }
 625  
 626  message EventIdsBySerialResponse {
 627    repeated uint64 event_ids = 1;
 628  }
 629  
 630  // === Blob Storage Messages ===
 631  
 632  message BlobMetadata {
 633    bytes pubkey = 1;
 634    string mime_type = 2;
 635    int64 size = 3;
 636    int64 uploaded = 4;  // Unix timestamp
 637    string extension = 5;
 638  }
 639  
 640  message BlobDescriptor {
 641    string url = 1;
 642    string sha256 = 2;
 643    int64 size = 3;
 644    string type = 4;  // MIME type
 645    int64 uploaded = 5;
 646  }
 647  
 648  message SaveBlobRequest {
 649    bytes sha256_hash = 1;
 650    bytes data = 2;
 651    bytes pubkey = 3;
 652    string mime_type = 4;
 653    string extension = 5;
 654  }
 655  
 656  message SaveBlobMetadataRequest {
 657    bytes sha256_hash = 1;
 658    int64 size = 2;
 659    bytes pubkey = 3;
 660    string mime_type = 4;
 661    string extension = 5;
 662  }
 663  
 664  message GetBlobRequest {
 665    bytes sha256_hash = 1;
 666  }
 667  
 668  message GetBlobResponse {
 669    bool found = 1;
 670    bytes data = 2;
 671    BlobMetadata metadata = 3;
 672  }
 673  
 674  message HasBlobRequest {
 675    bytes sha256_hash = 1;
 676  }
 677  
 678  message HasBlobResponse {
 679    bool exists = 1;
 680  }
 681  
 682  message DeleteBlobRequest {
 683    bytes sha256_hash = 1;
 684    bytes pubkey = 2;
 685  }
 686  
 687  message ListBlobsRequest {
 688    bytes pubkey = 1;
 689    int64 since = 2;
 690    int64 until = 3;
 691  }
 692  
 693  message ListBlobsResponse {
 694    repeated BlobDescriptor descriptors = 1;
 695  }
 696  
 697  message GetBlobMetadataRequest {
 698    bytes sha256_hash = 1;
 699  }
 700  
 701  message GetTotalBlobStorageUsedRequest {
 702    bytes pubkey = 1;
 703  }
 704  
 705  message GetTotalBlobStorageUsedResponse {
 706    int64 total_mb = 1;
 707  }
 708  
 709  message SaveBlobReportRequest {
 710    bytes sha256_hash = 1;
 711    bytes report_data = 2;
 712  }
 713  
 714  message UserBlobStats {
 715    string pubkey_hex = 1;
 716    int64 blob_count = 2;
 717    int64 total_size_bytes = 3;
 718  }
 719  
 720  message ListAllBlobUserStatsResponse {
 721    repeated UserBlobStats stats = 1;
 722  }
 723  
 724  message ReconcileBlobMetadataResponse {
 725    int32 reconciled = 1;
 726  }
 727  
 728  // Thumbnail caching messages
 729  message GetThumbnailRequest {
 730    string key = 1;
 731  }
 732  
 733  message GetThumbnailResponse {
 734    bool found = 1;
 735    bytes data = 2;
 736  }
 737  
 738  message SaveThumbnailRequest {
 739    string key = 1;
 740    bytes data = 2;
 741  }
 742  
 743  // Cypher Query Proxy
 744  message CypherReadRequest {
 745    string cypher = 1;
 746    map<string, bytes> params = 2;  // JSON-encoded parameter values
 747    int32 timeout_seconds = 3;       // query timeout (0 = use default)
 748  }
 749  
 750  message CypherReadResponse {
 751    repeated bytes records = 1;  // each record is a JSON-encoded map
 752    string error = 2;
 753  }
 754  
 755  // Paid ACL messages
 756  message PaidSubscriptionMsg {
 757    string pubkey_hex = 1;
 758    string alias = 2;
 759    int64 expires_at = 3;    // Unix timestamp
 760    int64 created_at = 4;    // Unix timestamp
 761    string invoice_hash = 5;
 762  }
 763  
 764  message GetPaidSubscriptionRequest {
 765    string pubkey_hex = 1;
 766  }
 767  
 768  message DeletePaidSubscriptionRequest {
 769    string pubkey_hex = 1;
 770  }
 771  
 772  message PaidSubscriptionList {
 773    repeated PaidSubscriptionMsg subscriptions = 1;
 774  }
 775  
 776  message ClaimAliasRequest {
 777    string alias = 1;
 778    string pubkey_hex = 2;
 779  }
 780  
 781  message GetAliasByPubkeyRequest {
 782    string pubkey_hex = 1;
 783  }
 784  
 785  message AliasResponse {
 786    string alias = 1;
 787  }
 788  
 789  message GetPubkeyByAliasRequest {
 790    string alias = 1;
 791  }
 792  
 793  message PubkeyResponse {
 794    string pubkey_hex = 1;
 795  }
 796  
 797  message IsAliasTakenRequest {
 798    string alias = 1;
 799  }
 800  
 801  message IsAliasTakenResponse {
 802    bool taken = 1;
 803  }
 804