@tequila99/githuman 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -13
- package/dist/cli/commands/export.js +4 -3
- package/dist/cli/index.js +7 -4
- package/dist/cli/server-runtime.js +13 -1
- package/dist/cli/startup-message.js +3 -2
- package/dist/server/adapters/agents/event-mapper.js +140 -0
- package/dist/server/app.js +27 -14
- package/dist/server/config/agents.js +141 -0
- package/dist/server/errors/agents.js +12 -0
- package/dist/server/errors/comments.js +2 -0
- package/dist/server/errors/export.js +2 -0
- package/dist/server/errors/http.js +4 -0
- package/dist/server/errors/reviews.js +3 -0
- package/dist/server/hooks/agents/local-origin.js +33 -0
- package/dist/server/repositories/review.repo.js +1 -3
- package/dist/server/routes/agent.js +135 -0
- package/dist/server/routes/comments.js +25 -72
- package/dist/server/routes/diff.js +5 -6
- package/dist/server/routes/events.js +9 -3
- package/dist/server/routes/export.js +7 -16
- package/dist/server/routes/git.js +12 -21
- package/dist/server/routes/reviews.js +11 -43
- package/dist/server/services/agent/agent-session.js +497 -0
- package/dist/server/services/agent/context-builder.js +137 -0
- package/dist/server/services/agent/file-index.js +58 -0
- package/dist/server/services/agent/session-registry.js +100 -0
- package/dist/server/services/comment.service.js +1 -2
- package/dist/server/services/diff.service.js +2 -1
- package/dist/server/services/export.service.js +208 -28
- package/dist/server/services/git.service.js +9 -7
- package/dist/server/services/review.service.js +3 -3
- package/dist/server/utils/executable.js +26 -0
- package/dist/server/utils/file-name.js +6 -0
- package/dist/server/utils/sse.js +11 -0
- package/dist/server/utils/text.js +29 -0
- package/dist/shared/agents/chat-names.js +17 -0
- package/dist/shared/agents/constants.js +6 -0
- package/dist/shared/agents/context-format.js +17 -0
- package/dist/shared/agents/file-search.js +48 -0
- package/dist/shared/agents/schemas.js +51 -0
- package/dist/shared/agents/types.js +11 -0
- package/dist/shared/app/types.js +1 -0
- package/dist/shared/comments/types.js +1 -0
- package/dist/shared/events/types.js +14 -0
- package/dist/shared/git/types.js +1 -0
- package/dist/shared/http/types.js +1 -0
- package/dist/shared/network/loopback.js +4 -0
- package/dist/shared/reviews/types.js +1 -0
- package/dist/shared/utils/error-message.js +3 -0
- package/dist/shared/utils/guards.js +4 -0
- package/dist/web/assets/AppPage-CSb8txs5.js +1 -0
- package/dist/web/assets/CreateReviewFabButton-B18DLuH6.js +1 -0
- package/dist/web/assets/CreateReviewFabButton-Cufd8hVJ.css +1 -0
- package/dist/web/assets/{ErrorNotFound-DMYc7a3H.js → ErrorNotFound-B5uYICL4.js} +1 -1
- package/dist/web/assets/IndexPage-B-hkrnvt.css +1 -0
- package/dist/web/assets/IndexPage-Cm_ssBeC.js +1 -0
- package/dist/web/assets/MainLayout-ChxXEt_Q.css +1 -0
- package/dist/web/assets/MainLayout-ClBPF545.js +7 -0
- package/dist/web/assets/QBtn-E32fUOKx.js +1 -0
- package/dist/web/assets/QItemLabel-Co6oCEUr.js +1 -0
- package/dist/web/assets/QMenu-BZ1eK_rS.js +1 -0
- package/dist/web/assets/QSelect-D1V4tbDr.js +1 -0
- package/dist/web/assets/ReviewDetailPage-BWa3EEkv.css +1 -0
- package/dist/web/assets/ReviewDetailPage-Ct0hXwZL.js +1 -0
- package/dist/web/assets/ReviewsPage-CbLAU6OM.js +1 -0
- package/dist/web/assets/ReviewsPage-Lb74-4L2.css +1 -0
- package/dist/web/assets/StatusSwitcher-C0k4efwK.js +4 -0
- package/dist/web/assets/StatusSwitcher-CZYtI0ID.css +1 -0
- package/dist/web/assets/abnfDiagram-O67JEVCF-DkOrqZLn.js +1 -0
- package/dist/web/assets/agent-icon-BKoq2a6T.js +17 -0
- package/dist/web/assets/agent-icon-BUd-vCRS.css +1 -0
- package/dist/web/assets/agent-store-CEQ6MmTC.js +1 -0
- package/dist/web/assets/app-info-store-CIqmSeWK.js +1 -0
- package/dist/web/assets/arc-BALAYY3f.js +1 -0
- package/dist/web/assets/architecture-7GRP2DOG-CTkghkmX.js +1 -0
- package/dist/web/assets/architectureDiagram-NJMV4G6O-C-xtrMea.js +36 -0
- package/dist/web/assets/array-BifhSqXX.js +1 -0
- package/dist/web/assets/blockDiagram-BEXU5L5S-DYo9Os-m.js +140 -0
- package/dist/web/assets/c4Diagram-YGBWAQC7-BpSFjUAX.js +38 -0
- package/dist/web/assets/channel-f9cE6NV9.js +1 -0
- package/dist/web/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
- package/dist/web/assets/chunk-3FUC2YCW-DtUqbu5K.js +2 -0
- package/dist/web/assets/chunk-5DYCD2WN-DeYTIM8k.js +1 -0
- package/dist/web/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
- package/dist/web/assets/chunk-742MDFTN-8BfKAsu6.js +1 -0
- package/dist/web/assets/chunk-7INBJB4K-PXK092EZ.js +62 -0
- package/dist/web/assets/chunk-7M6MHVWA-Db9reT-V.js +213 -0
- package/dist/web/assets/chunk-7PRAP22T-CNFpjNaH.js +1 -0
- package/dist/web/assets/chunk-DUW6YSOI-Dgx8z5s3.js +1 -0
- package/dist/web/assets/chunk-FOHPRMQF-CoA4358r.js +161 -0
- package/dist/web/assets/chunk-GTNCS2PH-C5gqOp1W.js +88 -0
- package/dist/web/assets/chunk-GWA4HPMP-BquHLG8L.js +1 -0
- package/dist/web/assets/chunk-J5ZVWO5B-oDCd-gWO.js +1 -0
- package/dist/web/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
- package/dist/web/assets/chunk-MBY4JIJT-DyOEFgCh.js +72 -0
- package/dist/web/assets/chunk-NETBCI7D-Bd3ij3-b.js +1 -0
- package/dist/web/assets/chunk-O7XYJQB3-DQvIvDe6.js +126 -0
- package/dist/web/assets/chunk-UA2S7LBM-BDIHwgCZ.js +1 -0
- package/dist/web/assets/chunk-WEXAMYUT-DwQKTsK-.js +1 -0
- package/dist/web/assets/chunk-XXDRQBXY-DTdiON6C.js +1 -0
- package/dist/web/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
- package/dist/web/assets/chunk-Z7XXMR3K-CIvnaGnU.js +10 -0
- package/dist/web/assets/chunk-ZIGJFQKS-BdT_T5CV.js +2 -0
- package/dist/web/assets/classDiagram-v2-NBCMYWYE-BUoclHlO.js +217 -0
- package/dist/web/assets/{core-Ruj7F7dx.js → core-B8kUiZoM.js} +1 -1
- package/dist/web/assets/cose-bilkent-JH36ORCC-C16eW6pc.js +1 -0
- package/dist/web/assets/create-B9ZqyTxV.js +1 -0
- package/dist/web/assets/cynefin-OW5HDTMX-1mS7ZeoM.js +1 -0
- package/dist/web/assets/cynefinDiagram-VND7K2PF-D1PC4YT4.js +62 -0
- package/dist/web/assets/cytoscape.esm-B-1iW2ft.js +321 -0
- package/dist/web/assets/dagre-6A5THRUB-amODOP74.js +4 -0
- package/dist/web/assets/debounce-CDIE7CV6.js +1 -0
- package/dist/web/assets/defaultLocale-BFoDCU3G.js +1 -0
- package/dist/web/assets/diagram-22UHCM2B-wUvcsN_B.js +200 -0
- package/dist/web/assets/diagram-3UASUU5V-CLiOVs2l.js +24 -0
- package/dist/web/assets/diagram-ATOU4E4O-Cs5cFGgz.js +3 -0
- package/dist/web/assets/diagram-CDSNMT55-C8Jb-YeE.js +30 -0
- package/dist/web/assets/diagram-MLGK6HIB-DA6C9fVB.js +24 -0
- package/dist/web/assets/diagram-MPIPVDR6-BOaFKuUi.js +41 -0
- package/dist/web/assets/dist-DuFVAX6Y.js +1 -0
- package/dist/web/assets/ebnfDiagram-ZINNZB2B-e5jvn-EE.js +1 -0
- package/dist/web/assets/elk-276RUBZZ-D7Gcu17D.js +27 -0
- package/dist/web/assets/engine-oniguruma-HbxpIAu4.js +1 -0
- package/dist/web/assets/erDiagram-OPXOYQCR-Cn1bBUF8.js +99 -0
- package/dist/web/assets/event-rR76ioXz.js +1 -0
- package/dist/web/assets/eventmodeling-NTZA5JFV-BjGpd42n.js +1 -0
- package/dist/web/assets/export-file-Di_2jvjP.js +1 -0
- package/dist/web/assets/fira-code-symbols2-400-normal-9X26I2LK.woff2 +0 -0
- package/dist/web/assets/fira-code-symbols2-500-normal-B5s_njw3.woff2 +0 -0
- package/dist/web/assets/fira-code-symbols2-600-normal-Bu3RnO39.woff2 +0 -0
- package/dist/web/assets/flowDiagram-KWPJA3E3-a3N8MVB8.js +1 -0
- package/dist/web/assets/ganttDiagram-FUAMR5RP-C3U8jeJB.js +292 -0
- package/dist/web/assets/gitGraph-4MIJSDKK-C165DC0H.js +1 -0
- package/dist/web/assets/gitGraphDiagram-X574FWY7-CVHdT1OC.js +106 -0
- package/dist/web/assets/golos-text-cyrillic-400-normal-BwL4n7Pb.woff +0 -0
- package/dist/web/assets/golos-text-cyrillic-400-normal-C7us6pn1.woff2 +0 -0
- package/dist/web/assets/golos-text-cyrillic-500-normal-BSLQUuP1.woff2 +0 -0
- package/dist/web/assets/golos-text-cyrillic-500-normal-hXinzVVQ.woff +0 -0
- package/dist/web/assets/golos-text-cyrillic-ext-400-normal-Bo67NYBq.woff2 +0 -0
- package/dist/web/assets/golos-text-cyrillic-ext-400-normal-D592xEk3.woff +0 -0
- package/dist/web/assets/golos-text-cyrillic-ext-500-normal-CLsu_eDI.woff2 +0 -0
- package/dist/web/assets/golos-text-cyrillic-ext-500-normal-InHNvB7v.woff +0 -0
- package/dist/web/assets/golos-text-latin-400-normal-Coi1FYaD.woff2 +0 -0
- package/dist/web/assets/golos-text-latin-400-normal-DOuJOmdK.woff +0 -0
- package/dist/web/assets/golos-text-latin-500-normal-BQo4s7Kn.woff +0 -0
- package/dist/web/assets/golos-text-latin-500-normal-BznAvurO.woff2 +0 -0
- package/dist/web/assets/golos-text-latin-ext-400-normal-BXgyX_iE.woff +0 -0
- package/dist/web/assets/golos-text-latin-ext-400-normal-BjKcJWKY.woff2 +0 -0
- package/dist/web/assets/golos-text-latin-ext-500-normal-9vZPKadr.woff2 +0 -0
- package/dist/web/assets/golos-text-latin-ext-500-normal-BmtMVntR.woff +0 -0
- package/dist/web/assets/graphlib-DS17s2tU.js +1 -0
- package/dist/web/assets/i18n-CyUPETur.js +1 -0
- package/dist/web/assets/{index-JAYe_SFI.css → index-C6ArNnKs.css} +1 -1
- package/dist/web/assets/index-C_B6Z2lc.js +2 -0
- package/dist/web/assets/info-A6RAGUB7-cbWtF3FJ.js +1 -0
- package/dist/web/assets/infoDiagram-VRGFBTTK-C_kWn3Ai.js +2 -0
- package/dist/web/assets/init-C-OQMol4.js +1 -0
- package/dist/web/assets/init-app-DnfYD-0k.js +1 -0
- package/dist/web/assets/is-ChHvR1Z-.js +1 -0
- package/dist/web/assets/ishikawaDiagram-OU5B5YK6-Xb9cDRr7.js +70 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-ext-400-normal-Bh0R7Dhr.woff +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-ext-400-normal-C_uLvvQ5.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-ext-500-normal-C9ShMxGR.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-ext-500-normal-CEVyPk4Y.woff +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-ext-600-normal-DVvC-yKp.woff +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-ext-600-normal-Dj6eZy5f.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-ByoDsISC.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-500-normal-BvH7FW2L.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-600-normal-Dtizs43-.woff2 +0 -0
- package/dist/web/assets/journeyDiagram-ZHPQQLJL-6eZcefap.js +139 -0
- package/dist/web/assets/kanban-definition-PNTS6WVX-D_z_AsKR.js +89 -0
- package/dist/web/assets/katex-ZlcWpGUi.js +257 -0
- package/dist/web/assets/line-v7JtZ-Za.js +1 -0
- package/dist/web/assets/linear-hp1KWmiC.js +1 -0
- package/dist/web/assets/mermaid-parser.core-Cwwk5NqO.js +7 -0
- package/dist/web/assets/mermaid.core-BjPJhwW0.js +44 -0
- package/dist/web/assets/mindmap-definition-NLK3R4M7-lQohhDXq.js +96 -0
- package/dist/web/assets/ordinal-BDEzSJ7C.js +1 -0
- package/dist/web/assets/packet-AYTQ26CC-DxiGe6nt.js +1 -0
- package/dist/web/assets/path-fybaL0A-.js +1 -0
- package/dist/web/assets/pegDiagram-GJSIUBJH-XZRpQTLx.js +1 -0
- package/dist/web/assets/pie-WAS4IAKB-fxN0O90k.js +1 -0
- package/dist/web/assets/pieDiagram-5QR66LMP-M_bBKE-n.js +39 -0
- package/dist/web/assets/pinia-0B8sIOgD.js +1 -0
- package/dist/web/assets/pinia-DbmXdZbT.js +1 -0
- package/dist/web/assets/quadrantDiagram-O4NWA36T-BNxVPrq3.js +7 -0
- package/dist/web/assets/radar-RG4KPBEZ-Cd-5aokk.js +1 -0
- package/dist/web/assets/railroad-74A4TZTK-BvSKL8Ze.js +1 -0
- package/dist/web/assets/railroad-abnf-HS5TGJTU-CB32K25L.js +1 -0
- package/dist/web/assets/railroad-ebnf-LZEXJU2U-CqQR3zRl.js +1 -0
- package/dist/web/assets/railroad-peg-WCYAUIDC-DRSdB-oL.js +1 -0
- package/dist/web/assets/railroadDiagram-XR7U4H2S-D664lPSL.js +1 -0
- package/dist/web/assets/requirementDiagram-PLB6GJNP-B_1Dl0-3.js +84 -0
- package/dist/web/assets/rough.esm-Dy-Kn_BL.js +1 -0
- package/dist/web/assets/runtime-core.esm-bundler-Carm8LUR.js +1 -0
- package/dist/web/assets/safe-storage-CWaD6Dq3.js +1 -0
- package/dist/web/assets/sankeyDiagram-IPEJSGJF-BvFmMC_F.js +40 -0
- package/dist/web/assets/sequenceDiagram-PO4LG4MO-DMK1ey4g.js +169 -0
- package/dist/web/assets/single-flight-r8B7stt1.js +1 -0
- package/dist/web/assets/sizeCapture-INFHLROL-B0uUizjq.js +1 -0
- package/dist/web/assets/src-Cy0hT8a0.js +1 -0
- package/dist/web/assets/stateDiagram-v2-GCMORJYK-VVElU-_E.js +291 -0
- package/dist/web/assets/swimlanes-2SLR337P-DyfYJvHL.js +1 -0
- package/dist/web/assets/swimlanesDiagram-TC7HE7FX-BQ0mK2BI.js +8 -0
- package/dist/web/assets/theme-BFceB3b9.js +1 -0
- package/dist/web/assets/timeline-definition-EJHVYXUP-DBoRb7uC.js +120 -0
- package/dist/web/assets/treeView-Q6P3EWNA-D8AoZg1Y.js +1 -0
- package/dist/web/assets/treemap-WGGIJYW6-DhcVtZsu.js +1 -0
- package/dist/web/assets/use-app-theme-DxZW-eOR.js +1 -0
- package/dist/web/assets/use-panel-width-CY75Fomr.js +1 -0
- package/dist/web/assets/usecaseDiagram-POWQR4AR-BCfvWOH4.js +328 -0
- package/dist/web/assets/vennDiagram-UO4OBE2U-BIk6-FLo.js +34 -0
- package/dist/web/assets/{vue-i18n.runtime-DSeWNwrW.js → vue-i18n.runtime-BQwnBdmC.js} +2 -2
- package/dist/web/assets/wardley-WFR3VGLG-BMHH70A3.js +1 -0
- package/dist/web/assets/wardleyDiagram-VNRHLVJA-DB-cxlSq.js +78 -0
- package/dist/web/assets/xychartDiagram-PMCCYNJV-BejE-3gi.js +7 -0
- package/dist/web/index.html +8 -6
- package/package.json +18 -8
- package/dist/web/assets/AppPage-BeqKILJT.js +0 -1
- package/dist/web/assets/CreateReviewFabButton-BqWdgfEf.css +0 -1
- package/dist/web/assets/CreateReviewFabButton-wjxKeOL1.js +0 -1
- package/dist/web/assets/IndexPage-BQ0T9rLY.css +0 -1
- package/dist/web/assets/IndexPage-CSaToIzA.js +0 -1
- package/dist/web/assets/MainLayout-BRPoh8y0.css +0 -1
- package/dist/web/assets/MainLayout-xQ92tgGe.js +0 -1
- package/dist/web/assets/Platform-CEDL_PgC.js +0 -1
- package/dist/web/assets/QBtn-Cf6WtiKt.js +0 -1
- package/dist/web/assets/QTooltip-IUM54Nhj.js +0 -1
- package/dist/web/assets/ReviewDetailPage-DJHzinT1.css +0 -1
- package/dist/web/assets/ReviewDetailPage-DdufL7Rh.js +0 -1
- package/dist/web/assets/ReviewsPage-D8u1IeQ4.js +0 -1
- package/dist/web/assets/ReviewsPage-D9TFy2OP.css +0 -1
- package/dist/web/assets/StatusSwitcher-7Yqs8azq.css +0 -1
- package/dist/web/assets/StatusSwitcher-CUOMaD34.js +0 -3
- package/dist/web/assets/app-info-store-BpeVVjNB.js +0 -1
- package/dist/web/assets/client-De34x_P_.js +0 -1
- package/dist/web/assets/engine-oniguruma-Bon2Icja.js +0 -1
- package/dist/web/assets/event-BQt_DXaR.js +0 -1
- package/dist/web/assets/format-BWCY39oA.js +0 -1
- package/dist/web/assets/i18n-DHrLpaJV.js +0 -1
- package/dist/web/assets/index-DphTOTJs.js +0 -2
- package/dist/web/assets/init-app-5wT4mURp.js +0 -1
- package/dist/web/assets/pinia-CrBjT8X4.js +0 -1
- package/dist/web/assets/pinia-DK4OjQ7o.js +0 -1
- package/dist/web/assets/runtime-core.esm-bundler-Wxr8ezGp.js +0 -1
- package/dist/web/assets/theme-CrTbazqt.js +0 -1
- package/dist/web/assets/use-app-theme-CsAmLZfp.js +0 -1
- /package/dist/shared/{types.js → diff/types.js} +0 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { NotFoundError } from "../errors/http.js";
|
|
2
|
+
import { SessionParams, PermissionParams, CreateSessionBody, AutoApproveBody, FileSearchQuery, PromptBody, PermissionBody, ConfigBody } from "../../shared/agents/schemas.js";
|
|
3
|
+
import { buildPromptBlocks } from "../services/agent/context-builder.js";
|
|
4
|
+
import { MAX_ATTACHMENT_BYTES, DEFAULT_FILE_SEARCH_LIMIT } from "../../shared/agents/constants.js";
|
|
5
|
+
import { createFileIndex } from "../services/agent/file-index.js";
|
|
6
|
+
import { safeSend } from "../utils/sse.js";
|
|
7
|
+
import { requireLocalOrigin } from "../hooks/agents/local-origin.js";
|
|
8
|
+
import { findReviewById } from "../repositories/review.repo.js";
|
|
9
|
+
/** Room for a handful of max-size base64 attachments; the schema caps each one. */
|
|
10
|
+
const PROMPT_BODY_LIMIT = 5 * Math.ceil((MAX_ATTACHMENT_BYTES * 4) / 3) + 1024 * 1024;
|
|
11
|
+
export async function agentRoutes(app, opts) {
|
|
12
|
+
const { registry, db, repositoryPath, eventBus } = opts;
|
|
13
|
+
const typedApp = app.withTypeProvider();
|
|
14
|
+
const fileIndex = createFileIndex(repositoryPath, eventBus);
|
|
15
|
+
app.addHook('onRequest', requireLocalOrigin);
|
|
16
|
+
app.addHook('onClose', async () => {
|
|
17
|
+
registry.closeAll();
|
|
18
|
+
fileIndex.close();
|
|
19
|
+
});
|
|
20
|
+
typedApp.get('/api/agent/presets', async () => registry.listPresets());
|
|
21
|
+
typedApp.get('/api/agent/sessions', async () => registry.list());
|
|
22
|
+
typedApp.post('/api/agent/sessions', { schema: { body: CreateSessionBody } }, async (request, reply) => {
|
|
23
|
+
const { presetId, reviewId, name } = request.body;
|
|
24
|
+
if (reviewId !== undefined && !findReviewById(db, reviewId)) {
|
|
25
|
+
throw new NotFoundError(`Review ${reviewId} not found`);
|
|
26
|
+
}
|
|
27
|
+
// The agent starts in the background; failure shows up as a closed session.
|
|
28
|
+
const session = registry.create(presetId, reviewId ?? null, name);
|
|
29
|
+
reply.code(201);
|
|
30
|
+
return session.info();
|
|
31
|
+
});
|
|
32
|
+
typedApp.get('/api/agent/sessions/:id', { schema: { params: SessionParams } }, async (request) => {
|
|
33
|
+
const session = registry.get(request.params.id);
|
|
34
|
+
if (!session) {
|
|
35
|
+
throw new NotFoundError('Session not found');
|
|
36
|
+
}
|
|
37
|
+
return session.info();
|
|
38
|
+
});
|
|
39
|
+
typedApp.delete('/api/agent/sessions/:id', { schema: { params: SessionParams } }, async (request, reply) => {
|
|
40
|
+
if (!registry.close(request.params.id)) {
|
|
41
|
+
throw new NotFoundError('Session not found');
|
|
42
|
+
}
|
|
43
|
+
return reply.code(204).send();
|
|
44
|
+
});
|
|
45
|
+
typedApp.post('/api/agent/sessions/:id/prompt', {
|
|
46
|
+
schema: { params: SessionParams, body: PromptBody },
|
|
47
|
+
bodyLimit: PROMPT_BODY_LIMIT
|
|
48
|
+
}, async (request, reply) => {
|
|
49
|
+
const session = registry.get(request.params.id);
|
|
50
|
+
if (!session) {
|
|
51
|
+
throw new NotFoundError('Session not found');
|
|
52
|
+
}
|
|
53
|
+
session.assertCanPrompt();
|
|
54
|
+
const blocks = await buildPromptBlocks(request.body, {
|
|
55
|
+
db,
|
|
56
|
+
repositoryPath,
|
|
57
|
+
embeddedContext: session.supportsEmbeddedContext,
|
|
58
|
+
image: session.supportsImages,
|
|
59
|
+
attachmentDir: session.attachmentDir
|
|
60
|
+
});
|
|
61
|
+
session.prompt(blocks, request.body);
|
|
62
|
+
return reply.code(202).send();
|
|
63
|
+
});
|
|
64
|
+
typedApp.post('/api/agent/sessions/:id/cancel', { schema: { params: SessionParams } }, async (request, reply) => {
|
|
65
|
+
const session = registry.get(request.params.id);
|
|
66
|
+
if (!session) {
|
|
67
|
+
throw new NotFoundError('Session not found');
|
|
68
|
+
}
|
|
69
|
+
await session.cancel();
|
|
70
|
+
return reply.code(204).send();
|
|
71
|
+
});
|
|
72
|
+
typedApp.post('/api/agent/sessions/:id/permissions/:requestId', { schema: { params: PermissionParams, body: PermissionBody } }, async (request, reply) => {
|
|
73
|
+
const session = registry.get(request.params.id);
|
|
74
|
+
if (!session?.answerPermission(request.params.requestId, request.body.optionId)) {
|
|
75
|
+
throw new NotFoundError('No such pending permission request');
|
|
76
|
+
}
|
|
77
|
+
return reply.code(204).send();
|
|
78
|
+
});
|
|
79
|
+
typedApp.post('/api/agent/sessions/:id/config', { schema: { params: SessionParams, body: ConfigBody } }, async (request, reply) => {
|
|
80
|
+
const session = registry.get(request.params.id);
|
|
81
|
+
if (!session) {
|
|
82
|
+
throw new NotFoundError('Session not found');
|
|
83
|
+
}
|
|
84
|
+
await session.setConfig(request.body.configId, request.body.value);
|
|
85
|
+
return reply.code(204).send();
|
|
86
|
+
});
|
|
87
|
+
typedApp.post('/api/agent/sessions/:id/auto-approve', { schema: { params: SessionParams, body: AutoApproveBody } }, async (request, reply) => {
|
|
88
|
+
const session = registry.get(request.params.id);
|
|
89
|
+
if (!session) {
|
|
90
|
+
throw new NotFoundError('Session not found');
|
|
91
|
+
}
|
|
92
|
+
session.setAutoApprove(request.body.enabled);
|
|
93
|
+
return reply.code(204).send();
|
|
94
|
+
});
|
|
95
|
+
typedApp.get('/api/agent/files', { schema: { querystring: FileSearchQuery } }, async (request) => ({
|
|
96
|
+
files: await fileIndex.search(request.query.q ?? '', request.query.limit ?? DEFAULT_FILE_SEARCH_LIMIT)
|
|
97
|
+
}));
|
|
98
|
+
// One stream for every chat (ADR 0024): browsers allow ~6 connections per
|
|
99
|
+
// origin over HTTP/1.1, so a stream per session would starve the page's own
|
|
100
|
+
// requests. Event ids are shared across sessions, so `Last-Event-ID` works.
|
|
101
|
+
typedApp.get('/api/agent/events', { sse: true }, async (request, reply) => {
|
|
102
|
+
const lastEventId = Number(request.headers['last-event-id']);
|
|
103
|
+
reply.sse.keepAlive();
|
|
104
|
+
// Flushes the headers: with no session yet nothing else would be sent, and
|
|
105
|
+
// the browser would not consider the stream open.
|
|
106
|
+
await reply.sse.send({ event: 'connected', data: {} });
|
|
107
|
+
// The client can leave during the await. `onClose` callbacks run once, so
|
|
108
|
+
// one added after the close would never run and the listener would leak.
|
|
109
|
+
if (!reply.sse.isConnected) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const unsubscribe = registry.subscribe(message => {
|
|
113
|
+
if (message.type === 'event') {
|
|
114
|
+
safeSend(reply.sse, {
|
|
115
|
+
id: String(message.envelope.id),
|
|
116
|
+
event: 'agent',
|
|
117
|
+
data: message.envelope
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
else if (message.type === 'gap') {
|
|
121
|
+
safeSend(reply.sse, {
|
|
122
|
+
event: 'gap',
|
|
123
|
+
data: { sessionId: message.sessionId }
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
else if (message.type === 'state') {
|
|
127
|
+
safeSend(reply.sse, { event: 'state', data: message.state });
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
safeSend(reply.sse, { event: 'sessions', data: {} });
|
|
131
|
+
}
|
|
132
|
+
}, Number.isInteger(lastEventId) ? lastEventId : 0);
|
|
133
|
+
reply.sse.onClose(unsubscribe);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
@@ -1,18 +1,22 @@
|
|
|
1
|
+
import { NotFoundError } from "../errors/http.js";
|
|
1
2
|
import { Type } from '@sinclair/typebox';
|
|
2
3
|
import { getReview } from "../services/review.service.js";
|
|
3
|
-
import { createComment, getComments, editComment, removeComment, resolveComment, unresolveComment
|
|
4
|
+
import { createComment, getComments, editComment, removeComment, resolveComment, unresolveComment } from "../services/comment.service.js";
|
|
5
|
+
// Null goes first in each union: Fastify's Ajv coerces types, and with
|
|
6
|
+
// Integer (or String) first it turns a JSON null into 0 (or "") before the
|
|
7
|
+
// Null branch is ever tried — a file-level comment would be stored as line 0.
|
|
4
8
|
const CreateCommentBody = Type.Object({
|
|
5
9
|
filePath: Type.String({ minLength: 1 }),
|
|
6
|
-
lineNumber: Type.Optional(Type.Union([Type.
|
|
7
|
-
lineNumberEnd: Type.Optional(Type.Union([Type.
|
|
10
|
+
lineNumber: Type.Optional(Type.Union([Type.Null(), Type.Integer()])),
|
|
11
|
+
lineNumberEnd: Type.Optional(Type.Union([Type.Null(), Type.Integer()])),
|
|
8
12
|
lineType: Type.Optional(Type.Union([
|
|
13
|
+
Type.Null(),
|
|
9
14
|
Type.Literal('added'),
|
|
10
15
|
Type.Literal('removed'),
|
|
11
|
-
Type.Literal('context')
|
|
12
|
-
Type.Null()
|
|
16
|
+
Type.Literal('context')
|
|
13
17
|
])),
|
|
14
18
|
content: Type.String(),
|
|
15
|
-
suggestion: Type.Optional(Type.Union([Type.
|
|
19
|
+
suggestion: Type.Optional(Type.Union([Type.Null(), Type.String()]))
|
|
16
20
|
});
|
|
17
21
|
const UpdateCommentBody = Type.Object({
|
|
18
22
|
content: Type.String()
|
|
@@ -24,87 +28,36 @@ export async function commentRoutes(app, opts) {
|
|
|
24
28
|
const typedApp = app.withTypeProvider();
|
|
25
29
|
typedApp.post('/api/reviews/:id/comments', { schema: { params: ReviewIdParams, body: CreateCommentBody } }, async (request, reply) => {
|
|
26
30
|
if (!getReview(db, request.params.id)) {
|
|
27
|
-
|
|
28
|
-
return {
|
|
29
|
-
error: 'Not Found',
|
|
30
|
-
message: `Review ${request.params.id} not found`,
|
|
31
|
-
statusCode: 404
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
try {
|
|
35
|
-
const comment = createComment(db, request.params.id, request.body, eventBus);
|
|
36
|
-
reply.code(201);
|
|
37
|
-
return comment;
|
|
38
|
-
}
|
|
39
|
-
catch (error) {
|
|
40
|
-
if (error instanceof ValidationError) {
|
|
41
|
-
reply.code(400);
|
|
42
|
-
return {
|
|
43
|
-
error: 'Bad Request',
|
|
44
|
-
message: error.message,
|
|
45
|
-
statusCode: 400
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
throw error;
|
|
31
|
+
throw new NotFoundError(`Review ${request.params.id} not found`);
|
|
49
32
|
}
|
|
33
|
+
const comment = createComment(db, request.params.id, request.body, eventBus);
|
|
34
|
+
reply.code(201);
|
|
35
|
+
return comment;
|
|
50
36
|
});
|
|
51
|
-
typedApp.get('/api/reviews/:id/comments', { schema: { params: ReviewIdParams } }, async (request
|
|
37
|
+
typedApp.get('/api/reviews/:id/comments', { schema: { params: ReviewIdParams } }, async (request) => {
|
|
52
38
|
if (!getReview(db, request.params.id)) {
|
|
53
|
-
|
|
54
|
-
return {
|
|
55
|
-
error: 'Not Found',
|
|
56
|
-
message: `Review ${request.params.id} not found`,
|
|
57
|
-
statusCode: 404
|
|
58
|
-
};
|
|
39
|
+
throw new NotFoundError(`Review ${request.params.id} not found`);
|
|
59
40
|
}
|
|
60
41
|
return getComments(db, request.params.id);
|
|
61
42
|
});
|
|
62
|
-
typedApp.patch('/api/comments/:id', { schema: { params: CommentIdParams, body: UpdateCommentBody } }, async (request
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
reply.code(404);
|
|
67
|
-
return {
|
|
68
|
-
error: 'Not Found',
|
|
69
|
-
message: `Comment ${request.params.id} not found`,
|
|
70
|
-
statusCode: 404
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
return updated;
|
|
74
|
-
}
|
|
75
|
-
catch (error) {
|
|
76
|
-
if (error instanceof ValidationError) {
|
|
77
|
-
reply.code(400);
|
|
78
|
-
return {
|
|
79
|
-
error: 'Bad Request',
|
|
80
|
-
message: error.message,
|
|
81
|
-
statusCode: 400
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
throw error;
|
|
43
|
+
typedApp.patch('/api/comments/:id', { schema: { params: CommentIdParams, body: UpdateCommentBody } }, async (request) => {
|
|
44
|
+
const updated = editComment(db, request.params.id, request.body.content, eventBus);
|
|
45
|
+
if (!updated) {
|
|
46
|
+
throw new NotFoundError(`Comment ${request.params.id} not found`);
|
|
85
47
|
}
|
|
48
|
+
return updated;
|
|
86
49
|
});
|
|
87
|
-
typedApp.patch('/api/comments/:id/resolve', { schema: { params: CommentIdParams } }, async (request
|
|
50
|
+
typedApp.patch('/api/comments/:id/resolve', { schema: { params: CommentIdParams } }, async (request) => {
|
|
88
51
|
const updated = resolveComment(db, request.params.id, eventBus);
|
|
89
52
|
if (!updated) {
|
|
90
|
-
|
|
91
|
-
return {
|
|
92
|
-
error: 'Not Found',
|
|
93
|
-
message: `Comment ${request.params.id} not found`,
|
|
94
|
-
statusCode: 404
|
|
95
|
-
};
|
|
53
|
+
throw new NotFoundError(`Comment ${request.params.id} not found`);
|
|
96
54
|
}
|
|
97
55
|
return updated;
|
|
98
56
|
});
|
|
99
|
-
typedApp.patch('/api/comments/:id/unresolve', { schema: { params: CommentIdParams } }, async (request
|
|
57
|
+
typedApp.patch('/api/comments/:id/unresolve', { schema: { params: CommentIdParams } }, async (request) => {
|
|
100
58
|
const updated = unresolveComment(db, request.params.id, eventBus);
|
|
101
59
|
if (!updated) {
|
|
102
|
-
|
|
103
|
-
return {
|
|
104
|
-
error: 'Not Found',
|
|
105
|
-
message: `Comment ${request.params.id} not found`,
|
|
106
|
-
statusCode: 404
|
|
107
|
-
};
|
|
60
|
+
throw new NotFoundError(`Comment ${request.params.id} not found`);
|
|
108
61
|
}
|
|
109
62
|
return updated;
|
|
110
63
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { BadRequestError } from "../errors/http.js";
|
|
1
2
|
import { Type } from '@sinclair/typebox';
|
|
2
3
|
import { getStagedDiff, getUnstagedDiff, getBranchDiff, getCommitsDiff } from "../services/diff.service.js";
|
|
3
4
|
const BranchQuery = Type.Object({
|
|
@@ -16,24 +17,22 @@ export async function diffRoutes(app, opts) {
|
|
|
16
17
|
typedApp.get('/api/diff/unstaged', async () => {
|
|
17
18
|
return getUnstagedDiff(repositoryPath);
|
|
18
19
|
});
|
|
19
|
-
typedApp.get('/api/diff/branch', { schema: { querystring: BranchQuery } }, async (request
|
|
20
|
+
typedApp.get('/api/diff/branch', { schema: { querystring: BranchQuery } }, async (request) => {
|
|
20
21
|
try {
|
|
21
22
|
return await getBranchDiff(repositoryPath, request.query.base);
|
|
22
23
|
}
|
|
23
24
|
catch (err) {
|
|
24
|
-
reply.code(400);
|
|
25
25
|
const message = err instanceof Error ? err.message : 'Bad ref';
|
|
26
|
-
|
|
26
|
+
throw new BadRequestError(message, { cause: err });
|
|
27
27
|
}
|
|
28
28
|
});
|
|
29
|
-
typedApp.get('/api/diff/commits', { schema: { querystring: CommitsQuery } }, async (request
|
|
29
|
+
typedApp.get('/api/diff/commits', { schema: { querystring: CommitsQuery } }, async (request) => {
|
|
30
30
|
try {
|
|
31
31
|
return await getCommitsDiff(repositoryPath, request.query.from, request.query.to);
|
|
32
32
|
}
|
|
33
33
|
catch (err) {
|
|
34
|
-
reply.code(400);
|
|
35
34
|
const message = err instanceof Error ? err.message : 'Bad ref';
|
|
36
|
-
|
|
35
|
+
throw new BadRequestError(message, { cause: err });
|
|
37
36
|
}
|
|
38
37
|
});
|
|
39
38
|
}
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
import { safeSend } from "../utils/sse.js";
|
|
1
2
|
export async function eventRoutes(app, opts) {
|
|
2
|
-
const { eventBus } = opts;
|
|
3
|
+
const { eventBus, instanceId } = opts;
|
|
4
|
+
const hello = { instanceId };
|
|
3
5
|
app.get('/api/events', { sse: true }, async (request, reply) => {
|
|
4
6
|
reply.sse.keepAlive();
|
|
5
|
-
await reply.sse.send({ event: 'connected', data:
|
|
7
|
+
await reply.sse.send({ event: 'connected', data: hello });
|
|
8
|
+
// The client can leave during the await; a later `onClose` would never run.
|
|
9
|
+
if (!reply.sse.isConnected) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
6
12
|
const unsubscribe = eventBus.subscribe(event => {
|
|
7
|
-
|
|
13
|
+
safeSend(reply.sse, {
|
|
8
14
|
event: event.type,
|
|
9
15
|
data: { reviewId: event.reviewId }
|
|
10
16
|
});
|
|
@@ -1,27 +1,18 @@
|
|
|
1
1
|
import { Type } from '@sinclair/typebox';
|
|
2
|
-
import { exportAsJson, exportAsMarkdown
|
|
2
|
+
import { exportAsJson, exportAsMarkdown } from "../services/export.service.js";
|
|
3
3
|
const ExportParams = Type.Object({ id: Type.String() });
|
|
4
4
|
const ExportQuery = Type.Object({
|
|
5
5
|
format: Type.Union([Type.Literal('json'), Type.Literal('markdown')])
|
|
6
6
|
});
|
|
7
7
|
export async function exportRoutes(app, opts) {
|
|
8
|
-
const { db } = opts;
|
|
8
|
+
const { db, repositoryPath } = opts;
|
|
9
9
|
const typedApp = app.withTypeProvider();
|
|
10
10
|
typedApp.get('/api/reviews/:id/export', { schema: { params: ExportParams, querystring: ExportQuery } }, async (request, reply) => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
return exportAsJson(db, request.params.id);
|
|
14
|
-
}
|
|
15
|
-
const markdown = exportAsMarkdown(db, request.params.id);
|
|
16
|
-
reply.type('text/markdown');
|
|
17
|
-
return markdown;
|
|
18
|
-
}
|
|
19
|
-
catch (error) {
|
|
20
|
-
if (error instanceof ExportNotFoundError) {
|
|
21
|
-
reply.code(404);
|
|
22
|
-
return { error: 'Not Found', message: error.message, statusCode: 404 };
|
|
23
|
-
}
|
|
24
|
-
throw error;
|
|
11
|
+
if (request.query.format === 'json') {
|
|
12
|
+
return exportAsJson(db, request.params.id);
|
|
25
13
|
}
|
|
14
|
+
const markdown = await exportAsMarkdown(db, request.params.id, repositoryPath);
|
|
15
|
+
reply.type('text/markdown');
|
|
16
|
+
return markdown;
|
|
26
17
|
});
|
|
27
18
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { BadRequestError } from "../errors/http.js";
|
|
1
2
|
import { Type } from '@sinclair/typebox';
|
|
2
3
|
import { getRepositoryInfo, stagePaths, unstagePaths, discardPaths, getFilesAtRef, getFileAtRef } from "../services/git.service.js";
|
|
3
4
|
const TreeParams = Type.Object({
|
|
@@ -20,61 +21,52 @@ export async function gitRoutes(app, opts) {
|
|
|
20
21
|
app.get('/api/git/info', async () => {
|
|
21
22
|
return getRepositoryInfo(repositoryPath);
|
|
22
23
|
});
|
|
23
|
-
typedApp.post('/api/git/stage', { schema: { body: OptionalPathsBody } }, async (request
|
|
24
|
+
typedApp.post('/api/git/stage', { schema: { body: OptionalPathsBody } }, async (request) => {
|
|
24
25
|
try {
|
|
25
26
|
await stagePaths(repositoryPath, request.body.paths ?? []);
|
|
26
27
|
return { ok: true };
|
|
27
28
|
}
|
|
28
29
|
catch (err) {
|
|
29
|
-
reply.code(400);
|
|
30
30
|
const message = err instanceof Error ? err.message : 'Failed to stage';
|
|
31
|
-
|
|
31
|
+
throw new BadRequestError(message, { cause: err });
|
|
32
32
|
}
|
|
33
33
|
});
|
|
34
|
-
typedApp.post('/api/git/unstage', { schema: { body: OptionalPathsBody } }, async (request
|
|
34
|
+
typedApp.post('/api/git/unstage', { schema: { body: OptionalPathsBody } }, async (request) => {
|
|
35
35
|
try {
|
|
36
36
|
await unstagePaths(repositoryPath, request.body.paths ?? []);
|
|
37
37
|
return { ok: true };
|
|
38
38
|
}
|
|
39
39
|
catch (err) {
|
|
40
|
-
reply.code(400);
|
|
41
40
|
const message = err instanceof Error ? err.message : 'Failed to unstage';
|
|
42
|
-
|
|
41
|
+
throw new BadRequestError(message, { cause: err });
|
|
43
42
|
}
|
|
44
43
|
});
|
|
45
|
-
typedApp.post('/api/git/discard', { schema: { body: DiscardBody } }, async (request
|
|
44
|
+
typedApp.post('/api/git/discard', { schema: { body: DiscardBody } }, async (request) => {
|
|
46
45
|
try {
|
|
47
46
|
await discardPaths(repositoryPath, request.body.paths);
|
|
48
47
|
return { ok: true };
|
|
49
48
|
}
|
|
50
49
|
catch (err) {
|
|
51
|
-
reply.code(400);
|
|
52
50
|
const message = err instanceof Error ? err.message : 'Failed to discard changes';
|
|
53
|
-
|
|
51
|
+
throw new BadRequestError(message, { cause: err });
|
|
54
52
|
}
|
|
55
53
|
});
|
|
56
|
-
typedApp.get('/api/git/tree/:ref', { schema: { params: TreeParams } }, async (request
|
|
54
|
+
typedApp.get('/api/git/tree/:ref', { schema: { params: TreeParams } }, async (request) => {
|
|
57
55
|
const { ref } = request.params;
|
|
58
56
|
try {
|
|
59
57
|
const files = await getFilesAtRef(repositoryPath, ref);
|
|
60
58
|
return { ref, files };
|
|
61
59
|
}
|
|
62
60
|
catch (err) {
|
|
63
|
-
reply.code(400);
|
|
64
61
|
const message = err instanceof Error ? err.message : 'Failed to list files';
|
|
65
|
-
|
|
62
|
+
throw new BadRequestError(message, { cause: err });
|
|
66
63
|
}
|
|
67
64
|
});
|
|
68
|
-
typedApp.get('/api/git/file/*', { schema: { querystring: FileContentQuery } }, async (request
|
|
65
|
+
typedApp.get('/api/git/file/*', { schema: { querystring: FileContentQuery } }, async (request) => {
|
|
69
66
|
const filePath = request.params['*'];
|
|
70
67
|
const { ref } = request.query;
|
|
71
68
|
if (!filePath) {
|
|
72
|
-
|
|
73
|
-
return {
|
|
74
|
-
error: 'Bad Request',
|
|
75
|
-
message: 'File path is required',
|
|
76
|
-
statusCode: 400
|
|
77
|
-
};
|
|
69
|
+
throw new BadRequestError('File path is required');
|
|
78
70
|
}
|
|
79
71
|
let content;
|
|
80
72
|
let isBinary;
|
|
@@ -83,9 +75,8 @@ export async function gitRoutes(app, opts) {
|
|
|
83
75
|
({ content, isBinary } = await getFileAtRef(repositoryPath, ref, filePath));
|
|
84
76
|
}
|
|
85
77
|
catch (err) {
|
|
86
|
-
reply.code(400);
|
|
87
78
|
const message = err instanceof Error ? err.message : 'Failed to read file';
|
|
88
|
-
|
|
79
|
+
throw new BadRequestError(message, { cause: err });
|
|
89
80
|
}
|
|
90
81
|
const lines = isBinary ? [] : content.split('\n');
|
|
91
82
|
return {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { NotFoundError, BadRequestError } from "../errors/http.js";
|
|
1
2
|
import { Type } from '@sinclair/typebox';
|
|
2
|
-
import { createReview, getReview, getReviews, setReviewStatus, removeReview
|
|
3
|
+
import { createReview, getReview, getReviews, setReviewStatus, removeReview } from "../services/review.service.js";
|
|
3
4
|
import { getRepositoryInfo } from "../services/git.service.js";
|
|
4
5
|
const CreateReviewBody = Type.Object({
|
|
5
6
|
sourceType: Type.Optional(Type.Union([
|
|
@@ -33,22 +34,9 @@ export async function reviewRoutes(app, opts) {
|
|
|
33
34
|
const { repositoryPath, db, eventBus } = opts;
|
|
34
35
|
const typedApp = app.withTypeProvider();
|
|
35
36
|
typedApp.post('/api/reviews', { schema: { body: CreateReviewBody } }, async (request, reply) => {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return review;
|
|
40
|
-
}
|
|
41
|
-
catch (error) {
|
|
42
|
-
if (error instanceof ValidationError) {
|
|
43
|
-
reply.code(400);
|
|
44
|
-
return {
|
|
45
|
-
error: 'Bad Request',
|
|
46
|
-
message: error.message,
|
|
47
|
-
statusCode: 400
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
throw error;
|
|
51
|
-
}
|
|
37
|
+
const review = await createReview(db, repositoryPath, request.body, eventBus);
|
|
38
|
+
reply.code(201);
|
|
39
|
+
return review;
|
|
52
40
|
});
|
|
53
41
|
typedApp.get('/api/reviews', { schema: { querystring: ReviewsQuery } }, async (request) => {
|
|
54
42
|
const { branch, search, createdFrom, createdTo, files } = request.query;
|
|
@@ -63,46 +51,26 @@ export async function reviewRoutes(app, opts) {
|
|
|
63
51
|
: undefined
|
|
64
52
|
});
|
|
65
53
|
});
|
|
66
|
-
typedApp.get('/api/reviews/:id', { schema: { params: ReviewIdParams } }, async (request
|
|
54
|
+
typedApp.get('/api/reviews/:id', { schema: { params: ReviewIdParams } }, async (request) => {
|
|
67
55
|
const review = getReview(db, request.params.id);
|
|
68
56
|
if (!review) {
|
|
69
|
-
|
|
70
|
-
return {
|
|
71
|
-
error: 'Not Found',
|
|
72
|
-
message: `Review ${request.params.id} not found`,
|
|
73
|
-
statusCode: 404
|
|
74
|
-
};
|
|
57
|
+
throw new NotFoundError(`Review ${request.params.id} not found`);
|
|
75
58
|
}
|
|
76
59
|
return review;
|
|
77
60
|
});
|
|
78
|
-
typedApp.patch('/api/reviews/:id', { schema: { params: ReviewIdParams, body: UpdateReviewBody } }, async (request
|
|
61
|
+
typedApp.patch('/api/reviews/:id', { schema: { params: ReviewIdParams, body: UpdateReviewBody } }, async (request) => {
|
|
79
62
|
if (!request.body.status) {
|
|
80
|
-
|
|
81
|
-
return {
|
|
82
|
-
error: 'Bad Request',
|
|
83
|
-
message: '"status" is required',
|
|
84
|
-
statusCode: 400
|
|
85
|
-
};
|
|
63
|
+
throw new BadRequestError('"status" is required');
|
|
86
64
|
}
|
|
87
65
|
const updated = setReviewStatus(db, request.params.id, request.body.status, eventBus);
|
|
88
66
|
if (!updated) {
|
|
89
|
-
|
|
90
|
-
return {
|
|
91
|
-
error: 'Not Found',
|
|
92
|
-
message: `Review ${request.params.id} not found`,
|
|
93
|
-
statusCode: 404
|
|
94
|
-
};
|
|
67
|
+
throw new NotFoundError(`Review ${request.params.id} not found`);
|
|
95
68
|
}
|
|
96
69
|
return updated;
|
|
97
70
|
});
|
|
98
71
|
typedApp.delete('/api/reviews/:id', { schema: { params: ReviewIdParams } }, async (request, reply) => {
|
|
99
72
|
if (!getReview(db, request.params.id)) {
|
|
100
|
-
|
|
101
|
-
return {
|
|
102
|
-
error: 'Not Found',
|
|
103
|
-
message: `Review ${request.params.id} not found`,
|
|
104
|
-
statusCode: 404
|
|
105
|
-
};
|
|
73
|
+
throw new NotFoundError(`Review ${request.params.id} not found`);
|
|
106
74
|
}
|
|
107
75
|
removeReview(db, request.params.id, eventBus);
|
|
108
76
|
reply.code(204);
|