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
53 changes: 48 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Full Circuit is a web application for exploring PC components and planning a cus

## Project status

The app displays a component catalog backed by PostgreSQL and GraphQL, with search, filtering, sorting, component details, and a load-more button. The frontend loads 40 components per request. The PC builder lets users select parts, view compatibility checks, and publish a build with a name and optional comment. Current selections are saved in the browser, while published builds are stored in the database and shown on the Community page.
The app displays a component catalog backed by PostgreSQL and GraphQL, with search, filtering, sorting, component details, and a load-more button. The frontend loads 40 components per request. The PC builder lets users select parts, view compatibility checks, and publish a build with a name and optional comment. Current selections are saved in the browser. Firebase authenticates users, and PostgreSQL stores their private and published builds. Users can reopen, update, publish, copy, and delete their own builds from My builds. Community lists published builds only.

## Accessibility

Expand Down Expand Up @@ -81,7 +81,7 @@ Files in `backend/`:

- `data/parts.csv` – the dataset that is loaded into the database.
- `data/part_images/` – one image per part, named `<part id>.webp`.
- `db/schema.sql` – creates the `parts` and `builds` tables and the parts indexes.
- `db/schema.sql` – creates the `parts`, `users`, and `builds` tables and their indexes.
- `db/seed.sql` – empties `parts` and loads `parts.csv`.
- `db/seed.sh` – runs `schema.sql` and `seed.sql`.

Expand Down Expand Up @@ -178,7 +178,7 @@ Open the builder through the navigation bar or by visiting `/project2/builder`.
- The summary shows the selected component count, progress, and total price in EUR. Missing prices currently contribute zero to the total, so the displayed sum may be incomplete.
- **Clear build** asks for confirmation before removing all selections.

Selections are stored as JSON under the `pc-builder` key in `localStorage` and restored when the app loads. This preserves the current build across refreshes and page navigation in the same browser and site. Localhost and the deployed site have separate storage. The compatibility panel checks selected parts and shows results such as compatible, incompatible, warning, or unknown. The checks may not cover every possible combination. Users can also publish their builds to the database, but private builds are not synchronized between browsers.
Selections and saved-build metadata are stored as JSON under `pc-builder-v2:<Firebase UID>` (or `pc-builder-v2:guest`) in `localStorage` and restored when the app loads. This preserves the current build across refreshes and page navigation in the same browser and site. Localhost and the deployed site have separate storage. The compatibility panel checks selected parts and shows results such as compatible, incompatible, warning, or unknown. The checks may not cover every possible combination. Signed-in users can save private builds or publish to Community, and retrieve them across sessions and devices through My builds.

### Navigation and shared build state

Expand All @@ -205,6 +205,45 @@ Most navigation uses React Router links or `navigate()`, changing pages without

Use `AddToBuildButton` for actions that add a component to this shared state. The generic `Button` remains available for other click handlers and form submissions.

### Authentication decision and ownership

We evaluated three providers for our React, GraphQL, and existing PostgreSQL architecture:

| Provider | Fit and decision |
| --- | --- |
| [Clerk](https://clerk.com/docs/guides/sessions/manual-jwt-verification) | Provides authentication UI and verifiable session tokens. Suitable, but we chose to keep the small login UI in our own React application. |
| [Firebase Authentication](https://firebase.google.com/docs/auth/admin/verify-id-tokens) | Supports Google and email/password sign-in with ID tokens our GraphQL backend can verify. Selected: it fits our existing backend without moving application data. |
| [Supabase Auth](https://supabase.com/docs/guides/auth/architecture) | Supports token-based authentication and integrates with Supabase PostgreSQL. Suitable, but its integrated database features provide less benefit while we retain our VM database. |

Firebase manages accounts and passwords. The frontend sends an ID token with authenticated GraphQL requests; Firebase Admin verifies its signature, expiry, issuer, and project. Basic token verification uses Google's public signing keys and the public project ID; no service-account private key is needed. We do not perform token-revocation checks or Firebase administrative account operations. Firebase public web configuration is in `frontend/src/lib/firebase.ts`, and the backend project ID is in `backend/server/src/firebase.ts`. Private database credentials remain in the ignored backend `.env`.

The backend finds or creates a PostgreSQL user by the verified Firebase UID. `authenticatedResolvers.ts` integrates ownership operations with the catalog resolvers and per-request part loader from `resolvers.ts`. Both private saves and publishing use the existing per-IP attempt limit. Ownership always comes from this authenticated context. `myBuild` and `myBuilds` return only owned builds; updates and deletion include both the build ID and owner ID in the SQL condition. Community only returns published builds. Email addresses and passwords are not stored in our PostgreSQL user table.

Existing builds are preserved as published with `owner_id = NULL`; users cannot claim, update, or delete them. The VM upgrade was applied separately from `schema.sql`. On another existing database, run the following once (not `seed.sh`):

```sql
BEGIN;
CREATE TABLE IF NOT EXISTS users (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
firebase_uid varchar(128) NOT NULL UNIQUE,
display_name text
);
ALTER TABLE builds
ADD COLUMN IF NOT EXISTS owner_id bigint REFERENCES users(id),
ADD COLUMN IF NOT EXISTS is_published boolean NOT NULL DEFAULT true;
ALTER TABLE builds ALTER COLUMN is_published SET DEFAULT false;
CREATE INDEX IF NOT EXISTS builds_owner_id_idx ON builds(owner_id);
COMMIT;
```

The upgrade's initial `true` default preserves pre-existing public builds; subsequent new builds default to private. If the columns already exist, the upgrade preserves their values. New databases use `schema.sql`, whose default is private from the start.

### Account builds

Sign in using Google or email/password, then use the account icon to open My builds. The PC builder supports **Save privately** and **Publish build**. Private builds appear only in their owner's account; published builds also appear in Community. Each save creates a build until an existing build is opened or saved; subsequent saves update that build. Select **Save as new** in the save dialog to create a separate copy.

Use **Open in builder** on an account build to restore its selected components, name, comment, and visibility. Account and login pages use `/project2/account` and `/project2/login`. Opening a saved build at `/project2/builder?build=<id>` replaces the current browser draft immediately; it does not modify any build in the database until Save or Publish is used. Drafts and the active build ID are stored separately for each account in the browser and survive navigation and refresh. **Clear build** starts a new draft. Saving an opened published build privately removes it from Community. The GraphQL API checks ownership when reading, updating, or deleting account builds. The Delete action asks for confirmation and removes the build from PostgreSQL and Community. If that build is open in the browser, its selected parts remain as a new local draft.

### Published builds

Users can publish their current build from the PC builder by entering a name and an optional comment. Publishing saves the chosen parts in PostgreSQL through GraphQL. Empty builds cannot be published, and the page shows an error if publishing fails.
Expand All @@ -215,8 +254,8 @@ The Community page at `/project2/community` lists published builds with their na

Features we still plan to add:

- A separate page and shareable link for each published build.
- Saving and reopening private builds across devices.
- Additional abuse protection beyond the existing per-IP publishing limit and GraphQL request limits. Authentication alone does not prevent publishing abuse.
- Firebase account deletion is outside the build ownership issue; build deletion is implemented.

## Use of AI

Expand All @@ -234,6 +273,10 @@ npm run test

See [frontend/src/test/README.md](frontend/src/test/README.md) for the test setup and covered scenarios.

Run backend authentication/build authorization tests from `backend/server/` with `npm test`. These execute the actual GraphQL schema and resolvers with isolated database fixtures, covering two user identities, anonymous requests, private reads, owner-only updates/deletion, private/public saving, copies, and ownerless legacy builds. They do not contact the VM, use real Google accounts, or prove the live database configuration. Frontend tests also cover reopening, account separation, draft persistence, and deletion confirmation and failure states.

For a live deployment smoke test, use two accounts: save a private build as A, confirm it is absent from Community and B's My builds, and verify B cannot retrieve/update/delete its ID through authenticated GraphQL. Reopen/update it as A, publish it, confirm it appears publicly, then delete it as A. Keep this live check separate from the automated fixture tests.

The backend has automated tests using Vitest, covering the GraphQL resolvers and build publishing validation. They mock the database, so no running PostgreSQL is needed. Run them from `backend/server/`:

```bash
Expand Down
12 changes: 11 additions & 1 deletion backend/db/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,17 @@ CREATE INDEX IF NOT EXISTS parts_cooler_height_idx
WHERE category = 'CPUCooler';


CREATE TABLE IF NOT EXISTS users (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
firebase_uid varchar(128) NOT NULL UNIQUE,
display_name text
);


/* Saved builds */
CREATE TABLE builds (
owner_id bigint REFERENCES users(id),
is_published boolean NOT NULL DEFAULT false,
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
tittel varchar(50) NOT NULL CHECK (length(trim(tittel)) > 0),
created_at timestamptz NOT NULL DEFAULT now(),
Expand All @@ -69,4 +77,6 @@ CREATE TABLE builds (
psu_id uuid REFERENCES parts(id),
case_id uuid REFERENCES parts(id),
cooler_id uuid REFERENCES parts(id)
);
);
CREATE INDEX IF NOT EXISTS builds_owner_id_idx
ON builds(owner_id);
Loading