Conversation
…, passwd, fetchJsonFromUrl) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sync shared CLAUDE rules
The mocking approach didn't work under this repo's swc transform: vi.mock only intercepts inlined local modules, not externalized node builtins/deps (bcrypt, fs/promises), so those mocks were never applied and the real implementations ran. Also removed tests for a non-existent getStartEndDates function (only getStartEnd exists). - passwd: test real bcrypt hash/verify behaviour + error wrapping - fetch-json: read a real temp file for the file-path branches - get-start-end-dates: drop the getStartEndDates block Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
test(#223): add unit tests for data utils
The compile-time type tests declare consts solely to trip their @ts-expect-error directives, so they are never read. Prefix them with `_` (the escape hatch the no-unused-vars rule is configured to allow) and apply Prettier formatting. Side effect: reference the shared .claude/shared-rules.md from CLAUDE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
chore(test): fix no-unused-vars lint errors in types.test.ts
…or-be add: reading port from env var
…#665) * ✨ link opportunity contact person to agent_person on legacy form submit When a legacy form submission creates an opportunity, the contact person is now also inserted into agent_person (VOLUNTEER_COORDINATOR) for the matched/created agent. This builds up the full list of known persons per agent over time without touching the existing representative. The submitter was already linked via getOrCreateSubmitterPerson; this covers the contact person which was previously only linked to the opportunity. Resolves #664 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(#665): use getOrCreateSubmitterPerson for contact person — dedup by email + guarded agent_person insert Replace the plain parseContactPerson + unconditional AgentPerson save with getOrCreateSubmitterPerson, which email-deduplicates the Person record and only inserts agent_person when the link does not already exist. Also removes the duplicate Person + AgentPerson creation from findOrCreateAgent's new-agent transaction: the handler now owns person resolution for both contactPersonId and submittedByPersonId. A bare parseContactPerson fallback is kept for the no-email case (no dedup possible, no agent_person link). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Arturas Mickiewicz <57557793+arturasmckwcz@users.noreply.github.com> Co-authored-by: arturasmckwcz <arturasmckwcz@gmail.com>
…d and POST /volunteer/ (#702) Exports langQuerySchema from querystring.ts and wires it into the three volunteer endpoints that accept a language param, so Swagger shows the parameter correctly. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(notify): extract shared email-template helper
Introduces `email-template.ts` with `fillTemplate`, `createManifestLoader`,
`resolveLocale`, and `resolveContent` so future email types (suggestions,
reminders, matching notifications) reuse the same CDN-manifest + placeholder
infrastructure without duplication.
- `fillTemplate(content, vars)` replaces all `{{ key }}` placeholders via
regex; unresolved keys are preserved in output and logged as warnings
- `createManifestLoader(url)` is a factory with per-instance TTL cache and
`resetCache()` for test isolation, replacing the module-level cache pattern
- `email-verification.ts` reduced from 141 → 70 lines; all infrastructure
imported from the new module, public API unchanged
- 20 new tests in `email-template.test.ts`; all 26 tests pass
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: widen TemplateVars to string|number, fix fake timer cleanup
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(#679): add ActivityLog entity and CRUD endpoints - ActivityLog entity (date + decimal hours) with FK → OpportunityVolunteer (CASCADE) - GET /opportunity-volunteer/:id/activity-log — returns entries + totalHours + count - POST /opportunity-volunteer/:id/activity-log — create entry - PATCH /activity-log/:id — update entry - DELETE /activity-log/:id — delete entry - All routes restricted to COORDINATOR | AGENT | ADMIN roles - SDK upgraded to 0.0.111 (ApiActivityLog* types) - Migration: add-activity-log Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(#679): address review findings on ActivityLog endpoints - Add transformer to decimal `hours` column so pg driver string is coerced to number at entity level instead of leaking as "2.50" - Extract dtoActivityLogEntry / dtoActivityLogGet; route handlers now go through DTO instead of returning raw entities - Remove redundant id <= 0 guards (idParamSchema already enforces minimum: 1) - Restrict PATCH/DELETE to COORDINATOR role via per-route authenticate() option; AGENT retains GET/POST access only Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add nullable `read_at TIMESTAMP` column to `comment_person` (migration)
- Replace `ApiComment.taggedPersonIds: number[]` with
`taggedPersons: { id, readAt }[]`; update commentSerializer and tests
- Add `ApiCommentTaggedPerson` schema to sdk-types.json
- PATCH /comment/:id/read — sets read_at = now() for the calling user's
comment_person row; returns updated comment; 404 if user is not tagged
- SDK upgraded to 0.0.112
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(fe#570): expose agentId on opportunity list response Add agentId to dtoOpportunityGetList and dtoOpportunityGet so the FE can compare each row's agentId against the authenticated user's agent and apply column masking for other orgs' opportunities. SDK → 0.0.113. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: agentId nullable in schema; SDK → 0.0.114 agentId type corrected to ["integer", "null"] to match the entity's nullable column. SDK bumped to 0.0.114 which carries the same fix. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Build and push ghcr.io/need4deed-org/be:develop on every merge to develop, then rollout restart the be deployment on the AITS VPS. Requires AITS_HOST and AITS_SSH_KEY repo secrets. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* ci: add AITS deploy workflow Build and push ghcr.io/need4deed-org/be:develop on every merge to develop, then rollout restart the be deployment on the AITS VPS. Requires AITS_HOST and AITS_SSH_KEY repo secrets. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci: build bootstrap image; make bootstrap idempotent - Add build-bootstrap.yaml workflow: builds ghcr.io/need4deed-org/bootstrap:latest on changes to data-bootstrap/ or manual dispatch - Make bootstrap.sh idempotent: skip if language table already exists Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* ci: add AITS deploy workflow Build and push ghcr.io/need4deed-org/be:develop on every merge to develop, then rollout restart the be deployment on the AITS VPS. Requires AITS_HOST and AITS_SSH_KEY repo secrets. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: gitignore data-bootstrap/*.sql — may contain PII Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Switch bootstrap from dev.dump.sql (raw PII) to scrambled-dump.sql (agent.info stripped, person/volunteer data anonymized) - Add idempotency check: skip if language table already exists - Narrow .gitignore to dev.dump.sql only so scrambled-dump.sql can be tracked Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
#712) Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Public POST /user/ blocks admin/coordinator roles by design. New POST /user/admin requires an active admin session and creates the account as immediately active (no email verification needed). Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* ✨ feat: add POST /user/admin for admin-privileged user creation Public POST /user/ blocks admin/coordinator roles by design. New POST /user/admin requires an active admin session and creates the account as immediately active (no email verification needed). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: add workflow_dispatch to deploy-aits workflow Allows manual re-deploys without needing a code push. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Bump need4deed-sdk 0.0.114 → 0.0.115 (adds agentId to ApiUserGet)
- serializeUserToMeDTO now returns ApiUserGet directly instead of ApiUserGet & { agentId?: number }
- Fix additionalProperties misplacement in sdk-types.json ApiUserMe schema (was inside properties, now a sibling)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Only AGENT-role users can have agent memberships; firing the agentPersonRepository queries for every user with a personId (volunteers, coordinators, admins) was wasted DB work on the hot session-check endpoint. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…MeDTO Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
chore: upgrade need4deed-sdk to 0.0.115, fix agentId handling in GET /user/me
Exercise trustLevel, email, and numOpportunities through real code paths instead of relying on fallback defaults. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…et-list Add num of opps to agent get list
Drop numActiveVolunteers from DTO and test (removed from SDK contract). Update test to use AgentTrustType.HIGH instead of bare number. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: upgrade need4deed-sdk to 0.0.120 Drop numActiveVolunteers from DTO and test (removed from SDK contract). Update test to use AgentTrustType.HIGH instead of bare number. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: switch to Infomaniak SMTP; redirect dry-run to test@; mute cron flag Replace Brevo with nodemailer SMTP (Infomaniak). DryRunEmailTransport now redirects to test@need4deed.org with [TO: <original>] subject prefix and actually delivers via the real transport. Add NOTIFY_CRON_MUTED env var to suppress cron email scans without disabling the scheduler. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: align SMTP_PASS env var name with infra Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…#741) SDK 0.0.122 adds the five CommunicationType values that were in the PostgreSQL enum since migration 1782903516653 but missing from the TypeScript enum (ACCOMPANYING_MATCHED, MATCHED, OPPORTUNITY_CONFIRMATION, ACCOMPANYING_NOT_FOUND, OPPORTUNITY_UPDATED) and adds opportunityId to ApiCommunicationGet — both were blocking tsc compilation. dtoAgentOpportunity now returns the full ApiAgentOpportunity shape (volunteerType, district, languages, activities, location, availability). The agent-opportunity route loads the deal relations needed to populate them. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Coordinators need to rename opportunities from the dashboard. The title field was present on the Opportunity entity and in GET responses but was not accepted by the PATCH endpoint. - Upgrade need4deed-sdk 0.0.122 → 0.0.125 (0.0.125 adds title to ApiOpportunityPatch) - Add "title" to ApiVolunteerOpportunityPatch JSON schema so Fastify/AJV accepts it (schema had additionalProperties: false) - Map body.title into the opportunity object in parseOpportunity so it reaches patchEntity(Opportunity, ...) Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Fastify's AJV coerces null → [null] for { type: "array" } fields.
When creating an event-type opportunity the frontend sends timeslots: null;
that null was coerced to [null], the parser looped over it, and tried to
destructure null as [day, daytime] → TypeError: opportunityTime is not iterable.
Change timeslots to { type: ["array", "null"] } so null passes through
unchanged; the existing parser guard (formData.timeslots || []) then
short-circuits to an empty array and the loop is never entered.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace four copy-pasted Set-intersection validation blocks in the POST and PATCH /post handlers with a single pure helper that throws a consistent BadRequestError for missing relation ids. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Infomaniak does not allow multiple senders on one SMTP account, so two separate transports are needed. The existing SMTP transport (SMTP_HOST / SMTP_USER / SMTP_PASS) is now exclusively for email-verification and password-reset. A new transport (SMTP_NOTIFY_HOST / SMTP_NOTIFY_USER / SMTP_NOTIFY_PASS / EMAIL_FROM_NOTIFY) handles all cron-scan and status-change notification emails. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a cc field to EmailMessage and wire it through SmtpEmailTransport
(passes to nodemailer) and DryRunEmailTransport (strips CC, appends
CC addresses to the [TO: ...] subject prefix for visibility).
Add emailFromAccompanying constant (accompanying@need4deed.org).
CC assignments per trigger:
- emailSuggestion → cc: volunteer@need4deed.org
- emailAccompanyMatch → cc: contact@need4deed.org
- emailIntroduction → cc: [contact@, volunteer@] (was incorrectly
stuffed into to[])
- emailNewAccompanying → cc: [contact@, accompanying@]
- emailStale → cc: volunteer@need4deed.org
- emailPostMatchCheckup → cc: volunteer@need4deed.org
- emailAccompanyNotFound → cc: accompanying@need4deed.org
- emailRegularUpdate → cc: contact@need4deed.org
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
EMAIL_FROM_VOLUNTEER, EMAIL_FROM_CONTACT, EMAIL_FROM_ACCOMPANYING fall back to the existing @need4deed.org defaults when unset. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All notification emails now send from coordinators@need4deed.org (the dedicated notify SMTP account). The cc addresses remain the role-specific ones (volunteer@, contact@, accompanying@) so replies still reach the right inbox. EMAIL_FROM_NOTIFY is configurable via env var. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat(notify): split SMTP into verify and notify transports
…cations feat(notify): add CC addresses to outbound notification emails
…treet alone (#750) The /agent/register/search guard required postcode, but the frontend only has the street at the time of typing. Make plz optional in getAgentByAddress (still used as a narrowing filter when provided) and drop !postcode from the route guard so the lookup fires as soon as street >= 3 chars. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…browser cookie (#751) n4d_pending_role cookie was fragile — absent when the link is opened in a different browser than where registration happened. Append ?role=agent to the verification URL for AGENT users so the redirect target is always in the URL. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* replace bootstrap SQL dump with clean seed infrastructure - Remove scrambled-dump.sql and bootstrap.sh from data-bootstrap/; the pg_dump approach was derived from production data and posed a PII risk even when scrambled. - Add dev/files/fixtures/ with three fake-data JSON files (nid-agents.json, nid-opportunities.json, nid-volunteers.json): 8 fake RACs, 12 opportunities, 15 volunteers, all cross-referenced by NID, zero real personal data. - Wire populate seeders to the fixtures directory (constants.ts paths updated from dev/files/notion/ to dev/files/fixtures/). - Add src/data/seeds/populate/agent-user.seed.ts: after agents are seeded, creates UserRole.USER accounts for the 5 RAC contact persons that carry an @example.com email in the fixture. - Expand user.seed.ts from 1+1 to 1 admin + 3 coordinators (sarah/michael/ julia.doe) + 1 dummy USER (anna.doe, kept as seed-utils fallback person). - Add src/data/seeds/run.ts entry point (initDatabase → seed) and a "seed" npm script so the bootstrap container can call yarn seed. - Rewrite data-bootstrap/Dockerfile: drop postgres:17 base, use node:22-alpine, install be deps, run yarn seed. - Update docker-compose.yaml: bootstrap now builds from the be/ root with dockerfile: data-bootstrap/Dockerfile; adds NODE_ENV=development and RUN_MIGRATIONS=true so migrations run before seeding. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * move populate fixtures from dev/ to src/data/seeds/fixtures/ dev/ is gitignored; fixtures are committed source files and belong alongside the seeder code. Update constants.ts paths accordingly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Dockerfile copies package.json, yarn.lock, and full source — needs the be/ root as context, not ./data-bootstrap alone. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
The 73-migration chain was written against a pre-existing schema and cannot replay from scratch — migration #2 fails with 'relation language does not exist'. This genesis migration (timestamp 1763036250000) runs first on any DB and: - Creates all enums, sequences, tables and constraints in their current final form using IF NOT EXISTS / DO EXCEPTION blocks (idempotent) - Marks all 73 existing migrations as already-run in be_migrations so TypeORM skips them on both fresh and existing DBs Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
TypeORM builds its full migration queue once by reading be_migrations, then runs every pending migration in order. Genesis creates the schema and inserts 73 records, but TypeORM already queued all 73 subsequent migrations — they then fail with "type already exists" conflicts. bootstrapFreshDb() now runs genesis.up() directly and inserts genesis's own record into be_migrations BEFORE dataSource.runMigrations() is called. On a fresh DB TypeORM therefore sees 74/74 migrations already done and runs zero. On an existing DB (cnt > 0) the function exits immediately. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…pre-populate-migrations fix: pre-populate be_migrations before TypeORM reads it on fresh DB
…uildtime fix(bootstrap): compile TypeScript at build time to reduce runtime memory
…ixtures fix(bootstrap): copy public assets and seed fixtures into production stage
…te passes (#758) * add: date util * add: job for onetimers expiration * add: new scheduler plugin for daily jobs and expires opps job * fix: change to berlin day before * fix: the same lock key and refactor duple * fix: refactor plugin names * fix: update comment * fix: use german calendar and update util with countdown * fix: remove stale code * fix: rename plugins * fix: add catching errs * test: add unit tests for scanExpiredOnetimers Covers the no-op empty-result case, correct INACTIVE/PAST status transitions, and the per-item try/catch isolation added for opportunity and opportunity-volunteer saves. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Production (`main`) hasn't been promoted from `develop` since PR #719 (2026-07-02) — `develop` is currently 101 commits ahead. This release catches production up, including (among other work):
Risk
Recommend reviewing/smoke-testing before merging rather than treating this as routine, given it's a production deploy.
Related