Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 7 additions & 182 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,128 +14,14 @@ In the end we are planning on making the data permanent, and building a better l

For our database we use SQLite with Apollo for the GraphQL Handling, this is a very efficient database structure good for a lot of data. SQLite is in some scenarios not recommended, as it does not have it's own database manager software, using only the program it runs in, but in this context, that is not a problem.

## Sustainability choices
For more information, see:

We have made several choices that help reduce unnecessary data transfer, processing,
and storage:

- **Cursor-based pagination:**
The GraphQL API uses cursors to retrieve Pokémon in
small pages instead of loading the entire dataset at once. Each request is
limited to a maximum of 100 results and returns an `endCursor` and page
indicators, allowing the client to request only the next or previous part of
the dataset. This reduces the amount of data sent and processed for each
interaction, especially when searching or browsing larger datasets.
- **SQLite database:**
SQLite stores the data locally in a single, lightweight
database file. This avoids the overhead of running a separate database server
for this project while still providing structured queries through Apollo
GraphQL.
- **Normalized data structure:**
Pokémon, abilities, types, and ratings are
stored in separate related tables. This reduces duplicated data and makes
updates more efficient, which helps limit storage requirements and
unnecessary database work.

## Local Setup/install

We use node/npm, Bun should also work but is not guaranteed.

To configure commit template run this while in the root of the project (/T26-Project-2)

```bash
git config --local commit.template .gitmessage
```

To setup the rest you can use npm from the project folder:

```bash
cd project2
npm install
```

and to run the project use these commands (must also be ran from the project2 folder):

| Command | Description |
|------------------------|----------------------------------------|
| `npm run dev` | Run the project (Frontend) |
| `npm run dev:server` | Run the project (Server) |
| `npm run dev:all` | Run the project (Everything) |
| `npm run build` | Build reactcode into html/js (For VM) |
| `npm run preview` | Preview the built project from `build` |
| `npm run lint` | Run Eslint on the project |
| `npm run format` | Run Prettier on the project |
| `npm run format:check` | Run Prettier on the project no writing |


## Production Setup/install

Running on the Virtual machine requires the machine to have an ssh key linked to your github account.
Since NTNU git really only is stable over ssh.

To get a key, you can do this:
```bash
ssh-keygen # Enter on every prompt
cat $HOME/.ssh/id_ed25519.pub
```

Copy the resulting public key (the entire line including the ssh-ed25519 part on the start, and the username+machinename on the end)
Add the key to https://git.ntnu.no/settings/keys

Now you can pull the project with these commands (first configuring git to use the key)
```bash
git config core.sshCommand "ssh -i $HOME/.ssh/id_ed25519"
git clone git@git.ntnu.no:IT2810-H26/T26-Project-2.git

cd T26-Project-2/project2

# Installing dependencies
npm install
# Tool to run server, only needed on the VM, so not a project dependency
npm install -g pm2

# Let Apache access the dist folder
chmod 755 ~
chmod -R 755 ~/T26-Project-2/project2/dist/

# Symlinking to apache host directory
# Dist must not have anything not publishable
sudo ln -s $HOME/T26-Project-2/project2/dist/ /var/www/html/project2

# Setup unattended running with pm2
npx pm2 start "npm run prod" --name pokemon-api --cwd ~/T26-Project-2/project2
npx pm2 save
npx pm2 startup

```

**Now the vm should be set up correctly!**

It should also start the program on boot by using pm2, which is a tool meant for running programs like this unatended.

To update to new versions, the only thing needed is to pull and restart pm2.
If dependencies are changed, an npm install might also be needed.

You can either do this manually by doing this:
```bash
cd ~/T26-Project-2/project2
git pull
npm install
npx pm2 restart pokemon-api
```

Or running the script with:
```bash
npm run prod:update
```

### Commands specifically for prod

| Command | Description |
|---------|-------------|
| `npm run prod` | Run prod with correct environment, requires dedicated webserver serving dist |
| `npm run prod:restart` | Restart the pm2 instance, requires VM setup |
| `npm run prod:update` | Pull from github, install new packages if needed, and restart server, requires VM setup |
- [Setup and production deployment](docs/Setup.md)
- [Architecture](docs/Architecture.md)
- [Testing](docs/Testing.md)
- [Performance testing](docs/Performance.md)
- [Sustainability choices](docs/Sustainability.md)
- [AI declaration](docs/AI%20decleration.md)

## Working in this repository

Expand All @@ -157,64 +43,3 @@ Before a pull request can be merged:
3. **Pushing new commits dismisses the approval**, so if you change something
after being approved, ask for another review. This is deliberate: what was
approved is what gets merged.

## Testing

The application is tested in separate layers so that failures are easier to
understand:

- **Component tests** verify one React component at a time. They check the
component's rendered output and user interactions without requiring the
GraphQL server or database to run.
- **API tests** will verify GraphQL requests against the server and its
pagination, search, filtering, and sorting behavior.
- **End-to-end tests** will verify a longer browser interaction through the
frontend and API together.

The project uses Vitest for component tests because it is designed to work with
the existing Vite setup. React Testing Library renders components in a simulated
browser (`jsdom`) and `user-event` performs interactions in the same way a user
would. These dependencies are development-only and do not affect the deployed
application.

The first component suite tests `Pagination`. Pagination is a good starting
point because it has clear responsibilities and several important edge cases:
the first and last pages must disable the appropriate buttons, page navigation
must call the correct callbacks, the displayed count must be correct, and a
changed page size must be reported to the parent component.

The second component suite tests `SearchBar`. It verifies that user input is
reported together with the existing search settings, that type filtering and
sorting are sent to the parent component, that sort direction can be toggled,
and that the settings dropdown closes through normal keyboard and mouse
interactions. These tests are valuable because `SearchBar` does not perform
the filtering itself; it must communicate the complete and correct state back
to `App`.

The third component suite tests `PokemonCard`. It checks that the card presents
the identity and image data supplied by the API, including the padded
Pokédex ID and accessible image alternative text. It also checks that clicking
the card passes the complete Pokémon object to the parent. This keeps the
display and selection behavior reliable without involving the API or the
detail modal.

Run the component tests from the repository root:

```bash
npm test
```

The root test command forwards the request to the `project2` application,
where the Vitest configuration and test dependencies are located. The tests
can also be run directly from that directory with the same command.

```bash
cd project2
npm test
```

For an interactive watch mode while developing:

```bash
npm run test:watch
```
5 changes: 5 additions & 0 deletions docs/AI decleration.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ understand how these parts of React fit together.
AI was also used to help with spelling, formatting, and fact-checking parts of
the sustainability documentation.

We also consulted AI while organizing the documentation. It assisted with the
structure and layout of the Markdown files, as well as the language used to
make the documentation clearer and easier to navigate. The team decided what
information to include and reviewed the final wording.

AI was used as a support tool rather than a replacement for understanding the
code. The team remains responsible for reviewing the suggestions and for the
final implementation and documentation.
Expand Down
145 changes: 145 additions & 0 deletions docs/Architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# Architecture

PokeDox is a React frontend connected to a Node.js GraphQL server. The server
uses SQLite to store Pokémon data and ratings. The frontend is responsible for
the user interface and local browser state, while the backend handles queries,
pagination, filtering, sorting, and database updates.

> **Suggested picture:** Add a screenshot of the main PokeDox page here. It
> would help explain how the search bar, pagination controls, Pokémon cards,
> rating controls, and detail view fit together.
## Frontend

The frontend is built with React, TypeScript, and Vite. `App.tsx` is the main
application component. It keeps track of search input, filters, sorting,
pagination, the selected Pokémon, and ratings.

The interface is divided into smaller components:

- `SearchBar` handles search input, type filters, and sorting options.
- `Pagination` handles moving between result pages.
- `PokemonCard` displays a Pokémon in the result grid.
- `PokemonDetail` displays more information about a selected Pokémon.
- `PokemonRating` handles the rating controls.

The search input is debounced before a new query is sent. The number of cards
per page is also calculated from the available grid space, so the layout can
adapt to different screen sizes.

Apollo Client connects the frontend to GraphQL. It sends queries and mutations
to the configured API URL and keeps an `InMemoryCache` for data that has
already been requested. The client setup is in `src/main.tsx`.

Ratings are stored in the browser's `localStorage` as well as being sent to the
server. A browser-generated voter ID connects a user's local rating state to
the corresponding server records.

## Backend

The backend runs on Node.js and uses Apollo Server to expose a GraphQL API. It
provides queries for retrieving individual Pokémon and paginated lists, as well
as a mutation for saving ratings.

The `pokemons` query supports:

- text search by name;
- filtering by type;
- sorting by Pokémon statistics;
- ascending and descending order;
- cursor-based pagination.

The server limits a request to a maximum of 100 Pokémon. It returns an
`endCursor`, `hasNextPage`, and `hasPreviousPage` so the frontend can keep
track of the current result window without loading the complete dataset.

The GraphQL schema and resolver logic are defined in
`server/schema.ts`. The server startup and seeding flow are handled in
`server/index.ts`.

## Database

SQLite is used through `better-sqlite3`. The database contains four main
tables:

- `pokemons` stores the Pokémon identity and statistics.
- `abilities` stores abilities belonging to a Pokémon.
- `types` stores the types belonging to a Pokémon.
- `ratings` stores a rating for a Pokémon and voter combination.

Foreign keys connect abilities, types, and ratings to the related Pokémon.
Ratings use a composite primary key made from `pokemon_id` and `voter_id`, so
one voter cannot create duplicate rating rows for the same Pokémon.

The database schema and seed function are in `server/db.ts`. On startup, the
server checks whether Pokémon already exist. If the database is empty, it
fetches data from PokeAPI and falls back to the local JSON data if the external
request fails.

> **Suggested diagram:** A database diagram would be useful here. It should
> show `pokemons` at the center, with one-to-many relationships to `abilities`
> and `types`, and the `ratings` table connected through `pokemon_id`. It would
> make the foreign keys and the composite rating key easier to understand than
> a text description alone.
## Frontend-backend communication

The frontend sends GraphQL requests over HTTP. A typical list request follows
this flow:

1. The user searches, filters, sorts, or changes page.
2. React updates the relevant state in `App`.
3. Apollo Client sends the `pokemons` query with the current variables.
4. The resolver validates the page size and sorting options.
5. SQLite returns the matching rows.
6. Nested resolvers add abilities, types, and image URLs.
7. Apollo returns the response to React.
8. React renders the updated Pokémon cards.

When a user submits a rating, the frontend first updates its local rating state
and then sends the `ratePokemon` mutation. If the server request fails, the
browser still retains the local rating and displays an error so the user can
retry.

## Main data flow

```text
User interaction
|
v
React components and App state
|
v
Apollo Client
|
v
GraphQL query or mutation
|
v
Apollo Server resolvers
|
v
SQLite database
|
v
GraphQL response
|
v
Updated React interface
```

The external PokeAPI is used during initial database seeding rather than for
every user search. Once the data is stored locally, normal application queries
use SQLite.

> **Suggested architecture diagram:** If the project needs a broader system
> overview, create a diagram showing the browser, React/Apollo Client,
> Apollo GraphQL server, SQLite database, and PokeAPI. This would complement
> the database diagram rather than replace it.
## Related documentation

- [Setup](Setup.md)
- [Testing](Testing.md)
- [Performance results](Performance.md)
- [Sustainability choices](Sustainability.md)
Loading