-
Notifications
You must be signed in to change notification settings - Fork 0
ENG-11298 generate protos for managed catalog api #16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
5e9d1cc
generate protos for managed catalog api
stephensxu 128d26b
fix enum definitions so they are consistent with linting rule and reg…
stephensxu a02a927
revert unrelated redoc.v1.html regeneration
stephensxu 616ed55
update doc description to add base url
stephensxu ea509bb
update exampel response with correct response string
stephensxu 6256cdb
update doc to include instruction on how to create ingress api key
stephensxu 2ce294f
proto updates to address soeren comments
stephensxu bb41f92
Revert non-deterministic redoc.v1.html regeneration
stephensxu 1a064ed
fix example to show published status
stephensxu File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| { | ||
| "swagger": "2.0", | ||
| "info": { | ||
| "title": "Stash Managed Catalog API", | ||
| "description": "Read-only API for querying the Stash managed product catalog. Designed for server-to-server communication between partner backends and Stash services.\n\n## Base URLs\n\n| Environment | Base URL |\n|---|---|\n| **Test** | `https://test-api.stash.gg` |\n| **Production** | `https://api.stash.gg` |\n\nAll endpoint paths below are relative to the base URL. For example, to list products in the test environment:\n\n```\nGET https://test-api.stash.gg/api/v1/studio/{shop_id}/products\n```\n\n## Authentication\n\nAll requests must include an HMAC-SHA256 signature in the `stash-hmac-signature` header.\n\n### Setup\n\n1. Go to **Stash Studio > Project Settings > API Secrets**\n2. Click **Generate Secret** to create a new key\n3. Keys created through this portal are Ingress keys by default and work with this API (this is the same key used for `/sdk/` API authentication)\n4. Store the secret securely — you will need it to sign every request\n\n### Signing a Request\n\n1. For GET requests (no body), sign an **empty string** using HMAC-SHA256 with your API secret\n2. Base64-encode the resulting signature\n3. Send it in the `stash-hmac-signature` HTTP header\n\n### Example (curl)\n\n```bash\nSIGNATURE=$(echo -n \"\" | openssl dgst -sha256 -hmac \"YOUR_API_SECRET\" -binary | base64)\ncurl -H \"stash-hmac-signature: $SIGNATURE\" \\\n \"https://test-api.stash.gg/api/v1/studio/{shop_id}/products\"\n```", | ||
| "version": "1.0.0", | ||
| "contact": { | ||
| "name": "API Support", | ||
| "url": "https://docs.stash.gg/", | ||
| "email": "help@stash.gg" | ||
| } | ||
| }, | ||
| "schemes": ["https"], | ||
| "consumes": ["application/json"], | ||
| "produces": ["application/json"], | ||
| "tags": [ | ||
| { | ||
| "name": "ManagedCatalog", | ||
| "description": "Read-only endpoints for querying the managed product catalog" | ||
| } | ||
| ], | ||
| "paths": { | ||
| "$ref": "../../gen/openapiv2/server/ingress/studio/v1/service.swagger.json#paths" | ||
| }, | ||
| "definitions": { | ||
| "$ref": "../../gen/openapiv2/server/ingress/studio/v1/service.swagger.json#definitions" | ||
| }, | ||
| "securityDefinitions": { | ||
| "$ref": "../../gen/openapiv2/server/ingress/server.swagger.json#securityDefinitions" | ||
| } | ||
| } |
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| syntax = "proto3"; | ||
|
|
||
| // buf:lint:ignore PACKAGE_VERSION_SUFFIX | ||
| package server.ingress; | ||
|
|
||
| import "protoc-gen-openapiv2/options/annotations.proto"; | ||
|
|
||
| option go_package = "github.com/stashgg/public-api/gen/proto/server/ingress"; | ||
| option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { | ||
| info: { | ||
| title: "Stash Managed Catalog API" | ||
| description: "Read-only API for querying the Stash managed product catalog. Designed for server-to-server communication between partner backends and Stash services." | ||
| version: "1.0" | ||
| contact: { | ||
| name: "API Support" | ||
| url: "https://docs.stash.gg/" | ||
| email: "help@stash.gg" | ||
| } | ||
| } | ||
| schemes: HTTPS | ||
| consumes: "application/json" | ||
| produces: "application/json" | ||
| security_definitions: { | ||
| security: { | ||
| key: "hmac" | ||
| value: { | ||
| name: "stash-hmac-signature" | ||
| in: IN_HEADER | ||
| type: TYPE_API_KEY | ||
| description: "HMAC-SHA256 signature generated by signing the request body (empty string for GET requests) with the Ingress API key from Stash Studio. Base64-encoded." | ||
| } | ||
| } | ||
| } | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| syntax = "proto3"; | ||
|
|
||
| package server.ingress.studio.v1; | ||
|
|
||
| import "protoc-gen-openapiv2/options/annotations.proto"; | ||
|
|
||
| option go_package = "github.com/stashgg/public-api/gen/proto/server/ingress/studio/v1"; | ||
|
|
||
| enum ProductStatus { | ||
| option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum) = {description: "Product publication status. This API only returns published products."}; | ||
| reserved 1; | ||
| reserved "PRODUCT_STATUS_DRAFT"; | ||
| PRODUCT_STATUS_UNSPECIFIED = 0; | ||
| PRODUCT_STATUS_PUBLISHED = 2; | ||
| } | ||
|
|
||
| enum ProductPricePlatform { | ||
| option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum) = {description: "Platform for a price entry"}; | ||
| PRODUCT_PRICE_PLATFORM_UNSPECIFIED = 0; | ||
| PRODUCT_PRICE_PLATFORM_UNIVERSAL = 1; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: looks like ANDROID and IOS were omitted. that's fine for now, you can always add them later. just checking that was intentional :) |
||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,220 @@ | ||
| syntax = "proto3"; | ||
|
|
||
| package server.ingress.studio.v1; | ||
|
|
||
| import "google/api/annotations.proto"; | ||
| import "google/api/field_behavior.proto"; | ||
| import "google/protobuf/timestamp.proto"; | ||
| import "protoc-gen-openapiv2/options/annotations.proto"; | ||
| import "server/ingress/studio/v1/enums.proto"; | ||
| import "server/ingress/studio/v1/types.proto"; | ||
| import "validate/validate.proto"; | ||
|
|
||
| option go_package = "github.com/stashgg/public-api/gen/proto/server/ingress/studio/v1"; | ||
|
|
||
| service ManagedCatalogService { | ||
| option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_tag) = { | ||
| name: "ManagedCatalog" | ||
| description: "Read-only endpoints for querying the managed product catalog" | ||
| }; | ||
| rpc ListProducts(ListProductsRequest) returns (ListProductsResponse) { | ||
| option (google.api.http) = {get: "/api/v1/studio/{shop_id}/products"}; | ||
| option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { | ||
| summary: "List published products" | ||
| description: "Retrieves a paginated list of published products for a shop. Results are ordered by creation date (most recent first). Only published products are returned; drafts and deleted products are excluded." | ||
| security: [ | ||
| { | ||
| security_requirement: {key: "hmac"} | ||
| } | ||
| ] | ||
| }; | ||
| } | ||
| rpc GetProduct(GetProductRequest) returns (GetProductResponse) { | ||
| option (google.api.http) = {get: "/api/v1/studio/{shop_id}/products/{guid}"}; | ||
| option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { | ||
| summary: "Get a single published product" | ||
| description: "Retrieves a single published product by its GUID. Returns NOT_FOUND if the product does not exist or is not published." | ||
| security: [ | ||
| { | ||
| security_requirement: {key: "hmac"} | ||
| } | ||
| ] | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| message Product { | ||
| option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { | ||
| json_schema: { | ||
| title: "Product" | ||
| description: "A published product from the managed catalog" | ||
| } | ||
| }; | ||
| string guid = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Stash-internal unique product identifier (UUID)"}]; | ||
| LocalizableText name = 2 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Product name"}]; | ||
| optional string product_id = 3 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Partner-defined product identifier (e.g., SKU). Always non-empty for published products"}]; | ||
| optional LocalizableText description = 4 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Product description. Always non-empty for published products"}]; | ||
| optional Images images = 5 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Product images"}]; | ||
| ProductStatus status = 6 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { | ||
| description: "Product status. Always PRODUCT_STATUS_PUBLISHED for this API" | ||
| default: "PRODUCT_STATUS_PUBLISHED" | ||
| example: "\"PRODUCT_STATUS_PUBLISHED\"" | ||
| }]; | ||
| google.protobuf.Timestamp created_at = 7 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Timestamp when the product was created (RFC 3339)"}]; | ||
| google.protobuf.Timestamp updated_at = 8 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Timestamp when the product was last updated (RFC 3339)"}]; | ||
| optional LocalizableText display_name = 9 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Human-readable display name. Always non-empty for published products"}]; | ||
| repeated Price prices = 10 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { | ||
| description: "Array of price entries. At least one entry (USD/UNIVERSAL/US) is guaranteed for paid products. Free gift products have an empty array." | ||
| example: "[{\"currency\": \"USD\", \"platform\": \"PRODUCT_PRICE_PLATFORM_UNIVERSAL\", \"region\": \"US\", \"cents\": 499, \"updatedAt\": \"2026-01-15T10:30:00Z\"}]" | ||
| }]; | ||
| repeated Item items = 11 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { | ||
| description: "Items contained in this product. May be empty if no items configured. If present, each item has a non-empty image and quantity > 0." | ||
| example: "[{\"image\": \"https://example.com/coin.png\", \"quantity\": \"100\", \"displayName\": {\"defaultText\": \"Gold Coin\"}}]" | ||
| }]; | ||
| } | ||
|
|
||
| message Images { | ||
| option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { | ||
| json_schema: { | ||
| title: "Images" | ||
| description: "Product images" | ||
| } | ||
| }; | ||
| string main_image = 1 [ | ||
| (validate.rules).string = { | ||
| uri: true | ||
| ignore_empty: true | ||
| }, | ||
| (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Primary product image URL. Always non-empty for published products"} | ||
| ]; | ||
| optional string background_image = 2 [ | ||
| (validate.rules).string = { | ||
| uri: true | ||
| ignore_empty: true | ||
| }, | ||
| (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Background image URL"} | ||
| ]; | ||
| } | ||
|
|
||
| message Price { | ||
| option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { | ||
| json_schema: { | ||
| title: "Price" | ||
| description: "A price entry for a product" | ||
| } | ||
| }; | ||
| string currency = 1 [ | ||
| (validate.rules).string = { | ||
| min_len: 3 | ||
| max_len: 3 | ||
| }, | ||
| (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Currency code (ISO-4217, e.g., \"USD\", \"EUR\")"} | ||
| ]; | ||
| ProductPricePlatform platform = 2 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { | ||
| description: "Platform (e.g., UNIVERSAL)" | ||
| default: "PRODUCT_PRICE_PLATFORM_UNIVERSAL" | ||
| example: "\"PRODUCT_PRICE_PLATFORM_UNIVERSAL\"" | ||
| }]; | ||
| string region = 3 [ | ||
| (validate.rules).string = { | ||
| min_len: 2 | ||
| max_len: 3 | ||
| }, | ||
| (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Region code (ISO-3166-1 alpha-2, e.g., \"US\", \"EU\")"} | ||
| ]; | ||
| uint32 cents = 4 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Price in smallest currency unit (e.g., 499 for $4.99)"}]; | ||
| google.protobuf.Timestamp updated_at = 5 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Timestamp when this price was last updated (RFC 3339)"}]; | ||
| } | ||
|
|
||
| message Item { | ||
| option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { | ||
| json_schema: { | ||
| title: "Item" | ||
| description: "An item contained in a product" | ||
| } | ||
| }; | ||
| string image = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "URL to the item image. Always non-empty for published products"}]; | ||
| string quantity = 2 [ | ||
| (validate.rules).string = { | ||
| min_len: 1 | ||
| pattern: "^[0-9]+$" | ||
| }, | ||
| (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "In-game item quantity as string for big integer support (no decimals allowed). Always > 0 for published products"} | ||
| ]; | ||
| optional LocalizableText display_name = 3 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Optional display name for the item (max 50 characters)"}]; | ||
| } | ||
|
|
||
| message ListProductsRequest { | ||
| option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { | ||
| json_schema: { | ||
| title: "List Products Request" | ||
| description: "Request to list published products for a shop" | ||
| required: ["shop_id"] | ||
| } | ||
| }; | ||
| string shop_id = 1 [ | ||
| (google.api.field_behavior) = REQUIRED, | ||
| (validate.rules).string.uuid = true, | ||
| (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Shop identifier from Stash Studio"} | ||
| ]; | ||
| optional uint32 limit = 2 [ | ||
| (validate.rules).uint32.lte = 100, | ||
| (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { | ||
| description: "Maximum results per page. Defaults to 50. Maximum allowed value is 100." | ||
| minimum: 1 | ||
| maximum: 100 | ||
| } | ||
| ]; | ||
| optional uint32 offset = 3 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { | ||
| description: "Pagination offset. Use next_offset from the previous response to fetch the next page. Defaults to 0." | ||
| minimum: 0 | ||
| }]; | ||
| } | ||
|
|
||
| message ListProductsResponse { | ||
| option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { | ||
| json_schema: { | ||
| title: "List Products Response" | ||
| description: "Paginated list of published products" | ||
| } | ||
| }; | ||
| repeated Product products = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "List of published products"}]; | ||
| optional uint32 next_offset = 2 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Offset for the next page. Omitted if there are no more results."}]; | ||
| } | ||
|
|
||
| message GetProductRequest { | ||
| option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { | ||
| json_schema: { | ||
| title: "Get Product Request" | ||
| description: "Request to get a single published product" | ||
| required: [ | ||
| "shop_id", | ||
| "guid" | ||
| ] | ||
| } | ||
| }; | ||
| string shop_id = 1 [ | ||
| (google.api.field_behavior) = REQUIRED, | ||
| (validate.rules).string.uuid = true, | ||
| (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Shop identifier from Stash Studio"} | ||
| ]; | ||
| string guid = 2 [ | ||
| (google.api.field_behavior) = REQUIRED, | ||
| (validate.rules).string.uuid = true, | ||
| (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Stash-internal unique product identifier (UUID)"} | ||
| ]; | ||
| } | ||
|
|
||
| message GetProductResponse { | ||
| option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { | ||
| json_schema: { | ||
| title: "Get Product Response" | ||
| description: "Response containing a single published product" | ||
| required: ["product"] | ||
| } | ||
| }; | ||
| Product product = 1 [ | ||
| (google.api.field_behavior) = REQUIRED, | ||
| (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The requested product"} | ||
| ]; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| syntax = "proto3"; | ||
|
|
||
| package server.ingress.studio.v1; | ||
|
|
||
| import "protoc-gen-openapiv2/options/annotations.proto"; | ||
| import "validate/validate.proto"; | ||
|
|
||
| option go_package = "github.com/stashgg/public-api/gen/proto/server/ingress/studio/v1"; | ||
|
|
||
| message LocalizableText { | ||
| option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { | ||
| json_schema: { | ||
| title: "Localizable Text" | ||
| description: "Text content that can be localized for a specific language" | ||
| } | ||
| }; | ||
| string default_text = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Default text content, usually in English"}]; | ||
| oneof localization { | ||
| string localization_key = 2 [ | ||
| (validate.rules).string = { | ||
| min_len: 1 | ||
| ignore_empty: true | ||
| }, | ||
| (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Key for client-side localization lookup"} | ||
| ]; | ||
| string localized_text = 3 [ | ||
| (validate.rules).string = { | ||
| min_len: 1 | ||
| ignore_empty: true | ||
| }, | ||
| (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Server-resolved localized text"} | ||
| ]; | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: looks like
ProductStatusjumps from UNSPECIFIED=0 to PUBLISHED=2, skipping 1is that intentional?
if your intent was to reserve 1 for
DRAFTin case it's needed at some point, consider doing: