diff --git a/.env.local.example b/.env.local.example new file mode 100644 index 0000000..0b213a4 --- /dev/null +++ b/.env.local.example @@ -0,0 +1,3 @@ +MONGODB_DATA_API_KEY='' +MONGODB_DATA_API_URL='' +MONGODB_DATA_SOURCE=Cluster0 \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..bffb357 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac12e94 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# local env files +.env +.env*.local + +# vercel +.vercel diff --git a/LICENSE copy b/LICENSE copy new file mode 100644 index 0000000..6cda4af --- /dev/null +++ b/LICENSE copy @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2022 MongoDB Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md index 64c0e0f..ee7e7d8 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,246 @@ -# The MongoDB Atlas Data API in the Jamstack: The Serverless Dream! +# Lesson 5 -Live Demo: [https://social-mongodb-demo.vercel.app/](https://social-mongodb-demo.vercel.app/) +<- Back to [previous lesson](https://github.com/mongodb-developer/social-app-demo/tree/4-lesson) -## Introduction +--- + +## Goal + +The goal of this lesson is to get your local application up and running. You should have basic CRUD functionality working by the end of this lesson. + +> Be sure to switch to the `5-lesson` branch in your local environment. + +## Task 1: Install dependencies + +To install the dependencies for this lesson, run the following command in the terminal from the root of the project: + +```bash +npm install +``` + +## Task 2: Add local environment variable + +In order to connect using the Atlas Data API, we must provide a `MONGODB_DATA_API_KEY` environment variable with our API key. + +You will find a [`.env.local.example`](.env.local.example) file in the root of the project. Rename this file to `.env.local`, add your API key to the `MONGODB_DATA_API_KEY` variable, and add your Data API URL Endpoint to the `MONGODB_DATA_API_URL` variable. + +If your `MONGODB_DATA_SOURCE` is not `Cluster0`, update it with your MongoDB Cluster name. + +## Serverless functions + +Next.js has a native api route for handling serverless functions. Within `pages/api/flutter` you will find an [`index.js`](./pages/api/flutter/index.js) file. This file will contain all of the basic CRUD routes for our application to connect to our Atlas Data API. + +## Task 3: Define the standard fetch variables that will be used for all requests. + +> You can reference the [Atlas Data API docs](https://www.mongodb.com/docs/atlas/api/data-api-resources) for more information. + +In the `fetchOptions` variable, you will need to define the `method` and `headers` properties. The method should be set to `POST` and the headers should include a `Content-Type`, `Access-Control-Request-Headers`, and `api-key`. + +> You can access your environment variables using the `process.env` object. + +In the `fetchBody` variable, you will need to define the `dataSource`, which is your Cluster name, the `database` name, and the `collection` name. + +In the `baseUrl` variable, we'll use our Data API URL Endpoint environment variable. + +
+Show solution + +```js +const fetchOptions = { + method: "POST", + headers: { + "Content-Type": "application/json", + "Access-Control-Request-Headers": "*", + "api-key": process.env.MONGODB_DATA_API_KEY, + }, +}; +const fetchBody = { + dataSource: process.env.MONGODB_DATA_SOURCE, + database: 'social_butterfly', + collection: 'flutters', +}; +const baseUrl = `${process.env.MONGODB_DATA_API_URL}/action`; +``` +
+ +## Task 4: Create the `find` endpoint + +Within the `GET` case of the `switch` statement, you should use `fetch` to make a request to the `find` Data API endpoint using the `baseUrl`, `fetchOptions`, and `fetchBody` variables. + +Add to the body object a `sort` field which should contain an object that sorts descending on the `postedAt` field. You will need to `stringify` the body of the request. + +> Hint: Since this is an `async` function, you can use the `await` keyword. + +After you have received the `json` from the request, return the `json` to the client along with a status code of `200`. + +> Hint: The response will contain a top level `documents` property that contains the documents returned from the request. + +### Test + +To test your application, from the terminal, run the following command: + +```bash +npm run dev +``` + +You can now navigate to `http://localhost:3000/api/flutter/` and see the response. You can also open `http://localhost:3000` to see the application with limited functionality. -Do you love the Jamstack? Do you love to create all of the boilerplate code required to connect to databases in their Jamstack applications? +
+Show solution -This workshop will show you how you can connect to MongoDB Atlas in your Jamstack application with minimal effort and without using any drivers. +```js +case "GET": + const readData = await fetch(`${baseUrl}/find`, { + ...fetchOptions, + body: JSON.stringify({ + ...fetchBody, + sort: { postedAt: -1 }, + }), + }); + const readDataJson = await readData.json(); + res.status(200).json(readDataJson.documents); + break; +``` +
-## What we'll cover in this workshop +## Task 5: Create the `insertOne` endpoint -- ~15 minutes of slides explaining Jamstack, serverless, and how the MongoDB Atlas Data API fits into these. -- Hands-on exercises resulting in you building a fully functional, deployed application. +Create within the `switch` statement a `POST` request case. -## Prerequisites +Within the `POST` case, create a `flutter` variable and set it equal to the `req.body` property. Our application will pass the new document using the body property of the request. -In order to successfully complete the tasks in this workshop, you should have: +Within the `POST` case, you should use `fetch` to make a request to the `insertOne` Data API endpoint using the `baseUrl`, `fetchOptions`, and `fetchBody` variables. You will need to `stringify` the body of the request. -- Familiarity with JavaScript -- Accounts: MongoDB Atlas, GitHub, Vercel, Auth0. (All Free) -- Node.js installed on your computer (12.2x / 14.x) -- git installed on your computer -- Code Editor (VS Code recommended) +Add to the `body` object a `document` field with its value set to the `flutter` variable. -That's it 🙌 *(no prior knowledge of MongoDB is required)* +After you have received the `json` from the request, return the `json` to the client along with a status code of `200`. -## Slides +> Hint: The response will not contain the document this time, but an indicaiton of what actions were performed on the database. -- +### Test -## Hands-on exercises +Test your application. If it is not already running, from the terminal, run the following command: -This repo is broken up into several branches. Each branch contains a set of exercises and builds upon the previous exercises. +```bash +npm run dev +``` -Throughout the workshop, you'll be working on the following exercises: -1. [Exercise 1]() -2. [Exercise 2]() -3. [Exercise 3]() -4. [Exercise 4]() -5. [Exercise 5]() -6. [Exercise 6]() -7. [Exercise 7]() -8. [Exercise 8]() -9. [Exercise 9]() -10. [Exercise 10]() +You can now navigate to `http://localhost:3000` and test creating a new flutter. + +
+Show solution + +```js +case "POST": + const flutter = req.body; + const insertData = await fetch(`${baseUrl}/insertOne`, { + ...fetchOptions, + body: JSON.stringify({ + ...fetchBody, + document: flutter, + }), + }); + const insertDataJson = await insertData.json(); + res.status(200).json(insertDataJson); + break; +``` +
+ +## Task 6: Create the `updateOne` endpoint + +Create within the `switch` statement a `PUT` request case. + +Within the `POST` case, you should use `fetch` to make a request to the `updateOne` Data API endpoint using the `baseUrl`, `fetchOptions`, and `fetchBody` variables. You will need to `stringify` the body of the request. + +Add to the `body` object a `filter` to define which document we want to update. This should filter by the `_id` field of the document using the `req.body._id`. + +> Hint: You can define an `objectId` using the MongoDB `$oid` operator. + +Also, add to the `body` object a `update` field to define the fields of the document that will be updated. Use the `$set` operator to update the flutters `body` field to `req.body.body`. + +After you have received the `json` from the request, return the `json` to the client along with a status code of `200`. + +> Hint: The response will not contain the document this time, but an indicaiton of what actions were performed on the database. + +### Test + +Test your application. If it is not already running, from the terminal, run the following command: + +```bash +npm run dev +``` + +You can now navigate to `http://localhost:3000` and test editing and updating an existing flutter. + +
+Show solution + +```js +case "PUT": + const updateData = await fetch(`${baseUrl}/updateOne`, { + ...fetchOptions, + body: JSON.stringify({ + ...fetchBody, + filter: { _id: { $oid: req.body._id } }, + update: { + $set: { + body: req.body.body, + }, + }, + }), + }); + const updateDataJson = await updateData.json(); + res.status(200).json(updateDataJson); + break; +``` +
+ +## Task 7: Create the `deleteOne` endpoint + +Create within the `switch` statement a `DELETE` request case. + +Within the `DELETE` case, you should use `fetch` to make a request to the `deleteOne` Data API endpoint using the `baseUrl`, `fetchOptions`, and `fetchBody` variables. You will need to `stringify` the body of the request. + +Add to the `body` object a `filter` to define which document we want to update. This should filter by the `_id` field of the document using the `req.body._id`. + +> Hint: You can define an `objectId` using the MongoDB `$oid` operator. + +After you have received the `json` from the request, return the `json` to the client along with a status code of `200`. + +> Hint: The response will not contain the document this time, but an indicaiton of what actions were performed on the database. + +### Test + +Test your application. If it is not already running, from the terminal, run the following command: + +```bash +npm run dev +``` + +You can now navigate to `http://localhost:3000` and test deleting an existing flutter. + +> Notice that you can delete any flutter, including ones that you did not create. + +
+Show solution + +```js +case "DELETE": + const deleteData = await fetch(`${baseUrl}/deleteOne`, { + ...fetchOptions, + body: JSON.stringify({ + ...fetchBody, + filter: { _id: { $oid: req.body._id } }, + }), + }); + const deleteDataJson = await deleteData.json(); + res.status(200).json(deleteDataJson); + break; +``` +
--- -Let's get started with the [first exercise]()! +Great job! Let's move on to the [next lesson](https://github.com/mongodb-developer/social-app-demo/tree/6-lesson) -> + +> Be sure to commit your branch changes and switch to the `6-lesson` branch in your local environment. diff --git a/components/ColorToggle/ColorToggle.js b/components/ColorToggle/ColorToggle.js new file mode 100644 index 0000000..c7cab4f --- /dev/null +++ b/components/ColorToggle/ColorToggle.js @@ -0,0 +1,24 @@ +import { useMantineColorScheme, ActionIcon, Group } from '@mantine/core'; +import { Sun, MoonStars } from 'tabler-icons-react'; + +const ColorToggle = () => { + const { colorScheme, toggleColorScheme } = useMantineColorScheme(); + + return ( + + toggleColorScheme()} + size="lg" + sx={(theme) => ({ + backgroundColor: + theme.colorScheme === 'dark' ? theme.colors.dark[6] : theme.colors.gray[0], + color: theme.colorScheme === 'dark' ? theme.colors.yellow[4] : theme.colors.blue[6], + })} + > + {colorScheme === 'dark' ? : } + + + ); +} + +export default ColorToggle; diff --git a/components/Flutters/CreateFlutter.js b/components/Flutters/CreateFlutter.js new file mode 100644 index 0000000..d599334 --- /dev/null +++ b/components/Flutters/CreateFlutter.js @@ -0,0 +1,108 @@ +import { useState } from "react"; +import { createStyles, Avatar, Group, Textarea, Button } from "@mantine/core"; +import { useForm } from "@mantine/form"; +import { showNotification } from '@mantine/notifications'; +import { Check } from 'tabler-icons-react'; + +const demoUser = { + id: "6276d0c602ce122f7b8b11ec", + name: "Jesse Hall", + nickname: "codestackr", + picture: + "https://lh3.googleusercontent.com/a-/AOh14GgPdA54bhnYcSngbZxMuSLe-khjk-BaaKWsvmxD=s96-c", +}; + +const useStyles = createStyles((theme) => ({ + flutter: { + padding: `${theme.spacing.lg}px ${theme.spacing.xl}px`, + }, + createFlutter: { + justifyContent: "center", + }, + media: { + width: "50vw", + [`@media (max-width: ${theme.breakpoints.lg}px)`]: { + width: "25vw", + }, + }, +})); + +const CreateFlutter = ({ setFlutters }) => { + const user = demoUser; + const { classes } = useStyles(); + const form = useForm({ + initialValues: { + flutter: "", + }, + }); + const [inputDisabled, setInputDisabled] = useState(false); + + const onSubmitFlutter = async (value) => { + setInputDisabled(true); + const flutter = { + postedAt: Date.now(), + body: value.flutter, + likes: [], + user: { + id: user.id, + name: user.name, + nickname: user.nickname, + picture: user.picture, + }, + }; + const response = await fetch("/api/flutter", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(flutter), + }); + + const responseJson = await response.json(); + + setFlutters((flutters) => [ + { + _id: responseJson.insertedId, + ...flutter + }, + ...flutters, + ]); + form.reset(); + setInputDisabled(false); + showSuccess(); + }; + + const showSuccess = () => { + showNotification({ + title: "Success", + message: "Your flutter has been sent", + icon: , + autoClose: 5000, + styles: (theme) => ({ + root: { + borderColor: theme.colors.green[6], + } + }), + }); + }; + + return ( + + +
onSubmitFlutter(value))}> + +