442 lines
No EOL
18 KiB
Markdown
442 lines
No EOL
18 KiB
Markdown
# FaceAI Integration Plan
|
|
|
|
## Goal
|
|
|
|
Integrate a new face-ID search application at `faceai.regalamiunsorriso.it` with the legacy site at `www.regalamiunsorriso.it`, while keeping changes to the legacy site small and preserving the current download/account rules.
|
|
|
|
The new app will:
|
|
|
|
- start from the race photo view page
|
|
- search only within the current race
|
|
- accept a selfie upload
|
|
- process the request inside FaceAI
|
|
- return the user to the original race page with the results filtered to only the matched images
|
|
- keep photo opening and downloading entirely on the legacy site
|
|
- use polling only in v1 if processing takes time
|
|
|
|
## What Exists Today
|
|
|
|
Relevant current integration points in the legacy site:
|
|
|
|
- The race photo page still uses a `select` with id `tipoPuntoFoto` in `www/fotoCR.jsp` and `www/fotoCR-en.jsp`.
|
|
- The client-side navigation for race photo search is built in `www/_js/rus-ecom-240621.js` through `searching()`, `searchingTPF()`, `searchingPF()`, `goPage()`, and `mostraFoto()`.
|
|
- The logged-in user is exposed from the Java session as `utenteLogon` and related session beans in shared JSP includes such as `www/_inc_header.jsp`.
|
|
- The current photo modal and download gate are driven by `www/fotoView.jsp`, where download access is checked with `user.puoScaricareFoto()` or free-race logic.
|
|
- The actual download link still goes through the existing legacy file path and business rules, so photo-credit subtraction should remain on the legacy side instead of being reimplemented in Node.
|
|
|
|
## Important Constraint
|
|
|
|
There is one hard technical constraint to make explicit:
|
|
|
|
The legacy site identity is held in the Java web session, not in PHP. That means a pure PHP-only bridge on `www` cannot securely determine who the user is from the existing session cookie unless there is already an external shared auth service.
|
|
|
|
Because of that, the recommended plan needs one of these two options:
|
|
|
|
1. Preferred: a very small server-side bridge on the legacy Java or JSP side, or at the reverse proxy level with app support, to mint a trusted handoff token for FaceAI.
|
|
2. Fallback: a separate login flow for FaceAI.
|
|
|
|
If the requirement is strict single sign-on based on the current site session, option 1 is the only realistic path.
|
|
|
|
There is also an operational constraint from the implementation side:
|
|
|
|
The bridge should be designed so it can be deployed without setting up a full local Java development environment and without recompiling the existing application locally. That makes a tiny JSP-based handoff endpoint or a minimal existing-controller extension preferable to adding new compiled Java modules.
|
|
|
|
## Recommended Architecture
|
|
|
|
Use three deployable parts:
|
|
|
|
1. Legacy site at `www.regalamiunsorriso.it`
|
|
2. New user-facing app at `faceai.regalamiunsorriso.it`
|
|
3. Separate async processing service for face matching, deployed independently and fed through a queue
|
|
|
|
### 1. Legacy site responsibilities
|
|
|
|
- Keep authentication, membership checks, and download-credit subtraction as the source of truth.
|
|
- Launch the FaceAI app from the race page.
|
|
- Issue a short-lived signed handoff token that identifies the user and race context.
|
|
- Accept the final FaceAI match result and turn it into a legacy race-page filter.
|
|
- Continue to serve original photo downloads so existing counters and permissions remain unchanged.
|
|
|
|
### 2. FaceAI app responsibilities
|
|
|
|
- Read the handoff token or FaceAI session cookie.
|
|
- Show the legacy-like header and navigation.
|
|
- Check whether the mounted FaceAI dataset exists for the selected race before enabling uploads.
|
|
- Let the user upload a selfie.
|
|
- Create a race-scoped search request.
|
|
- Poll job status or show queued state.
|
|
- Return a stable list of matched legacy photo identifiers.
|
|
- Redirect the user back to the legacy race page with a filter payload that reproduces the matched set.
|
|
- Preserve the page the user came from and offer a one-click return.
|
|
|
|
### 3. Processing service responsibilities
|
|
|
|
- Receive a race-scoped search job.
|
|
- Queue requests and process them one by one.
|
|
- Resolve `year/monthFolder/raceFolder` inside the mounted dataset root, take the first `.pkl` file in that race directory, and run the external face-recognition program against it.
|
|
- Return match results with confidence and photo ids or file identifiers.
|
|
- Return a completed result set usable by the legacy filter handoff.
|
|
|
|
## Authentication And Cookie Strategy
|
|
|
|
## Recommendation
|
|
|
|
Do not try to make the Node app directly trust the legacy `JSESSIONID` cookie.
|
|
|
|
Even if the cookie domain is widened to `.regalamiunsorriso.it`, the Node app still cannot safely resolve that session unless both apps share the same session store and session format. That would be more invasive than a handoff bridge.
|
|
|
|
Instead:
|
|
|
|
1. User clicks the new FaceAI button on the legacy race page.
|
|
2. Legacy site creates a short-lived signed token with:
|
|
- legacy user id
|
|
- email
|
|
- display name
|
|
- language
|
|
- access flags for FaceAI
|
|
- race id
|
|
- race slug or descriptor
|
|
- race storage metadata needed to resolve the mounted FaceAI dataset:
|
|
- `year`
|
|
- `monthFolder` like `04.APRILE`
|
|
- `raceFolder` like `LIVORNO` or `PISA`
|
|
- current page URL as `returnUrl`
|
|
- expiry time, ideally 1 to 5 minutes
|
|
3. Browser is redirected to `https://faceai.regalamiunsorriso.it/auth/callback?token=...`
|
|
4. FaceAI validates the token and sets its own cookie on `.regalamiunsorriso.it`, for example `rus_faceai_session`
|
|
5. FaceAI uses its own session cookie afterward
|
|
|
|
This gives the new app shared-domain cookies while avoiding direct dependency on the Java session internals.
|
|
|
|
### Preferred bridge implementation shape
|
|
|
|
Given the local environment constraint, the bridge should preferably be one of these:
|
|
|
|
- a tiny JSP endpoint that reads the existing session beans and performs a redirect
|
|
- a minimal addition to an already-existing legacy action/controller endpoint
|
|
- a reverse-proxy-assisted signed redirect if the platform already supports auth subrequests
|
|
|
|
Avoid a plan that requires introducing new compiled Java packages as the first step.
|
|
|
|
## Access Check
|
|
|
|
The handoff token should already include whether the feature is allowed. That check should be done on the legacy side where the real account state already exists.
|
|
|
|
Minimal validation inputs:
|
|
|
|
- logged-in user exists
|
|
- account is active enough to use the feature
|
|
|
|
To avoid unnecessary database reads, compute this from already-loaded session/account state when possible. Only hit the database if the existing session object does not contain enough information.
|
|
|
|
## Minimal Changes To The Old Site
|
|
|
|
The smallest practical change set on the legacy site is:
|
|
|
|
### Frontend change
|
|
|
|
Remove the old `tipoPuntoFoto` select from the user flow and replace it with the FaceAI launch button.
|
|
|
|
The lowest-risk way to do that is to update `www/_js/rus-ecom-240621.js` so that on the race page it:
|
|
|
|
- detects `#tipoPuntoFoto`
|
|
- removes that select from the rendered UI
|
|
- inserts a `Face ID` button in the same area
|
|
- builds the launch URL using the current race context and current page URL
|
|
- carries `raceId`, race description or slug, `raceYear`, `raceMonthFolder`, `raceFolder`, language, and exact `returnUrl`
|
|
|
|
This avoids fragile JSP layout edits and keeps the change deployable as a single JS asset update.
|
|
|
|
### Server-side bridge change
|
|
|
|
Add one minimal auth bridge endpoint on the legacy stack. It can be:
|
|
|
|
- a JSP-backed endpoint
|
|
- a servlet endpoint
|
|
- or an existing controller action if the platform already has one suitable for custom commands
|
|
|
|
That endpoint should:
|
|
|
|
- read the current legacy session
|
|
- verify the user and active-membership access
|
|
- generate the signed handoff token
|
|
- redirect to FaceAI
|
|
|
|
If this endpoint truly cannot be added, then single sign-on should be considered blocked and the plan should switch to a separate login flow.
|
|
|
|
### Legacy return filter change
|
|
|
|
The old site needs one small return-integration path so FaceAI can send the user back to the race page showing only the matched images.
|
|
|
|
That should be implemented as one of these:
|
|
|
|
- a new legacy endpoint that accepts a signed FaceAI result token and loads the matched photo set into the request or session before rendering the race page
|
|
- an extension of the existing photo search flow so it can accept a FaceAI result id and fetch the matched photo ids server-side
|
|
|
|
This is preferable to putting the matched ids directly in the browser URL, because the result set may be long and should remain tamper-resistant.
|
|
|
|
## FaceAI App Structure
|
|
|
|
The target folder is `faceai/`, and this workspace now contains an implemented scaffold there.
|
|
|
|
Suggested structure:
|
|
|
|
```text
|
|
faceai/
|
|
app/
|
|
frontend/ # Vue UI
|
|
backend/ # Node API for auth/session/job orchestration
|
|
shared/ # shared types and config
|
|
docker/
|
|
Dockerfile
|
|
nginx.conf
|
|
docs/
|
|
api-contracts/
|
|
```
|
|
|
|
## FaceAI User Flow
|
|
|
|
1. User opens a race photo page on `www`.
|
|
2. The old `tipoPuntoFoto` dropdown is removed from the visible UI and replaced by a `Face ID` button.
|
|
3. User clicks the button.
|
|
4. Legacy bridge validates session and redirects to FaceAI with signed context.
|
|
5. FaceAI shows a page styled like the old site, including a matching header and a clear `Back to race page` action.
|
|
6. User uploads a selfie.
|
|
7. FaceAI creates a search job with `userId`, `raceId`, `requestId`, and selfie file reference.
|
|
8. FaceAI checks the mounted race directory immediately and, if no `.pkl` is present for that race, disables processing and offers only the return path.
|
|
9. FaceAI polls until the processing job completes.
|
|
10. Once the result is ready, FaceAI redirects the browser back to the original race page on `www`.
|
|
11. The legacy site resolves the matched photo ids and renders the race page filtered to those photos only, similar in spirit to the existing pettorale-based flow.
|
|
12. User opens and downloads photos exactly as they do today, through the legacy site.
|
|
|
|
## Result And Download Strategy
|
|
|
|
Do not duplicate either the final photo listing view or the download-credit logic in FaceAI.
|
|
|
|
Instead:
|
|
|
|
- FaceAI should store legacy photo identifiers, not its own download copies.
|
|
- FaceAI v1 should not become a second gallery UI for final results.
|
|
- When matching is complete, FaceAI should hand the result back to the legacy site.
|
|
- The legacy site should render the final matched-photo page using its existing race photo UI and existing photo modal/download endpoints.
|
|
|
|
This keeps the business rule in one place and avoids mismatched counters.
|
|
|
|
### Recommended handoff model for match results
|
|
|
|
Use a signed result reference instead of passing the whole match list in the browser.
|
|
|
|
Recommended flow:
|
|
|
|
1. FaceAI stores the match result under a `resultId`.
|
|
2. FaceAI redirects to something like `https://www.regalamiunsorriso.it/faceai-return?...` with:
|
|
- `resultId`
|
|
- `raceId`
|
|
- signed token
|
|
3. Legacy endpoint validates the token.
|
|
4. Legacy endpoint fetches the matched legacy photo ids from FaceAI or from a shared temporary store.
|
|
5. Legacy endpoint renders the normal race page constrained to that id set.
|
|
|
|
This avoids URL-length issues and reduces tampering risk.
|
|
|
|
## Matching Result Model
|
|
|
|
The processing service should return at least:
|
|
|
|
- `raceId`
|
|
- `requestId`
|
|
- `status`
|
|
- `submittedAt`
|
|
- `completedAt`
|
|
- `matches[]`
|
|
|
|
Each match should contain:
|
|
|
|
- `photoId` compatible with legacy photo endpoints
|
|
- `score` or confidence
|
|
- `capturedAt` if known
|
|
- `puntoFoto` or checkpoint info if available
|
|
|
|
For v1, `photoId` is the most important field. If the legacy page is the final renderer, thumbnails can remain a legacy concern after redirect.
|
|
|
|
Race scope is mandatory. The service must never search globally by default.
|
|
|
|
The mounted dataset layout is now assumed to be:
|
|
|
|
```text
|
|
/mounted-pkl-root/
|
|
2026/
|
|
04.APRILE/
|
|
PISA/
|
|
any-file-name.pkl
|
|
LIVORNO/
|
|
any-file-name.pkl
|
|
```
|
|
|
|
The `.pkl` filename does not matter. The first `.pkl` found at the race root is the one passed to the matcher.
|
|
|
|
## Async Processing Design
|
|
|
|
Use an API plus worker model.
|
|
|
|
### Public FaceAI backend API
|
|
|
|
- `POST /api/auth/callback` or `GET /auth/callback?token=...`
|
|
- `GET /api/session`
|
|
- `POST /api/searches`
|
|
- `GET /api/searches/:id`
|
|
- `GET /api/searches/:id/results`
|
|
- `GET /api/searches/:id/redirect`
|
|
- `POST /api/searches/:id/cancel` optional
|
|
|
|
### Internal worker API or queue contract
|
|
|
|
Input job:
|
|
|
|
- request id
|
|
- race id
|
|
- race storage metadata: `year`, `monthFolder`, `raceFolder`
|
|
- selfie storage path
|
|
- user id
|
|
- email
|
|
- timeout policy
|
|
|
|
Output job:
|
|
|
|
- success or failure
|
|
- match list
|
|
- logs
|
|
- processing duration
|
|
- legacy-renderable result reference
|
|
|
|
### Queue choice
|
|
|
|
Any of these are reasonable:
|
|
|
|
- Redis plus BullMQ for simpler Docker deployment
|
|
- RabbitMQ if stronger broker semantics are preferred
|
|
- orchestrator-native job queue if the platform already provides it
|
|
|
|
For this use case, Redis plus BullMQ is the most pragmatic default.
|
|
|
|
## Polling And Timeout Strategy For V1
|
|
|
|
V1 should use polling only and should not send email.
|
|
|
|
Recommended behavior:
|
|
|
|
1. FaceAI submits the job.
|
|
2. Browser polls `GET /api/searches/:id`.
|
|
3. While waiting, FaceAI shows a queue or processing state.
|
|
4. When complete, FaceAI redirects to the legacy filtered-result page.
|
|
|
|
Recommended timeout split:
|
|
|
|
- up to 30 seconds: keep the user on the page with polling
|
|
- beyond 30 seconds: keep polling but show a clear long-running state and a manual retry or refresh path
|
|
|
|
Email can be revisited in a later phase after the core handoff flow is stable.
|
|
|
|
## Database Usage
|
|
|
|
Keep database usage minimal.
|
|
|
|
Recommended storage responsibilities:
|
|
|
|
- Legacy DB remains authoritative for users, membership state, photo ownership/rules, and download counters.
|
|
- FaceAI DB stores only:
|
|
- face search requests
|
|
- job status
|
|
- uploaded selfie metadata
|
|
- result references to legacy photo ids
|
|
- audit fields
|
|
|
|
Avoid copying full user profiles or photo business state into the FaceAI database.
|
|
|
|
## Legacy Look And Navigation
|
|
|
|
The FaceAI app should feel like part of the existing site, but it should not depend on JSP includes at runtime.
|
|
|
|
Recommended approach:
|
|
|
|
- Copy the visual structure of `www/_inc_header.jsp` into a Vue header component.
|
|
- Keep the same main logo, colors, and top navigation destinations.
|
|
- Show account actions based on FaceAI session state.
|
|
- Add a prominent `Back to race results` link using the captured `returnUrl`.
|
|
|
|
For v1, the header should be a very close copy, not just a lightweight brand reference. The goal is that the user should feel they are still inside the same site family during the upload and waiting flow.
|
|
|
|
This is safer than trying to embed the old JSP header directly into a Node app.
|
|
|
|
## Security Requirements
|
|
|
|
- Handoff token must be signed and short-lived.
|
|
- FaceAI session cookie must be `HttpOnly`, `Secure`, and `SameSite=Lax` unless a stricter policy breaks the flow.
|
|
- Uploaded selfies should have a short retention period.
|
|
- Face search results must be visible only to the requesting user.
|
|
- Queue jobs must be race-scoped and tied to the authenticated user.
|
|
- Result handoff back to legacy must be signed and must not trust raw photo ids coming from the browser.
|
|
|
|
## Rollout Plan
|
|
|
|
### Phase 1: spike and contracts
|
|
|
|
- Confirm that a minimal JSP or existing-controller auth bridge endpoint is possible.
|
|
- Define the signed token payload.
|
|
- Define the worker input and output contract.
|
|
- Confirm which legacy photo id is stable enough to use in FaceAI results.
|
|
- Define how legacy will accept a FaceAI result reference and render a filtered race page.
|
|
|
|
### Phase 2: legacy launch integration
|
|
|
|
- Update `www/_js/rus-ecom-240621.js` to remove the dropdown from the UI and insert the FaceAI button.
|
|
- Add the legacy auth bridge endpoint.
|
|
- Pass `raceId`, `lang`, `returnUrl`, `raceYear`, `raceMonthFolder`, and `raceFolder` into the FaceAI launch.
|
|
- Add the legacy return endpoint or result-aware race filter path.
|
|
|
|
### Phase 3: FaceAI app shell
|
|
|
|
- Create `faceai/` app structure.
|
|
- Implement auth callback and FaceAI session cookie.
|
|
- Build the legacy-style header and return navigation.
|
|
- Add selfie upload UI and request status page.
|
|
- Implement polling-only job completion flow.
|
|
|
|
### Phase 4: processing service
|
|
|
|
- Add queue and worker.
|
|
- Integrate the external face-recognition program.
|
|
- Return matched legacy photo ids and a stored result reference suitable for legacy rendering.
|
|
|
|
### Phase 5: legacy filtered-results integration
|
|
|
|
- Redirect results back to the legacy race page.
|
|
- Verify that the legacy page can render only the matched id set.
|
|
- Verify that photo-credit subtraction still happens only on successful legacy downloads.
|
|
|
|
### Phase 6: optional future enhancements
|
|
|
|
- Add email or offline completion flow if polling-only v1 proves insufficient.
|
|
- Add richer FaceAI-side previews only if needed after the legacy handoff works reliably.
|
|
|
|
## Open Questions To Resolve Early
|
|
|
|
1. Which existing legacy endpoint is the best place to implement the FaceAI return filter flow?
|
|
2. Should the return flow fetch matched photo ids directly from FaceAI, or from a shared short-lived store?
|
|
3. What is the acceptable selfie retention period for privacy compliance?
|
|
4. Should long-running polling survive page refresh via persisted request id in the FaceAI session?
|
|
5. Does the legacy race page need an explicit visual label that the current listing comes from FaceAI results?
|
|
|
|
## Recommended First Implementation
|
|
|
|
For the first version, keep the scope strict:
|
|
|
|
- launch from one race page only
|
|
- remove `tipoPuntoFoto` from the user-facing race search UI
|
|
- polling only, no email
|
|
- final results rendered on the legacy race page, not inside FaceAI
|
|
- all downloads still served by the legacy site
|
|
- one lightweight auth bridge only
|
|
- one lightweight return-filter bridge only
|
|
|
|
This version gives the new experience without moving the fragile parts of the old platform. |