Skip to content

Handle KAI token config and route normalization#16

Open
ahmadnathanfirdaus wants to merge 1 commit intocomuline:mainfrom
ahmadnathanfirdaus:fix/sync-schedule
Open

Handle KAI token config and route normalization#16
ahmadnathanfirdaus wants to merge 1 commit intocomuline:mainfrom
ahmadnathanfirdaus:fix/sync-schedule

Conversation

@ahmadnathanfirdaus
Copy link

@ahmadnathanfirdaus ahmadnathanfirdaus commented Jan 26, 2026

Summary by CodeRabbit

  • Documentation

    • Updated setup instructions for development and production environments to reflect new configuration requirements.
  • Configuration

    • New bearer token environment variable required for API authentication setup.
  • Improvements

    • Enhanced schedule synchronization with improved error handling and station name resolution capabilities.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 26, 2026

📝 Walkthrough

Walkthrough

This PR introduces dynamic bearer token configuration by adding a KAI_BEARER_TOKEN environment variable to replace hard-coded tokens, updates documentation with setup instructions for this token in dev and production workflows, and refactors the schedule synchronization logic to normalize station names and improve error handling.

Changes

Cohort / File(s) Summary
Environment Configuration
.dev.example.vars
Added KAI_BEARER_TOKEN environment variable to configuration template for dynamic token management
Documentation
README.md
Updated setup instructions in Development and Production sections to include KAI_BEARER_TOKEN configuration step; reordered and renumbered subsequent steps accordingly
Authentication Headers
src/sync/headers.ts
Replaced hard-coded static Authorization header with dynamic bearer token sourced from KAI_BEARER_TOKEN environment variable; introduced non-exported constant to hold trimmed environment value
Schedule Synchronization
src/sync/schedule.ts
Refactored station ID resolution to normalize station names via normalizeStationKey, replaced direct name-based lookups with map-based key resolution, added missingRoutes tracking, changed endpoint from /schedule to /schedules, and enhanced error handling with safe body parsing and informative error logging

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A token dances in the config,
From hard-coded chains now set free,
Stations aligned by their proper names,
Headers refreshed dynamically—
The sync pipeline hops with new grace! 🚂✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly summarizes the two main changes: KAI token configuration handling and route normalization in the sync schedule.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/sync/schedule.ts (1)

211-214: Wrong table reference in WHERE clause.

The update targets stationTable but the WHERE clause references scheduleTable.id. This condition will never match the intended station row.

Proposed fix
         await db
           .update(stationTable)
           .set(payload)
-          .where(eq(scheduleTable.id, id))
+          .where(eq(stationTable.id, id))
🤖 Fix all issues with AI agents
In @.dev.example.vars:
- Line 10: Replace the real JWT value assigned to KAI_BEARER_TOKEN in the
.dev.example.vars file with a clear placeholder (e.g., "your-bearer-token-here"
or "<KAI_BEARER_TOKEN>") so the example contains no secrets; update the
KAI_BEARER_TOKEN entry only (preserve surrounding formatting) and add a brief
comment if desired indicating it is a placeholder. After changing the example,
ensure any real token shown here is revoked/rotated in the credential store and
confirm the repo history is purged or rotate credentials if the token was
committed publicly.

In `@src/sync/headers.ts`:
- Around line 1-8: The module currently builds KAI_HEADERS using
KAI_BEARER_TOKEN which can be "undefined"; update the module to validate
process.env.KAI_BEARER_TOKEN at import time and fail fast if missing (e.g.,
throw a clear Error like "Missing KAI_BEARER_TOKEN env var") before exporting
KAI_HEADERS so Authorization is never "Bearer undefined"; reference the
KAI_BEARER_TOKEN constant and the KAI_HEADERS export when adding this check.

In `@src/sync/schedule.ts`:
- Around line 51-64: The regexes in normalizeStationKey use double-escaped
sequences (e.g. /VIA\\s*MRI/g and /\\s+/g) which match a backslash + 's' instead
of whitespace; update those regex literals to use single backslashes (e.g.
/VIA\s*MRI/g and /\s+/g) so \s behaves as the whitespace metacharacter, leaving
other patterns like /VIAMRI/g and /[^A-Z0-9]/g unchanged.
🧹 Nitpick comments (1)
README.md (1)

90-92: Add language specifier to fenced code block.

Per markdownlint (MD040), fenced code blocks should have a language specified.

Proposed fix
-```
+```bash
 COMULINE_ENV=production
</details>

</blockquote></details>

</blockquote></details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

# KRL stuff
KRL_ENDPOINT_BASE_URL="https://api-partner.krl.co.id/krl-webs/v1" No newline at end of file
KRL_ENDPOINT_BASE_URL="https://api-partner.krl.co.id/krl-webs/v1"
KAI_BEARER_TOKEN="eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIzIiwianRpIjoiYmE0Yzc4MzE4ODNjYTI0N2YzMTBkMTJhYzc3ZjE5ZTdjMTVkNjgxOTk2ODM0MDc0MGM3MzliYmRjNGQ3YTI5MzczYzMyNWM2NDFiZjgxYzciLCJpYXQiOjE3NTQ0NTkxMjYsIm5iZiI6MTc1NDQ1OTEyNiwiZXhwIjoxNzg1OTk1MTI2LCJzdWIiOiI1Iiwic2NvcGVzIjpbXX0.zPA0IDAN3NycMKa6DaOdRmkcFz1oUTX1dkxEp3MLBlhibTQI0L0WB9mY-pUlQW5vQj8ktOdo-rRvrjxiXaHFqLQM6ebONbqTg8V0AjBXwrkBjLZDCE4dop9iZyDXcG2b9XTLCgPgpOBbduW_Dy0-bIkJOOIgIzl9mEEUVQf3T6G_zA796SGJ6rtLqfBK-sMnhOV4eZSqQIXIrxPyCJ8SA893p-29PFxfQfcbXW_6cYBFhDzyiilhJ6xQd6znN2eWOL4MPAxYeS2ZGnaZ7ijUN91MAyPnV0dQU7loVtS1jt2HlM5oMSsE2Zoz6FP31GvG6f7o_MWogEp0ZMOus50bVly3II8Rjjc4IGgswbw0h-RS0Ipo3f2QmXp4GfhRNUoTyqq-7oiCIDPUJcdg39lSIy9Fz7-ECNfbjEiH60V3GyftuiFGrayMoE7XeWaC9wQZo3fLHhI1aPgbXXsP-rqWLFf2km4zdG5Y5CYpUNb_Z11VOU6aaFCdRtoC6e7VcxHxLwCBT22wluNpbfFtEQSYDQE1JlegijvFmnRHTM88n-zp7sWhuCWVX6oE0ULdy51SR4iOqpYOA4B1ZymmYrQz1kBxSA_52lnTBlU9gfWkUiFX8GLSh7wQ8a4dVMYoJj6t1VCJt9-d30jn4S3tXsim_3wpp71RE9SSazV35j8o7do"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Remove real JWT token from example file.

This example file contains what appears to be a real, valid JWT token. Example files are committed to version control and should only contain placeholder values like "your-bearer-token-here" or "<KAI_BEARER_TOKEN>". Real tokens risk exposure even after rotation if the repository history is public.

Proposed fix
-KAI_BEARER_TOKEN="eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIzIiwianRpIjoiYmE0Yzc4MzE4ODNjYTI0N2YzMTBkMTJhYzc3ZjE5ZTdjMTVkNjgxOTk2ODM0MDc0MGM3MzliYmRjNGQ3YTI5MzczYzMyNWM2NDFiZjgxYzciLCJpYXQiOjE3NTQ0NTkxMjYsIm5iZiI6MTc1NDQ1OTEyNiwiZXhwIjoxNzg1OTk1MTI2LCJzdWIiOiI1Iiwic2NvcGVzIjpbXX0.zPA0IDAN3NycMKa6DaOdRmkcFz1oUTX1dkxEp3MLBlhibTQI0L0WB9mY-pUlQW5vQj8ktOdo-rRvrjxiXaHFqLQM6ebONbqTg8V0AjBXwrkBjLZDCE4dop9iZyDXcG2b9XTLCgPgpOBbduW_Dy0-bIkJOOIgIzl9mEEUVQf3T6G_zA796SGJ6rtLqfBK-sMnhOV4eZSqQIXIrxPyCJ8SA893p-29PFxfQfcbXW_6cYBFhDzyiilhJ6xQd6znN2eWOL4MPAxYeS2ZGnaZ7ijUN91MAyPnV0dQU7loVtS1jt2HlM5oMSsE2Zoz6FP31GvG6f7o_MWogEp0ZMOus50bVly3II8Rjjc4IGgswbw0h-RS0Ipo3f2QmXp4GfhRNUoTyqq-7oiCIDPUJcdg39lSIy9Fz7-ECNfbjEiH60V3GyftuiFGrayMoE7XeWaC9wQZo3fLHhI1aPgbXXsP-rqWLFf2km4zdG5Y5CYpUNb_Z11VOU6aaFCdRtoC6e7VcxHxLwCBT22wluNpbfFtEQSYDQE1JlegijvFmnRHTM88n-zp7sWhuCWVX6oE0ULdy51SR4iOqpYOA4B1ZymmYrQz1kBxSA_52lnTBlU9gfWkUiFX8GLSh7wQ8a4dVMYoJj6t1VCJt9-d30jn4S3tXsim_3wpp71RE9SSazV35j8o7do"
+KAI_BEARER_TOKEN="<your-kai-bearer-token>"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
KAI_BEARER_TOKEN="eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIzIiwianRpIjoiYmE0Yzc4MzE4ODNjYTI0N2YzMTBkMTJhYzc3ZjE5ZTdjMTVkNjgxOTk2ODM0MDc0MGM3MzliYmRjNGQ3YTI5MzczYzMyNWM2NDFiZjgxYzciLCJpYXQiOjE3NTQ0NTkxMjYsIm5iZiI6MTc1NDQ1OTEyNiwiZXhwIjoxNzg1OTk1MTI2LCJzdWIiOiI1Iiwic2NvcGVzIjpbXX0.zPA0IDAN3NycMKa6DaOdRmkcFz1oUTX1dkxEp3MLBlhibTQI0L0WB9mY-pUlQW5vQj8ktOdo-rRvrjxiXaHFqLQM6ebONbqTg8V0AjBXwrkBjLZDCE4dop9iZyDXcG2b9XTLCgPgpOBbduW_Dy0-bIkJOOIgIzl9mEEUVQf3T6G_zA796SGJ6rtLqfBK-sMnhOV4eZSqQIXIrxPyCJ8SA893p-29PFxfQfcbXW_6cYBFhDzyiilhJ6xQd6znN2eWOL4MPAxYeS2ZGnaZ7ijUN91MAyPnV0dQU7loVtS1jt2HlM5oMSsE2Zoz6FP31GvG6f7o_MWogEp0ZMOus50bVly3II8Rjjc4IGgswbw0h-RS0Ipo3f2QmXp4GfhRNUoTyqq-7oiCIDPUJcdg39lSIy9Fz7-ECNfbjEiH60V3GyftuiFGrayMoE7XeWaC9wQZo3fLHhI1aPgbXXsP-rqWLFf2km4zdG5Y5CYpUNb_Z11VOU6aaFCdRtoC6e7VcxHxLwCBT22wluNpbfFtEQSYDQE1JlegijvFmnRHTM88n-zp7sWhuCWVX6oE0ULdy51SR4iOqpYOA4B1ZymmYrQz1kBxSA_52lnTBlU9gfWkUiFX8GLSh7wQ8a4dVMYoJj6t1VCJt9-d30jn4S3tXsim_3wpp71RE9SSazV35j8o7do"
KAI_BEARER_TOKEN="<your-kai-bearer-token>"
🤖 Prompt for AI Agents
In @.dev.example.vars at line 10, Replace the real JWT value assigned to
KAI_BEARER_TOKEN in the .dev.example.vars file with a clear placeholder (e.g.,
"your-bearer-token-here" or "<KAI_BEARER_TOKEN>") so the example contains no
secrets; update the KAI_BEARER_TOKEN entry only (preserve surrounding
formatting) and add a brief comment if desired indicating it is a placeholder.
After changing the example, ensure any real token shown here is revoked/rotated
in the credential store and confirm the repo history is purged or rotate
credentials if the token was committed publicly.

Comment on lines +1 to +8
const KAI_BEARER_TOKEN = process.env.KAI_BEARER_TOKEN?.trim()

export const KAI_HEADERS = {
"User-Agent":
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:132.0) Gecko/20100101 Firefox/132.0",
Accept: "application/json, text/javascript, */*; q=0.01",
"Accept-Language": "en-US,en;q=0.5",
Authorization:
"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIzIiwianRpIjoiMDYzNWIyOGMzYzg3YTY3ZTRjYWE4YTI0MjYxZGYwYzIxNjYzODA4NWM2NWU4ZjhiYzQ4OGNlM2JiZThmYWNmODU4YzY0YmI0MjgyM2EwOTUiLCJpYXQiOjE3MjI2MTc1MTQsIm5iZiI6MTcyMjYxNzUxNCwiZXhwIjoxNzU0MTUzNTE0LCJzdWIiOiI1Iiwic2NvcGVzIjpbXX0.Jz_sedcMtaZJ4dj0eWVc4_pr_wUQ3s1-UgpopFGhEmJt_iGzj6BdnOEEhcDDdIz-gydQL5ek0S_36v5h6P_X3OQyII3JmHp1SEDJMwrcy4FCY63-jGnhPBb4sprqUFruDRFSEIs1cNQ-3rv3qRDzJtGYc_bAkl2MfgZj85bvt2DDwBWPraZuCCkwz2fJvox-6qz6P7iK9YdQq8AjJfuNdl7t_1hMHixmtDG0KooVnfBV7PoChxvcWvs8FOmtYRdqD7RSEIoOXym2kcwqK-rmbWf9VuPQCN5gjLPimL4t2TbifBg5RWNIAAuHLcYzea48i3okbhkqGGlYTk3iVMU6Hf_Jruns1WJr3A961bd4rny62lNXyGPgNLRJJKedCs5lmtUTr4gZRec4Pz_MqDzlEYC3QzRAOZv0Ergp8-W1Vrv5gYyYNr-YQNdZ01mc7JH72N2dpU9G00K5kYxlcXDNVh8520-R-MrxYbmiFGVlNF2BzEH8qq6Ko9m0jT0NiKEOjetwegrbNdNq_oN4KmHvw2sHkGWY06rUeciYJMhBF1JZuRjj3JTwBUBVXcYZMFtwUAoikVByzKuaZZeTo1AtCiSjejSHNdpLxyKk_SFUzog5MOkUN1ktAhFnBFoz6SlWAJBJIS-lHYsdFLSug2YNiaNllkOUsDbYkiDtmPc9XWc",
Authorization: `Bearer ${KAI_BEARER_TOKEN}`,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle missing KAI_BEARER_TOKEN environment variable.

If KAI_BEARER_TOKEN is not set, the Authorization header will be "Bearer undefined", causing silent authentication failures. Consider adding validation or a fallback.

Proposed fix: fail fast if token is missing
-const KAI_BEARER_TOKEN = process.env.KAI_BEARER_TOKEN?.trim()
+const KAI_BEARER_TOKEN = process.env.KAI_BEARER_TOKEN?.trim()
+
+if (!KAI_BEARER_TOKEN) {
+  throw new Error("KAI_BEARER_TOKEN environment variable is required")
+}

 export const KAI_HEADERS = {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const KAI_BEARER_TOKEN = process.env.KAI_BEARER_TOKEN?.trim()
export const KAI_HEADERS = {
"User-Agent":
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:132.0) Gecko/20100101 Firefox/132.0",
Accept: "application/json, text/javascript, */*; q=0.01",
"Accept-Language": "en-US,en;q=0.5",
Authorization:
"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIzIiwianRpIjoiMDYzNWIyOGMzYzg3YTY3ZTRjYWE4YTI0MjYxZGYwYzIxNjYzODA4NWM2NWU4ZjhiYzQ4OGNlM2JiZThmYWNmODU4YzY0YmI0MjgyM2EwOTUiLCJpYXQiOjE3MjI2MTc1MTQsIm5iZiI6MTcyMjYxNzUxNCwiZXhwIjoxNzU0MTUzNTE0LCJzdWIiOiI1Iiwic2NvcGVzIjpbXX0.Jz_sedcMtaZJ4dj0eWVc4_pr_wUQ3s1-UgpopFGhEmJt_iGzj6BdnOEEhcDDdIz-gydQL5ek0S_36v5h6P_X3OQyII3JmHp1SEDJMwrcy4FCY63-jGnhPBb4sprqUFruDRFSEIs1cNQ-3rv3qRDzJtGYc_bAkl2MfgZj85bvt2DDwBWPraZuCCkwz2fJvox-6qz6P7iK9YdQq8AjJfuNdl7t_1hMHixmtDG0KooVnfBV7PoChxvcWvs8FOmtYRdqD7RSEIoOXym2kcwqK-rmbWf9VuPQCN5gjLPimL4t2TbifBg5RWNIAAuHLcYzea48i3okbhkqGGlYTk3iVMU6Hf_Jruns1WJr3A961bd4rny62lNXyGPgNLRJJKedCs5lmtUTr4gZRec4Pz_MqDzlEYC3QzRAOZv0Ergp8-W1Vrv5gYyYNr-YQNdZ01mc7JH72N2dpU9G00K5kYxlcXDNVh8520-R-MrxYbmiFGVlNF2BzEH8qq6Ko9m0jT0NiKEOjetwegrbNdNq_oN4KmHvw2sHkGWY06rUeciYJMhBF1JZuRjj3JTwBUBVXcYZMFtwUAoikVByzKuaZZeTo1AtCiSjejSHNdpLxyKk_SFUzog5MOkUN1ktAhFnBFoz6SlWAJBJIS-lHYsdFLSug2YNiaNllkOUsDbYkiDtmPc9XWc",
Authorization: `Bearer ${KAI_BEARER_TOKEN}`,
const KAI_BEARER_TOKEN = process.env.KAI_BEARER_TOKEN?.trim()
if (!KAI_BEARER_TOKEN) {
throw new Error("KAI_BEARER_TOKEN environment variable is required")
}
export const KAI_HEADERS = {
"User-Agent":
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:132.0) Gecko/20100101 Firefox/132.0",
Accept: "application/json, text/javascript, */*; q=0.01",
"Accept-Language": "en-US,en;q=0.5",
Authorization: `Bearer ${KAI_BEARER_TOKEN}`,
🤖 Prompt for AI Agents
In `@src/sync/headers.ts` around lines 1 - 8, The module currently builds
KAI_HEADERS using KAI_BEARER_TOKEN which can be "undefined"; update the module
to validate process.env.KAI_BEARER_TOKEN at import time and fail fast if missing
(e.g., throw a clear Error like "Missing KAI_BEARER_TOKEN env var") before
exporting KAI_HEADERS so Authorization is never "Bearer undefined"; reference
the KAI_BEARER_TOKEN constant and the KAI_HEADERS export when adding this check.

Comment on lines +51 to +64
const normalizeStationKey = (name: string) => {
let normalized = name
.toUpperCase()
.replace(/VIA\\s*MRI/g, "")
.replace(/VIAMRI/g, "")
.replace(/\\s+/g, "")
.replace(/[^A-Z0-9]/g, "")

if (normalized === "TANJUNGPRIUK") {
normalized = "TANJUNGPRIOK"
}

return normalized
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Regex escape sequences are incorrect.

In JavaScript regex literals, \\s matches a literal backslash followed by 's', not whitespace. Use single backslashes for regex metacharacters.

Proposed fix
 const normalizeStationKey = (name: string) => {
   let normalized = name
     .toUpperCase()
-    .replace(/VIA\\s*MRI/g, "")
-    .replace(/VIAMRI/g, "")
-    .replace(/\\s+/g, "")
-    .replace(/[^A-Z0-9]/g, "")
+    .replace(/VIA\s*MRI/g, "")
+    .replace(/VIAMRI/g, "")
+    .replace(/\s+/g, "")
+    .replace(/[^A-Z0-9]/g, "")

   if (normalized === "TANJUNGPRIUK") {
     normalized = "TANJUNGPRIOK"
   }

   return normalized
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const normalizeStationKey = (name: string) => {
let normalized = name
.toUpperCase()
.replace(/VIA\\s*MRI/g, "")
.replace(/VIAMRI/g, "")
.replace(/\\s+/g, "")
.replace(/[^A-Z0-9]/g, "")
if (normalized === "TANJUNGPRIUK") {
normalized = "TANJUNGPRIOK"
}
return normalized
}
const normalizeStationKey = (name: string) => {
let normalized = name
.toUpperCase()
.replace(/VIA\s*MRI/g, "")
.replace(/VIAMRI/g, "")
.replace(/\s+/g, "")
.replace(/[^A-Z0-9]/g, "")
if (normalized === "TANJUNGPRIUK") {
normalized = "TANJUNGPRIOK"
}
return normalized
}
🤖 Prompt for AI Agents
In `@src/sync/schedule.ts` around lines 51 - 64, The regexes in
normalizeStationKey use double-escaped sequences (e.g. /VIA\\s*MRI/g and
/\\s+/g) which match a backslash + 's' instead of whitespace; update those regex
literals to use single backslashes (e.g. /VIA\s*MRI/g and /\s+/g) so \s behaves
as the whitespace metacharacter, leaving other patterns like /VIAMRI/g and
/[^A-Z0-9]/g unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant