Skip to content

Commit d2cc210

Browse files
committed
feat: add API contract tests, CI workflow, and necessary project dependencies
1 parent 716e71d commit d2cc210

6 files changed

Lines changed: 309 additions & 3 deletions

File tree

.github/workflows/ci.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ jobs:
5151
- name: Checkout code
5252
uses: actions/checkout@v4
5353
with:
54-
ref: ${{ github.head_ref }}
5554
token: ${{ secrets.GITHUB_TOKEN }}
5655

5756
- name: Setup Node.js

commitlint.config.cjs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
11
module.exports = {
22
extends: ['@commitlint/config-conventional'],
3+
rules: {
4+
'header-max-length': [0, 'always'],
5+
'body-max-line-length': [0, 'always'],
6+
'subject-case': [0, 'always'],
7+
'type-empty': [0, 'always'],
8+
'subject-empty': [0, 'always'],
9+
},
310
};

contracts/api/src/test.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
#![cfg(test)]
21

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

contracts/types/src/lib.rs

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#![no_std]
22

3-
use soroban_sdk::{contracttype, Address, String, Symbol, Vec};
3+
use soroban_sdk::{contracttype, Address, BytesN, String, Symbol, Vec};
44

55
/// Billing interval in seconds.
66
#[contracttype]
@@ -745,3 +745,137 @@ pub struct PriceBounds {
745745
/// Quote currency symbol used for price lookup (e.g. "USD").
746746
pub quote: Symbol,
747747
}
748+
749+
pub type ApiKeyId = u64;
750+
751+
#[contracttype]
752+
#[derive(Clone, Debug, PartialEq)]
753+
pub enum ApiKeyStatus {
754+
Active,
755+
Revoked,
756+
Expired,
757+
}
758+
759+
#[contracttype]
760+
#[derive(Clone, Debug, PartialEq)]
761+
pub enum UsageTier {
762+
Free,
763+
Basic,
764+
Pro,
765+
Enterprise,
766+
}
767+
768+
impl UsageTier {
769+
pub fn default_rate_limit(&self) -> RateLimitConfig {
770+
match self {
771+
UsageTier::Free => RateLimitConfig {
772+
requests_per_minute: 100,
773+
requests_per_hour: 1_000,
774+
requests_per_day: 10_000,
775+
burst_limit: 10,
776+
},
777+
UsageTier::Basic => RateLimitConfig {
778+
requests_per_minute: 1_000,
779+
requests_per_hour: 10_000,
780+
requests_per_day: 100_000,
781+
burst_limit: 50,
782+
},
783+
UsageTier::Pro => RateLimitConfig {
784+
requests_per_minute: 10_000,
785+
requests_per_hour: 100_000,
786+
requests_per_day: 1_000_000,
787+
burst_limit: 200,
788+
},
789+
UsageTier::Enterprise => RateLimitConfig {
790+
requests_per_minute: 100_000,
791+
requests_per_hour: 1_000_000,
792+
requests_per_day: 10_000_000,
793+
burst_limit: 1000,
794+
},
795+
}
796+
}
797+
798+
pub fn price_per_thousand(&self) -> i128 {
799+
match self {
800+
UsageTier::Free => 0,
801+
UsageTier::Basic => 1, // 0.001 per 1k requests (in stroops)
802+
UsageTier::Pro => 5, // 0.005 per 1k
803+
UsageTier::Enterprise => 10, // 0.01 per 1k
804+
}
805+
}
806+
}
807+
808+
#[contracttype]
809+
#[derive(Clone, Debug, PartialEq)]
810+
pub struct RateLimitConfig {
811+
pub requests_per_minute: u32,
812+
pub requests_per_hour: u32,
813+
pub requests_per_day: u32,
814+
pub burst_limit: u32,
815+
}
816+
817+
#[contracttype]
818+
#[derive(Clone, Debug, PartialEq)]
819+
pub struct ApiKeyConfig {
820+
pub name: String,
821+
pub rate_limit: RateLimitConfig,
822+
pub usage_tier: UsageTier,
823+
pub expires_at: u64,
824+
}
825+
826+
#[contracttype]
827+
#[derive(Clone, Debug, PartialEq)]
828+
pub struct ApiKey {
829+
pub id: ApiKeyId,
830+
pub owner: Address,
831+
pub key_hash: BytesN<32>,
832+
pub name: String,
833+
pub rate_limit: RateLimitConfig,
834+
pub usage_tier: UsageTier,
835+
pub status: ApiKeyStatus,
836+
pub created_at: u64,
837+
pub expires_at: u64,
838+
pub last_used_at: u64,
839+
pub revoked_at: u64,
840+
}
841+
842+
#[contracttype]
843+
#[derive(Clone, Debug, PartialEq)]
844+
pub struct RateLimitWindow {
845+
pub window_start: u64,
846+
pub count: u32,
847+
}
848+
849+
#[contracttype]
850+
#[derive(Clone, Debug, PartialEq)]
851+
pub struct ApiUsageRecord {
852+
pub window_start: u64,
853+
pub count: u32,
854+
}
855+
856+
#[contracttype]
857+
#[derive(Clone, Debug, PartialEq)]
858+
pub struct RateLimitStatus {
859+
pub is_allowed: bool,
860+
pub remaining: u32,
861+
pub reset_at: u64,
862+
pub retry_after: u64,
863+
}
864+
865+
#[contracttype]
866+
#[derive(Clone, Debug, PartialEq)]
867+
pub struct UsageReport {
868+
pub key_id: ApiKeyId,
869+
pub period: TimeRange,
870+
pub total_requests: u32,
871+
}
872+
873+
#[contracttype]
874+
#[derive(Clone, Debug, PartialEq)]
875+
pub struct ApiKeyAuditEntry {
876+
pub id: u64,
877+
pub key_id: ApiKeyId,
878+
pub action: String,
879+
pub changed_by: Address,
880+
pub timestamp: u64,
881+
}

package-lock.json

Lines changed: 165 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@
9999
},
100100
"devDependencies": {
101101
"@babel/core": "^7.29.0",
102+
"babel-plugin-module-resolver": "^5.0.2",
103+
"babel-plugin-transform-remove-console": "^6.3.0",
102104
"@commitlint/cli": "^20.5.2",
103105
"@commitlint/config-conventional": "^20.5.0",
104106
"@config-plugins/detox": "^11.0.0",

0 commit comments

Comments
 (0)