Skip to content
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

feat: added endpoint for product details #20

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ The image below shows the architecture of these serverless functions in connecti

## Running Locally

The serverless functions within this project are managed using [netlify-dev](https://www.netlify.com/products/dev/).
The serverless functions within this project are managed using [netlify-dev](https://www.netlify.com/products/dev/).
Start the function emulator using `yarn netlify-dev` command to handle any HTTP request made to any of the created functions on port `5050`.

Service credentials used within the serverless functions in this project are loaded as environment variables using [dotenv](https://www.npmjs.com/package/dotenv).
Expand All @@ -21,9 +21,9 @@ Before executing the `netlify dev` command, create a `.env` file in the root dir
# Access key from Stripe to access your Stripe resources
STRIPE_KEY=STRIPE_KEY

# The ID of the email price product used in billing subscribers.
# The ID of the price entity attached to a product.
EMAIL_PRODUCT_PRICE_ID=EMAIL_PRODUCT_PRICE_ID
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vickywane is this doc line still necessary? We do not hardcode product ID in the API.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, yes it is.

The EMAIL_PRODUCT_PRICE_ID that is used to retrieve only the pricing entity is different from the EMAIL_PRODUCT_ID.

The EMAIL_PRODUCT_PRICE_ID is used in the /subscription function when creating a subscription. The product ID response retrieved using the /product function does not include the ID of the pricing entity.

See subscription and price docs.

I would further rephrase the description of the EMAIL_PRODUCT_PRICE_ID to better reflect it's use


# Your Auth0 Domain
AUTHO_DOMAIN=AUTHO_DOMAIN

Expand Down Expand Up @@ -60,12 +60,12 @@ The following sensitive credentials are used within the CI jobs are stored using
## Cloud Deployment
All serverless functions within this project were designed to be deployed and executed as [Netlify Functions](https://www.netlify.com/products/functions/).
The following environment variables used within the serverless functions, and should be added in your application's [Build Environment Variables](https://docs.netlify.com/configure-builds/environment-variables/) when the project is deployed to [Netlify](https://www.netlify.com).

```
# Access key from Stripe to access your Stripe resources
STRIPE_KEY=STRIPE_KEY

# The ID of the email price product used in billing subscribers.
# The ID of the price entity attached to a product.
EMAIL_PRODUCT_PRICE_ID=EMAIL_PRODUCT_PRICE_ID

# Your Auth0 Domain
Expand Down
95 changes: 95 additions & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,18 @@ <h2 id="swagger--summary-no-tags">Summary</h2>

</td>
</tr>
ivelin marked this conversation as resolved.
Show resolved Hide resolved
<tr>
<td class="swagger--summary-path" rowspan="1">
<a href="#path--product">/product</a>
</td>
<td>
<a href="#operation--product-get">GET</a>
</td>
<td>
<p>An endpoint to retrieve details about an Ambianic premium subscription product.</p>

</td>
</tr>
<tr>
<td class="swagger--summary-path" rowspan="3">
<a href="#path--subscription">/subscription</a>
Expand Down Expand Up @@ -184,6 +196,89 @@ <h3 class="panel-title"><span class="operation-name">POST</span> <strong>/notifi
</div>
</div>

<span id="path--product"></span>
<div id="operation--product-get" class="swagger--panel-operation-get panel">
<div class="panel-heading">
<div class="operation-summary">An endpoint to retrieve details about an Ambianic premium subscription product.</div>
<h3 class="panel-title"><span class="operation-name">GET</span> <strong>/product</strong></h3>
</div>
<div class="panel-body">
<section class="sw-operation-description">
<p>Retrieve product and pricing information associated with an Ambianic product.</p>

</section>


<section class="sw-request-params">
<table class="table">
<thead>
<tr>
<th class="sw-param-name"></th>
<th class="sw-param-description"></th>
<th class="sw-param-type"></th>
<th class="sw-param-data-type"></th>
<th class="sw-param-annotation"></th>
</tr>
</thead>
<tbody>
<tr>
<td>
Access-Control-Allow-Origin
</td>
<td></td>
<td>header</td>
<td>
<span class="json-property-type">object</span>
<span class="json-property-range" title="Value limits"></span>

</td>
<td>
<span class="json-property-required"></span>
</td>
</tr>
<tr>
<td>
productId
</td>
<td><p>Unique ID of product to be retrieved</p>
</td>
<td>query</td>
<td>
<span class="json-property-type">object</span>
<span class="json-property-range" title="Value limits"></span>

</td>
<td>
</td>
</tr>
</tbody>
</table>
</section>

<section class="sw-responses">

<dl>
<dt class="sw-response-200">
200 OK

</dt>
<dd class="sw-response-200">
<div class="row">
<div class="col-md-12">

</div>
</div>
<div class="row">

<div class="col-md-6 sw-response-model">
</div>

</div> </dd>
</dl>
</section>
</div>
</div>

<span id="path--subscription"></span>
<div id="operation--subscription-delete" class="swagger--panel-operation-delete panel">
<div class="panel-heading">
Expand Down
75 changes: 36 additions & 39 deletions netlify/functions/notification.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
require("dotenv").config();
const { ManagementClient } = require("auth0");
const moment = require("moment");
const { extendMoment } = require("moment-range");
const stripe = require("stripe")(process.env.STRIPE_KEY);


const Moment = extendMoment(moment);
const nodemailer = require("nodemailer");

const headers = {
Expand All @@ -31,59 +28,59 @@ const transport = nodemailer.createTransport({
const sender = process.env.SMTP_SENDER;

exports.handler = async (event, context, callback) => {
const { userId, notification } = JSON.parse(`${event.body}`);
const { userId, notification } = JSON.parse(`${event.body}`);

try {
const { user_metadata, email, nickname } = await management.getUser({ id: userId });
try {
const { user_metadata, email, nickname } = await management.getUser({ id: userId });

const { userSubscriptionId } = user_metadata;
const {status } = await stripe.subscriptions.retrieve(userSubscriptionId);
const { userSubscriptionId } = user_metadata;
const {status } = await stripe.subscriptions.retrieve(userSubscriptionId);

if (status === 'active') {
try {
await transport.sendMail({
from: sender,
to: email,
subject: notification.title,
text: "An object has been detected from your Ambianic Device",
html:
`
if (status === 'active') {
try {
await transport.sendMail({
from: sender,
to: email,
subject: notification.title,
text: "An object has been detected from your Ambianic Device",
html:
`
<div>
<h3> Hello ${nickname}</h3> <br />
<p> ${notification.message} today at <b> ${moment(new Date()).format("hh:mm A")}</b></p>
</div>
`,
});
});

callback(null, {
statusCode: 200,
headers,
body: JSON.stringify({
message: `Notification sent to ${email}`,
}),
});
} catch (error) {
console.log(error);
callback(null, {
statusCode: 422,
headers,
body: JSON.stringify({ error }),
});
}
} else {
callback(null, {
statusCode: 200,
statusCode: 403,
headers,
body: JSON.stringify({
message: `Notification sent to ${email}`,
message: "Premium subscription inactive. Cannot send notification email. Please (re)activate premium subscription service.",
}),
});
} catch (error) {
console.log(error);
callback(null, {
statusCode: 422,
headers,
body: JSON.stringify({ error }),
});
}
} else {
} catch (error) {
callback(null, {
statusCode: 403,
headers,
body: JSON.stringify({
message: "NO ACTIVE SUBSCRIPTION TO SEND EMAIL",
}),
statusCode: 422,
body: JSON.stringify({ error: error.message }),
});
}
} catch (error) {
callback(null, {
headers,
statusCode: 422,
body: JSON.stringify({ error: error.message }),
});
}
};
46 changes: 46 additions & 0 deletions netlify/functions/product.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
require("dotenv").config();
const stripe = require("stripe")(process.env.STRIPE_KEY);

const headers = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "Content-Type",
"Content-Type": "application/json",
};

exports.handler = async ({ httpMethod, queryStringParameters }, context, callback) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vickywane I see that the implementation is now generic and works for any valid Product ID. Therefore the JS function name should match that behavior and be named getProductInfo as opposed to the more narrow getNotificationProduct naming.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright.

I have adjusted the operation-id to effect this change.

const { productId } = queryStringParameters

if (!productId) {
callback(null, {
statusCode: 422,
headers,
body: JSON.stringify({ message: "Provide productId param to retrieve product details" }),
});
}

if (httpMethod === "OPTIONS") {
// for preflight check
callback(null, {
statusCode: 200,
headers,
body: JSON.stringify({ status: "OK" }),
});
} else if (httpMethod === "GET") {

try {
const product = await stripe.products.retrieve(productId);

callback(null, {
statusCode: 200,
headers,
body: JSON.stringify({ product }),
});
} catch (error) {
callback(null, {
statusCode: 500,
headers,
body: JSON.stringify({ error }),
});
}
}
};
4 changes: 4 additions & 0 deletions openapi-docs/.openapi-generator/FILES
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ docs/InlineResponse200.md
docs/InlineResponse2001.md
docs/InlineResponse2002.md
docs/InlineResponse2003.md
docs/InlineResponse2004.md
docs/InlineResponse2004Product.md
src/ApiClient.js
src/api/DefaultApi.js
src/index.js
Expand All @@ -15,3 +17,5 @@ src/model/InlineResponse200.js
src/model/InlineResponse2001.js
src/model/InlineResponse2002.js
src/model/InlineResponse2003.js
src/model/InlineResponse2004.js
src/model/InlineResponse2004Product.js
3 changes: 3 additions & 0 deletions openapi-docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ Class | Method | HTTP request | Description
------------ | ------------- | ------------- | -------------
*AmbianicCloudApiCollection.DefaultApi* | [**createSubscription**](docs/DefaultApi.md#createSubscription) | **POST** /subscription | Subscribe a user to Ambianic&#39;s Premium Services
*AmbianicCloudApiCollection.DefaultApi* | [**deleteSubscription**](docs/DefaultApi.md#deleteSubscription) | **DELETE** /subscription | Delete an Ambianic&#39;s user subscription
*AmbianicCloudApiCollection.DefaultApi* | [**getProductInfo**](docs/DefaultApi.md#getProductInfo) | **GET** /product | An endpoint to retrieve details about an Ambianic premium subscription product.
*AmbianicCloudApiCollection.DefaultApi* | [**getSubscriptionData**](docs/DefaultApi.md#getSubscriptionData) | **GET** /subscription | Get a user&#39;s subscription data
*AmbianicCloudApiCollection.DefaultApi* | [**sendNotification**](docs/DefaultApi.md#sendNotification) | **POST** /notification | Send an event detection notification

Expand All @@ -138,6 +139,8 @@ Class | Method | HTTP request | Description
- [AmbianicCloudApiCollection.InlineResponse2001](docs/InlineResponse2001.md)
- [AmbianicCloudApiCollection.InlineResponse2002](docs/InlineResponse2002.md)
- [AmbianicCloudApiCollection.InlineResponse2003](docs/InlineResponse2003.md)
- [AmbianicCloudApiCollection.InlineResponse2004](docs/InlineResponse2004.md)
- [AmbianicCloudApiCollection.InlineResponse2004Product](docs/InlineResponse2004Product.md)


## Documentation for Authorization
Expand Down
50 changes: 50 additions & 0 deletions openapi-docs/docs/DefaultApi.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Method | HTTP request | Description
------------- | ------------- | -------------
[**createSubscription**](DefaultApi.md#createSubscription) | **POST** /subscription | Subscribe a user to Ambianic&#39;s Premium Services
[**deleteSubscription**](DefaultApi.md#deleteSubscription) | **DELETE** /subscription | Delete an Ambianic&#39;s user subscription
[**getProductInfo**](DefaultApi.md#getProductInfo) | **GET** /product | An endpoint to retrieve details about an Ambianic premium subscription product.
[**getSubscriptionData**](DefaultApi.md#getSubscriptionData) | **GET** /subscription | Get a user&#39;s subscription data
[**sendNotification**](DefaultApi.md#sendNotification) | **POST** /notification | Send an event detection notification

Expand Down Expand Up @@ -117,6 +118,55 @@ No authorization required
- **Accept**: application/json


## getProductInfo

> InlineResponse2004 getProductInfo(accessControlAllowOrigin, opts)

An endpoint to retrieve details about an Ambianic premium subscription product.

Retrieve product and pricing information associated with an Ambianic product.

### Example

```javascript
import AmbianicCloudApiCollection from 'ambianic_cloud_api_collection';

let apiInstance = new AmbianicCloudApiCollection.DefaultApi();
let accessControlAllowOrigin = *; // String |
let opts = {
'productId': "productId_example" // String | Unique ID of product to be retrieved
};
apiInstance.getProductInfo(accessControlAllowOrigin, opts, (error, data, response) => {
if (error) {
console.error(error);
} else {
console.log('API called successfully. Returned data: ' + data);
}
});
```

### Parameters


Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**accessControlAllowOrigin** | **String**| |
**productId** | **String**| Unique ID of product to be retrieved | [optional]

### Return type

[**InlineResponse2004**](InlineResponse2004.md)

### Authorization

No authorization required

### HTTP request headers

- **Content-Type**: Not defined
- **Accept**: application/json


## getSubscriptionData

> InlineResponse200 getSubscriptionData(userStripeId, userSubscriptionId, accessControlAllowOrigin, accessControlAllowHeaders, contentType)
Expand Down
Loading