diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..2087dd7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = false +insert_final_newline = true diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..b8681cb --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,34 @@ +module.exports = { + parser: "@typescript-eslint/parser", + env: { + browser: true, + es2021: true, + node: true, + }, + // plugins: ["react", "prettier", "@typescript-eslint"], + plugins: ["react", "@typescript-eslint"], + extends: [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:react/recommended", + "plugin:prettier/recommended", + ], + overrides: [], + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + }, + settings: { + react: { + version: "detect", + }, + }, + ignorePatterns: ["node_modules/", "cypress/"], + // Cherry of the Cake + rules: { + "prettier/prettier": 0, + "nonblock-statement-body-position": "off", + // "no-console": "error", + "react/no-unknown-property": ["error", { ignore: ["jsx", "global"] }], + }, +}; diff --git a/.github/workflows/continuous-delivery.yml b/.github/workflows/continuous-delivery.yml new file mode 100644 index 0000000..f4933ad --- /dev/null +++ b/.github/workflows/continuous-delivery.yml @@ -0,0 +1,28 @@ +name: "CD- Continuous Delivery" + +# on: +# push: +# branches: [ main ] + +on: + pull_request: + types: [opened, synchronize] + +env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + ## [Common_CI_Steps] + - uses: actions/checkout@v3 + ## =========================== + - name: "Debug" + run: | + ls -la + - name: "Install Dependencies" + run: "npm install" + - name: "Deploy" + run: "npx vercel --prod --token=${{ secrets.VERCEL_TOKEN }} " diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml new file mode 100644 index 0000000..d995410 --- /dev/null +++ b/.github/workflows/continuous-integration.yml @@ -0,0 +1,35 @@ +name: "CI - Continuous Integration" + +on: + pull_request: + types: [opened, synchronize] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + ## [Common_CI_Steps] + - uses: actions/checkout@v3 + ## =========================== + - name: "Debug" + run: | + ls -la + echo "Second command line!" + - name: "Install Dependencies" + run: "npm install" + - name: "Lint" + run: "npm run lint" + test: + runs-on: ubuntu-22.04 + container: + image: "cypress/browsers:node-20.9.0-chrome-118.0.5993.88-1-ff-118.0.2-edge-118.0.2088.46-1" + options: --user 1001 + steps: + ## [Common_CI_Steps] + - uses: actions/checkout@v3 + + - name: "Install Dependencies" + run: "npm install " + ## =========================== + - name: "Test" + run: "npm run test" diff --git a/.gitignore b/.gitignore index c6bba59..caf544d 100644 --- a/.gitignore +++ b/.gitignore @@ -128,3 +128,4 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* +.vercel diff --git a/.vercelignore b/.vercelignore new file mode 100644 index 0000000..8f4a96f --- /dev/null +++ b/.vercelignore @@ -0,0 +1,5 @@ +.github +.vscode +core +cypress +.env diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..ea9a615 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "EditorConfig.EditorConfig", + "dbaeumer.vscode-eslint", + "github.vscode-github-actions" + ] +} diff --git a/app/api/route.ts b/app/api/route.ts deleted file mode 100644 index 3a81af8..0000000 --- a/app/api/route.ts +++ /dev/null @@ -1,3 +0,0 @@ -export async function GET() { - return new Response(JSON.stringify({ message: 'Hello World!' })); -} \ No newline at end of file diff --git a/app/layout.tsx b/app/layout.tsx deleted file mode 100644 index c7e45af..0000000 --- a/app/layout.tsx +++ /dev/null @@ -1,11 +0,0 @@ -export default function RootLayout({ - children, -}: { - children: React.ReactNode; -}) { - return ( - - {children} - - ) -} \ No newline at end of file diff --git a/app/page.tsx b/app/page.tsx deleted file mode 100644 index 16ff8f1..0000000 --- a/app/page.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function Page() { - return

Hello, Next.js!

-} \ No newline at end of file diff --git a/core/crud.ts b/core/crud.ts new file mode 100644 index 0000000..ecbc8d6 --- /dev/null +++ b/core/crud.ts @@ -0,0 +1,79 @@ +import fs from "fs"; +import { v4 as uuid } from "uuid"; + +// MARK: - CONSTANTS +const DB_FILE_PATH = "./core/db"; + +// MARK: - TYPES +type UUID = string; + +type Todo = { + id: UUID; + date: string; + content: string; + done: boolean; +}; + +// MARK: - CRUD FUNCTIONS +export function create(content: string): Todo { + const todo: Todo = { + id: uuid(), + date: new Date().toISOString(), + content: content, + done: false, + }; + + const todos: Array = [ + ...read(), + todo, + ]; + + // salvar o content no sistema + fs.writeFileSync(DB_FILE_PATH, JSON.stringify({ + todos, + "dogs": [] + }, null, 2)); // Sync -> synchronous + return todo; +} + +export function read(): Array { + const dbString = fs.readFileSync(DB_FILE_PATH, "utf-8"); + const db = JSON.parse(dbString || "{}"); + if (!db.todos) { + return []; + } + return db.todos; +} + +export function update(id: UUID, partialTodo: Partial): Todo { // Partial -> Partial is a built-in type that makes all properties of Todo optional + let updatedTodo; + + const todos = read(); + todos.forEach((currentItem) => { + const isToUpdate = currentItem.id === id; + if(isToUpdate) { + updatedTodo = Object.assign(currentItem, partialTodo); // Object.assign -> built-in function that copies all properties from the second object to the first object + } + }); + + fs.writeFileSync(DB_FILE_PATH, JSON.stringify({ + todos, + }, null, 2)); + + if(!updatedTodo) { + throw new Error("Please provide another ID!") + } + + return updatedTodo; +} + +export function deleteById(id: UUID) { + const todos = read(); + + const todosWithoutDeleted = todos.filter((todo) => { + if(todo.id === id) { return false; } + return true; + }); + + fs.writeFileSync(DB_FILE_PATH, JSON.stringify({ todos: todosWithoutDeleted }, null, 2)); +} diff --git a/core/db b/core/db new file mode 100644 index 0000000..e52d909 --- /dev/null +++ b/core/db @@ -0,0 +1,29 @@ +{ + "todos": [ + { + "id": "30c08ad9-cd30-4e5f-8fb9-b84cb358b9a5", + "date": "2024-04-27T22:31:07.560Z", + "content": "Primeira TODO", + "done": true + }, + { + "id": "a49eb7da-9586-4646-b2d9-c365e84b261d", + "date": "2024-04-27T22:31:07.561Z", + "content": "Terceira TODO com novo content!", + "done": false + }, + { + "id": "4613fcbf-2561-47c8-9021-30cb0a4e226f", + "date": "2024-05-16T01:36:18.008Z", + "content": "Next day task", + "done": true + }, + { + "id": "c733e23e-37f7-4cc0-b765-a3de7a157345", + "date": "2024-05-19T20:32:49.395Z", + "content": "alo alo w brazil", + "done": false + } + ], + "dogs": [] +} diff --git a/cypress.config.ts b/cypress.config.ts new file mode 100644 index 0000000..e1c5581 --- /dev/null +++ b/cypress.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "cypress"; + +export default defineConfig({ + e2e: { + setupNodeEvents() { + // implement node event listeners here + }, + }, +}); diff --git a/cypress/_e2e/1-getting-started/todo.cy.js b/cypress/_e2e/1-getting-started/todo.cy.js new file mode 100644 index 0000000..4768ff9 --- /dev/null +++ b/cypress/_e2e/1-getting-started/todo.cy.js @@ -0,0 +1,143 @@ +/// + +// Welcome to Cypress! +// +// This spec file contains a variety of sample tests +// for a todo list app that are designed to demonstrate +// the power of writing tests in Cypress. +// +// To learn more about how Cypress works and +// what makes it such an awesome testing tool, +// please read our getting started guide: +// https://on.cypress.io/introduction-to-cypress + +describe('example to-do app', () => { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + cy.visit('https://example.cypress.io/todo') + }) + + it('displays two todo items by default', () => { + // We use the `cy.get()` command to get all elements that match the selector. + // Then, we use `should` to assert that there are two matched items, + // which are the two default items. + cy.get('.todo-list li').should('have.length', 2) + + // We can go even further and check that the default todos each contain + // the correct text. We use the `first` and `last` functions + // to get just the first and last matched elements individually, + // and then perform an assertion with `should`. + cy.get('.todo-list li').first().should('have.text', 'Pay electric bill') + cy.get('.todo-list li').last().should('have.text', 'Walk the dog') + }) + + it('can add new todo items', () => { + // We'll store our item text in a variable so we can reuse it + const newItem = 'Feed the cat' + + // Let's get the input element and use the `type` command to + // input our new list item. After typing the content of our item, + // we need to type the enter key as well in order to submit the input. + // This input has a data-test attribute so we'll use that to select the + // element in accordance with best practices: + // https://on.cypress.io/selecting-elements + cy.get('[data-test=new-todo]').type(`${newItem}{enter}`) + + // Now that we've typed our new item, let's check that it actually was added to the list. + // Since it's the newest item, it should exist as the last element in the list. + // In addition, with the two default items, we should have a total of 3 elements in the list. + // Since assertions yield the element that was asserted on, + // we can chain both of these assertions together into a single statement. + cy.get('.todo-list li') + .should('have.length', 3) + .last() + .should('have.text', newItem) + }) + + it('can check off an item as completed', () => { + // In addition to using the `get` command to get an element by selector, + // we can also use the `contains` command to get an element by its contents. + // However, this will yield the