Skip to content
Merged
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
40 changes: 40 additions & 0 deletions .github/workflows/database.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Database Migrations

on:
push:
branches: [main, develop]
paths:
- 'supabase/migrations/**'
- 'supabase/config.toml'
pull_request:
branches: [main, develop]
paths:
- 'supabase/migrations/**'
- 'supabase/config.toml'

jobs:
validate-migrations:
name: Validate Migrations
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Supabase CLI
uses: supabase/setup-cli@v1
with:
version: latest

- name: Start Supabase local stack
run: supabase start

- name: Apply all migrations
run: supabase db push

- name: Lint SQL migrations
run: supabase db lint

- name: Stop Supabase local stack
if: always()
run: supabase stop
1 change: 1 addition & 0 deletions .kiro/specs/slack-notifications/.config.kiro
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"specId": "7fce59ae-4f86-43e1-b223-34de74af24d0", "workflowType": "requirements-first", "specType": "feature"}
87 changes: 87 additions & 0 deletions .kiro/specs/slack-notifications/requirements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Requirements Document

## Introduction

This feature adds Slack notification support to SYNCRO, allowing users to receive renewal reminders and risk alerts directly in their Slack workspace via Incoming Webhooks. Users configure a Slack webhook URL in their notification settings, and SYNCRO posts richly formatted messages with actionable buttons when subscriptions are approaching renewal or when risk conditions are detected. Slack delivery is additive — failures must not interfere with existing email delivery.

## Glossary

- **Slack_Service**: The SYNCRO backend service responsible for constructing and delivering messages to Slack via webhook URLs.
- **Webhook_URL**: A Slack-provided HTTPS endpoint that accepts POST requests to deliver messages to a specific Slack channel or workspace.
- **Reminder_Engine**: The existing SYNCRO component that schedules and dispatches renewal reminder notifications.
- **Risk_Alert_Engine**: The existing SYNCRO component that detects and dispatches risk condition alerts for subscriptions.
- **Renewal_Reminder**: A notification informing the user that a subscription is approaching its renewal date.
- **Risk_Alert**: A notification informing the user that a subscription has entered a risk condition (e.g., approval expiring).
- **Block_Kit_Message**: A Slack message formatted using Slack's Block Kit JSON structure, supporting sections, buttons, and markdown.
- **Notification_Settings**: The SYNCRO user interface and backend configuration where users manage their notification preferences, including the Webhook_URL.
- **User**: A SYNCRO account holder who manages subscriptions.
- **Subscription**: A tracked software or service contract managed within SYNCRO.

---

## Requirements

### Requirement 1: Webhook URL Configuration

**User Story:** As a User, I want to add my Slack webhook URL in SYNCRO notification settings, so that I can receive SYNCRO alerts in my Slack workspace.

#### Acceptance Criteria

1. THE Notification_Settings SHALL provide a field for the User to input a Webhook_URL.
2. WHEN the User submits a Webhook_URL, THE Slack_Service SHALL send a test POST request to the provided Webhook_URL before saving.
3. WHEN the test POST request returns an HTTP 200 response, THE Notification_Settings SHALL save the Webhook_URL to the User's profile.
4. IF the test POST request returns a non-200 response or times out within 5 seconds, THEN THE Notification_Settings SHALL reject the Webhook_URL and display a descriptive error message to the User.
5. WHEN a Webhook_URL is saved, THE Notification_Settings SHALL display a confirmation to the User that Slack notifications are enabled.
6. THE Notification_Settings SHALL allow the User to remove a previously saved Webhook_URL, disabling Slack notifications.

---

### Requirement 2: Renewal Reminder Delivery

**User Story:** As a User, I want to receive renewal reminder notifications in Slack, so that I can act on upcoming subscription renewals without leaving my workspace.

#### Acceptance Criteria

1. WHEN the Reminder_Engine triggers a renewal reminder and the User has a saved Webhook_URL, THE Slack_Service SHALL deliver a Renewal_Reminder message to the Webhook_URL.
2. THE Slack_Service SHALL format Renewal_Reminder messages as Block_Kit_Messages containing the subscription name, days until renewal, and renewal cost.
3. THE Slack_Service SHALL include a "Renew Now" button, a "View Details" button, and a "Snooze" button in each Renewal_Reminder Block_Kit_Message.
4. WHEN the Reminder_Engine triggers a renewal reminder and the User does not have a saved Webhook_URL, THE Reminder_Engine SHALL skip Slack delivery and proceed with other configured notification channels.

---

### Requirement 3: Risk Alert Delivery

**User Story:** As a User, I want to receive risk alerts in Slack, so that I can respond quickly to time-sensitive subscription risk conditions.

#### Acceptance Criteria

1. WHEN the Risk_Alert_Engine triggers a risk alert and the User has a saved Webhook_URL, THE Slack_Service SHALL deliver a Risk_Alert message to the Webhook_URL.
2. THE Slack_Service SHALL format Risk_Alert messages as Block_Kit_Messages containing the subscription name, a description of the risk condition, and the time remaining before the risk condition escalates.
3. THE Slack_Service SHALL include a "Renew Approval" button and a "View Dashboard" button in each Risk_Alert Block_Kit_Message.
4. WHEN the Risk_Alert_Engine triggers a risk alert and the User does not have a saved Webhook_URL, THE Risk_Alert_Engine SHALL skip Slack delivery and proceed with other configured notification channels.

---

### Requirement 4: Delivery Failure Isolation

**User Story:** As a User, I want Slack delivery failures to be handled gracefully, so that a Slack outage or misconfiguration does not prevent me from receiving notifications through other channels.

#### Acceptance Criteria

1. IF the Slack_Service receives a non-200 HTTP response from a Webhook_URL during message delivery, THEN THE Slack_Service SHALL log the failure with the HTTP status code and continue execution without throwing an exception to the caller.
2. IF the Slack_Service does not receive a response from a Webhook_URL within 5 seconds, THEN THE Slack_Service SHALL log a timeout error and continue execution without throwing an exception to the caller.
3. WHILE a Slack delivery attempt is in progress, THE Reminder_Engine SHALL continue processing remaining notification channels independently of the Slack delivery result.
4. WHILE a Slack delivery attempt is in progress, THE Risk_Alert_Engine SHALL continue processing remaining notification channels independently of the Slack delivery result.

---

### Requirement 5: Message Construction

**User Story:** As a User, I want Slack messages to be clearly formatted and actionable, so that I can understand the alert context and take action without navigating to SYNCRO.

#### Acceptance Criteria

1. THE Slack_Service SHALL construct Renewal_Reminder Block_Kit_Messages using the subscription name, the number of days until renewal, and the renewal cost formatted as a currency value.
2. THE Slack_Service SHALL construct Risk_Alert Block_Kit_Messages using the subscription name, the risk condition description, and the time remaining until the risk condition escalates.
3. THE Slack_Service SHALL prefix all outgoing messages with the "[SYNCRO]" identifier.
4. WHEN a Block_Kit_Message is constructed with a missing required field (subscription name, renewal date, or cost for reminders; subscription name or risk description for alerts), THE Slack_Service SHALL return a descriptive error and not attempt delivery.
100 changes: 100 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Contributing to SYNCRO

## Database Migrations

SYNCRO uses the [Supabase CLI](https://supabase.com/docs/guides/cli) to manage database migrations.
All migration files live in `supabase/migrations/` and are applied in lexicographic order.

### Prerequisites

Install the Supabase CLI:

```bash
# macOS / Linux (Homebrew)
brew install supabase/tap/supabase

# Windows (Scoop)
scoop bucket add supabase https://github.com/supabase/scoop-bucket.git
scoop install supabase

# npm (any platform)
npm install -g supabase
```

### Local development setup

```bash
# 1. Start the local Supabase stack (Postgres + Studio + Auth)
supabase start

# 2. Apply all pending migrations
npm run db:migrate # from /backend, or: supabase db push

# 3. Seed local database with test data
supabase db reset # applies migrations + seed.sql automatically
```

The local Studio UI is available at http://localhost:54323.

### Creating a new migration

Always use the CLI to generate migration files — this ensures the timestamp prefix is correct:

```bash
# From the repo root
supabase migration new <description>
# e.g. supabase migration new add_notifications_table
```

This creates `supabase/migrations/YYYYMMDDHHMMSS_<description>.sql`.
Write your SQL in that file, then apply it locally with `supabase db push`.

### Migration naming convention

```
YYYYMMDDHHMMSS_short_description.sql
```

Examples:
- `20240115000000_create_push_subscriptions.sql`
- `20240117000000_add_2fa_tables.sql`

### Applying migrations

| Environment | Command |
|-------------|---------|
| Local | `npm run db:migrate` |
| Production | `npm run db:migrate:prod` (requires `PRODUCTION_DB_URL` env var) |
| Reset local | `npm run db:reset` |

### Rollback strategy

Supabase does not support automatic down migrations. For each migration that makes
destructive changes, document the manual rollback steps in a comment block at the
top of the migration file:

```sql
-- ROLLBACK:
-- ALTER TABLE public.example DROP COLUMN IF EXISTS new_column;
```

For non-destructive migrations (adding tables, indexes, columns with defaults),
the rollback is simply dropping the added object.

### CI validation

Every pull request that touches `supabase/migrations/` triggers the
`.github/workflows/database.yml` workflow, which:

1. Starts a fresh local Supabase stack
2. Applies all migrations from scratch (`supabase db push`)
3. Runs `supabase db lint` to catch SQL issues

A PR cannot be merged if this workflow fails.

### Seed data

`supabase/seed.sql` contains fake data for local development only.
It is applied automatically by `supabase db reset`.

**Never add real emails, payment data, or any PII to seed.sql.**
7 changes: 6 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@
"start": "node dist/index.js",
"dev": "ts-node-dev --respawn --transpile-only src/index.ts",
"validate-env": "node scripts/validate-env.js",
"test": "jest"
"test": "jest",
"swagger:export": "ts-node scripts/export-swagger.ts",
"db:migrate": "supabase db push",
"db:migrate:prod": "supabase db push --db-url \"$PRODUCTION_DB_URL\"",
"db:reset": "supabase db reset",
"db:new": "supabase migration new"
},
"dependencies": {
"@supabase/supabase-js": "^2.47.10",
Expand Down
11 changes: 11 additions & 0 deletions backend/scripts/export-swagger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Exports the OpenAPI spec to openapi.json
* Run with: npx ts-node scripts/export-swagger.ts
*/
import * as fs from 'fs';
import * as path from 'path';
import { swaggerSpec } from '../src/swagger';

const outputPath = path.join(__dirname, '..', 'openapi.json');
fs.writeFileSync(outputPath, JSON.stringify(swaggerSpec, null, 2));
console.log(`OpenAPI spec exported to ${outputPath}`);
Loading