@oneuptime/common 7.0.5065 → 7.0.5068
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/Models/DatabaseModels/Project.ts +28 -1
- package/Server/Infrastructure/Postgres/SchemaMigrations/1756293325324-MigrationName.ts +11 -10
- package/Server/Infrastructure/Postgres/SchemaMigrations/1756296282627-MigrationName.ts +23 -12
- package/Server/Infrastructure/Postgres/SchemaMigrations/1756300358095-MigrationName.ts +17 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +3 -1
- package/Server/Services/BillingService.ts +30 -8
- package/Server/Services/ProjectService.ts +11 -4
- package/Server/Services/StatusPageService.ts +19 -3
- package/Server/Types/Markdown.ts +17 -17
- package/UI/Components/Detail/Detail.tsx +2 -3
- package/UI/Utils/Countries.ts +251 -248
- package/build/dist/Models/DatabaseModels/Project.js +27 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1756293325324-MigrationName.js +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1756293325324-MigrationName.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1756296282627-MigrationName.js +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1756296282627-MigrationName.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1756300358095-MigrationName.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1756300358095-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +3 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/BillingService.js +27 -10
- package/build/dist/Server/Services/BillingService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +8 -3
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +11 -2
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +16 -16
- package/build/dist/UI/Components/Detail/Detail.js +2 -1
- package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
- package/build/dist/UI/Utils/Countries.js +247 -247
- package/build/dist/UI/Utils/Countries.js.map +1 -1
- package/package.json +1 -1
|
@@ -289,7 +289,8 @@ export default class Project extends TenantModel {
|
|
|
289
289
|
@TableColumn({
|
|
290
290
|
type: TableColumnType.ShortText,
|
|
291
291
|
title: "Business Country (ISO Alpha-2)",
|
|
292
|
-
description:
|
|
292
|
+
description:
|
|
293
|
+
"Two-letter ISO country code for billing address (e.g., US, GB, DE).",
|
|
293
294
|
})
|
|
294
295
|
@Column({
|
|
295
296
|
type: ColumnType.ShortText,
|
|
@@ -299,6 +300,32 @@ export default class Project extends TenantModel {
|
|
|
299
300
|
})
|
|
300
301
|
public businessDetailsCountry?: string = undefined;
|
|
301
302
|
|
|
303
|
+
@ColumnAccessControl({
|
|
304
|
+
create: [Permission.ProjectOwner, Permission.ManageProjectBilling],
|
|
305
|
+
read: [
|
|
306
|
+
Permission.ProjectOwner,
|
|
307
|
+
Permission.ProjectAdmin,
|
|
308
|
+
Permission.ProjectMember,
|
|
309
|
+
Permission.ReadProject,
|
|
310
|
+
Permission.UnAuthorizedSsoUser,
|
|
311
|
+
Permission.ProjectUser,
|
|
312
|
+
],
|
|
313
|
+
update: [Permission.ProjectOwner, Permission.ManageProjectBilling],
|
|
314
|
+
})
|
|
315
|
+
@TableColumn({
|
|
316
|
+
type: TableColumnType.Email,
|
|
317
|
+
title: "Finance / Accounting Email",
|
|
318
|
+
description:
|
|
319
|
+
"Invoices, receipts and billing related notifications will be sent to this email in addition to project owner.",
|
|
320
|
+
})
|
|
321
|
+
@Column({
|
|
322
|
+
type: ColumnType.Email,
|
|
323
|
+
length: ColumnLength.Email,
|
|
324
|
+
nullable: true,
|
|
325
|
+
unique: false,
|
|
326
|
+
})
|
|
327
|
+
public financeAccountingEmail?: string = undefined;
|
|
328
|
+
|
|
302
329
|
@ColumnAccessControl({
|
|
303
330
|
create: [],
|
|
304
331
|
read: [
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
2
2
|
|
|
3
3
|
export class MigrationName1756293325324 implements MigrationInterface {
|
|
4
|
-
|
|
4
|
+
public name = "MigrationName1756293325324";
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
await queryRunner.query(`ALTER TABLE "Project" DROP COLUMN "businessDetails"`);
|
|
13
|
-
|
|
14
|
-
}
|
|
6
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
7
|
+
await queryRunner.query(
|
|
8
|
+
`ALTER TABLE "Project" ADD "businessDetails" character varying(500)`,
|
|
9
|
+
);
|
|
10
|
+
}
|
|
15
11
|
|
|
12
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
13
|
+
await queryRunner.query(
|
|
14
|
+
`ALTER TABLE "Project" DROP COLUMN "businessDetails"`,
|
|
15
|
+
);
|
|
16
|
+
}
|
|
16
17
|
}
|
|
@@ -1,18 +1,29 @@
|
|
|
1
1
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
2
2
|
|
|
3
3
|
export class MigrationName1756296282627 implements MigrationInterface {
|
|
4
|
-
|
|
4
|
+
public name = "MigrationName1756296282627";
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
6
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
7
|
+
await queryRunner.query(
|
|
8
|
+
`ALTER TABLE "Project" ADD "businessDetailsCountry" character varying(100)`,
|
|
9
|
+
);
|
|
10
|
+
await queryRunner.query(
|
|
11
|
+
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}'`,
|
|
12
|
+
);
|
|
13
|
+
await queryRunner.query(
|
|
14
|
+
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`,
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
17
|
|
|
18
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
19
|
+
await queryRunner.query(
|
|
20
|
+
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`,
|
|
21
|
+
);
|
|
22
|
+
await queryRunner.query(
|
|
23
|
+
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`,
|
|
24
|
+
);
|
|
25
|
+
await queryRunner.query(
|
|
26
|
+
`ALTER TABLE "Project" DROP COLUMN "businessDetailsCountry"`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
18
29
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
2
|
+
|
|
3
|
+
export class MigrationName1756300358095 implements MigrationInterface {
|
|
4
|
+
public name = "MigrationName1756300358095";
|
|
5
|
+
|
|
6
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
7
|
+
await queryRunner.query(
|
|
8
|
+
`ALTER TABLE "Project" ADD "financeAccountingEmail" character varying(100)`,
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
13
|
+
await queryRunner.query(
|
|
14
|
+
`ALTER TABLE "Project" DROP COLUMN "financeAccountingEmail"`,
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -163,6 +163,7 @@ import { MigrationName1755778495455 } from "./1755778495455-MigrationName";
|
|
|
163
163
|
import { MigrationName1755778934927 } from "./1755778934927-MigrationName";
|
|
164
164
|
import { MigrationName1756293325324 } from "./1756293325324-MigrationName";
|
|
165
165
|
import { MigrationName1756296282627 } from "./1756296282627-MigrationName";
|
|
166
|
+
import { MigrationName1756300358095 } from "./1756300358095-MigrationName";
|
|
166
167
|
|
|
167
168
|
export default [
|
|
168
169
|
InitialMigration,
|
|
@@ -329,5 +330,6 @@ export default [
|
|
|
329
330
|
MigrationName1755778495455,
|
|
330
331
|
MigrationName1755778934927,
|
|
331
332
|
MigrationName1756293325324,
|
|
332
|
-
MigrationName1756296282627
|
|
333
|
+
MigrationName1756296282627,
|
|
334
|
+
MigrationName1756300358095,
|
|
333
335
|
];
|
|
@@ -85,6 +85,7 @@ export class BillingService extends BaseService {
|
|
|
85
85
|
id: string,
|
|
86
86
|
businessDetails: string,
|
|
87
87
|
countryCode?: string | null,
|
|
88
|
+
financeAccountingEmail?: string | null,
|
|
88
89
|
): Promise<void> {
|
|
89
90
|
if (!this.isBillingEnabled()) {
|
|
90
91
|
throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED);
|
|
@@ -99,8 +100,12 @@ export class BillingService extends BaseService {
|
|
|
99
100
|
|
|
100
101
|
const lines: Array<string> = businessDetails
|
|
101
102
|
.split(/\r?\n/)
|
|
102
|
-
.map((l: string) =>
|
|
103
|
-
|
|
103
|
+
.map((l: string) => {
|
|
104
|
+
return l.trim();
|
|
105
|
+
})
|
|
106
|
+
.filter((l: string) => {
|
|
107
|
+
return l.length > 0;
|
|
108
|
+
});
|
|
104
109
|
|
|
105
110
|
let line1: string | undefined = undefined;
|
|
106
111
|
let line2: string | undefined = undefined;
|
|
@@ -110,17 +115,34 @@ export class BillingService extends BaseService {
|
|
|
110
115
|
line1 = first.substring(0, 200); // Stripe typical limit safeguard.
|
|
111
116
|
}
|
|
112
117
|
if (lines && lines.length > 1) {
|
|
113
|
-
const rest: string = lines.slice(1).join(
|
|
118
|
+
const rest: string = lines.slice(1).join(", ");
|
|
114
119
|
line2 = rest.substring(0, 200);
|
|
115
120
|
}
|
|
116
121
|
|
|
122
|
+
const metadata: Record<string, string> = {
|
|
123
|
+
business_details_full: businessDetails.substring(0, 5000),
|
|
124
|
+
};
|
|
125
|
+
if (financeAccountingEmail) {
|
|
126
|
+
metadata["finance_accounting_email"] = financeAccountingEmail.substring(
|
|
127
|
+
0,
|
|
128
|
+
200,
|
|
129
|
+
);
|
|
130
|
+
} else {
|
|
131
|
+
// Remove if cleared
|
|
132
|
+
metadata["finance_accounting_email"] = "";
|
|
133
|
+
}
|
|
134
|
+
|
|
117
135
|
const updateParams: Stripe.CustomerUpdateParams = {
|
|
118
|
-
metadata
|
|
119
|
-
business_details_full: businessDetails.substring(0, 5000),
|
|
120
|
-
},
|
|
136
|
+
metadata,
|
|
121
137
|
address: {},
|
|
122
138
|
};
|
|
123
139
|
|
|
140
|
+
// If finance / accounting email provided, set it as the customer email so Stripe sends
|
|
141
|
+
// invoices / receipts there. (Stripe only supports a single email via API currently.)
|
|
142
|
+
if (financeAccountingEmail && financeAccountingEmail.trim().length > 0) {
|
|
143
|
+
updateParams.email = financeAccountingEmail.trim();
|
|
144
|
+
}
|
|
145
|
+
|
|
124
146
|
if (line1) {
|
|
125
147
|
updateParams.address = updateParams.address || {};
|
|
126
148
|
updateParams.address.line1 = line1;
|
|
@@ -138,8 +160,8 @@ export class BillingService extends BaseService {
|
|
|
138
160
|
if (!line1 && !line2 && !countryCode) {
|
|
139
161
|
// Clear address if empty details submitted.
|
|
140
162
|
updateParams.address = {
|
|
141
|
-
line1:
|
|
142
|
-
line2:
|
|
163
|
+
line1: "",
|
|
164
|
+
line2: "",
|
|
143
165
|
} as any;
|
|
144
166
|
}
|
|
145
167
|
|
|
@@ -273,13 +273,17 @@ export class ProjectService extends DatabaseService<Model> {
|
|
|
273
273
|
updateBy: UpdateBy<Model>,
|
|
274
274
|
): Promise<OnUpdate<Model>> {
|
|
275
275
|
if (IsBillingEnabled) {
|
|
276
|
-
|
|
277
|
-
|
|
276
|
+
if (
|
|
277
|
+
updateBy.data.businessDetails ||
|
|
278
|
+
updateBy.data.businessDetailsCountry ||
|
|
279
|
+
updateBy.data.financeAccountingEmail
|
|
280
|
+
) {
|
|
278
281
|
// Sync to Stripe.
|
|
279
282
|
const project: Model | null = await this.findOneById({
|
|
280
283
|
id: new ObjectID(updateBy.query._id! as string),
|
|
281
284
|
select: {
|
|
282
285
|
paymentProviderCustomerId: true,
|
|
286
|
+
financeAccountingEmail: true,
|
|
283
287
|
},
|
|
284
288
|
props: { isRoot: true },
|
|
285
289
|
});
|
|
@@ -288,12 +292,15 @@ export class ProjectService extends DatabaseService<Model> {
|
|
|
288
292
|
try {
|
|
289
293
|
await BillingService.updateCustomerBusinessDetails(
|
|
290
294
|
project.paymentProviderCustomerId,
|
|
291
|
-
(updateBy.data.businessDetails as string) ||
|
|
295
|
+
(updateBy.data.businessDetails as string) || "",
|
|
292
296
|
(updateBy.data.businessDetailsCountry as string) || null,
|
|
297
|
+
(updateBy.data.financeAccountingEmail as string) ||
|
|
298
|
+
(project as any).financeAccountingEmail ||
|
|
299
|
+
null,
|
|
293
300
|
);
|
|
294
301
|
} catch (err) {
|
|
295
302
|
logger.error(
|
|
296
|
-
|
|
303
|
+
"Failed to update Stripe customer business details: " + err,
|
|
297
304
|
);
|
|
298
305
|
}
|
|
299
306
|
}
|
|
@@ -340,10 +340,26 @@ export class Service extends DatabaseService<StatusPage> {
|
|
|
340
340
|
projectId: ObjectID,
|
|
341
341
|
statusPageId: ObjectID,
|
|
342
342
|
): Promise<URL> {
|
|
343
|
-
|
|
343
|
+
if (!projectId) {
|
|
344
|
+
throw new BadDataException(
|
|
345
|
+
"projectId is required to build status page dashboard link",
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (!statusPageId) {
|
|
350
|
+
throw new BadDataException(
|
|
351
|
+
"statusPageId is required to build status page dashboard link",
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const dashboardUrl: URL = await DatabaseConfig.getDashboardUrl();
|
|
356
|
+
|
|
357
|
+
// Defensive: ensure objects have toString
|
|
358
|
+
const projectIdStr: string = projectId.toString();
|
|
359
|
+
const statusPageIdStr: string = statusPageId.toString();
|
|
344
360
|
|
|
345
|
-
return URL.fromString(
|
|
346
|
-
`/${
|
|
361
|
+
return URL.fromString(dashboardUrl.toString()).addRoute(
|
|
362
|
+
`/${projectIdStr}/status-pages/${statusPageIdStr}`,
|
|
347
363
|
);
|
|
348
364
|
}
|
|
349
365
|
|
package/Server/Types/Markdown.ts
CHANGED
|
@@ -203,30 +203,30 @@ export default class Markdown {
|
|
|
203
203
|
return text as string;
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
const isHash: boolean = href.startsWith(
|
|
207
|
-
const isMailTo: boolean = href.startsWith(
|
|
208
|
-
const isTel: boolean = href.startsWith(
|
|
206
|
+
const isHash: boolean = href.startsWith("#");
|
|
207
|
+
const isMailTo: boolean = href.startsWith("mailto:");
|
|
208
|
+
const isTel: boolean = href.startsWith("tel:");
|
|
209
209
|
const isInternal: boolean =
|
|
210
|
-
href.startsWith(
|
|
211
|
-
href.includes(
|
|
210
|
+
href.startsWith("/") ||
|
|
211
|
+
href.includes("oneuptime.com") ||
|
|
212
212
|
isHash ||
|
|
213
213
|
isMailTo ||
|
|
214
214
|
isTel;
|
|
215
215
|
|
|
216
216
|
const baseClasses: string = [
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
].join(
|
|
226
|
-
|
|
227
|
-
const titleAttr: string = title ? ` title="${title}"` :
|
|
217
|
+
"font-semibold",
|
|
218
|
+
"text-indigo-600",
|
|
219
|
+
"underline",
|
|
220
|
+
"underline-offset-2",
|
|
221
|
+
"decoration-indigo-300",
|
|
222
|
+
"hover:decoration-indigo-500",
|
|
223
|
+
"hover:text-indigo-500",
|
|
224
|
+
"transition-colors",
|
|
225
|
+
].join(" ");
|
|
226
|
+
|
|
227
|
+
const titleAttr: string = title ? ` title="${title}"` : "";
|
|
228
228
|
const externalAttrs: string = isInternal
|
|
229
|
-
?
|
|
229
|
+
? ""
|
|
230
230
|
: ' target="_blank" rel="noopener noreferrer"';
|
|
231
231
|
|
|
232
232
|
return `<a href="${href}"${titleAttr} class="${baseClasses}"${externalAttrs}>${text}</a>`;
|
|
@@ -420,9 +420,8 @@ const Detail: DetailFunction = <T extends GenericObject>(
|
|
|
420
420
|
)}
|
|
421
421
|
</div>
|
|
422
422
|
)}
|
|
423
|
-
{(data === null || data === undefined || data ===
|
|
424
|
-
<PlaceholderText text={field.placeholder} />
|
|
425
|
-
)}
|
|
423
|
+
{(data === null || data === undefined || data === "") &&
|
|
424
|
+
field.placeholder && <PlaceholderText text={field.placeholder} />}
|
|
426
425
|
</div>
|
|
427
426
|
</div>
|
|
428
427
|
);
|