feat(server): add /search and /:did/search endpoints
GET /search is public and filters to discoverable=true. GET /:did/search
requires a JWT for that DID and includes non-discoverable notes (drafts).
Both reuse the existing PAGINATION default and { results, cursor }
envelope. The requireDidOwnership 403 message is generalized since it
now guards more than webhooks.
This commit is contained in:
40
server.ts
40
server.ts
@@ -11,6 +11,7 @@ import {
|
|||||||
type WebhookVerb,
|
type WebhookVerb,
|
||||||
} from "./src/data/db.ts";
|
} from "./src/data/db.ts";
|
||||||
import { authenticateRequest } from "./src/auth/verify.ts";
|
import { authenticateRequest } from "./src/auth/verify.ts";
|
||||||
|
import { searchNotes } from "./src/search/opensearch.ts";
|
||||||
import { log } from "./src/log.ts";
|
import { log } from "./src/log.ts";
|
||||||
|
|
||||||
type AuthCtx = {
|
type AuthCtx = {
|
||||||
@@ -34,7 +35,7 @@ const requireDidOwnership = async (
|
|||||||
}
|
}
|
||||||
if (verifiedDid !== did) {
|
if (verifiedDid !== did) {
|
||||||
ctx.response.status = 403;
|
ctx.response.status = 403;
|
||||||
ctx.response.body = { error: "You can only manage your own webhooks" };
|
ctx.response.body = { error: "Forbidden: DID mismatch" };
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -131,6 +132,43 @@ router.get("/:did/notes", (ctx) => {
|
|||||||
ctx.response.body = getNotesByDid(did, cursor, limit);
|
ctx.response.body = getNotesByDid(did, cursor, limit);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.get("/search", async (ctx) => {
|
||||||
|
const q = ctx.request.url.searchParams.get("q") ?? "";
|
||||||
|
if (!q.trim()) {
|
||||||
|
ctx.response.status = 400;
|
||||||
|
ctx.response.body = { error: "q is required" };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const cursor = ctx.request.url.searchParams.get("cursor") ?? undefined;
|
||||||
|
const limit = Number(ctx.request.url.searchParams.get("limit")) || PAGINATION;
|
||||||
|
ctx.response.body = await searchNotes({
|
||||||
|
q,
|
||||||
|
discoverableOnly: true,
|
||||||
|
cursor,
|
||||||
|
limit,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/:did/search", async (ctx) => {
|
||||||
|
const { did } = ctx.params;
|
||||||
|
if (!(await requireDidOwnership(ctx, did))) return;
|
||||||
|
const q = ctx.request.url.searchParams.get("q") ?? "";
|
||||||
|
if (!q.trim()) {
|
||||||
|
ctx.response.status = 400;
|
||||||
|
ctx.response.body = { error: "q is required" };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const cursor = ctx.request.url.searchParams.get("cursor") ?? undefined;
|
||||||
|
const limit = Number(ctx.request.url.searchParams.get("limit")) || PAGINATION;
|
||||||
|
ctx.response.body = await searchNotes({
|
||||||
|
q,
|
||||||
|
discoverableOnly: false,
|
||||||
|
did,
|
||||||
|
cursor,
|
||||||
|
limit,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
router.post("/notes/feed", async (ctx) => {
|
router.post("/notes/feed", async (ctx) => {
|
||||||
const body = await ctx.request.body.json();
|
const body = await ctx.request.body.json();
|
||||||
const { dids, cursor, limit } = body ?? {};
|
const { dids, cursor, limit } = body ?? {};
|
||||||
|
|||||||
Reference in New Issue
Block a user