@@ -15,7 +15,15 @@ type SeedCampaign = {
1515 claimedAt : number | null ;
1616} ;
1717
18- const SEED_CAMPAIGNS : SeedCampaign [ ] = [
18+ type SeedPledge = {
19+ campaignId : string ;
20+ contributor : string ;
21+ amount : number ;
22+ assetCode : string ;
23+ createdAt : number ;
24+ } ;
25+
26+ const BASE_CAMPAIGNS : SeedCampaign [ ] = [
1927 {
2028 id : '1' ,
2129 creator : `G${ 'A' . repeat ( 55 ) } ` ,
@@ -54,10 +62,107 @@ const SEED_CAMPAIGNS: SeedCampaign[] = [
5462 } ,
5563] ;
5664
57- export function seedDeterministicState ( ) : void {
65+ const BASE_PLEDGES : SeedPledge [ ] = [
66+ { campaignId : '1' , contributor : `G${ 'D' . repeat ( 55 ) } ` , amount : 100 , assetCode : 'USDC' , createdAt : FIXED_NOW - 250 } ,
67+ { campaignId : '2' , contributor : `G${ 'E' . repeat ( 55 ) } ` , amount : 250 , assetCode : 'XLM' , createdAt : FIXED_NOW - 150 } ,
68+ ] ;
69+
70+ // Deterministic status/asset rotation used to extend past the 3 base campaigns.
71+ // Each generated campaign gets one matching pledge, same as the base set.
72+ const EXTRA_STATUS_CYCLE : Array < 'open' | 'funded' | 'claimed' > = [ 'open' , 'funded' , 'claimed' ] ;
73+ const EXTRA_ASSET_CYCLE = [ 'USDC' , 'XLM' ] ;
74+
75+ function letterFor ( index : number ) : string {
76+ // A-Z, then AA, BB... style wrap for very large counts (index is 0-based
77+ // continuing on from the 3 base campaigns, which already used A/B/C).
78+ const cycleIndex = index % 26 ;
79+ const letter = String . fromCharCode ( 65 + cycleIndex ) ;
80+ return letter ;
81+ }
82+
83+ function buildExtraCampaign ( index : number ) : { campaign : SeedCampaign ; pledge : SeedPledge } {
84+ // index is 0-based for campaigns beyond the 3 base ones, so real id = index + 4
85+ const id = String ( index + 4 ) ;
86+ const letter = letterFor ( index + 3 ) ; // continue the A/B/C sequence
87+ const creator = `G${ letter . repeat ( 55 ) } ` ;
88+ const status = EXTRA_STATUS_CYCLE [ index % EXTRA_STATUS_CYCLE . length ] ;
89+ const assetCode = EXTRA_ASSET_CYCLE [ index % EXTRA_ASSET_CYCLE . length ] ;
90+
91+ const targetAmount = 200 + index * 50 ;
92+ let pledgedAmount : number ;
93+ let deadline : number ;
94+ let claimedAt : number | null ;
95+
96+ if ( status === 'open' ) {
97+ pledgedAmount = Math . floor ( targetAmount * 0.4 ) ;
98+ deadline = FIXED_NOW + 86_400 + index * 1_000 ;
99+ claimedAt = null ;
100+ } else if ( status === 'funded' ) {
101+ pledgedAmount = targetAmount ;
102+ deadline = FIXED_NOW + 43_200 + index * 1_000 ;
103+ claimedAt = null ;
104+ } else {
105+ pledgedAmount = targetAmount ;
106+ deadline = FIXED_NOW - 600 - index * 1_000 ;
107+ claimedAt = FIXED_NOW - 100 - index * 1_000 ;
108+ }
109+
110+ const campaign : SeedCampaign = {
111+ id,
112+ creator,
113+ title : `Deterministic campaign ${ id } (${ status } )` ,
114+ description : `Generated deterministic campaign seed #${ id } for ${ status } status checks.` ,
115+ assetCode,
116+ targetAmount,
117+ pledgedAmount,
118+ deadline,
119+ createdAt : FIXED_NOW - 300 - index * 100 ,
120+ claimedAt,
121+ } ;
122+
123+ const pledgeLetter = letterFor ( index + 3 + 100 ) ; // distinct pool from campaign creators
124+ const pledge : SeedPledge = {
125+ campaignId : id ,
126+ contributor : `G${ pledgeLetter . repeat ( 55 ) } ` ,
127+ amount : pledgedAmount > 0 ? pledgedAmount : 1 ,
128+ assetCode,
129+ createdAt : FIXED_NOW - 250 - index * 100 ,
130+ } ;
131+
132+ return { campaign, pledge } ;
133+ }
134+
135+ function buildSeedSet ( count : number ) : { campaigns : SeedCampaign [ ] ; pledges : SeedPledge [ ] } {
136+ if ( count <= 0 ) {
137+ throw new Error ( 'count must be a positive integer' ) ;
138+ }
139+
140+ if ( count <= BASE_CAMPAIGNS . length ) {
141+ const campaigns = BASE_CAMPAIGNS . slice ( 0 , count ) ;
142+ const ids = new Set ( campaigns . map ( ( c ) => c . id ) ) ;
143+ const pledges = BASE_PLEDGES . filter ( ( p ) => ids . has ( p . campaignId ) ) ;
144+ return { campaigns, pledges } ;
145+ }
146+
147+ const extraCount = count - BASE_CAMPAIGNS . length ;
148+ const extras = Array . from ( { length : extraCount } , ( _ , i ) => buildExtraCampaign ( i ) ) ;
149+
150+ return {
151+ campaigns : [ ...BASE_CAMPAIGNS , ...extras . map ( ( e ) => e . campaign ) ] ,
152+ pledges : [ ...BASE_PLEDGES , ...extras . map ( ( e ) => e . pledge ) ] ,
153+ } ;
154+ }
155+
156+ /**
157+ * Wipes and repopulates the dev database with `count` deterministic campaigns
158+ * (default 3). Returns the seeded campaign IDs in insertion order.
159+ */
160+ export function seedDeterministicState ( count : number = BASE_CAMPAIGNS . length ) : string [ ] {
58161 initDb ( ) ;
59162 const db = getDb ( ) ;
60163
164+ const { campaigns, pledges } = buildSeedSet ( count ) ;
165+
61166 db . prepare ( `DELETE FROM campaign_events` ) . run ( ) ;
62167 db . prepare ( `DELETE FROM pledges` ) . run ( ) ;
63168 db . prepare ( `DELETE FROM campaigns` ) . run ( ) ;
@@ -68,7 +173,7 @@ export function seedDeterministicState(): void {
68173 ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)` ,
69174 ) ;
70175
71- for ( const campaign of SEED_CAMPAIGNS ) {
176+ for ( const campaign of campaigns ) {
72177 insertCampaign . run (
73178 campaign . id ,
74179 campaign . creator ,
@@ -83,19 +188,32 @@ export function seedDeterministicState(): void {
83188 ) ;
84189 }
85190
86- db . prepare (
191+ const insertPledge = db . prepare (
87192 `INSERT INTO pledges (campaign_id, contributor, amount, asset_code, created_at, refunded_at, transaction_hash)
88193 VALUES (?, ?, ?, ?, ?, NULL, NULL)` ,
89- ) . run ( '1' , `G ${ 'D' . repeat ( 55 ) } ` , 100 , 'USDC' , FIXED_NOW - 250 ) ;
194+ ) ;
90195
91- db . prepare (
92- `INSERT INTO pledges (campaign_id, contributor, amount, asset_code, created_at, refunded_at, transaction_hash)
93- VALUES (?, ?, ?, ?, ?, NULL, NULL)` ,
94- ) . run ( '2' , `G${ 'E' . repeat ( 55 ) } ` , 250 , 'XLM' , FIXED_NOW - 150 ) ;
95- }
196+ for ( const pledge of pledges ) {
197+ insertPledge . run ( pledge . campaignId , pledge . contributor , pledge . amount , pledge . assetCode , pledge . createdAt ) ;
198+ }
96199
97- if ( require . main === module ) {
98- seedDeterministicState ( ) ;
99- // eslint-disable-next-line no-console
100- console . log ( 'Deterministic database seed complete.' ) ;
200+ return campaigns . map ( ( c ) => c . id ) ;
101201}
202+
203+ export function parseCountArg ( argv : string [ ] ) : number {
204+ const flagIndex = argv . findIndex ( ( arg ) => arg === '--count' || arg . startsWith ( '--count=' ) ) ;
205+ if ( flagIndex === - 1 ) {
206+ return BASE_CAMPAIGNS . length ;
207+ }
208+
209+ const raw = argv [ flagIndex ] . includes ( '=' )
210+ ? argv [ flagIndex ] . split ( '=' ) [ 1 ]
211+ : argv [ flagIndex + 1 ] ;
212+
213+ const parsed = Number ( raw ) ;
214+ if ( ! Number . isInteger ( parsed ) || parsed <= 0 ) {
215+ throw new Error ( `Invalid --count value: "${ raw } ". Must be a positive integer.` ) ;
216+ }
217+
218+ return parsed ;
219+ }
0 commit comments