shutro-erp 0.3.1 → 0.3.3
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/dist/account_portal/.kosh/bindings.js +1 -1
- package/dist/account_portal/.kosh/schema.d.ts +29 -1
- package/dist/account_portal/index.js +3 -2
- package/dist/accounting/.kosh/bindings.js +1 -1
- package/dist/accounting/.kosh/schema.d.ts +29 -1
- package/dist/accounting/index.js +4 -0
- package/dist/assistant/index.js +1 -1
- package/dist/cli/init.js +2 -0
- package/dist/cli/launch.js +8 -5
- package/dist/cli/templates.js +4 -1
- package/dist/cli/ui.js +19 -1
- package/dist/contacts/.kosh/bindings.js +1 -1
- package/dist/contacts/.kosh/schema.d.ts +29 -1
- package/dist/integrations/inbound.d.ts +36 -0
- package/dist/integrations/inbound.js +171 -0
- package/dist/integrations/index.d.ts +18 -0
- package/dist/integrations/index.js +400 -0
- package/dist/integrations/models.d.ts +6 -0
- package/dist/integrations/models.js +119 -0
- package/dist/integrations/web/DeliveryTools.d.ts +4 -0
- package/dist/integrations/web/DeliveryTools.js +15 -0
- package/dist/integrations/web/EndpointTools.d.ts +7 -0
- package/dist/integrations/web/EndpointTools.js +25 -0
- package/dist/integrations/web/InboundTools.d.ts +4 -0
- package/dist/integrations/web/InboundTools.js +15 -0
- package/dist/integrations/web/WebhookTools.d.ts +7 -0
- package/dist/integrations/web/WebhookTools.js +25 -0
- package/dist/integrations/web/index.d.ts +16 -0
- package/dist/integrations/web/index.js +27 -0
- package/dist/integrations/web/messages.d.ts +2 -0
- package/dist/integrations/web/messages.js +53 -0
- package/dist/integrations/webhooks.d.ts +63 -0
- package/dist/integrations/webhooks.js +217 -0
- package/dist/l10n_bd/.kosh/bindings.js +1 -1
- package/dist/l10n_bd/.kosh/schema.d.ts +29 -1
- package/dist/l10n_in/.kosh/bindings.js +1 -1
- package/dist/l10n_in/.kosh/schema.d.ts +29 -4
- package/dist/l10n_in/module.d.ts +0 -62
- package/dist/l10n_in/module.js +2 -4
- package/dist/modules-sdk/index.d.ts +1 -1
- package/dist/modules-sdk/manifest.d.ts +48 -0
- package/dist/payment/.kosh/bindings.d.ts +22 -0
- package/dist/payment/.kosh/bindings.js +15 -0
- package/dist/payment/.kosh/schema.d.ts +1581 -0
- package/dist/payment/bkash.d.ts +28 -0
- package/dist/payment/bkash.js +169 -0
- package/dist/payment/index.d.ts +38 -0
- package/dist/payment/index.js +431 -0
- package/dist/payment/module.d.ts +6856 -0
- package/dist/payment/module.js +77 -0
- package/dist/payment/money.d.ts +7 -0
- package/dist/payment/money.js +37 -0
- package/dist/payment/providers.d.ts +75 -0
- package/dist/payment/providers.js +77 -0
- package/dist/payment/razorpay.d.ts +12 -0
- package/dist/payment/razorpay.js +149 -0
- package/dist/payment/settle.d.ts +11 -0
- package/dist/payment/settle.js +59 -0
- package/dist/payment/sslcommerz.d.ts +12 -0
- package/dist/payment/sslcommerz.js +185 -0
- package/dist/payment/stripe.d.ts +15 -0
- package/dist/payment/stripe.js +182 -0
- package/dist/permissions/admin.d.ts +3 -0
- package/dist/permissions/admin.js +108 -10
- package/dist/permissions/index.d.ts +1 -0
- package/dist/permissions/index.js +3 -0
- package/dist/permissions/roles.d.ts +2 -0
- package/dist/permissions/roles.js +236 -0
- package/dist/portal/.kosh/bindings.js +1 -1
- package/dist/portal/.kosh/schema.d.ts +29 -1
- package/dist/portal/routes-documents.js +5 -0
- package/dist/product/.kosh/bindings.js +1 -1
- package/dist/product/.kosh/schema.d.ts +29 -1
- package/dist/project/.kosh/bindings.js +1 -1
- package/dist/project/.kosh/schema.d.ts +29 -1
- package/dist/project_portal/.kosh/bindings.js +1 -1
- package/dist/project_portal/.kosh/schema.d.ts +29 -1
- package/dist/purchase/.kosh/bindings.js +1 -1
- package/dist/purchase/.kosh/schema.d.ts +29 -1
- package/dist/purchase_account/.kosh/bindings.js +1 -1
- package/dist/purchase_account/.kosh/schema.d.ts +29 -1
- package/dist/purchase_portal/.kosh/bindings.js +1 -1
- package/dist/purchase_portal/.kosh/schema.d.ts +29 -1
- package/dist/sale_account/.kosh/bindings.js +1 -1
- package/dist/sale_account/.kosh/schema.d.ts +29 -1
- package/dist/sale_portal/.kosh/bindings.js +1 -1
- package/dist/sale_portal/.kosh/schema.d.ts +29 -1
- package/dist/sale_project/.kosh/bindings.js +1 -1
- package/dist/sale_project/.kosh/schema.d.ts +29 -1
- package/dist/sales/.kosh/bindings.js +1 -1
- package/dist/sales/.kosh/schema.d.ts +29 -1
- package/dist/sales/index.js +2 -0
- package/dist/server/api/rest.d.ts +6 -1
- package/dist/server/api/rest.js +11 -0
- package/dist/server/auth/base.js +22 -0
- package/dist/server/auth/routes.d.ts +3 -0
- package/dist/server/auth/routes.js +29 -0
- package/dist/server/auth/service.d.ts +52 -0
- package/dist/server/auth/service.js +168 -3
- package/dist/server/identity.d.ts +2 -0
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +1 -1
- package/dist/server/server.js +11 -3
- package/dist/settings/company.d.ts +34 -0
- package/dist/settings/company.js +187 -0
- package/dist/settings/index.d.ts +10 -10
- package/dist/settings/index.js +159 -30
- package/dist/settings/people.d.ts +16 -0
- package/dist/settings/people.js +505 -0
- package/dist/settings/security.d.ts +13 -0
- package/dist/settings/security.js +158 -0
- package/dist/settings/web/AuditPage.d.ts +8 -0
- package/dist/settings/web/AuditPage.js +74 -0
- package/dist/settings/web/CompaniesPage.d.ts +8 -0
- package/dist/settings/web/CompaniesPage.js +52 -0
- package/dist/settings/web/CompanyPage.d.ts +9 -0
- package/dist/settings/web/CompanyPage.js +94 -0
- package/dist/settings/web/OverviewPage.d.ts +9 -0
- package/dist/settings/web/OverviewPage.js +66 -0
- package/dist/settings/web/PeoplePage.d.ts +9 -0
- package/dist/settings/web/PeoplePage.js +163 -0
- package/dist/settings/web/ProfilePage.d.ts +14 -0
- package/dist/settings/web/ProfilePage.js +194 -0
- package/dist/settings/web/RolesEditor.d.ts +35 -0
- package/dist/settings/web/RolesEditor.js +49 -0
- package/dist/settings/web/RolesPage.d.ts +9 -0
- package/dist/settings/web/RolesPage.js +296 -0
- package/dist/settings/web/SecurityPage.d.ts +8 -0
- package/dist/settings/web/SecurityPage.js +81 -0
- package/dist/settings/web/index.d.ts +9 -0
- package/dist/settings/web/index.js +26 -1
- package/dist/settings/web/messages.js +566 -0
- package/dist/ui/api.d.ts +4 -0
- package/dist/ui/app.d.ts +5 -0
- package/dist/ui/app.js +108 -31
- package/dist/ui/components/primitives.d.ts +9 -1
- package/dist/ui/components/primitives.js +12 -4
- package/dist/ui/context.d.ts +10 -1
- package/dist/ui/context.js +60 -2
- package/dist/ui/i18n/messages.d.ts +46 -0
- package/dist/ui/i18n/messages.js +92 -0
- package/dist/ui/index.d.ts +2 -1
- package/dist/ui/index.js +2 -1
- package/dist/ui/lib/accent.d.ts +13 -0
- package/dist/ui/lib/accent.js +36 -0
- package/dist/ui/shell/Activate.d.ts +6 -0
- package/dist/ui/shell/Activate.js +39 -0
- package/dist/ui/shell/CommandPalette.d.ts +7 -1
- package/dist/ui/shell/CommandPalette.js +16 -2
- package/dist/ui/shell/Sidebar.d.ts +27 -0
- package/dist/ui/shell/Sidebar.js +84 -0
- package/dist/ui/shell/TwoFactorSetup.d.ts +5 -0
- package/dist/ui/shell/TwoFactorSetup.js +32 -0
- package/dist/ui/shell/shortcuts.d.ts +3 -0
- package/dist/ui/shell/shortcuts.js +13 -4
- package/dist/ui/styles.css +70 -28
- package/dist/ui/views/DateField.d.ts +10 -0
- package/dist/ui/views/DateField.js +129 -0
- package/dist/ui/views/FormView.js +161 -21
- package/dist/ui/views/ListView.d.ts +2 -0
- package/dist/ui/views/ListView.js +8 -3
- package/dist/ui/views/StatusChip.d.ts +10 -0
- package/dist/ui/views/StatusChip.js +22 -0
- package/dist/ui/views/widgets.d.ts +3 -0
- package/dist/ui/views/widgets.js +26 -8
- package/dist/views/types.d.ts +7 -0
- package/dist/views/validate.js +4 -0
- package/package.json +14 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash:
|
|
1
|
+
// GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash: 668898d9db81475e
|
|
2
2
|
// Bindings of module `account_portal`: import the definition API from here to get types scoped to what it can see.
|
|
3
3
|
export { defineModel, defineModule, extendModel, f, replace } from '../../server/orm.js';
|
|
4
4
|
import { definitions } from '../../server/orm.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash:
|
|
1
|
+
// GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash: 668898d9db81475e
|
|
2
2
|
// Schema view of module `account_portal` (RFC 0002 §7.7): the models it owns or depends on, with the extensions it can see.
|
|
3
3
|
import type { F } from '../../server/orm.js';
|
|
4
4
|
|
|
@@ -24,6 +24,7 @@ export interface SchemaView {
|
|
|
24
24
|
readonly 'audit.event': AuditEvent;
|
|
25
25
|
readonly 'auth.api_key': AuthApiKey;
|
|
26
26
|
readonly 'auth.credential': AuthCredential;
|
|
27
|
+
readonly 'auth.policy': AuthPolicy;
|
|
27
28
|
readonly 'auth.session': AuthSession;
|
|
28
29
|
readonly 'auth.user_grant': AuthUserGrant;
|
|
29
30
|
readonly 'auth.user_group': AuthUserGroup;
|
|
@@ -634,6 +635,26 @@ export interface AuthCredential {
|
|
|
634
635
|
export interface AuthCredentialMethods extends F.Methods<typeof import('../../server/index.js').baseShutro, 'auth.credential'> {
|
|
635
636
|
}
|
|
636
637
|
|
|
638
|
+
/** `auth.policy` (owner: base). */
|
|
639
|
+
export interface AuthPolicy {
|
|
640
|
+
readonly name: 'auth.policy';
|
|
641
|
+
readonly fields: {
|
|
642
|
+
readonly id: F.Id<'auth.policy'>;
|
|
643
|
+
readonly createdAt: F.Datetime<{ readonly required: true; readonly readonly: true }>;
|
|
644
|
+
readonly updatedAt: F.Datetime<{ readonly required: true; readonly readonly: true }>;
|
|
645
|
+
readonly createdBy: F.Many2one<'res.users', { readonly readonly: true }>;
|
|
646
|
+
readonly updatedBy: F.Many2one<'res.users', { readonly readonly: true }>;
|
|
647
|
+
readonly displayName: F.Char<{ readonly required: true; readonly readonly: true; readonly compute: true }>;
|
|
648
|
+
readonly twoFactor: F.Selection<'administrators' | 'everyone', { readonly required: true; readonly default: true }>;
|
|
649
|
+
readonly sessionDays: F.Integer;
|
|
650
|
+
};
|
|
651
|
+
readonly methods: AuthPolicyMethods;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/** Methods of `auth.policy`: declared ones and transitions. */
|
|
655
|
+
export interface AuthPolicyMethods extends F.Methods<typeof import('../../server/index.js').baseShutro, 'auth.policy'> {
|
|
656
|
+
}
|
|
657
|
+
|
|
637
658
|
/** `auth.session` (owner: base). */
|
|
638
659
|
export interface AuthSession {
|
|
639
660
|
readonly name: 'auth.session';
|
|
@@ -1200,6 +1221,13 @@ export interface ResCompany {
|
|
|
1200
1221
|
readonly name: F.Char<{ readonly required: true }>;
|
|
1201
1222
|
readonly currency: F.Many2one<'res.currency', { readonly required: true }>;
|
|
1202
1223
|
readonly numberGrouping: F.Selection<'international' | 'lakh', { readonly required: true; readonly default: true }>;
|
|
1224
|
+
readonly accentColor: F.Char;
|
|
1225
|
+
readonly street: F.Char;
|
|
1226
|
+
readonly city: F.Char;
|
|
1227
|
+
readonly zip: F.Char;
|
|
1228
|
+
readonly email: F.Char;
|
|
1229
|
+
readonly phone: F.Char;
|
|
1230
|
+
readonly website: F.Char;
|
|
1203
1231
|
readonly fiscalYearStart: F.Integer<{ readonly required: true; readonly default: true }>; // accounting
|
|
1204
1232
|
readonly chartTemplate: F.Char; // accounting
|
|
1205
1233
|
readonly country: F.Char; // accounting
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* @packageDocumentation
|
|
3
3
|
* `shutro-erp/account_portal`: Accounting in the portal (RFC 0015 §5.4). Proposed whenever Accounting and the portal
|
|
4
4
|
* are installed. Customers see their posted invoices and credit notes, what is still due, print them and write on
|
|
5
|
-
* them; vendors see the bills recorded for them.
|
|
6
|
-
*
|
|
5
|
+
* them; vendors see the bills recorded for them. Open invoices are payable online when the `payment` module and
|
|
6
|
+
* a provider are installed (RFC 0017 §5.5); otherwise by the payment details on the invoice.
|
|
7
7
|
*/
|
|
8
8
|
import { accounting } from '../accounting/index.js';
|
|
9
9
|
import { defineModule } from '../modules-sdk/index.js';
|
|
@@ -55,6 +55,7 @@ export const accountPortal = defineModule({
|
|
|
55
55
|
thread: true,
|
|
56
56
|
attention: { kind: 'out_invoice', paymentState: { in: ['not_paid', 'partial'] } },
|
|
57
57
|
announce: { state: 'posted' },
|
|
58
|
+
payment: { when: { kind: 'out_invoice', paymentState: { in: ['not_paid', 'partial'] } } },
|
|
58
59
|
},
|
|
59
60
|
{
|
|
60
61
|
key: 'bills',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash:
|
|
1
|
+
// GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash: e7d2cbddec9ade69
|
|
2
2
|
// Bindings of module `accounting`: import the definition API from here to get types scoped to what it can see.
|
|
3
3
|
export { defineModel, defineModule, extendModel, f, replace } from '../../server/orm.js';
|
|
4
4
|
import { definitions } from '../../server/orm.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash:
|
|
1
|
+
// GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash: e7d2cbddec9ade69
|
|
2
2
|
// Schema view of module `accounting` (RFC 0002 §7.7): the models it owns or depends on, with the extensions it can see.
|
|
3
3
|
import type { F } from '../../server/orm.js';
|
|
4
4
|
|
|
@@ -24,6 +24,7 @@ export interface SchemaView {
|
|
|
24
24
|
readonly 'audit.event': AuditEvent;
|
|
25
25
|
readonly 'auth.api_key': AuthApiKey;
|
|
26
26
|
readonly 'auth.credential': AuthCredential;
|
|
27
|
+
readonly 'auth.policy': AuthPolicy;
|
|
27
28
|
readonly 'auth.session': AuthSession;
|
|
28
29
|
readonly 'auth.user_grant': AuthUserGrant;
|
|
29
30
|
readonly 'auth.user_group': AuthUserGroup;
|
|
@@ -631,6 +632,26 @@ export interface AuthCredential {
|
|
|
631
632
|
export interface AuthCredentialMethods extends F.Methods<typeof import('../../server/index.js').baseShutro, 'auth.credential'> {
|
|
632
633
|
}
|
|
633
634
|
|
|
635
|
+
/** `auth.policy` (owner: base). */
|
|
636
|
+
export interface AuthPolicy {
|
|
637
|
+
readonly name: 'auth.policy';
|
|
638
|
+
readonly fields: {
|
|
639
|
+
readonly id: F.Id<'auth.policy'>;
|
|
640
|
+
readonly createdAt: F.Datetime<{ readonly required: true; readonly readonly: true }>;
|
|
641
|
+
readonly updatedAt: F.Datetime<{ readonly required: true; readonly readonly: true }>;
|
|
642
|
+
readonly createdBy: F.Many2one<'res.users', { readonly readonly: true }>;
|
|
643
|
+
readonly updatedBy: F.Many2one<'res.users', { readonly readonly: true }>;
|
|
644
|
+
readonly displayName: F.Char<{ readonly required: true; readonly readonly: true; readonly compute: true }>;
|
|
645
|
+
readonly twoFactor: F.Selection<'administrators' | 'everyone', { readonly required: true; readonly default: true }>;
|
|
646
|
+
readonly sessionDays: F.Integer;
|
|
647
|
+
};
|
|
648
|
+
readonly methods: AuthPolicyMethods;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/** Methods of `auth.policy`: declared ones and transitions. */
|
|
652
|
+
export interface AuthPolicyMethods extends F.Methods<typeof import('../../server/index.js').baseShutro, 'auth.policy'> {
|
|
653
|
+
}
|
|
654
|
+
|
|
634
655
|
/** `auth.session` (owner: base). */
|
|
635
656
|
export interface AuthSession {
|
|
636
657
|
readonly name: 'auth.session';
|
|
@@ -1125,6 +1146,13 @@ export interface ResCompany {
|
|
|
1125
1146
|
readonly name: F.Char<{ readonly required: true }>;
|
|
1126
1147
|
readonly currency: F.Many2one<'res.currency', { readonly required: true }>;
|
|
1127
1148
|
readonly numberGrouping: F.Selection<'international' | 'lakh', { readonly required: true; readonly default: true }>;
|
|
1149
|
+
readonly accentColor: F.Char;
|
|
1150
|
+
readonly street: F.Char;
|
|
1151
|
+
readonly city: F.Char;
|
|
1152
|
+
readonly zip: F.Char;
|
|
1153
|
+
readonly email: F.Char;
|
|
1154
|
+
readonly phone: F.Char;
|
|
1155
|
+
readonly website: F.Char;
|
|
1128
1156
|
readonly fiscalYearStart: F.Integer<{ readonly required: true; readonly default: true }>; // accounting
|
|
1129
1157
|
readonly chartTemplate: F.Char; // accounting
|
|
1130
1158
|
readonly country: F.Char; // accounting
|
package/dist/accounting/index.js
CHANGED
|
@@ -404,6 +404,10 @@ export const accounting = defineModule({
|
|
|
404
404
|
sequence: 5,
|
|
405
405
|
},
|
|
406
406
|
],
|
|
407
|
+
// What webhooks may subscribe to besides record changes (RFC 0017 §5.3).
|
|
408
|
+
events: [
|
|
409
|
+
{ topic: 'account.move.posted', model: 'account.move', label: 'Entry, invoice or bill posted' },
|
|
410
|
+
],
|
|
407
411
|
approvals: [
|
|
408
412
|
{
|
|
409
413
|
name: 'Vendor bill approvals',
|
package/dist/assistant/index.js
CHANGED
package/dist/cli/init.js
CHANGED
|
@@ -503,6 +503,7 @@ async function modulesFor(apps) {
|
|
|
503
503
|
(await import('../notify/index.js')).notify,
|
|
504
504
|
(await import('../mail/index.js')).mail,
|
|
505
505
|
(await import('../automation/index.js')).automation,
|
|
506
|
+
(await import('../integrations/index.js')).integrations,
|
|
506
507
|
(await import('../studio/index.js')).studio,
|
|
507
508
|
(await import('../import/index.js')).dataImport,
|
|
508
509
|
];
|
|
@@ -940,6 +941,7 @@ export async function runInit(argv, io) {
|
|
|
940
941
|
'notify',
|
|
941
942
|
'mail',
|
|
942
943
|
'automation',
|
|
944
|
+
'integrations',
|
|
943
945
|
'studio',
|
|
944
946
|
'import',
|
|
945
947
|
...answers.apps,
|
package/dist/cli/launch.js
CHANGED
|
@@ -24,14 +24,17 @@ export function browserCommand(url, platform = process.platform) {
|
|
|
24
24
|
const windows = process.platform === 'win32';
|
|
25
25
|
/** A child process running `pm args` in `cwd`, its output kept (the last 200 lines). */
|
|
26
26
|
function start(pm, args, cwd) {
|
|
27
|
-
const
|
|
27
|
+
const options = {
|
|
28
28
|
cwd,
|
|
29
29
|
env: { ...process.env, FORCE_COLOR: '0' },
|
|
30
30
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
detached: !windows, // elsewhere a process group, stopped as a whole
|
|
32
|
+
};
|
|
33
|
+
// npm, pnpm… are .cmd scripts on Windows: run through the shell as one command line (the arguments are this
|
|
34
|
+
// module's own words, never input), which Node prefers to separate arguments with `shell: true`.
|
|
35
|
+
const child = windows
|
|
36
|
+
? spawn([pm, ...args].join(' '), { ...options, shell: true })
|
|
37
|
+
: spawn(pm, args, options);
|
|
35
38
|
const lines = [];
|
|
36
39
|
const keep = (chunk) => {
|
|
37
40
|
lines.push(...chunk
|
package/dist/cli/templates.js
CHANGED
|
@@ -88,6 +88,7 @@ const REACT = {
|
|
|
88
88
|
react: '^19.1.0',
|
|
89
89
|
'react-dom': '^19.1.0',
|
|
90
90
|
'@tanstack/react-query': '^5.90.0',
|
|
91
|
+
'@tanstack/react-router': '^1.170.0',
|
|
91
92
|
i18next: '^25.0.0 || ^26.0.0',
|
|
92
93
|
'react-i18next': '^16.0.0 || ^17.0.0',
|
|
93
94
|
};
|
|
@@ -136,6 +137,7 @@ export function modulesTs(_options) {
|
|
|
136
137
|
*/
|
|
137
138
|
import { base } from 'shutro-erp';
|
|
138
139
|
import { automation } from 'shutro-erp/automation';
|
|
140
|
+
import { integrations } from 'shutro-erp/integrations';
|
|
139
141
|
import { mail } from 'shutro-erp/mail';
|
|
140
142
|
import { notify } from 'shutro-erp/notify';
|
|
141
143
|
import { realtime } from 'shutro-erp/realtime';
|
|
@@ -145,7 +147,7 @@ import { dataImport } from 'shutro-erp/import';
|
|
|
145
147
|
import { studio } from 'shutro-erp/studio';
|
|
146
148
|
${imports.join('\n')}
|
|
147
149
|
|
|
148
|
-
export const modules = [base, settings, realtime, notify, mail, automation, studio, dataImport, assistant${apps.map((a) => `, ${APPS[a].server}`).join('')}];
|
|
150
|
+
export const modules = [base, settings, realtime, notify, mail, automation, integrations, studio, dataImport, assistant${apps.map((a) => `, ${APPS[a].server}`).join('')}];
|
|
149
151
|
`;
|
|
150
152
|
}
|
|
151
153
|
export function serverTs() {
|
|
@@ -216,6 +218,7 @@ export function mainTsx(_options) {
|
|
|
216
218
|
{ name: 'notify', fn: 'registerNotifyWeb' },
|
|
217
219
|
{ name: 'mail', fn: 'registerMailWeb' },
|
|
218
220
|
{ name: 'automation', fn: 'registerAutomationWeb' },
|
|
221
|
+
{ name: 'integrations', fn: 'registerIntegrationsWeb' },
|
|
219
222
|
{ name: 'studio', fn: 'registerStudioWeb' },
|
|
220
223
|
{ name: 'assistant', fn: 'registerAssistantWeb' },
|
|
221
224
|
{ name: 'import', fn: 'registerImportWeb' },
|
package/dist/cli/ui.js
CHANGED
|
@@ -251,7 +251,25 @@ export function createUi(out, terminal = PLAIN_TERMINAL, options = {}) {
|
|
|
251
251
|
},
|
|
252
252
|
card(title, rows, tone = 'green') {
|
|
253
253
|
const labelWidth = Math.max(0, ...rows.map(([l]) => width(l)));
|
|
254
|
-
|
|
254
|
+
// Long values wrap at the terminal's width, aligned after their label.
|
|
255
|
+
const room = Math.max(20, Math.min(terminal.columns, 100) - labelWidth - 10);
|
|
256
|
+
const wrap = (value) => {
|
|
257
|
+
const lines = [];
|
|
258
|
+
let current = '';
|
|
259
|
+
for (const word of value.split(' ')) {
|
|
260
|
+
const next = current === '' ? word : `${current} ${word}`;
|
|
261
|
+
if (current !== '' && width(next) > room) {
|
|
262
|
+
lines.push(current);
|
|
263
|
+
current = word;
|
|
264
|
+
}
|
|
265
|
+
else
|
|
266
|
+
current = next;
|
|
267
|
+
}
|
|
268
|
+
return [...lines, current];
|
|
269
|
+
};
|
|
270
|
+
const body = rows.flatMap(([l, v]) => wrap(v).map((part, i) => i === 0
|
|
271
|
+
? `${s('gray', l.padEnd(labelWidth))} ${part}`
|
|
272
|
+
: `${' '.repeat(labelWidth)} ${part}`));
|
|
255
273
|
const inner = Math.max(width(title), ...body.map(width)) + 2;
|
|
256
274
|
const [tl, tr, bl, br, h, v] = terminal.unicode
|
|
257
275
|
? ['╭', '╮', '╰', '╯', '─', '│']
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash:
|
|
1
|
+
// GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash: 31cf57953410c779
|
|
2
2
|
// Bindings of module `contacts`: import the definition API from here to get types scoped to what it can see.
|
|
3
3
|
export { defineModel, defineModule, extendModel, f, replace } from '../../server/orm.js';
|
|
4
4
|
import { definitions } from '../../server/orm.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash:
|
|
1
|
+
// GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash: 31cf57953410c779
|
|
2
2
|
// Schema view of module `contacts` (RFC 0002 §7.7): the models it owns or depends on, with the extensions it can see.
|
|
3
3
|
import type { F } from '../../server/orm.js';
|
|
4
4
|
|
|
@@ -7,6 +7,7 @@ export interface SchemaView {
|
|
|
7
7
|
readonly 'audit.event': AuditEvent;
|
|
8
8
|
readonly 'auth.api_key': AuthApiKey;
|
|
9
9
|
readonly 'auth.credential': AuthCredential;
|
|
10
|
+
readonly 'auth.policy': AuthPolicy;
|
|
10
11
|
readonly 'auth.session': AuthSession;
|
|
11
12
|
readonly 'auth.user_grant': AuthUserGrant;
|
|
12
13
|
readonly 'auth.user_group': AuthUserGroup;
|
|
@@ -94,6 +95,26 @@ export interface AuthCredential {
|
|
|
94
95
|
export interface AuthCredentialMethods extends F.Methods<typeof import('../../server/index.js').baseShutro, 'auth.credential'> {
|
|
95
96
|
}
|
|
96
97
|
|
|
98
|
+
/** `auth.policy` (owner: base). */
|
|
99
|
+
export interface AuthPolicy {
|
|
100
|
+
readonly name: 'auth.policy';
|
|
101
|
+
readonly fields: {
|
|
102
|
+
readonly id: F.Id<'auth.policy'>;
|
|
103
|
+
readonly createdAt: F.Datetime<{ readonly required: true; readonly readonly: true }>;
|
|
104
|
+
readonly updatedAt: F.Datetime<{ readonly required: true; readonly readonly: true }>;
|
|
105
|
+
readonly createdBy: F.Many2one<'res.users', { readonly readonly: true }>;
|
|
106
|
+
readonly updatedBy: F.Many2one<'res.users', { readonly readonly: true }>;
|
|
107
|
+
readonly displayName: F.Char<{ readonly required: true; readonly readonly: true; readonly compute: true }>;
|
|
108
|
+
readonly twoFactor: F.Selection<'administrators' | 'everyone', { readonly required: true; readonly default: true }>;
|
|
109
|
+
readonly sessionDays: F.Integer;
|
|
110
|
+
};
|
|
111
|
+
readonly methods: AuthPolicyMethods;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Methods of `auth.policy`: declared ones and transitions. */
|
|
115
|
+
export interface AuthPolicyMethods extends F.Methods<typeof import('../../server/index.js').baseShutro, 'auth.policy'> {
|
|
116
|
+
}
|
|
117
|
+
|
|
97
118
|
/** `auth.session` (owner: base). */
|
|
98
119
|
export interface AuthSession {
|
|
99
120
|
readonly name: 'auth.session';
|
|
@@ -203,6 +224,13 @@ export interface ResCompany {
|
|
|
203
224
|
readonly name: F.Char<{ readonly required: true }>;
|
|
204
225
|
readonly currency: F.Many2one<'res.currency', { readonly required: true }>;
|
|
205
226
|
readonly numberGrouping: F.Selection<'international' | 'lakh', { readonly required: true; readonly default: true }>;
|
|
227
|
+
readonly accentColor: F.Char;
|
|
228
|
+
readonly street: F.Char;
|
|
229
|
+
readonly city: F.Char;
|
|
230
|
+
readonly zip: F.Char;
|
|
231
|
+
readonly email: F.Char;
|
|
232
|
+
readonly phone: F.Char;
|
|
233
|
+
readonly website: F.Char;
|
|
206
234
|
};
|
|
207
235
|
readonly methods: ResCompanyMethods;
|
|
208
236
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { InboundCall, InboundWebhookSpec } from '../modules-sdk/index.js';
|
|
2
|
+
import type { KoshRegistry } from '../server/index.js';
|
|
3
|
+
import type { Shutro } from '../server/orm.js';
|
|
4
|
+
export declare const INBOUND_JOB = "webhook.inbound";
|
|
5
|
+
/** The declared inbound webhooks of the loaded modules, by `<module>.<key>`. */
|
|
6
|
+
export declare function inboundHooks(registry: KoshRegistry): Map<string, InboundWebhookSpec & {
|
|
7
|
+
readonly module: string;
|
|
8
|
+
}>;
|
|
9
|
+
/**
|
|
10
|
+
* A `verify` for providers that sign with an HMAC of the raw body: the signature in `header` (hex or base64,
|
|
11
|
+
* optionally after a prefix such as `sha256=`), compared in constant time; the event id from `eventId`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function hmacVerifier(options: {
|
|
14
|
+
readonly header: string;
|
|
15
|
+
readonly algorithm?: 'sha256' | 'sha512' | 'sha1';
|
|
16
|
+
readonly encoding?: 'hex' | 'base64';
|
|
17
|
+
readonly prefix?: string;
|
|
18
|
+
readonly eventId: (call: InboundCall) => string | null | undefined;
|
|
19
|
+
}): (call: InboundCall, secret: string) => string | null;
|
|
20
|
+
export interface ReceiveResult {
|
|
21
|
+
readonly status: 202 | 200 | 401 | 404;
|
|
22
|
+
readonly body: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
/** Receives a call on the endpoint of `token`: checked, stored, queued once per event id. */
|
|
25
|
+
export declare function receive(context: {
|
|
26
|
+
readonly shutro: Shutro;
|
|
27
|
+
readonly registry: KoshRegistry;
|
|
28
|
+
}, token: string, call: InboundCall): Promise<ReceiveResult>;
|
|
29
|
+
/**
|
|
30
|
+
* Handles a stored call with its module's `handle`, as the system in the endpoint's company; records the outcome
|
|
31
|
+
* in its own transaction. Throws to be retried while attempts remain.
|
|
32
|
+
*/
|
|
33
|
+
export declare function handleInbound(context: {
|
|
34
|
+
readonly shutro: Shutro;
|
|
35
|
+
readonly registry: KoshRegistry;
|
|
36
|
+
}, callId: number, company: number, last: boolean): Promise<void>;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inbound webhooks (RFC 0017 §5.4). Modules declare endpoints (`inboundWebhooks` in their manifest); a company
|
|
3
|
+
* switches one on (`webhook.endpoint`: a URL token and a secret). A call to `POST /api/v1/hooks/<token>` is checked
|
|
4
|
+
* by the module's `verify` against the raw body, stored (refused calls too, for the log), and — once per event id —
|
|
5
|
+
* handled by the module in a job, as the system in the endpoint's company, retried on failure. A repeated event id
|
|
6
|
+
* is answered 200 and not handled again; a call can be handled again from the log (replay).
|
|
7
|
+
*/
|
|
8
|
+
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
9
|
+
import { ref } from './webhooks.js';
|
|
10
|
+
export const INBOUND_JOB = 'webhook.inbound';
|
|
11
|
+
/** The declared inbound webhooks of the loaded modules, by `<module>.<key>`. */
|
|
12
|
+
export function inboundHooks(registry) {
|
|
13
|
+
const out = new Map();
|
|
14
|
+
for (const m of registry.loaded.modules)
|
|
15
|
+
for (const hook of m.inboundWebhooks ?? [])
|
|
16
|
+
out.set(`${m.name}.${hook.key}`, { ...hook, module: m.name });
|
|
17
|
+
return out;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A `verify` for providers that sign with an HMAC of the raw body: the signature in `header` (hex or base64,
|
|
21
|
+
* optionally after a prefix such as `sha256=`), compared in constant time; the event id from `eventId`.
|
|
22
|
+
*/
|
|
23
|
+
export function hmacVerifier(options) {
|
|
24
|
+
return (call, secret) => {
|
|
25
|
+
let given = call.headers[options.header.toLowerCase()] ?? '';
|
|
26
|
+
if (options.prefix !== undefined) {
|
|
27
|
+
if (!given.startsWith(options.prefix))
|
|
28
|
+
return null;
|
|
29
|
+
given = given.slice(options.prefix.length);
|
|
30
|
+
}
|
|
31
|
+
const expected = createHmac(options.algorithm ?? 'sha256', secret)
|
|
32
|
+
.update(call.body)
|
|
33
|
+
.digest(options.encoding ?? 'hex');
|
|
34
|
+
const a = Buffer.from(given);
|
|
35
|
+
const b = Buffer.from(expected);
|
|
36
|
+
if (a.length !== b.length || !timingSafeEqual(a, b))
|
|
37
|
+
return null;
|
|
38
|
+
const id = options.eventId(call);
|
|
39
|
+
return id === null || id === undefined || id === '' ? null : String(id);
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** Headers worth keeping in the log (no cookies, no credentials). */
|
|
43
|
+
const KEPT = /^(content-type|user-agent|x-[a-z0-9-]+)$/;
|
|
44
|
+
function keptHeaders(headers) {
|
|
45
|
+
return Object.fromEntries(Object.entries(headers).filter((e) => e[1] !== undefined && KEPT.test(e[0]) && !/authorization|api-key|cookie/.test(e[0])));
|
|
46
|
+
}
|
|
47
|
+
/** Receives a call on the endpoint of `token`: checked, stored, queued once per event id. */
|
|
48
|
+
export async function receive(context, token, call) {
|
|
49
|
+
const hooks = inboundHooks(context.registry);
|
|
50
|
+
// Which endpoint (and company) the token names: looked up across companies (the token is unique).
|
|
51
|
+
const found = await context.shutro.bootstrap({ reason: 'inbound webhook: endpoint' }, (env) => env.asSuperuser({ reason: 'inbound webhook: which endpoint' }, async (su) => {
|
|
52
|
+
const set = await ref(su.withFlags({ checkCompany: false }), 'webhook.endpoint').search({
|
|
53
|
+
where: { token, active: true },
|
|
54
|
+
limit: 1,
|
|
55
|
+
});
|
|
56
|
+
if (set.length === 0)
|
|
57
|
+
return null;
|
|
58
|
+
const [row] = (await set.load(['hook', 'secret', 'company'])).read([
|
|
59
|
+
'id',
|
|
60
|
+
'hook',
|
|
61
|
+
'secret',
|
|
62
|
+
'company',
|
|
63
|
+
]);
|
|
64
|
+
return row ?? null;
|
|
65
|
+
}));
|
|
66
|
+
const hook = found === null ? undefined : hooks.get(String(found['hook']));
|
|
67
|
+
if (found === null || hook === undefined)
|
|
68
|
+
return { status: 404, body: { title: 'Not found', detail: 'No such webhook endpoint.' } };
|
|
69
|
+
const company = found['company'];
|
|
70
|
+
let eventId = null;
|
|
71
|
+
try {
|
|
72
|
+
eventId = await hook.verify(call, String(found['secret']));
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
eventId = null;
|
|
76
|
+
}
|
|
77
|
+
return context.shutro.bootstrap({ reason: `inbound webhook ${hook.module}.${hook.key}`, company }, async (env) => {
|
|
78
|
+
const calls = ref(env, 'webhook.inbound');
|
|
79
|
+
const base = {
|
|
80
|
+
endpoint: found['id'],
|
|
81
|
+
headers: keptHeaders(call.headers),
|
|
82
|
+
body: call.body.slice(0, 256 * 1024),
|
|
83
|
+
company,
|
|
84
|
+
};
|
|
85
|
+
if (eventId === null) {
|
|
86
|
+
await calls.create([{ ...base, verified: false, state: 'refused' }]);
|
|
87
|
+
return {
|
|
88
|
+
status: 401,
|
|
89
|
+
body: { title: 'Not verified', detail: 'The call’s signature does not match.' },
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const seen = await calls.search({
|
|
93
|
+
where: { endpoint: found['id'], eventId, verified: true },
|
|
94
|
+
limit: 1,
|
|
95
|
+
});
|
|
96
|
+
if (seen.length > 0)
|
|
97
|
+
return { status: 200, body: { duplicate: true, eventId } };
|
|
98
|
+
const created = await calls.create([{ ...base, eventId, verified: true, state: 'received' }]);
|
|
99
|
+
const id = created.ids[0];
|
|
100
|
+
await env.enqueue(INBOUND_JOB, { call: id, company }, { attempts: 6 });
|
|
101
|
+
return { status: 202, body: { received: true, eventId } };
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Handles a stored call with its module's `handle`, as the system in the endpoint's company; records the outcome
|
|
106
|
+
* in its own transaction. Throws to be retried while attempts remain.
|
|
107
|
+
*/
|
|
108
|
+
export async function handleInbound(context, callId, company, last) {
|
|
109
|
+
const hooks = inboundHooks(context.registry);
|
|
110
|
+
const system = (reason, fn) => context.shutro.bootstrap({ reason, company }, fn);
|
|
111
|
+
const found = await system('inbound webhook: the call', async (env) => {
|
|
112
|
+
const set = ref(env, 'webhook.inbound').browse([callId]);
|
|
113
|
+
const [c] = (await set.load(['endpoint', 'body', 'headers', 'state', 'attempts'])).read([
|
|
114
|
+
'endpoint',
|
|
115
|
+
'body',
|
|
116
|
+
'headers',
|
|
117
|
+
'state',
|
|
118
|
+
'attempts',
|
|
119
|
+
]);
|
|
120
|
+
if (c === undefined)
|
|
121
|
+
return null;
|
|
122
|
+
const [e] = (await ref(env, 'webhook.endpoint')
|
|
123
|
+
.browse([c['endpoint']])
|
|
124
|
+
.load(['hook'])).read(['hook']);
|
|
125
|
+
return { row: c, hook: String(e?.['hook'] ?? '') };
|
|
126
|
+
});
|
|
127
|
+
if (found === null || found.row['state'] === 'done')
|
|
128
|
+
return;
|
|
129
|
+
const { row, hook: name } = found;
|
|
130
|
+
const hook = hooks.get(name);
|
|
131
|
+
const attempts = Number(row['attempts'] ?? 0) + 1;
|
|
132
|
+
const write = (values) => system('inbound webhook: the outcome', (env) => ref(env, 'webhook.inbound').browse([callId]).write(values));
|
|
133
|
+
if (hook === undefined) {
|
|
134
|
+
await write({
|
|
135
|
+
state: 'failed',
|
|
136
|
+
attempts,
|
|
137
|
+
error: `The endpoint ${name} is no longer installed.`,
|
|
138
|
+
});
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const body = String(row['body']);
|
|
142
|
+
let json = null;
|
|
143
|
+
try {
|
|
144
|
+
json = JSON.parse(body);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
json = null;
|
|
148
|
+
}
|
|
149
|
+
const call = {
|
|
150
|
+
headers: (row['headers'] ?? {}),
|
|
151
|
+
query: {},
|
|
152
|
+
body,
|
|
153
|
+
json,
|
|
154
|
+
};
|
|
155
|
+
try {
|
|
156
|
+
const result = await system(`inbound webhook ${name}`, (env) => hook.handle(env, call));
|
|
157
|
+
await write({
|
|
158
|
+
state: 'done',
|
|
159
|
+
attempts,
|
|
160
|
+
result: typeof result === 'string' ? result : null,
|
|
161
|
+
error: null,
|
|
162
|
+
handledAt: new Date(),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
167
|
+
await write({ state: last ? 'failed' : 'received', attempts, error: message });
|
|
168
|
+
if (!last)
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type KoshModule } from '../modules-sdk/index.js';
|
|
2
|
+
import type { Worker } from '../server/orm.js';
|
|
3
|
+
import { type IntegrationsContext } from './webhooks.js';
|
|
4
|
+
export { handleInbound, hmacVerifier, INBOUND_JOB, inboundHooks, type ReceiveResult, receive, } from './inbound.js';
|
|
5
|
+
export { integrationsShutro, newSecret, newToken } from './models.js';
|
|
6
|
+
export { CHANGE_EVENTS, DELIVER_JOB, deliver, fanOut, type IntegrationsContext, PING_EVENT, signature, urlProblem, type WebhookEvent, webhookEvents, webhookHosts, } from './webhooks.js';
|
|
7
|
+
/** The workers: events to deliveries (change events and the modules' events), and the delivery jobs. */
|
|
8
|
+
export declare function webhookWorkers(integrations: IntegrationsContext): Worker[];
|
|
9
|
+
export interface IntegrationsOptions {
|
|
10
|
+
/** The HTTP client deliveries use (tests). */
|
|
11
|
+
readonly fetch?: typeof fetch;
|
|
12
|
+
/** Start the workers (default: unless `SHUTRO_ERP_SCHEDULES=off`). */
|
|
13
|
+
readonly workers?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/** The Integrations app with explicit options (tests, installations wiring their own). */
|
|
16
|
+
export declare function integrationsModule(options?: IntegrationsOptions): KoshModule;
|
|
17
|
+
/** The Integrations app (providers and workers from the environment). */
|
|
18
|
+
export declare const integrations: KoshModule;
|