Skip to content

Commit 7297cbc

Browse files
committed
Resolve conflicts: merge main into PR #245 (combine both changes)
2 parents 0292a4d + dfa0c10 commit 7297cbc

54 files changed

Lines changed: 21100 additions & 6548 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/contracts.yml

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
name: Contracts
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- "contracts/**"
8+
pull_request:
9+
branches: [main]
10+
paths:
11+
- "contracts/**"
12+
13+
permissions:
14+
contents: read
15+
16+
concurrency:
17+
group: contracts-${{ github.workflow }}-${{ github.ref }}
18+
cancel-in-progress: true
19+
20+
jobs:
21+
build-contracts:
22+
runs-on: ubuntu-latest
23+
timeout-minutes: 20
24+
defaults:
25+
run:
26+
working-directory: contracts
27+
28+
steps:
29+
- uses: actions/checkout@v4
30+
31+
- name: Install Rust
32+
uses: dtolnay/rust-toolchain@stable
33+
with:
34+
targets: wasm32-unknown-unknown
35+
36+
- name: Cache Rust dependencies
37+
uses: Swatinem/rust-cache@v2
38+
with:
39+
workspaces: contracts
40+
41+
- name: Build contracts
42+
run: cargo build --target wasm32-unknown-unknown --release
43+
44+
- name: Run contract tests
45+
run: cargo test
46+
47+
- name: Check contract sizes
48+
shell: bash
49+
run: |
50+
set -euo pipefail
51+
shopt -s nullglob
52+
53+
wasm_files=(target/wasm32-unknown-unknown/release/*.wasm)
54+
55+
if [ ${#wasm_files[@]} -eq 0 ]; then
56+
echo "No WASM artifacts found in target/wasm32-unknown-unknown/release"
57+
exit 1
58+
fi
59+
60+
for wasm in "${wasm_files[@]}"; do
61+
size=$(wc -c < "$wasm")
62+
echo "$wasm: ${size} bytes"
63+
64+
if [ "$size" -gt 65536 ]; then
65+
echo "WARNING: Contract exceeds 64KB limit"
66+
fi
67+
done

.github/workflows/lint.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: Lint
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
lint-backend:
11+
runs-on: ubuntu-latest
12+
defaults:
13+
run:
14+
working-directory: backend
15+
steps:
16+
- uses: actions/checkout@v4
17+
- uses: actions/setup-node@v4
18+
with:
19+
node-version: '20'
20+
cache: npm
21+
cache-dependency-path: backend/package-lock.json
22+
- run: npm ci
23+
- run: npx eslint src --ext .ts --max-warnings 0
24+
25+
lint-client:
26+
runs-on: ubuntu-latest
27+
defaults:
28+
run:
29+
working-directory: client
30+
steps:
31+
- uses: actions/checkout@v4
32+
- uses: actions/setup-node@v4
33+
with:
34+
node-version: '20'
35+
cache: npm
36+
cache-dependency-path: client/package-lock.json
37+
- run: npm ci
38+
- run: npx next lint

.github/workflows/soroban_tests.yml

Lines changed: 0 additions & 29 deletions
This file was deleted.

.github/workflows/test.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
test-backend:
11+
runs-on: ubuntu-latest
12+
defaults:
13+
run:
14+
working-directory: backend
15+
env:
16+
NODE_ENV: test
17+
SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
18+
SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }}
19+
JWT_SECRET: test-secret
20+
ADMIN_API_KEY: test-admin-key
21+
steps:
22+
- uses: actions/checkout@v4
23+
- uses: actions/setup-node@v4
24+
with:
25+
node-version: '20'
26+
cache: 'npm'
27+
cache-dependency-path: backend/package-lock.json
28+
- run: npm ci
29+
- run: npm test -- --coverage --ci
30+
- uses: actions/upload-artifact@v4
31+
if: always()
32+
with:
33+
name: coverage-report
34+
path: backend/coverage/

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,4 @@ coverage/
100100
*.claude
101101
CLAUDE.md
102102

103-
backend/package-lock.json
103+
!backend/package-lock.json
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"specId": "7fce59ae-4f86-43e1-b223-34de74af24d0", "workflowType": "requirements-first", "specType": "feature"}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Requirements Document
2+
3+
## Introduction
4+
5+
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.
6+
7+
## Glossary
8+
9+
- **Slack_Service**: The SYNCRO backend service responsible for constructing and delivering messages to Slack via webhook URLs.
10+
- **Webhook_URL**: A Slack-provided HTTPS endpoint that accepts POST requests to deliver messages to a specific Slack channel or workspace.
11+
- **Reminder_Engine**: The existing SYNCRO component that schedules and dispatches renewal reminder notifications.
12+
- **Risk_Alert_Engine**: The existing SYNCRO component that detects and dispatches risk condition alerts for subscriptions.
13+
- **Renewal_Reminder**: A notification informing the user that a subscription is approaching its renewal date.
14+
- **Risk_Alert**: A notification informing the user that a subscription has entered a risk condition (e.g., approval expiring).
15+
- **Block_Kit_Message**: A Slack message formatted using Slack's Block Kit JSON structure, supporting sections, buttons, and markdown.
16+
- **Notification_Settings**: The SYNCRO user interface and backend configuration where users manage their notification preferences, including the Webhook_URL.
17+
- **User**: A SYNCRO account holder who manages subscriptions.
18+
- **Subscription**: A tracked software or service contract managed within SYNCRO.
19+
20+
---
21+
22+
## Requirements
23+
24+
### Requirement 1: Webhook URL Configuration
25+
26+
**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.
27+
28+
#### Acceptance Criteria
29+
30+
1. THE Notification_Settings SHALL provide a field for the User to input a Webhook_URL.
31+
2. WHEN the User submits a Webhook_URL, THE Slack_Service SHALL send a test POST request to the provided Webhook_URL before saving.
32+
3. WHEN the test POST request returns an HTTP 200 response, THE Notification_Settings SHALL save the Webhook_URL to the User's profile.
33+
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.
34+
5. WHEN a Webhook_URL is saved, THE Notification_Settings SHALL display a confirmation to the User that Slack notifications are enabled.
35+
6. THE Notification_Settings SHALL allow the User to remove a previously saved Webhook_URL, disabling Slack notifications.
36+
37+
---
38+
39+
### Requirement 2: Renewal Reminder Delivery
40+
41+
**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.
42+
43+
#### Acceptance Criteria
44+
45+
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.
46+
2. THE Slack_Service SHALL format Renewal_Reminder messages as Block_Kit_Messages containing the subscription name, days until renewal, and renewal cost.
47+
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.
48+
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.
49+
50+
---
51+
52+
### Requirement 3: Risk Alert Delivery
53+
54+
**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.
55+
56+
#### Acceptance Criteria
57+
58+
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.
59+
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.
60+
3. THE Slack_Service SHALL include a "Renew Approval" button and a "View Dashboard" button in each Risk_Alert Block_Kit_Message.
61+
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.
62+
63+
---
64+
65+
### Requirement 4: Delivery Failure Isolation
66+
67+
**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.
68+
69+
#### Acceptance Criteria
70+
71+
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.
72+
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.
73+
3. WHILE a Slack delivery attempt is in progress, THE Reminder_Engine SHALL continue processing remaining notification channels independently of the Slack delivery result.
74+
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.
75+
76+
---
77+
78+
### Requirement 5: Message Construction
79+
80+
**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.
81+
82+
#### Acceptance Criteria
83+
84+
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.
85+
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.
86+
3. THE Slack_Service SHALL prefix all outgoing messages with the "[SYNCRO]" identifier.
87+
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.

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# SYNCRO
2+
3+
![Tests](https://github.com/Calebux/SYNCRO/actions/workflows/test.yml/badge.svg)
4+
15
## Synchro — Self-Custodial Subscription Manager (MVP)
26

37
Synchro is a decentralized, self-custodial subscription management platform that empowers users to take full control of their recurring payments while using crypto. This MVP focuses on gift card–compatible subscriptions and optional email-based subscription detection, pending future automation with non-custodial card issuance on Stellar.

backend/.eslintrc.cjs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
module.exports = {
2+
root: true,
3+
env: {
4+
node: true,
5+
es2022: true,
6+
jest: true,
7+
},
8+
parser: "@typescript-eslint/parser",
9+
parserOptions: {
10+
project: "./tsconfig.json",
11+
tsconfigRootDir: __dirname,
12+
},
13+
plugins: ["@typescript-eslint"],
14+
extends: [
15+
"eslint:recommended",
16+
"plugin:@typescript-eslint/recommended",
17+
],
18+
rules: {
19+
"@typescript-eslint/no-explicit-any": "error",
20+
"@typescript-eslint/no-floating-promises": "error",
21+
"no-console": "warn",
22+
"@typescript-eslint/no-unused-vars": "error",
23+
},
24+
};

backend/jest.config.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,12 @@ module.exports = {
2626
transformIgnorePatterns: [
2727
'/node_modules/(?!@stellar/stellar-sdk)',
2828
],
29+
coverageThreshold: {
30+
global: {
31+
branches: 80,
32+
functions: 80,
33+
lines: 80,
34+
statements: 80,
35+
},
36+
},
2937
};

0 commit comments

Comments
 (0)