1640850190
Durante a programação, os desenvolvedores encontram problemas que exigem a reutilização de código, levando a uma programação repetitiva que pode ser uma perda de tempo e reduzir a produtividade. Isso dá origem à necessidade de código-fonte reutilizável, denominado “fragmentos de código”. Esses trechos evitam código repetitivo durante a programação, podem ser salvos para uso futuro e são compartilháveis.
Neste tutorial, construiremos um site para ajudar os usuários a salvar trechos de código diários usando a estrutura de desenvolvimento da Web Next.js e com base no banco de dados Fauna para lidar com o armazenamento, manipulação e exibição de trechos de código. Ao trabalhar neste projeto divertido, também aprenderemos como criar um aplicativo CRUD básico com Next.js e FaunaDB que também pode ser usado para construir outros projetos semelhantes.
Uma versão funcional deste projeto pode ser encontrada no GitHub . Para acompanhar, você precisará do Node instalado em sua máquina , bem como uma conta FaunaDB e uma conta Google (para autenticação).
Nesta seção, veremos como instalar Next.js usando o npx create-next-app
comando. Isso inicializará o Next CLI e construirá um novo aplicativo Next.js.
Também instalaremos as dependências que usaremos para o back end - FaunaDB e SWR - por meio da linha de comando. SWR (state-while-revalidate) é um gancho Next.js para buscar dados. Entraremos em detalhes mais adiante neste tutorial.
Para instalar Next.js, digite o seguinte comando na CLI:
npx create-next-app snippetapp
O comando acima cria um diretório de projeto chamado snippetapp
com o modelo inicial Next.js, que contém os arquivos necessários para codificação com Next. Quando o Next terminar de instalar, mude para o diretório recém-criado:
cd snippetapp
Para instalar o Fauna, usaremos o seguinte comando na CLI:
npm install --save faunadb
Em seguida, para instalar o SWR:
npm install swr@0.3.8
Com isso, instalamos todas as dependências que usaremos para construir nosso aplicativo e agora podemos prosseguir com a configuração de nosso banco de dados no Fauna.
FaunaDB é um banco de dados em tempo real sem servidor. Ele transforma um banco de dados tradicional em uma API de dados flexível que ainda retém os recursos de um banco de dados e seu desempenho, ao mesmo tempo que oferece acesso seguro e escalonável aos dados do aplicativo.
Aqui, criaremos uma conta de usuário e configuraremos o banco de dados para armazenar os dados que usaremos em nosso aplicativo de snippet.
Para criar uma conta de usuário, navegue até a página de inscrição do Fauna e crie uma conta.
Depois de criar uma conta de usuário, você será redirecionado para o painel.
Aqui, criaremos um banco de dados com as coleções necessárias para gerenciar os trechos de código de nosso aplicativo. Clique em CRIAR BANCO DE DADOS . Vamos criar um banco de dados chamado snippets
.
Na nova página que se abre, clique em NOVA COLEÇÃO e crie uma coleção chamada codesnippet
.
Depois de criar uma coleção, obtemos uma página onde podemos criar um documento.
Aqui, você clica em NOVO DOCUMENTO . Um documento JSON será aberto, onde você pode inserir os detalhes, conforme ilustrado abaixo.
{
name: "Prompt User",
description: "prompts the user",
language: "javascript",
code: "prompt('would you like to continue')"
}
Aqui, nós definimos um trecho com atributos: name
, description
, language
e code
. Clique em SALVAR para salvar a nova coleção. Adicionamos com sucesso um snippet ao nosso banco de dados. Agora podemos prosseguir para obter nossas credenciais de acesso para usar em nosso aplicativo.
No painel, clique em Segurança . Isso abre uma nova página para criar nossa chave de segurança.
Aqui, definiremos a função como “servidor” em vez de “admin” e você poderá dar um nome à chave. Clique no botão SALVAR para gerar sua chave.
.env
arquivoAgora vamos criar um .env
arquivo dentro do diretório do nosso projeto. Este arquivo armazenará nossa chave secreta gerada. No .env
arquivo, temos o seguinte:
FAUNA_SECRET = paste your key here
Nesta seção, construiremos a página de exibição e upload para os snippets e também adicionaremos funcionalidade a ela.
Abra o diretório do projeto em seu editor de código e navegue até o index.js
arquivo na pasta de páginas. Aqui, vamos limpar o código e começar a construir nosso aplicativo:
import Head from "next/head"
import Image from "next/image"
import styles from "../styles/Home.module.css"
export default function Home() {
return (
<div className={styles.container}>
<Head>
<title>View Snippet</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<h1 className={styles.title}>Re-usuable Code Snippets</h1>
<p className={styles.info}>Add your code snippets here...</p>
<button>Create new snippet</button>
</main>
</div>
)
}
Agora vamos criar um arquivo de componente que renderizará nossos snippets. Crie uma pasta nomeada component
em seu diretório de trabalho e crie um arquivo nomeado Snippets.js
dentro dela com o seguinte código:
import React from "react"
import styles from "../styles/Home.module.css"
function Snippets() {
return (
<div className={styles.cont}>
<p className={styles.lang}>language</p>
<h3 className={styles.name}>name of snippet</h3>
<p className={styles.descp}>description of snippet</p>
{/* Code will be displayed here*/}
<div className={styles.links}>
<a>Edit</a>
<a>Delete</a>
</div>
</div>
)
}
export default Snippets
Agora adicionaremos importações para este arquivo em nosso index.js
:
import Snippets from "../components/Snippets"
E use-o em nosso aplicativo:
<button>Create new snippet</button>
<Snippets/>
Agora podemos estilizar nossa página. Navegue até o Home.module.css
arquivo na styles
pasta e substitua os estilos lá pelo seguinte:
.container{
display: flex;
height: 100%;
min-height: 100vh;
background: rgb(48, 48, 255);
flex-direction: column;
align-items: center;
color: #fff;
font-family: Montserrat;
}
.cont{
color: #333;
margin-top: 5px;
background: rgb(214, 214, 214);
border-radius: 15px;
padding: 10px 15px;
}
.main button{
width: fit-content;
flex-grow: unset;
display: inline-block;
padding: 5px 10px;
outline: none;
border: none;
border-radius: 5%;
font-weight: bold;
color: rgb(48, 48, 255);
}
.main button:hover{
cursor: pointer;
}
.links{
margin-top: 10px;
}
.links a{
margin-left: 5px;
}
.links a:hover{
cursor: pointer;
}
Nesse ponto, você deve conseguir iniciar o servidor de desenvolvimento com npm run dev
, visite http: // localhost: 3000 e veja o esqueleto de nosso aplicativo.
A seguir, criaremos a seção de exibição para o código do snippet. Crie um novo arquivo chamado Code.js
na pasta de componentes e importe-o para Snippets.js
:
import React from 'react'
import styles from '../styles/Home.module.css'
import Code from "./Code";
function Snippets() {
return (
<div className={styles.cont}>
<p className={styles.lang}>language</p>
<h3 className={styles.name}>name of snippet</h3>
<p className={styles.descp}>description of snippet</p>
{/* Code will be displayed here*/}
<Code />
<div className={styles.links}>
<a>Edit</a>
<a>Delete</a>
</div>
</div>
)
}
export default Snippets
Para o realce da sintaxe do código, usaremos dois pacotes, a saber, react-syntax-highlighter e react-copy-to-clipboard . Podemos fazer o download por meio da CLI:
npm install react-syntax-highlighter react-copy-to-clipboard --save
Então em Code.js
:
import React from "react"
import { PrismLight as SyntaxHighlighter } from "react-syntax-highlighter"
import {atomDark} from "react-syntax-highlighter/dist/cjs/styles/prism"
import { CopyToClipboard } from "react-copy-to-clipboard"
import styles from "../styles/Home.module.css"
function Code() {
const codeString = "npm install import react from 'react'"
const [show, setshow] = React.useState(false)
return (
<div>
<button onClick={() => setshow(!show)}>Show Code</button>
{show ? (
<div>
<CopyToClipboard text={codeString}>
<button className={styles.btn}>Copy</button>
</CopyToClipboard>
<SyntaxHighlighter language="javascript" style={atomDark}>
{codeString}
</SyntaxHighlighter>
</div>
) : null}
</div>
)
}
export default Code
Aqui, criamos um componente para exibir o código com destaque de sintaxe. Também adicionamos a funcionalidade de cópia e alternância de exibição. Agora no styles
arquivo:
.btn{
left: 80%;
position: relative;
}
Para visualizar essa mudança, você pode executar npm run dev
na linha de comando e visualizá-la em seu navegador. Temos a string “npm install import react from 'react'” exibida com destaque de sintaxe como um bloco de código. Há também um botão para ocultar e exibir o trecho de código e um botão que nos permite copiar o código do bloco de código.
Nesta seção, buscaremos dados de nosso banco de dados FaunaDB para nosso aplicativo. Crie um arquivo chamado Fauna.js
no diretório do seu projeto:
const faunadb = require("faunadb")
const faunaClient = new faunadb.Client({
secret: process.env.FAUNA_SECRET
})
const q = faunadb.query
const getResponse = async () => {
const { data } = await faunaClient.query(
q.Map(
q.Paginate(q.Documents(q.Collection("codesnippet"))),
q.Lambda("doc", q.Get(q.Var("doc")))
)
)
const snippets = data.map((snippet) => {
snippet.id = snippet.ref.id
delete snippet.ref
return snippet
})
return snippets
}
module.exports = {
getResponse,
}
Aqui, inicializamos o FaunaDB com nossa chave secreta. Também configuramos uma async
solicitação para consultar nossa coleção e retornar os dados. Armazenamos os dados retornados em uma variável chamada snippets
e excluímos o ref para melhor estruturar os dados. Outras funcionalidades para criar, atualizar e excluir snippets serão adicionadas posteriormente neste tutorial.
Observe que, se você estiver recebendo um erro não autorizado no console, pode ser necessário especificar o nome de domínio do terminal de destino. O padrão é db.fauna.com
, mas desde a introdução de Grupos de regiões , três domínios de nuvem estão disponíveis. Use o domínio correto para o Grupo de Região do seu banco de dados:
db.fauna.com
db.us.fauna.com
db.eu.fauna.com
Código de exemplo:
const faunaClient = new faunadb.Client({
secret: process.env.FAUNA_SECRET,
domain: "db.eu.fauna.com"
})
Também criaremos um arquivo para lidar com nossa solicitação de API para nosso banco de dados. Dentro da api
pasta em pages
, crie um arquivo chamado snippets.js
com o seguinte código:
import { getResponse } from "../../Fauna.js"
export default async function handler(req, res) {
console.log(req)
if (req.method !== "GET") {
return res.status(405)
}
try {
const snippets = await getResponse()
return res.status(200).json(snippets)
} catch (err) {
console.log(err)
res.status(500).json({ msg: "Something went wrong." })
}
}
Acima, simplesmente configuramos uma função para lidar com as solicitações de nosso banco de dados. Os snippets são retornados como Json
e registrarão os erros, se ocorrerem. Em Next.js, qualquer arquivo armazenado na api
pasta é tratado como endpoints de API em vez de uma página e é renderizado no lado do servidor.
Como dito anteriormente, SWR (state-while-revalidate) é um gancho Next.js para buscar dados. É uma solução perfeita para buscar dados atualizados com frequência e é uma boa opção para nosso aplicativo.
Usaremos isso para buscar dados do FaunaDB. Para usar isso, precisamos importá-lo para index.js
:
import useSWR from "swr"
export default function Home() {
const { data:snippets, mutate }=useSWR("api/snippets")
...
})
Aqui, importamos o SWR e o usamos para buscar dados conforme configurado em snippets.js
. Em seguida, armazenamos esses snippets na snippets
variável e os enviaremos a partir daí. Agora, passaremos o snippets
para nosso Snippets
componente para exibir:
- <Snippets />
+ {snippets &&
+ snippets.map((snippet) => (
+ <Snippets
+ key={snippet.id}
+ snippet={snippet}
+ snippetDeleted={mutate}
+ />
+ ))
+ }
Acima, passamos a chave e o snippet para Snippets
. Também configuramos uma mutate
propriedade para atualizar (buscar novamente) snippets quando um snippet é excluído. Para usar os dados passados, modificamos o Snippets
componente com o seguinte:
function Snippets({snippet}) {
return (
<div className={styles.cont}>
<p className={styles.lang}>{snippet.data.language}</p>
<h3 className={styles.name}>{snippet.data.name}</h3>
<p className={styles.descp}>{snippet.data.description}</p>
<Code snippet={snippet}/>
<div className={styles.links}>
<a>Edit</a>
<a>Delete</a>
</div>
</div>
)
}
Acima, inserimos a linguagem, o nome e a descrição do snippet recebido do FaunaDB em nosso código. Para obter o código do Fauna em nosso aplicativo, também tivemos que passar o snippet prop para o Code
componente.
Em seguida, no Code
componente:
function Code({snippet}){
...
<div>
<CopyToClipboard text={snippet.data.code}>
<button className={styles.btn}>Copy</button>
</CopyToClipboard>
<SyntaxHighlighter language="javascript" style={atomDark}>
{snippet.data.code}
</SyntaxHighlighter>
</div>
...
}
Agora terminamos com a GetSnippet
funcionalidade. Se voltarmos ao FaunaDB e criarmos um novo snippet, veremos o que está na imagem abaixo.
{
"name": "console.log()",
"language": "javascript",
"description": "logs out data",
"code": "console.log('Hello, world!')"'
}
Para rodar na CLI:
npm run dev
Se você abrir a página em seu navegador, terá um resultado semelhante à imagem abaixo.
Nós criamos com sucesso uma página de exibição de snippet com funcionalidades para mostrar e ocultar o código e copiar o snippet de código.
Precisamos criar um link para a página de upload de nosso componente inicial. Next.js tem disposições que tornam o roteamento mais fácil sem a necessidade de instalação react-router
e outras dependências, como você faria se estivesse usando o código React nativo.
Em index.js
, vamos importar o Link
módulo de next
:
import Link from "next/link"
Em seguida, adicione-o ao nosso botão Criar novo snippet :
- <button>Create new snippet</button>
+ <Link href="/upload">
+ <button>Create new snippet</button>
+ </Link>
Vamos criar uma nova página em nossa pages
pasta e nomeá-la upload.js
.
De volta ao nosso Fauna.js
arquivo, criaremos e também exportaremos uma função para criar snippets em nosso aplicativo:
const createSnippet = async (code, language, description, name) => {
return await faunaClient.query(q.Create(q.Collection("codesnippet"), {
data:{code, language, description, name}
}))
}
module.exports = {
getResponse,
createSnippet,
}
Aqui, criamos a função createSnippet
, que pegará alguns parâmetros e os passará como dados no novo documento que será criado no banco de dados.
Também configuraremos nosso endpoint para criar snippets. Crie um novo arquivo chamado createSnippet.js
na api
pasta e preencha-o com o seguinte código:
import { createSnippet } from "../../Fauna"
export default async function handler(req, res) {
const { code, language, description, name } = req.body
if (req.method !== "POST") {
return res.status(405).json({msg:"unauthorized"})
}
try {
const createdSnippet = await createSnippet(code, language, description, name)
return res.status(200).json(createdSnippet)
} catch (error) {
console.log(error)
res.status(500).json({msg:"unauthorized"})
}
}
Agora vamos criar a página de upload em nosso upload.js
arquivo. Para o nosso formulário criar fragmentos, usaremos o formulário react-hook . Vamos instalar isso por meio da CLI:
npm install react-hook-form
Então, em nosso upload.js
arquivo:
import React from "react"
import { useForm } from "react-hook-form"
import { useRouter } from "next/router"
import style from "../styles/form.module.css"
import { Link } from "next/link"
function upload({ snippet }) {
const { register, handleSubmit, errors, reset } = useForm()
const router = useRouter()
const createSnippet = async (data) => {
const { code, language, description, name } = data
console.log(data)
try {
// code here to push to Fauna
} catch (error) {
console.log(error)
}
}
return (
<div className={style.cont}>
<form
className={style.form}
onSubmit={handleSubmit(snippet ? updateSnippet : createSnippet)}
>
<div>
<label htmlFor="name">Name</label>
<input
className={style.input}
type="text"
id="name"
{...register("name", { required: true })}
/>
</div>
<div>
<label className={style.label} htmlFor="language">
language
</label>
<select
className={style.select}
type="text"
id="language"
{...register("language", { required: true })}
>
<option>Javascript</option>
<option>Html</option>
<option>CSS</option>
</select>
</div>
<div>
<label className={style.label} htmlFor="description">
description
</label>
<textarea
className={style.input}
rows={7}
type="text"
id="description"
placeholder="snippet description"
{...register("description", { required: true })}
/>
</div>
<div>
<label className={style.label} htmlFor="code">
Code
</label>
<textarea
className={style.input}
rows={8}
columns={8}
type="text"
id="code"
{...register("code", { required: true })}
placeholder="background: none;"
/>
</div>
<div>
<button className={style.button}>Submit</button>
<button className={style.button}>Cancel</button>
</div>
</form>
</div>
)
}
export default upload
Acima, criamos nosso formulário usando o react-hook-form
pacote. Usamos a handleSubmit
função useForm()
no operador ternário. Após o envio do formulário, ele determina se o envio feito é para criar ou atualizar um snippet existente. Com register
, adicionamos a propriedade necessária a todos os campos de nosso formulário. Também adicionamos importações para uma folha de estilo chamada, form.module.css
onde temos os seguintes estilos para nosso formulário:
.form {
max-width: 800px;
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
}
.cont{
background: rgb(48, 48, 255);
height: 100%;
min-height: 100vh;
padding: 10px 0 0 0;
display: flex;
justify-content: center;
align-items: center;
}
.select,
.input {
display: block;
box-sizing: border-box;
width: 100%;
border-radius: 4px;
border: 1px solid black;
padding: 10px 15px;
margin-bottom: 15px;
font-size: 14px;
}
.label{
line-height: 2;
text-align: left;
display: block;
margin: 5px;
color: white;
font-size: 14px;
font-weight: 200;
}
.button {
background : #fff;
color: #444;
border: none;
border-radius: 5%;
margin-right: 8px;
}
Para enviar os dados do nosso formulário para o banco de dados FaunaDB, adicione o seguinte código ao try...catch
bloco na createSnippet
função em upload.js
:
try {
await fetch("/api/createSnippet", {
method: "POST",
body: JSON.stringify({ code, language, description, name }),
headers: {
"Content-type": "application/json"
},
})
router.push("/")
} catch (error) {
console.log(error)
}
Execute o código e navegue até a página de upload. Agora, se adicionarmos um novo snippet ao formulário e clicarmos em Enviar , veremos o que está ilustrado abaixo.
Quando navegamos para nosso componente inicial, podemos ver o trecho criado.
Para criar nossa funcionalidade de edição de snippet, de volta ao Fauna.js
arquivo, criaremos e exportaremos uma função para lidar com esta tarefa:
const updateSnippet = async (id, code, language, description, name) => {
return await faunaClient.query(q.Update(q.Ref(q.Collection("codesnippet"), id), {
data: {code, language, name, description},
}))
}
module.exports = {
...
updateSnippet,
}
Esta função é semelhante à createSnippet
função, mas também recebe um parâmetro de id
. Ele usa esse ID para identificar quais trechos devem ser editados. Se o id
corresponder, atualizamos os dados com os outros parâmetros. Também criaremos um arquivo de endpoint no api
diretório chamado updateSnippet.js
para lidar com as atualizações:
import { updateSnippet } from "../../Fauna"
export default async function handler(req, res) {
const { id, code, language, description, name } = req.body
if (req.method !== "PUT") {
return res.status(405).json({ msg: "unauthorized" })
}
try {
const updated = await updateSnippet(
id,
code,
language,
description,
name
)
return res.status(200).json(updated)
}
catch (error) {
console.log(error)
res.status(500).json({ msg: "unauthorized" })
}
}
Agora, vá para o Snippets
componente e modifique-o para usar esta função. Primeiro, vamos importar o Link
módulo:
...
import Link from "next/link"
Também modificamos nosso edit
botão:
- <a>Edit</a>
+ <Link href={`/edit/${snippet.id}`}>
+ <a>Edit</a>
+ </Link>
Quando clicado, ele envia uma solicitação para a página edit
com o id
do snippet selecionado. Na pages
pasta, crie uma pasta chamada edit
com um arquivo [id].js
dentro dela:
import { getSnippetById } from "../../Fauna"
import Upload from "../upload"
export default function Home({ snippet }) {
const email = ""
const user = ""
return (
<div>
<h3>Update a snippet</h3>
<Upload snippet={snippet} email={email} user={user}/>
</div>
)
}
export async function getServerSideProps(context) {
try {
//get and update record
const id = context.params.id
}
catch (error) {
console.log(error)
context.res.statusCode = 302
context.res.setHeader("Location", "/")
return {props: {}}
}
}
Em [id].js
, estamos passando o snippet de código props
para a página de upload de snippet. No entanto, desta vez, a página de upload conterá os dados armazenados no snippet de código referenciado pelo id
. Para buscar o snippet por ID, precisaremos criar a getSnippetById
função no Fauna.js
arquivo:
const getSnippetById = async (id) => {
const snippet = await faunaClient.query(q.Get(q.Ref(q.Collection("codesnippet"),id)))
snippet.id = snippet.ref.id
delete snippet.ref
return snippet
}
module.exports = {
getResponse,
createSnippet,
updateSnippet,
getSnippetById,
}
À medida que exportamos a função, de volta ao [id].js
arquivo, podemos usá-la para buscar um snippet específico com seu ID:
try {
const id = context.params.id;
const snippet = await getSnippetById(id);
return {
props: { snippet },
};
} catch (error) {
// as before
}
Agora, no upload.js
arquivo, vamos modificá-lo para poder acessar os dados armazenados se um snippet for editado:
- const { register, handleSubmit, errors, reset } = useForm()
+ const { register, handleSubmit, errors, reset } = useForm({
+ defaultValues: {
+ code: snippet ? snippet.data.code : "",
+ language: snippet ? snippet.data.language : "",
+ description: snippet ? snippet.data.description : "",
+ name: snippet ? snippet.data.name : "",
+ }
+ })
O código acima verifica se o snippet armazenou dados nele. Se ele retorna true, ele retorna os dados para os parâmetros: code
, language
, description
e code
. Se retornar false
, ele retornará uma string vazia.
A seguir, criaremos uma função para atualizar o snippet de código:
const createSnippet = async (data) => { ... }
const updateSnippet = async (data) => {
const { code, language, description, name } = data
const id = snippet.id
try {
await fetch("/api/updateSnippet", {
method: "PUT",
body: JSON.stringify({ code, language, description, name, id }),
headers: {
"Content-Type": "application/json",
},
})
router.push("/")
}
catch (error) {
console.log(error)
}
}
return ( ,,, )
Se executarmos nosso código, podemos editar os trechos de código criados anteriormente clicando no botão Editar , fazendo alterações nos dados do formulário e clicando em Enviar .
Agora, se retornarmos ao Home
componente em nosso navegador, poderemos editar e atualizar trechos de código. Podemos finalmente adicionar a funcionalidade final para excluir nosso trecho de código. Crie e exporte uma nova função - deleteSnippet
- no Fauna.js
arquivo:
const deleteSnippet = async (id) => {
return await faunaClient.query(q.Delete(q.Ref(q.Collection("codesnippet"),id)))
}
module.exports = {
...
deleteSnippet,
}
Criaremos outro endpoint para esta função em nossa api
pasta chamada deleteSnippet.js
e o preencheremos com o seguinte código:
import { deleteSnippet } from "../../Fauna"
export default async function handler(req, res) {
if (req.method !== "DELETE") {
return res.status(405).json({ msg: "unauthorized" })
}
const { id } = req.body
try {
const deleted = await deleteSnippet(id)
return res.status(200).json(deleted)
}
catch (error) {
console.log(error)
res.status(500).join({ msg: "error occured" })
}
}
Em seguida, modificamos o Snippets.js
arquivo para adicionar a nova funcionalidade:
function Snippets({ snippet, snippetDeleted }) {
...
}
Em seguida, crie uma deleteSnippet
função para buscar o endpoint do api
e exclua o snippet referenciado pelo ID:
function Snippets({snippet, snippetDeleted}) {
const deleteSnippet = async () => {
try {
await fetch("/api/deleteSnippet", {
method: "DELETE",
body: JSON.stringify({ id: snippet.id }),
headers: {
"Content-Type": "application/json",
},
});
snippetDeleted();
} catch (e) {
console.log(e);
}
};
return (
<div className={styles.cont}>
<p className={styles.lang}>{snippet.data.language}</p>
<h3 className={styles.name}>{snippet.data.name}</h3>
<p className={styles.descp}>{snippet.data.description}</p>
<Code snippet={snippet}/>
<div className={styles.links}>
<Link href={`/edit/${snippet.id}`}>
<a>Edit</a>
</Link>
<a onClick={deleteSnippet}>Delete</a>
</div>
</div>
)
}
Também atualizamos o elemento âncora para chamar a deleteSnippet
função quando ela é clicada.
Adicionamos funcionalidade para excluir trechos de código. Agora podemos excluir trechos clicando no botão Excluir em nosso aplicativo.
Isso conclui as funcionalidades do aplicativo snippet. Agora prosseguiremos para adicionar medidas de autenticação ao nosso aplicativo para permitir que apenas usuários autorizados criem ou modifiquem snippets em nosso aplicativo.
Por que precisamos de autenticação? Atualmente, os usuários podem criar fragmentos, mas também podem excluir e modificar fragmentos que não criaram. Precisamos fornecer um meio para autorizar os usuários a acessar nosso site - e, portanto, a necessidade de autenticação do usuário.
Instalaremos next-auth para autenticação por meio de nossa CLI:
npm i next-auth
Estaremos usando um token JWT para nossa autenticação. JWT é um padrão usado para criar tokens de acesso para um aplicativo.
Crie uma pasta com o nome auth
em sua api
pasta e, dentro dela, crie um arquivo [...nextauth].js
com o seguinte código:
import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google"
export default NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth?prompt=consent&access_type=offline&response_type=code",
})
],
jwt: {
encryption: true
},
secret: process.env.secret,
callbacks: {
async jwt(token, account) {
if (account ?.accessToken) {
token.accessToken = account.accessToken
}
return token
},
redirect: async (url, _baseUrl)=>{
if (url === "/profile") {
return Promise.resolve("/")
}
return Promise.resolve("/")
}
}
})
Depois disso, agruparemos nossos componentes no _app.js
arquivo:
import '../styles/globals.css'
import {Provider} from "next-auth/client"
function MyApp({ Component, pageProps }) {
return (
<Provider session={pageProps.session}>
<Component {...pageProps} />
</Provider>
)
}
export default MyApp
Modificaremos nosso Home
componente index.js
para retornar ao nosso componente se o usuário for autenticado; caso contrário, ele retornará um botão que leva à página de autenticação:
import {signIn, signOut, useSession} from "next-auth/client"
...
Então dentro Home
:
export default function Home() {
const { data:snippets, mutate }=useSWR("api/snippets")
const [session, loadingSession] = useSession()
if (loadingSession) {
<>
<p>...authenticating</p>
</>
}
...
}
O código acima verifica se o aplicativo é loadingSession
. Se verdadeiro, ele retorna o p
bloco de tags, caso contrário, retorna o resto do nosso aplicativo, se houver session
. A seguir, vamos renderizar o “login” se não houver sessão:
return (
<div className={styles.container}>
<Head> ... </Head>
{!session && (
<>
<h1>Sign in to access snippet app</h1>
<button onClick={() => signIn()}>Sign In</button>
</>
)}
{session && (
<>
<main className={styles.main}>
<h3>welcome {session.user.email}</h3>
<button onClick={() => signOut()}>Sign Out</button>
...
</main>
</>
)}
</div>
)
Para fazer uso do “serviço de login do Google”, precisamos de credenciais de acesso do console da nuvem do Google. Para obter isso, faça login em sua conta do Google e navegue até o console do Google Cloud . Clique em CRIAR PROJETO na página, digite o nome do seu projeto e clique em Criar .
Na nova página que se abre, clique em + CRIAR CREDENCIAIS na barra de menu superior e, por fim, selecione o ID do cliente OAuth no menu suspenso.
Na página que se abre, você receberá uma notificação com um botão pedindo para você “Configurar tela de consentimento”. Clique neste botão.
Na próxima página, selecione Externo sob o tipo de usuário e clique em Criar . Insira os campos obrigatórios para o seu “Nome do aplicativo” e “Email” e clique em Salvar e continuar .
Nas seções Escopos e usuários de teste , role para baixo e clique em Salvar e continuar .
Por fim, clique em Voltar ao painel e clique no botão Publicar .
Agora, podemos criar nossa chave clicando em Credenciais no menu lateral e, em seguida, em Criar Credenciais na barra de menu superior. Selecione Oauth Client ID na lista suspensa e você verá uma página solicitando o tipo de aplicativo.
Selecione Aplicativo da Web e, em “Origens JavaScript autorizadas”, clique em Adicionar URI e digite http://localhost
. Por fim, em “URIs de redirecionamento autorizados”, clique em Adicionar URI e entre http://localhost/api/auth/callback/google
no campo, antes de clicar em Criar .
Copie o ID do cliente e a chave secreta do cliente do pop-up que é aberto e adicione-os ao .env
arquivo:
GOOGLE_CLIENT_ID=id
GOOGLE_CLIENT_SECRET=secret
Agora podemos fazer login usando a autenticação do Google em nosso aplicativo. Além disso, configuraremos nosso upload.js
arquivo como uma rota protegida para que usuários não autorizados não possam criar novos snippets:
import { getSession } from "next-auth/client"
function Upload({ snippet, user }) { ... }
export async function getServerSideProps(context) {
const session = await getSession(context)
if (!session) {
context.res.writeHead(302, { Location: "/" })
context.res.end()
return {}
}
return {
props: {
user: session.user,
}
}
}
export default Upload;
Se executarmos nosso aplicativo com o npm run dev
comando, a princípio obteremos uma página solicitando que “entremos”. Não podemos navegar para a página de upload por meio do /upload
caminho em nosso URL. Só podemos acessar nosso aplicativo quando usamos o recurso de login do Google para fazer login em nosso aplicativo.
Por fim, modificaremos a createSnippet
funcionalidade para adicionar o e-mail do usuário ao banco de dados e só mostraremos os botões Editar e Excluir se o e-mail corresponder.
Em Fauna.js
, altere a createSnippet
função da seguinte maneira:
const createSnippet = async (code, language, description, name, mail) => {
return await faunaClient.query(q.Create(q.Collection("codesnippet"), {
data:{code, language, description, name, mail}
}))
}
No createSnippet.js
arquivo, faça as seguintes alterações:
- const { code, language, description, name } = req.body;
+ const { code, language, description, name, mail } = req.body;
- const createdSnippet = await createSnippet(code, language, description, name);
+ const createdSnippet = await createSnippet(code, language, description, name, mail);
Em upload.js
:
function upload({ snippet, user }) {
+ const email = user.email;
...
}
E altere a createSnippet
função e a updateSnippet
função, da seguinte maneira:
const createSnippet = async (data) => {
const { code, language, description, name, mail } = data;
console.log(data)
try {
await fetch("/api/createSnippet", {
method: "POST",
body: JSON.stringify({ code, language, description, name, mail:email }),
headers: {
"Content-type": "application/json"
},
})
router.push("/")
} catch (error) {
console.log(error)
}
}
const updateSnippet = async (data) => {
const { code, language, description, name } = data
const id = snippet.id
try {
await fetch("/api/updateSnippet", {
method: "PUT",
body: JSON.stringify({ code, language, description, name, mail:email }),
headers: {
"Content-Type": "application/json",
},
})
router.push("/")
}
catch (error) {
console.log(error)
}
}
Agora podemos continuar fazendo com que os botões Editar e Excluir sejam exibidos apenas se o e-mail corresponder.
Primeiro, passamos os user.mail
como adereços para o Snippet
componente em index.js
:
<Snippets
key={snippet.id}
snippet={snippet}
snippetDeleted={mutate}
+ email={session.user.email}
/>
Então em Snippet.js
:
function Snippets({ snippet, snippetDeleted, email }) {
...
{email == snippet.data.mail && (
<>
<div className={styles.links}>
<Link href={`/edit/${snippet.id}`}>
<a>Edit</a>
</Link>
<a onClick={deleteSnippet}>Delete</a>
</div>
</>
)}
...
}
Execute npm run dev
na CLI e abra o aplicativo em seu navegador. Agora, se você criar um novo snippet, o e-mail do usuário será adicionado ao banco de dados. Se o e-mail não corresponder, os botões Editar e Excluir não serão exibidos na página de exibição do snippet. Você pode testar isso fazendo login com um endereço de e-mail diferente do usado para criar os trechos de código.
Finalmente chegamos ao final deste tutorial. Aprendemos como construir um aplicativo CRUD com Next.js e FaunaDB e como realizar operações CRUD com base na autenticação do usuário.
fonte: https://www.sitepoint.com/nextjs-faunadb-build-code-snippet-app/
1632537859
Not babashka. Node.js babashka!?
Ad-hoc CLJS scripting on Node.js.
Experimental. Please report issues here.
Nbb's main goal is to make it easy to get started with ad hoc CLJS scripting on Node.js.
Additional goals and features are:
Nbb requires Node.js v12 or newer.
CLJS code is evaluated through SCI, the same interpreter that powers babashka. Because SCI works with advanced compilation, the bundle size, especially when combined with other dependencies, is smaller than what you get with self-hosted CLJS. That makes startup faster. The trade-off is that execution is less performant and that only a subset of CLJS is available (e.g. no deftype, yet).
Install nbb
from NPM:
$ npm install nbb -g
Omit -g
for a local install.
Try out an expression:
$ nbb -e '(+ 1 2 3)'
6
And then install some other NPM libraries to use in the script. E.g.:
$ npm install csv-parse shelljs zx
Create a script which uses the NPM libraries:
(ns script
(:require ["csv-parse/lib/sync$default" :as csv-parse]
["fs" :as fs]
["path" :as path]
["shelljs$default" :as sh]
["term-size$default" :as term-size]
["zx$default" :as zx]
["zx$fs" :as zxfs]
[nbb.core :refer [*file*]]))
(prn (path/resolve "."))
(prn (term-size))
(println (count (str (fs/readFileSync *file*))))
(prn (sh/ls "."))
(prn (csv-parse "foo,bar"))
(prn (zxfs/existsSync *file*))
(zx/$ #js ["ls"])
Call the script:
$ nbb script.cljs
"/private/tmp/test-script"
#js {:columns 216, :rows 47}
510
#js ["node_modules" "package-lock.json" "package.json" "script.cljs"]
#js [#js ["foo" "bar"]]
true
$ ls
node_modules
package-lock.json
package.json
script.cljs
Nbb has first class support for macros: you can define them right inside your .cljs
file, like you are used to from JVM Clojure. Consider the plet
macro to make working with promises more palatable:
(defmacro plet
[bindings & body]
(let [binding-pairs (reverse (partition 2 bindings))
body (cons 'do body)]
(reduce (fn [body [sym expr]]
(let [expr (list '.resolve 'js/Promise expr)]
(list '.then expr (list 'clojure.core/fn (vector sym)
body))))
body
binding-pairs)))
Using this macro we can look async code more like sync code. Consider this puppeteer example:
(-> (.launch puppeteer)
(.then (fn [browser]
(-> (.newPage browser)
(.then (fn [page]
(-> (.goto page "https://clojure.org")
(.then #(.screenshot page #js{:path "screenshot.png"}))
(.catch #(js/console.log %))
(.then #(.close browser)))))))))
Using plet
this becomes:
(plet [browser (.launch puppeteer)
page (.newPage browser)
_ (.goto page "https://clojure.org")
_ (-> (.screenshot page #js{:path "screenshot.png"})
(.catch #(js/console.log %)))]
(.close browser))
See the puppeteer example for the full code.
Since v0.0.36, nbb includes promesa which is a library to deal with promises. The above plet
macro is similar to promesa.core/let
.
$ time nbb -e '(+ 1 2 3)'
6
nbb -e '(+ 1 2 3)' 0.17s user 0.02s system 109% cpu 0.168 total
The baseline startup time for a script is about 170ms seconds on my laptop. When invoked via npx
this adds another 300ms or so, so for faster startup, either use a globally installed nbb
or use $(npm bin)/nbb script.cljs
to bypass npx
.
Nbb does not depend on any NPM dependencies. All NPM libraries loaded by a script are resolved relative to that script. When using the Reagent module, React is resolved in the same way as any other NPM library.
To load .cljs
files from local paths or dependencies, you can use the --classpath
argument. The current dir is added to the classpath automatically. So if there is a file foo/bar.cljs
relative to your current dir, then you can load it via (:require [foo.bar :as fb])
. Note that nbb
uses the same naming conventions for namespaces and directories as other Clojure tools: foo-bar
in the namespace name becomes foo_bar
in the directory name.
To load dependencies from the Clojure ecosystem, you can use the Clojure CLI or babashka to download them and produce a classpath:
$ classpath="$(clojure -A:nbb -Spath -Sdeps '{:aliases {:nbb {:replace-deps {com.github.seancorfield/honeysql {:git/tag "v2.0.0-rc5" :git/sha "01c3a55"}}}}}')"
and then feed it to the --classpath
argument:
$ nbb --classpath "$classpath" -e "(require '[honey.sql :as sql]) (sql/format {:select :foo :from :bar :where [:= :baz 2]})"
["SELECT foo FROM bar WHERE baz = ?" 2]
Currently nbb
only reads from directories, not jar files, so you are encouraged to use git libs. Support for .jar
files will be added later.
The name of the file that is currently being executed is available via nbb.core/*file*
or on the metadata of vars:
(ns foo
(:require [nbb.core :refer [*file*]]))
(prn *file*) ;; "/private/tmp/foo.cljs"
(defn f [])
(prn (:file (meta #'f))) ;; "/private/tmp/foo.cljs"
Nbb includes reagent.core
which will be lazily loaded when required. You can use this together with ink to create a TUI application:
$ npm install ink
ink-demo.cljs
:
(ns ink-demo
(:require ["ink" :refer [render Text]]
[reagent.core :as r]))
(defonce state (r/atom 0))
(doseq [n (range 1 11)]
(js/setTimeout #(swap! state inc) (* n 500)))
(defn hello []
[:> Text {:color "green"} "Hello, world! " @state])
(render (r/as-element [hello]))
Working with callbacks and promises can become tedious. Since nbb v0.0.36 the promesa.core
namespace is included with the let
and do!
macros. An example:
(ns prom
(:require [promesa.core :as p]))
(defn sleep [ms]
(js/Promise.
(fn [resolve _]
(js/setTimeout resolve ms))))
(defn do-stuff
[]
(p/do!
(println "Doing stuff which takes a while")
(sleep 1000)
1))
(p/let [a (do-stuff)
b (inc a)
c (do-stuff)
d (+ b c)]
(prn d))
$ nbb prom.cljs
Doing stuff which takes a while
Doing stuff which takes a while
3
Also see API docs.
Since nbb v0.0.75 applied-science/js-interop is available:
(ns example
(:require [applied-science.js-interop :as j]))
(def o (j/lit {:a 1 :b 2 :c {:d 1}}))
(prn (j/select-keys o [:a :b])) ;; #js {:a 1, :b 2}
(prn (j/get-in o [:c :d])) ;; 1
Most of this library is supported in nbb, except the following:
:syms
.-x
notation. In nbb, you must use keywords.See the example of what is currently supported.
See the examples directory for small examples.
Also check out these projects built with nbb:
See API documentation.
See this gist on how to convert an nbb script or project to shadow-cljs.
Prequisites:
To build:
bb release
Run bb tasks
for more project-related tasks.
Download Details:
Author: borkdude
Download Link: Download The Source Code
Official Website: https://github.com/borkdude/nbb
License: EPL-1.0
#node #javascript
1660212000
Durante a programação, os desenvolvedores encontram problemas que exigem a reutilização de código, levando a uma programação repetitiva que pode desperdiçar tempo e reduzir a produtividade. Isso dá origem à necessidade de código-fonte reutilizável chamado “snippets de código”. Esses trechos evitam código repetitivo durante a programação, podem ser salvos para uso futuro e são compartilháveis.
Neste tutorial, criaremos um site para ajudar os usuários a salvar trechos de código diários usando a estrutura de desenvolvimento da Web Next.js e alimentado pelo banco de dados Fauna para lidar com o armazenamento, manipulação e exibição de trechos de código. Trabalhando neste projeto divertido, também aprenderemos como criar um aplicativo CRUD básico com Next.js e FaunaDB que também pode ser usado para construir outros projetos semelhantes.
Uma versão funcional deste projeto pode ser encontrada no GitHub . Para acompanhar, você precisará do Node instalado em sua máquina , bem como uma conta FaunaDB e uma conta Google (para autenticação).
Nesta seção, veremos como instalar o Next.js usando o npx create-next-app
comando. Isso inicializará a Next CLI e criará um novo aplicativo Next.js.
Também instalaremos as dependências que usaremos para o back-end — FaunaDB e SWR — por meio da linha de comando. SWR (state-while-revalidate) é um gancho Next.js para buscar dados. Vamos entrar nisso em profundidade mais tarde neste tutorial.
Para instalar o Next.js, digite o seguinte comando na CLI:
npx create-next-app snippetapp
O comando acima cria um diretório de projeto chamado snippetapp
com o modelo inicial Next.js, que contém os arquivos necessários para codificação com Next. Quando o Next terminar a instalação, mude para o diretório recém-criado:
cd snippetapp
Para instalar o Fauna, usaremos o seguinte comando na CLI:
npm install --save faunadb
Em seguida, para instalar o SWR:
npm install swr@0.3.8
Com isso, instalamos todas as dependências que usaremos para construir nossa aplicação e agora podemos prosseguir para configurar nosso banco de dados no Fauna.
O FaunaDB é um banco de dados em tempo real sem servidor. Ele transforma um banco de dados tradicional em uma API de dados flexível que ainda retém os recursos de um banco de dados e seu desempenho, ao mesmo tempo em que oferece acesso seguro e escalável aos dados do aplicativo.
Aqui, criaremos uma conta de usuário e configuraremos o banco de dados para armazenar os dados que usaremos em nosso aplicativo de snippet.
Para criar uma conta de usuário, navegue até a página de inscrição do Fauna e crie uma conta.
Depois de criar uma conta de usuário, você será redirecionado para o painel.
Aqui, criaremos um banco de dados com as coleções necessárias para gerenciar os trechos de código do nosso aplicativo. Clique em CRIAR BANCO DE DADOS . Vamos criar um banco de dados chamado snippets
.
Na nova página que se abre, clique em NOVA COLEÇÃO e crie uma coleção chamada codesnippet
.
Depois de criar uma coleção, obtemos uma página onde podemos criar um documento.
Aqui, você clicará em NOVO DOCUMENTO . Um documento JSON será aberto, onde você poderá inserir os detalhes, conforme ilustrado abaixo.
{
name: "Prompt User",
description: "prompts the user",
language: "javascript",
code: "prompt('would you like to continue')"
}
Aqui, definimos um snippet com atributos: name
, description
, language
e code
. Clique em SALVAR para salvar a nova coleção. Adicionamos com sucesso um snippet ao nosso banco de dados. Agora podemos prosseguir para obter nossas credenciais de acesso para usar em nosso aplicativo.
No painel, clique em Segurança . Isso abre uma nova página para criar nossa chave de segurança.
Aqui, definiremos a função como “servidor” em vez de “admin”, e você pode dar um nome à chave. Clique no botão SAVE para gerar sua chave.
.env
arquivoAgora vamos criar um .env
arquivo dentro do diretório do nosso projeto. Este arquivo armazenará nossa chave secreta gerada. No .env
arquivo temos isso:
FAUNA_SECRET = paste your key here
Nesta seção, criaremos a página de exibição e upload para os snippets e também adicionaremos funcionalidades a ela.
Abra o diretório do projeto em seu editor de código e navegue até o index.js
arquivo na pasta de páginas. Aqui vamos limpar o código e começar a construir nosso aplicativo:
import Head from "next/head"
import Image from "next/image"
import styles from "../styles/Home.module.css"
export default function Home() {
return (
<div className={styles.container}>
<Head>
<title>View Snippet</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<h1 className={styles.title}>Re-usuable Code Snippets</h1>
<p className={styles.info}>Add your code snippets here...</p>
<button>Create new snippet</button>
</main>
</div>
)
}
Agora vamos criar um arquivo componente que irá renderizar nossos trechos. Crie uma pasta nomeada component
em seu diretório de trabalho e crie um arquivo nomeado Snippets.js
dentro dela com o seguinte código:
import React from "react"
import styles from "../styles/Home.module.css"
function Snippets() {
return (
<div className={styles.cont}>
<p className={styles.lang}>language</p>
<h3 className={styles.name}>name of snippet</h3>
<p className={styles.descp}>description of snippet</p>
{/* Code will be displayed here*/}
<div className={styles.links}>
<a>Edit</a>
<a>Delete</a>
</div>
</div>
)
}
export default Snippets
Agora adicionaremos importações para este arquivo em nosso index.js
:
import Snippets from "../components/Snippets"
E use-o em nosso aplicativo:
<button>Create new snippet</button>
<Snippets/>
Agora podemos estilizar nossa página. Navegue até o Home.module.css
arquivo na styles
pasta e substitua os estilos pelo seguinte:
.container{
display: flex;
height: 100%;
min-height: 100vh;
background: rgb(48, 48, 255);
flex-direction: column;
align-items: center;
color: #fff;
font-family: Montserrat;
}
.cont{
color: #333;
margin-top: 5px;
background: rgb(214, 214, 214);
border-radius: 15px;
padding: 10px 15px;
}
.main button{
width: fit-content;
flex-grow: unset;
display: inline-block;
padding: 5px 10px;
outline: none;
border: none;
border-radius: 5%;
font-weight: bold;
color: rgb(48, 48, 255);
}
.main button:hover{
cursor: pointer;
}
.links{
margin-top: 10px;
}
.links a{
margin-left: 5px;
}
.links a:hover{
cursor: pointer;
}
Neste ponto, você poderá iniciar o servidor dev com npm run dev
, visite http://localhost:3000 e veja o esqueleto do nosso aplicativo.
Em seguida, criaremos a seção de exibição para o código do snippet. Crie um novo arquivo chamado Code.js
na pasta de componentes e importe-o para Snippets.js
:
import React from 'react'
import styles from '../styles/Home.module.css'
import Code from "./Code";
function Snippets() {
return (
<div className={styles.cont}>
<p className={styles.lang}>language</p>
<h3 className={styles.name}>name of snippet</h3>
<p className={styles.descp}>description of snippet</p>
{/* Code will be displayed here*/}
<Code />
<div className={styles.links}>
<a>Edit</a>
<a>Delete</a>
</div>
</div>
)
}
export default Snippets
Para o realce de sintaxe do código, usaremos dois pacotes, a saber, react-syntax-highlighter e react-copy-to-clipboard . Podemos fazer o download através da CLI:
npm install react-syntax-highlighter react-copy-to-clipboard --save
Então em Code.js
:
import React from "react"
import { PrismLight as SyntaxHighlighter } from "react-syntax-highlighter"
import {atomDark} from "react-syntax-highlighter/dist/cjs/styles/prism"
import { CopyToClipboard } from "react-copy-to-clipboard"
import styles from "../styles/Home.module.css"
function Code() {
const codeString = "npm install import react from 'react'"
const [show, setshow] = React.useState(false)
return (
<div>
<button onClick={() => setshow(!show)}>Show Code</button>
{show ? (
<div>
<CopyToClipboard text={codeString}>
<button className={styles.btn}>Copy</button>
</CopyToClipboard>
<SyntaxHighlighter language="javascript" style={atomDark}>
{codeString}
</SyntaxHighlighter>
</div>
) : null}
</div>
)
}
export default Code
Aqui, criamos um componente para exibir código com realce de sintaxe. Também adicionamos a funcionalidade de cópia e alternância de exibição. Agora no styles
arquivo:
.btn{
left: 80%;
position: relative;
}
Para visualizar essa alteração, você pode executar npm run dev
na linha de comando e visualizá-la em seu navegador. Temos a string “npm install import react from 'react'” exibida com destaque de sintaxe como um bloco de código. Há também um botão para ocultar e exibir o trecho de código e um botão que nos permite copiar o código do bloco de código.
Nesta seção, buscaremos dados do nosso banco de dados FaunaDB para nosso aplicativo. Crie um arquivo chamado Fauna.js
no diretório do seu projeto:
const faunadb = require("faunadb")
const faunaClient = new faunadb.Client({
secret: process.env.FAUNA_SECRET
})
const q = faunadb.query
const getResponse = async () => {
const { data } = await faunaClient.query(
q.Map(
q.Paginate(q.Documents(q.Collection("codesnippet"))),
q.Lambda("doc", q.Get(q.Var("doc")))
)
)
const snippets = data.map((snippet) => {
snippet.id = snippet.ref.id
delete snippet.ref
return snippet
})
return snippets
}
module.exports = {
getResponse,
}
Aqui, inicializamos o FaunaDB com nossa chave secreta. Também configuramos uma async
solicitação para consultar nossa coleção e retornar os dados. Armazenamos os dados retornados em uma variável chamada snippets
e excluímos a ref para estruturar melhor os dados. Outras funcionalidades para criar, atualizar e excluir snippets serão adicionadas posteriormente neste tutorial.
Observe que, se você estiver recebendo um erro não autorizado no console, talvez seja necessário especificar o nome de domínio do endpoint de destino. O padrão é db.fauna.com
, mas desde a introdução dos Region Groups , três domínios de nuvem estão disponíveis. Use o domínio correto para o Grupo de regiões do seu banco de dados:
db.fauna.com
db.us.fauna.com
db.eu.fauna.com
Código de exemplo:
const faunaClient = new faunadb.Client({
secret: process.env.FAUNA_SECRET,
domain: "db.eu.fauna.com"
})
Também criaremos um arquivo para lidar com nossa solicitação de API para nosso banco de dados. Dentro da api
pasta em pages
, crie um arquivo chamado snippets.js
com o seguinte código:
import { getResponse } from "../../Fauna.js"
export default async function handler(req, res) {
console.log(req)
if (req.method !== "GET") {
return res.status(405)
}
try {
const snippets = await getResponse()
return res.status(200).json(snippets)
} catch (err) {
console.log(err)
res.status(500).json({ msg: "Something went wrong." })
}
}
Acima, simplesmente configuramos uma função para lidar com solicitações do nosso banco de dados. Os snippets são retornados como Json
e registrarão erros se ocorrerem. No Next.js, qualquer arquivo armazenado na api
pasta é tratado como endpoints de API em vez de uma página e é renderizado no lado do servidor.
Como dito anteriormente, SWR (state-while-revalidate) é um gancho Next.js para buscar dados. É uma solução perfeita para buscar dados atualizados com frequência e é uma boa opção para o nosso aplicativo.
Usaremos isso para buscar dados do FaunaDB. Para usar isso, precisamos importá-lo para index.js
:
import useSWR from "swr"
export default function Home() {
const { data:snippets, mutate }=useSWR("api/snippets")
...
})
Aqui, importamos o SWR e o usamos para buscar dados conforme configurado em snippets.js
. Em seguida, armazenamos esses trechos na snippets
variável e os produziremos a partir daí. Agora vamos passar o snippets
para o nosso Snippets
componente para exibir:
- <Snippets />
+ {snippets &&
+ snippets.map((snippet) => (
+ <Snippets
+ key={snippet.id}
+ snippet={snippet}
+ snippetDeleted={mutate}
+ />
+ ))
+ }
Acima, passamos a chave e o snippet para Snippets
. Também configuramos uma mutate
propriedade para atualizar (re-buscar) snippets quando um snippet é excluído. Para usar os dados passados, modificamos o Snippets
componente com o seguinte:
function Snippets({snippet}) {
return (
<div className={styles.cont}>
<p className={styles.lang}>{snippet.data.language}</p>
<h3 className={styles.name}>{snippet.data.name}</h3>
<p className={styles.descp}>{snippet.data.description}</p>
<Code snippet={snippet}/>
<div className={styles.links}>
<a>Edit</a>
<a>Delete</a>
</div>
</div>
)
}
Acima, inserimos o idioma do snippet, nome e descrição recebidos do FaunaDB em nosso código. Para obter o código da Fauna em nosso aplicativo, também tivemos que passar o snippet prop para o Code
componente.
Então no Code
componente:
function Code({snippet}){
...
<div>
<CopyToClipboard text={snippet.data.code}>
<button className={styles.btn}>Copy</button>
</CopyToClipboard>
<SyntaxHighlighter language="javascript" style={atomDark}>
{snippet.data.code}
</SyntaxHighlighter>
</div>
...
}
Agora terminamos com a GetSnippet
funcionalidade. Se retornarmos ao FaunaDB e criarmos um novo snippet, veremos o que está ilustrado abaixo.
{
"name": "console.log()",
"language": "javascript",
"description": "logs out data",
"code": "console.log('Hello, world!')"'
}
Para executar na CLI:
npm run dev
Se você abrir a página em seu navegador, terá um resultado semelhante ao da imagem abaixo.
Criamos com sucesso uma página de exibição de snippet com funcionalidades para mostrar e ocultar o código e copiar o snippet de código.
Precisaremos criar um link para a página de upload do nosso componente inicial. O Next.js tem provisões que facilitam o roteamento sem que você precise instalar react-router
e outras dependências, como faria se estivesse usando o código React nativo.
Em index.js
, importaremos o Link
módulo de next
:
import Link from "next/link"
Em seguida, adicione-o ao nosso botão Criar novo trecho :
- <button>Create new snippet</button>
+ <Link href="/upload">
+ <button>Create new snippet</button>
+ </Link>
Vamos criar uma nova página em nossa pages
pasta e nomeá-la upload.js
.
De volta ao nosso Fauna.js
arquivo, vamos criar e também exportar uma função para criar snippets em nosso aplicativo:
const createSnippet = async (code, language, description, name) => {
return await faunaClient.query(q.Create(q.Collection("codesnippet"), {
data:{code, language, description, name}
}))
}
module.exports = {
getResponse,
createSnippet,
}
Aqui, criamos a função createSnippet
, que receberá alguns parâmetros e os passará como dados no novo documento que será criado no banco de dados.
Também configuraremos nosso endpoint para criar snippets. Crie um novo arquivo chamado createSnippet.js
na api
pasta e preencha-o com o seguinte código:
import { createSnippet } from "../../Fauna"
export default async function handler(req, res) {
const { code, language, description, name } = req.body
if (req.method !== "POST") {
return res.status(405).json({msg:"unauthorized"})
}
try {
const createdSnippet = await createSnippet(code, language, description, name)
return res.status(200).json(createdSnippet)
} catch (error) {
console.log(error)
res.status(500).json({msg:"unauthorized"})
}
}
Agora vamos criar a página de upload em nosso upload.js
arquivo. Para nosso formulário criar snippets, usaremos o react-hook-form . Vamos instalar isso via CLI:
npm install react-hook-form
Então, em nosso upload.js
arquivo:
import React from "react"
import { useForm } from "react-hook-form"
import { useRouter } from "next/router"
import style from "../styles/form.module.css"
import { Link } from "next/link"
function upload({ snippet }) {
const { register, handleSubmit, errors, reset } = useForm()
const router = useRouter()
const createSnippet = async (data) => {
const { code, language, description, name } = data
console.log(data)
try {
// code here to push to Fauna
} catch (error) {
console.log(error)
}
}
return (
<div className={style.cont}>
<form
className={style.form}
onSubmit={handleSubmit(snippet ? updateSnippet : createSnippet)}
>
<div>
<label htmlFor="name">Name</label>
<input
className={style.input}
type="text"
id="name"
{...register("name", { required: true })}
/>
</div>
<div>
<label className={style.label} htmlFor="language">
language
</label>
<select
className={style.select}
type="text"
id="language"
{...register("language", { required: true })}
>
<option>Javascript</option>
<option>Html</option>
<option>CSS</option>
</select>
</div>
<div>
<label className={style.label} htmlFor="description">
description
</label>
<textarea
className={style.input}
rows={7}
type="text"
id="description"
placeholder="snippet description"
{...register("description", { required: true })}
/>
</div>
<div>
<label className={style.label} htmlFor="code">
Code
</label>
<textarea
className={style.input}
rows={8}
columns={8}
type="text"
id="code"
{...register("code", { required: true })}
placeholder="background: none;"
/>
</div>
<div>
<button className={style.button}>Submit</button>
<button className={style.button}>Cancel</button>
</div>
</form>
</div>
)
}
export default upload
Acima, criamos nosso formulário usando o react-hook-form
pacote. Usamos a handleSubmit
função useForm()
no operador ternário. Ao enviar o formulário, determina se o envio realizado é para criar ou atualizar um trecho existente. Com register
, adicionamos a propriedade obrigatória a todos os campos do nosso formulário. Também adicionamos importações para uma folha de estilo chamada form.module.css
onde temos os seguintes estilos para nosso formulário:
.form {
max-width: 800px;
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
}
.cont{
background: rgb(48, 48, 255);
height: 100%;
min-height: 100vh;
padding: 10px 0 0 0;
display: flex;
justify-content: center;
align-items: center;
}
.select,
.input {
display: block;
box-sizing: border-box;
width: 100%;
border-radius: 4px;
border: 1px solid black;
padding: 10px 15px;
margin-bottom: 15px;
font-size: 14px;
}
.label{
line-height: 2;
text-align: left;
display: block;
margin: 5px;
color: white;
font-size: 14px;
font-weight: 200;
}
.button {
background : #fff;
color: #444;
border: none;
border-radius: 5%;
margin-right: 8px;
}
Para enviar os dados do nosso formulário para o banco de dados FaunaDB, adicione o seguinte código ao try...catch
bloco na createSnippet
função em upload.js
:
try {
await fetch("/api/createSnippet", {
method: "POST",
body: JSON.stringify({ code, language, description, name }),
headers: {
"Content-type": "application/json"
},
})
router.push("/")
} catch (error) {
console.log(error)
}
Execute o código e navegue até a página de upload. Agora, se adicionarmos um novo snippet ao formulário e clicarmos em Submit , veremos o que está na imagem abaixo.
Quando navegamos para nosso componente inicial, podemos ver o trecho criado.
Para criar nossa funcionalidade de edição de trechos, de volta ao Fauna.js
arquivo, criaremos e exportaremos uma função para lidar com essa tarefa:
const updateSnippet = async (id, code, language, description, name) => {
return await faunaClient.query(q.Update(q.Ref(q.Collection("codesnippet"), id), {
data: {code, language, name, description},
}))
}
module.exports = {
...
updateSnippet,
}
Essa função é semelhante à createSnippet
função, mas também recebe um parâmetro de id
. Ele usa esse ID para identificar quais snippets devem ser editados. Se id
corresponder, atualizamos os dados com os outros parâmetros. Também criaremos um arquivo de endpoint no api
diretório chamado updateSnippet.js
para lidar com as atualizações:
import { updateSnippet } from "../../Fauna"
export default async function handler(req, res) {
const { id, code, language, description, name } = req.body
if (req.method !== "PUT") {
return res.status(405).json({ msg: "unauthorized" })
}
try {
const updated = await updateSnippet(
id,
code,
language,
description,
name
)
return res.status(200).json(updated)
}
catch (error) {
console.log(error)
res.status(500).json({ msg: "unauthorized" })
}
}
Agora, vá para o Snippets
componente e modifique este componente para fazer uso desta função. Primeiro, vamos importar o Link
módulo:
...
import Link from "next/link"
Também modificamos nosso edit
botão:
- <a>Edit</a>
+ <Link href={`/edit/${snippet.id}`}>
+ <a>Edit</a>
+ </Link>
Quando clicado, ele envia uma solicitação para a página edit
com o id
snippet selecionado. Na pages
pasta, crie uma pasta nomeada edit
com um arquivo [id].js
dentro dela:
import { getSnippetById } from "../../Fauna"
import Upload from "../upload"
export default function Home({ snippet }) {
const email = ""
const user = ""
return (
<div>
<h3>Update a snippet</h3>
<Upload snippet={snippet} email={email} user={user}/>
</div>
)
}
export async function getServerSideProps(context) {
try {
//get and update record
const id = context.params.id
}
catch (error) {
console.log(error)
context.res.statusCode = 302
context.res.setHeader("Location", "/")
return {props: {}}
}
}
Em [id].js
, estamos passando o snippet de código props
para a página de upload do snippet. No entanto, desta vez, a página de upload conterá os dados armazenados no snippet de código referenciado pelo id
. Para buscar o snippet por ID, precisaremos criar a getSnippetById
função no Fauna.js
arquivo:
const getSnippetById = async (id) => {
const snippet = await faunaClient.query(q.Get(q.Ref(q.Collection("codesnippet"),id)))
snippet.id = snippet.ref.id
delete snippet.ref
return snippet
}
module.exports = {
getResponse,
createSnippet,
updateSnippet,
getSnippetById,
}
À medida que exportamos a função, de volta ao [id].js
arquivo, podemos usá-la para buscar um trecho específico com seu ID:
try {
const id = context.params.id;
const snippet = await getSnippetById(id);
return {
props: { snippet },
};
} catch (error) {
// as before
}
Agora, no upload.js
arquivo, vamos modificá-lo para poder acessar os dados armazenados se um snippet for editado:
- const { register, handleSubmit, errors, reset } = useForm()
+ const { register, handleSubmit, errors, reset } = useForm({
+ defaultValues: {
+ code: snippet ? snippet.data.code : "",
+ language: snippet ? snippet.data.language : "",
+ description: snippet ? snippet.data.description : "",
+ name: snippet ? snippet.data.name : "",
+ }
+ })
O código acima verifica se o snippet tem dados armazenados nele. Se retornar true, retorna os dados para os parâmetros: code
, language
, description
e code
. Se ele retornar false
, ele retornará uma string vazia.
Em seguida, criaremos uma função para atualizar o snippet de código:
const createSnippet = async (data) => { ... }
const updateSnippet = async (data) => {
const { code, language, description, name } = data
const id = snippet.id
try {
await fetch("/api/updateSnippet", {
method: "PUT",
body: JSON.stringify({ code, language, description, name, id }),
headers: {
"Content-Type": "application/json",
},
})
router.push("/")
}
catch (error) {
console.log(error)
}
}
return ( ,,, )
Se executarmos nosso código, podemos editar os trechos de código criados anteriormente clicando no botão Editar , alterando os dados do formulário e clicando em Enviar .
Agora, se retornarmos ao Home
componente em nosso navegador, poderemos editar e atualizar trechos de código. Podemos finalmente adicionar a funcionalidade final para excluir nosso trecho de código. Crie e exporte uma nova função — deleteSnippet
— no Fauna.js
arquivo:
const deleteSnippet = async (id) => {
return await faunaClient.query(q.Delete(q.Ref(q.Collection("codesnippet"),id)))
}
module.exports = {
...
deleteSnippet,
}
Vamos criar outro endpoint para esta função em nossa api
pasta chamada deleteSnippet.js
e preenchê-la com o seguinte código:
import { deleteSnippet } from "../../Fauna"
export default async function handler(req, res) {
if (req.method !== "DELETE") {
return res.status(405).json({ msg: "unauthorized" })
}
const { id } = req.body
try {
const deleted = await deleteSnippet(id)
return res.status(200).json(deleted)
}
catch (error) {
console.log(error)
res.status(500).join({ msg: "error occured" })
}
}
Em seguida, modificamos o Snippets.js
arquivo para adicionar a nova funcionalidade:
function Snippets({ snippet, snippetDeleted }) {
...
}
Em seguida, crie uma deleteSnippet
função para buscar o endpoint do api
e exclua o snippet referenciado pelo ID:
function Snippets({snippet, snippetDeleted}) {
const deleteSnippet = async () => {
try {
await fetch("/api/deleteSnippet", {
method: "DELETE",
body: JSON.stringify({ id: snippet.id }),
headers: {
"Content-Type": "application/json",
},
});
snippetDeleted();
} catch (e) {
console.log(e);
}
};
return (
<div className={styles.cont}>
<p className={styles.lang}>{snippet.data.language}</p>
<h3 className={styles.name}>{snippet.data.name}</h3>
<p className={styles.descp}>{snippet.data.description}</p>
<Code snippet={snippet}/>
<div className={styles.links}>
<Link href={`/edit/${snippet.id}`}>
<a>Edit</a>
</Link>
<a onClick={deleteSnippet}>Delete</a>
</div>
</div>
)
}
Também atualizamos o elemento âncora para chamar a deleteSnippet
função quando ela é clicada.
Adicionamos a funcionalidade para excluir trechos de código. Agora podemos excluir trechos clicando no botão Excluir em nosso aplicativo.
Isso conclui as funcionalidades do aplicativo snippet. Vamos agora adicionar medidas de autenticação ao nosso aplicativo para permitir que apenas usuários autorizados criem ou modifiquem snippets em nosso aplicativo.
Por que precisamos de autenticação? Atualmente, os usuários podem criar snippets, mas também podem excluir e modificar snippets que não criaram. Precisaremos fornecer um meio para autorizar os usuários a acessar nosso site - e, portanto, a necessidade de autenticação do usuário.
Instalaremos o next-auth para autenticação por meio de nossa CLI:
npm i next-auth
Usaremos um token JWT para nossa autenticação. JWT é um padrão usado para criar tokens de acesso para um aplicativo.
Crie uma pasta nomeada auth
em sua api
pasta e dentro dela, crie um arquivo [...nextauth].js
com o seguinte código:
import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google"
export default NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth?prompt=consent&access_type=offline&response_type=code",
})
],
jwt: {
encryption: true
},
secret: process.env.secret,
callbacks: {
async jwt(token, account) {
if (account ?.accessToken) {
token.accessToken = account.accessToken
}
return token
},
redirect: async (url, _baseUrl)=>{
if (url === "/profile") {
return Promise.resolve("/")
}
return Promise.resolve("/")
}
}
})
Depois disso, encerraremos nossos componentes no _app.js
arquivo:
import '../styles/globals.css'
import {Provider} from "next-auth/client"
function MyApp({ Component, pageProps }) {
return (
<Provider session={pageProps.session}>
<Component {...pageProps} />
</Provider>
)
}
export default MyApp
Vamos modificar nosso Home
componente index.js
para retornar ao nosso componente se o usuário for autenticado, caso contrário ele retornará um botão que leva à página de autenticação:
import {signIn, signOut, useSession} from "next-auth/client"
...
Então dentro de Home
:
export default function Home() {
const { data:snippets, mutate }=useSWR("api/snippets")
const [session, loadingSession] = useSession()
if (loadingSession) {
<>
<p>...authenticating</p>
</>
}
...
}
O código acima verifica se o aplicativo é loadingSession
. Se true, ele retorna o p
bloco de tags, caso contrário, retorna o restante do nosso aplicativo se houver session
. Em seguida, renderizaremos o “log in” se não houver sessão:
return (
<div className={styles.container}>
<Head> ... </Head>
{!session && (
<>
<h1>Sign in to access snippet app</h1>
<button onClick={() => signIn()}>Sign In</button>
</>
)}
{session && (
<>
<main className={styles.main}>
<h3>welcome {session.user.email}</h3>
<button onClick={() => signOut()}>Sign Out</button>
...
</main>
</>
)}
</div>
)
Para fazer uso do “serviço de login do Google”, precisamos de credenciais de acesso do console de nuvem do Google. Para obter isso, faça login na sua conta do Google e navegue até o console do Google Cloud . Clique em CRIAR PROJETO na página, digite o nome do seu projeto e clique em Criar .
Na nova página que se abre, clique em + CRIAR CREDENCIAIS na barra de menu superior e, finalmente, selecione ID do cliente OAuth no menu suspenso.
Na página que se abre, você receberá uma notificação com um botão solicitando “Configurar a tela de consentimento”. Clique neste botão.
Na próxima página, selecione Externo no tipo de usuário e clique em Criar . Digite os campos obrigatórios para seu “Nome do aplicativo” e “E-mail” e clique em Salvar e continuar .
Nas seções Scopes and Test users , role para baixo e clique em Save and Continue .
Por fim, clique em Voltar ao painel e clique no botão Publicar .
Agora, podemos criar nossa chave clicando em Credenciais no menu lateral e, em seguida, Criar Credenciais na barra de menu superior. Selecione Oauth Client ID na lista suspensa e você verá uma página solicitando o tipo de aplicativo.
Selecione Aplicativo da Web e, em “Origens de JavaScript autorizadas”, clique em Adicionar URI e insira http://localhost
. Por fim, em “URIs de redirecionamento autorizados”, clique em Adicionar URI e digite http://localhost/api/auth/callback/google
no campo, antes de clicar em Criar .
Copie o ID do cliente e o segredo do cliente do pop-up que é aberto e adicione-os ao .env
arquivo:
GOOGLE_CLIENT_ID=id
GOOGLE_CLIENT_SECRET=secret
Agora podemos fazer login usando a autenticação do Google em nosso aplicativo. Além disso, configuraremos nosso upload.js
arquivo como uma rota protegida para que usuários não autorizados não possam criar novos snippets:
import { getSession } from "next-auth/client"
function Upload({ snippet, user }) { ... }
export async function getServerSideProps(context) {
const session = await getSession(context)
if (!session) {
context.res.writeHead(302, { Location: "/" })
context.res.end()
return {}
}
return {
props: {
user: session.user,
}
}
}
export default Upload;
Se executarmos nosso aplicativo com o npm run dev
comando, primeiro obteremos uma página solicitando que façamos login. Não podemos navegar para a página de upload pelo /upload
caminho em nosso URL. Só podemos acessar nosso aplicativo quando usamos o recurso de login do Google para fazer login em nosso aplicativo.
Por fim, modificaremos a createSnippet
funcionalidade para adicionar o e-mail do usuário ao banco de dados e só mostraremos os botões Editar e Excluir se o e-mail corresponder.
Em Fauna.js
, altere a createSnippet
função assim:
const createSnippet = async (code, language, description, name, mail) => {
return await faunaClient.query(q.Create(q.Collection("codesnippet"), {
data:{code, language, description, name, mail}
}))
}
No createSnippet.js
arquivo, faça as seguintes alterações:
- const { code, language, description, name } = req.body;
+ const { code, language, description, name, mail } = req.body;
- const createdSnippet = await createSnippet(code, language, description, name);
+ const createdSnippet = await createSnippet(code, language, description, name, mail);
Em upload.js
:
function upload({ snippet, user }) {
+ const email = user.email;
...
}
E altere a createSnippet
função e a updateSnippet
função, como segue:
const createSnippet = async (data) => {
const { code, language, description, name, mail } = data;
console.log(data)
try {
await fetch("/api/createSnippet", {
method: "POST",
body: JSON.stringify({ code, language, description, name, mail:email }),
headers: {
"Content-type": "application/json"
},
})
router.push("/")
} catch (error) {
console.log(error)
}
}
const updateSnippet = async (data) => {
const { code, language, description, name } = data
const id = snippet.id
try {
await fetch("/api/updateSnippet", {
method: "PUT",
body: JSON.stringify({ code, language, description, name, mail:email }),
headers: {
"Content-Type": "application/json",
},
})
router.push("/")
}
catch (error) {
console.log(error)
}
}
Agora podemos continuar fazendo com que os botões Editar e Excluir sejam exibidos apenas se o email corresponder.
Primeiro, passamos as user.mail
props para o Snippet
componente em index.js
:
<Snippets
key={snippet.id}
snippet={snippet}
snippetDeleted={mutate}
+ email={session.user.email}
/>
Então em Snippet.js
:
function Snippets({ snippet, snippetDeleted, email }) {
...
{email == snippet.data.mail && (
<>
<div className={styles.links}>
<Link href={`/edit/${snippet.id}`}>
<a>Edit</a>
</Link>
<a onClick={deleteSnippet}>Delete</a>
</div>
</>
)}
...
}
Execute npm run dev
na CLI e abra o aplicativo em seu navegador. Agora, se você criar um novo snippet, o email do usuário será adicionado ao banco de dados. Se o e-mail não corresponder, os botões Editar e Excluir não serão exibidos na página de exibição do snippet. Você pode testar isso fazendo login com um endereço de e-mail diferente daquele usado para criar os snippets de código.
Finalmente chegamos ao fim deste tutorial. Aprendemos como criar um aplicativo CRUD com Next.js e FaunaDB e como realizar operações CRUD com base na autenticação do usuário.
Para conferir o código completo, visite o repositório GitHub .
Fonte: https://www.sitepoint.com/nextjs-faunadb-build-code-snippet-app/
1640850190
Durante a programação, os desenvolvedores encontram problemas que exigem a reutilização de código, levando a uma programação repetitiva que pode ser uma perda de tempo e reduzir a produtividade. Isso dá origem à necessidade de código-fonte reutilizável, denominado “fragmentos de código”. Esses trechos evitam código repetitivo durante a programação, podem ser salvos para uso futuro e são compartilháveis.
Neste tutorial, construiremos um site para ajudar os usuários a salvar trechos de código diários usando a estrutura de desenvolvimento da Web Next.js e com base no banco de dados Fauna para lidar com o armazenamento, manipulação e exibição de trechos de código. Ao trabalhar neste projeto divertido, também aprenderemos como criar um aplicativo CRUD básico com Next.js e FaunaDB que também pode ser usado para construir outros projetos semelhantes.
Uma versão funcional deste projeto pode ser encontrada no GitHub . Para acompanhar, você precisará do Node instalado em sua máquina , bem como uma conta FaunaDB e uma conta Google (para autenticação).
Nesta seção, veremos como instalar Next.js usando o npx create-next-app
comando. Isso inicializará o Next CLI e construirá um novo aplicativo Next.js.
Também instalaremos as dependências que usaremos para o back end - FaunaDB e SWR - por meio da linha de comando. SWR (state-while-revalidate) é um gancho Next.js para buscar dados. Entraremos em detalhes mais adiante neste tutorial.
Para instalar Next.js, digite o seguinte comando na CLI:
npx create-next-app snippetapp
O comando acima cria um diretório de projeto chamado snippetapp
com o modelo inicial Next.js, que contém os arquivos necessários para codificação com Next. Quando o Next terminar de instalar, mude para o diretório recém-criado:
cd snippetapp
Para instalar o Fauna, usaremos o seguinte comando na CLI:
npm install --save faunadb
Em seguida, para instalar o SWR:
npm install swr@0.3.8
Com isso, instalamos todas as dependências que usaremos para construir nosso aplicativo e agora podemos prosseguir com a configuração de nosso banco de dados no Fauna.
FaunaDB é um banco de dados em tempo real sem servidor. Ele transforma um banco de dados tradicional em uma API de dados flexível que ainda retém os recursos de um banco de dados e seu desempenho, ao mesmo tempo que oferece acesso seguro e escalonável aos dados do aplicativo.
Aqui, criaremos uma conta de usuário e configuraremos o banco de dados para armazenar os dados que usaremos em nosso aplicativo de snippet.
Para criar uma conta de usuário, navegue até a página de inscrição do Fauna e crie uma conta.
Depois de criar uma conta de usuário, você será redirecionado para o painel.
Aqui, criaremos um banco de dados com as coleções necessárias para gerenciar os trechos de código de nosso aplicativo. Clique em CRIAR BANCO DE DADOS . Vamos criar um banco de dados chamado snippets
.
Na nova página que se abre, clique em NOVA COLEÇÃO e crie uma coleção chamada codesnippet
.
Depois de criar uma coleção, obtemos uma página onde podemos criar um documento.
Aqui, você clica em NOVO DOCUMENTO . Um documento JSON será aberto, onde você pode inserir os detalhes, conforme ilustrado abaixo.
{
name: "Prompt User",
description: "prompts the user",
language: "javascript",
code: "prompt('would you like to continue')"
}
Aqui, nós definimos um trecho com atributos: name
, description
, language
e code
. Clique em SALVAR para salvar a nova coleção. Adicionamos com sucesso um snippet ao nosso banco de dados. Agora podemos prosseguir para obter nossas credenciais de acesso para usar em nosso aplicativo.
No painel, clique em Segurança . Isso abre uma nova página para criar nossa chave de segurança.
Aqui, definiremos a função como “servidor” em vez de “admin” e você poderá dar um nome à chave. Clique no botão SALVAR para gerar sua chave.
.env
arquivoAgora vamos criar um .env
arquivo dentro do diretório do nosso projeto. Este arquivo armazenará nossa chave secreta gerada. No .env
arquivo, temos o seguinte:
FAUNA_SECRET = paste your key here
Nesta seção, construiremos a página de exibição e upload para os snippets e também adicionaremos funcionalidade a ela.
Abra o diretório do projeto em seu editor de código e navegue até o index.js
arquivo na pasta de páginas. Aqui, vamos limpar o código e começar a construir nosso aplicativo:
import Head from "next/head"
import Image from "next/image"
import styles from "../styles/Home.module.css"
export default function Home() {
return (
<div className={styles.container}>
<Head>
<title>View Snippet</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<h1 className={styles.title}>Re-usuable Code Snippets</h1>
<p className={styles.info}>Add your code snippets here...</p>
<button>Create new snippet</button>
</main>
</div>
)
}
Agora vamos criar um arquivo de componente que renderizará nossos snippets. Crie uma pasta nomeada component
em seu diretório de trabalho e crie um arquivo nomeado Snippets.js
dentro dela com o seguinte código:
import React from "react"
import styles from "../styles/Home.module.css"
function Snippets() {
return (
<div className={styles.cont}>
<p className={styles.lang}>language</p>
<h3 className={styles.name}>name of snippet</h3>
<p className={styles.descp}>description of snippet</p>
{/* Code will be displayed here*/}
<div className={styles.links}>
<a>Edit</a>
<a>Delete</a>
</div>
</div>
)
}
export default Snippets
Agora adicionaremos importações para este arquivo em nosso index.js
:
import Snippets from "../components/Snippets"
E use-o em nosso aplicativo:
<button>Create new snippet</button>
<Snippets/>
Agora podemos estilizar nossa página. Navegue até o Home.module.css
arquivo na styles
pasta e substitua os estilos lá pelo seguinte:
.container{
display: flex;
height: 100%;
min-height: 100vh;
background: rgb(48, 48, 255);
flex-direction: column;
align-items: center;
color: #fff;
font-family: Montserrat;
}
.cont{
color: #333;
margin-top: 5px;
background: rgb(214, 214, 214);
border-radius: 15px;
padding: 10px 15px;
}
.main button{
width: fit-content;
flex-grow: unset;
display: inline-block;
padding: 5px 10px;
outline: none;
border: none;
border-radius: 5%;
font-weight: bold;
color: rgb(48, 48, 255);
}
.main button:hover{
cursor: pointer;
}
.links{
margin-top: 10px;
}
.links a{
margin-left: 5px;
}
.links a:hover{
cursor: pointer;
}
Nesse ponto, você deve conseguir iniciar o servidor de desenvolvimento com npm run dev
, visite http: // localhost: 3000 e veja o esqueleto de nosso aplicativo.
A seguir, criaremos a seção de exibição para o código do snippet. Crie um novo arquivo chamado Code.js
na pasta de componentes e importe-o para Snippets.js
:
import React from 'react'
import styles from '../styles/Home.module.css'
import Code from "./Code";
function Snippets() {
return (
<div className={styles.cont}>
<p className={styles.lang}>language</p>
<h3 className={styles.name}>name of snippet</h3>
<p className={styles.descp}>description of snippet</p>
{/* Code will be displayed here*/}
<Code />
<div className={styles.links}>
<a>Edit</a>
<a>Delete</a>
</div>
</div>
)
}
export default Snippets
Para o realce da sintaxe do código, usaremos dois pacotes, a saber, react-syntax-highlighter e react-copy-to-clipboard . Podemos fazer o download por meio da CLI:
npm install react-syntax-highlighter react-copy-to-clipboard --save
Então em Code.js
:
import React from "react"
import { PrismLight as SyntaxHighlighter } from "react-syntax-highlighter"
import {atomDark} from "react-syntax-highlighter/dist/cjs/styles/prism"
import { CopyToClipboard } from "react-copy-to-clipboard"
import styles from "../styles/Home.module.css"
function Code() {
const codeString = "npm install import react from 'react'"
const [show, setshow] = React.useState(false)
return (
<div>
<button onClick={() => setshow(!show)}>Show Code</button>
{show ? (
<div>
<CopyToClipboard text={codeString}>
<button className={styles.btn}>Copy</button>
</CopyToClipboard>
<SyntaxHighlighter language="javascript" style={atomDark}>
{codeString}
</SyntaxHighlighter>
</div>
) : null}
</div>
)
}
export default Code
Aqui, criamos um componente para exibir o código com destaque de sintaxe. Também adicionamos a funcionalidade de cópia e alternância de exibição. Agora no styles
arquivo:
.btn{
left: 80%;
position: relative;
}
Para visualizar essa mudança, você pode executar npm run dev
na linha de comando e visualizá-la em seu navegador. Temos a string “npm install import react from 'react'” exibida com destaque de sintaxe como um bloco de código. Há também um botão para ocultar e exibir o trecho de código e um botão que nos permite copiar o código do bloco de código.
Nesta seção, buscaremos dados de nosso banco de dados FaunaDB para nosso aplicativo. Crie um arquivo chamado Fauna.js
no diretório do seu projeto:
const faunadb = require("faunadb")
const faunaClient = new faunadb.Client({
secret: process.env.FAUNA_SECRET
})
const q = faunadb.query
const getResponse = async () => {
const { data } = await faunaClient.query(
q.Map(
q.Paginate(q.Documents(q.Collection("codesnippet"))),
q.Lambda("doc", q.Get(q.Var("doc")))
)
)
const snippets = data.map((snippet) => {
snippet.id = snippet.ref.id
delete snippet.ref
return snippet
})
return snippets
}
module.exports = {
getResponse,
}
Aqui, inicializamos o FaunaDB com nossa chave secreta. Também configuramos uma async
solicitação para consultar nossa coleção e retornar os dados. Armazenamos os dados retornados em uma variável chamada snippets
e excluímos o ref para melhor estruturar os dados. Outras funcionalidades para criar, atualizar e excluir snippets serão adicionadas posteriormente neste tutorial.
Observe que, se você estiver recebendo um erro não autorizado no console, pode ser necessário especificar o nome de domínio do terminal de destino. O padrão é db.fauna.com
, mas desde a introdução de Grupos de regiões , três domínios de nuvem estão disponíveis. Use o domínio correto para o Grupo de Região do seu banco de dados:
db.fauna.com
db.us.fauna.com
db.eu.fauna.com
Código de exemplo:
const faunaClient = new faunadb.Client({
secret: process.env.FAUNA_SECRET,
domain: "db.eu.fauna.com"
})
Também criaremos um arquivo para lidar com nossa solicitação de API para nosso banco de dados. Dentro da api
pasta em pages
, crie um arquivo chamado snippets.js
com o seguinte código:
import { getResponse } from "../../Fauna.js"
export default async function handler(req, res) {
console.log(req)
if (req.method !== "GET") {
return res.status(405)
}
try {
const snippets = await getResponse()
return res.status(200).json(snippets)
} catch (err) {
console.log(err)
res.status(500).json({ msg: "Something went wrong." })
}
}
Acima, simplesmente configuramos uma função para lidar com as solicitações de nosso banco de dados. Os snippets são retornados como Json
e registrarão os erros, se ocorrerem. Em Next.js, qualquer arquivo armazenado na api
pasta é tratado como endpoints de API em vez de uma página e é renderizado no lado do servidor.
Como dito anteriormente, SWR (state-while-revalidate) é um gancho Next.js para buscar dados. É uma solução perfeita para buscar dados atualizados com frequência e é uma boa opção para nosso aplicativo.
Usaremos isso para buscar dados do FaunaDB. Para usar isso, precisamos importá-lo para index.js
:
import useSWR from "swr"
export default function Home() {
const { data:snippets, mutate }=useSWR("api/snippets")
...
})
Aqui, importamos o SWR e o usamos para buscar dados conforme configurado em snippets.js
. Em seguida, armazenamos esses snippets na snippets
variável e os enviaremos a partir daí. Agora, passaremos o snippets
para nosso Snippets
componente para exibir:
- <Snippets />
+ {snippets &&
+ snippets.map((snippet) => (
+ <Snippets
+ key={snippet.id}
+ snippet={snippet}
+ snippetDeleted={mutate}
+ />
+ ))
+ }
Acima, passamos a chave e o snippet para Snippets
. Também configuramos uma mutate
propriedade para atualizar (buscar novamente) snippets quando um snippet é excluído. Para usar os dados passados, modificamos o Snippets
componente com o seguinte:
function Snippets({snippet}) {
return (
<div className={styles.cont}>
<p className={styles.lang}>{snippet.data.language}</p>
<h3 className={styles.name}>{snippet.data.name}</h3>
<p className={styles.descp}>{snippet.data.description}</p>
<Code snippet={snippet}/>
<div className={styles.links}>
<a>Edit</a>
<a>Delete</a>
</div>
</div>
)
}
Acima, inserimos a linguagem, o nome e a descrição do snippet recebido do FaunaDB em nosso código. Para obter o código do Fauna em nosso aplicativo, também tivemos que passar o snippet prop para o Code
componente.
Em seguida, no Code
componente:
function Code({snippet}){
...
<div>
<CopyToClipboard text={snippet.data.code}>
<button className={styles.btn}>Copy</button>
</CopyToClipboard>
<SyntaxHighlighter language="javascript" style={atomDark}>
{snippet.data.code}
</SyntaxHighlighter>
</div>
...
}
Agora terminamos com a GetSnippet
funcionalidade. Se voltarmos ao FaunaDB e criarmos um novo snippet, veremos o que está na imagem abaixo.
{
"name": "console.log()",
"language": "javascript",
"description": "logs out data",
"code": "console.log('Hello, world!')"'
}
Para rodar na CLI:
npm run dev
Se você abrir a página em seu navegador, terá um resultado semelhante à imagem abaixo.
Nós criamos com sucesso uma página de exibição de snippet com funcionalidades para mostrar e ocultar o código e copiar o snippet de código.
Precisamos criar um link para a página de upload de nosso componente inicial. Next.js tem disposições que tornam o roteamento mais fácil sem a necessidade de instalação react-router
e outras dependências, como você faria se estivesse usando o código React nativo.
Em index.js
, vamos importar o Link
módulo de next
:
import Link from "next/link"
Em seguida, adicione-o ao nosso botão Criar novo snippet :
- <button>Create new snippet</button>
+ <Link href="/upload">
+ <button>Create new snippet</button>
+ </Link>
Vamos criar uma nova página em nossa pages
pasta e nomeá-la upload.js
.
De volta ao nosso Fauna.js
arquivo, criaremos e também exportaremos uma função para criar snippets em nosso aplicativo:
const createSnippet = async (code, language, description, name) => {
return await faunaClient.query(q.Create(q.Collection("codesnippet"), {
data:{code, language, description, name}
}))
}
module.exports = {
getResponse,
createSnippet,
}
Aqui, criamos a função createSnippet
, que pegará alguns parâmetros e os passará como dados no novo documento que será criado no banco de dados.
Também configuraremos nosso endpoint para criar snippets. Crie um novo arquivo chamado createSnippet.js
na api
pasta e preencha-o com o seguinte código:
import { createSnippet } from "../../Fauna"
export default async function handler(req, res) {
const { code, language, description, name } = req.body
if (req.method !== "POST") {
return res.status(405).json({msg:"unauthorized"})
}
try {
const createdSnippet = await createSnippet(code, language, description, name)
return res.status(200).json(createdSnippet)
} catch (error) {
console.log(error)
res.status(500).json({msg:"unauthorized"})
}
}
Agora vamos criar a página de upload em nosso upload.js
arquivo. Para o nosso formulário criar fragmentos, usaremos o formulário react-hook . Vamos instalar isso por meio da CLI:
npm install react-hook-form
Então, em nosso upload.js
arquivo:
import React from "react"
import { useForm } from "react-hook-form"
import { useRouter } from "next/router"
import style from "../styles/form.module.css"
import { Link } from "next/link"
function upload({ snippet }) {
const { register, handleSubmit, errors, reset } = useForm()
const router = useRouter()
const createSnippet = async (data) => {
const { code, language, description, name } = data
console.log(data)
try {
// code here to push to Fauna
} catch (error) {
console.log(error)
}
}
return (
<div className={style.cont}>
<form
className={style.form}
onSubmit={handleSubmit(snippet ? updateSnippet : createSnippet)}
>
<div>
<label htmlFor="name">Name</label>
<input
className={style.input}
type="text"
id="name"
{...register("name", { required: true })}
/>
</div>
<div>
<label className={style.label} htmlFor="language">
language
</label>
<select
className={style.select}
type="text"
id="language"
{...register("language", { required: true })}
>
<option>Javascript</option>
<option>Html</option>
<option>CSS</option>
</select>
</div>
<div>
<label className={style.label} htmlFor="description">
description
</label>
<textarea
className={style.input}
rows={7}
type="text"
id="description"
placeholder="snippet description"
{...register("description", { required: true })}
/>
</div>
<div>
<label className={style.label} htmlFor="code">
Code
</label>
<textarea
className={style.input}
rows={8}
columns={8}
type="text"
id="code"
{...register("code", { required: true })}
placeholder="background: none;"
/>
</div>
<div>
<button className={style.button}>Submit</button>
<button className={style.button}>Cancel</button>
</div>
</form>
</div>
)
}
export default upload
Acima, criamos nosso formulário usando o react-hook-form
pacote. Usamos a handleSubmit
função useForm()
no operador ternário. Após o envio do formulário, ele determina se o envio feito é para criar ou atualizar um snippet existente. Com register
, adicionamos a propriedade necessária a todos os campos de nosso formulário. Também adicionamos importações para uma folha de estilo chamada, form.module.css
onde temos os seguintes estilos para nosso formulário:
.form {
max-width: 800px;
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
}
.cont{
background: rgb(48, 48, 255);
height: 100%;
min-height: 100vh;
padding: 10px 0 0 0;
display: flex;
justify-content: center;
align-items: center;
}
.select,
.input {
display: block;
box-sizing: border-box;
width: 100%;
border-radius: 4px;
border: 1px solid black;
padding: 10px 15px;
margin-bottom: 15px;
font-size: 14px;
}
.label{
line-height: 2;
text-align: left;
display: block;
margin: 5px;
color: white;
font-size: 14px;
font-weight: 200;
}
.button {
background : #fff;
color: #444;
border: none;
border-radius: 5%;
margin-right: 8px;
}
Para enviar os dados do nosso formulário para o banco de dados FaunaDB, adicione o seguinte código ao try...catch
bloco na createSnippet
função em upload.js
:
try {
await fetch("/api/createSnippet", {
method: "POST",
body: JSON.stringify({ code, language, description, name }),
headers: {
"Content-type": "application/json"
},
})
router.push("/")
} catch (error) {
console.log(error)
}
Execute o código e navegue até a página de upload. Agora, se adicionarmos um novo snippet ao formulário e clicarmos em Enviar , veremos o que está ilustrado abaixo.
Quando navegamos para nosso componente inicial, podemos ver o trecho criado.
Para criar nossa funcionalidade de edição de snippet, de volta ao Fauna.js
arquivo, criaremos e exportaremos uma função para lidar com esta tarefa:
const updateSnippet = async (id, code, language, description, name) => {
return await faunaClient.query(q.Update(q.Ref(q.Collection("codesnippet"), id), {
data: {code, language, name, description},
}))
}
module.exports = {
...
updateSnippet,
}
Esta função é semelhante à createSnippet
função, mas também recebe um parâmetro de id
. Ele usa esse ID para identificar quais trechos devem ser editados. Se o id
corresponder, atualizamos os dados com os outros parâmetros. Também criaremos um arquivo de endpoint no api
diretório chamado updateSnippet.js
para lidar com as atualizações:
import { updateSnippet } from "../../Fauna"
export default async function handler(req, res) {
const { id, code, language, description, name } = req.body
if (req.method !== "PUT") {
return res.status(405).json({ msg: "unauthorized" })
}
try {
const updated = await updateSnippet(
id,
code,
language,
description,
name
)
return res.status(200).json(updated)
}
catch (error) {
console.log(error)
res.status(500).json({ msg: "unauthorized" })
}
}
Agora, vá para o Snippets
componente e modifique-o para usar esta função. Primeiro, vamos importar o Link
módulo:
...
import Link from "next/link"
Também modificamos nosso edit
botão:
- <a>Edit</a>
+ <Link href={`/edit/${snippet.id}`}>
+ <a>Edit</a>
+ </Link>
Quando clicado, ele envia uma solicitação para a página edit
com o id
do snippet selecionado. Na pages
pasta, crie uma pasta chamada edit
com um arquivo [id].js
dentro dela:
import { getSnippetById } from "../../Fauna"
import Upload from "../upload"
export default function Home({ snippet }) {
const email = ""
const user = ""
return (
<div>
<h3>Update a snippet</h3>
<Upload snippet={snippet} email={email} user={user}/>
</div>
)
}
export async function getServerSideProps(context) {
try {
//get and update record
const id = context.params.id
}
catch (error) {
console.log(error)
context.res.statusCode = 302
context.res.setHeader("Location", "/")
return {props: {}}
}
}
Em [id].js
, estamos passando o snippet de código props
para a página de upload de snippet. No entanto, desta vez, a página de upload conterá os dados armazenados no snippet de código referenciado pelo id
. Para buscar o snippet por ID, precisaremos criar a getSnippetById
função no Fauna.js
arquivo:
const getSnippetById = async (id) => {
const snippet = await faunaClient.query(q.Get(q.Ref(q.Collection("codesnippet"),id)))
snippet.id = snippet.ref.id
delete snippet.ref
return snippet
}
module.exports = {
getResponse,
createSnippet,
updateSnippet,
getSnippetById,
}
À medida que exportamos a função, de volta ao [id].js
arquivo, podemos usá-la para buscar um snippet específico com seu ID:
try {
const id = context.params.id;
const snippet = await getSnippetById(id);
return {
props: { snippet },
};
} catch (error) {
// as before
}
Agora, no upload.js
arquivo, vamos modificá-lo para poder acessar os dados armazenados se um snippet for editado:
- const { register, handleSubmit, errors, reset } = useForm()
+ const { register, handleSubmit, errors, reset } = useForm({
+ defaultValues: {
+ code: snippet ? snippet.data.code : "",
+ language: snippet ? snippet.data.language : "",
+ description: snippet ? snippet.data.description : "",
+ name: snippet ? snippet.data.name : "",
+ }
+ })
O código acima verifica se o snippet armazenou dados nele. Se ele retorna true, ele retorna os dados para os parâmetros: code
, language
, description
e code
. Se retornar false
, ele retornará uma string vazia.
A seguir, criaremos uma função para atualizar o snippet de código:
const createSnippet = async (data) => { ... }
const updateSnippet = async (data) => {
const { code, language, description, name } = data
const id = snippet.id
try {
await fetch("/api/updateSnippet", {
method: "PUT",
body: JSON.stringify({ code, language, description, name, id }),
headers: {
"Content-Type": "application/json",
},
})
router.push("/")
}
catch (error) {
console.log(error)
}
}
return ( ,,, )
Se executarmos nosso código, podemos editar os trechos de código criados anteriormente clicando no botão Editar , fazendo alterações nos dados do formulário e clicando em Enviar .
Agora, se retornarmos ao Home
componente em nosso navegador, poderemos editar e atualizar trechos de código. Podemos finalmente adicionar a funcionalidade final para excluir nosso trecho de código. Crie e exporte uma nova função - deleteSnippet
- no Fauna.js
arquivo:
const deleteSnippet = async (id) => {
return await faunaClient.query(q.Delete(q.Ref(q.Collection("codesnippet"),id)))
}
module.exports = {
...
deleteSnippet,
}
Criaremos outro endpoint para esta função em nossa api
pasta chamada deleteSnippet.js
e o preencheremos com o seguinte código:
import { deleteSnippet } from "../../Fauna"
export default async function handler(req, res) {
if (req.method !== "DELETE") {
return res.status(405).json({ msg: "unauthorized" })
}
const { id } = req.body
try {
const deleted = await deleteSnippet(id)
return res.status(200).json(deleted)
}
catch (error) {
console.log(error)
res.status(500).join({ msg: "error occured" })
}
}
Em seguida, modificamos o Snippets.js
arquivo para adicionar a nova funcionalidade:
function Snippets({ snippet, snippetDeleted }) {
...
}
Em seguida, crie uma deleteSnippet
função para buscar o endpoint do api
e exclua o snippet referenciado pelo ID:
function Snippets({snippet, snippetDeleted}) {
const deleteSnippet = async () => {
try {
await fetch("/api/deleteSnippet", {
method: "DELETE",
body: JSON.stringify({ id: snippet.id }),
headers: {
"Content-Type": "application/json",
},
});
snippetDeleted();
} catch (e) {
console.log(e);
}
};
return (
<div className={styles.cont}>
<p className={styles.lang}>{snippet.data.language}</p>
<h3 className={styles.name}>{snippet.data.name}</h3>
<p className={styles.descp}>{snippet.data.description}</p>
<Code snippet={snippet}/>
<div className={styles.links}>
<Link href={`/edit/${snippet.id}`}>
<a>Edit</a>
</Link>
<a onClick={deleteSnippet}>Delete</a>
</div>
</div>
)
}
Também atualizamos o elemento âncora para chamar a deleteSnippet
função quando ela é clicada.
Adicionamos funcionalidade para excluir trechos de código. Agora podemos excluir trechos clicando no botão Excluir em nosso aplicativo.
Isso conclui as funcionalidades do aplicativo snippet. Agora prosseguiremos para adicionar medidas de autenticação ao nosso aplicativo para permitir que apenas usuários autorizados criem ou modifiquem snippets em nosso aplicativo.
Por que precisamos de autenticação? Atualmente, os usuários podem criar fragmentos, mas também podem excluir e modificar fragmentos que não criaram. Precisamos fornecer um meio para autorizar os usuários a acessar nosso site - e, portanto, a necessidade de autenticação do usuário.
Instalaremos next-auth para autenticação por meio de nossa CLI:
npm i next-auth
Estaremos usando um token JWT para nossa autenticação. JWT é um padrão usado para criar tokens de acesso para um aplicativo.
Crie uma pasta com o nome auth
em sua api
pasta e, dentro dela, crie um arquivo [...nextauth].js
com o seguinte código:
import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google"
export default NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth?prompt=consent&access_type=offline&response_type=code",
})
],
jwt: {
encryption: true
},
secret: process.env.secret,
callbacks: {
async jwt(token, account) {
if (account ?.accessToken) {
token.accessToken = account.accessToken
}
return token
},
redirect: async (url, _baseUrl)=>{
if (url === "/profile") {
return Promise.resolve("/")
}
return Promise.resolve("/")
}
}
})
Depois disso, agruparemos nossos componentes no _app.js
arquivo:
import '../styles/globals.css'
import {Provider} from "next-auth/client"
function MyApp({ Component, pageProps }) {
return (
<Provider session={pageProps.session}>
<Component {...pageProps} />
</Provider>
)
}
export default MyApp
Modificaremos nosso Home
componente index.js
para retornar ao nosso componente se o usuário for autenticado; caso contrário, ele retornará um botão que leva à página de autenticação:
import {signIn, signOut, useSession} from "next-auth/client"
...
Então dentro Home
:
export default function Home() {
const { data:snippets, mutate }=useSWR("api/snippets")
const [session, loadingSession] = useSession()
if (loadingSession) {
<>
<p>...authenticating</p>
</>
}
...
}
O código acima verifica se o aplicativo é loadingSession
. Se verdadeiro, ele retorna o p
bloco de tags, caso contrário, retorna o resto do nosso aplicativo, se houver session
. A seguir, vamos renderizar o “login” se não houver sessão:
return (
<div className={styles.container}>
<Head> ... </Head>
{!session && (
<>
<h1>Sign in to access snippet app</h1>
<button onClick={() => signIn()}>Sign In</button>
</>
)}
{session && (
<>
<main className={styles.main}>
<h3>welcome {session.user.email}</h3>
<button onClick={() => signOut()}>Sign Out</button>
...
</main>
</>
)}
</div>
)
Para fazer uso do “serviço de login do Google”, precisamos de credenciais de acesso do console da nuvem do Google. Para obter isso, faça login em sua conta do Google e navegue até o console do Google Cloud . Clique em CRIAR PROJETO na página, digite o nome do seu projeto e clique em Criar .
Na nova página que se abre, clique em + CRIAR CREDENCIAIS na barra de menu superior e, por fim, selecione o ID do cliente OAuth no menu suspenso.
Na página que se abre, você receberá uma notificação com um botão pedindo para você “Configurar tela de consentimento”. Clique neste botão.
Na próxima página, selecione Externo sob o tipo de usuário e clique em Criar . Insira os campos obrigatórios para o seu “Nome do aplicativo” e “Email” e clique em Salvar e continuar .
Nas seções Escopos e usuários de teste , role para baixo e clique em Salvar e continuar .
Por fim, clique em Voltar ao painel e clique no botão Publicar .
Agora, podemos criar nossa chave clicando em Credenciais no menu lateral e, em seguida, em Criar Credenciais na barra de menu superior. Selecione Oauth Client ID na lista suspensa e você verá uma página solicitando o tipo de aplicativo.
Selecione Aplicativo da Web e, em “Origens JavaScript autorizadas”, clique em Adicionar URI e digite http://localhost
. Por fim, em “URIs de redirecionamento autorizados”, clique em Adicionar URI e entre http://localhost/api/auth/callback/google
no campo, antes de clicar em Criar .
Copie o ID do cliente e a chave secreta do cliente do pop-up que é aberto e adicione-os ao .env
arquivo:
GOOGLE_CLIENT_ID=id
GOOGLE_CLIENT_SECRET=secret
Agora podemos fazer login usando a autenticação do Google em nosso aplicativo. Além disso, configuraremos nosso upload.js
arquivo como uma rota protegida para que usuários não autorizados não possam criar novos snippets:
import { getSession } from "next-auth/client"
function Upload({ snippet, user }) { ... }
export async function getServerSideProps(context) {
const session = await getSession(context)
if (!session) {
context.res.writeHead(302, { Location: "/" })
context.res.end()
return {}
}
return {
props: {
user: session.user,
}
}
}
export default Upload;
Se executarmos nosso aplicativo com o npm run dev
comando, a princípio obteremos uma página solicitando que “entremos”. Não podemos navegar para a página de upload por meio do /upload
caminho em nosso URL. Só podemos acessar nosso aplicativo quando usamos o recurso de login do Google para fazer login em nosso aplicativo.
Por fim, modificaremos a createSnippet
funcionalidade para adicionar o e-mail do usuário ao banco de dados e só mostraremos os botões Editar e Excluir se o e-mail corresponder.
Em Fauna.js
, altere a createSnippet
função da seguinte maneira:
const createSnippet = async (code, language, description, name, mail) => {
return await faunaClient.query(q.Create(q.Collection("codesnippet"), {
data:{code, language, description, name, mail}
}))
}
No createSnippet.js
arquivo, faça as seguintes alterações:
- const { code, language, description, name } = req.body;
+ const { code, language, description, name, mail } = req.body;
- const createdSnippet = await createSnippet(code, language, description, name);
+ const createdSnippet = await createSnippet(code, language, description, name, mail);
Em upload.js
:
function upload({ snippet, user }) {
+ const email = user.email;
...
}
E altere a createSnippet
função e a updateSnippet
função, da seguinte maneira:
const createSnippet = async (data) => {
const { code, language, description, name, mail } = data;
console.log(data)
try {
await fetch("/api/createSnippet", {
method: "POST",
body: JSON.stringify({ code, language, description, name, mail:email }),
headers: {
"Content-type": "application/json"
},
})
router.push("/")
} catch (error) {
console.log(error)
}
}
const updateSnippet = async (data) => {
const { code, language, description, name } = data
const id = snippet.id
try {
await fetch("/api/updateSnippet", {
method: "PUT",
body: JSON.stringify({ code, language, description, name, mail:email }),
headers: {
"Content-Type": "application/json",
},
})
router.push("/")
}
catch (error) {
console.log(error)
}
}
Agora podemos continuar fazendo com que os botões Editar e Excluir sejam exibidos apenas se o e-mail corresponder.
Primeiro, passamos os user.mail
como adereços para o Snippet
componente em index.js
:
<Snippets
key={snippet.id}
snippet={snippet}
snippetDeleted={mutate}
+ email={session.user.email}
/>
Então em Snippet.js
:
function Snippets({ snippet, snippetDeleted, email }) {
...
{email == snippet.data.mail && (
<>
<div className={styles.links}>
<Link href={`/edit/${snippet.id}`}>
<a>Edit</a>
</Link>
<a onClick={deleteSnippet}>Delete</a>
</div>
</>
)}
...
}
Execute npm run dev
na CLI e abra o aplicativo em seu navegador. Agora, se você criar um novo snippet, o e-mail do usuário será adicionado ao banco de dados. Se o e-mail não corresponder, os botões Editar e Excluir não serão exibidos na página de exibição do snippet. Você pode testar isso fazendo login com um endereço de e-mail diferente do usado para criar os trechos de código.
Finalmente chegamos ao final deste tutorial. Aprendemos como construir um aplicativo CRUD com Next.js e FaunaDB e como realizar operações CRUD com base na autenticação do usuário.
fonte: https://www.sitepoint.com/nextjs-faunadb-build-code-snippet-app/
1643530053
Durante a programação, os desenvolvedores encontram problemas que exigem a reutilização de código, levando a uma programação repetitiva que pode desperdiçar tempo e reduzir a produtividade. Isso dá origem à necessidade de código-fonte reutilizável chamado “snippets de código”. Esses trechos evitam código repetitivo durante a programação, podem ser salvos para uso futuro e são compartilháveis.
Neste tutorial, criaremos um site para ajudar os usuários a salvar trechos de código diários usando a estrutura de desenvolvimento da Web Next.js e alimentado pelo banco de dados Fauna para lidar com o armazenamento, manipulação e exibição de trechos de código. Ao trabalhar neste projeto divertido, também aprenderemos como criar um aplicativo CRUD básico com Next.js e FaunaDB que também pode ser usado para construir outros projetos semelhantes.
Uma versão funcional deste projeto pode ser encontrada no GitHub . Para acompanhar, você precisará do Node instalado em sua máquina , bem como uma conta FaunaDB e uma conta Google (para autenticação).
Nesta seção, veremos como instalar o Next.js usando o npx create-next-app
comando. Isso inicializará a Next CLI e criará um novo aplicativo Next.js.
Também instalaremos as dependências que usaremos para o back-end — FaunaDB e SWR — por meio da linha de comando. SWR (state-while-revalidate) é um gancho Next.js para buscar dados. Entraremos em detalhes mais adiante neste tutorial.
Para instalar o Next.js, digite o seguinte comando na CLI:
npx create-next-app snippetapp
O comando acima cria um diretório de projeto chamado snippetapp
com o modelo inicial Next.js, que contém os arquivos necessários para codificação com Next. Quando o Next terminar a instalação, mude para o diretório recém-criado:
cd snippetapp
Para instalar o Fauna, usaremos o seguinte comando na CLI:
npm install --save faunadb
Em seguida, para instalar o SWR:
npm install swr@0.3.8
Com isso, instalamos todas as dependências que usaremos para construir nossa aplicação e agora podemos prosseguir para configurar nosso banco de dados no Fauna.
O FaunaDB é um banco de dados em tempo real sem servidor. Ele transforma um banco de dados tradicional em uma API de dados flexível que ainda retém os recursos de um banco de dados e seu desempenho, ao mesmo tempo em que oferece acesso seguro e escalável aos dados do aplicativo.
Aqui, criaremos uma conta de usuário e configuraremos o banco de dados para armazenar os dados que usaremos em nosso aplicativo de snippet.
Para criar uma conta de usuário, navegue até a página de inscrição do Fauna e crie uma conta.
Depois de criar uma conta de usuário, você será redirecionado para o painel.
Aqui, criaremos um banco de dados com as coleções necessárias para gerenciar os trechos de código do nosso aplicativo. Clique em CRIAR BANCO DE DADOS . Vamos criar um banco de dados chamado snippets
.
Na nova página que se abre, clique em NOVA COLEÇÃO e crie uma coleção chamada codesnippet
.
Depois de criar uma coleção, obtemos uma página onde podemos criar um documento.
Aqui, você clicará em NOVO DOCUMENTO . Um documento JSON será aberto, onde você poderá inserir os detalhes, conforme ilustrado abaixo.
{
name: "Prompt User",
description: "prompts the user",
language: "javascript",
code: "prompt('would you like to continue')"
}
Aqui, definimos um snippet com atributos: name
, description
, language
e code
. Clique em SALVAR para salvar a nova coleção. Adicionamos com sucesso um snippet ao nosso banco de dados. Agora podemos prosseguir para obter nossas credenciais de acesso para usar em nosso aplicativo.
No painel, clique em Segurança . Isso abre uma nova página para criar nossa chave de segurança.
Aqui, definiremos a função como “servidor” em vez de “admin”, e você pode dar um nome à chave. Clique no botão SAVE para gerar sua chave.
.env
arquivoAgora vamos criar um .env
arquivo dentro do diretório do nosso projeto. Este arquivo armazenará nossa chave secreta gerada. No .env
arquivo temos isso:
FAUNA_SECRET = paste your key here
Nesta seção, criaremos a página de exibição e upload para os snippets e também adicionaremos funcionalidades a ela.
Abra o diretório do projeto em seu editor de código e navegue até o index.js
arquivo na pasta de páginas. Aqui vamos limpar o código e começar a construir nosso aplicativo:
import Head from "next/head"
import Image from "next/image"
import styles from "../styles/Home.module.css"
export default function Home() {
return (
<div className={styles.container}>
<Head>
<title>View Snippet</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<h1 className={styles.title}>Re-usuable Code Snippets</h1>
<p className={styles.info}>Add your code snippets here...</p>
<button>Create new snippet</button>
</main>
</div>
)
}
Agora vamos criar um arquivo componente que irá renderizar nossos trechos. Crie uma pasta nomeada component
em seu diretório de trabalho e crie um arquivo nomeado Snippets.js
dentro dela com o seguinte código:
import React from "react"
import styles from "../styles/Home.module.css"
function Snippets() {
return (
<div className={styles.cont}>
<p className={styles.lang}>language</p>
<h3 className={styles.name}>name of snippet</h3>
<p className={styles.descp}>description of snippet</p>
{/* Code will be displayed here*/}
<div className={styles.links}>
<a>Edit</a>
<a>Delete</a>
</div>
</div>
)
}
export default Snippets
Agora adicionaremos importações para este arquivo em nosso index.js
:
import Snippets from "../components/Snippets"
E use-o em nosso aplicativo:
<button>Create new snippet</button>
<Snippets/>
Agora podemos estilizar nossa página. Navegue até o Home.module.css
arquivo na styles
pasta e substitua os estilos pelo seguinte:
.container{
display: flex;
height: 100%;
min-height: 100vh;
background: rgb(48, 48, 255);
flex-direction: column;
align-items: center;
color: #fff;
font-family: Montserrat;
}
.cont{
color: #333;
margin-top: 5px;
background: rgb(214, 214, 214);
border-radius: 15px;
padding: 10px 15px;
}
.main button{
width: fit-content;
flex-grow: unset;
display: inline-block;
padding: 5px 10px;
outline: none;
border: none;
border-radius: 5%;
font-weight: bold;
color: rgb(48, 48, 255);
}
.main button:hover{
cursor: pointer;
}
.links{
margin-top: 10px;
}
.links a{
margin-left: 5px;
}
.links a:hover{
cursor: pointer;
}
Neste ponto, você deve ser capaz de iniciar o servidor dev com npm run dev
, visite http://localhost:3000 e veja o esqueleto do nosso aplicativo.
Em seguida, criaremos a seção de exibição para o código do snippet. Crie um novo arquivo chamado Code.js
na pasta de componentes e importe-o para Snippets.js
:
import React from 'react'
import styles from '../styles/Home.module.css'
import Code from "./Code";
function Snippets() {
return (
<div className={styles.cont}>
<p className={styles.lang}>language</p>
<h3 className={styles.name}>name of snippet</h3>
<p className={styles.descp}>description of snippet</p>
{/* Code will be displayed here*/}
<Code />
<div className={styles.links}>
<a>Edit</a>
<a>Delete</a>
</div>
</div>
)
}
export default Snippets
Para o realce de sintaxe do código, usaremos dois pacotes, a saber, react-syntax-highlighter e react-copy-to-clipboard . Podemos fazer o download através da CLI:
npm install react-syntax-highlighter react-copy-to-clipboard --save
Então em Code.js
:
import React from "react"
import { PrismLight as SyntaxHighlighter } from "react-syntax-highlighter"
import {atomDark} from "react-syntax-highlighter/dist/cjs/styles/prism"
import { CopyToClipboard } from "react-copy-to-clipboard"
import styles from "../styles/Home.module.css"
function Code() {
const codeString = "npm install import react from 'react'"
const [show, setshow] = React.useState(false)
return (
<div>
<button onClick={() => setshow(!show)}>Show Code</button>
{show ? (
<div>
<CopyToClipboard text={codeString}>
<button className={styles.btn}>Copy</button>
</CopyToClipboard>
<SyntaxHighlighter language="javascript" style={atomDark}>
{codeString}
</SyntaxHighlighter>
</div>
) : null}
</div>
)
}
export default Code
Aqui, criamos um componente para exibir código com realce de sintaxe. Também adicionamos a funcionalidade de cópia e alternância de exibição. Agora no styles
arquivo:
.btn{
left: 80%;
position: relative;
}
Para visualizar essa alteração, você pode executar npm run dev
na linha de comando e visualizá-la em seu navegador. Temos a string “npm install import react from 'react'” exibida com destaque de sintaxe como um bloco de código. Há também um botão para ocultar e exibir o trecho de código e um botão que nos permite copiar o código do bloco de código.
Nesta seção, buscaremos dados do nosso banco de dados FaunaDB para nosso aplicativo. Crie um arquivo chamado Fauna.js
no diretório do seu projeto:
const faunadb = require("faunadb")
const faunaClient = new faunadb.Client({
secret: process.env.FAUNA_SECRET
})
const q = faunadb.query
const getResponse = async () => {
const { data } = await faunaClient.query(
q.Map(
q.Paginate(q.Documents(q.Collection("codesnippet"))),
q.Lambda("doc", q.Get(q.Var("doc")))
)
)
const snippets = data.map((snippet) => {
snippet.id = snippet.ref.id
delete snippet.ref
return snippet
})
return snippets
}
module.exports = {
getResponse,
}
Here, we’ve initialized FaunaDB with our secret key. We’ve also set up an async
request to query our collection and return the data. We’ve stored the returned data in a variable named snippets
, and deleted the ref to better structure the data. Other functionalities for creating, updating and deleting snippets will be added later in this tutorial.
Note that, if you’re getting an unauthorized error in the console, you may need to specify the domain name of the target endpoint. The default is db.fauna.com
, but since the introduction of Region Groups, three cloud domains are available. Use the correct domain for your database’s Region Group:
db.fauna.com
db.us.fauna.com
db.eu.fauna.com
Example code:
const faunaClient = new faunadb.Client({
secret: process.env.FAUNA_SECRET,
domain: "db.eu.fauna.com"
})
Também criaremos um arquivo para lidar com nossa solicitação de API para nosso banco de dados. Dentro da api
pasta em pages
, crie um arquivo chamado snippets.js
com o seguinte código:
import { getResponse } from "../../Fauna.js"
export default async function handler(req, res) {
console.log(req)
if (req.method !== "GET") {
return res.status(405)
}
try {
const snippets = await getResponse()
return res.status(200).json(snippets)
} catch (err) {
console.log(err)
res.status(500).json({ msg: "Something went wrong." })
}
}
Acima, simplesmente configuramos uma função para lidar com solicitações do nosso banco de dados. Os snippets são retornados como Json
e registrarão erros se ocorrerem. No Next.js, qualquer arquivo armazenado na api
pasta é tratado como endpoints de API em vez de uma página e é renderizado no lado do servidor.
Como dito anteriormente, SWR (state-while-revalidate) é um gancho Next.js para buscar dados. É uma solução perfeita para buscar dados atualizados com frequência e é uma boa opção para o nosso aplicativo.
Usaremos isso para buscar dados do FaunaDB. Para usar isso, precisamos importá-lo para index.js
:
import useSWR from "swr"
export default function Home() {
const { data:snippets, mutate }=useSWR("api/snippets")
...
})
Aqui, importamos o SWR e o usamos para buscar dados conforme configurado em snippets.js
. Em seguida, armazenamos esses trechos na snippets
variável e os produziremos a partir daí. Agora vamos passar o snippets
para o nosso Snippets
componente para exibir:
- <Snippets />
+ {snippets &&
+ snippets.map((snippet) => (
+ <Snippets
+ key={snippet.id}
+ snippet={snippet}
+ snippetDeleted={mutate}
+ />
+ ))
+ }
Acima, passamos a chave e o snippet para Snippets
. Também configuramos uma mutate
propriedade para atualizar (re-buscar) snippets quando um snippet é excluído. Para usar os dados passados, modificamos o Snippets
componente com o seguinte:
function Snippets({snippet}) {
return (
<div className={styles.cont}>
<p className={styles.lang}>{snippet.data.language}</p>
<h3 className={styles.name}>{snippet.data.name}</h3>
<p className={styles.descp}>{snippet.data.description}</p>
<Code snippet={snippet}/>
<div className={styles.links}>
<a>Edit</a>
<a>Delete</a>
</div>
</div>
)
}
Acima, inserimos o idioma do snippet, nome e descrição recebidos do FaunaDB em nosso código. Para obter o código da Fauna em nosso aplicativo, também tivemos que passar o snippet prop para o Code
componente.
Então no Code
componente:
function Code({snippet}){
...
<div>
<CopyToClipboard text={snippet.data.code}>
<button className={styles.btn}>Copy</button>
</CopyToClipboard>
<SyntaxHighlighter language="javascript" style={atomDark}>
{snippet.data.code}
</SyntaxHighlighter>
</div>
...
}
Agora terminamos com a GetSnippet
funcionalidade. Se retornarmos ao FaunaDB e criarmos um novo snippet, veremos o que está ilustrado abaixo.
{
"name": "console.log()",
"language": "javascript",
"description": "logs out data",
"code": "console.log('Hello, world!')"'
}
Para executar na CLI:
npm run dev
Se você abrir a página em seu navegador, terá um resultado semelhante à imagem abaixo.
Criamos com sucesso uma página de exibição de snippet com funcionalidades para mostrar e ocultar o código e copiar o snippet de código.
Precisaremos criar um link para a página de upload do nosso componente inicial. O Next.js tem provisões que facilitam o roteamento sem que você precise instalar react-router
e outras dependências, como faria se estivesse usando o código React nativo.
Em index.js
, importaremos o Link
módulo de next
:
import Link from "next/link"
Em seguida, adicione-o ao nosso botão Criar novo trecho :
- <button>Create new snippet</button>
+ <Link href="/upload">
+ <button>Create new snippet</button>
+ </Link>
Vamos criar uma nova página em nossa pages
pasta e nomeá-la upload.js
.
De volta ao nosso Fauna.js
arquivo, vamos criar e também exportar uma função para criar snippets em nosso aplicativo:
const createSnippet = async (code, language, description, name) => {
return await faunaClient.query(q.Create(q.Collection("codesnippet"), {
data:{code, language, description, name}
}))
}
module.exports = {
getResponse,
createSnippet,
}
Aqui, criamos a função createSnippet
, que receberá alguns parâmetros e os passará como dados no novo documento que será criado no banco de dados.
Também configuraremos nosso endpoint para criar snippets. Crie um novo arquivo chamado createSnippet.js
na api
pasta e preencha-o com o seguinte código:
import { createSnippet } from "../../Fauna"
export default async function handler(req, res) {
const { code, language, description, name } = req.body
if (req.method !== "POST") {
return res.status(405).json({msg:"unauthorized"})
}
try {
const createdSnippet = await createSnippet(code, language, description, name)
return res.status(200).json(createdSnippet)
} catch (error) {
console.log(error)
res.status(500).json({msg:"unauthorized"})
}
}
Agora vamos criar a página de upload em nosso upload.js
arquivo. Para nosso formulário criar snippets, usaremos o react-hook-form . Vamos instalar isso via CLI:
npm install react-hook-form
Então, em nosso upload.js
arquivo:
import React from "react"
import { useForm } from "react-hook-form"
import { useRouter } from "next/router"
import style from "../styles/form.module.css"
import { Link } from "next/link"
function upload({ snippet }) {
const { register, handleSubmit, errors, reset } = useForm()
const router = useRouter()
const createSnippet = async (data) => {
const { code, language, description, name } = data
console.log(data)
try {
// code here to push to Fauna
} catch (error) {
console.log(error)
}
}
return (
<div className={style.cont}>
<form
className={style.form}
onSubmit={handleSubmit(snippet ? updateSnippet : createSnippet)}
>
<div>
<label htmlFor="name">Name</label>
<input
className={style.input}
type="text"
id="name"
{...register("name", { required: true })}
/>
</div>
<div>
<label className={style.label} htmlFor="language">
language
</label>
<select
className={style.select}
type="text"
id="language"
{...register("language", { required: true })}
>
<option>Javascript</option>
<option>Html</option>
<option>CSS</option>
</select>
</div>
<div>
<label className={style.label} htmlFor="description">
description
</label>
<textarea
className={style.input}
rows={7}
type="text"
id="description"
placeholder="snippet description"
{...register("description", { required: true })}
/>
</div>
<div>
<label className={style.label} htmlFor="code">
Code
</label>
<textarea
className={style.input}
rows={8}
columns={8}
type="text"
id="code"
{...register("code", { required: true })}
placeholder="background: none;"
/>
</div>
<div>
<button className={style.button}>Submit</button>
<button className={style.button}>Cancel</button>
</div>
</form>
</div>
)
}
export default upload
Acima, criamos nosso formulário usando o react-hook-form
pacote. Usamos a handleSubmit
função useForm()
no operador ternário. Ao enviar o formulário, determina se o envio realizado é para criar ou atualizar um trecho existente. Com register
, adicionamos a propriedade obrigatória a todos os campos do nosso formulário. Também adicionamos importações para uma folha de estilo chamada form.module.css
onde temos os seguintes estilos para nosso formulário:
.form {
max-width: 800px;
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
}
.cont{
background: rgb(48, 48, 255);
height: 100%;
min-height: 100vh;
padding: 10px 0 0 0;
display: flex;
justify-content: center;
align-items: center;
}
.select,
.input {
display: block;
box-sizing: border-box;
width: 100%;
border-radius: 4px;
border: 1px solid black;
padding: 10px 15px;
margin-bottom: 15px;
font-size: 14px;
}
.label{
line-height: 2;
text-align: left;
display: block;
margin: 5px;
color: white;
font-size: 14px;
font-weight: 200;
}
.button {
background : #fff;
color: #444;
border: none;
border-radius: 5%;
margin-right: 8px;
}
Para enviar os dados do nosso formulário para o banco de dados FaunaDB, adicione o seguinte código ao try...catch
bloco na createSnippet
função em upload.js
:
try {
await fetch("/api/createSnippet", {
method: "POST",
body: JSON.stringify({ code, language, description, name }),
headers: {
"Content-type": "application/json"
},
})
router.push("/")
} catch (error) {
console.log(error)
}
Execute o código e navegue até a página de upload. Agora, se adicionarmos um novo snippet ao formulário e clicarmos em Submit , veremos o que está na foto abaixo.
Quando navegamos para nosso componente inicial, podemos ver o trecho criado.
Para criar nossa funcionalidade de edição de trechos, de volta ao Fauna.js
arquivo, criaremos e exportaremos uma função para lidar com esta tarefa:
const updateSnippet = async (id, code, language, description, name) => {
return await faunaClient.query(q.Update(q.Ref(q.Collection("codesnippet"), id), {
data: {code, language, name, description},
}))
}
module.exports = {
...
updateSnippet,
}
Essa função é semelhante à createSnippet
função, mas também recebe um parâmetro de id
. Ele usa esse ID para identificar quais snippets devem ser editados. Se id
corresponder, atualizamos os dados com os outros parâmetros. Também criaremos um arquivo de endpoint no api
diretório chamado updateSnippet.js
para lidar com as atualizações:
import { updateSnippet } from "../../Fauna"
export default async function handler(req, res) {
const { id, code, language, description, name } = req.body
if (req.method !== "PUT") {
return res.status(405).json({ msg: "unauthorized" })
}
try {
const updated = await updateSnippet(
id,
code,
language,
description,
name
)
return res.status(200).json(updated)
}
catch (error) {
console.log(error)
res.status(500).json({ msg: "unauthorized" })
}
}
Agora, vá para o Snippets
componente e modifique este componente para fazer uso desta função. Primeiro, vamos importar o Link
módulo:
...
import Link from "next/link"
Também modificamos nosso edit
botão:
- <a>Edit</a>
+ <Link href={`/edit/${snippet.id}`}>
+ <a>Edit</a>
+ </Link>
Quando clicado, ele envia uma solicitação para a página edit
com o id
snippet selecionado. Na pages
pasta, crie uma pasta nomeada edit
com um arquivo [id].js
dentro dela:
import { getSnippetById } from "../../Fauna"
import Upload from "../upload"
export default function Home({ snippet }) {
const email = ""
const user = ""
return (
<div>
<h3>Update a snippet</h3>
<Upload snippet={snippet} email={email} user={user}/>
</div>
)
}
export async function getServerSideProps(context) {
try {
//get and update record
const id = context.params.id
}
catch (error) {
console.log(error)
context.res.statusCode = 302
context.res.setHeader("Location", "/")
return {props: {}}
}
}
Em [id].js
, estamos passando o snippet de código props
para a página de upload do snippet. No entanto, desta vez, a página de upload conterá os dados armazenados no snippet de código referenciado pelo id
. Para buscar o snippet por ID, precisaremos criar a getSnippetById
função no Fauna.js
arquivo:
const getSnippetById = async (id) => {
const snippet = await faunaClient.query(q.Get(q.Ref(q.Collection("codesnippet"),id)))
snippet.id = snippet.ref.id
delete snippet.ref
return snippet
}
module.exports = {
getResponse,
createSnippet,
updateSnippet,
getSnippetById,
}
À medida que exportamos a função, de volta ao [id].js
arquivo, podemos usá-la para buscar um trecho específico com seu ID:
try {
const id = context.params.id;
const snippet = await getSnippetById(id);
return {
props: { snippet },
};
} catch (error) {
// as before
}
Agora, no upload.js
arquivo, vamos modificá-lo para poder acessar os dados armazenados se um snippet for editado:
- const { register, handleSubmit, errors, reset } = useForm()
+ const { register, handleSubmit, errors, reset } = useForm({
+ defaultValues: {
+ code: snippet ? snippet.data.code : "",
+ language: snippet ? snippet.data.language : "",
+ description: snippet ? snippet.data.description : "",
+ name: snippet ? snippet.data.name : "",
+ }
+ })
O código acima verifica se o snippet tem dados armazenados nele. Se retornar true, retorna os dados para os parâmetros: code
, language
, description
e code
. Se ele retornar false
, ele retornará uma string vazia.
Em seguida, criaremos uma função para atualizar o snippet de código:
const createSnippet = async (data) => { ... }
const updateSnippet = async (data) => {
const { code, language, description, name } = data
const id = snippet.id
try {
await fetch("/api/updateSnippet", {
method: "PUT",
body: JSON.stringify({ code, language, description, name, id }),
headers: {
"Content-Type": "application/json",
},
})
router.push("/")
}
catch (error) {
console.log(error)
}
}
return ( ,,, )
Se executarmos nosso código, podemos editar os trechos de código criados anteriormente clicando no botão Editar , alterando os dados do formulário e clicando em Enviar .
Agora, se retornarmos ao Home
componente em nosso navegador, poderemos editar e atualizar trechos de código. Podemos finalmente adicionar a funcionalidade final para excluir nosso trecho de código. Crie e exporte uma nova função — deleteSnippet
— no Fauna.js
arquivo:
const deleteSnippet = async (id) => {
return await faunaClient.query(q.Delete(q.Ref(q.Collection("codesnippet"),id)))
}
module.exports = {
...
deleteSnippet,
}
Vamos criar outro endpoint para esta função em nossa api
pasta chamada deleteSnippet.js
e preenchê-la com o seguinte código:
import { deleteSnippet } from "../../Fauna"
export default async function handler(req, res) {
if (req.method !== "DELETE") {
return res.status(405).json({ msg: "unauthorized" })
}
const { id } = req.body
try {
const deleted = await deleteSnippet(id)
return res.status(200).json(deleted)
}
catch (error) {
console.log(error)
res.status(500).join({ msg: "error occured" })
}
}
Em seguida, modificamos o Snippets.js
arquivo para adicionar a nova funcionalidade:
function Snippets({ snippet, snippetDeleted }) {
...
}
Em seguida, crie uma deleteSnippet
função para buscar o endpoint do api
e exclua o snippet referenciado pelo ID:
function Snippets({snippet, snippetDeleted}) {
const deleteSnippet = async () => {
try {
await fetch("/api/deleteSnippet", {
method: "DELETE",
body: JSON.stringify({ id: snippet.id }),
headers: {
"Content-Type": "application/json",
},
});
snippetDeleted();
} catch (e) {
console.log(e);
}
};
return (
<div className={styles.cont}>
<p className={styles.lang}>{snippet.data.language}</p>
<h3 className={styles.name}>{snippet.data.name}</h3>
<p className={styles.descp}>{snippet.data.description}</p>
<Code snippet={snippet}/>
<div className={styles.links}>
<Link href={`/edit/${snippet.id}`}>
<a>Edit</a>
</Link>
<a onClick={deleteSnippet}>Delete</a>
</div>
</div>
)
}
Também atualizamos o elemento âncora para chamar a deleteSnippet
função quando ela é clicada.
Adicionamos a funcionalidade para excluir trechos de código. Agora podemos excluir trechos clicando no botão Excluir em nosso aplicativo.
Isso conclui as funcionalidades do aplicativo snippet. Vamos agora adicionar medidas de autenticação ao nosso aplicativo para permitir que apenas usuários autorizados criem ou modifiquem snippets em nosso aplicativo.
Por que precisamos de autenticação? Atualmente, os usuários podem criar snippets, mas também podem excluir e modificar snippets que não criaram. Precisaremos fornecer um meio para autorizar os usuários a acessar nosso site - e, portanto, a necessidade de autenticação do usuário.
Instalaremos o next-auth para autenticação por meio de nossa CLI:
npm i next-auth
Usaremos um token JWT para nossa autenticação. JWT é um padrão usado para criar tokens de acesso para um aplicativo.
Crie uma pasta nomeada auth
em sua api
pasta e dentro dela, crie um arquivo [...nextauth].js
com o seguinte código:
import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google"
export default NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth?prompt=consent&access_type=offline&response_type=code",
})
],
jwt: {
encryption: true
},
secret: process.env.secret,
callbacks: {
async jwt(token, account) {
if (account ?.accessToken) {
token.accessToken = account.accessToken
}
return token
},
redirect: async (url, _baseUrl)=>{
if (url === "/profile") {
return Promise.resolve("/")
}
return Promise.resolve("/")
}
}
})
Depois disso, encerraremos nossos componentes no _app.js
arquivo:
import '../styles/globals.css'
import {Provider} from "next-auth/client"
function MyApp({ Component, pageProps }) {
return (
<Provider session={pageProps.session}>
<Component {...pageProps} />
</Provider>
)
}
export default MyApp
Vamos modificar nosso Home
componente index.js
para retornar ao nosso componente se o usuário for autenticado, caso contrário ele retornará um botão que leva à página de autenticação:
import {signIn, signOut, useSession} from "next-auth/client"
...
Então dentro de Home
:
export default function Home() {
const { data:snippets, mutate }=useSWR("api/snippets")
const [session, loadingSession] = useSession()
if (loadingSession) {
<>
<p>...authenticating</p>
</>
}
...
}
O código acima verifica se o aplicativo é loadingSession
. Se true, ele retorna o p
bloco de tags, caso contrário, retorna o restante do nosso aplicativo se houver session
. Em seguida, renderizaremos o “log in” se não houver sessão:
return (
<div className={styles.container}>
<Head> ... </Head>
{!session && (
<>
<h1>Sign in to access snippet app</h1>
<button onClick={() => signIn()}>Sign In</button>
</>
)}
{session && (
<>
<main className={styles.main}>
<h3>welcome {session.user.email}</h3>
<button onClick={() => signOut()}>Sign Out</button>
...
</main>
</>
)}
</div>
)
Para fazer uso do “serviço de login do Google”, precisamos de credenciais de acesso do console de nuvem do Google. Para obter isso, faça login na sua conta do Google e navegue até o console do Google Cloud . Clique em CRIAR PROJETO na página, digite o nome do seu projeto e clique em Criar .
Na nova página que se abre, clique em + CRIAR CREDENCIAIS na barra de menu superior e, finalmente, selecione ID do cliente OAuth no menu suspenso.
Na página que se abre, você receberá uma notificação com um botão solicitando “Configurar a tela de consentimento”. Clique neste botão.
Na próxima página, selecione Externo no tipo de usuário e clique em Criar . Digite os campos obrigatórios para seu “Nome do aplicativo” e “E-mail” e clique em Salvar e continuar .
Nas seções Scopes and Test users , role para baixo e clique em Save and Continue .
Por fim, clique em Voltar ao painel e clique no botão Publicar .
Agora, podemos criar nossa chave clicando em Credenciais no menu lateral e, em seguida, Criar Credenciais na barra de menu superior. Selecione Oauth Client ID na lista suspensa e você verá uma página solicitando o tipo de aplicativo.
Selecione Aplicativo da Web e, em “Origens de JavaScript autorizadas”, clique em Adicionar URI e insira http://localhost
. Por fim, em “URIs de redirecionamento autorizados”, clique em Adicionar URI e digite http://localhost/api/auth/callback/google
no campo, antes de clicar em Criar .
Copie o ID do cliente e o segredo do cliente do pop-up que é aberto e adicione-os ao .env
arquivo:
GOOGLE_CLIENT_ID=id
GOOGLE_CLIENT_SECRET=secret
Agora podemos fazer login usando a autenticação do Google em nosso aplicativo. Além disso, configuraremos nosso upload.js
arquivo como uma rota protegida para que usuários não autorizados não possam criar novos snippets:
import { getSession } from "next-auth/client"
function Upload({ snippet, user }) { ... }
export async function getServerSideProps(context) {
const session = await getSession(context)
if (!session) {
context.res.writeHead(302, { Location: "/" })
context.res.end()
return {}
}
return {
props: {
user: session.user,
}
}
}
export default Upload;
Se executarmos nosso aplicativo com o npm run dev
comando, primeiro obteremos uma página solicitando que façamos login. Não podemos navegar para a página de upload pelo /upload
caminho em nosso URL. Só podemos acessar nosso aplicativo quando usamos o recurso de login do Google para fazer login em nosso aplicativo.
Por fim, modificaremos a createSnippet
funcionalidade para adicionar o e-mail do usuário ao banco de dados e só mostraremos os botões Editar e Excluir se o e-mail corresponder.
Em Fauna.js
, altere a createSnippet
função assim:
const createSnippet = async (code, language, description, name, mail) => {
return await faunaClient.query(q.Create(q.Collection("codesnippet"), {
data:{code, language, description, name, mail}
}))
}
No createSnippet.js
arquivo, faça as seguintes alterações:
- const { code, language, description, name } = req.body;
+ const { code, language, description, name, mail } = req.body;
- const createdSnippet = await createSnippet(code, language, description, name);
+ const createdSnippet = await createSnippet(code, language, description, name, mail);
Em upload.js
:
function upload({ snippet, user }) {
+ const email = user.email;
...
}
E altere a createSnippet
função e a updateSnippet
função, como segue:
const createSnippet = async (data) => {
const { code, language, description, name, mail } = data;
console.log(data)
try {
await fetch("/api/createSnippet", {
method: "POST",
body: JSON.stringify({ code, language, description, name, mail:email }),
headers: {
"Content-type": "application/json"
},
})
router.push("/")
} catch (error) {
console.log(error)
}
}
const updateSnippet = async (data) => {
const { code, language, description, name } = data
const id = snippet.id
try {
await fetch("/api/updateSnippet", {
method: "PUT",
body: JSON.stringify({ code, language, description, name, mail:email }),
headers: {
"Content-Type": "application/json",
},
})
router.push("/")
}
catch (error) {
console.log(error)
}
}
Agora podemos continuar fazendo com que os botões Editar e Excluir sejam exibidos apenas se o email corresponder.
Primeiro, passamos as user.mail
props para o Snippet
componente em index.js
:
<Snippets
key={snippet.id}
snippet={snippet}
snippetDeleted={mutate}
+ email={session.user.email}
/>
Então em Snippet.js
:
function Snippets({ snippet, snippetDeleted, email }) {
...
{email == snippet.data.mail && (
<>
<div className={styles.links}>
<Link href={`/edit/${snippet.id}`}>
<a>Edit</a>
</Link>
<a onClick={deleteSnippet}>Delete</a>
</div>
</>
)}
...
}
Execute npm run dev
na CLI e abra o aplicativo em seu navegador. Agora, se você criar um novo snippet, o email do usuário será adicionado ao banco de dados. Se o e-mail não corresponder, os botões Editar e Excluir não serão exibidos na página de exibição do snippet. Você pode testar isso fazendo login com um endereço de e-mail diferente daquele usado para criar os snippets de código.
Finalmente chegamos ao fim deste tutorial. Aprendemos como criar um aplicativo CRUD com Next.js e FaunaDB e como realizar operações CRUD com base na autenticação do usuário.
Fonte: https://www.sitepoint.com
1625674200
In this video, we are going to implement Google Analytics to our Next JS application. Tracking page views of an application is very important.
Google analytics will allow us to track analytics information.
Frontend: https://github.com/amitavroy/video-reviews
API: https://github.com/amitavdevzone/video-review-api
App link: https://video-reviews.vercel.app
You can find me on:
Twitter: https://twitter.com/amitavroy7
Discord: https://discord.gg/Em4nuvQk
#next js #js #react js #react #next #google analytics