Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,11 @@ dmypy.json
!**/.gitkeep

# Rally was extracted into its own standalone repo (Platform-rally-extension)
# and is no longer a submodule. A local clone may still sit here; ignore it.
# and is no longer a submodule. A local clone (or a leftover worktree of the
# old submodule, e.g. extensions/rally-worktree-session-*/) may still sit
# here; ignore it.
extensions/rally/
extensions/rally-worktree-session-*/

# Extension build artifacts (submodules handle their own .gitignore)
extensions/*/web-*/node_modules/
Expand Down
56 changes: 15 additions & 41 deletions EXTENSIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,8 @@ The NEI Platform supports extensions that can be enabled or disabled without mod
# Start with no extensions
./start-platform.sh

# Start with Rally extension
ENABLED_EXTENSIONS="rally" ./start-platform.sh

# Start with multiple extensions
ENABLED_EXTENSIONS="rally,gala" ./start-platform.sh
# Start with Gala extension
ENABLED_EXTENSIONS="gala" ./start-platform.sh
```

### Manual Extension Control
Expand All @@ -27,18 +24,16 @@ ENABLED_EXTENSIONS="rally,gala" ./start-platform.sh
# Disable all extensions
ENABLED_EXTENSIONS="" ./scripts/manage-extensions.sh

# Enable Rally only
ENABLED_EXTENSIONS="rally" ./scripts/manage-extensions.sh

# Enable multiple extensions
ENABLED_EXTENSIONS="rally,gala" ./scripts/manage-extensions.sh
# Enable Gala only
ENABLED_EXTENSIONS="gala" ./scripts/manage-extensions.sh
```

## Available Extensions

- **rally**: Rally Tascas extension (team management, checkpoints, scoring)
- **gala**: Gala extension (event management)

> Rally Tascas is no longer an embedded extension — it now runs as an independent, self-contained project (`Platform-rally-extension`) with its own database, auth, and reverse proxy. It is linked from the navbar as an external service, not managed through this system.

## Generic Extension Management System

The platform uses a generic extension management system that:
Expand Down Expand Up @@ -104,7 +99,7 @@ The generic extension management system can be adapted for external nginx server

```bash
# Generate extension nginx configs
ENABLED_EXTENSIONS="rally" ./scripts/manage-extensions.sh
ENABLED_EXTENSIONS="gala" ./scripts/manage-extensions.sh

# Copy configs to external nginx server
scp proxy/locations.*.conf user@nginx-server:/etc/nginx/conf.d/
Expand All @@ -128,11 +123,8 @@ The system works seamlessly with the existing GitHub Actions deploy workflow:
Create a `.env` file in the Platform root directory:

```bash
# Enable only Rally extension
ENABLED_EXTENSIONS=rally

# Enable multiple extensions
ENABLED_EXTENSIONS=rally,gala
# Enable only Gala extension
ENABLED_EXTENSIONS=gala

# Disable all extensions
ENABLED_EXTENSIONS=
Expand All @@ -150,15 +142,9 @@ docker-compose up -d --build api_nei
Override the environment variable directly in the command:

```bash
# Enable Rally
ENABLED_EXTENSIONS=rally docker-compose up -d --build api_nei

# Enable Gala
ENABLED_EXTENSIONS=gala docker-compose up -d --build api_nei

# Enable both
ENABLED_EXTENSIONS=rally,gala docker-compose up -d --build api_nei

# Disable all
ENABLED_EXTENSIONS= docker-compose up -d --build api_nei
```
Expand All @@ -169,21 +155,21 @@ You can directly modify the `compose.yml` file, but this is not recommended as i

```yaml
environment:
ENABLED_EXTENSIONS: "rally" # or "rally,gala" or ""
ENABLED_EXTENSIONS: "gala" # or ""
```

## Quick Start Examples

### Run with Rally Extension
### Run with Gala Extension

```bash
# Create .env file
echo "ENABLED_EXTENSIONS=rally" > .env
echo "ENABLED_EXTENSIONS=gala" > .env

# Start platform
docker-compose up -d --build api_nei

# Verify Rally is loaded
# Verify Gala is loaded
curl http://localhost:8000/api/nei/v1/extensions/manifest
```

Expand All @@ -200,16 +186,6 @@ docker-compose up -d --build api_nei
curl http://localhost:8000/api/nei/v1/extensions/manifest
```

### Run with Multiple Extensions

```bash
# Enable both Rally and Gala
echo "ENABLED_EXTENSIONS=rally,gala" > .env

# Start platform
docker-compose up -d --build api_nei
```

## Verification

### Check Extensions API
Expand All @@ -218,8 +194,8 @@ docker-compose up -d --build api_nei
# Check which extensions are loaded
curl http://localhost:8000/api/nei/v1/extensions/manifest

# Expected output with Rally enabled:
# {"nav":[{"label":"Rally Tascas","href":"/rally","requiresScopes":["manager-rally","admin"],"extension":"rally"}]}
# Expected output with Gala enabled:
# {"nav":[{"label":"Jantar Gala","href":"/gala","requiresScopes":["manager-gala","admin"],"extension":"gala"}]}

# Expected output with no extensions:
# {"nav":[]}
Expand All @@ -231,14 +207,12 @@ curl http://localhost:8000/api/nei/v1/extensions/manifest
# Check available OAuth2 scopes
curl http://localhost:8000/api/nei/v1/auth/scopes

# Rally extension adds: manager-rally, rally-staff
# Gala extension adds: manager-gala
```

### Check Frontend Navigation

Visit http://localhost:3000 and check if extension navigation items appear:
- **Rally**: "Rally Tascas" navtab
- **Gala**: Gala-related navigation items

## Extension Development
Expand Down
4 changes: 2 additions & 2 deletions api-nei/app/core/extension_scopes.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ def register_scope(
Register a scope from an extension

Args:
extension_name: Name of the extension (e.g., 'rally', 'gala')
scope: The scope string (e.g., 'manager-rally')
extension_name: Name of the extension (e.g., 'gala')
scope: The scope string (e.g., 'manager-gala')
description: Human-readable description of the scope
"""
scope_key = f"{extension_name}:{scope}"
Expand Down
7 changes: 7 additions & 0 deletions compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,13 @@ services:
context: web-nei
ports:
- 3000:3000
environment:
# Rally and Gamification are standalone external services (not
# embedded extensions) - set their URLs to show them in the navbar.
ENABLE_RALLY: ${ENABLE_RALLY:-False}
RALLY_URL: ${RALLY_URL:-}
ENABLE_GAMIFICATION: ${ENABLE_GAMIFICATION:-False}
GAMIFICATION_URL: ${GAMIFICATION_URL:-}
volumes:
- nei_modules:/web_nei/node_modules
- ./web-nei:/web_nei
Expand Down
21 changes: 10 additions & 11 deletions dev/proxy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,25 @@ This internal proxy is **not exposed externally** in production environments.

- `nginx.conf` - Base nginx configuration
- `locations.conf` - Core NEI routes (API and web)
- `locations.rally.conf` - Auto-generated by manage-extensions.sh (when Rally enabled)
- `locations.gala.conf` - Auto-generated by manage-extensions.sh (when Gala enabled)
- `nei.web.ua.pt/` - Domain-specific configs (development/production variants)

## How It Works

1. Developer runs: `ENABLED_EXTENSIONS="rally" ./start-platform.sh`
1. Developer runs: `ENABLED_EXTENSIONS="gala" ./start-platform.sh`
2. `manage-extensions.sh` generates extension-specific location files
3. `compose.yml` mounts these files into the proxy container
4. Proxy routes requests to internal container names:
- `api_rally` → Rally API container
- `web_rally` → Rally web container
- `api_gala` → Gala API container
- `web_gala` → Gala web container
- `api_nei` → Core API container
- `web_nei` → Core web container

## Architecture

### Development (this proxy)
```
Browser → proxy:80 → {api_nei, web_nei, api_rally, web_rally}
Browser → proxy:80 → {api_nei, web_nei, api_gala, web_gala}
```

### Production (Infrastructure/nginx)
Expand All @@ -53,20 +52,20 @@ Extension location blocks are generated by `../scripts/manage-extensions.sh`:
- **Enabled**: Routes to extension containers
- **Disabled**: Returns 404 or proxies to main platform

Example enabled config (`locations.rally.conf`):
Example enabled config (`locations.gala.conf`):
```nginx
location ~ ^/(api|static)/rally(/.*)?$ {
proxy_pass http://api_rally:8003;
location ~ ^/(api|static)/gala(/.*)?$ {
proxy_pass http://api_gala:8004;
}

location ~ ^/rally(/.*)?$ {
proxy_pass http://web_rally:3003;
location ~ ^/gala(/.*)?$ {
proxy_pass http://web_gala:3002;
}
```

Example disabled config:
```nginx
location ~ ^/(api|static)/rally(/.*)?$ {
location ~ ^/(api|static)/gala(/.*)?$ {
return 404;
}
```
Expand Down
6 changes: 6 additions & 0 deletions web-nei/.env
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
VITE_ENABLE_GALA=${ENABLE_GALA}

# Rally and Gamification are standalone external services, not embedded
# platform extensions - set their public URLs to enable the navbar links.
VITE_ENABLE_RALLY=${ENABLE_RALLY}
VITE_RALLY_URL=${RALLY_URL}
VITE_ENABLE_GAMIFICATION=${ENABLE_GAMIFICATION}
VITE_GAMIFICATION_URL=${GAMIFICATION_URL}

VITE_ANIMATION_BASE=0.05
VITE_ANIMATION_INCREMENT=0.1
6 changes: 5 additions & 1 deletion web-nei/src/config/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,13 @@ const config = {
API_FAMILY_URL: `${BASE_URL}/api/family/v1`,
WS_URL: `${WS_SCHEME}${HOST}/api/nei/v1`,
WEB_GALA_URL: `${BASE_URL}/gala`,
WEB_RALLY_URL: `${BASE_URL}/rally`,
ENABLE_GALA: import.meta.env.VITE_ENABLE_GALA === "True",
// Rally and Gamification are standalone external services (own domain,
// own auth, own DB) - no longer embedded platform extensions.
WEB_RALLY_URL: import.meta.env.VITE_RALLY_URL || "",
ENABLE_RALLY: import.meta.env.VITE_ENABLE_RALLY === "True",
WEB_GAMIFICATION_URL: import.meta.env.VITE_GAMIFICATION_URL || "",
ENABLE_GAMIFICATION: import.meta.env.VITE_ENABLE_GAMIFICATION === "True",
GOOGLE_CALENDAR_URL: `https://www.googleapis.com/calendar/v3`,
GOOGLE_RECAPTCHA_CDN: `https://www.google.com/recaptcha/api.js`,
GOOGLE_RECAPTCHA_KEY: `6LejnQ4lAAAAAFsMWR1S2Rw3LJv02KcbdOL-aNUh`,
Expand Down
73 changes: 73 additions & 0 deletions web-nei/src/hooks/useServiceHealth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { useEffect, useState } from "react";

const PROBE_TIMEOUT_MS = 4000;
const DEFAULT_INTERVAL_MS = 60000;

/**
* Probes a single URL for reachability without requiring CORS to be
* configured on the target - `no-cors` mode still lets the browser attempt
* the request and resolve/reject on network-level success/failure, even
* though the response body itself is opaque and unreadable.
*
* @param {string} url
* @returns {Promise<boolean>} true if reachable, false otherwise
*/
async function probe(url) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS);
try {
await fetch(url, {
mode: "no-cors",
cache: "no-store",
signal: controller.signal,
});
return true;
} catch (_) {
return false;
} finally {

Check warning on line 27 in web-nei/src/hooks/useServiceHealth.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Handle this exception or don't catch it at all.

See more on https://sonarcloud.io/project/issues?id=NEI-AAUAV_Platform&issues=AaA_JL_tUzyOKZ-DnyGd&open=AaA_JL_tUzyOKZ-DnyGd&pullRequest=213
clearTimeout(timeoutId);
}
}

/**
* Tracks aggregate reachability of one or more external service URLs.
* Meant for services outside the platform (Rally, Gamification) that are no
* longer embedded extensions and can't be trusted to expose readable
* health JSON across origins.
*
* @param {string[]} targets - URLs to probe (e.g. web root + per-microservice health endpoints)
* @param {{ intervalMs?: number }} [options]
* @returns {"checking" | "up" | "degraded" | "down"}
*/
export function useServiceHealth(targets, { intervalMs = DEFAULT_INTERVAL_MS } = {}) {
const [status, setStatus] = useState("checking");
const targetsKey = Array.isArray(targets) ? targets.filter(Boolean).join("|") : "";

useEffect(() => {
const activeTargets = targetsKey ? targetsKey.split("|") : [];
if (activeTargets.length === 0) {
setStatus("checking");
return;
}

let cancelled = false;

const check = async () => {
const results = await Promise.all(activeTargets.map(probe));
if (cancelled) return;
const reachable = results.filter(Boolean).length;
if (reachable === 0) setStatus("down");
else if (reachable === activeTargets.length) setStatus("up");
else setStatus("degraded");
};

check();
const intervalId = setInterval(check, intervalMs);
return () => {
cancelled = true;
clearInterval(intervalId);
};
}, [targetsKey, intervalMs]);

return status;
}
15 changes: 0 additions & 15 deletions web-nei/src/layouts/Navbar/data.jsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,5 @@
import config from "config";

const specialData = [
config.ENABLE_GALA && {
name: "Jantar de Gala",
link: config.WEB_GALA_URL,
},
config.ENABLE_RALLY && {
name: "Rally Tascas",
link: config.WEB_RALLY_URL,
},
];

const data = [
{
name: "Estudo",
Expand Down Expand Up @@ -99,10 +88,6 @@ const data = [
name: "Taça UA",
link: "/taca-ua",
},
// {
// name: "Rally Tascas",
// link: "/breakthebars",
// },
{
name: "Finalistas",
link: "/seniors",
Expand Down
Loading
Loading