@maudecode/kanvil 0.4.4 → 0.5.1
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/apps/server/.env.example +5 -0
- package/apps/server/dist/{auth-B-Tz8SF4.mjs → auth-Bm6cIseE.mjs} +314 -127
- package/apps/server/dist/client.mjs +10 -9
- package/apps/server/dist/{config-CRADzIvL.mjs → config-CrJuk5mY.mjs} +15 -7
- package/apps/server/dist/index.mjs +98 -30
- package/apps/server/dist/migrate.mjs +2 -2
- package/apps/server/dist/recover.mjs +4 -4
- package/apps/server/dist/{src-BCtZ3wbK.mjs → src-D80LNZKP.mjs} +58 -20
- package/apps/web/dist/assets/{CompositeItem-DvEBmVYM.js → CompositeItem-Tueq2VKR.js} +1 -1
- package/apps/web/dist/assets/{Trans-NwgJNiLl.js → Trans-BgU0eWAX.js} +1 -1
- package/apps/web/dist/assets/admin-settings-Ckis3XJG.js +1 -0
- package/apps/web/dist/assets/{archive-restore-j20QLQCl.js → archive-restore-CSpoH6mS.js} +1 -1
- package/apps/web/dist/assets/az-AZ-bFxpMMZN.js +1 -0
- package/apps/web/dist/assets/{az-Dj0LhQ5O.js → az-DUcDY8Bp.js} +1 -1
- package/apps/web/dist/assets/{corner-left-up-BAGDaT_1.js → corner-left-up-C3OGlXIB.js} +1 -1
- package/apps/web/dist/assets/{de-BWVFKRmU.js → de-89C1V8Ut.js} +1 -1
- package/apps/web/dist/assets/de-DE-BeXI30Ll.js +1 -0
- package/apps/web/dist/assets/default-Dnep8Yat.js +56 -0
- package/apps/web/dist/assets/{delivery-settings-B26dRk_O.js → delivery-settings-cMldZ9K7.js} +1 -1
- package/apps/web/dist/assets/{el-IYSYQmsa.js → el-D6CICkhi.js} +1 -1
- package/apps/web/dist/assets/el-GR-SgRHlWhY.js +1 -0
- package/apps/web/dist/assets/{ellipsis-Cx1SIn8a.js → ellipsis-lJJU8zB7.js} +1 -1
- package/apps/web/dist/assets/{es-C3DJP0aS.js → es-B20Izib2.js} +1 -1
- package/apps/web/dist/assets/es-ES-BF1J5vap.js +1 -0
- package/apps/web/dist/assets/{forms-BwtRqvGj.js → forms--87R7VQW.js} +1 -1
- package/apps/web/dist/assets/{fr-BlVUPa15.js → fr-DVlGnGsu.js} +1 -1
- package/apps/web/dist/assets/fr-FR-D_sldwf6.js +1 -0
- package/apps/web/dist/assets/{hi-CINzKB6O.js → hi-D1F5Pi_I.js} +1 -1
- package/apps/web/dist/assets/hi-IN-CfOw4TWf.js +1 -0
- package/apps/web/dist/assets/{highlight-XHbOOx7A.js → highlight-ygDJPvRy.js} +1 -1
- package/apps/web/dist/assets/id-ID-CLKtHO7Q.js +1 -0
- package/apps/web/dist/assets/{id-CpyQwNyO.js → id-LESXKW7G.js} +1 -1
- package/apps/web/dist/assets/index-Bn-MADyj.css +2 -0
- package/apps/web/dist/assets/index-Ds3IHeIi.js +17 -0
- package/apps/web/dist/assets/{invitations-Ch02KEAB.js → invitations-C5WbdDUQ.js} +1 -1
- package/apps/web/dist/assets/{it-CxMkGyiF.js → it-CgRGAs6u.js} +1 -1
- package/apps/web/dist/assets/it-IT-eVZNW5qX.js +1 -0
- package/apps/web/dist/assets/{item-CRS9Ai9p.js → item-BD1Poxfs.js} +1 -1
- package/apps/web/dist/assets/{ja-D72d7qkv.js → ja-B7arhzgX.js} +1 -1
- package/apps/web/dist/assets/ja-JP-D96njn-P.js +1 -0
- package/apps/web/dist/assets/ko-KR-Bash_ieA.js +1 -0
- package/apps/web/dist/assets/{ko-BHuNjRQ3.js → ko-Ro_UonyA.js} +1 -1
- package/apps/web/dist/assets/{label-settings-tE57CalJ.js → label-settings-BaikCfal.js} +1 -1
- package/apps/web/dist/assets/{markdown-blocks-CN-hJdJS.js → markdown-blocks-toSPlHSc.js} +1 -1
- package/apps/web/dist/assets/{member-directory-CoLB-2Oc.js → member-directory-BjUy1KuK.js} +1 -1
- package/apps/web/dist/assets/{mk-D3TPH_Mz.js → mk-DR3kmrIp.js} +1 -1
- package/apps/web/dist/assets/mk-MK-BWQWGIRx.js +1 -0
- package/apps/web/dist/assets/{nl-BHJbk-Wr.js → nl-D3uRJJLT.js} +1 -1
- package/apps/web/dist/assets/nl-NL-D1tvo0Gd.js +1 -0
- package/apps/web/dist/assets/{pl-JW_P_Gb9.js → pl-CtzDOLdV.js} +1 -1
- package/apps/web/dist/assets/pl-PL-DNgGUEcQ.js +1 -0
- package/apps/web/dist/assets/{project-lifecycle-DuV33mlL.js → project-lifecycle-BBHcRi6p.js} +1 -1
- package/apps/web/dist/assets/project-settings-BaYuxYjg.js +1 -0
- package/apps/web/dist/assets/pt-BR-CZsUZQ6P.js +1 -0
- package/apps/web/dist/assets/{pt-BR-DWL_sEp3.js → pt-BR-FarXHHuf.js} +1 -1
- package/apps/web/dist/assets/{public-project-M93Zpx_e.js → public-project-DGf3nkg2.js} +1 -1
- package/apps/web/dist/assets/{rotate-ccw-DcHaluuA.js → rotate-ccw-Byx7O3RH.js} +1 -1
- package/apps/web/dist/assets/{ru-DQNby7hW.js → ru-DHMEVxvB.js} +1 -1
- package/apps/web/dist/assets/ru-RU-4ew92YE_.js +1 -0
- package/apps/web/dist/assets/{search-DQtwggUH.js → search-DwsPEC4E.js} +35 -35
- package/apps/web/dist/assets/{send-gpGpxsCa.js → send-DlGV2XQS.js} +1 -1
- package/apps/web/dist/assets/settings-C_sN8Y1h.js +1 -0
- package/apps/web/dist/assets/{sliders-horizontal-BxgR4pFt.js → sliders-horizontal-CEHGGft_.js} +1 -1
- package/apps/web/dist/assets/{switch-UPVGS5kv.js → switch-B_rsZYBf.js} +1 -1
- package/apps/web/dist/assets/{table-D8iAxhJH.js → table-2AEfwytk.js} +1 -1
- package/apps/web/dist/assets/ticket-detail-C2l8nwF-.js +2 -0
- package/apps/web/dist/assets/ticket-editor-B0UB98HS.js +2 -0
- package/apps/web/dist/assets/ticket-list-D_MHudj1.js +6 -0
- package/apps/web/dist/assets/{ticket-list-controls-ua3m0uRB.js → ticket-list-controls-jKg4i-NA.js} +1 -1
- package/apps/web/dist/assets/{tr-BNZEkwqd.js → tr-CjdSDu0z.js} +1 -1
- package/apps/web/dist/assets/tr-TR-CM381XCw.js +1 -0
- package/apps/web/dist/assets/{uk-Dmlqvk63.js → uk-C8od6u_H.js} +1 -1
- package/apps/web/dist/assets/uk-UA-AeOiWCVo.js +1 -0
- package/apps/web/dist/assets/{upload-Cu0J8ZFJ.js → upload-BeTAGU_W.js} +1 -1
- package/apps/web/dist/assets/{useQueries-BEzeS0hU.js → useQueries-BtRgTciJ.js} +1 -1
- package/apps/web/dist/assets/{vi-KALEc3bk.js → vi-BwdBtgfS.js} +1 -1
- package/apps/web/dist/assets/vi-VN-td08xXv9.js +1 -0
- package/apps/web/dist/assets/{visual-editor-DyhI5fa5.js → visual-editor-CzB_qwvB.js} +3 -3
- package/apps/web/dist/assets/{workspace-settings-DGr-Lwnz.js → workspace-settings-DOr9j8X1.js} +1 -1
- package/apps/web/dist/assets/zh-CN-D3dhEYLS.js +1 -0
- package/apps/web/dist/assets/{zh-CN-DfruhRl9.js → zh-CN-DM-UoFmL.js} +1 -1
- package/apps/web/dist/assets/zh-TW-CfhOEFo8.js +1 -0
- package/apps/web/dist/assets/{zh-TW-C4keUYMa.js → zh-TW-q2nrwFjt.js} +1 -1
- package/apps/web/dist/index.html +8 -8
- package/docs/api.md +19 -7
- package/docs/cli.md +1 -1
- package/docs/releasing.md +16 -2
- package/docs/self-hosting.md +8 -0
- package/package.json +1 -1
- package/apps/web/dist/assets/admin-settings-BVWOe67Z.js +0 -1
- package/apps/web/dist/assets/az-AZ-DlsPlUZi.js +0 -1
- package/apps/web/dist/assets/de-DE-DLCaqUUe.js +0 -1
- package/apps/web/dist/assets/default-D6I-Xfqm.js +0 -56
- package/apps/web/dist/assets/el-GR-P2XcwrSU.js +0 -1
- package/apps/web/dist/assets/es-ES-CJoX8f3w.js +0 -1
- package/apps/web/dist/assets/fr-FR-yLkQdy6a.js +0 -1
- package/apps/web/dist/assets/hi-IN-y8dqej7z.js +0 -1
- package/apps/web/dist/assets/id-ID-DSvh8mGH.js +0 -1
- package/apps/web/dist/assets/index-DyQNFS69.css +0 -2
- package/apps/web/dist/assets/index-MTE_naHG.js +0 -17
- package/apps/web/dist/assets/it-IT-BUcfUqNo.js +0 -1
- package/apps/web/dist/assets/ja-JP-C-jLP_Ay.js +0 -1
- package/apps/web/dist/assets/ko-KR-C5Ozdm2E.js +0 -1
- package/apps/web/dist/assets/mk-MK-BdM_SsE8.js +0 -1
- package/apps/web/dist/assets/nl-NL-CwjB5sva.js +0 -1
- package/apps/web/dist/assets/pl-PL-6-PMhTBd.js +0 -1
- package/apps/web/dist/assets/project-settings-9yJbfZRu.js +0 -1
- package/apps/web/dist/assets/pt-BR-TD_wt6Hr.js +0 -1
- package/apps/web/dist/assets/ru-RU-Bk1a6Oln.js +0 -1
- package/apps/web/dist/assets/settings-Co61IE04.js +0 -1
- package/apps/web/dist/assets/ticket-detail-BKilKtg_.js +0 -2
- package/apps/web/dist/assets/ticket-editor-B5eOWIj4.js +0 -2
- package/apps/web/dist/assets/ticket-list-C965rmmf.js +0 -5
- package/apps/web/dist/assets/tr-TR-CUfBb7K9.js +0 -1
- package/apps/web/dist/assets/uk-UA-BDaXBsDP.js +0 -1
- package/apps/web/dist/assets/vi-VN-Bgb-L6mE.js +0 -1
- package/apps/web/dist/assets/zh-CN-BypkvBe6.js +0 -1
- package/apps/web/dist/assets/zh-TW-CgMsT_XS.js +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as auth_schema_exports, c as oauthConsent, d as user, i as apikey, l as oauthRefreshToken, o as oauthAccessToken, r as account, s as oauthClient, u as session } from "./config-
|
|
2
|
-
import { $ as permissionLabels, A as hasAdminRole, At as
|
|
1
|
+
import { a as auth_schema_exports, c as oauthConsent, d as user, i as apikey, l as oauthRefreshToken, o as oauthAccessToken, r as account, s as oauthClient, u as session } from "./config-CrJuk5mY.mjs";
|
|
2
|
+
import { $ as permissionLabels, A as hasAdminRole, At as workflowOrder, Bt as searchSettingsSchema, D as filterKey, Dt as ticketPredicates, E as eventSchema, Et as ticketImportSchema, F as isHexColor, Ft as instanceUpdateSchema, J as operationPermission, N as importSecretFields, Nt as authenticationPolicySchema, O as filterList, Ot as ticketSchema, P as invitationStatuses, Pt as brokerSettingsSchema, Q as parsePublicOutput, Rt as s3SettingsSchema, S as difficulties, St as states, T as eventLabels, Tt as ticketFilter, V as mentionScheme, X as parseCustomFieldFilter, Y as operations, Z as parseOutput, _ as deletedAccountName, _t as seededPermissions, at as publicOperations, b as destinationEventKeys, bt as stagedUploadLifetime, ct as repositoryProviderDefinitions, d as customFieldSchema, dt as resourceProvider, et as permissionScope, f as customFieldValue, ft as roleCategory, g as defaultWorkflow, gt as searchResultTypes, h as defaultOrder, ht as scopes, i as channelRouteAllows, it as projectSchema, jt as workspaceSchema, k as formatEvent, kt as ticketSummarySchema, l as credentialProjectsSchema, lt as repositoryProviders, m as defaultClaimMaxLeaseSeconds, mt as roles, n as attachmentLimit, nt as priorities, p as customFieldValueSchema, pt as roleLabels, r as channelKinds, rt as priorityLabels, s as closedStates, st as remoteKinds, t as ApiError, tt as permissions, u as customFieldFilterTokens, ut as repositorySecretFields, v as deliverySecretFields, vt as signInMethodLabels, w as eventCategories, wt as ticketExportFormat, x as destinationKinds, xt as stateLabels, y as deliverySettingsSchema, yt as snapshotSchema, z as isSeededRole, zt as searchAuthentication } from "./src-D80LNZKP.mjs";
|
|
3
3
|
import { and, asc, count, desc, eq, getTableColumns, gt, ilike, inArray, isNotNull, isNull, lt, lte, ne, notInArray, or, sql } from "drizzle-orm";
|
|
4
4
|
import { alias, bigint, boolean, check, customType, foreignKey, index, integer, jsonb, pgTable, primaryKey, text, timestamp, unionAll, unique, uniqueIndex, uuid } from "drizzle-orm/pg-core";
|
|
5
5
|
import { z } from "zod";
|
|
@@ -15,6 +15,7 @@ import { request as request$1 } from "node:https";
|
|
|
15
15
|
import { BlockList, isIP } from "node:net";
|
|
16
16
|
import nodemailer from "nodemailer";
|
|
17
17
|
import { APIError, createAuthMiddleware } from "better-auth/api";
|
|
18
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
18
19
|
import { betterAuth } from "better-auth";
|
|
19
20
|
import { admin, genericOAuth, jwt } from "better-auth/plugins";
|
|
20
21
|
import { emailOTP } from "better-auth/plugins/email-otp";
|
|
@@ -769,7 +770,7 @@ function createBlobStorage(database, instance, factory = createObjectStore, sche
|
|
|
769
770
|
skipLocked: true
|
|
770
771
|
});
|
|
771
772
|
for (const row of rows) {
|
|
772
|
-
const backend = await instance.backend(requireRow(row.backendId ?? void 0)), key = requireRow(row.objectKey ?? void 0), bytes = requireRow(row.data ?? void 0);
|
|
773
|
+
const backend = await instance.backend(requireRow(row.backendId ?? void 0), tx), key = requireRow(row.objectKey ?? void 0), bytes = requireRow(row.data ?? void 0);
|
|
773
774
|
await transfer(backend.spec, async (store) => {
|
|
774
775
|
await store.put(key, bytes, row.contentType);
|
|
775
776
|
const verified = await store.get(key, row.size);
|
|
@@ -818,7 +819,7 @@ function createBlobStorage(database, instance, factory = createObjectStore, sche
|
|
|
818
819
|
});
|
|
819
820
|
for (const { object } of rows) {
|
|
820
821
|
if (object.backendId && object.objectKey) {
|
|
821
|
-
const backend = await instance.backend(object.backendId), key = object.objectKey;
|
|
822
|
+
const backend = await instance.backend(object.backendId, tx), key = object.objectKey;
|
|
822
823
|
await transfer(backend.spec, (store) => store.remove(key));
|
|
823
824
|
}
|
|
824
825
|
await tx.delete(blobObjects).where(eq(blobObjects.id, object.id));
|
|
@@ -872,6 +873,14 @@ function imageResponse(blob) {
|
|
|
872
873
|
}
|
|
873
874
|
//#endregion
|
|
874
875
|
//#region src/adapters/elasticsearch.ts
|
|
876
|
+
const fields$1 = [
|
|
877
|
+
"title^3",
|
|
878
|
+
"key^3",
|
|
879
|
+
"description",
|
|
880
|
+
"acceptanceCriteria",
|
|
881
|
+
"labels",
|
|
882
|
+
"scopes"
|
|
883
|
+
];
|
|
875
884
|
function fail$1(_status, _code, message) {
|
|
876
885
|
throw new Error(message);
|
|
877
886
|
}
|
|
@@ -970,37 +979,44 @@ function createElasticsearchIndex(configuration) {
|
|
|
970
979
|
let scrollId;
|
|
971
980
|
try {
|
|
972
981
|
const found = [];
|
|
982
|
+
let score;
|
|
973
983
|
let response = await accepted(await call$1(settings, `${settings.index}/_search?scroll=1m`, {
|
|
974
984
|
method: "POST",
|
|
975
985
|
body: JSON.stringify({
|
|
976
986
|
size: 1e3,
|
|
977
987
|
_source: false,
|
|
978
|
-
sort: ["_doc"],
|
|
979
988
|
query: { bool: {
|
|
980
989
|
filter: [{ terms: { projectId: [...projectIds] } }, { term: { instanceId: identity } }],
|
|
981
|
-
must: [{ multi_match: {
|
|
982
|
-
query,
|
|
983
|
-
fields:
|
|
984
|
-
"title^3",
|
|
985
|
-
"key^3",
|
|
986
|
-
"description",
|
|
987
|
-
"acceptanceCriteria",
|
|
988
|
-
"labels",
|
|
989
|
-
"scopes"
|
|
990
|
-
],
|
|
990
|
+
must: [...query.terms ? [{ multi_match: {
|
|
991
|
+
query: query.terms,
|
|
992
|
+
fields: fields$1,
|
|
991
993
|
operator: "and",
|
|
992
994
|
fuzziness: "AUTO"
|
|
993
|
-
} }]
|
|
995
|
+
} }] : [], ...query.phrases.map((phrase) => ({ multi_match: {
|
|
996
|
+
query: phrase,
|
|
997
|
+
fields: fields$1,
|
|
998
|
+
type: "phrase"
|
|
999
|
+
} }))]
|
|
994
1000
|
} }
|
|
995
1001
|
})
|
|
996
1002
|
}));
|
|
997
1003
|
for (;;) {
|
|
998
1004
|
const result = z.object({
|
|
999
1005
|
_scroll_id: z.string().optional(),
|
|
1000
|
-
hits: z.object({ hits: z.array(z.object({
|
|
1006
|
+
hits: z.object({ hits: z.array(z.object({
|
|
1007
|
+
_id: z.uuid(),
|
|
1008
|
+
_score: z.number()
|
|
1009
|
+
})) })
|
|
1001
1010
|
}).parse(await response.json());
|
|
1002
1011
|
scrollId = result["_scroll_id"] ?? scrollId;
|
|
1003
|
-
|
|
1012
|
+
for (const hit of result.hits.hits) {
|
|
1013
|
+
const rank = (found.at(-1)?.rank ?? 0) + (score === hit["_score"] ? 0 : 1);
|
|
1014
|
+
score = hit["_score"];
|
|
1015
|
+
found.push({
|
|
1016
|
+
id: hit["_id"],
|
|
1017
|
+
rank
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1004
1020
|
if (found.length > 1e4) throw new Error("External search unavailable");
|
|
1005
1021
|
if (!result.hits.hits.length || !scrollId) break;
|
|
1006
1022
|
response = await accepted(await call$1(settings, "_search/scroll", {
|
|
@@ -1034,12 +1050,32 @@ const createTicketIndex = (configuration) => {
|
|
|
1034
1050
|
//#endregion
|
|
1035
1051
|
//#region src/search.ts
|
|
1036
1052
|
const likePattern = (query) => `%${query.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_")}%`;
|
|
1053
|
+
function parseQuery(query) {
|
|
1054
|
+
const phrases = [];
|
|
1055
|
+
const terms = query.replace(/"([^"]*)"/gu, (_, phrase) => {
|
|
1056
|
+
const value = phrase.replace(/\s+/gu, " ").trim();
|
|
1057
|
+
if (value && phrases.length < 8 && !phrases.some((seen) => seen.toLowerCase() === value.toLowerCase())) phrases.push(value);
|
|
1058
|
+
return " ";
|
|
1059
|
+
}).replace(/\s+/gu, " ").trim();
|
|
1060
|
+
return phrases.length || terms ? {
|
|
1061
|
+
phrases,
|
|
1062
|
+
terms
|
|
1063
|
+
} : {
|
|
1064
|
+
phrases,
|
|
1065
|
+
terms: query
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
const querySegments = ({ phrases, terms }) => terms ? [...phrases, terms] : phrases;
|
|
1037
1069
|
function postgresSearch(query) {
|
|
1038
|
-
|
|
1039
|
-
|
|
1070
|
+
return and(...querySegments(parseQuery(query)).map((segment) => {
|
|
1071
|
+
const value = likePattern(segment);
|
|
1072
|
+
return or(ilike(tickets.title, value), ilike(tickets.description, value), ilike(tickets.key, value), ilike(tickets.acceptanceCriteria, value), sql`array_to_string(${tickets.labels}, ' ') ILIKE ${value}`, sql`array_to_string(${tickets.scopes}, ' ') ILIKE ${value}`);
|
|
1073
|
+
}));
|
|
1040
1074
|
}
|
|
1075
|
+
const postgresRank = (query) => sql`CASE WHEN ${and(...querySegments(parseQuery(query)).map((segment) => ilike(tickets.title, likePattern(segment))))} THEN 0 ELSE 1 END`;
|
|
1041
1076
|
const builtinSearch = { where: async (_db, _ids, query) => ({
|
|
1042
1077
|
where: postgresSearch(query),
|
|
1078
|
+
rank: postgresRank(query),
|
|
1043
1079
|
engine: "postgres",
|
|
1044
1080
|
indexing: false
|
|
1045
1081
|
}) };
|
|
@@ -1093,6 +1129,7 @@ function createTicketSearch(database, instance, factory = createTicketIndex, sch
|
|
|
1093
1129
|
const settings = await instance.search();
|
|
1094
1130
|
const fallback = () => ({
|
|
1095
1131
|
where: postgresSearch(query),
|
|
1132
|
+
rank: postgresRank(query),
|
|
1096
1133
|
engine: "postgres",
|
|
1097
1134
|
indexing: !!settings
|
|
1098
1135
|
});
|
|
@@ -1101,12 +1138,14 @@ function createTicketSearch(database, instance, factory = createTicketIndex, sch
|
|
|
1101
1138
|
if (!state || !clock || state.error || state.configHash !== fingerprint(settings) || state.cursor !== clock.value) return fallback();
|
|
1102
1139
|
const root = await instance.read();
|
|
1103
1140
|
try {
|
|
1104
|
-
const
|
|
1141
|
+
const hits = await factory({
|
|
1105
1142
|
provider: "elasticsearch",
|
|
1106
1143
|
config: settings
|
|
1107
|
-
}).find(root.identity, projectIds, query);
|
|
1144
|
+
}).find(root.identity, projectIds, parseQuery(query));
|
|
1145
|
+
const ids = hits.map((hit) => hit.id), ranks = hits.map((hit) => hit.rank);
|
|
1108
1146
|
return {
|
|
1109
1147
|
where: ids.length ? inArray(tickets.id, ids) : sql`false`,
|
|
1148
|
+
rank: sql`(${`{${ranks.join(",")}}`}::int[])[array_position(${`{${ids.join(",")}}`}::uuid[], ${tickets.id})]`,
|
|
1110
1149
|
engine: "index",
|
|
1111
1150
|
indexing: false
|
|
1112
1151
|
};
|
|
@@ -2254,23 +2293,17 @@ async function validateReferences(ctx, ticketId, changes) {
|
|
|
2254
2293
|
}
|
|
2255
2294
|
async function assertNoCycle(ctx, sourceId, targetId) {
|
|
2256
2295
|
if (sourceId === targetId) fail(409, "dependency_cycle", "A ticket cannot depend on itself");
|
|
2257
|
-
const
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
const node = pending.pop();
|
|
2269
|
-
if (!node || visited.has(node)) continue;
|
|
2270
|
-
if (node === sourceId) fail(409, "dependency_cycle", "This relationship would create a dependency cycle");
|
|
2271
|
-
visited.add(node);
|
|
2272
|
-
pending.push(...edges.get(node) ?? []);
|
|
2273
|
-
}
|
|
2296
|
+
const { rows } = await ctx.db.execute(sql`WITH RECURSIVE reach(id) AS (
|
|
2297
|
+
SELECT ${targetId}::uuid
|
|
2298
|
+
UNION
|
|
2299
|
+
SELECT edge.next FROM reach JOIN (
|
|
2300
|
+
SELECT source_id AS id, target_id AS next FROM ${relations$1} WHERE type = 'blocks'
|
|
2301
|
+
UNION ALL
|
|
2302
|
+
SELECT id, parent_id FROM ${tickets} WHERE parent_id IS NOT NULL
|
|
2303
|
+
) edge ON edge.id = reach.id
|
|
2304
|
+
)
|
|
2305
|
+
SELECT 1 FROM reach WHERE id = ${sourceId}::uuid LIMIT 1`);
|
|
2306
|
+
if (rows.length) fail(409, "dependency_cycle", "This relationship would create a dependency cycle");
|
|
2274
2307
|
}
|
|
2275
2308
|
async function updateTicketRow(ctx, input) {
|
|
2276
2309
|
const current = await getTicket(ctx, input.ticketId);
|
|
@@ -2417,7 +2450,7 @@ async function createTicketRow(ctx, input) {
|
|
|
2417
2450
|
...fields,
|
|
2418
2451
|
...state,
|
|
2419
2452
|
...dates,
|
|
2420
|
-
customFields: applyCustomFieldValues(await projectCustomFields(ctx), null, customFields),
|
|
2453
|
+
customFields: intakeOnly(ctx.actor) ? {} : applyCustomFieldValues(await projectCustomFields(ctx), null, customFields),
|
|
2421
2454
|
...await allocateTicket(ctx),
|
|
2422
2455
|
id,
|
|
2423
2456
|
createdBy: ctx.actor.id
|
|
@@ -2464,6 +2497,20 @@ const hitRow = z.object({
|
|
|
2464
2497
|
title: z.string().nullable(),
|
|
2465
2498
|
status: z.enum(states).nullable()
|
|
2466
2499
|
});
|
|
2500
|
+
const summarize = (ticket) => ticketSummarySchema.parse(json(ticket));
|
|
2501
|
+
const snippetLength = 240;
|
|
2502
|
+
function snippet(body, query) {
|
|
2503
|
+
const text = body?.replace(/\s+/gu, " ").trim() ?? "", lower = text.toLowerCase(), parsed = parseQuery(query);
|
|
2504
|
+
let at = -1, size = 0;
|
|
2505
|
+
for (const needle of [...querySegments(parsed), ...parsed.terms.split(" ")]) {
|
|
2506
|
+
const found = needle ? lower.indexOf(needle.toLowerCase()) : -1;
|
|
2507
|
+
if (found >= 0 && (at < 0 || found < at)) [at, size] = [found, needle.length];
|
|
2508
|
+
}
|
|
2509
|
+
if (at < 0) return "";
|
|
2510
|
+
if (text.length <= snippetLength) return text;
|
|
2511
|
+
const start = Math.max(0, Math.min(at + Math.floor((size - snippetLength) / 2), text.length - snippetLength)), end = start + snippetLength;
|
|
2512
|
+
return `${start > 0 ? "…" : ""}${text.slice(start > 0 ? start + 1 : start, end < text.length ? end - 1 : end)}${end < text.length ? "…" : ""}`;
|
|
2513
|
+
}
|
|
2467
2514
|
function excerpt(body, query, radius = 80) {
|
|
2468
2515
|
const text = body?.replace(/\s+/gu, " ").trim() ?? "", at = text.toLowerCase().indexOf(query.toLowerCase());
|
|
2469
2516
|
if (at < 0) return "";
|
|
@@ -2481,25 +2528,29 @@ async function deleteTicketRows(ctx, selection) {
|
|
|
2481
2528
|
const ids = [...selected.keys()];
|
|
2482
2529
|
const [claimed] = await ctx.db.select({ ticketId: claims.ticketId }).from(claims).where(inArray(claims.ticketId, ids)).limit(1);
|
|
2483
2530
|
if (claimed) fail(409, "claim_conflict", "Release ownership before deleting this ticket", { ticketId: claimed.ticketId });
|
|
2484
|
-
await publishMany(ctx, [
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2531
|
+
await publishMany(ctx, [
|
|
2532
|
+
...(await ctx.db.update(tickets).set({
|
|
2533
|
+
parentId: null,
|
|
2534
|
+
version: sql`${tickets.version} + 1`,
|
|
2535
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2536
|
+
}).where(and(inArray(tickets.parentId, ids), notInArray(tickets.id, ids))).returning()).map((child) => ({
|
|
2537
|
+
type: "ticket.updated",
|
|
2538
|
+
ticketId: child.id,
|
|
2539
|
+
data: {
|
|
2540
|
+
version: child.version,
|
|
2541
|
+
changedFields: ["parentId"]
|
|
2542
|
+
}
|
|
2543
|
+
})),
|
|
2544
|
+
...[...selected.values()].map((ticket) => ({
|
|
2545
|
+
type: "ticket.deleted",
|
|
2546
|
+
ticketId: ticket.id,
|
|
2547
|
+
data: {
|
|
2548
|
+
key: ticket.key,
|
|
2549
|
+
version: ticket.version
|
|
2550
|
+
}
|
|
2551
|
+
})),
|
|
2552
|
+
...await severedRelationEvents(ctx, ids)
|
|
2553
|
+
]);
|
|
2503
2554
|
await ctx.db.delete(tickets).where(inArray(tickets.id, ids));
|
|
2504
2555
|
}
|
|
2505
2556
|
async function relocation(ctx, ticket, destinationProjectId) {
|
|
@@ -2568,6 +2619,16 @@ async function relocation(ctx, ticket, destinationProjectId) {
|
|
|
2568
2619
|
fields
|
|
2569
2620
|
};
|
|
2570
2621
|
}
|
|
2622
|
+
async function readableTicket(ctx, identifier) {
|
|
2623
|
+
const allowed = await accessibleProjectRows(ctx.db, ctx.actor);
|
|
2624
|
+
const [ticket] = allowed.length ? await ctx.db.select().from(tickets).where(and(inArray(tickets.projectId, allowed.map((row) => row.id)), ticketMatch(identifier))).limit(1) : [];
|
|
2625
|
+
const owner = allowed.find((row) => row.id === ticket?.projectId);
|
|
2626
|
+
if (!ticket || !owner) fail(404, "not_found", "Ticket was not found");
|
|
2627
|
+
return {
|
|
2628
|
+
ticket,
|
|
2629
|
+
project: owner
|
|
2630
|
+
};
|
|
2631
|
+
}
|
|
2571
2632
|
const ticketHandlers = {
|
|
2572
2633
|
async list_ticket_labels(ctx, input) {
|
|
2573
2634
|
const labels = labelUsage(ctx, input.archived).as("usage");
|
|
@@ -2578,7 +2639,11 @@ const ticketHandlers = {
|
|
|
2578
2639
|
const related = input.relatedTo ? await getTicket(ctx, input.relatedTo) : void 0;
|
|
2579
2640
|
const search = input.q ? await ctx.search.where(ctx.db, [project(ctx).id], input.q) : void 0;
|
|
2580
2641
|
await assertCustomFieldFilters(ctx, [project(ctx).id], input);
|
|
2581
|
-
|
|
2642
|
+
const page = await pageOf(ctx, and(eq(tickets.projectId, project(ctx).id), parent ? eq(tickets.parentId, parent.id) : void 0, related ? or(inArray(tickets.id, ctx.db.select({ id: relations$1.targetId }).from(relations$1).where(eq(relations$1.sourceId, related.id))), inArray(tickets.id, ctx.db.select({ id: relations$1.sourceId }).from(relations$1).where(eq(relations$1.targetId, related.id)))) : void 0, predicateWhere(input), input.archived === "include" ? void 0 : eq(tickets.archived, input.archived === "only"), dueWhere(input.due), input.stateId ? eq(tickets.stateId, input.stateId) : void 0, input.scheduledFrom ? sql`${scheduleEnd} >= ${input.scheduledFrom}::timestamptz` : void 0, input.scheduledTo ? sql`${scheduleStart} < ${input.scheduledTo}::timestamptz` : void 0, search?.where), ticketOrder(input), input);
|
|
2643
|
+
return input.view === "summary" ? {
|
|
2644
|
+
...page,
|
|
2645
|
+
items: page.items.map(summarize)
|
|
2646
|
+
} : page;
|
|
2582
2647
|
},
|
|
2583
2648
|
async list_my_work(ctx, input) {
|
|
2584
2649
|
const allowed = await accessibleProjectRows(ctx.db, ctx.actor, input.workspaceId, "exclude");
|
|
@@ -2607,6 +2672,21 @@ const ticketHandlers = {
|
|
|
2607
2672
|
};
|
|
2608
2673
|
},
|
|
2609
2674
|
async create_ticket(ctx, input) {
|
|
2675
|
+
if (intakeOnly(ctx.actor)) input = {
|
|
2676
|
+
...input,
|
|
2677
|
+
automationReady: false,
|
|
2678
|
+
holdReason: intakeHoldReason,
|
|
2679
|
+
status: "todo",
|
|
2680
|
+
stateId: void 0,
|
|
2681
|
+
assigneeId: null,
|
|
2682
|
+
parentId: null,
|
|
2683
|
+
archived: false,
|
|
2684
|
+
dueAt: null,
|
|
2685
|
+
startAt: null,
|
|
2686
|
+
customFields: {},
|
|
2687
|
+
uploads: [],
|
|
2688
|
+
labels: operations.create_ticket.input.shape.labels.parse([.../* @__PURE__ */ new Set([...input.labels, "intake"])])
|
|
2689
|
+
};
|
|
2610
2690
|
const { row, event } = await createTicketRow(ctx, input);
|
|
2611
2691
|
await publishMany(ctx, [event, ...(await claimStagedUploads(ctx, row.id, input.uploads)).map((item) => ({
|
|
2612
2692
|
type: "attachment.created",
|
|
@@ -2652,7 +2732,7 @@ const ticketHandlers = {
|
|
|
2652
2732
|
...event.data,
|
|
2653
2733
|
duplicateOf: source.id
|
|
2654
2734
|
}
|
|
2655
|
-
}, ...relationEvents("relation.added", relation)]);
|
|
2735
|
+
}, ...relationEvents("relation.added", relation, [row, source])]);
|
|
2656
2736
|
return row;
|
|
2657
2737
|
},
|
|
2658
2738
|
update_ticket: updateTicket,
|
|
@@ -2742,10 +2822,7 @@ const ticketHandlers = {
|
|
|
2742
2822
|
return row;
|
|
2743
2823
|
},
|
|
2744
2824
|
async resolve_ticket(ctx, input) {
|
|
2745
|
-
const
|
|
2746
|
-
const [ticket] = allowed.length ? await ctx.db.select().from(tickets).where(and(inArray(tickets.projectId, allowed.map((row) => row.id)), ticketMatch(input.ticketId))).limit(1) : [];
|
|
2747
|
-
const owner = allowed.find((row) => row.id === ticket?.projectId);
|
|
2748
|
-
if (!ticket || !owner) fail(404, "not_found", "Ticket was not found");
|
|
2825
|
+
const { ticket, project: owner } = await readableTicket(ctx, input.ticketId);
|
|
2749
2826
|
return {
|
|
2750
2827
|
ticket,
|
|
2751
2828
|
project: {
|
|
@@ -2793,17 +2870,20 @@ const ticketHandlers = {
|
|
|
2793
2870
|
async search_tickets(ctx, input) {
|
|
2794
2871
|
const allowed = await accessibleProjectRows(ctx.db, ctx.actor, input.workspaceId, "exclude");
|
|
2795
2872
|
if (!allowed.length) return { items: [] };
|
|
2796
|
-
const ids = allowed.map((row) => row.id);
|
|
2797
|
-
return { items: (await ctx.db.select().from(tickets).where(and(inArray(tickets.projectId, ids), eq(tickets.archived, false), or(ticketMatch(input.q),
|
|
2873
|
+
const ids = allowed.map((row) => row.id), match = await ctx.search.where(ctx.db, ids, input.q);
|
|
2874
|
+
return { items: (await ctx.db.select().from(tickets).where(and(inArray(tickets.projectId, ids), eq(tickets.archived, false), or(ticketMatch(input.q), match.where))).orderBy(sql`CASE WHEN ${ticketMatch(input.q)} THEN 0 ELSE 1 END`, sql`${match.rank} NULLS LAST`, desc(tickets.updatedAt)).limit(input.limit)).flatMap((ticket) => {
|
|
2798
2875
|
const owner = allowed.find((row) => row.id === ticket.projectId);
|
|
2799
|
-
|
|
2800
|
-
|
|
2876
|
+
if (!owner) return [];
|
|
2877
|
+
const summary = input.view === "summary", body = `${ticket.description} ${ticket.acceptanceCriteria}`;
|
|
2878
|
+
return [{
|
|
2879
|
+
ticket: summary ? summarize(ticket) : ticket,
|
|
2801
2880
|
project: {
|
|
2802
2881
|
id: owner.id,
|
|
2803
2882
|
key: owner.key,
|
|
2804
2883
|
name: owner.name
|
|
2805
|
-
}
|
|
2806
|
-
|
|
2884
|
+
},
|
|
2885
|
+
...summary && { snippet: snippet(body, input.q) }
|
|
2886
|
+
}];
|
|
2807
2887
|
}) };
|
|
2808
2888
|
},
|
|
2809
2889
|
async search_workspace(ctx, input) {
|
|
@@ -2828,23 +2908,26 @@ const ticketHandlers = {
|
|
|
2828
2908
|
filtered ? void 0 : sql`SELECT 'project' AS type, projects.id::text AS id, projects.id AS project_id,
|
|
2829
2909
|
NULL::uuid AS ticket_id, NULL::text AS actor_id, projects.description AS body,
|
|
2830
2910
|
NULL::text AS event_type, NULL::jsonb AS data, projects.updated_at AS at,
|
|
2831
|
-
CASE WHEN projects.key = ${key} THEN 0 ELSE 1 END AS rank, 0 AS family
|
|
2911
|
+
CASE WHEN projects.key = ${key} THEN 0 ELSE 1 END AS rank, 0 AS family,
|
|
2912
|
+
NULL::int AS score
|
|
2832
2913
|
FROM projects WHERE ${inArray(projects.id, ids)}
|
|
2833
2914
|
AND (projects.name ILIKE ${like} OR projects.key ILIKE ${like} OR projects.description ILIKE ${like})`,
|
|
2834
2915
|
sql`SELECT 'ticket' AS type, tickets.id::text AS id, tickets.project_id,
|
|
2835
2916
|
tickets.id AS ticket_id, NULL::text AS actor_id,
|
|
2836
2917
|
concat_ws(' ', tickets.description, tickets.acceptance_criteria) AS body,
|
|
2837
2918
|
NULL::text AS event_type, NULL::jsonb AS data, tickets.updated_at AS at,
|
|
2838
|
-
CASE WHEN ${ticketMatch(input.q)} THEN 0 ELSE 1 END AS rank, 1 AS family
|
|
2919
|
+
CASE WHEN ${ticketMatch(input.q)} THEN 0 ELSE 1 END AS rank, 1 AS family,
|
|
2920
|
+
${match.rank}::int AS score
|
|
2839
2921
|
FROM tickets WHERE ${and(ticketScope, or(ticketMatch(input.q), match.where))}`,
|
|
2840
2922
|
sql`SELECT 'comment' AS type, comments.id::text AS id, comments.project_id,
|
|
2841
2923
|
comments.ticket_id, comments.actor_id, comments.body, NULL::text AS event_type,
|
|
2842
|
-
NULL::jsonb AS data, comments.created_at AS at, 1 AS rank, 2 AS family
|
|
2924
|
+
NULL::jsonb AS data, comments.created_at AS at, 1 AS rank, 2 AS family,
|
|
2925
|
+
NULL::int AS score
|
|
2843
2926
|
FROM comments JOIN tickets ON tickets.id = comments.ticket_id
|
|
2844
2927
|
WHERE ${and(ticketScope, sql`comments.body ILIKE ${like}`)}`,
|
|
2845
2928
|
sql`SELECT 'activity' AS type, events.id::text AS id, events.project_id,
|
|
2846
2929
|
events.ticket_id, events.actor_id, NULL::text AS body, events.type AS event_type,
|
|
2847
|
-
events.data, events.created_at AS at, 1 AS rank, 3 AS family
|
|
2930
|
+
events.data, events.created_at AS at, 1 AS rank, 3 AS family, NULL::int AS score
|
|
2848
2931
|
FROM events
|
|
2849
2932
|
LEFT JOIN tickets ON tickets.id = events.ticket_id AND tickets.project_id = events.project_id
|
|
2850
2933
|
LEFT JOIN "user" ON "user".id = events.actor_id
|
|
@@ -2861,14 +2944,15 @@ const ticketHandlers = {
|
|
|
2861
2944
|
FROM (${hits}) AS hits
|
|
2862
2945
|
LEFT JOIN tickets ON tickets.id = hits.ticket_id AND tickets.project_id = hits.project_id
|
|
2863
2946
|
WHERE ${input.type ? sql`hits.type = ${input.type}` : sql`true`}
|
|
2864
|
-
ORDER BY hits.rank, hits.family, hits.at DESC, hits.id DESC
|
|
2947
|
+
ORDER BY hits.rank, hits.family, hits.score NULLS LAST, hits.at DESC, hits.id DESC
|
|
2865
2948
|
LIMIT ${input.limit} OFFSET ${input.offset}`)).rows.map((row) => hitRow.parse(row));
|
|
2866
2949
|
const projectsById = new Map(allowed.map((row) => [row.id, row]));
|
|
2950
|
+
const ticketIds = rows.flatMap((row) => row.ticket_id ? [row.ticket_id] : []), summaries = input.view === "summary" && ticketIds.length ? new Map((await ctx.db.select().from(tickets).where(inArray(tickets.id, ticketIds))).map((row) => [row.id, summarize(row)])) : void 0;
|
|
2867
2951
|
return {
|
|
2868
2952
|
items: rows.flatMap((row) => {
|
|
2869
2953
|
const owner = projectsById.get(row.project_id);
|
|
2870
2954
|
if (!owner) return [];
|
|
2871
|
-
const ticket = row.ticket_id && row.key && row.title && row.status ? {
|
|
2955
|
+
const ticket = row.ticket_id && row.key && row.title && row.status ? summaries?.get(row.ticket_id) ?? {
|
|
2872
2956
|
id: row.ticket_id,
|
|
2873
2957
|
key: row.key,
|
|
2874
2958
|
title: row.title,
|
|
@@ -2894,6 +2978,7 @@ const ticketHandlers = {
|
|
|
2894
2978
|
createdAt: at
|
|
2895
2979
|
}, true) : ticket?.title ?? "",
|
|
2896
2980
|
excerpt: excerpt(row.body, input.q),
|
|
2981
|
+
...input.view === "summary" && { snippet: snippet(row.body, input.q) },
|
|
2897
2982
|
actorId: row.actor_id,
|
|
2898
2983
|
at
|
|
2899
2984
|
}];
|
|
@@ -2951,11 +3036,17 @@ const ticketHandlers = {
|
|
|
2951
3036
|
},
|
|
2952
3037
|
async recover_claim(ctx, input) {
|
|
2953
3038
|
const ticket = await getTicket(ctx, input.ticketId);
|
|
2954
|
-
const current = one(await ctx.db.select().from(claims).where(and(eq(claims.id, input.claimId), eq(claims.ticketId, ticket.id))));
|
|
2955
|
-
if (
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
3039
|
+
const current = input.claimId === void 0 ? void 0 : one(await ctx.db.select().from(claims).where(and(eq(claims.id, input.claimId), eq(claims.ticketId, ticket.id))));
|
|
3040
|
+
if (!current) {
|
|
3041
|
+
const held = (await ctx.db.select({ id: claims.id }).from(claims).where(eq(claims.ticketId, ticket.id)).limit(1))[0];
|
|
3042
|
+
if (held) fail(409, "claim_conflict", "This ticket has a claim; recover it by its claim ID", { claimId: held.id });
|
|
3043
|
+
if (!ticket.claimRequired) fail(409, "claim_not_required", "This ticket does not require ownership to recover");
|
|
3044
|
+
} else {
|
|
3045
|
+
if (input.cancelActive && !can(ctx, "project.manage")) fail(403, "forbidden", "Cancelling an active claim requires project management access and admin scope");
|
|
3046
|
+
if (current.expiresAt > /* @__PURE__ */ new Date() && !input.cancelActive) fail(409, "claim_not_expired", "The current claim has not expired");
|
|
3047
|
+
}
|
|
3048
|
+
if (input.expectedVersion !== void 0 && (input.reschedule || !current)) assertVersion(ticket, input.expectedVersion, "ticket", "recovering ownership");
|
|
3049
|
+
if (input.reschedule) {
|
|
2959
3050
|
const row = one(await ctx.db.update(tickets).set({
|
|
2960
3051
|
...await resolveState(ctx, { status: "todo" }, ticket),
|
|
2961
3052
|
version: ticket.version + 1,
|
|
@@ -2976,12 +3067,12 @@ const ticketHandlers = {
|
|
|
2976
3067
|
changedFields: ["claimRequired"]
|
|
2977
3068
|
});
|
|
2978
3069
|
}
|
|
2979
|
-
await ctx.db.delete(claims).where(eq(claims.id, current.id));
|
|
3070
|
+
if (current) await ctx.db.delete(claims).where(eq(claims.id, current.id));
|
|
2980
3071
|
await publish(ctx, "claim.recovered", ticket.id, {
|
|
2981
|
-
claimId: current
|
|
3072
|
+
claimId: current?.id ?? null,
|
|
2982
3073
|
reason: input.reason,
|
|
2983
3074
|
confirmedStopped: true,
|
|
2984
|
-
...input.cancelActive && { cancelActive: true }
|
|
3075
|
+
...current && input.cancelActive && { cancelActive: true }
|
|
2985
3076
|
});
|
|
2986
3077
|
return { ok: true };
|
|
2987
3078
|
}
|
|
@@ -3259,6 +3350,13 @@ function mentionedUserIds(markdown) {
|
|
|
3259
3350
|
//#region src/collaboration-operations.ts
|
|
3260
3351
|
const sourceTicket = alias(tickets, "source_ticket");
|
|
3261
3352
|
const targetTicket = alias(tickets, "target_ticket");
|
|
3353
|
+
const sourceProject = alias(projects, "source_project");
|
|
3354
|
+
const targetProject = alias(projects, "target_project");
|
|
3355
|
+
async function assertMayConstrain(ctx, relation, sides) {
|
|
3356
|
+
const constrained = relation.type === "blocks" ? relation.targetId : relation.type === "duplicate" ? relation.sourceId : null;
|
|
3357
|
+
const side = sides.find((item) => item.id === constrained);
|
|
3358
|
+
if (side && side.projectId !== project(ctx).id) await context(ctx.db, ctx.actor, "write", side.projectId);
|
|
3359
|
+
}
|
|
3262
3360
|
async function editableComment(ctx, commentId, expectedVersion) {
|
|
3263
3361
|
const row = one(await ctx.db.select().from(comments).where(and(eq(comments.projectId, project(ctx).id), eq(comments.id, commentId))));
|
|
3264
3362
|
if (row.actorId !== ctx.actor.id && !can(ctx, "project.manage")) fail(403, "forbidden", "Only the author or a project administrator can edit this comment");
|
|
@@ -3289,18 +3387,45 @@ async function addComment(ctx, input) {
|
|
|
3289
3387
|
const collaborationHandlers = {
|
|
3290
3388
|
async list_relations(ctx, input) {
|
|
3291
3389
|
const ticket = await getTicket(ctx, input.ticketId);
|
|
3292
|
-
|
|
3390
|
+
const rows = await ctx.db.select({
|
|
3293
3391
|
...getTableColumns(relations$1),
|
|
3294
3392
|
sourceKey: sourceTicket.key,
|
|
3295
3393
|
sourceTitle: sourceTicket.title,
|
|
3394
|
+
sourceProjectId: sourceTicket.projectId,
|
|
3395
|
+
sourceProjectKey: sourceProject.key,
|
|
3296
3396
|
targetKey: targetTicket.key,
|
|
3297
|
-
targetTitle: targetTicket.title
|
|
3298
|
-
|
|
3397
|
+
targetTitle: targetTicket.title,
|
|
3398
|
+
targetProjectId: targetTicket.projectId,
|
|
3399
|
+
targetProjectKey: targetProject.key
|
|
3400
|
+
}).from(relations$1).innerJoin(sourceTicket, eq(sourceTicket.id, relations$1.sourceId)).innerJoin(targetTicket, eq(targetTicket.id, relations$1.targetId)).innerJoin(sourceProject, eq(sourceProject.id, sourceTicket.projectId)).innerJoin(targetProject, eq(targetProject.id, targetTicket.projectId)).where(or(eq(relations$1.sourceId, ticket.id), eq(relations$1.targetId, ticket.id)));
|
|
3401
|
+
const here = project(ctx).id, elsewhere = rows.some((row) => row.sourceProjectId !== here || row.targetProjectId !== here), readable = /* @__PURE__ */ new Set([here, ...(elsewhere ? await accessibleProjectRows(ctx.db, ctx.actor) : []).map((row) => row.id)]);
|
|
3402
|
+
return rows.map(({ sourceTitle, sourceProjectId, sourceProjectKey, targetTitle, targetProjectId, targetProjectKey, ...row }) => {
|
|
3403
|
+
const source = readable.has(sourceProjectId), target = readable.has(targetProjectId);
|
|
3404
|
+
return {
|
|
3405
|
+
...row,
|
|
3406
|
+
...source && {
|
|
3407
|
+
sourceTitle,
|
|
3408
|
+
sourceProjectId,
|
|
3409
|
+
sourceProjectKey
|
|
3410
|
+
},
|
|
3411
|
+
...target && {
|
|
3412
|
+
targetTitle,
|
|
3413
|
+
targetProjectId,
|
|
3414
|
+
targetProjectKey
|
|
3415
|
+
},
|
|
3416
|
+
restricted: !source || !target
|
|
3417
|
+
};
|
|
3418
|
+
});
|
|
3299
3419
|
},
|
|
3300
3420
|
async add_relation(ctx, input) {
|
|
3301
|
-
const current = await getTicket(ctx, input.ticketId), other = await
|
|
3421
|
+
const current = await getTicket(ctx, input.ticketId), { ticket: other } = await readableTicket(ctx, input.targetId);
|
|
3302
3422
|
const [source, target] = input.direction === "incoming" ? [other, current] : [current, other];
|
|
3303
3423
|
if (source.id === target.id) fail(409, "invalid_relation", "Choose a different ticket");
|
|
3424
|
+
await assertMayConstrain(ctx, {
|
|
3425
|
+
type: input.type,
|
|
3426
|
+
sourceId: source.id,
|
|
3427
|
+
targetId: target.id
|
|
3428
|
+
}, [current, other]);
|
|
3304
3429
|
if (input.type === "blocks") await assertNoCycle(ctx, source.id, target.id);
|
|
3305
3430
|
const [sourceId, targetId] = input.type === "related" ? [source.id, target.id].toSorted() : [source.id, target.id];
|
|
3306
3431
|
const row = one(await ctx.db.insert(relations$1).values({
|
|
@@ -3316,12 +3441,19 @@ const collaborationHandlers = {
|
|
|
3316
3441
|
],
|
|
3317
3442
|
set: { type: input.type }
|
|
3318
3443
|
}).returning());
|
|
3319
|
-
await publishMany(ctx, relationEvents("relation.added", row));
|
|
3444
|
+
await publishMany(ctx, relationEvents("relation.added", row, [current, other]));
|
|
3320
3445
|
return row;
|
|
3321
3446
|
},
|
|
3322
3447
|
async remove_relation(ctx, input) {
|
|
3323
|
-
const
|
|
3324
|
-
await
|
|
3448
|
+
const found = one(await ctx.db.select().from(relations$1).where(eq(relations$1.id, input.relationId)));
|
|
3449
|
+
const sides = await ctx.db.select({
|
|
3450
|
+
id: tickets.id,
|
|
3451
|
+
projectId: tickets.projectId
|
|
3452
|
+
}).from(tickets).where(inArray(tickets.id, [found.sourceId, found.targetId]));
|
|
3453
|
+
if (!sides.some((side) => side.projectId === project(ctx).id)) fail(404, "not_found", "The requested record was not found");
|
|
3454
|
+
await assertMayConstrain(ctx, found, sides);
|
|
3455
|
+
const row = one(await ctx.db.delete(relations$1).where(eq(relations$1.id, found.id)).returning());
|
|
3456
|
+
await publishMany(ctx, relationEvents("relation.removed", row, sides));
|
|
3325
3457
|
return { ok: true };
|
|
3326
3458
|
},
|
|
3327
3459
|
async list_comments(ctx, input) {
|
|
@@ -3974,7 +4106,7 @@ function matches(rule, names, labels) {
|
|
|
3974
4106
|
const has = (id) => carried.has(names.get(id)?.toLowerCase() ?? "\0");
|
|
3975
4107
|
return rule.match === "all" ? rule.labels.every(has) : rule.labels.some(has);
|
|
3976
4108
|
}
|
|
3977
|
-
const qualifies = (rules, names, link, ticket) => link
|
|
4109
|
+
const qualifies = (rules, names, link, ticket) => !intakeQuarantined(ticket) && (link?.origin === "inbound" ? matches(rules.inbound, names, link.remote?.labels ?? []) : matches(rules.outbound, names, ticket.labels));
|
|
3978
4110
|
const workspaceOf = async (db, projectId) => one(await db.select({ workspaceId: projects.workspaceId }).from(projects).where(eq(projects.id, projectId))).workspaceId;
|
|
3979
4111
|
async function setLinks(db, projectId, links, changes) {
|
|
3980
4112
|
if (!links.length) return [];
|
|
@@ -4036,7 +4168,7 @@ async function queueIssueSync(db, rows) {
|
|
|
4036
4168
|
for (const ticket of found) for (const connection of connections) {
|
|
4037
4169
|
const link = links.find((item) => item.connectionId === connection.id && item.ticketId === ticket.id), labelled = names.get(connection.id) ?? /* @__PURE__ */ new Map();
|
|
4038
4170
|
if (!link) {
|
|
4039
|
-
if (connection.projectId === ticket.projectId && !ticket.archived && !complete(ticket.status) &&
|
|
4171
|
+
if (connection.projectId === ticket.projectId && !ticket.archived && !complete(ticket.status) && qualifies(connection, labelled, null, ticket)) await queueSync(db, await db.insert(issueLinks).values({
|
|
4040
4172
|
connectionId: connection.id,
|
|
4041
4173
|
ticketId: ticket.id,
|
|
4042
4174
|
origin: "outbound",
|
|
@@ -4057,8 +4189,9 @@ async function queueIssueSync(db, rows) {
|
|
|
4057
4189
|
state: "review",
|
|
4058
4190
|
reason: "labels"
|
|
4059
4191
|
});
|
|
4060
|
-
else if (link.state === "active" && !connection.paused) {
|
|
4192
|
+
else if ((link.state === "active" || link.state === "creating") && qualified && !connection.paused) {
|
|
4061
4193
|
await queueSync(db, [link]);
|
|
4194
|
+
if (link.state !== "active") continue;
|
|
4062
4195
|
const fresh = commentEvents.filter((event) => event.ticketId === ticket.id && !copied.has(`${link.id}:${String(event.data.commentId)}`));
|
|
4063
4196
|
if (fresh.length) await db.insert(deliveries).values(fresh.map((event) => ({
|
|
4064
4197
|
connectionId: connection.id,
|
|
@@ -4311,6 +4444,10 @@ async function sendIssueSync(database, senders, secrets, drivers, job, event) {
|
|
|
4311
4444
|
}).from(issueLinks).innerJoin(tickets, eq(tickets.id, issueLinks.ticketId)).innerJoin(repositoryConnections, eq(repositoryConnections.id, issueLinks.connectionId)).innerJoin(projects, eq(projects.id, repositoryConnections.projectId)).where(eq(issueLinks.id, job.issueLinkId ?? "")))[0];
|
|
4312
4445
|
if (!loaded) return finish(database.db, job, "link_removed");
|
|
4313
4446
|
const { link, ticket, connection } = loaded;
|
|
4447
|
+
if (intakeQuarantined(ticket)) return locked(database, async (db) => {
|
|
4448
|
+
const current = (await db.select({ holdReason: tickets.holdReason }).from(tickets).where(eq(tickets.id, ticket.id)))[0];
|
|
4449
|
+
if (!current || intakeQuarantined(current)) await finish(db, job, current ? "quarantined" : "link_removed");
|
|
4450
|
+
});
|
|
4314
4451
|
const skip = connection.paused ? "paused" : !loaded.writer ? "access_revoked" : loaded.archivedAt ? "project_archived" : ticket.projectId !== connection.projectId ? "moved" : null;
|
|
4315
4452
|
if (skip === "access_revoked" || skip === "project_archived") return locked(database, async (db) => {
|
|
4316
4453
|
await finish(db, job, skip);
|
|
@@ -4525,7 +4662,7 @@ async function plan(ctx, connection, { inbound, outbound }) {
|
|
|
4525
4662
|
if (link.state === "active") (qualified ? matching : pause).push(link);
|
|
4526
4663
|
else if (link.state === "paused" && link.reason === "labels" && qualified) review.push(link);
|
|
4527
4664
|
}
|
|
4528
|
-
const exported = rules.outbound.enabled ? (await ctx.db.select().from(tickets).where(and(eq(tickets.projectId, connection.projectId), eq(tickets.archived, false), notInArray(tickets.status, [...closedStates]), notInArray(tickets.id, ctx.db.select({ id: issueLinks.ticketId }).from(issueLinks).where(eq(issueLinks.connectionId, connection.id)))))).filter((ticket) =>
|
|
4665
|
+
const exported = rules.outbound.enabled ? (await ctx.db.select().from(tickets).where(and(eq(tickets.projectId, connection.projectId), eq(tickets.archived, false), notInArray(tickets.status, [...closedStates]), notInArray(tickets.id, ctx.db.select({ id: issueLinks.ticketId }).from(issueLinks).where(eq(issueLinks.connectionId, connection.id)))))).filter((ticket) => qualifies(rules, names, null, ticket)) : [];
|
|
4529
4666
|
return {
|
|
4530
4667
|
matching,
|
|
4531
4668
|
pause,
|
|
@@ -5769,22 +5906,26 @@ async function removeProject(ctx) {
|
|
|
5769
5906
|
key: tickets.key,
|
|
5770
5907
|
version: tickets.version
|
|
5771
5908
|
}).from(tickets).where(eq(tickets.projectId, current.id)).orderBy(asc(tickets.number));
|
|
5772
|
-
await recordAudience(ctx, requireRow((await publishMany(ctx, [
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5909
|
+
await recordAudience(ctx, requireRow((await publishMany(ctx, [
|
|
5910
|
+
...removed.map((ticket) => ({
|
|
5911
|
+
type: "ticket.deleted",
|
|
5912
|
+
ticketId: ticket.id,
|
|
5913
|
+
data: {
|
|
5914
|
+
key: ticket.key,
|
|
5915
|
+
version: ticket.version
|
|
5916
|
+
}
|
|
5917
|
+
})),
|
|
5918
|
+
...await severedRelationEvents(ctx),
|
|
5919
|
+
{
|
|
5920
|
+
type: "project.deleted",
|
|
5921
|
+
ticketId: null,
|
|
5922
|
+
data: {
|
|
5923
|
+
key: current.key,
|
|
5924
|
+
name: current.name,
|
|
5925
|
+
tickets: removed.length
|
|
5926
|
+
}
|
|
5786
5927
|
}
|
|
5787
|
-
|
|
5928
|
+
], false)).at(-1)), readers.map((person) => person.userId));
|
|
5788
5929
|
await ctx.db.delete(tickets).where(eq(tickets.projectId, current.id));
|
|
5789
5930
|
await ctx.db.delete(stagedUploads).where(eq(stagedUploads.projectId, current.id));
|
|
5790
5931
|
await ctx.db.delete(projectLabels).where(eq(projectLabels.projectId, current.id));
|
|
@@ -6021,7 +6162,8 @@ const timeEntryHandlers = {
|
|
|
6021
6162
|
//#region src/public-operations.ts
|
|
6022
6163
|
const listTickets = async (ctx, input) => parseOutput("list_tickets", json(await ticketHandlers.list_tickets(ctx, operations.list_tickets.input.parse({
|
|
6023
6164
|
...input,
|
|
6024
|
-
projectId: project(ctx).id
|
|
6165
|
+
projectId: project(ctx).id,
|
|
6166
|
+
view: "full"
|
|
6025
6167
|
}))));
|
|
6026
6168
|
async function published(ctx, ids) {
|
|
6027
6169
|
if (!ids.length) return /* @__PURE__ */ new Set();
|
|
@@ -6824,7 +6966,13 @@ function json(value) {
|
|
|
6824
6966
|
return JSON.parse(JSON.stringify(value, (_, item) => typeof item === "bigint" ? item.toString() : item));
|
|
6825
6967
|
}
|
|
6826
6968
|
const complete = (status) => closedStates.some((closed) => closed === status);
|
|
6827
|
-
const
|
|
6969
|
+
const intakeHoldReason = "Untrusted intake: human triage required";
|
|
6970
|
+
const intakeQuarantined = (ticket) => ticket.holdReason === intakeHoldReason;
|
|
6971
|
+
const intakeOnly = (actor) => actor.scopes.length > 0 && actor.scopes.every((scope) => scope === "intake");
|
|
6972
|
+
const hasScope = (actor, scope, operation) => intakeOnly(actor) ? operation === "create_ticket" : actor.scopes.includes("admin") || actor.scopes.includes(scope) || scope === "read" && actor.scopes.some((value) => value !== "intake");
|
|
6973
|
+
function requireScope(actor, scope, operation) {
|
|
6974
|
+
if (!hasScope(actor, scope, operation)) fail(403, "insufficient_scope", `This credential requires ${scope} permission`);
|
|
6975
|
+
}
|
|
6828
6976
|
const capabilities = (actor, granted) => granted.filter((permission) => hasScope(actor, permissionScope(permission)));
|
|
6829
6977
|
const can = (ctx, permission) => capabilities(ctx.actor, ctx.permissions).includes(permission);
|
|
6830
6978
|
function requirePermission(granted, permission, where) {
|
|
@@ -6875,8 +7023,8 @@ function ticketMatch(identifier) {
|
|
|
6875
7023
|
const key = identifier.toUpperCase(), [, prefix, number] = issuedKey.exec(key) ?? [];
|
|
6876
7024
|
return or(sql`${tickets.id}::text = ${identifier}`, eq(tickets.key, key), sql`${tickets.id} IN (SELECT ta.ticket_id FROM ${ticketAliases} ta WHERE ta.key = ${key})`, prefix && number ? and(eq(tickets.number, Number(number)), sql`${tickets.projectId} IN (SELECT pp.project_id FROM ${projectPrefixes} pp WHERE pp.prefix = ${prefix})`) : void 0);
|
|
6877
7025
|
}
|
|
6878
|
-
async function context(db, actor, scope, projectId, workspaceId, permission = scope === "read" ? null : scope === "admin" ? "project.manage" : "ticket.write") {
|
|
6879
|
-
|
|
7026
|
+
async function context(db, actor, scope, projectId, workspaceId, permission = scope === "read" ? null : scope === "admin" ? "project.manage" : "ticket.write", operation) {
|
|
7027
|
+
requireScope(actor, scope, operation);
|
|
6880
7028
|
const ctx = baseContext(db, actor);
|
|
6881
7029
|
if (workspaceId) {
|
|
6882
7030
|
const access = await requireWorkspace(ctx, workspaceId, permission);
|
|
@@ -7052,11 +7200,26 @@ async function publish(ctx, type, ticketId, data) {
|
|
|
7052
7200
|
data
|
|
7053
7201
|
}]));
|
|
7054
7202
|
}
|
|
7055
|
-
const relationEvents = (type, relation) => [relation.sourceId, relation.targetId].map((ticketId) => ({
|
|
7203
|
+
const relationEvents = (type, relation, sides) => [relation.sourceId, relation.targetId].map((ticketId) => ({
|
|
7056
7204
|
type,
|
|
7057
7205
|
ticketId,
|
|
7058
|
-
data: { relation }
|
|
7206
|
+
data: { relation },
|
|
7207
|
+
projectId: requireRow(sides.find((side) => side.id === ticketId)).projectId
|
|
7059
7208
|
}));
|
|
7209
|
+
const deletedSide = alias(tickets, "deleted_side");
|
|
7210
|
+
const survivingSide = alias(tickets, "surviving_side");
|
|
7211
|
+
async function severedRelationEvents(ctx, ticketIds) {
|
|
7212
|
+
return (await ctx.db.select({
|
|
7213
|
+
relation: relations$1,
|
|
7214
|
+
ticketId: survivingSide.id,
|
|
7215
|
+
projectId: survivingSide.projectId
|
|
7216
|
+
}).from(relations$1).innerJoin(deletedSide, or(eq(deletedSide.id, relations$1.sourceId), eq(deletedSide.id, relations$1.targetId))).innerJoin(survivingSide, or(eq(survivingSide.id, relations$1.sourceId), eq(survivingSide.id, relations$1.targetId))).where(and(ticketIds ? inArray(deletedSide.id, [...ticketIds]) : eq(deletedSide.projectId, project(ctx).id), ne(survivingSide.projectId, project(ctx).id)))).map(({ relation, ticketId, projectId }) => ({
|
|
7217
|
+
type: "relation.removed",
|
|
7218
|
+
ticketId,
|
|
7219
|
+
projectId,
|
|
7220
|
+
data: { relation }
|
|
7221
|
+
}));
|
|
7222
|
+
}
|
|
7060
7223
|
function ownsLiveClaim(ctx, claim) {
|
|
7061
7224
|
return claim.ownerId === ctx.actor.ownerId && claim.expiresAt > /* @__PURE__ */ new Date() && hasScope(ctx.actor, "claim") && ctx.permissions.includes("ticket.write");
|
|
7062
7225
|
}
|
|
@@ -7148,6 +7311,7 @@ function createDomain(database, services = {}) {
|
|
|
7148
7311
|
const input = spec.input.parse(value);
|
|
7149
7312
|
const projectId = "projectId" in input ? input.projectId : void 0;
|
|
7150
7313
|
const workspaceId = "workspaceId" in input && !workspaceIsFilter.has(name) ? input.workspaceId : void 0;
|
|
7314
|
+
if (intakeOnly(actor)) requireScope(actor, spec.scope, name);
|
|
7151
7315
|
if (actor.machine && sessionOnly.has(name)) fail(403, "session_required", "Use a signed-in account for this action");
|
|
7152
7316
|
if (actor.projectIds && spec.method !== "GET" && !projectId) fail(403, "insufficient_scope", "Project-bound credentials can only change their configured projects");
|
|
7153
7317
|
const outgoing = [];
|
|
@@ -7161,19 +7325,20 @@ function createDomain(database, services = {}) {
|
|
|
7161
7325
|
return parseOutput(name, json(await handler(ctx, input)));
|
|
7162
7326
|
};
|
|
7163
7327
|
const permission = operationPermission(name);
|
|
7164
|
-
if (spec.method === "GET" || remoteReads.has(name)) return invoke(await context(database.db, actor, spec.scope, projectId, workspaceId, permission));
|
|
7328
|
+
if (spec.method === "GET" || remoteReads.has(name)) return invoke(await context(database.db, actor, spec.scope, projectId, workspaceId, permission, name));
|
|
7165
7329
|
const requestId = "requestId" in input ? input.requestId : void 0;
|
|
7166
7330
|
const reader = sourceReads[name];
|
|
7167
|
-
if (reader && !(requestId && (await database.db.select({ id: receipts.requestId }).from(receipts).where(and(eq(receipts.actorId, actor.id), eq(receipts.requestId, requestId))).limit(1)).length)) read = await reader.read(withServices(await context(database.db, actor, spec.scope, projectId, workspaceId, permission)), input);
|
|
7331
|
+
if (reader && !(requestId && (await database.db.select({ id: receipts.requestId }).from(receipts).where(and(eq(receipts.actorId, actor.id), eq(receipts.requestId, requestId))).limit(1)).length)) read = await reader.read(withServices(await context(database.db, actor, spec.scope, projectId, workspaceId, permission, name)), input);
|
|
7168
7332
|
const committed = await locked(async (tx) => {
|
|
7169
7333
|
const hash = createHash("sha256").update(JSON.stringify({
|
|
7170
7334
|
name,
|
|
7171
|
-
input: reader ? reader.receipt(input) : input
|
|
7335
|
+
input: reader ? reader.receipt(input) : input,
|
|
7336
|
+
...intakeOnly(actor) && { intakeCredential: actor.ownerId }
|
|
7172
7337
|
})).digest("hex");
|
|
7173
7338
|
const receipt = requestId ? (await tx.select().from(receipts).where(and(eq(receipts.actorId, actor.id), eq(receipts.requestId, requestId))).limit(1))[0] : void 0;
|
|
7174
7339
|
if (receipt && (receipt.hash !== hash || receipt.operation !== name)) fail(409, "idempotency_conflict", "This requestId was already used for another write");
|
|
7175
7340
|
if (receipt && (name === "delete_project" || name === "delete_workspace")) return parseOutput(name, receipt.result);
|
|
7176
|
-
const ctx = await context(tx, actor, spec.scope, projectId, workspaceId, permission);
|
|
7341
|
+
const ctx = await context(tx, actor, spec.scope, projectId, workspaceId, permission, name);
|
|
7177
7342
|
if (receipt) {
|
|
7178
7343
|
if ((name === "create_time_entry" || name === "update_time_entry" || name === "delete_time_entry") && "ticketId" in input) await getTicket(ctx, input.ticketId);
|
|
7179
7344
|
const prior = parseOutput(name, receipt.result);
|
|
@@ -7670,6 +7835,8 @@ function createAuth(database, config) {
|
|
|
7670
7835
|
schema: auth_schema_exports,
|
|
7671
7836
|
transaction: true
|
|
7672
7837
|
});
|
|
7838
|
+
const verification = new AsyncLocalStorage();
|
|
7839
|
+
const consumed = () => verification.getStore()?.row ?? null;
|
|
7673
7840
|
const instance = createInstance(database, config);
|
|
7674
7841
|
const credentials = socialCredentials(config);
|
|
7675
7842
|
const mail = config.SMTP_URL && config.SMTP_FROM ? createMailSender(config.SMTP_URL, config.SMTP_FROM) : void 0;
|
|
@@ -7704,23 +7871,35 @@ function createAuth(database, config) {
|
|
|
7704
7871
|
disableIdTokenSignIn: true
|
|
7705
7872
|
};
|
|
7706
7873
|
};
|
|
7707
|
-
|
|
7874
|
+
const auth = betterAuth({
|
|
7708
7875
|
appName: "Kanvil",
|
|
7709
7876
|
baseURL: config.KANVIL_URL,
|
|
7710
7877
|
secret: config.BETTER_AUTH_SECRET,
|
|
7711
7878
|
database: (options) => {
|
|
7712
7879
|
const base = adapter(options);
|
|
7713
|
-
const update = (input) => {
|
|
7880
|
+
const update = async (input) => {
|
|
7714
7881
|
const fields = Object.keys(input.update);
|
|
7715
|
-
if (input.model === "apikey" && fields.length > 0 && fields.every((field) => field === "lastRequest" || field === "updatedAt")) return base.findOne({
|
|
7882
|
+
if (input.model === "apikey" && fields.length > 0 && fields.every((field) => field === "lastRequest" || field === "updatedAt")) return await base.findOne({
|
|
7716
7883
|
model: input.model,
|
|
7717
7884
|
where: input.where
|
|
7718
|
-
});
|
|
7885
|
+
}) ?? consumed();
|
|
7719
7886
|
return base.update(input);
|
|
7720
7887
|
};
|
|
7888
|
+
const incrementOne = async (input) => {
|
|
7889
|
+
const row = await base.incrementOne(input);
|
|
7890
|
+
const store = input.model === "apikey" ? verification.getStore() : void 0;
|
|
7891
|
+
if (!store) return row;
|
|
7892
|
+
if (row) store.row = row;
|
|
7893
|
+
else if (store.row && !await base.findOne({
|
|
7894
|
+
model: input.model,
|
|
7895
|
+
where: input.where.filter(({ field }) => field === "id")
|
|
7896
|
+
})) return store.row;
|
|
7897
|
+
return row;
|
|
7898
|
+
};
|
|
7721
7899
|
return {
|
|
7722
7900
|
...base,
|
|
7723
|
-
update
|
|
7901
|
+
update,
|
|
7902
|
+
incrementOne
|
|
7724
7903
|
};
|
|
7725
7904
|
},
|
|
7726
7905
|
trustedOrigins: config.trustedOrigins,
|
|
@@ -7871,6 +8050,14 @@ function createAuth(database, config) {
|
|
|
7871
8050
|
}] : [] })
|
|
7872
8051
|
]
|
|
7873
8052
|
});
|
|
8053
|
+
const verifyApiKey = Object.assign((input) => verification.run({}, () => auth.api.verifyApiKey(input)), auth.api.verifyApiKey);
|
|
8054
|
+
return {
|
|
8055
|
+
...auth,
|
|
8056
|
+
api: {
|
|
8057
|
+
...auth.api,
|
|
8058
|
+
verifyApiKey
|
|
8059
|
+
}
|
|
8060
|
+
};
|
|
7874
8061
|
}
|
|
7875
8062
|
//#endregion
|
|
7876
|
-
export {
|
|
8063
|
+
export { stagedUploads as $, requireSession as A, attachmentContent as B, defaultSenders as C, createAccess as D, createHttpSender as E, decodeImage as F, events as G, blobObjects as H, imageResponse as I, notificationPreferences as J, instanceSettings as K, stagedUploadCutoff as L, createTicketSearch as M, createTicketIndex as N, createWriteLimiter as O, createBlobStorage as P, pushDevices as Q, createObjectStore as R, receiveRepositoryWebhook as S, DeliveryError as T, deliveryCheckpoints as U, attachments as V, eventClock as W, profilePhotos as X, notifications as Y, projects as Z, one as _, deliverySecrets as a, matchesOIDCBinding as at, requireRow as b, ownerBlocker as c, createDomain as d, storageBackends as et, errorEnvelope as f, notify as g, json as h, createInstance as i, workspaces as it, readCalendarFeed as j, requireInstanceAdmin as k, appendEvents as l, intakeOnly as m, emailCodesAvailable as n, tickets as nt, otherActiveAdministrator as o, fail as p, members as q, signInProviders as r, workspaceMembers as rt, ownedWorkspaces as s, createAuth as t, subscriptions as tt, assertVersion as u, reachable as v, deliverBatch as w, processImports as x, recipientColumns as y, createPollingJob as z };
|