-
Notifications
You must be signed in to change notification settings - Fork 210
fix(agents): default live listings deadline cutoff to now and validate ISO-8601 params (#1456) #1470
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
fix(agents): default live listings deadline cutoff to now and validate ISO-8601 params (#1456) #1470
Changes from 8 commits
c3ac9fb
65e3fb6
7ed1d2d
29362aa
1c3bd13
b72f0b8
be7700f
84f6ffd
aa0f6cf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import { getLiveListingsCutoffDate, filterAgentEligibleListings, AgentListing } from './live-filter'; | ||
|
|
||
| describe('Live Agent Listings Cutoff & Filtering', () => { | ||
| it('should default cutoff to start of current UTC date when deadline omitted', () => { | ||
| const cutoff = getLiveListingsCutoffDate(); | ||
| expect(cutoff.getUTCHours()).toBe(0); | ||
| expect(cutoff.getUTCMinutes()).toBe(0); | ||
| expect(cutoff.getUTCSeconds()).toBe(0); | ||
| expect(cutoff.getUTCMilliseconds()).toBe(0); | ||
| }); | ||
|
|
||
| it('should parse valid customDeadline and fallback on invalid input', () => { | ||
| const custom = getLiveListingsCutoffDate('2026-08-15T12:00:00.000Z'); | ||
| expect(custom.toISOString()).toBe('2026-08-15T12:00:00.000Z'); | ||
|
|
||
| const invalid = getLiveListingsCutoffDate('invalid-date'); | ||
| expect(invalid.getUTCHours()).toBe(0); | ||
| }); | ||
|
|
||
| it('should include both AGENT_ALLOWED and AGENT_ONLY listings while rejecting closed and past deadline items', () => { | ||
| const cutoff = new Date('2026-08-01T00:00:00.000Z'); | ||
| const mockListings: AgentListing[] = [ | ||
| { id: '1', agentAccess: 'AGENT_ALLOWED', status: 'OPEN', deadline: '2026-08-28T21:59:59.000Z' }, | ||
| { id: '2', agentAccess: 'AGENT_ONLY', status: 'OPEN', deadline: '2026-08-15T00:00:00.000Z' }, | ||
| { id: '3', agentAccess: 'NONE', status: 'OPEN', deadline: '2026-08-28T21:59:59.000Z' }, | ||
| { id: '4', agentAccess: 'AGENT_ALLOWED', status: 'CLOSED', deadline: '2026-08-28T21:59:59.000Z' }, | ||
| { id: '5', agentAccess: 'AGENT_ONLY', status: 'OPEN', deadline: '2026-07-01T00:00:00.000Z' } | ||
| ]; | ||
|
|
||
| const result = filterAgentEligibleListings(mockListings, cutoff); | ||
| expect(result.length).toBe(2); | ||
| expect(result.map(r => r.id)).toEqual(['1', '2']); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| export interface AgentListing { | ||
| readonly id: string | undefined; | ||
| readonly agentAccess: string | undefined; | ||
| readonly status: string | undefined; | ||
| readonly deadline: string | undefined; | ||
| } | ||
|
|
||
| export function getLiveListingsCutoffDate(customDeadline?: string): Date { | ||
| if (customDeadline) { | ||
| const parsed = new Date(customDeadline); | ||
| if (!isNaN(parsed.getTime())) return parsed; | ||
| } | ||
| const now = new Date(); | ||
| return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), 0, 0, 0, 0)); | ||
| } | ||
|
|
||
| export function filterAgentEligibleListings( | ||
| listings: readonly AgentListing[], | ||
| cutoffDate: Date | ||
| ): AgentListing[] { | ||
| return listings.filter((item) => { | ||
| const isAgentAllowed = item.agentAccess === "AGENT_ALLOWED" || item.agentAccess === "AGENT_ONLY"; | ||
| const isOpen = item.status === "OPEN"; | ||
| const isNotExpired = item.deadline ? new Date(item.deadline) >= cutoffDate : true; | ||
| return isAgentAllowed && isOpen && isNotExpired; | ||
| }); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,8 +23,15 @@ async function handler(req: NextApiRequestWithAgent, res: NextApiResponse) { | |
| if (!takeResult.ok) { | ||
| return res.status(400).json({ error: takeResult.error }); | ||
| } | ||
| const take = takeResult.value; | ||
| const deadline = params.deadline as string; | ||
| let deadlineDate: Date | undefined = new Date(); | ||
| if (params.deadline) { | ||
| const rawDeadline = params.deadline as string; | ||
| const parsed = new Date(rawDeadline); | ||
| if (isNaN(parsed.getTime())) { | ||
| return res.status(400).json({ error: 'Expected ISO-8601 datetime format for deadline' }); | ||
| } | ||
| deadlineDate = parsed; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- target file outline ---'
ast-grep outline src/pages/api/agents/listings/live.ts || true
printf '%s\n' '--- target file ---'
cat -n src/pages/api/agents/listings/live.ts
printf '%s\n' '--- relevant configuration and validation usage ---'
rg -n --glob 'tsconfig.json' --glob 'package.json' --glob 'src/pages/api/**/*.ts' \
'noUncheckedIndexedAccess|z\.object|safeParse|NextApiResponse|export default|function handler|deadline' \
tsconfig.json package.json src/pages/api 2>/dev/null | head -300
printf '%s\n' '--- Date parsing behavior for representative query values ---'
node - <<'JS'
const values = [
'',
'2024-01-01',
'2024-01-01T00:00:00Z',
'01/01/2024',
'2024-1-1',
'not-a-date',
];
for (const value of values) {
const date = new Date(value);
console.log(JSON.stringify(value), Number.isNaN(date.getTime()) ? 'Invalid Date' : date.toISOString());
}
JSRepository: SuperteamDAO/earn Length of output: 30715 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- query coercion behavior ---'
node - <<'JS'
const values = [
undefined,
'',
'01/01/2024',
'2024-1-1',
['2024-01-01T00:00:00Z'],
['2024-01-01T00:00:00Z', '2030-01-01T00:00:00Z'],
];
for (const value of values) {
const entersValidation = Boolean(value);
const rawDeadline = value;
const parsed = new Date(rawDeadline);
console.log(JSON.stringify(value), {
entersValidation,
coercedValue: String(rawDeadline),
valid: !Number.isNaN(parsed.getTime()),
parsed: Number.isNaN(parsed.getTime()) ? null : parsed.toISOString(),
});
}
JS
printf '%s\n' '--- Zod dependency and date schema conventions ---'
rg -n --glob 'package.json' --glob 'pnpm-lock.yaml' --glob 'yarn.lock' --glob 'package-lock.json' \
'"zod"' .
rg -n --glob '*.{ts,tsx}' \
'z\.date|z\.string\(\)\.datetime|datetime\(|Expected ISO|ISO-8601|safeParse\(.*query|safeParse\(req\.query' \
src | head -250
printf '%s\n' '--- agent auth wrapper and nearby endpoint signatures ---'
fd -i 'withAgentAuth' src
cat -n src/features/auth/utils/withAgentAuth.ts 2>/dev/null || true
cat -n src/pages/api/agents/listings/details/'[slug].ts' | sed -n '65,115p'Repository: SuperteamDAO/earn Length of output: 4509 Reject invalid
🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| } | ||
| const exclusiveSponsorId = params.exclusiveSponsorId as string | undefined; | ||
| let excludeIds = params['excludeIds[]']; | ||
| if (typeof excludeIds === 'string') { | ||
|
|
@@ -41,7 +48,7 @@ async function handler(req: NextApiRequestWithAgent, res: NextApiResponse) { | |
| isPrivate: false, | ||
| isArchived: false, | ||
| status: 'OPEN', | ||
| deadline: { gte: deadline }, | ||
| deadline: deadlineDate ? { gte: deadlineDate } : undefined, | ||
| type: type || { in: ['bounty', 'project', 'hackathon'] }, | ||
| agentAccess: { in: ['AGENT_ALLOWED', 'AGENT_ONLY'] }, | ||
| sponsor: { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Align the cutoff utility and its test with the current-time contract. The utility defaults to UTC midnight, while the endpoint and PR requirement use the current time.
src/pages/api/agents/listings/live-filter.ts#L8-L15: return the current time when no custom deadline is supplied.src/pages/api/agents/listings/live-filter.test.ts#L4-L18: replace the UTC-midnight assertions with a bounded current-time assertion and an earlier-today expiry case.📍 Affects 2 files
src/pages/api/agents/listings/live-filter.ts#L8-L15(this comment)src/pages/api/agents/listings/live-filter.test.ts#L4-L18🤖 Prompt for AI Agents