Skip to content

Commit 6aad76c

Browse files
author
Tyler Ayers
committed
updated firestore demo
1 parent b702dc6 commit 6aad76c

35 files changed

+871
-0
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Apigee Firestore Quickstart & Adapter
2+
This repository contains a quickstart proxy for the [Apige API Management](cloud.google.com/apigee) platform to connect to a collection in a [Firestore](cloud.google.com/firestore) database. The API provided by the proxy can then be easily used by any REST clients, including [AppSheet](cloud.google.com/appsheet) no-code apps.
3+
4+
## Prerequisites
5+
Firestore is a great document database for storing any kind of unstructured data, however we do need some structures to automatically use any database with this quickstart.
6+
* An **id** field in each document, which should match the document key. This makes identifying the record quick and easy. It's easiest if this field is also called **id** in the document.
7+
* Supported data types - we only support these data types in the documents for now:
8+
* string
9+
* boolean
10+
* geopoint (locations)
11+
* timestamp
12+
* Not-supported data types - we don't support these data types, since they would be recursive and be difficult for REST clients like AppSheet to work with.
13+
* array - It is recommended to have sub-array collections as separate collections (using an id field as link).
14+
* map - This could be easily added and flattened into the REST response object, but on the other hand makes life difficult for clients like AppSheet, so it is recommended to also model sub-map structures as separate collections (using an id field as link).
15+
16+
As you can see, only primitive data types are supported in the Firestore documents, which makes it easy to map into REST resource structures.
17+
18+
## Quickstart deploy
19+
The fastest way to deploy and spin up an API offering CRUD operations on your Firestore database is to import the **Firestore Quickstart** proxy bundle in Apigee, and then set these properties:
20+
21+
1. In the default Endpoint PreFlow, replace the following variables in the 1st policy **Set-Firestore-Variables** to your own values:
22+
1. **GCP_PROJECT** - replace with the GCP project name where your Firestore database is located.
23+
2. **firestore.key** - in case the **id** index field isn't named **id** in the Firestore documents (see prerequisites above), then you should set the correct name here (it is recommended to just use **id**).
24+
3. **GCP_SVC_KEY** - set this to your GCP service account private key (which has the permissions to access Firestore in your GCP project) from the **private_key** field in the key JSON file (beginning with -----BEGIN PRIVATE KEY----- and ending with -----END PRIVATE KEY-----)
25+
4. **GCP_SVC_EMAIL** - set this to the GCP service account email address from the **client_email** field in the private key JSON file.
26+
2. Change the proxy basepath to something meaningful (default is **/importantstuff**, for no particular reason)
27+
3. Deploy the proxy. By default an API key is activated for the proxy, disable the 2nd policy in the PreFlow if you want to test without one. Now you can call https://host/basepath/collection/document to all CRUD operations on your Firestore data, using simple JSON payload messages. WooHoo!
28+
4. You can also use this proxy as an API data source in an AppSheet app, it will just work.
29+
30+
## Example
31+
An example is deployed for this Firestore data structure:
32+
```json
33+
{
34+
"name": "projects/tyler-240211/databases/(default)/documents/jokes/8ebb59f9",
35+
"fields": {
36+
"funny": {
37+
"booleanValue": true
38+
},
39+
"imagePath": {
40+
"stringValue": "https://dakiniland.files.wordpress.com/2011/05/102-0907085235-simpsons-mutant-fish-blinky.jpg"
41+
},
42+
"location": {
43+
"geoPointValue": {
44+
"latitude": 29.987294,
45+
"longitude": -39.6875
46+
}
47+
},
48+
"punchline": {
49+
"stringValue": "A fiiish."
50+
},
51+
"text": {
52+
"stringValue": "What do you call a fish with 3 eyes?"
53+
},
54+
"id": {
55+
"stringValue": "8ebb59f9"
56+
},
57+
"timestamp": {
58+
"timestampValue": "2020-10-08T13:05:29Z"
59+
}
60+
},
61+
"createTime": "2020-10-08T11:06:55.234785Z",
62+
"updateTime": "2020-10-08T11:21:56.903039Z"
63+
}
64+
```
65+
This proxy then offers REST CRUD APIs with this much nicer structure:
66+
```json
67+
{
68+
"id": "8ebb59f9",
69+
"funny": true,
70+
"text": "What do you call a fish with 3 eyes?",
71+
"imagePath": "https://dakiniland.files.wordpress.com/2011/05/102-0907085235-simpsons-mutant-fish-blinky.jpg",
72+
"timestamp": "2020-10-08T13:05:29Z",
73+
"punchline": "A fiiish.",
74+
"location": "29.987294, -39.6875"
75+
}
76+
```
77+
The OpenAPI spec for the example is under /specs, and can also be used to create an AppSheet app based on the API.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<APIProxy revision="2" name="Firestore Quickstart">
3+
<Basepaths>/importantstuff</Basepaths>
4+
<ConfigurationVersion majorVersion="4" minorVersion="0"/>
5+
<CreatedAt>1602160821771</CreatedAt>
6+
<CreatedBy>defaultUser</CreatedBy>
7+
<Description></Description>
8+
<DisplayName>Firestore Quickstart</DisplayName>
9+
<LastModifiedAt>1602163293230</LastModifiedAt>
10+
<LastModifiedBy>defaultUser</LastModifiedBy>
11+
<ManifestVersion>SHA-512:f55c15016835194ad271386c01805f23ab7de0f0150b4fe54abdbef4e3234c63d192850e55867d31fcce62f54651cbbc1858305b57ebd95a7743dd1b455c5087</ManifestVersion>
12+
<Policies>
13+
<Policy>Add-Bearer-Token</Policy>
14+
<Policy>Assign-Message-2</Policy>
15+
<Policy>Extract-Token</Policy>
16+
<Policy>Generate-JWT-1</Policy>
17+
<Policy>Get-Token</Policy>
18+
<Policy>Get-Url-Variables</Policy>
19+
<Policy>Remove-Key</Policy>
20+
<Policy>Set-Firestore-Variables</Policy>
21+
<Policy>Set-Verb</Policy>
22+
<Policy>Verify-API-Key-1</Policy>
23+
<Policy>set-request</Policy>
24+
<Policy>set-response</Policy>
25+
</Policies>
26+
<ProxyEndpoints>
27+
<ProxyEndpoint>default</ProxyEndpoint>
28+
</ProxyEndpoints>
29+
<Resources>
30+
<Resource>jsc://set-request.js</Resource>
31+
<Resource>jsc://set-response.js</Resource>
32+
</Resources>
33+
<Spec></Spec>
34+
<TargetServers/>
35+
<TargetEndpoints>
36+
<TargetEndpoint>default</TargetEndpoint>
37+
</TargetEndpoints>
38+
</APIProxy>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<Manifest name="manifest">
3+
<Policies>
4+
<VersionInfo resourceName="Add-Bearer-Token" version="SHA-512:0a42e948dc886f1ee94cf3a6cd2044a5b8004e5d239e8ebc4265f17e4dda90a2d9886429b233d78907cfb729e225c189c8ee241bb0fc8a636c052475f2c5c489"/>
5+
<VersionInfo resourceName="Assign-Message-2" version="SHA-512:5c9a169578d7f452969b7dbb44a000475b0a61a6c7b739114963456a658337a098bdd7174bdd0651754e85489baaffe3db918628a3b689382d88d88466b827c2"/>
6+
<VersionInfo resourceName="Extract-Token" version="SHA-512:4f4270a4ed5d1002201079227cadc3253ad782d975dac8c11fc008078fe9ea5b81f854997b65177b671eef415533a7c2ce3bd3f276c2e50bda3fb0a593b416b8"/>
7+
<VersionInfo resourceName="Generate-JWT-1" version="SHA-512:d19b26a4a6c624a7796fb141d7e949b43efea04b044f26a39c14dac54a30543d8f31dd0cd0a0417415a05a806b9dae3a60bb94d46b837f8949f4b15533f858fe"/>
8+
<VersionInfo resourceName="Get-Token" version="SHA-512:d4ac90e3b0ac6b99a90d6053f08eaccec7266a9f434653ea2790d035a6bb04f4408831cbf8c71725c1595ce1651a69847ae2764d9a6201647b0831ce78fd0770"/>
9+
<VersionInfo resourceName="Get-Url-Variables" version="SHA-512:b4a5dd888e335d53285434fded8d4d84d7e40fe76657e4d3cc3e2d245662fd7048fec80317db35b3a4bd340f2be7ba2902f268176f8aef6201810c1180ff1edc"/>
10+
<VersionInfo resourceName="Remove-Key" version="SHA-512:b6fb9136a1298dcf9ce8b43499a435b65036ccb75dfaf769e4fab75da31df42160aa3d2a15d1e785bc30680c9bdb236265177144d8643ac96622a2c876bfe164"/>
11+
<VersionInfo resourceName="Set-Firestore-Variables" version="SHA-512:32babd2deb3befbf46befd4d854a55e7e9ceb9e3abfe39b54628fdccb944bec37df81473c78f5205eda90ca1f1dd83dd850f652e71072d074b53f00dc96dcb0f"/>
12+
<VersionInfo resourceName="Set-Verb" version="SHA-512:76784cb53c1295f9e2ac251f02e6eb26d9286347fb2fdfb90e82e72d294013f7d78cf5c0d33168bcb9f9dd2f6b9f557b8a6254c237ba8cc20ff74c0faa35c1f6"/>
13+
<VersionInfo resourceName="Verify-API-Key-1" version="SHA-512:28cb2309eaccd38bee6b4efc4faa587ed762418dd22ff3bf3ae55660878f593e73af43854dfe0dd2b482a3522a59862620f935e366755089b2494bbde894ae4f"/>
14+
<VersionInfo resourceName="set-request" version="SHA-512:0237b172885d6edaa7e8d2c84707e6dd4fbc62ed5e8ee69064db009c47a0622ae92946d3115df780055157e373bbed0d16f7c9f51afa81e9d2d3c20d6cde278a"/>
15+
<VersionInfo resourceName="set-response" version="SHA-512:4c83ad5e2bb224e89afef7a4b97bb86c5d35619f27380745a928847a0d2ac4a3252d5e2d48fe622aedbbfda4741e6f1d44da98a43601622b946506e90ac92c2d"/>
16+
</Policies>
17+
<ProxyEndpoints>
18+
<VersionInfo resourceName="default" version="SHA-512:827d0169b798a0c16bf27a591965504cc89b7c6ef6d466a799650926710922ddd73af8d6568148f5894742ef0dcbcc1240fce32923f6db53f0eb4da180a56049"/>
19+
</ProxyEndpoints>
20+
<Resources>
21+
<VersionInfo resourceName="jsc://set-request.js" version="SHA-512:273a744f438d33ebf0349b156593fa9bdf972caaaefc3acc96bffedcd8cecb43e13c4b7b5ab8df4a8703f90967249f3ec5780896fcc5c9ec6edb1afb20b2cef2"/>
22+
<VersionInfo resourceName="jsc://set-response.js" version="SHA-512:8406be79fa657b8c9ba3d755b27129b71395fba86b146eb37b6884fc000efba05ed287cac997c056834ebeb39f35f2bf4928c1fa4d36daa2c4142acaddee0d57"/>
23+
</Resources>
24+
<SharedFlows/>
25+
<TargetEndpoints>
26+
<VersionInfo resourceName="default" version="SHA-512:396130306b0d6d56e456ef651457aa11677bc4d2092fe7a57580aa2cd593ad1d116cc5cc94ca0684914093d8700cae1492c2474a8b0d0b5deb4e77bb4b3abe55"/>
27+
</TargetEndpoints>
28+
</Manifest>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<AssignMessage async="false" continueOnError="false" enabled="true" name="Add-Bearer-Token">
3+
<DisplayName>Add Bearer Token</DisplayName>
4+
<Properties/>
5+
<Set>
6+
<Headers>
7+
<Header name="Authorization">Bearer {firestore.token}</Header>
8+
</Headers>
9+
<Path/>
10+
</Set>
11+
<IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
12+
<AssignTo createNew="false" transport="http" type="request"/>
13+
</AssignMessage>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<AssignMessage async="false" continueOnError="false" enabled="true" name="Assign-Message-2">
3+
<DisplayName>Assign Message-2</DisplayName>
4+
<Properties/>
5+
<AssignVariable>
6+
<Name>target.copy.pathsuffix</Name>
7+
<Value>false</Value>
8+
</AssignVariable>
9+
<IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
10+
<AssignTo createNew="false" transport="http" type="request"/>
11+
</AssignMessage>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<ExtractVariables async="false" continueOnError="false" enabled="true" name="Extract-Token">
3+
<DisplayName>Extract Token</DisplayName>
4+
<Properties/>
5+
<IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
6+
<JSONPayload>
7+
<Variable name="firestore.token">
8+
<JSONPath>$.access_token</JSONPath>
9+
</Variable>
10+
</JSONPayload>
11+
<Source clearPayload="false">google-token-response</Source>
12+
<VariablePrefix/>
13+
</ExtractVariables>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<GenerateJWT async="false" continueOnError="false" enabled="true" name="Generate-JWT-1">
3+
<Algorithm>RS256</Algorithm>
4+
<IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
5+
<PrivateKey>
6+
<Value ref="private.gkey"/>
7+
</PrivateKey>
8+
<Subject ref="firestore.serviceemail"/>
9+
<Issuer ref="firestore.serviceemail"/>
10+
<Audience>https://www.googleapis.com/oauth2/v4/token</Audience>
11+
<ExpiresIn>60m</ExpiresIn>
12+
<Id/>
13+
<AdditionalClaims>
14+
<Claim name="scope">https://www.googleapis.com/auth/datastore</Claim>
15+
</AdditionalClaims>
16+
<OutputVariable>google-jwt</OutputVariable>
17+
</GenerateJWT>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<ServiceCallout async="false" continueOnError="false" enabled="true" name="Get-Token">
3+
<DisplayName>Get-Google-Token</DisplayName>
4+
<Properties/>
5+
<Request clearPayload="true" variable="googletokenrequest">
6+
<Set>
7+
<Headers>
8+
<Header name="Content-Type">application/x-www-form-urlencoded</Header>
9+
</Headers>
10+
<QueryParams>
11+
<QueryParam name="grant_type">urn:ietf:params:oauth:grant-type:jwt-bearer</QueryParam>
12+
<QueryParam name="assertion">{google-jwt}</QueryParam>
13+
</QueryParams>
14+
<FormParams/>
15+
<Payload/>
16+
<ReasonPhrase/>
17+
<StatusCode/>
18+
<Path/>
19+
<Version>1.1</Version>
20+
<Verb>POST</Verb>
21+
</Set>
22+
<IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
23+
</Request>
24+
<Response>google-token-response</Response>
25+
<HTTPTargetConnection>
26+
<Properties/>
27+
<URL>https://www.googleapis.com/oauth2/v4/token</URL>
28+
</HTTPTargetConnection>
29+
</ServiceCallout>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<ExtractVariables async="false" continueOnError="false" enabled="true" name="Get-Url-Variables">
3+
<DisplayName>Get Url Variables</DisplayName>
4+
<URIPath>
5+
<Pattern ignoreCase="true">/{collection}</Pattern>
6+
<Pattern ignoreCase="true">/{collection}/{document}</Pattern>
7+
</URIPath>
8+
<VariablePrefix>firestore</VariablePrefix>
9+
<IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
10+
<Source clearPayload="false">request</Source>
11+
</ExtractVariables>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<AssignMessage async="false" continueOnError="false" enabled="true" name="Remove-Key">
3+
<DisplayName>Remove Key</DisplayName>
4+
<Properties/>
5+
<Remove>
6+
<Headers>
7+
<Header name="x-apikey"/>
8+
</Headers>
9+
</Remove>
10+
<IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
11+
<AssignTo createNew="false" transport="http" type="request"/>
12+
</AssignMessage>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<AssignMessage async="false" continueOnError="false" enabled="true" name="Set-Firestore-Variables">
3+
<DisplayName>Set Firestore Variables</DisplayName>
4+
<Properties/>
5+
<AssignVariable>
6+
<Name>firestore.project</Name>
7+
<Value>GCP_PROJECT</Value>
8+
</AssignVariable>
9+
<AssignVariable>
10+
<Name>firestore.document</Name>
11+
<Value/>
12+
</AssignVariable>
13+
<AssignVariable>
14+
<Name>firestore.input</Name>
15+
<Ref>request.content</Ref>
16+
</AssignVariable>
17+
<AssignVariable>
18+
<Name>firestore.key</Name>
19+
<Value>id</Value>
20+
</AssignVariable>
21+
<AssignVariable>
22+
<Name>private.gkey</Name>
23+
<Value>
24+
GCP_SVC_KEY
25+
</Value>
26+
</AssignVariable>
27+
<AssignVariable>
28+
<Name>firestore.serviceemail</Name>
29+
<Value>GCP_SVC_EMAIL</Value>
30+
</AssignVariable>
31+
<IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
32+
<AssignTo createNew="false" transport="http" type="request"/>
33+
</AssignMessage>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<AssignMessage async="false" continueOnError="false" enabled="true" name="Set-Verb">
3+
<DisplayName>Set-Verb</DisplayName>
4+
<Properties/>
5+
<Set>
6+
<Verb>PATCH</Verb>
7+
</Set>
8+
<IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
9+
<AssignTo createNew="false" transport="http" type="request"/>
10+
</AssignMessage>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<VerifyAPIKey async="false" continueOnError="false" enabled="false" name="Verify-API-Key-1">
3+
<DisplayName>Verify API Key-1</DisplayName>
4+
<Properties/>
5+
<APIKey ref="request.header.x-apikey"/>
6+
</VerifyAPIKey>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<Javascript async="false" continueOnError="false" enabled="true" timeLimit="200" name="set-request">
3+
<DisplayName>set-request</DisplayName>
4+
<Properties/>
5+
<ResourceURL>jsc://set-request.js</ResourceURL>
6+
</Javascript>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<Javascript async="false" continueOnError="false" enabled="true" timeLimit="200" name="set-response">
3+
<DisplayName>set-response</DisplayName>
4+
<Properties/>
5+
<ResourceURL>jsc://set-response.js</ResourceURL>
6+
</Javascript>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<ProxyEndpoint name="default">
3+
<PreFlow name="PreFlow">
4+
<Request>
5+
<Step>
6+
<Name>Set-Firestore-Variables</Name>
7+
</Step>
8+
<Step>
9+
<Name>Verify-API-Key-1</Name>
10+
</Step>
11+
<Step>
12+
<Name>Remove-Key</Name>
13+
</Step>
14+
<Step>
15+
<Name>Get-Url-Variables</Name>
16+
</Step>
17+
<Step>
18+
<Name>Generate-JWT-1</Name>
19+
</Step>
20+
<Step>
21+
<Name>Get-Token</Name>
22+
</Step>
23+
<Step>
24+
<Name>Extract-Token</Name>
25+
</Step>
26+
<Step>
27+
<Condition>request.verb = "PUT" or request.verb = "POST"</Condition>
28+
<Name>set-request</Name>
29+
</Step>
30+
<Step>
31+
<Condition>request.verb = "PUT" or request.verb = "POST"</Condition>
32+
<Name>Set-Verb</Name>
33+
</Step>
34+
</Request>
35+
<Response/>
36+
</PreFlow>
37+
<Flows/>
38+
<PostFlow name="PostFlow">
39+
<Request/>
40+
<Response>
41+
<Step>
42+
<Name>set-response</Name>
43+
</Step>
44+
</Response>
45+
</PostFlow>
46+
<HTTPProxyConnection>
47+
<BasePath>/importantstuff</BasePath>
48+
<VirtualHost>secure</VirtualHost>
49+
</HTTPProxyConnection>
50+
<RouteRule name="default">
51+
<TargetEndpoint>default</TargetEndpoint>
52+
</RouteRule>
53+
</ProxyEndpoint>

0 commit comments

Comments
 (0)