diff --git a/README.md b/README.md index 96c975f..024ebd4 100644 --- a/README.md +++ b/README.md @@ -32,35 +32,7 @@ http://it2810-03.idi.ntnu.no/project2 ## Architecture -See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for how the frontend, backend and database fit together, and how the code is organised. - -## API and database - -### Plant data: the Perenual API - -The plant data comes from the [Perenual API](https://perenual.com/docs/api), which has information about thousands of plant species, including images, care information and plant traits. - -We do **not** call Perenual from the app while it is running. Instead, the script [backend/seedDataFetch.js](backend/seedDataFetch.js) has fetched the data once, cleans it and stored it in our own MongoDB database on the VM. We have keept the seedDataFetch file so you can see how this has been done. The app only talks to our own GraphQL backend. We chose this approach for three reasons: - -- **Rate limits:** The free tier on the Perenual API allows about 100 requests per day, and only returns details for plants with an ID up to 3000. If the app called Perenual directly, a handful of users would use up the daily limit in minutes. This is therfore a scalable solution for using this API source as the source for our data. -- **Control over the data:** We only keep the fields we use, in the format we want. That keeps responses small, and makes searching, filtering, sorting and pagination something we decide on ourselves instead of something limited by what Perenual supports. -- **Reliability and security:** The app keeps working even if Perenual is down or changes its API, since the data is stored in our own database. - -### Backend: GraphQL with Apollo Server - -The backend exposes a single GraphQL endpoint. With GraphQL, the frontend asks for exactly the fields each view needs. A plant card needs more fields than a list item, and both are served by the same `Plant` type without extra endpoints. The schema also gives us one typed contract between frontend and backend. - -### Database: MongoDB - -We store the plants in MongoDB, a document database. It is a good fit for this data: - -- **The data is already documents.** Perenual returns JSON with nested objects and lists (such as sunlight requirements and scientific names). Each plant becomes one document with almost the same shape, so there is no need to split it into several tables and join them back together. This limits the cleaning necessary. -- **Plants have different fields.** Some plants have information that others are missing. A document database handles optional fields naturally, without a lot of empty columns. -- **It is simple to update.** The seed script first inserts a basic entry for each plant, and later adds details to the same document (an upsert keyed on `perenualId`). - -The trade-off in using a NoSQL databse such as MongoDB commpared to a relational database such as MySQL is that it does not enforce relationships between collections the way a relational database does. That matters little here, because the plant collection is mostly read-only, and user data such as saved matches only needs to refer to a plant by its ID. - -MongoDB runs on the course VM and only listens on `127.0.0.1`, so it cannot be reached directly from the internet. During development, you reach it through an SSH tunnel (see below). +See [architecture.md](docs/architecture.md) for how the frontend, backend and database fit together, and how the code is organised. It also describes the plant data and the Perenual API, why we chose GraphQL and MongoDB, and how the data flows from the API to the frontend. ## Run the project locally @@ -91,14 +63,14 @@ The default values work as they are. `.env` is ignored by git and must never be ### 3. Open an SSH tunnel to the database (in PC terminal window) -Open a **separate terminal**, replace `` with your username and run: +Open a **separate terminal**, replace `` with your username and run the command below. + +After you enter your password, the command looks like it hangs. That means the tunnel is open. Keep the terminal open while you work, and stop the tunnel with `Ctrl+C` when you are done. ```bash ssh -N -L 27017:localhost:27017 @it2810-03.idi.ntnu.no ``` -After you enter your password, the command looks like it hangs. That means the tunnel is open. Keep the terminal open while you work, and stop the tunnel with `Ctrl+C` when you are done. - ### 4. Start the app (in the VSC terminal window) From the repo root: @@ -107,7 +79,7 @@ From the repo root: npm run dev ``` -This starts both the backend (http://localhost:4000) and the frontend. Open http://localhost:5173/project2/ in your browser. The frontend forwards `/graphql` requests to the backend, so both must be running. +This starts both the backend (http://localhost:3001) and the frontend. Open http://localhost:5173/project2/ in your browser. The frontend forwards `/graphql` requests to the backend, so both must be running. See [backend/README.md](backend/README.md) for more details about the backend setup (in Norwegian). @@ -118,7 +90,7 @@ The project has three `package.json` files: one in the repo root, one in `backen | Command | What it does | When to use it | | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | | `npm run install:all` | Runs `npm install` in the root, `backend` and `frontend`. | After cloning, and after pulling changes to any `package.json`. | -| `npm run dev` | Starts the backend (`tsx watch`, port 4000) and the frontend (Vite, port 5173) in the same terminal with [concurrently](https://www.npmjs.com/package/concurrently). Output is prefixed with `[backend]` (blue) and `[frontend]` (green). Both restart or reload automatically when you save a file. Stop both with `Ctrl+C`. | Day-to-day development. The SSH tunnel must be open first. | +| `npm run dev` | Starts the backend (`tsx watch`, port 3001) and the frontend (Vite, port 5173) in the same terminal with [concurrently](https://www.npmjs.com/package/concurrently). Output is prefixed with `[backend]` (blue) and `[frontend]` (green). Both restart or reload automatically when you save a file. Stop both with `Ctrl+C`. | Day-to-day development. The SSH tunnel must be open first. | | `npm run build` | Compiles the backend with `tsc` to `backend/dist`, then type checks and builds the frontend with Vite to `frontend/dist`. Stops at the first error. | To check that everything compiles, and before deploying. | | `npm run lint` | Runs ESLint in `backend`, then in `frontend`. | Before you push or open a pull request. | | `npm test` | Runs the frontend tests once with Vitest and exits. | Before you push or open a pull request. | @@ -139,7 +111,7 @@ You can also run scripts in one part of the project only, either with `cd backen ## Testing -See [frontend/src/tests/README.md](frontend/src/tests/README.md) for what we test, how the tests are organised and how to run them. +See [tests.md](docs/tests.md) for what we test, how the tests are organised and how to run them. To run all tests from the repo root: @@ -151,6 +123,11 @@ Lint, tests and build also run automatically on every pull request through GitHu TODO! The testing coverage is: +## Quality and design + +See [quality.md](docs/quality.md) for our design choices, accessibility considerations, sustainable web development +and work practices. + ## Known issues TODO! diff --git a/backend/package-lock.json b/backend/package-lock.json index 94c1fed..bfcee5b 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1177,7 +1177,6 @@ "integrity": "sha512-oM/5sAqz/l1v/zYK1qTOPrn2+JIrBGC1V6uNYucRO5gh6eT9vWJ/RhPbGE59byvZEUQ4VP6XkLBaS5Jti5jpvw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.71.1", "@typescript-eslint/types": "8.71.1", @@ -1381,7 +1380,6 @@ "integrity": "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1823,7 +1821,6 @@ "integrity": "sha512-npHjrHb2o4ehH3mE+YuxABR+0gyb0aVWCIolgJDArwCiHEIsonBm3ZKjel5b0U5+a+MbEvIyRCGCzD3eKKK2NA==", "dev": true, "license": "MIT", - "peer": true, "workspaces": [ "packages/*" ], @@ -2214,7 +2211,6 @@ "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.2.tgz", "integrity": "sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==", "license": "MIT", - "peer": true, "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -2435,7 +2431,6 @@ "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@keyv/serialize": "^1.1.1" } @@ -2792,7 +2787,6 @@ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -3237,7 +3231,6 @@ "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index e69de29..741e068 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,92 @@ +# Architecture + +This document describes how the data flows from the API to the frontend, how the frontend, backend and database fit together. It also describes the plant data and the Perenual API and why we chose GraphQL and MongoDB. +The folder structure is shown at the end of this document. + +## Data flow + +```text +Perenual API → seedDataFetch.js → MongoDB → Apollo Server (GraphQL) → Frontend +``` + +The plant data is fetched from the Perenual API once and stored in our own database. The frontend never talks to the database directly. All data goes through the GraphQL API in the backend: + +1. The frontend sends a GraphQL request to the backend (for example the `getPlants` query with `limit` and `offset`). +2. Apollo Server runs the matching resolver, which reads from the `plants` collection in MongoDB. +3. The resolver maps the database documents to the `Plant` type (for example `commonName` becomes `name`) and returns only the fields the frontend asked for. +4. The frontend receives the data as JSON and renders it. + +The sections below describe each part in more detail. + +## Frontend: React, TypeScript and Apollo Client + +The frontend is built with React and TypeScript (Vite). + +- **Routing:** Routes are defined in `AppRoutes.tsx` with React Router, and the app is served under the base path `/project2`. +- **Reusable logic in hooks:** Logic without UI is kept in hooks. `useDebounce` delays updates while the user is typing, and `useSessionState` stores state in `sessionStorage`. +- **Internationalisation:** Text is stored in translation files for Norwegian and English (i18next). +- **Shared types and helpers:** `Plant` and `Preference` are defined in `types/`, and helper functions such as `isPreferenceMatch` in `utils/`. + +### Communication with the backend + +The frontend uses Apollo Client, which is set up in `main.tsx` with an in-memory cache. During development, requests to `/graphql` are proxied by Vite to the backend on port 3001 (see [vite.config.ts](../frontend/vite.config.ts)). + +## Backend: GraphQL with Apollo Server + +The backend exposes a single GraphQL endpoint. With GraphQL, the frontend asks for exactly the fields each view needs. A plant card needs more fields than a list item, and both are served by the same `Plant` type without extra endpoints. The schema also gives us one typed contract between frontend and backend. + +- `db.ts` connects to MongoDB using the connection string in `.env` (see `.env.example`). +- `server.ts` defines the schema and resolvers. The `getPlants(limit, offset)` query returns plants with pagination. The resolver maps database documents to the GraphQL type (for example `commonName` becomes `name`), so the database structure is not exposed to the frontend. +- `seedDataFetch.js` fills the database with plant data (see [Data source](#data-source-the-perenual-api) below). + +## Database: MongoDB + +We store the plants in MongoDB, a document database. It is a good fit for this data: + +- **The data is already documents.** Perenual returns JSON with nested objects and lists (such as sunlight requirements and scientific names). Each plant becomes one document with almost the same shape, so there is no need to split it into several tables and join them back together. This limits the cleaning needed. +- **Plants have different fields.** Some plants have information that others lack. A document database handles optional fields naturally, without many empty columns. +- **It is simple to update.** The seed script first inserts a basic entry for each plant, and later adds details to the same document (an upsert keyed on `perenualId`). + +The trade-off in using a NoSQL database such as MongoDB compared to a relational database such as MySQL is that it does not enforce relationships between collections the way a relational database does. That matters little here, because the plant collection is mostly read-only, and user data such as saved matches only needs to refer to a plant by its ID. + +MongoDB runs on the course VM and only listens on `127.0.0.1`, so it cannot be reached directly from the internet. During development, it is reached through an SSH tunnel (see [Run the project locally](../README.md#run-the-project-locally) in the README). + +## Data source: the Perenual API + +The plant data comes from the [Perenual API](https://perenual.com/docs/api), which has information about thousands of plant species, including images, care information and plant traits. + +The app does **not** call Perenual while it is running. Instead, the script [backend/seedDataFetch.js](../backend/seedDataFetch.js) fetched the data once, cleaned it and stored it in our own MongoDB database on the VM. We have kept the seedDataFetch file so you can see how this has been done. The app only talks to our own GraphQL backend. We chose this approach for these reasons: + +- **Rate limits:** The free tier on the Perenual API allows about 100 requests per day, and only returns details for plants with an ID up to 3000. If the app called Perenual directly, a handful of users would use up the daily limit in minutes. By fetching the data once and serving it from our own database, the number of users no longer affects the number of requests to Perenual. +- **Fewer requests:** Because the data is fetched only once, and the raw responses are cached locally so the script never requests the same data twice, we avoid unnecessary network traffic and resource use. +- **Control over the data:** We only keep the fields we use, in the format we want. That keeps responses small, and makes searching, filtering, sorting and pagination something we decide on ourselves, instead of being limited by what Perenual supports. +- **Reliability:** The app keeps working even if Perenual is down or changes its API, since the data is stored in our own database. + +## Folder structure + +```text +T03-Project-2/ +├── .github/ # CI workflow (lint, tests, build) and CODEOWNERS (required PR review) +├── backend/ +│ ├── src/ +│ │ ├── db.ts # MongoDB connection (MongoDB driver, configured via .env) +│ │ └── server.ts # Apollo Server with GraphQL schema and resolvers (getPlants query) +│ ├── tests/ # Backend tests (none written yet) +│ ├── seedDataFetch.js # Script that fetches plant data from the Perenual API and stores it in MongoDB +│ └── .env.example # Template for required environment variables +├── docs/ # Project documentation (architecture, tests, ...) +├── frontend/ +│ ├── src/ +│ │ ├── assets/ # Static images +│ │ ├── components/ # Reusable UI components, each in its own folder with a matching .css file +│ │ ├── hooks/ # Reusable logic without UI (useDebounce, useSessionState) +│ │ ├── i18n/ # Translation setup and locale files (no, en) +│ │ ├── pages/ # One folder per page/route with a matching .css file +│ │ ├── tests/ # Unit and component tests, including test setup (see tests.md) +│ │ ├── types/ # Shared TypeScript types (Plant, Preference) +│ │ ├── utils/ # Helper functions (e.g. matching plant traits against user preferences) +│ │ ├── AppRoutes.tsx # Route definitions +│ │ └── main.tsx # Application entry point +│ └── vite.config.ts # Vite and Vitest configuration, including proxy to the backend +└── README.md # Project overview and how to run the application +``` diff --git a/docs/ai-usage.md b/docs/ai-usage.md new file mode 100644 index 0000000..27bc63f --- /dev/null +++ b/docs/ai-usage.md @@ -0,0 +1,10 @@ +# AI usage + +AI was used as an assistant throughout the development of the web application. It was mainly used for: + +- **Setup and configuration** — setting up the project in VS Code, Vitest, and project structure with backend and database +- **Explaining technical concepts** — how the database, backend and frontend are related, and how to implement debounce +- **Writing and reviewing tests** — drafting test files with mocked dependencies, and explaining the reasoning behind test structure and snapshot testing. AI also came up with suggestions regarding what should be tested to get a better test coverage +- **Writing documentation** — drafting and refining documentation and comments in the code + +All AI-generated suggestions were reviewed, tested, and adapted by the team before being committed. Design and architectural decisions were made by the team. diff --git a/docs/quality.md b/docs/quality.md new file mode 100644 index 0000000..c8ed5ea --- /dev/null +++ b/docs/quality.md @@ -0,0 +1,79 @@ +# Quality + +This document describes how we have worked to ensure the quality of the application through work practices, testing, accessibility, sustainable web development and design choices. + +## Quality aspects + +- Work practices: [Work practices](#work-practices) +- Testing: [Testing](#testing) +- Accessibility: [Accessibility](#accessibility) +- Sustainable web development: [Sustainable web development](#sustainable-web-development) +- Design choices: [Design choices](#design-choices) + +## Work practices + +- Before development began, the group created a prototype in Figma to establish a shared visual understanding of the application and agree on the design before writing code. + +- We followed three rules before merging a pull request: + 1. **At least one member of IT2810-H26-T03 had to approve the pull request.** Members could not approve their own pull requests, and staff approvals did not count towards this requirement. This ensured that team members reviewed each other's code. + 2. **All review comments had to be resolved.** We addressed each comment by making the necessary changes or explaining why no changes were needed, and marked the conversation as resolved. + 3. **New commits after approval required another review.** Pushing new commits dismissed the previous approval, ensuring that the code being merged had been reviewed in its final state. + +- In addition to writing automated tests, we manually tested the application throughout development to identify issues and verify its functionality. + +## Testing + +- Snapshot tests are used to detect unintended changes to the rendered UI. + +- In addition to automated testing, we manually tested the application throughout development as new features were implemented. This allowed us to evaluate usability and verify that the application behaved as intended. + +- Testing was performed on both mobile and desktop devices, leading to adjustments such as improvements to the mobile layout. + +- As part of our code review process, team members checked out pull requests locally and tested the new functionality before approving changes. This helped us verify that changes worked as expected in practice, rather than relying solely on code review. + +For more details about individual test files, see test coverage, and instructions for running the tests, see [Tests](tests.md). + +## Accessibility + +We considered accessibility throughout the design and development of the application. + +- **Semantic HTML and navigation:** We use semantic HTML elements such as `