Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
fe301ed
feat(perf): implement lazy loading and code splitting for faster star…
sweetesty May 29, 2026
1c96410
feat(#384): Implement sandbox environment for developer testing
sweetesty May 31, 2026
c5d9e2a
refactor: Reorganize backend services into domain subdirectories
sweetesty May 31, 2026
ad92b72
Merge remote-tracking branch 'origin/main' into feature/384-sandbox-e…
sweetesty May 31, 2026
fb5378b
Merge origin/main into feat/410-lazy-loading-code-splitting
sweetesty May 31, 2026
d329adf
fix: Fix subscriptionStore syntax errors, broken imports, formatting,…
sweetesty May 31, 2026
906dcb3
fix: Fix AppNavigator lazyScreen type constraint and SettingsScreen i…
sweetesty May 31, 2026
2171870
fix: Fix subscriptionStore syntax errors, broken imports, formatting,…
sweetesty May 31, 2026
e25c3e8
fix: Fix subscriptionStore syntax errors, broken imports, formatting,…
sweetesty May 31, 2026
6197d3e
feat: implement affiliate marketing, multi-touch attribution, clawbac…
sweetesty May 31, 2026
acb8580
Merge branch 'main' of https://github.com/sweetesty/SubTrackr into fe…
sweetesty May 31, 2026
4dcc604
feat: merge feat/410-lazy-loading-code-splitting with conflict resolu…
sweetesty Jun 7, 2026
5d14613
feat: implement billing infrastructure including error handling, merc…
sweetesty Jun 7, 2026
3424d78
chore: merge main into feat/affiliate-referral-system
sweetesty Jun 7, 2026
0ced2b9
style: apply prettier and eslint --fix formatting
sweetesty Jun 7, 2026
1f73cd0
feat: merge feat/410-lazy-loading-code-splitting into feat/affiliate-…
sweetesty Jun 7, 2026
a9dddfc
Resolve git merge conflicts, fix styling bugs, and typecheck issues
sweetesty Jun 7, 2026
734f4c3
feat: implement gamification components, affiliate dashboard, and sup…
sweetesty Jun 7, 2026
2b3d468
chore: resolve merge conflicts
sweetesty Jun 7, 2026
81e3041
chore: allowlist 4 new axios advisories in audit-ci
sweetesty Jun 7, 2026
b0322c5
fix: resolve CI failures across Rust, k6, i18n, and npm audit
sweetesty Jun 7, 2026
b3b484a
fix: replace grafana/k6-action@v0 with direct k6 apt install
sweetesty Jun 7, 2026
9783999
fix: bump RUST_VERSION to 1.88 in CI
sweetesty Jun 7, 2026
b6b32a6
fix: resolve remaining CI failures in build and e2e workflows
sweetesty Jun 7, 2026
2ab1741
fix: resolve cargo-fuzz rustix breakage and babel cache conflict
sweetesty Jun 7, 2026
716e71d
fix: rust fmt, batch syntax error, k6 json import, ios workspace, kot…
sweetesty Jun 7, 2026
d2cc210
feat: add API contract tests, CI workflow, and necessary project depe…
sweetesty Jun 8, 2026
14734c6
feat: implement PaymentTimeoutService to handle transaction tracking,…
sweetesty Jun 8, 2026
45e3a55
feat: implement subscription plan change management with proration pr…
sweetesty Jun 8, 2026
02b0f2a
Merge branch 'Smartdevs17:main' into main
sweetesty Jun 8, 2026
44ecb15
Merge branch 'main' of https://github.com/sweetesty/SubTrackr into fe…
sweetesty Jun 8, 2026
53c8576
chore: implement postinstall script to patch Metro dependencies and a…
sweetesty Jun 8, 2026
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
1 change: 0 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
token: ${{ secrets.GITHUB_TOKEN }}

- name: Setup Node.js
Expand Down
10 changes: 5 additions & 5 deletions app/screens/AnalyticsDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ function createStyles(colors: ReturnType<typeof useThemeColors>) {
container: { flex: 1, backgroundColor: colors.background.primary },
scrollView: { flex: 1 },
header: { padding: spacing.lg, paddingBottom: spacing.sm },
title: { ...typography.h1, color: colors.text },
title: { ...typography.h1, color: colors.text.primary },
subtitle: { ...typography.body, color: colors.textSecondary, marginTop: spacing.xs },
row: {
flexDirection: 'row',
Expand All @@ -180,19 +180,19 @@ function createStyles(colors: ReturnType<typeof useThemeColors>) {
},
metricCard: { flex: 1, alignItems: 'center' },
metricLabel: { ...typography.caption, color: colors.textSecondary, marginBottom: spacing.xs },
metricValue: { ...typography.h2, color: colors.text },
metricValue: { ...typography.h2, color: colors.text.primary },
card: { marginHorizontal: spacing.lg, marginBottom: spacing.md },
sectionTitle: { ...typography.h3, color: colors.text, marginBottom: spacing.md },
sectionTitle: { ...typography.h3, color: colors.text.primary, marginBottom: spacing.md },
statRow: {
flexDirection: 'row',
justifyContent: 'space-between',
paddingVertical: spacing.sm,
borderBottomWidth: 1,
borderBottomColor: colors.border,
borderBottomColor: colors.border.default,
},
lastRow: { borderBottomWidth: 0 },
statLabel: { ...typography.body, color: colors.textSecondary },
statValue: { ...typography.body, color: colors.text, fontWeight: '600' },
statValue: { ...typography.body, color: colors.text.primary, fontWeight: '600' },
emptyText: { ...typography.body, color: colors.textSecondary, textAlign: 'center' },
exportContainer: { padding: spacing.lg, paddingTop: 0, marginBottom: spacing.xl },
loadingText: { ...typography.body, color: colors.textSecondary, padding: spacing.lg },
Expand Down
18 changes: 9 additions & 9 deletions app/screens/PaymentMethodsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -255,14 +255,14 @@ function createStyles(colors: ReturnType<typeof useThemeColors>) {
container: { flex: 1, backgroundColor: colors.background.primary },
scrollView: { flex: 1 },
card: { margin: spacing.lg, marginBottom: spacing.md },
sectionTitle: { ...typography.h3, color: colors.text, marginBottom: spacing.md },
sectionTitle: { ...typography.h3, color: colors.text.primary, marginBottom: spacing.md },
input: {
...typography.body,
borderWidth: 1,
borderColor: colors.border,
borderColor: colors.border.default,
borderRadius: borderRadius.md,
padding: spacing.md,
color: colors.text,
color: colors.text.primary,
backgroundColor: colors.surface,
marginBottom: spacing.md,
},
Expand All @@ -273,20 +273,20 @@ function createStyles(colors: ReturnType<typeof useThemeColors>) {
padding: spacing.sm,
borderRadius: borderRadius.md,
borderWidth: 1,
borderColor: colors.border,
borderColor: colors.border.default,
alignItems: 'center',
},
priorityOptionActive: { backgroundColor: colors.primary, borderColor: colors.primary },
priorityOptionText: { ...typography.caption, color: colors.text },
priorityOptionTextActive: { color: colors.text, fontWeight: '600' },
priorityOptionText: { ...typography.caption, color: colors.text.primary },
priorityOptionTextActive: { color: colors.text.inverse, fontWeight: '600' },
methodsSection: { padding: spacing.lg, paddingTop: 0 },
methodCard: { marginBottom: spacing.md },
methodHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: spacing.sm,
},
methodLabel: { ...typography.body, color: colors.text, fontWeight: '600' },
methodLabel: { ...typography.body, color: colors.text.primary, fontWeight: '600' },
methodToken: { ...typography.caption, color: colors.textSecondary },
methodBadges: { flexDirection: 'row', gap: spacing.sm, alignItems: 'center' },
priorityBadge: { paddingHorizontal: spacing.sm, paddingVertical: 2, borderRadius: borderRadius.sm },
Expand All @@ -299,14 +299,14 @@ function createStyles(colors: ReturnType<typeof useThemeColors>) {
flexWrap: 'wrap',
marginTop: spacing.sm,
},
expiringItem: { ...typography.body, color: colors.text, marginBottom: spacing.xs },
expiringItem: { ...typography.body, color: colors.text.primary, marginBottom: spacing.xs },
emptyText: { ...typography.body, color: colors.textSecondary, textAlign: 'center' },
attemptRow: {
flexDirection: 'row',
justifyContent: 'space-between',
paddingVertical: spacing.xs,
},
attemptLabel: { ...typography.caption, color: colors.text },
attemptLabel: { ...typography.caption, color: colors.text.primary },
attemptStatus: { ...typography.caption, fontWeight: '600' },
});
}
Expand Down
6 changes: 3 additions & 3 deletions backend/services/accessControl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
*/

import { randomUUID } from 'crypto';
import { AuditService } from './auditService';
import { AlertingService } from './alerting';
import type { Alert } from './types';
import { AuditService } from './shared/auditService';
import { AlertingService } from './notification/alerting';
import type { Alert } from './shared/types';

// ─── Resource & Action Types ──────────────────────────────────────────────────

Expand Down
4 changes: 2 additions & 2 deletions backend/services/affiliate/AffiliateService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AuditService } from '../auditService';
import type { AuditAction } from '../auditTypes';
import { AuditService } from '../shared/auditService';
import type { AuditAction } from '../shared/auditTypes';
import {
Affiliate,
AffiliateProgram,
Expand Down
4 changes: 2 additions & 2 deletions backend/services/paymentTimeoutService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
* - Stuck transaction alerting
*/

import type { AlertingService } from './alerting';
import type { Alert } from './types';
import type { AlertingService } from './notification/alerting';
import type { Alert } from './shared/types';

// ── Chain timeout configuration ───────────────────────────────────────────────

Expand Down
4 changes: 2 additions & 2 deletions backend/services/shared/__tests__/accessControl.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AccessControlService, ROLE_HIERARCHY, ROLE_PERMISSIONS } from '../accessControl';
import { AccessControlService, ROLE_HIERARCHY, ROLE_PERMISSIONS } from '../../accessControl';
import { AuditService } from '../auditService';
import { AlertingService } from '../alerting';
import { AlertingService } from '../../notification/alerting';

describe('AccessControlService', () => {
let svc: AccessControlService;
Expand Down
2 changes: 1 addition & 1 deletion backend/services/shared/__tests__/auditService.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AlertingService } from '../alerting';
import { AlertingService } from '../../notification/alerting';
import { AuditService } from '../auditService';

const SECRET = 'test-secret-key';
Expand Down
2 changes: 1 addition & 1 deletion backend/services/shared/auditService.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createHmac, randomUUID } from 'crypto';
import AsyncStorage from '@react-native-async-storage/async-storage';
import type { AlertingService, AlertDispatcher } from './alerting';
import type { AlertingService, AlertDispatcher } from '../notification/alerting';
import type {
AuditAction,
AuditArchiveEntry,
Expand Down
8 changes: 8 additions & 0 deletions commitlint.config.cjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'header-max-length': [0, 'always'],
'body-max-line-length': [0, 'always'],
'subject-case': [0, 'always'],
'type-empty': [0, 'always'],
'subject-empty': [0, 'always'],
'subject-full-stop': [0, 'never'],
},
};
2 changes: 1 addition & 1 deletion contracts/api/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ pub fn create_api_key(
.get(&DataKey::OwnerKeys(owner.clone()))
.unwrap_or(Vec::new(env));
assert!(
(owner_keys.len() as u32) < MAX_KEYS_PER_OWNER,
owner_keys.len() < MAX_KEYS_PER_OWNER,
"Max keys per owner reached"
);
let mut new_owner_keys = owner_keys;
Expand Down
12 changes: 3 additions & 9 deletions contracts/api/src/ratelimit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,9 @@ pub fn check_rate_limit(env: &Env, key: &ApiKey, now: u64) -> RateLimitStatus {
SECS_PER_DAY,
);

let exceeded = if min_count > cfg.requests_per_minute {
true
} else if hour_count > cfg.requests_per_hour {
true
} else if day_count > cfg.requests_per_day {
true
} else {
false
};
let exceeded = min_count > cfg.requests_per_minute
|| hour_count > cfg.requests_per_hour
|| day_count > cfg.requests_per_day;

if exceeded {
let reset_at = core::cmp::min(core::cmp::min(min_reset, hour_reset), day_reset);
Expand Down
3 changes: 1 addition & 2 deletions contracts/api/src/test.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#![cfg(test)]

use soroban_sdk::{testutils::Address as _, testutils::Ledger as _, Address, Bytes, BytesN, Env};
use subtrackr_types::{ApiKeyConfig, ApiKeyStatus, RateLimitConfig, TimeRange, UsageTier};

Expand Down Expand Up @@ -49,7 +48,7 @@ fn test_create_api_key() {

let (key_id, raw_key) = client.create_api_key(&owner, &default_config(&env));
assert!(key_id >= 1, "Key id should be >= 1");
assert!(raw_key.len() > 0, "Raw key bytes should not be empty");
assert!(!raw_key.is_empty(), "Raw key bytes should not be empty");

let stored = client.get_api_key(&key_id).unwrap();
assert_eq!(stored.id, key_id);
Expand Down
8 changes: 2 additions & 6 deletions contracts/fraud/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,10 +239,6 @@ fn build_evidence(
.device_fingerprint
.clone()
.unwrap_or_else(|| String::from_str(env, "unknown"));
let trusted = profile
.trusted_device_fingerprint
.clone()
.unwrap_or_else(|| String::from_str(env, "unknown"));
evidence.push_back(subtrackr_types::FraudEvidence {
label: String::from_str(env, "device mismatch"),
value: current,
Expand Down Expand Up @@ -661,7 +657,7 @@ impl SubTrackrFraud {
}

if let Some(case) = review_case_for_subscription(&env, score.subscription_id) {
if case.evidence.len() == 0 {
if case.evidence.is_empty() {
pending_evidence += 1;
}
if case.status == FraudReviewStatus::Dismissed {
Expand All @@ -670,7 +666,7 @@ impl SubTrackrFraud {
recent_cases.push_back(case);
} else if score.total_score >= REVIEW_THRESHOLD {
let case = persist_case(&env, &score, FraudReviewStatus::Pending);
if case.evidence.len() == 0 {
if case.evidence.is_empty() {
pending_evidence += 1;
}
recent_cases.push_back(case);
Expand Down
2 changes: 0 additions & 2 deletions contracts/oracle/src/test.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#![cfg(test)]

use super::*;
use soroban_sdk::{testutils::Address as _, testutils::Ledger as _, Address, Env, Symbol};

Expand Down
136 changes: 135 additions & 1 deletion contracts/types/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#![no_std]

use soroban_sdk::{contracttype, Address, String, Symbol, Vec};
use soroban_sdk::{contracttype, Address, BytesN, String, Symbol, Vec};

/// Billing interval in seconds.
#[contracttype]
Expand Down Expand Up @@ -745,3 +745,137 @@ pub struct PriceBounds {
/// Quote currency symbol used for price lookup (e.g. "USD").
pub quote: Symbol,
}

pub type ApiKeyId = u64;

#[contracttype]
#[derive(Clone, Debug, PartialEq)]
pub enum ApiKeyStatus {
Active,
Revoked,
Expired,
}

#[contracttype]
#[derive(Clone, Debug, PartialEq)]
pub enum UsageTier {
Free,
Basic,
Pro,
Enterprise,
}

impl UsageTier {
pub fn default_rate_limit(&self) -> RateLimitConfig {
match self {
UsageTier::Free => RateLimitConfig {
requests_per_minute: 100,
requests_per_hour: 1_000,
requests_per_day: 10_000,
burst_limit: 10,
},
UsageTier::Basic => RateLimitConfig {
requests_per_minute: 1_000,
requests_per_hour: 10_000,
requests_per_day: 100_000,
burst_limit: 50,
},
UsageTier::Pro => RateLimitConfig {
requests_per_minute: 10_000,
requests_per_hour: 100_000,
requests_per_day: 1_000_000,
burst_limit: 200,
},
UsageTier::Enterprise => RateLimitConfig {
requests_per_minute: 100_000,
requests_per_hour: 1_000_000,
requests_per_day: 10_000_000,
burst_limit: 1000,
},
}
}

pub fn price_per_thousand(&self) -> i128 {
match self {
UsageTier::Free => 0,
UsageTier::Basic => 1, // 0.001 per 1k requests (in stroops)
UsageTier::Pro => 5, // 0.005 per 1k
UsageTier::Enterprise => 10, // 0.01 per 1k
}
}
}

#[contracttype]
#[derive(Clone, Debug, PartialEq)]
pub struct RateLimitConfig {
pub requests_per_minute: u32,
pub requests_per_hour: u32,
pub requests_per_day: u32,
pub burst_limit: u32,
}

#[contracttype]
#[derive(Clone, Debug, PartialEq)]
pub struct ApiKeyConfig {
pub name: String,
pub rate_limit: RateLimitConfig,
pub usage_tier: UsageTier,
pub expires_at: u64,
}

#[contracttype]
#[derive(Clone, Debug, PartialEq)]
pub struct ApiKey {
pub id: ApiKeyId,
pub owner: Address,
pub key_hash: BytesN<32>,
pub name: String,
pub rate_limit: RateLimitConfig,
pub usage_tier: UsageTier,
pub status: ApiKeyStatus,
pub created_at: u64,
pub expires_at: u64,
pub last_used_at: u64,
pub revoked_at: u64,
}

#[contracttype]
#[derive(Clone, Debug, PartialEq)]
pub struct RateLimitWindow {
pub window_start: u64,
pub count: u32,
}

#[contracttype]
#[derive(Clone, Debug, PartialEq)]
pub struct ApiUsageRecord {
pub window_start: u64,
pub count: u32,
}

#[contracttype]
#[derive(Clone, Debug, PartialEq)]
pub struct RateLimitStatus {
pub is_allowed: bool,
pub remaining: u32,
pub reset_at: u64,
pub retry_after: u64,
}

#[contracttype]
#[derive(Clone, Debug, PartialEq)]
pub struct UsageReport {
pub key_id: ApiKeyId,
pub period: TimeRange,
pub total_requests: u32,
}

#[contracttype]
#[derive(Clone, Debug, PartialEq)]
pub struct ApiKeyAuditEntry {
pub id: u64,
pub key_id: ApiKeyId,
pub action: String,
pub changed_by: Address,
pub timestamp: u64,
}
Loading
Loading