Skip to content

Commit 99e2d73

Browse files
authored
Merge pull request #138 from Adeyemi-cmd/Add_Campaign_Goal
feat: add campaign goal edit endpoint for creators
2 parents 4a3f52e + 8036d14 commit 99e2d73

5 files changed

Lines changed: 9 additions & 90 deletions

File tree

backend/src/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { config, walletIntegrationReady } from "./config";
77
import {
88
addPledge,
99
calculateProgress,
10+
CampaignProgress,
11+
CampaignRecord,
1012
CampaignStatus,
1113
claimCampaign,
1214
createCampaign,
@@ -19,6 +21,7 @@ import {
1921
softDeleteCampaign,
2022
reconcileOnChainPledge,
2123
refundContributor,
24+
updateCampaign,
2225
} from "./services/campaignStore";
2326
import { checkDbHealth } from "./services/db";
2427
import { getCampaignHistory } from "./services/eventHistory";
@@ -34,13 +37,14 @@ import {
3437
parseCampaignListPaginationQuery,
3538
reconcilePledgePayloadSchema,
3639
refundPayloadSchema,
40+
updateCampaignPayloadSchema,
3741
zodIssuesToErrorMessage,
3842
zodIssuesToValidationIssues,
3943
} from "./validation/schemas";
4044
import { logError, logInfo, logRequest } from "./logger";
4145

4246
type RequestWithId = Request & { requestId?: string };
43-
type CampaignListItem = ReturnType<typeof import("./services/campaignStore").getCampaign> & { progress: ReturnType<typeof import("./services/campaignStore").calculateProgress> };
47+
4448

4549
export const app = express();
4650

@@ -322,7 +326,7 @@ app.post("/api/campaigns", (req: Request, res: Response) => {
322326
res.status(201).json({ data: { ...campaign, progress: calculateProgress(campaign) } });
323327
});
324328

325-
app.post("/api/campaigns/:id/pledges", applyRateLimit(WRITE_RATE_LIMIT_MAX_REQUESTS), (req: Request, res: Response) => {
329+
326330
const parsedId = parseCampaignId(req.params.id);
327331
if (!parsedId.ok) {
328332
sendValidationError(parsedId.issues);

backend/src/services/campaignStore.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ let createCampaign: CampaignStoreModule["createCampaign"];
1919
let initCampaignStore: CampaignStoreModule["initCampaignStore"];
2020
let listCampaigns: CampaignStoreModule["listCampaigns"];
2121
let reconcileOnChainPledge: CampaignStoreModule["reconcileOnChainPledge"];
22+
let updateCampaign: CampaignStoreModule["updateCampaign"];
2223
let getCampaign: CampaignStoreModule["getCampaign"];
2324
let getPledges: CampaignStoreModule["getPledges"];
2425
let getGlobalStats: CampaignStoreModule["getGlobalStats"];
@@ -40,6 +41,7 @@ beforeAll(async () => {
4041
initCampaignStore,
4142
listCampaigns,
4243
reconcileOnChainPledge,
44+
updateCampaign,
4345
getCampaign,
4446
getPledges,
4547

@@ -165,4 +167,3 @@ describe("on-chain pledge reconciliation", () => {
165167
});
166168
});
167169

168-

backend/src/services/campaignStore.ts

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -425,23 +425,7 @@ export function createCampaign(input: CampaignInput): CampaignRecord {
425425
return campaign;
426426
}
427427

428-
function checkContributorLimit(
429-
campaign: CampaignRecord,
430-
contributor: string,
431-
amount: number,
432-
): void {
433-
if (campaign.maxPerContributor === undefined) {
434-
return;
435-
}
436428

437-
const alreadyPledged = getContributorPledgedTotal(campaign.id, contributor);
438-
if (alreadyPledged + amount > campaign.maxPerContributor) {
439-
throw toServiceError(
440-
`Contributor limit exceeded. Max: ${campaign.maxPerContributor}, Already pledged: ${alreadyPledged}, Attempted: ${amount}`,
441-
400,
442-
"CONTRIBUTOR_LIMIT_EXCEEDED",
443-
);
444-
}
445429
}
446430

447431
export function addPledge(campaignId: string, input: PledgeInput): CampaignRecord {

backend/src/services/eventHistory.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { getDb } from "./db";
22

3-
export type CampaignEventType = "created" | "pledged" | "claimed" | "refunded";
3+
export type CampaignEventType = "created" | "pledged" | "claimed" | "refunded" | "updated";
44

55
export interface BlockchainMetadata {
66
txHash?: string;

backend/src/validation/schemas.ts

Lines changed: 0 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -110,76 +110,6 @@ export const refundPayloadSchema = z.object({
110110
soroban: sorobanRefundMetadataSchema,
111111
});
112112

113-
function singleCampaignListQueryParam(value: unknown): string | undefined {
114-
if (value === undefined || value === null) {
115-
return undefined;
116-
}
117-
const raw = Array.isArray(value) ? value[0] : value;
118-
if (typeof raw !== "string" && typeof raw !== "number") {
119-
return undefined;
120-
}
121-
const s = String(raw).trim();
122-
return s === "" ? undefined : s;
123-
}
124-
125-
/**
126-
* Parses optional `page` and `limit` for GET /api/campaigns.
127-
* Omitting both means no pagination (caller lists the full filtered set).
128-
* Supplying only one is invalid (400).
129-
*/
130-
export function parseCampaignListPaginationQuery(query: {
131-
page?: unknown;
132-
limit?: unknown;
133-
}): { ok: true; page?: number; limit?: number } | { ok: false; issues: z.core.$ZodIssue[] } {
134-
const pageStr = singleCampaignListQueryParam(query.page);
135-
const limitStr = singleCampaignListQueryParam(query.limit);
136-
137-
if (pageStr === undefined && limitStr === undefined) {
138-
return { ok: true };
139-
}
140-
if (pageStr === undefined || limitStr === undefined) {
141-
return {
142-
ok: false,
143-
issues: [
144-
{
145-
code: "custom",
146-
message: "Pagination requires both page and limit query parameters.",
147-
path: pageStr === undefined ? ["page"] : ["limit"],
148-
},
149-
],
150-
};
151-
}
152-
153-
const pageNum = Number(pageStr);
154-
const limitNum = Number(limitStr);
155-
const issues: z.core.$ZodIssue[] = [];
156-
157-
if (!Number.isFinite(pageNum) || !Number.isInteger(pageNum) || pageNum < 1) {
158-
issues.push({
159-
code: "custom",
160-
message: "page must be a positive integer.",
161-
path: ["page"],
162-
});
163-
}
164-
if (
165-
!Number.isFinite(limitNum) ||
166-
!Number.isInteger(limitNum) ||
167-
limitNum < 1 ||
168-
limitNum > 100
169-
) {
170-
issues.push({
171-
code: "custom",
172-
message: "limit must be an integer from 1 to 100.",
173-
path: ["limit"],
174-
});
175-
}
176-
177-
if (issues.length > 0) {
178-
return { ok: false, issues };
179-
}
180-
181-
return { ok: true, page: pageNum, limit: limitNum };
182-
}
183113

184114

185115
export type ValidationIssue = {

0 commit comments

Comments
 (0)