# Technical documentation — orginal_code

Evidence date: 2026-10-08.

Source: `main` at `18caf8e` (2024-11-12). Commands were not executed. Versions are the ranges in `package.json`.

## What this repository is

A Node.js course-operations service. `app.js` is the HTTP and Socket.IO process. `grinder.js` turns GPS fixes into rounds. `schedule.js` runs cron. All three use Mongoose against one MongoDB database. The example database name is `tagmarshal`.

## Runtime

| Item | Evidence |
|---|---|
| Language | JavaScript, CommonJS (`require`) |
| Stated Node | `README.md`: `^v16.19.0`. No `engines` field |
| Package manager documented | `yarn` |
| Lockfile present | `package-lock.json` only. No `yarn.lock` |
| Start | `node app.js`, `node grinder.js`, `node schedule.js` |
| Port | `process.env.PORT`. Example `5000` |
| Config load | `dotenv` twice: `./.env` and `../.env` |

`package.json` has no `name`, `version`, or `main`. The only script is `"test": "jest"`.

## Repository layout

```text
app.js                 HTTP server
grinder.js             fix-to-round loop
schedule.js            node-cron
config/database.js     mongoose.connect wrapper
controllers/           request handlers
handlers/teetimes.js   Socket.IO stub
middleware/            auth, server IP, device IMEI, CSV
models/                Mongoose schemas
modules/               pace, migration, notifications, providers' helpers
routes/                Express routers
teetimes/providers/    one file per tee-sheet vendor
tests/app.test.js      placeholder
.env.example           sample keys
.env                   tracked (values not copied here)
```

There is no `Dockerfile`, pipeline YAML, or `sonar-project.properties` on this commit.

## Request lifecycle

1. `app.js` creates an Express app and an HTTP server, then a Socket.IO `Server` on that HTTP server with an empty options object.
2. `bodyParser.json()` is registered with no size limit in the call.
3. MongoDB connects before listen. Failure calls `process.exit(0)` (`config/database.js`), which is exit code success.
4. CORS callback runs per request.
5. Routers mount as in the table below.
6. `httpServer.listen(PORT)`.

`controllers/modules/pinlocations.js` and `controllers/modules/messaging.js` also call `mongoose.connect(process.env.DATABASE_URL)` when those modules load.

| Mount | Middleware | Router |
|---|---|---|
| `/user-server` | server | `routes/modules/user-server.js` |
| `/device` | none at mount; some routes add auth or server | `routes/device.js` |
| `/course` | none | `routes/course.js` |
| `/caddietag` | per route | `routes/caddietag.js` |
| `/token` | server | `routes/token.js` |
| `/dashboard` | auth | `routes/dashboard.js` |
| `/teetime` | auth | `routes/modules/teetime.js` |
| `/interaction` | auth | `routes/modules/interaction.js` |
| `/trackmap` | auth | `routes/modules/trackmap.js` |
| `/goaltime` | auth | `routes/modules/goaltime.js` |
| `/user` | auth | `routes/modules/user.js` |
| `/roundreport` | auth | `routes/modules/roundreport.js` |
| `/advertisements` | auth | `routes/modules/advertisement.js` |
| `/datadirectory` | auth | `routes/modules/datadirectory.js` |
| `/playerreport` | auth | `routes/modules/playerreport.js` |
| `/caddie` | auth | `routes/modules/caddie.js` |
| `/players` | auth | `routes/modules/player.js` |
| `/usernotification` | auth | `routes/modules/usernotification.js` |
| `/shotgun` | auth | `routes/modules/shotgun.js` |
| `/geofence` | auth | `routes/modules/geofence.js` |
| `/goalsetting` | auth | `routes/modules/goalsetting.js` |
| `/timingschedule` | auth | `routes/modules/timingschedule.js` |
| `/rounds` | auth | `routes/modules/roundreplay.js` |
| `/holetimes` | auth | `routes/modules/holetimes.js` |
| `/analyticshub` | auth | `routes/modules/analytics.js` |
| `/golfgenius` | auth | `routes/modules/golfgenius.js` |
| `/two-ways` | twoway | `routes/modules/twoway.js` |
| `/messaging` | auth | `routes/modules/messaging.js` |
| `/foodandbev` | auth | `routes/modules/foodandbev.js` |
| `/rerun` | auth | `routes/modules/rerun.js` |
| `/fixes` | auth | `routes/modules/fixes.js` |
| `/pinlocations` | auth | `routes/modules/pinlocations.js` |

There is no global error handler in `app.js`. Uncaught rejections inside a route depend on that controller.

## Database

Driver: `mongoose` `^6.2.7`. Soft delete: `mongoose-delete` `^0.5.4` on the schemas that were opened (`Config`, `User`, `Device`, `Round`, `Teetime`, `Section`, and the same plugin import pattern is on other models).

Connection string: `DATABASE_URL`. Example: `mongodb://127.0.0.1:27017/tagmarshal`. No replica set, auth database, or TLS option is set in `config/database.js`. The scheduler and the grinder call `mongoose.connect` themselves and do not use `config/database.js`.

Models (collection names are Mongoose defaults unless a model overrides them; none of the opened models pass a collection name):

`advertisement`, `cachedFix`, `caddie`, `caddieInvite`, `config`, `contactSupport`, `dailyGoalSetting`, `dataDirectoryTemplate`, `device`, `fix`, `fnbGroup`, `fnbItem`, `geofence`, `geofenceBreach`, `geofenceGroups`, `geofencePreset`, `goalSetting`, `goalTime`, `golfGeniusEvent`, `golfGeniusRound`, `golfGeniusScore`, `hole`, `interaction`, `interactionTemplate`, `lastFix`, `message`, `modifier`, `notification`, `option`, `order`, `pinSchedule`, `pinTemplate`, `player`, `playerReport`, `round`, `section`, `sectionEntry`, `teetime`, `tempRound`, `timeTemplate`, `tournament`, `user`, `user_notification`, `widget`.

`Section.section` is a Polygon with `index({ section: '2dsphere' })`.

`LastFix` stores the grinder's cursor (`fixId`). Round and tee-time ids are also used as time bounds via `modules/main.js` `getIdByTimestamp` (ObjectId from a date). That helper was not re-derived here beyond its call sites.

## Authentication

JWT library: `jsonwebtoken` `^8.5.1`.

| Variable | Example | Use |
|---|---|---|
| `JWT_SECRET_KEY` | empty in the example | Sign and verify |
| `TOKEN_HEADER_KEY` | `UserToken` | Header name |
| `JWT_EXPIRES_IN` | `7d` | `expiresIn` and the `expiresAt` claim |

`middleware/auth.js` also accepts a `Bearer` header or an `authorization` header. It does not check a denylist. Logout is not implemented in the files read.

`bcrypt` is a dependency and is never required. Passwords are not hashed in this service. User passwords, if any, live in the `LOCALHOST` service.

## Authorisation

Three gates, applied per mount:

- `middleware/auth.js` — JWT.
- `middleware/server.js` — `SERVER_IP` or `ENV=local`.
- `middleware/twoway.js` — `DeviceIMEI`.

`User.permissions` is enforced only where a controller queries it (notification recipients). It is not a route guard.

## Background work

No Redis, SQS, or worker framework.

`grinder.js`: `setInterval(grind, 5000)` with a `busy` flag.

`schedule.js`: `node-cron` `^3.0.3`, six-field expressions, timezone from the first `Config` read. Jobs are listed in the PRD (WF-5). The process does not listen on a port.

## Realtime

`socket.io` `^4.7.2`. No CORS, path, or adapter options are set. Events:

- In: `teetimes:assignPlayer`, `teetimes:assignTag`.
- Out: `teetimes:teetime` with the string `{}`.

## External HTTP and SOAP

| Client | Library | Where |
|---|---|---|
| Legacy course and user API | `axios` `^0.26.1`, `axios-retry` `^4.1.0` (3 retries in migration) | `modules/migration.js`, `controllers/modules/user.js` |
| Tee sheets | `axios` or `soap` `^1.1.1` | `teetimes/providers/` |
| MessageBird | `messagebird` `^4.0.1` | `modules/messagebird.js` |
| Mailgun | `mailgun-js` `^0.22.0` | `modules/caddie.js` |
| Gmail | `nodemailer` `^6.7.8` | `controllers/modules/trackmap.js` |
| Firebase | `firebase-admin` `^11.11.1` | `modules/notifications.js` |

`firebase-admin/database` `enableLogging` is required in `controllers/modules/teetime.js`. That import's effect was not traced.

Provider modules export `processTeetimes`. Date formats differ (`YYYY-MM-DD`, `YYYYMMDD`, `MM/DD/YYYY`, `MM-DD-YYYY`). The scheduler passes the config object and lets the provider default the date.

## Environment variables

From `.env.example` (values that are hosts or flags only):

| Key | Example |
|---|---|
| `ENV` | `local` |
| `LARAVEL_AUTH_SERVER` | `http://127.0.0.1:8000` |
| `PORT` | `5000` |
| `JWT_SECRET_KEY` | empty |
| `TOKEN_HEADER_KEY` | `UserToken` |
| `JWT_EXPIRES_IN` | `7d` |
| `SERVER_IP` | `165.22.126.152` |
| `ALLOWED_ORIGINS` | localhost:3000 http and https |
| `DATABASE_URL` | `mongodb://127.0.0.1:27017/tagmarshal` |
| `FIX_LIMIT` | `1` |

Read by code and not listed in `.env.example`: `LOCALHOST`, `CLOSEST` (grinder fallback `120`), `MESSAGEBIRD_ACCESS_KEY`, `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`, `MAILGUN_FROM_EMAIL`, `ANDROID_LINK`, `IOS_LINK`, `WEB_LINK`, `VIRTUALTAG_FIREBASE`, `TMAPP_FIREBASE`.

`LARAVEL_AUTH_SERVER` and `FIX_LIMIT` are not referenced by a `process.env` read in the JavaScript searched.

A `.env` file is in git. `.gitignore` does not list it. Its contents are not in this document.

## Email and push

- Caddie mail: Mailgun, from `Tagmarshal <MAILGUN_FROM_EMAIL>`.
- Round share: Nodemailer service `gmail`, host `smtp.gmail.com`, port `587`, user `customerexperience@tagmarshal.com`. The password is hardcoded in `controllers/modules/trackmap.js` and is omitted here.
- Push: Firebase multicast. Caddie tokens use the Virtual Tag service account. Device actions use the TMApp service account. Missing tokens return `{ message: "No Devices Found." }`.

## Security notes (observed, not a penetration test)

- Unauthenticated mounts: `/course`, and most of `/device` including fix ingest.
- `ENV=local` turns off the IP gate. The example file sets that.
- Auth catch-all returns 401 and does not distinguish expiry from a missing header.
- `POST /token` will sign for any email that matches a caddie or player, for any caller who passes the server gate.
- Support-ticket creation and round share return or use `401` for validation or save failures in the paths that were read, including cases that are not an authentication failure.
- Committed secrets, values omitted: Monday.com bearer token in dead code in `controllers/course.js`; Gmail password in `controllers/modules/trackmap.js`; possible secrets in the tracked `.env`.
- No rate limit and no helmet-style headers in `app.js`.
- CORS reflects only listed origins. Socket.IO options are empty, so its own CORS behaviour is the library default. That default was not executed.

## Development and testing

README install:

```text
yarn
cp .env.example .env
# set JWT_SECRET_KEY
node grinder.js
node app.js
node schedule.js
```

Then `GET /course/change/steeberg` and `POST /device/fixes/:imei` with the sample body in the README.

Jest is the test runner. `tests/app.test.js` imports the live server, asserts nothing, then closes the server and the Mongoose connection. `supertest` is a dev dependency and is imported. It is not used.

No coverage gate, lint script, or formatter config is in the tree.

## Deployment

**Unknown.** No Dockerfile, compose file, process file, or pipeline. The example `SERVER_IP` is a public address in `.env.example`. Whether that host still runs this code is **Requires Confirmation**.

`Config.maintenance` exists (default false). No middleware read for this document checks it before serving.

## Dependency list

Runtime dependencies from `package.json`:

`async`, `axios`, `axios-retry`, `bcrypt` (unused), `body-parser`, `cors`, `countries-and-timezones`, `csv-generate`, `csv-parser`, `date-fns`, `dotenv`, `express`, `firebase-admin`, `formidable`, `he`, `json-2-csv`, `json2csv`, `jsonwebtoken`, `mailgun-js`, `messagebird`, `moment`, `moment-duration-format`, `moment-timezone`, `mongoose`, `mongoose-delete`, `ms`, `node-cron`, `nodemailer`, `soap`, `socket.io`, `superagent`, `uuid`, `validatorjs`, `xml2js`.

Dev: `jest`, `supertest`.

`moment` is in maintenance mode upstream. That is library status, not a behaviour of this repo.

## Files that need care before any run

- `app.js` throws if `ALLOWED_ORIGINS` is unset.
- `modules/messagebird.js` constructs a client at load from `MESSAGEBIRD_ACCESS_KEY`.
- `modules/notifications.js` `require`s a JSON path from the environment when a push is sent.
- The placeholder test boots the real listener and a real database connection.
- Dead Monday.com code and the hardcoded SMTP password should be treated as leaked material even though one path does not run.
