Skip to content
Open
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
16 changes: 13 additions & 3 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,18 @@ SERVE_URL="/" # can be something like /sbc_translation/static/ in .env.productio
# can be something like /sbc_translation/static/ in .env.production.local file
VITE_WEB_URL="/"

# Odoo instance URL
VITE_ODOO_URL="yoyoyo"
# Odoo instance URL. Leave empty to call /auth/* and /xmlrpc/* on the
# current origin — the recommended setting for both:
# - local dev (npm run dev) when using the Vite proxy below
# - production where the SPA is served by Odoo from static/tp/
# Set to a full URL only when the SPA is hosted on a different origin
# from Odoo (cross-origin); see README for the CORS caveat.
VITE_ODOO_URL=""

# Odoo instance database name
VITE_ODOO_DBNAME="yoyoyo"
VITE_ODOO_DBNAME="yoyoyo"

# Where `npm run dev` proxies /auth/* and /xmlrpc/* to. Defaults to
# http://localhost:8069 if unset. Override if your Odoo runs on a
# different port or host.
VITE_DEV_PROXY_TARGET="http://localhost:8069"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing trailing newline

The file still ends without a trailing newline (\ No newline at end of file). Some tooling (e.g. dotenv parsers, git diff, linters) can misbehave or produce noisy diffs when a file is missing the final newline.

Suggested change
VITE_DEV_PROXY_TARGET="http://localhost:8069"
VITE_DEV_PROXY_TARGET="http://localhost:8069"

64 changes: 63 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,63 @@ built with [Owl](https://github.com/odoo/owl) and [Vite](https://vitejs.dev/). I
2. Run `npm run build`, it will build static files in the `/dist` directory
3. Copy those files wherever you want

## Running against Odoo 18

The backend module is `auth_external` (in
`compassion-switzerland/compassion-switzerland/auth_external`). It
exposes:

- `POST /auth/login {login, password, totp}` → `{user_id, auth_tokens: {access_token, refresh_token, expires_at}}`
- `POST /auth/refresh {refresh_token}` → rotated `auth_tokens`
- `POST /auth/logout {refresh_token}` → revoke family

Subsequent XML-RPC calls authenticate via the `Authorization: Bearer
<access_token>` header (set automatically by `OdooAPI.ts`) with
`password='None'` in the `execute_kw` arguments. The Odoo-side
`res.users.check` override in `auth_external` validates the header.

### Deployment modes

**1. Served by Odoo (production / staging).** Run `npm run build` and
copy `dist/*` into `sbc_translation/static/tp/`. The
`TranslationPlatformController` in `sbc_translation/controllers/main.py`
serves the SPA at `/translation-platform`. The SPA and the Odoo API
share the same origin, so no CORS concerns.

Set in `.env.production.local`:

```
SERVE_URL="/translation-platform/"
VITE_ODOO_URL=""
VITE_ODOO_DBNAME="<prod db>"
```

**2. `npm run dev` against a local Odoo (local development).** This
is the recommended dev workflow. The Vite dev server proxies the
`/auth/*` and `/xmlrpc/*` paths to your local Odoo, so the browser
sees same-origin requests and there is no CORS preflight to deal
with.

Set in `.env.local`:

```
SERVE_URL="/"
VITE_ODOO_URL=""
VITE_ODOO_DBNAME="<your test db>"
# Only override if Odoo isn't on the default port/host:
# VITE_DEV_PROXY_TARGET="http://localhost:8069"
```

Then `npm run dev` and open <http://localhost:3000>.

**3. Cross-origin hosting (non-default).** If you ever need the SPA
to live on a different host from Odoo, you have to enable CORS on
`/xmlrpc/2/*` (stock v18 declares `cors=` only on `/auth/*`). Do it
narrowly, set the `cors=` value to the exact origin of the SPA, not
`"*"`, and only on that endpoint. Neither of the two recommended
deployments above triggers a CORS preflight (both are same-origin),
so we don't ship such an override.

## Environment files
Please read the [vite documentation](https://vitejs.dev/guide/env-and-mode.html#modes). Mainly, environment files are loaded based
on their name given the running mode:
Expand Down Expand Up @@ -86,7 +143,12 @@ Whenever a missing translation is found it will be logged to the browser's conso
the various missing translations by running `dumpMissingTranslations()` in your browser console, which will
log a JSON object containing them.

#### ODOO Dev environment and CORS requests
#### ODOO Dev environment and CORS requests (legacy v12/v14)

> **For v18:** prefer the Vite dev proxy described in the "Running
> against Odoo 18" section above — it sidesteps CORS entirely without
> touching Odoo's source. The patch below is kept for historical
> reference and for v12/v14 setups only.

When running the platform in dev environment, you will very probably run into a cross-origin requests
problem. To fix it quick and dirty, edit the `/odoo/service/wsgi_server.py` in Odoo's source code.
Expand Down
3 changes: 2 additions & 1 deletion src/models/LetterDAO.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,10 @@ const LetterDAO: BaseDAO<Letter> & LetterDAOApi = {
// Add global state
// @ts-ignore
searchParams[0].push(['state', '=', 'Global Partner translation queue']);
const countParams = [searchParams[0]];
const [letterIds, total] = await Promise.all([
OdooAPI.execute_kw('correspondence', 'search', searchParams),
OdooAPI.execute_kw('correspondence', 'search', [...searchParams, true]) as Promise<number>
OdooAPI.execute_kw('correspondence', 'search_count', countParams) as Promise<number>,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The OdooAPI.execute_kw method can return undefined if the request fails. Since the total property in the returned object is expected to be a number (per the ListResponse interface), it is safer to provide a default value of 0 to avoid potential runtime issues in UI components (like pagination) and to maintain type safety.

Suggested change
OdooAPI.execute_kw('correspondence', 'search_count', countParams) as Promise<number>,
OdooAPI.execute_kw('correspondence', 'search_count', countParams).then((res) => res ?? 0) as Promise<number>,

]);

const rawLetters = await OdooAPI.execute_kw<Letter[]>('correspondence', 'list_letters', [letterIds]);
Expand Down
3 changes: 2 additions & 1 deletion src/models/TranslatorDAO.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,10 @@ const TranslatorDAO: BaseDAO<Translator> & TranslatorDAOApi = {

async list(params) {
const searchParams = generateSearchQuery<Translator>(params, translatorFieldsMapping);
const countParams = [searchParams[0]];
const [translatorIds, total] = await Promise.all([
OdooAPI.execute_kw('translation.user', 'search', searchParams),
OdooAPI.execute_kw('translation.user', 'search', [...searchParams, true]) as Promise<number>
OdooAPI.execute_kw('translation.user', 'search_count', countParams) as Promise<number>,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to the LetterDAO, the total count should have a fallback value. If the API call fails, execute_kw returns undefined, which violates the number type requirement for the total field in the response. Defaulting to 0 ensures robustness.

Suggested change
OdooAPI.execute_kw('translation.user', 'search_count', countParams) as Promise<number>,
OdooAPI.execute_kw('translation.user', 'search_count', countParams).then((res) => res ?? 0) as Promise<number>,

]);

const rawTranslators = await OdooAPI.execute_kw<Translator[]>('translation.user', 'list_users', [translatorIds]);
Expand Down
14 changes: 14 additions & 0 deletions vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,22 @@ import { defineConfig, loadEnv } from "vite";
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), "");

// VITE_DEV_PROXY_TARGET sets where `npm run dev` proxies the Odoo
// backend (`/auth/*` and `/xmlrpc/*`). When set, the webapp can
// leave VITE_ODOO_URL empty and call relative paths; the browser
// sees same-origin requests and the dev server forwards them.
// This avoids the Odoo-side CORS gap on `/xmlrpc/2/*` (which has
// no `cors=` declared in stock v18).
const devProxyTarget = env.VITE_DEV_PROXY_TARGET || "http://localhost:8069";

return {
base: env.SERVE_URL,
server: {
proxy: {
"/auth": { target: devProxyTarget, changeOrigin: true },
"/xmlrpc": { target: devProxyTarget, changeOrigin: true },
},
},
build: {
commonjsOptions: {
ignoreTryCatch: (id) => id !== "stream",
Expand Down