UNPKG

gatefold

Version:

The compact URL shortener. Developed at Cimpress.

770 lines (768 loc) 26.4 kB
{ "swagger": "2.0", "info": { "description": "A simple service that provide URL shortening capabilities through the $GATEFOLD_DOMAIN domain. \n\n A longer URL can be shortened to the following form: https://$GATEFOLD_DOMAIN/[uniqueId]. When accessed, this short URL will automatically redirect to the original using a standard HTTP 302 response with a properly set Location header.\n\nHappy shortening!", "version": "$GATEFOLD_VERSION", "title": "Gatefold" }, "host": "$GATEFOLD_DOMAIN", "schemes": [ "https" ], "paths": { "/": { "post": { "consumes": [ "application/json" ], "produces": [ "application/json" ], "summary": "Request a short URL within the $GATEFOLD_DOMAIN domain.", "parameters": [ { "in": "body", "name": "PostRequest", "required": true, "schema": { "$ref": "#/definitions/PostRequest" } } ], "responses": { "201": { "description": "Shortened version created successfully", "schema": { "$ref": "#/definitions/PostPutResponse" }, "headers": { "Access-Control-Allow-Origin": { "type": "string" }, "Location": { "type": "string" } } }, "500": { "description": "Internal server error", "headers": { "Access-Control-Allow-Origin": { "type": "string" } } }, "503": { "description": "Service temporarily unavailable", "headers": { "Access-Control-Allow-Origin": { "type": "string" } } } }, "x-amazon-apigateway-request-validator": "Validate body", "x-amazon-apigateway-integration": { "credentials": { "Fn::GetAtt": [ "GatefoldAPIRole", "Arn" ] }, "uri": "arn:aws:apigateway:eu-west-1:dynamodb:action/UpdateItem", "responses": { "4\\d{2}": { "statusCode": "503", "responseParameters": { "method.response.header.Access-Control-Allow-Origin": "'*'" } }, "5\\d{2}": { "statusCode": "500", "responseParameters": { "method.response.header.Access-Control-Allow-Origin": "'*'" } }, "2\\d{2}": { "statusCode": "201", "responseParameters": { "method.response.header.Access-Control-Allow-Origin": "'*'", "method.response.header.Location": "integration.response.body.Attributes.ShortUrl.S" }, "responseTemplates": { "application/json": "#set($short = $input.json(\"$.Attributes.ShortUrl.S\").replace('\"', \"\"))\n{\n \"longUrl\": $input.json(\"$.Attributes.LongUrl.S\"),\n \"shortUrl\": \"$short\",\n \"token\": $input.json(\"$.Attributes.Token.S\")\n}" } } }, "passthroughBehavior": "never", "httpMethod": "POST", "requestTemplates": { "application/json": "#set ($guid = $context.requestId)\n#set ($suffix1 = $guid.substring(0,4))\n#set ($suffix2 = $guid.substring(24,28))\n#set ($short = \"https://$GATEFOLD_DOMAIN/$suffix1$suffix2\")\n#set ($token = $guid.substring(0))\n#set ($curTime = $context.requestTimeEpoch / 1000)\n#set ($ttl = $curTime + 86400 * $GATEFOLD_TTL)\n{\n \"TableName\": \"gatefold-$GATEFOLD_DOMAIN\",\n \"Key\": {\n \"ShortUrl\": {\n \"S\": \"$short\"\n }\n },\n \"ExpressionAttributeNames\": {\n \"#Token\": \"Token\"\n },\n \"ExpressionAttributeValues\": {\n \":longUrl\": {\n \"S\": $input.json(\"$.longUrl\")\n },\n \":token\": {\n \"S\": \"$token\"\n },\n \":currentTime\": {\n \"N\": \"$curTime\"\n },\n \":expiresAt\": {\n \"N\": \"$ttl\"\n }\n },\n \"UpdateExpression\": \"SET LongUrl = :longUrl, #Token = :token, CreatedAt = :currentTime, ModifiedAt = :currentTime, ExpiresAt = :expiresAt\",\n \"ConditionExpression\": \"attribute_not_exists(ShortUrl)\",\n \"ReturnValues\": \"ALL_NEW\"\n}" }, "type": "aws" } }, "options": { "consumes": [ "application/json" ], "produces": [ "application/json" ], "responses": { "200": { "description": "200 response", "schema": { "$ref": "#/definitions/Empty" }, "headers": { "Access-Control-Allow-Origin": { "type": "string" }, "Access-Control-Allow-Methods": { "type": "string" }, "Access-Control-Allow-Headers": { "type": "string" } } } }, "x-amazon-apigateway-integration": { "responses": { "default": { "statusCode": "200", "responseParameters": { "method.response.header.Access-Control-Allow-Methods": "'POST,OPTIONS'", "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'", "method.response.header.Access-Control-Allow-Origin": "'*'" } } }, "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, "type": "mock" } } }, "/swagger": { "get": { "consumes": [ "application/json" ], "produces": [ "application/json" ], "description": "Produces the Gatefold API definition.", "responses": { "200": { "description": "OpenAPI 2.0 (Swagger) Gatefold API definition", "schema": { "$ref": "#/definitions/Empty" }, "headers": { "Access-Control-Allow-Origin": { "type": "string" }, "Access-Control-Allow-Methods": { "type": "string" }, "Access-Control-Allow-Headers": { "type": "string" } } } }, "x-amazon-apigateway-integration": { "responses": { "default": { "statusCode": "200", "responseParameters": { "method.response.header.Access-Control-Allow-Methods": "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'", "method.response.header.Access-Control-Allow-Headers": "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'", "method.response.header.Access-Control-Allow-Origin": "'*'" }, "responseTemplates": { "application/json": "" } } }, "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, "passthroughBehavior": "when_no_match", "type": "mock" } }, "options": { "consumes": [ "application/json" ], "produces": [ "application/json" ], "responses": { "200": { "description": "200 response", "schema": { "$ref": "#/definitions/Empty" }, "headers": { "Access-Control-Allow-Origin": { "type": "string" }, "Access-Control-Allow-Methods": { "type": "string" }, "Access-Control-Allow-Headers": { "type": "string" } } } }, "x-amazon-apigateway-integration": { "responses": { "default": { "statusCode": "200", "responseParameters": { "method.response.header.Access-Control-Allow-Methods": "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'", "method.response.header.Access-Control-Allow-Headers": "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'", "method.response.header.Access-Control-Allow-Origin": "'*'" } } }, "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, "passthroughBehavior": "when_no_match", "type": "mock" } } }, "/livecheck": { "get": { "consumes": [ "application/json" ], "produces": [ "application/json" ], "description": "Provide a healthcheck interface.", "responses": { "200": { "description": "200 response", "schema": { "$ref": "#/definitions/Empty" }, "headers": { "Access-Control-Allow-Origin": { "type": "string" } } } }, "x-amazon-apigateway-integration": { "responses": { "default": { "statusCode": "200", "responseParameters": { "method.response.header.Access-Control-Allow-Origin": "'*'" } } }, "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, "type": "mock" } }, "options": { "consumes": [ "application/json" ], "produces": [ "application/json" ], "responses": { "200": { "description": "200 response", "schema": { "$ref": "#/definitions/Empty" }, "headers": { "Access-Control-Allow-Origin": { "type": "string" }, "Access-Control-Allow-Methods": { "type": "string" }, "Access-Control-Allow-Headers": { "type": "string" } } } }, "x-amazon-apigateway-integration": { "responses": { "default": { "statusCode": "200", "responseParameters": { "method.response.header.Access-Control-Allow-Methods": "'GET,OPTIONS'", "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'", "method.response.header.Access-Control-Allow-Origin": "'*'" } } }, "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, "type": "mock" } } }, "/not-found": { "get": { "consumes": [ "application/json" ], "responses": { "404": { "description": "404 response", "headers": { "Access-Control-Allow-Origin": { "type": "string" } } } }, "description": "Returns a 404 status code.", "x-amazon-apigateway-integration": { "responses": { "default": { "statusCode": "404", "responseParameters": { "method.response.header.Access-Control-Allow-Origin": "'*'" } } }, "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 404}" }, "type": "mock" } }, "options": { "consumes": [ "application/json" ], "produces": [ "application/json" ], "responses": { "200": { "description": "200 response", "schema": { "$ref": "#/definitions/Empty" }, "headers": { "Access-Control-Allow-Origin": { "type": "string" }, "Access-Control-Allow-Methods": { "type": "string" }, "Access-Control-Allow-Headers": { "type": "string" } } } }, "x-amazon-apigateway-integration": { "responses": { "default": { "statusCode": "200", "responseParameters": { "method.response.header.Access-Control-Allow-Methods": "'GET,OPTIONS'", "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'", "method.response.header.Access-Control-Allow-Origin": "'*'" } } }, "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, "type": "mock" } } }, "/{id}": { "get": { "produces": [ "application/json" ], "summary": "Retrieve the long URL associated with the id. This endpoint returns a HTTP 302 response with a Location header containing the long URL.", "parameters": [ { "name": "id", "in": "path", "required": true, "type": "string" } ], "responses": { "302": { "description": "Redirection to the original URL", "headers": { "Location": { "description": "Location of the original resource", "type": "string" }, "Access-Control-Expose-Headers": { "type": "string" } } } }, "x-amazon-apigateway-integration": { "uri": "https://$GATEFOLD_DOMAIN/{id}/proxied", "responses": { "default": { "statusCode": "302", "responseParameters": { "method.response.header.Location": "integration.response.body.longUrl", "method.response.header.Access-Control-Expose-Headers": "'Location'" }, "responseTemplates": { "application/json": "#set($inputRoot = $input.path('$'))\n{}" } } }, "requestParameters": { "integration.request.path.id": "method.request.path.id" }, "passthroughBehavior": "when_no_match", "httpMethod": "GET", "type": "http" } }, "put": { "consumes": [ "application/json" ], "produces": [ "application/json" ], "summary": "Updates the URL hidden behind the specified ID and refreshes the TTL of the ID, meaning that the short URL will continue to be available for a longer period of time.", "parameters": [ { "name": "id", "description": "An unique ID identifying the short URL to update", "in": "path", "required": true, "type": "string" }, { "in": "body", "name": "PutRequest", "required": true, "schema": { "$ref": "#/definitions/PutRequest" } } ], "responses": { "200": { "description": "Short URL successfully updated", "schema": { "$ref": "#/definitions/PostPutResponse" }, "headers": { "Access-Control-Allow-Origin": { "type": "string" } } }, "400": { "description": "400 response", "headers": { "Access-Control-Allow-Origin": { "type": "string" } } }, "500": { "description": "500 response", "headers": { "Access-Control-Allow-Origin": { "type": "string" } } } }, "x-amazon-apigateway-request-validator": "Validate body", "x-amazon-apigateway-integration": { "credentials": { "Fn::GetAtt": [ "GatefoldAPIRole", "Arn" ] }, "uri": "arn:aws:apigateway:eu-west-1:dynamodb:action/UpdateItem", "responses": { "4\\d{2}": { "statusCode": "400", "responseParameters": { "method.response.header.Access-Control-Allow-Origin": "'*'" }, "responseTemplates": { "application/json": "{\n \"message\" : \"There already exists a resource with that ID and you've specified an incorrect token.\"\n}" } }, "5\\d{2}": { "statusCode": "500", "responseParameters": { "method.response.header.Access-Control-Allow-Origin": "'*'" } }, "2\\d{2}": { "statusCode": "200", "responseParameters": { "method.response.header.Access-Control-Allow-Origin": "'*'" }, "responseTemplates": { "application/json": "#set($short = $input.json(\"$.Attributes.ShortUrl.S\").replace('\"', \"\"))\n{\n \"longUrl\": $input.json(\"$.Attributes.LongUrl.S\"),\n \"shortUrl\": \"$short\",\n \"token\": $input.json(\"$.Attributes.Token.S\")\n}" } } }, "passthroughBehavior": "never", "httpMethod": "POST", "requestTemplates": { "application/json": "#set ($suffix = $input.params(\"id\"))\n#set ($token = $input.json(\"$.token\"))\n#set ($curTime = $context.requestTimeEpoch / 1000)\n#set ($ttl = $curTime + 86400 * $GATEFOLD_TTL)\n{\n \"TableName\": \"gatefold-$GATEFOLD_DOMAIN\",\n \"Key\": {\n \"ShortUrl\": {\n \"S\": \"https://$GATEFOLD_DOMAIN/$suffix\"\n }\n },\n \"ExpressionAttributeNames\": {\n \"#Token\": \"Token\"\n },\n \"ExpressionAttributeValues\": {\n \":longUrl\": {\n \"S\": $input.json('$.longUrl')\n },\n \":token\": {\n \"S\": $token\n },\n \":currentTime\": {\n \"N\": \"$curTime\"\n },\n \":expiresAt\": {\n \"N\": \"$ttl\"\n }\n },\n \"UpdateExpression\": \"SET LongUrl = :longUrl, #Token = :token, CreatedAt = if_not_exists(CreatedAt, :currentTime), ModifiedAt = :currentTime, ExpiresAt = :expiresAt\",\n \"ConditionExpression\": \"(attribute_not_exists(ShortUrl)) or (#Token = :token)\",\n \"ReturnValues\": \"ALL_NEW\"\n}" }, "type": "aws" } }, "options": { "consumes": [ "application/json" ], "produces": [ "application/json" ], "parameters": [ { "name": "id", "in": "path", "required": true, "type": "string" } ], "responses": { "200": { "description": "200 response", "schema": { "$ref": "#/definitions/Empty" }, "headers": { "Access-Control-Allow-Origin": { "type": "string" }, "Access-Control-Allow-Methods": { "type": "string" }, "Access-Control-Allow-Headers": { "type": "string" } } } }, "x-amazon-apigateway-integration": { "responses": { "default": { "statusCode": "200", "responseParameters": { "method.response.header.Access-Control-Allow-Methods": "'GET,PUT,OPTIONS'", "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'", "method.response.header.Access-Control-Allow-Origin": "'*'" } } }, "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, "type": "mock" } } }, "/{id}/proxied": { "get": { "consumes": [ "application/json" ], "produces": [ "application/json" ], "description": "Required for applying a logical OR operator on one of the returned fields. This endpoint has no SLA and may be terminated without notice.", "parameters": [ { "name": "id", "in": "path", "required": true, "type": "string" } ], "responses": { "200": { "description": "200 response" } }, "x-amazon-apigateway-integration": { "credentials": { "Fn::GetAtt": [ "GatefoldAPIRole", "Arn" ] }, "uri": "arn:aws:apigateway:eu-west-1:dynamodb:action/GetItem", "responses": { "default": { "statusCode": "200", "responseTemplates": { "application/json": "#set($inputRoot = $input.path('$'))\n#if($inputRoot.Item.LongUrl.S.isEmpty())\n #set($val = \"https://$GATEFOLD_DOMAIN/not-found\")\n#else\n #set($val = $inputRoot.Item.LongUrl.S)\n#end\n\n{\n \"longUrl\": \"$val\"\n}" } } }, "passthroughBehavior": "never", "httpMethod": "POST", "requestTemplates": { "application/json": "{\n \"TableName\": \"gatefold-$GATEFOLD_DOMAIN\",\n \"Key\": {\n \"ShortUrl\": {\n \"S\": \"https://$GATEFOLD_DOMAIN/$input.params(\"id\")\"\n }\n }\n}" }, "type": "aws" } }, "options": { "produces": [ "application/json" ], "parameters": [ { "name": "id", "in": "path", "required": true, "type": "string" } ], "responses": { "200": { "description": "200 response", "schema": { "$ref": "#/definitions/Empty" }, "headers": { "Access-Control-Allow-Origin": { "type": "string" }, "Access-Control-Allow-Methods": { "type": "string" }, "Access-Control-Allow-Headers": { "type": "string" } } } }, "x-amazon-apigateway-integration": { "credentials": { "Fn::GetAtt": [ "GatefoldAPIRole", "Arn" ] }, "uri": "arn:aws:apigateway:eu-west-1:dynamodb:action/GetItem", "responses": { "default": { "statusCode": "200" } }, "passthroughBehavior": "never", "httpMethod": "POST", "type": "aws" } } } }, "definitions": { "Empty": { "type": "object", "title": "Empty Schema" }, "PostPutResponse": { "type": "object", "properties": { "longUrl": { "type": "string", "description": "The long URL which the short URL will redirect to.", "example": "https://www.example.org" }, "shortUrl": { "type": "string", "description": "The short URL that would automatically redirect to the configured long URL.", "example": "https://$GATEFOLD_DOMAIN/ae7d2f3e" }, "token": { "type": "string", "description": "A token that can be used to update the long URL for the generated short URL at later stage.", "example": "ae7d8ca0-8533-11e8-8eba-2f3e608cc07e" } }, "title": "PostPutRequest" }, "PostRequest": { "type": "object", "required": [ "longUrl" ], "properties": { "longUrl": { "type": "string", "description": "The long URL to shorten", "example": "https://www.example.org" } }, "title": "PostRequest" }, "PutRequest": { "type": "object", "required": [ "longUrl", "token" ], "properties": { "longUrl": { "type": "string", "description": "A (new) URL to use for the specified short URL", "example": "https://www.example.org" }, "token": { "type": "string", "description": "The token that was generated when the short URL was initially created.", "example": "ae7d8ca0-8533-11e8-8eba-2f3e608cc07e" } }, "title": "PutRequest" } }, "x-amazon-apigateway-gateway-responses": { "DEFAULT_4XX": { "statusCode": "400", "responseParameters": { "gatewayresponse.header.Access-Control-Allow-Origin": "'*'" }, "responseTemplates": { "application/json": "{\"message\":$context.error.messageString}" } }, "DEFAULT_5XX": { "statusCode": "500", "responseParameters": { "gatewayresponse.header.Access-Control-Allow-Origin": "'*'" }, "responseTemplates": { "application/json": "{\"message\":$context.error.messageString}" } } }, "x-amazon-apigateway-request-validators": { "Validate body": { "validateRequestParameters": false, "validateRequestBody": true } } }