├── .env ├── .eslintrc.json ├── .gitignore ├── .vscode └── settings.json ├── Fauna.js ├── README.md ├── components ├── Code.js └── Snippets.js ├── next.config.js ├── package-lock.json ├── package.json ├── pages ├── _app.js ├── api │ ├── auth │ │ └── [...nextauth].js │ ├── createSnippet.js │ ├── deleteSnippet.js │ ├── hello.js │ ├── snippets.js │ └── updateSnippet.js ├── edit │ └── [id].js ├── index.js └── upload.js ├── public ├── favicon.ico └── vercel.svg ├── styles ├── Home.module.css ├── form.module.css └── globals.css └── yarn.lock /.env: -------------------------------------------------------------------------------- 1 | FAUNA_SECRET=fnAEUxjTOYAAQIzUpUIuOb-iso12g84J27hVcsqm 2 | GOOGLE_CLIENT_ID: id 3 | GOOGLE_CLIENT_SECRET: secret -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2 3 | } -------------------------------------------------------------------------------- /Fauna.js: -------------------------------------------------------------------------------- 1 | const faunadb = require("faunadb"); 2 | const faunaClient = new faunadb.Client({ 3 | secret: process.env.FAUNA_SECRET, 4 | }); 5 | const q = faunadb.query; 6 | const getResponse = async () => { 7 | const { data } = await faunaClient.query( 8 | q.Map( 9 | q.Paginate(q.Documents(q.Collection("codesnippet"))), 10 | q.Lambda("doc", q.Get(q.Var("doc"))) 11 | ) 12 | ); 13 | const snippets = data.map((snippet) => { 14 | snippet.id = snippet.ref.id; 15 | delete snippet.ref; 16 | return snippet; 17 | }); 18 | return snippets; 19 | }; 20 | 21 | const createSnippet = async (code, language, description, name, mail) => { 22 | return await faunaClient.query(q.Create(q.Collection('codesnippet'), { 23 | data:{code, language, description, name, mail} 24 | })) 25 | } 26 | 27 | const updateSnippet = async (id, code, language, description, name) => { 28 | return await faunaClient.query(q.Update(q.Ref(q.Collection('codesnippet'), id), { 29 | data: {code, language, name, description}, 30 | })) 31 | } 32 | 33 | const getSnippetById = async (id) => { 34 | const snippet = await faunaClient.query(q.Get(q.Ref(q.Collection('codesnippet'),id))) 35 | snippet.id = snippet.ref.id; 36 | delete snippet.ref; 37 | return snippet; 38 | } 39 | 40 | const deleteSnippet = async (id) => { 41 | return await faunaClient.query(q.Delete(q.Ref(q.Collection('codesnippet'),id))) 42 | } 43 | 44 | 45 | module.exports = { 46 | getResponse, 47 | createSnippet, 48 | updateSnippet, 49 | getSnippetById, 50 | deleteSnippet, 51 | }; 52 | 53 | // getResponse(); 54 | 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Code Snippet Application 2 | 3 | This repo contains the source code for a code snippet application built with NextJs and powered by FaunaDB for the back-end. It utilizes Google authentication to allow users create snippets, edit and delete created snippets. 4 | 5 | ## Requirements 6 | 7 | * [Node.js](http://nodejs.org/) 8 | * [FaunaDB](https://fauna.com/) 9 | 10 | ## Installation Steps 11 | 12 | 1. Clone repo 13 | 2. Run `npm install` 14 | 3. Set up a FaunaDB database with a collection "codesnippet". 15 | 4. Set up access credentials for FaunaDB and Google Cloud console in the `.env` file 16 | 5. Run `npm run server` 17 | 6. Visit http://localhost:3000 18 | 19 | ## License 20 | 21 | SitePoint's code archives and code examples are licensed under the MIT license. 22 | 23 | Copyright © 2021 SitePoint 24 | 25 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 26 | 27 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | -------------------------------------------------------------------------------- /components/Code.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { PrismLight as SyntaxHighlighter } from "react-syntax-highlighter"; 3 | import {atomDark} from "react-syntax-highlighter/dist/cjs/styles/prism" 4 | import { CopyToClipboard } from "react-copy-to-clipboard"; 5 | import styles from "../styles/Home.module.css" 6 | function Code({snippet}) { 7 | const codeString = "npm install import react from 'react'"; 8 | const [show, setshow] = React.useState(false) 9 | return ( 10 |
11 | 12 | {show ? ( 13 |
14 | 15 | 16 | 17 | 18 | {snippet.data.code} 19 | 20 |
21 | ) : null} 22 |
23 | ); 24 | } 25 | export default Code; 26 | -------------------------------------------------------------------------------- /components/Snippets.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styles from "../styles/Home.module.css"; 3 | import Code from "./Code"; 4 | import Link from "next/link"; 5 | import { useRouter } from "next/dist/client/router"; 6 | 7 | function Snippets({ snippet, snippetDeleted, email }) { 8 | const deleteSnippet = async () => { 9 | try { 10 | await fetch("/api/deleteSnippet", { 11 | method: "DELETE", 12 | body: JSON.stringify({ id: snippet.id }), 13 | headers: { 14 | "Content-Type": "application/json", 15 | }, 16 | }); 17 | snippetDeleted(); 18 | } catch (e) { 19 | console.log(e); 20 | } 21 | }; 22 | const router = useRouter(); 23 | 24 | return ( 25 |
26 |

{snippet.data.language}

27 |

{snippet.data.name}

28 |

{snippet.data.description}

29 | 30 | {email == snippet.data.mail && ( 31 | <> 32 |
33 | 34 | Edit 35 | 36 | Delete 37 |
38 | 39 | )} 40 |
41 | ); 42 | } 43 | 44 | export default Snippets; 45 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | reactStrictMode: true, 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "snippetapp", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "dotenv": "^10.0.0", 13 | "faunadb": "^4.4.1", 14 | "mdx-prism": "^0.3.4", 15 | "next": "11.1.2", 16 | "next-auth": "^3.29.0", 17 | "react": "17.0.2", 18 | "react-copy-to-clipboard": "^5.0.4", 19 | "react-dom": "17.0.2", 20 | "react-hook-form": "^7.17.4", 21 | "react-syntax-highlighter": "^15.4.4", 22 | "rehype": "^12.0.0", 23 | "remark-autolink-headings": "^7.0.1", 24 | "remark-capitalize": "^1.1.0", 25 | "remark-code-titles": "^0.1.2", 26 | "remark-slug": "^7.0.0", 27 | "shiki": "^0.9.11", 28 | "swr": "^0.3.8" 29 | }, 30 | "devDependencies": { 31 | "eslint": "7.32.0", 32 | "eslint-config-next": "11.1.2" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css' 2 | import {Provider} from 'next-auth/client' 3 | function MyApp({ Component, pageProps }) { 4 | return ( 5 | 6 | 7 | 8 | ); 9 | } 10 | 11 | export default MyApp 12 | -------------------------------------------------------------------------------- /pages/api/auth/[...nextauth].js: -------------------------------------------------------------------------------- 1 | import NextAuth from "next-auth"; 2 | import GoogleProvider from 'next-auth/providers/google' 3 | 4 | export default NextAuth({ 5 | providers: [ 6 | GoogleProvider({ 7 | clientId:process.env.GOOGLE_CLIENT_ID, 8 | clientSecret: process.env.GOOGLE_CLIENT_SECRET, 9 | authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth?prompt=consent&access_type=offline&response_type=code", 10 | }), 11 | ], 12 | jwt: { 13 | encryption: true, 14 | }, 15 | secret: process.env.secret, 16 | callbacks: { 17 | async jwt(token, account) { 18 | if (account?.accessToken) { 19 | token.accessToken = account.accessToken; 20 | } 21 | return token; 22 | }, 23 | redirect: async (url, _baseUrl) => { 24 | if (url === "/profile") { 25 | return Promise.resolve("/"); 26 | } 27 | return Promise.resolve("/"); 28 | }, 29 | }, 30 | }); 31 | -------------------------------------------------------------------------------- /pages/api/createSnippet.js: -------------------------------------------------------------------------------- 1 | import { createSnippet } from '../../Fauna' 2 | 3 | export default async function handler(req, res) { 4 | const { code, language, description, name, mail } = req.body; 5 | if (req.method !== 'POST') { 6 | return res.status(405).json({msg:'unauthorized'}); 7 | } 8 | try { 9 | const createdSnippet = await createSnippet(code, language, description, name, mail); 10 | return res.status(200).json(createdSnippet) 11 | } catch (error) { 12 | console.log(error); 13 | res.status(500).json({msg:'unauthorized'}) 14 | } 15 | } -------------------------------------------------------------------------------- /pages/api/deleteSnippet.js: -------------------------------------------------------------------------------- 1 | import { deleteSnippet } from "../../Fauna"; 2 | export default async function handler(req, res) { 3 | if (req.method !== 'DELETE') { 4 | return res.status(405).json({ msg: "unauthorized" }); 5 | } 6 | const { id } = req.body; 7 | try { 8 | const deleted = await deleteSnippet(id); 9 | return res.status(200).json(deleted); 10 | } catch (error) { 11 | console.log(error) 12 | res.status(500).join({ msg: "error occured" }); 13 | } 14 | } -------------------------------------------------------------------------------- /pages/api/hello.js: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | 3 | export default function handler(req, res) { 4 | res.status(200).json({ name: 'John Doe' }) 5 | } 6 | -------------------------------------------------------------------------------- /pages/api/snippets.js: -------------------------------------------------------------------------------- 1 | import { getResponse } from "../../Fauna.js"; 2 | export default async function handler(req, res) { 3 | console.log(req) 4 | if (req.method !== "GET") { 5 | return res.status(405); 6 | } 7 | try { 8 | const snippets = await getResponse(); 9 | return res.status(200).json(snippets); 10 | } catch (err) { 11 | console.log(err); 12 | res.status(500).json({ msg: "Something went wrong." }); 13 | } 14 | } -------------------------------------------------------------------------------- /pages/api/updateSnippet.js: -------------------------------------------------------------------------------- 1 | import { updateSnippet } from "../../Fauna.js"; 2 | 3 | export default async function handler(req, res) { 4 | const { id, code, language, description, name } = req.body; 5 | if (req.method !== "PUT") { 6 | return res.status(405).json({ msg: "unauthorized" }); 7 | } 8 | try { 9 | const updated = await updateSnippet( 10 | id, 11 | code, 12 | language, 13 | description, 14 | name 15 | ); 16 | return res.status(200).json(updated); 17 | } catch (error) { 18 | console.log(error); 19 | res.status(500).json({ msg: "unauthorized" }); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /pages/edit/[id].js: -------------------------------------------------------------------------------- 1 | import { getSnippetById } from "../../Fauna"; 2 | import Upload from "../upload"; 3 | 4 | export default function Home({ snippet }) { 5 | const email = ""; 6 | const user = ""; 7 | return ( 8 |
9 |

Update a snippet

10 | 11 |
12 | ); 13 | } 14 | 15 | export async function getServerSideProps(context) { 16 | try { 17 | const id = context.params.id; 18 | const snippet = await getSnippetById(id); 19 | return { 20 | props: { snippet }, 21 | }; 22 | } catch (error) { 23 | console.error(error); 24 | context.res.statusCode = 302; 25 | context.res.setHeader("Location", `/`); 26 | return { props: {} }; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import styles from "../styles/Home.module.css"; 3 | import Snippets from "../components/Snippets"; 4 | import useSWR from "swr"; 5 | import Link from 'next/link'; 6 | import { signIn, signOut, useSession } from "next-auth/client"; 7 | 8 | export default function Home() { 9 | const { data: snippets, mutate } = useSWR( 10 | "/api/snippets" 11 | ); 12 | const [session, loadingSession] = useSession(); 13 | if (loadingSession) { 14 | <> 15 |

...authenticating

16 | ; 17 | } 18 | 19 | return ( 20 |
21 | 22 | View Snippet 23 | 24 | 25 | 26 | {!session && ( 27 | <> 28 |

Sign in to access snippet app

29 | 30 | 31 | )} 32 | {session && ( 33 | <> 34 |
35 |

welcome {session.user.email}

36 | 37 |

Re-usuable Code Snippets

38 |

Add your code snippets here...

39 | 40 | 41 | 42 | {snippets && 43 | snippets.map((snippet) => ( 44 | 50 | ))} 51 |
52 | 53 | )} 54 |
55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /pages/upload.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useForm } from "react-hook-form"; 3 | import { useRouter } from "next/router"; 4 | import style from "../styles/form.module.css"; 5 | import { Link } from "next/link"; 6 | import { getSession } from 'next-auth/client' 7 | 8 | function Upload({ snippet, user }) { 9 | 10 | const email = user.email; 11 | const { register, handleSubmit, errors, reset } = useForm({ 12 | defaultValues: { 13 | code: snippet ? snippet.data.code : "", 14 | language: snippet ? snippet.data.language : "", 15 | description: snippet ? snippet.data.description : "", 16 | name: snippet ? snippet.data.name : "", 17 | }, 18 | }); 19 | const router = useRouter(); 20 | const createSnippet = async (data) => { 21 | const { code, language, description, name, mail } = data; 22 | // console.log(data); 23 | try { 24 | await fetch("/api/createSnippet", { 25 | method: "POST", 26 | body: JSON.stringify({ code, language, description, name, mail:email }), 27 | headers: { 28 | "Content-type": "application/json", 29 | }, 30 | }); 31 | router.push("/"); 32 | } catch (error) { 33 | console.log(error); 34 | } 35 | }; 36 | const updateSnippet = async (data) => { 37 | const { code, language, description, name } = data; 38 | const id = snippet.id; 39 | try { 40 | await fetch("/api/updateSnippet", { 41 | method: "PUT", 42 | body: JSON.stringify({ code, language, description, name, id }), 43 | headers: { 44 | "Content-Type": "application/json", 45 | }, 46 | }); 47 | router.push("/"); 48 | } catch (err) { 49 | console.error(err); 50 | } 51 | }; 52 | return ( 53 |
54 |
58 | {/*

welcome {user.email}

*/} 59 |
60 | 61 | 67 |
68 |
69 | 72 | 82 |
83 |
84 | 87 |