# PRD — coreUnit

Evidence date: 2026-10-18.

Source: `git@bitbucket.org:overdrive/coreunit.git`, `master` at `e424b25` (2019-08-05).

There is no prior PRD in the source repository or in Silicon Ark. Requirements below are recovered from `README.md` and the PHP and JavaScript on that revision. Provenance is labelled. The Google Maps key in `frontend/.env` is omitted.

## Product purpose

**Documented.** `README.md` titles the work "Unity" and says the API is Laravel, auth uses Passport, and the frontend is React. The login URL it gives is `http://localhost/coreUnit/api/public/api/v1/login`.

**Implemented.** The Fuse login copy calls the product UNITY and describes it as business software for a startup, an SME, and beyond. The API registers a user, logs that user in with a Passport personal access token, returns the user, and stores products with a name and a detail. The product screens in the Fuse theme load demo routes. They do not call `ProductController`.

**Unknown / Requires Confirmation:** who the operator is, whether UNITY here is the same product as the other Unity repositories, and whether anyone still deploys this tree. The last commit on `master` is 2019-08-05.

## Users and roles

| Actor | Evidence | Status |
|---|---|---|
| Person registering | `POST /api/register` and `POST /api/v1/register` | **Implemented**. The README does not name this actor |
| Person logging in | `POST /api/v1/login`, Fuse `/login`, Fuse `/pages/auth/login-2`, small app `/` | **Implemented**. Three different clients |
| Authenticated API caller | `auth:api` on `GET /api/user`, `POST /api/v1/getUser`, and `/api/products` | **Implemented**. Bearer token from Passport |
| Fuse `admin` / `staff` / `user` / guest | `frontend/src/app/auth/authRoles.js` and the initial role `[]` in `user.reducer.js` | **Implemented** as theme data. `Auth.js` leaves JWT, Auth0, and Firebase checks commented out, so nothing in the inspected login path fills that role |
| Web session user | `api/app/Http/Controllers/Auth/*` | Scaffold only. `routes/web.php` does not register `Auth::routes()`, and those controllers redirect to `/home`, which has no route |

No permission table, policy class, or team model exists. `AuthServiceProvider` maps `App\Model` to `App\Policies\ModelPolicy`. Neither of those classes is in the tree.

## Systems the product talks to

1. MySQL through Laravel, default connection `mysql`. `api/.env.example` uses host `127.0.0.1`, database `homestead`, user `homestead`, password `secret`. Those are the framework sample values. No filled `api/.env` is committed (`api/.gitignore` ignores `.env`).
2. The browser, for both React apps.
3. Passport token storage in the same database. The OAuth migrations are not in `api/database/migrations/`.
4. Mailtrap, only as `MAIL_HOST` in `.env.example`. `EventServiceProvider` listens for `Registered` and would send a verification notification. Neither API register method fires `Registered`.

No queue worker, Redis client call, S3 upload, or Pusher event is implemented. Those keys exist as empty placeholders in `.env.example`. The Fuse demo apps (mail, chat, e-commerce, calendar) ship inside `frontend/src/app/main/apps/` and call theme URLs such as `/api/e-commerce-app/products`. There is no `@fake-db` directory and no Laravel route under that path.

## Major workflows

### WF-1 — Open the Fuse app

**Implemented** in `frontend/src/app/configs/routesConfig.js`:

1. `/` redirects to `/login`.
2. `/login` lazy-loads `frontend/src/app/main/login/Login.js`.
3. The shell still mounts the Fuse demo configs: dashboards, mail, todo, file manager, contacts, calendar, chat, e-commerce, scrumboard, academy, notes, pages, role examples, UI pages, documentation, register, logout, and callback.
4. The navigation config has a single item, Example, pointing at `/example`. `ExampleConfig` is not imported by `routesConfig.js`.

`yarn start` is the documented dev command in `frontend/README.md` and `frontend/package.json`. It runs Tailwind and then Create React App, which serves `http://localhost:3000`.

### WF-2 — Log in from the Fuse home screen

**Documented** login URL in the README: `http://localhost/coreUnit/api/public/api/v1/login`.

**Implemented** on `/login` (`Login.js`):

1. The form requires a non-empty email and a non-empty password. The Remember Me checkbox is not sent.
2. Submit posts JSON `{email, password}` to `http://local.unity/api/public/api/v1/login`.
3. The client sets `Content-Type` and `Access-Control-Allow-Origin` on the request. The origin header is a response header. The server is the side that must send it.
4. HTTP 200 logs "Login successfull" and builds a user object with role `admin`, `from: 'auth0'`, and a fixed display name. `setUserData` returns a thunk and the page never dispatches it. The Passport token in the body is not written to `localStorage`.
5. The callback returns `<Redirect to='/apps/dashboards/analytics' />` from the Axios promise. That return value is not rendered.
6. HTTP 204 alerts that the username and password do not match. Any other non-200 status alerts that the username does not exist. The API's failure status is 401, which Axios sends to `catch`. The catch only logs the error.
7. Google and Facebook buttons have no click handler. "Create an account" links to `/pages/auth/register-2`. "Forgot Password?" links to `/pages/auth/forgot-password-2`. Those pages do not call the Laravel API.

### WF-3 — Log in from the second Fuse page

**Implemented** on `/pages/auth/login-2` (`Login2Page.js`):

1. The same empty-field check as WF-2.
2. `apiBaseUrl` is already `http://localhost/coreUnit/api/public/api/v1/login`. The post appends `login` again, so the request URL is `.../api/v1/loginlogin`.
3. `handleSubmit` does not call `preventDefault`.
4. Success is `response.data.code === 200`. The API returns `success.token` and HTTP 200. It does not return `code`.
5. The token is not stored. The page does not navigate.

### WF-4 — Register and log in on the API

**Documented:** Passport, and the v1 login URL.

**Implemented** in `api/routes/api.php`, `AuthController`, and `API\RegisterController`. The route group prefix is `api` (`RouteServiceProvider`).

1. `POST /api/v1/register` validates `name` (required), `email` (required email), `password` (required), `c_password` (required and identical to `password`). Failure returns HTTP 401 and `{"error": <messages>}`.
2. The password is `bcrypt`'d. `User::create` mass-assigns `name`, `email`, and `password`. `c_password` is not fillable.
3. `createToken('AppName')` returns a personal access token. The JSON body is `{"success":{"token":"..."}}` with HTTP 200.
4. `POST /api/register` uses the same field rules. Failure goes through `BaseController::sendError`, whose default status is 404, with `success: false` and message `Validation Error.`
5. That action names the token `MyApp` and returns HTTP 200 with `success: true`, `data.token`, `data.name`, and message `User register successfully.`
6. `POST /api/v1/login` calls `Auth::attempt` with the request email and password. The default guard is `web` (session). On success it creates a token named `AppName` and returns HTTP 200 `{"success":{"token":"..."}}`. On failure it returns HTTP 401 `{"error":"Unauthorised"}`.
7. **Inferred.** `Auth::attempt` uses the session guard, and the `api` middleware group does not start a session. Laravel's session guard throws when the session store is missing. This path was not executed during the investigation.
8. `POST /api/v1/getUser` and `GET /api/user` require `auth:api`. `getUser` returns `{"success": <user>}` at HTTP 200. The user JSON hides `password` and `remember_token`.

Email is unique in MySQL. Neither validator includes `unique:users`. A duplicate email is a query exception, not the validation payload.

`User` implements `MustVerifyEmail`. The API register methods do not dispatch `Registered`, and no route uses the `verified` middleware. A verification mail is not sent by these actions.

### WF-5 — Products

**Implemented** in `ProductController`, behind `auth:api`, at `/api/products`:

1. `GET /api/products` returns every row and the message `Products retrieved successfully.`
2. `POST /api/products` requires `name` and `detail`. Failure is `sendError` (HTTP 404). Success creates the row and returns it at HTTP 200.
3. `GET /api/products/{id}` uses `Product::find`. A missing row returns `Product not found.` at HTTP 404.
4. `PUT` and `PATCH /api/products/{product}` require `name` and `detail`, assign those two fields, and save. The argument is a route-model `Product`. A missing id is Laravel's own 404, before `sendError`.
5. `DELETE /api/products/{product}` deletes and returns the deleted attributes.
6. The resource also registers `GET /api/products/create` and `GET /api/products/{product}/edit`. The controller has no `create` or `edit` method.

No React file calls `/api/products`. The e-commerce screens call `/api/e-commerce-app/products`.

### WF-6 — Small app under `public/`

**Implemented** in `public/src/index.js`:

1. `/` renders `LoginForm`. `/dashboard` renders `Dashboard`. Any other path renders `Notfound` (`<h1>Not found</h1>`).
2. `App` is imported and never rendered.
3. The form uses Yup: email is required and must be an email; password is required, at least 8 characters, and must contain a digit. Those rules are not the API's rules.
4. Submit waits 500ms, logs the values, and then reads `this.props.history`. The component is a function, so `this` is not the router. A constant `https://localhost/api/public/api` is declared and never called. A commented comparison against a fixed username and password is not executed. The fixed values are omitted here.
5. `Dashboard` starts with four hardcoded client rows (id, name, email). On mount it GETs `http://localhost/coreUnit/api/public/api/client/list`. No such route exists. The response is stored on `contacts`. The table reads `clients`, so the fetch cannot change the rows.

## Functional requirements

### FR-001 — v1 registration

The system accepts `POST /api/v1/register` with `name`, `email`, `password`, and `c_password`.

- Actor: anonymous caller.
- Trigger: HTTP POST.
- Validation: name required, email required and formatted as an email, password required, `c_password` required and equal to `password`.
- Behaviour: bcrypt the password, insert a user, create a Passport personal access token named `AppName`.
- Output: HTTP 200 and `success.token`.
- Error: HTTP 401 and `error` set to the validator messages.
- Dependency: `users` table and Passport's OAuth tables.
- Evidence: `AuthController::register`. Implementation: **Implemented**. Confidence: high for the source, medium for a live call until Passport is installed.

### FR-002 — Unversioned registration

The system accepts `POST /api/register` with the same four fields.

- Behaviour: same insert, token name `MyApp`.
- Output: HTTP 200, `success: true`, `data.token`, `data.name`, message `User register successfully.`
- Error: HTTP 404, `success: false`, message `Validation Error.`, `data` set to the validator messages.
- Evidence: `API\RegisterController`, `BaseController::sendError`. Implementation: **Implemented**. Confidence: high.

The two register actions are both mounted. Which one a client must use is **Unknown / Requires Confirmation**.

### FR-003 — v1 login

The system accepts `POST /api/v1/login` with `email` and `password`.

- Behaviour: `Auth::attempt` against the users table. Success creates a token named `AppName`.
- Output: HTTP 200 and `success.token`, or HTTP 401 and `error: Unauthorised`.
- The Remember Me flag is not read.
- Evidence: `AuthController::login`. Implementation: **Implemented**, with the session-guard risk in WF-4 step 7 marked **Inferred**.

### FR-004 — Current user

An authenticated caller can `POST /api/v1/getUser` or `GET /api/user`.

- Input: `Authorization: Bearer <passport token>`.
- Output: the user, without `password` or `remember_token`. `getUser` wraps it in `success`. `GET /api/user` returns the user model directly.
- Missing or bad token: Laravel's `auth:api` rejection (401).
- Evidence: `routes/api.php`, `User::$hidden`. Implementation: **Implemented**.

### FR-005 — Product catalogue API

An authenticated caller can list, create, read, update, and delete products.

- Create and update require `name` and `detail`.
- List returns every product. There is no filter, page, or owner column.
- Evidence: `ProductController`, products migration. Implementation: **Implemented** on the API. No UI calls it.

### FR-006 — Fuse entry login

Opening the Fuse app shows the UNITY login and posts a non-empty email and password to `http://local.unity/api/public/api/v1/login`.

- A HTTP 200 response does not store the token, does not dispatch the user into Redux, and does not navigate.
- Evidence: `Login.js`, `routesConfig.js`. Implementation: **Implemented** as that client behaviour. It does not complete a signed-in session.

### FR-007 — Second login page

`/pages/auth/login-2` posts to `http://localhost/coreUnit/api/public/api/v1/loginlogin` and treats `response.data.code` as the result.

- Evidence: `Login2Page.js`. Implementation: **Implemented**. The URL and the `code` check do not match FR-003.

### FR-008 — Small-app login

`public/` route `/` validates email and password in the browser (minimum 8 characters, one digit) and does not call the API.

- Evidence: `LoginForm.js`. Implementation: **Implemented**.

### FR-009 — Client dashboard

`/dashboard` in the small app renders the four hardcoded clients and requests `/api/client/list`.

- The API has no client resource. The table does not render the response.
- Evidence: `Dashboard.js`, `routes/api.php`. Implementation: **Implemented** on the client. The server route is absent.

### FR-010 — Rate limit

Every route in `routes/api.php` is in the `api` group, throttled to 60 requests per minute.

- Evidence: `Kernel.php` `'throttle:60,1'`. Implementation: **Implemented**.

### FR-011 — Welcome page

`GET /` on the Laravel app returns the stock welcome view.

- Evidence: `routes/web.php`, `resources/views/welcome.blade.php`. Implementation: **Implemented**. The feature test asserts status 200 for `/`.

## Constraints

1. PHP `^7.1.3`, Laravel `5.8.*`, Passport `^7.3`, `spatie/laravel-cors` `^1.5`.
2. Documented host is WAMP, with the API under `http://localhost/coreUnit/api/public/`.
3. Passwords are bcrypt, 10 rounds unless `BCRYPT_ROUNDS` is set. Tests set `BCRYPT_ROUNDS` to 4.
4. API responses from `BaseController` use HTTP 200 for success and HTTP 404 for errors, including validation errors.
5. `AuthController` uses HTTP 401 for validation and for a bad login, and HTTP 200 for success.
6. CORS: Spatie `DefaultProfile` allows origin `*`, methods POST, GET, OPTIONS, PUT, PATCH, DELETE, and headers `*`, with `allow_credentials` true. The route middleware `cors` adds `Access-Control-Allow-Origin: *` and allows GET, POST, PATCH, PUT, DELETE, OPTIONS. Its `Allow-Headers` line is commented out.
7. Timezone in `config/app.php` is `UTC`.

## Discrepancies

1. The README login host is `localhost/coreUnit`. `Login.js` uses `local.unity`. `Login2Page.js` uses `localhost/coreUnit` and then appends a second `login`. `LoginForm.js` declares `https://localhost/api/public/api` and does not call it. `Dashboard.js` calls `/api/client/list`.
2. The README says auth uses Passport. From 2019-07-10 to 2019-07-14 the API depended on `tymon/jwt-auth`. Passport replaced it on 2019-08-05. JWT files are gone at `e424b25`. The Fuse `jwtService` still posts to `/api/auth`, and `Auth.js` does not start it.
3. Two register endpoints return different status codes, token names, and JSON shapes.
4. `User` implements `MustVerifyEmail`. Login and `auth:api` do not check `email_verified_at`.
5. The navigation item Example has a page component. The route config that would mount it is not registered.
6. `Login.js` and `Login2Page.js` describe one business product. The mounted Fuse apps are the theme demos. The product API is a separate resource with no screen.
7. Directory `api/app/Http/Controllers/API` holds `AuthController` in namespace `App\Http\Controllers\Api` and the other controllers in namespace `App\Http\Controllers\API`. The route strings use `Api\AuthController` and `API\RegisterController`. WAMP on Windows can load both. A case-sensitive filesystem resolves `Api` and `API` as different directories. **Inferred** portability defect. Not executed on Linux during this investigation.
