Skip to content

Commit 23ea90d

Browse files
authored
Merge branch 'main' into fix/revert-segment-middleware-leaf-expansion
2 parents 571a3bb + 71cd667 commit 23ea90d

10 files changed

Lines changed: 900 additions & 35 deletions

File tree

backend/src/api/public/v1/akrites/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import { getPackageHistory } from '../packages/getPackageHistory'
1616
import { getPackagesMetrics } from '../packages/getPackagesMetrics'
1717
import { assignStewardHandler } from '../stewardships/assignSteward'
1818
import { escalateHandler } from '../stewardships/escalate'
19+
import { getMyActivityHandler } from '../stewardships/getMyActivity'
20+
import { getMyPackagesHandler } from '../stewardships/getMyPackages'
1921
import { openStewardship } from '../stewardships/openStewardship'
2022
import { updateStatusHandler } from '../stewardships/updateStatus'
2123

@@ -84,6 +86,16 @@ export function akritesRouter(): Router {
8486
// --- stewardships ---
8587
const stewardshipsSubRouter = Router()
8688
stewardshipsSubRouter.use(rateLimiter)
89+
stewardshipsSubRouter.get(
90+
'/me/packages',
91+
requireScopes([SCOPES.READ_STEWARDSHIPS]),
92+
safeWrap(getMyPackagesHandler),
93+
)
94+
stewardshipsSubRouter.get(
95+
'/me/activity',
96+
requireScopes([SCOPES.READ_STEWARDSHIPS]),
97+
safeWrap(getMyActivityHandler),
98+
)
8799
stewardshipsSubRouter.post(
88100
'/open',
89101
requireScopes([SCOPES.WRITE_STEWARDSHIPS]),

backend/src/api/public/v1/akrites/openapi.yaml

Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,6 +1127,299 @@ paths:
11271127

11281128
# ── Stewardships ───────────────────────────────────────────────────────────
11291129

1130+
/akrites/stewardships/me/packages:
1131+
get:
1132+
operationId: getMyPackages
1133+
summary: List packages stewarded by the authenticated user
1134+
description: >
1135+
Returns a paginated list of packages where the authenticated user
1136+
(`req.actor.id`) is an active steward (`lead` or `co_steward`).
1137+
Includes package metadata, health, open vulnerabilities, last activity,
1138+
and the user's role and stewardship status.
1139+
The `meta.statusCounts` object always reflects counts across **all**
1140+
of the user's stewardships, regardless of the active `status` filter,
1141+
so the tab bar can render all buckets at once.
1142+
tags:
1143+
- Stewardships
1144+
parameters:
1145+
- name: page
1146+
in: query
1147+
schema:
1148+
type: integer
1149+
minimum: 1
1150+
default: 1
1151+
- name: pageSize
1152+
in: query
1153+
schema:
1154+
type: integer
1155+
minimum: 1
1156+
maximum: 100
1157+
default: 25
1158+
- name: status
1159+
in: query
1160+
description: Filter by stewardship status.
1161+
schema:
1162+
type: string
1163+
enum: [assessing, active, needs_attention, escalated, blocked]
1164+
- name: search
1165+
in: query
1166+
description: Case-insensitive substring match on package name or PURL.
1167+
schema:
1168+
type: string
1169+
- name: ecosystem
1170+
in: query
1171+
schema:
1172+
type: string
1173+
enum: [npm, maven, pypi, go, cargo]
1174+
- name: healthBand
1175+
in: query
1176+
schema:
1177+
type: string
1178+
enum: [healthy, fair, concerning, critical]
1179+
- name: vulnSeverity
1180+
in: query
1181+
description: Filter to packages with at least one vulnerability of this severity or worse.
1182+
schema:
1183+
type: string
1184+
enum: [high, critical]
1185+
- name: sortBy
1186+
in: query
1187+
schema:
1188+
type: string
1189+
enum: [risk, health, vulns, name, last_activity]
1190+
default: risk
1191+
- name: sortDir
1192+
in: query
1193+
schema:
1194+
type: string
1195+
enum: [asc, desc]
1196+
default: desc
1197+
responses:
1198+
'200':
1199+
description: Paginated list of the user's stewarded packages.
1200+
content:
1201+
application/json:
1202+
schema:
1203+
type: object
1204+
required: [data, meta]
1205+
properties:
1206+
data:
1207+
type: array
1208+
items:
1209+
type: object
1210+
required:
1211+
- purl
1212+
- name
1213+
- ecosystem
1214+
- openVulns
1215+
- stewardshipId
1216+
- stewardshipStatus
1217+
- myRole
1218+
properties:
1219+
purl:
1220+
type: string
1221+
example: pkg:npm/minimist
1222+
name:
1223+
type: string
1224+
example: minimist
1225+
ecosystem:
1226+
type: string
1227+
example: npm
1228+
lifecycle:
1229+
type: string
1230+
nullable: true
1231+
example: abandoned
1232+
healthScore:
1233+
type: integer
1234+
minimum: 0
1235+
maximum: 100
1236+
nullable: true
1237+
description: Scorecard score scaled to 0–100.
1238+
healthBand:
1239+
type: string
1240+
enum: [healthy, fair, concerning, critical]
1241+
openVulns:
1242+
type: integer
1243+
example: 2
1244+
vulnSeverity:
1245+
type: string
1246+
enum: [critical, high, medium, low]
1247+
nullable: true
1248+
description: Worst open vulnerability severity.
1249+
lastActivityDescription:
1250+
type: string
1251+
nullable: true
1252+
example: Escalated for intervention
1253+
lastActivityAt:
1254+
type: string
1255+
format: date-time
1256+
nullable: true
1257+
stewardshipId:
1258+
type: string
1259+
example: '42'
1260+
stewardshipStatus:
1261+
type: string
1262+
enum: [assessing, active, needs_attention, escalated, blocked]
1263+
myRole:
1264+
type: string
1265+
enum: [lead, co_steward]
1266+
meta:
1267+
type: object
1268+
required: [total, page, pageSize, statusCounts]
1269+
properties:
1270+
total:
1271+
type: integer
1272+
page:
1273+
type: integer
1274+
pageSize:
1275+
type: integer
1276+
statusCounts:
1277+
type: object
1278+
required: [assessing, active, needs_attention, escalated, blocked]
1279+
properties:
1280+
assessing:
1281+
type: integer
1282+
active:
1283+
type: integer
1284+
needs_attention:
1285+
type: integer
1286+
escalated:
1287+
type: integer
1288+
blocked:
1289+
type: integer
1290+
'400':
1291+
description: Validation error.
1292+
content:
1293+
application/json:
1294+
schema:
1295+
$ref: '#/components/schemas/Error'
1296+
'401':
1297+
description: Missing or invalid bearer token.
1298+
content:
1299+
application/json:
1300+
schema:
1301+
$ref: '#/components/schemas/Error'
1302+
1303+
/akrites/stewardships/me/activity:
1304+
get:
1305+
operationId: getMyActivity
1306+
summary: Latest activity feed for the authenticated user's stewardships
1307+
description: >
1308+
Returns the most recent stewardship activity events scoped to packages
1309+
where the authenticated user is an active steward. Results are
1310+
**deduplicated by stewardship** — only the single most recent event per
1311+
package is returned — and sorted newest-first.
1312+
Designed to power the "Latest activity" strip on the My Stewardships
1313+
page. Default `pageSize` is 3 (one card per attention-needed status);
1314+
increase for a "load more" experience.
1315+
tags:
1316+
- Stewardships
1317+
parameters:
1318+
- name: page
1319+
in: query
1320+
schema:
1321+
type: integer
1322+
minimum: 1
1323+
default: 1
1324+
- name: pageSize
1325+
in: query
1326+
schema:
1327+
type: integer
1328+
minimum: 1
1329+
maximum: 100
1330+
default: 3
1331+
- name: status
1332+
in: query
1333+
description: >
1334+
Comma-separated list of stewardship statuses to filter.
1335+
Example: `needs_attention,blocked,escalated,assessing`
1336+
schema:
1337+
type: string
1338+
responses:
1339+
'200':
1340+
description: Deduplicated activity feed for the user's stewardships.
1341+
content:
1342+
application/json:
1343+
schema:
1344+
type: object
1345+
required: [data, meta]
1346+
properties:
1347+
data:
1348+
type: array
1349+
items:
1350+
type: object
1351+
required:
1352+
- stewardshipId
1353+
- packageName
1354+
- purl
1355+
- packageEcosystem
1356+
- stewardshipStatus
1357+
- activityType
1358+
- createdAt
1359+
properties:
1360+
stewardshipId:
1361+
type: string
1362+
example: '42'
1363+
packageName:
1364+
type: string
1365+
example: jackson-databind
1366+
purl:
1367+
type: string
1368+
example: pkg:maven/com.fasterxml.jackson.core/jackson-databind
1369+
packageEcosystem:
1370+
type: string
1371+
example: maven
1372+
stewardshipStatus:
1373+
type: string
1374+
enum:
1375+
[
1376+
assessing,
1377+
active,
1378+
needs_attention,
1379+
escalated,
1380+
blocked,
1381+
unassigned,
1382+
open,
1383+
inactive,
1384+
]
1385+
activityType:
1386+
type: string
1387+
example: advisory_detected
1388+
description:
1389+
type: string
1390+
nullable: true
1391+
example: New security advisory detected
1392+
createdAt:
1393+
type: string
1394+
format: date-time
1395+
suggestedAction:
1396+
type: string
1397+
nullable: true
1398+
description: Label for the primary CTA button on the activity card.
1399+
example: Review & respond
1400+
meta:
1401+
type: object
1402+
required: [total, page, pageSize]
1403+
properties:
1404+
total:
1405+
type: integer
1406+
page:
1407+
type: integer
1408+
pageSize:
1409+
type: integer
1410+
'400':
1411+
description: Validation error.
1412+
content:
1413+
application/json:
1414+
schema:
1415+
$ref: '#/components/schemas/Error'
1416+
'401':
1417+
description: Missing or invalid bearer token.
1418+
content:
1419+
application/json:
1420+
schema:
1421+
$ref: '#/components/schemas/Error'
1422+
11301423
/akrites/stewardships/open:
11311424
post:
11321425
operationId: openStewardship

backend/src/api/public/v1/packages/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ export interface OpenVulns {
2424

2525
export interface Steward {
2626
userId: string
27-
name: string
27+
username: string | null
28+
displayName: string | null
2829
role: 'lead' | 'co_steward'
2930
assignedAt: string
3031
}

backend/src/api/public/v1/stewardships/assignSteward.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,32 @@ const paramsSchema = z.object({
1212
id: z.coerce.number().int().positive(),
1313
})
1414

15-
const bodySchema = z.object({
16-
userId: z.string().trim().min(1),
17-
role: z.enum(['lead', 'co_steward']),
18-
note: z.string().trim().min(1).optional(),
19-
moveToAssessing: z.boolean().optional().default(false),
20-
})
15+
const bodySchema = z
16+
.object({
17+
userId: z.string().trim().min(1),
18+
username: z.string().trim().min(1).optional().nullable(),
19+
displayName: z.string().trim().min(1).optional().nullable(),
20+
role: z.enum(['lead', 'co_steward']),
21+
note: z.string().trim().min(1).optional(),
22+
moveToAssessing: z.boolean().optional().default(false),
23+
})
24+
.refine((d) => (d.username == null) === (d.displayName == null), {
25+
message: 'username and displayName must both be provided or both be absent',
26+
path: ['displayName'],
27+
})
2128

2229
export async function assignStewardHandler(req: Request, res: Response): Promise<void> {
2330
const { id } = validateOrThrow(paramsSchema, req.params)
24-
const { userId, role, note, moveToAssessing } = validateOrThrow(bodySchema, req.body)
31+
const { userId, username, displayName, role, note, moveToAssessing } = validateOrThrow(
32+
bodySchema,
33+
req.body,
34+
)
2535

2636
const qx = await getPackagesQx()
2737
const result = await assignSteward(qx, id, {
2838
userId,
39+
username,
40+
displayName,
2941
role,
3042
note,
3143
assignedBy: req.actor.id,

0 commit comments

Comments
 (0)