@sferarc/aula-pulumi 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/domain.gen.d.ts +16 -0
- package/dist/domain.gen.js +73 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/lessonType.gen.d.ts +36 -0
- package/dist/lessonType.gen.js +73 -0
- package/dist/siteConfig.gen.js +31 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,4 +27,6 @@ export const site = new SiteConfig("site", {
|
|
|
27
27
|
});
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
`pulumi up` creates or updates the school's site config; a second run plans no changes. The resource is never deleted with the stack.
|
|
30
|
+
`pulumi up` creates or updates the school's site config; a second run plans no changes (a lesson type likewise, whatever order its prices are listed in). The resource is never deleted with the stack.
|
|
31
|
+
|
|
32
|
+
`Domain` adds the school's custom domain and exposes the DNS records to publish as outputs (`pointsTo`, `ownershipRecord`, `verification`), so a DNS resource can reference them. It starts pending; run `pulumi refresh` once the records exist and the provider verifies it. Changing the hostname replaces the domain, and deleting it removes the domain from the school. The token needs `domain:write` and the plan `custom_domain`.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as pulumi from "@pulumi/pulumi";
|
|
2
|
+
export interface DomainArgs {
|
|
3
|
+
hostname: pulumi.Input<string>;
|
|
4
|
+
}
|
|
5
|
+
export declare const domainProvider: pulumi.dynamic.ResourceProvider;
|
|
6
|
+
/** The school's custom domain for its main site. Its outputs hold the DNS records to publish (`pointsTo`, `ownershipRecord`, `verification`), so DNS resources can reference them. A pending domain is verified on each refresh once the records exist. Changing the hostname replaces the domain; deleting takes it off the school. */
|
|
7
|
+
export declare class Domain extends pulumi.dynamic.Resource {
|
|
8
|
+
readonly hostname: pulumi.Output<string>;
|
|
9
|
+
readonly status: pulumi.Output<"pending" | "active" | "misconfigured" | "failed" | "removed">;
|
|
10
|
+
/** Where the hostname points: an A record at an apex, else a CNAME. */
|
|
11
|
+
readonly pointsTo: pulumi.Output<Record<string, unknown>>;
|
|
12
|
+
readonly ownershipRecord: pulumi.Output<Record<string, unknown>>;
|
|
13
|
+
readonly verification: pulumi.Output<Array<Record<string, unknown>>>;
|
|
14
|
+
readonly verifiedAt: pulumi.Output<string>;
|
|
15
|
+
constructor(name: string, args: DomainArgs, opts?: pulumi.CustomResourceOptions);
|
|
16
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Code generated by @sferarc/iac-gen. DO NOT EDIT.
|
|
2
|
+
import * as pulumi from "@pulumi/pulumi";
|
|
3
|
+
import { callApi, setProviderConfig, stripUndefined } from "./runtime.js";
|
|
4
|
+
const inputFields = ["hostname"];
|
|
5
|
+
const updateFields = ["hostname"];
|
|
6
|
+
const replaceFields = [];
|
|
7
|
+
const defaultValues = {};
|
|
8
|
+
const sortKeys = {};
|
|
9
|
+
const derivedFields = [];
|
|
10
|
+
const writeOnlyFields = [];
|
|
11
|
+
// The form the API stores: trimmed text, no empty strings, sorted keys, and the lists it sorts.
|
|
12
|
+
function canonical(value) {
|
|
13
|
+
if (typeof value === "string")
|
|
14
|
+
return value.trim() === "" ? null : value.trim();
|
|
15
|
+
if (Array.isArray(value))
|
|
16
|
+
return value.map(canonical);
|
|
17
|
+
if (value !== null && typeof value === "object") {
|
|
18
|
+
const entries = Object.entries(value).filter(([, v]) => v !== undefined);
|
|
19
|
+
return Object.fromEntries(entries.sort(([a], [b]) => (a < b ? -1 : 1)).map(([k, v]) => [k, canonical(v)]));
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
function comparable(key, value) {
|
|
24
|
+
const form = canonical(value) ?? defaultValues[key] ?? null;
|
|
25
|
+
const by = sortKeys[key];
|
|
26
|
+
if (by && Array.isArray(form)) {
|
|
27
|
+
const order = (item) => JSON.stringify(item?.[by] ?? null);
|
|
28
|
+
return JSON.stringify([...form].sort((a, b) => (order(a) < order(b) ? -1 : order(a) > order(b) ? 1 : 0)));
|
|
29
|
+
}
|
|
30
|
+
return JSON.stringify(form);
|
|
31
|
+
}
|
|
32
|
+
function pick(source, keys) {
|
|
33
|
+
const picked = {};
|
|
34
|
+
for (const key of keys)
|
|
35
|
+
picked[key] = source[key];
|
|
36
|
+
return picked;
|
|
37
|
+
}
|
|
38
|
+
export const domainProvider = {
|
|
39
|
+
async configure(req) {
|
|
40
|
+
setProviderConfig(req.config);
|
|
41
|
+
},
|
|
42
|
+
async create(inputs) {
|
|
43
|
+
const result = (await callApi("POST", `/api/v1/domain`, stripUndefined(pick(inputs, inputFields))));
|
|
44
|
+
const id = String(result["id"]);
|
|
45
|
+
return { id, outs: stripUndefined({ ...inputs, ...result }) };
|
|
46
|
+
},
|
|
47
|
+
async read(id, props) {
|
|
48
|
+
const current = (await callApi("GET", `/api/v1/domain`));
|
|
49
|
+
if (!current)
|
|
50
|
+
return {};
|
|
51
|
+
if (current["status"] === "pending") {
|
|
52
|
+
const verified = (await callApi("POST", `/api/v1/domain/verify`));
|
|
53
|
+
if (verified)
|
|
54
|
+
return { id, props: stripUndefined({ ...props, ...verified, ...pick(props, writeOnlyFields) }) };
|
|
55
|
+
}
|
|
56
|
+
const kept = pick(props, writeOnlyFields);
|
|
57
|
+
return { id, props: stripUndefined({ ...props, ...current, ...kept }) };
|
|
58
|
+
},
|
|
59
|
+
async delete(id, props) {
|
|
60
|
+
await callApi("DELETE", `/api/v1/domain`);
|
|
61
|
+
},
|
|
62
|
+
async diff(_id, olds, news) {
|
|
63
|
+
const changes = inputFields.filter((key) => !(derivedFields.includes(key) && canonical(news[key]) == null) &&
|
|
64
|
+
comparable(key, olds[key]) !== comparable(key, news[key]));
|
|
65
|
+
return { changes: changes.length > 0, deleteBeforeReplace: true, replaces: changes };
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
/** The school's custom domain for its main site. Its outputs hold the DNS records to publish (`pointsTo`, `ownershipRecord`, `verification`), so DNS resources can reference them. A pending domain is verified on each refresh once the records exist. Changing the hostname replaces the domain; deleting takes it off the school. */
|
|
69
|
+
export class Domain extends pulumi.dynamic.Resource {
|
|
70
|
+
constructor(name, args, opts) {
|
|
71
|
+
super(domainProvider, name, { "status": undefined, "pointsTo": undefined, "ownershipRecord": undefined, "verification": undefined, "verifiedAt": undefined, ...args }, { ...opts, additionalSecretOutputs: [] });
|
|
72
|
+
}
|
|
73
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as pulumi from "@pulumi/pulumi";
|
|
2
|
+
export interface LessonTypeArgs {
|
|
3
|
+
teacherId: pulumi.Input<string>;
|
|
4
|
+
title: pulumi.Input<string>;
|
|
5
|
+
description?: pulumi.Input<string>;
|
|
6
|
+
isTrial?: pulumi.Input<boolean>;
|
|
7
|
+
active?: pulumi.Input<boolean>;
|
|
8
|
+
capacity?: pulumi.Input<number>;
|
|
9
|
+
mode?: pulumi.Input<"online" | "in_person" | "either">;
|
|
10
|
+
address?: pulumi.Input<string>;
|
|
11
|
+
roomId?: pulumi.Input<string>;
|
|
12
|
+
subject?: pulumi.Input<string>;
|
|
13
|
+
level?: pulumi.Input<string>;
|
|
14
|
+
/** One price per lesson length, in minor units of the school's currency. */
|
|
15
|
+
prices: pulumi.Input<Array<Record<string, unknown>>>;
|
|
16
|
+
}
|
|
17
|
+
export declare const lessonTypeProvider: pulumi.dynamic.ResourceProvider;
|
|
18
|
+
/** A lesson type of the catalogue. The teacher is fixed at creation, so changing it replaces the lesson type. Deleting archives it: bookings and purchases keep pointing at it. */
|
|
19
|
+
export declare class LessonType extends pulumi.dynamic.Resource {
|
|
20
|
+
readonly teacherId: pulumi.Output<string>;
|
|
21
|
+
readonly title: pulumi.Output<string>;
|
|
22
|
+
readonly description: pulumi.Output<string>;
|
|
23
|
+
readonly isTrial: pulumi.Output<boolean>;
|
|
24
|
+
readonly active: pulumi.Output<boolean>;
|
|
25
|
+
readonly capacity: pulumi.Output<number>;
|
|
26
|
+
readonly mode: pulumi.Output<"online" | "in_person" | "either">;
|
|
27
|
+
readonly address: pulumi.Output<string>;
|
|
28
|
+
readonly roomId: pulumi.Output<string>;
|
|
29
|
+
readonly subject: pulumi.Output<string>;
|
|
30
|
+
readonly level: pulumi.Output<string>;
|
|
31
|
+
/** One price per lesson length, in minor units of the school's currency. */
|
|
32
|
+
readonly prices: pulumi.Output<Array<Record<string, unknown>>>;
|
|
33
|
+
readonly currency: pulumi.Output<string>;
|
|
34
|
+
readonly archived: pulumi.Output<boolean>;
|
|
35
|
+
constructor(name: string, args: LessonTypeArgs, opts?: pulumi.CustomResourceOptions);
|
|
36
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Code generated by @sferarc/iac-gen. DO NOT EDIT.
|
|
2
|
+
import * as pulumi from "@pulumi/pulumi";
|
|
3
|
+
import { callApi, setProviderConfig, stripUndefined } from "./runtime.js";
|
|
4
|
+
const inputFields = ["teacherId", "title", "description", "isTrial", "active", "capacity", "mode", "address", "roomId", "subject", "level", "prices"];
|
|
5
|
+
const updateFields = ["title", "description", "isTrial", "active", "capacity", "mode", "address", "roomId", "subject", "level", "prices"];
|
|
6
|
+
const replaceFields = ["teacherId"];
|
|
7
|
+
const defaultValues = { "description": null, "isTrial": false, "active": true, "capacity": 1, "mode": "online", "address": null, "roomId": null, "subject": null, "level": null };
|
|
8
|
+
const sortKeys = { "prices": "durationMinutes" };
|
|
9
|
+
const derivedFields = ["address"];
|
|
10
|
+
const writeOnlyFields = [];
|
|
11
|
+
// The form the API stores: trimmed text, no empty strings, sorted keys, and the lists it sorts.
|
|
12
|
+
function canonical(value) {
|
|
13
|
+
if (typeof value === "string")
|
|
14
|
+
return value.trim() === "" ? null : value.trim();
|
|
15
|
+
if (Array.isArray(value))
|
|
16
|
+
return value.map(canonical);
|
|
17
|
+
if (value !== null && typeof value === "object") {
|
|
18
|
+
const entries = Object.entries(value).filter(([, v]) => v !== undefined);
|
|
19
|
+
return Object.fromEntries(entries.sort(([a], [b]) => (a < b ? -1 : 1)).map(([k, v]) => [k, canonical(v)]));
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
function comparable(key, value) {
|
|
24
|
+
const form = canonical(value) ?? defaultValues[key] ?? null;
|
|
25
|
+
const by = sortKeys[key];
|
|
26
|
+
if (by && Array.isArray(form)) {
|
|
27
|
+
const order = (item) => JSON.stringify(item?.[by] ?? null);
|
|
28
|
+
return JSON.stringify([...form].sort((a, b) => (order(a) < order(b) ? -1 : order(a) > order(b) ? 1 : 0)));
|
|
29
|
+
}
|
|
30
|
+
return JSON.stringify(form);
|
|
31
|
+
}
|
|
32
|
+
function pick(source, keys) {
|
|
33
|
+
const picked = {};
|
|
34
|
+
for (const key of keys)
|
|
35
|
+
picked[key] = source[key];
|
|
36
|
+
return picked;
|
|
37
|
+
}
|
|
38
|
+
export const lessonTypeProvider = {
|
|
39
|
+
async configure(req) {
|
|
40
|
+
setProviderConfig(req.config);
|
|
41
|
+
},
|
|
42
|
+
async create(inputs) {
|
|
43
|
+
const result = (await callApi("POST", `/api/v1/lesson-types`, stripUndefined(pick(inputs, inputFields))));
|
|
44
|
+
const id = String(result["id"]);
|
|
45
|
+
return { id, outs: stripUndefined({ ...inputs, ...result }) };
|
|
46
|
+
},
|
|
47
|
+
async read(id, props) {
|
|
48
|
+
const current = (await callApi("GET", `/api/v1/lesson-types/${encodeURIComponent(id)}`));
|
|
49
|
+
if (!current)
|
|
50
|
+
return {};
|
|
51
|
+
const kept = pick(props, writeOnlyFields);
|
|
52
|
+
return { id, props: stripUndefined({ ...props, ...current, ...kept }) };
|
|
53
|
+
},
|
|
54
|
+
async update(id, _olds, news) {
|
|
55
|
+
const result = (await callApi("PUT", `/api/v1/lesson-types/${encodeURIComponent(id)}`, stripUndefined(pick(news, updateFields))));
|
|
56
|
+
return { outs: stripUndefined({ ...news, ...result }) };
|
|
57
|
+
},
|
|
58
|
+
async delete(id, props) {
|
|
59
|
+
await callApi("DELETE", `/api/v1/lesson-types/${encodeURIComponent(id)}`);
|
|
60
|
+
},
|
|
61
|
+
async diff(_id, olds, news) {
|
|
62
|
+
const changes = inputFields.filter((key) => !(derivedFields.includes(key) && canonical(news[key]) == null) &&
|
|
63
|
+
comparable(key, olds[key]) !== comparable(key, news[key]));
|
|
64
|
+
const replaces = changes.filter((key) => replaceFields.includes(key));
|
|
65
|
+
return { changes: changes.length > 0, replaces, deleteBeforeReplace: false };
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
/** A lesson type of the catalogue. The teacher is fixed at creation, so changing it replaces the lesson type. Deleting archives it: bookings and purchases keep pointing at it. */
|
|
69
|
+
export class LessonType extends pulumi.dynamic.Resource {
|
|
70
|
+
constructor(name, args, opts) {
|
|
71
|
+
super(lessonTypeProvider, name, { "currency": undefined, "archived": undefined, ...args }, { ...opts, additionalSecretOutputs: [] });
|
|
72
|
+
}
|
|
73
|
+
}
|
package/dist/siteConfig.gen.js
CHANGED
|
@@ -2,7 +2,33 @@
|
|
|
2
2
|
import * as pulumi from "@pulumi/pulumi";
|
|
3
3
|
import { callApi, setProviderConfig, stripUndefined } from "./runtime.js";
|
|
4
4
|
const inputFields = ["version", "identity", "design", "locale", "classPolicy", "fees", "faq", "sections", "navigation", "pages", "emails", "sites"];
|
|
5
|
+
const updateFields = ["version", "identity", "design", "locale", "classPolicy", "fees", "faq", "sections", "navigation", "pages", "emails", "sites"];
|
|
6
|
+
const replaceFields = [];
|
|
7
|
+
const defaultValues = {};
|
|
8
|
+
const sortKeys = {};
|
|
9
|
+
const derivedFields = [];
|
|
5
10
|
const writeOnlyFields = [];
|
|
11
|
+
// The form the API stores: trimmed text, no empty strings, sorted keys, and the lists it sorts.
|
|
12
|
+
function canonical(value) {
|
|
13
|
+
if (typeof value === "string")
|
|
14
|
+
return value.trim() === "" ? null : value.trim();
|
|
15
|
+
if (Array.isArray(value))
|
|
16
|
+
return value.map(canonical);
|
|
17
|
+
if (value !== null && typeof value === "object") {
|
|
18
|
+
const entries = Object.entries(value).filter(([, v]) => v !== undefined);
|
|
19
|
+
return Object.fromEntries(entries.sort(([a], [b]) => (a < b ? -1 : 1)).map(([k, v]) => [k, canonical(v)]));
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
function comparable(key, value) {
|
|
24
|
+
const form = canonical(value) ?? defaultValues[key] ?? null;
|
|
25
|
+
const by = sortKeys[key];
|
|
26
|
+
if (by && Array.isArray(form)) {
|
|
27
|
+
const order = (item) => JSON.stringify(item?.[by] ?? null);
|
|
28
|
+
return JSON.stringify([...form].sort((a, b) => (order(a) < order(b) ? -1 : order(a) > order(b) ? 1 : 0)));
|
|
29
|
+
}
|
|
30
|
+
return JSON.stringify(form);
|
|
31
|
+
}
|
|
6
32
|
function pick(source, keys) {
|
|
7
33
|
const picked = {};
|
|
8
34
|
for (const key of keys)
|
|
@@ -26,7 +52,7 @@ export const siteConfigProvider = {
|
|
|
26
52
|
return { id, props: stripUndefined({ ...props, ...current, ...kept }) };
|
|
27
53
|
},
|
|
28
54
|
async update(id, _olds, news) {
|
|
29
|
-
const result = (await callApi("PUT", `/api/v1/site-config`, stripUndefined(pick(news,
|
|
55
|
+
const result = (await callApi("PUT", `/api/v1/site-config`, stripUndefined(pick(news, updateFields))));
|
|
30
56
|
return { outs: stripUndefined({ ...news, ...result }) };
|
|
31
57
|
},
|
|
32
58
|
async delete(id, props) {
|
|
@@ -34,8 +60,10 @@ export const siteConfigProvider = {
|
|
|
34
60
|
void props;
|
|
35
61
|
},
|
|
36
62
|
async diff(_id, olds, news) {
|
|
37
|
-
const changes = inputFields.filter((key) =>
|
|
38
|
-
|
|
63
|
+
const changes = inputFields.filter((key) => !(derivedFields.includes(key) && canonical(news[key]) == null) &&
|
|
64
|
+
comparable(key, olds[key]) !== comparable(key, news[key]));
|
|
65
|
+
const replaces = changes.filter((key) => replaceFields.includes(key));
|
|
66
|
+
return { changes: changes.length > 0, replaces, deleteBeforeReplace: false };
|
|
39
67
|
},
|
|
40
68
|
};
|
|
41
69
|
/** The school's whole site document. One per school: it exists with the school, so it is read and replaced, never created or deleted. */
|