diff --git a/endpoints/endpoint_schema.sql b/endpoints/endpoint_schema.sql index 8517c1e7799a323da2341109284b5f2506f887ef..eba0d359744e2165e105035f930d77680e280e22 100644 --- a/endpoints/endpoint_schema.sql +++ b/endpoints/endpoint_schema.sql @@ -34,7 +34,6 @@ DO $__$ END IF; CREATE SCHEMA IF NOT EXISTS nfttracker_endpoints AUTHORIZATION nfttracker_owner; - CREATE SCHEMA IF NOT EXISTS nfttracker_backend AUTHORIZATION nfttracker_owner; EXECUTE FORMAT( 'create or replace function nfttracker_endpoints.root() returns json as $_$ @@ -45,7 +44,7 @@ DO $__$ { "components": { "schemas": { - "nfttracker_backend.nft_type": { + "nfttracker_endpoints.nft_type": { "type": "object", "properties": { "id": { @@ -91,7 +90,7 @@ DO $__$ } } }, - "nfttracker_backend.nft_instance": { + "nfttracker_endpoints.nft_instance": { "type": "object", "properties": { "id": { @@ -217,24 +216,29 @@ DO $__$ ], "responses": { "200": { - "description": "Issued NFT instances of given symbol\n\n* Returns `nfttracker_backend.nft_instance`\n", + "description": "Issued NFT instances of given symbol\n\n* Returns `nfttracker_endpoints.nft_instance`\n", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/nfttracker_backend.nft_instance" + "type": "array", + "items": { + "$ref": "#/components/schemas/nfttracker_endpoints.nft_instance" + } }, - "example": { - "id": 1, - "holder": "alice", - "data": "{\"key\": \"value\"}", - "tags": [ - "item", - "collectible" - ], - "soulbound": false, - "created_at": "2025-08-22T12:00:00", - "updated_at": "2025-08-22T12:00:00" - } + "example": [ + { + "id": 1, + "holder": "alice", + "data": "{\"key\": \"value\"}", + "tags": [ + "item", + "collectible" + ], + "soulbound": false, + "created_at": "2025-08-22T12:00:00", + "updated_at": "2025-08-22T12:00:00" + } + ] } } }, @@ -254,22 +258,27 @@ DO $__$ "operationId": "nfttracker_endpoints.get_nft_types", "responses": { "200": { - "description": "Registered NFT types\n\n* Returns `nfttracker_backend.nft_type`\n", + "description": "Registered NFT types\n\n* Returns `nfttracker_endpoints.nft_type`\n", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/nfttracker_backend.nft_type" + "type": "array", + "items": { + "$ref": "#/components/schemas/nfttracker_endpoints.nft_type" + } }, - "example": { - "id": 1, - "creator": "alice", - "owner": "bob", - "symbol": "TEST", - "name": "Test symbol", - "max_count": 10, - "created_at": "2025-08-22T12:00:00", - "updated_at": "2025-08-22T12:00:00" - } + "example": [ + { + "id": 1, + "creator": "alice", + "owner": "bob", + "symbol": "TEST", + "name": "Test symbol", + "max_count": 10, + "created_at": "2025-08-22T12:00:00", + "updated_at": "2025-08-22T12:00:00" + } + ] } } } diff --git a/endpoints/get_nft_instances.sql b/endpoints/get_nft_instances.sql index f0593ecb7d0a5416a647de18f027e19e8cd3a10f..2b6a820c872f0395b047c9e1e133ef46bae2e25e 100644 --- a/endpoints/get_nft_instances.sql +++ b/endpoints/get_nft_instances.sql @@ -1,4 +1,4 @@ -SET ROLE nft_owner; +SET ROLE nfttracker_owner; /** openapi:paths /nfts/{creator}/{symbol}: @@ -33,12 +33,14 @@ SET ROLE nft_owner; description: | Issued NFT instances of given symbol - * Returns `nfttracker_backend.nft_instance` + * Returns `nfttracker_endpoints.nft_instance` content: application/json: schema: - $ref: '#/components/schemas/nfttracker_backend.nft_instance' - example: { + type: array + items: + $ref: '#/components/schemas/nfttracker_endpoints.nft_instance' + example: [{ "id": 1, "holder": "alice", "data": "{\"key\": \"value\"}", @@ -46,7 +48,7 @@ SET ROLE nft_owner; "soulbound": false, "created_at": "2025-08-22T12:00:00", "updated_at": "2025-08-22T12:00:00" - } + }] '404': description: | creator/symbol combination does not exist @@ -57,7 +59,7 @@ CREATE OR REPLACE FUNCTION nfttracker_endpoints.get_nft_instances( "creator" TEXT, "symbol" TEXT ) -RETURNS nfttracker_backend.nft_instance +RETURNS nfttracker_endpoints.nft_instance[] -- openapi-generated-code-end LANGUAGE 'plpgsql' STABLE AS @@ -68,23 +70,25 @@ DECLARE BEGIN PERFORM set_config('response.headers', '[{"Cache-Control": "public, max-age=2"}]', true); - RETURN ( - SELECT ROW( - i.id, - h.name, - i.data, - i.tags, - i.soulbound, - i.created_at, - i.updated_at - )::nfttracker_backend.nft_instance - FROM nfttracker_app.instances AS i - INNER JOIN nfttracker_app.types AS t ON i.type_id = t.id - LEFT JOIN hafd.accounts AS h ON i.holder = h.id - WHERE t.symbol = _symbol - AND t.creator = (SELECT id FROM hafd.accounts WHERE name = _creator) - ORDER BY t.id - LIMIT 1 + RETURN COALESCE( + ARRAY( + SELECT ROW( + i.id, + h.name, + i.data, + i.tags, + i.soulbound, + i.created_at, + i.updated_at + )::nfttracker_endpoints.nft_instance + FROM nfttracker_app.instances AS i + INNER JOIN nfttracker_app.types AS t ON i.type_id = t.id + LEFT JOIN hafd.accounts AS h ON i.holder = h.id + WHERE t.symbol = _symbol + AND t.creator = (SELECT id FROM hafd.accounts WHERE name = _creator) + ORDER BY i.id + ), + ARRAY[]::nfttracker_endpoints.nft_instance[] ); END $$; diff --git a/endpoints/get_nft_types.sql b/endpoints/get_nft_types.sql index 6f2883ee591b09bb22b905c8da180bfcd2d2417c..8798f8e9e305427eda0828b78742a03b35c82604 100644 --- a/endpoints/get_nft_types.sql +++ b/endpoints/get_nft_types.sql @@ -1,4 +1,4 @@ -SET ROLE nft_owner; +SET ROLE nfttracker_owner; /** openapi:paths /nfts: @@ -20,12 +20,14 @@ SET ROLE nft_owner; description: | Registered NFT types - * Returns `nfttracker_backend.nft_type` + * Returns `nfttracker_endpoints.nft_type` content: application/json: schema: - $ref: '#/components/schemas/nfttracker_backend.nft_type' - example: { + type: array + items: + $ref: '#/components/schemas/nfttracker_endpoints.nft_type' + example: [{ "id": 1, "creator": "alice", "owner": "bob", @@ -34,12 +36,12 @@ SET ROLE nft_owner; "max_count": 10, "created_at": "2025-08-22T12:00:00", "updated_at": "2025-08-22T12:00:00" - } + }] */ -- openapi-generated-code-begin DROP FUNCTION IF EXISTS nfttracker_endpoints.get_nft_types; CREATE OR REPLACE FUNCTION nfttracker_endpoints.get_nft_types() -RETURNS nfttracker_backend.nft_type +RETURNS nfttracker_endpoints.nft_type[] -- openapi-generated-code-end LANGUAGE 'plpgsql' STABLE AS @@ -47,26 +49,28 @@ $$ BEGIN PERFORM set_config('response.headers', '[{"Cache-Control": "public, max-age=2"}]', true); - RETURN ( - SELECT ROW( - t.id, - c.name, - o.name, - t.symbol, - t.name, - t.max_count, - t.created_at, - t.updated_at, - ARRAY_AGG(DISTINCT a.name) FILTER (WHERE a.name IS NOT NULL) - )::nfttracker_backend.nft_type - FROM nfttracker_app.types AS t - LEFT JOIN nfttracker_app.authorized_issuers AS ai ON t.id = ai.type_id - LEFT JOIN hafd.accounts AS a ON ai.account_id = a.id - LEFT JOIN hafd.accounts AS c ON t.creator = c.id - LEFT JOIN hafd.accounts AS o ON t.owner = o.id - GROUP BY t.id, c.name, o.name, t.symbol, t.name, t.max_count, t.created_at, t.updated_at - ORDER BY t.id - LIMIT 1 + RETURN COALESCE( + ARRAY( + SELECT ROW( + t.id, + c.name, + o.name, + t.symbol, + t.name, + t.max_count, + t.created_at, + t.updated_at, + ARRAY_AGG(DISTINCT a.name) FILTER (WHERE a.name IS NOT NULL) + )::nfttracker_endpoints.nft_type + FROM nfttracker_app.types AS t + LEFT JOIN nfttracker_app.authorized_issuers AS ai ON t.id = ai.type_id + LEFT JOIN hafd.accounts AS a ON ai.account_id = a.id + LEFT JOIN hafd.accounts AS c ON t.creator = c.id + LEFT JOIN hafd.accounts AS o ON t.owner = o.id + GROUP BY t.id, c.name, o.name, t.symbol, t.name, t.max_count, t.created_at, t.updated_at + ORDER BY t.id + ), + ARRAY[]::nfttracker_endpoints.nft_type[] ); END $$; diff --git a/endpoints/types/nft_instance.sql b/endpoints/types/nft_instance.sql index ece8954ef343c55bc8aa77ec1602c2f274b7928d..01c1c796e7d67ec619b8e1ce2801edf144210fab 100644 --- a/endpoints/types/nft_instance.sql +++ b/endpoints/types/nft_instance.sql @@ -1,5 +1,5 @@ /** openapi:components:schemas -nfttracker_backend.nft_instance: +nfttracker_endpoints.nft_instance: type: object properties: id: @@ -30,8 +30,8 @@ nfttracker_backend.nft_instance: description: the timestamp this instance was last modified */ -- openapi-generated-code-begin -DROP TYPE IF EXISTS nfttracker_backend.nft_instance CASCADE; -CREATE TYPE nfttracker_backend.nft_instance AS ( +DROP TYPE IF EXISTS nfttracker_endpoints.nft_instance CASCADE; +CREATE TYPE nfttracker_endpoints.nft_instance AS ( "id" INT, "holder" TEXT, "data" TEXT, diff --git a/endpoints/types/nft_type.sql b/endpoints/types/nft_type.sql index 976292864caa5365e9c49bb23efcd674f3018073..2445ceb936ad53878a460ae4f14cc5d91135d7bd 100644 --- a/endpoints/types/nft_type.sql +++ b/endpoints/types/nft_type.sql @@ -1,5 +1,5 @@ /** openapi:components:schemas -nfttracker_backend.nft_type: +nfttracker_endpoints.nft_type: type: object properties: id: @@ -35,8 +35,8 @@ nfttracker_backend.nft_type: description: list of accounts that can issue instance of this NFT type */ -- openapi-generated-code-begin -DROP TYPE IF EXISTS nfttracker_backend.nft_type CASCADE; -CREATE TYPE nfttracker_backend.nft_type AS ( +DROP TYPE IF EXISTS nfttracker_endpoints.nft_type CASCADE; +CREATE TYPE nfttracker_endpoints.nft_type AS ( "id" INT, "creator" TEXT, "owner" TEXT, diff --git a/scripts/install_app.sh b/scripts/install_app.sh index c99d0dc8c29b14fce614ac99eb4fd5bdc2edcc20..a876a4bada655df2391a738c3fea9b9210e9e42b 100755 --- a/scripts/install_app.sh +++ b/scripts/install_app.sh @@ -12,6 +12,7 @@ OPTIONS: --postgres-port=PORT PostgreSQL port (default: 5432) --postgres-user=USERNAME PostgreSQL user name (default: haf_admin) --postgres-url=URL PostgreSQL URL (if set, overrides three previous options, empty by default) + --swagger-url=URL Server URL for OpenAPI documentation (default: localhost) --help,-h,-? Displays this help message EOF } @@ -21,6 +22,7 @@ POSTGRES_HOST=${POSTGRES_HOST:-"localhost"} POSTGRES_PORT=${POSTGRES_PORT:-5432} POSTGRES_URL=${POSTGRES_URL:-""} NFTTRACKER_SCHEMA=${NFTTRACKER_SCHEMA:-"nfttracker_app"} +SWAGGER_URL=${SWAGGER_URL:-"localhost"} while [ $# -gt 0 ]; do case "$1" in @@ -39,6 +41,9 @@ while [ $# -gt 0 ]; do --schema=*) NFTTRACKER_SCHEMA="${1#*=}" ;; + --swagger-url=*) + SWAGGER_URL="${1#*=}" + ;; --help|-h|-?) print_help exit 0 @@ -66,9 +71,15 @@ psql "$POSTGRES_ACCESS" -v ON_ERROR_STOP=on -f "$SCRIPTPATH/../db/nft_actions.s psql "$POSTGRES_ACCESS" -v ON_ERROR_STOP=on -f "$SCRIPTPATH/../db/main_loop.sql" echo "Installing API endpoints..." +# Install type definitions first +psql "$POSTGRES_ACCESS" -v ON_ERROR_STOP=on -f "$SCRIPTPATH/../endpoints/types/nft_type.sql" +psql "$POSTGRES_ACCESS" -v ON_ERROR_STOP=on -f "$SCRIPTPATH/../endpoints/types/nft_instance.sql" + # Install endpoint schema and functions -psql "$POSTGRES_ACCESS" -v ON_ERROR_STOP=on -f "$SCRIPTPATH/../endpoints/endpoint_schema.sql" +psql "$POSTGRES_ACCESS" -v ON_ERROR_STOP=on -c "SET custom.swagger_url = '$SWAGGER_URL';" -f "$SCRIPTPATH/../endpoints/endpoint_schema.sql" psql "$POSTGRES_ACCESS" -v ON_ERROR_STOP=on -f "$SCRIPTPATH/../endpoints/get_version.sql" +psql "$POSTGRES_ACCESS" -v ON_ERROR_STOP=on -f "$SCRIPTPATH/../endpoints/get_nft_types.sql" +psql "$POSTGRES_ACCESS" -v ON_ERROR_STOP=on -f "$SCRIPTPATH/../endpoints/get_nft_instances.sql" echo "Granting permissions..." psql "$POSTGRES_ACCESS" -v ON_ERROR_STOP=on -c "SET ROLE nfttracker_owner; GRANT USAGE ON SCHEMA ${NFTTRACKER_SCHEMA} to nfttracker_user;"