@go-labs-sg/event-tech 0.7.0 → 0.8.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 +62 -0
- package/dist/index.js +165 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -63,10 +63,72 @@ et organization list
|
|
|
63
63
|
et organization get --organization-id <id>
|
|
64
64
|
et event list --organization-id <id>
|
|
65
65
|
et event get --event-id <id>
|
|
66
|
+
et event appearance get --event-id <id>
|
|
67
|
+
et event appearance get --event-id <id> --public
|
|
68
|
+
et event appearance update --event-id <id> --appearance-version <n> --appearance '<json>'
|
|
69
|
+
et event appearance reset --event-id <id> --appearance-version <n>
|
|
70
|
+
et event appearance upload --event-id <id> --file ./background.mp4 --media-type video --content-type video/mp4
|
|
71
|
+
et event photo-wall display-items --event-id <id> --limit 30
|
|
66
72
|
```
|
|
67
73
|
|
|
68
74
|
Commands emit deterministic JSON envelopes. Authentication credentials are never included in successful output, and bearer credentials are redacted from errors.
|
|
69
75
|
|
|
76
|
+
### Event appearance and Photo Wall display
|
|
77
|
+
|
|
78
|
+
`event appearance get` returns the canonical appearance, its optimistic-lock
|
|
79
|
+
version, and resolved media URLs. It uses the authenticated event procedure by
|
|
80
|
+
default. Add `--public` for the same safe projection through the unauthenticated
|
|
81
|
+
public procedure; that read does not access the credential store or require CLI
|
|
82
|
+
login. Pass the authenticated result's version back unchanged when saving:
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
et event appearance update \
|
|
86
|
+
--event-id <id> \
|
|
87
|
+
--appearance-version 3 \
|
|
88
|
+
--appearance '{"version":1,"theme":{"accentColor":"#f84404","backgroundColor":"#f6f7f9","textColor":"#20242a","fontFamily":"SYSTEM"},"guestBackground":{"type":"none","key":null},"displayBackground":{"type":"none","key":null},"photoWall":{"durationSeconds":18,"maxVisible":4,"motion":"gentle","showQrCode":true,"showEventName":true}}'
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Reset is an explicit, version-fenced null update:
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
et event appearance reset --event-id <id> --appearance-version 3
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Upload appearance media before referencing its returned key in the appearance
|
|
98
|
+
JSON. The CLI requests the existing signed attachment URL with the event scope
|
|
99
|
+
and uploads with the declared content type. The server-bound expected size is
|
|
100
|
+
carried in the SDK-signed URL as `x-amz-meta-event-appearance-size`; the CLI and
|
|
101
|
+
browser use that URL unchanged and do not add duplicate metadata headers.
|
|
102
|
+
Before saving a new key, the API checks the event namespace and the actual
|
|
103
|
+
object size/content type, including its expected-size metadata.
|
|
104
|
+
|
|
105
|
+
The contract is covered by offline tests using the real AWS SDK presigner and
|
|
106
|
+
shared object validator, with only storage HEAD responses modelled. This is not
|
|
107
|
+
live AWS verification. See the [AWS presigner documentation](https://github.com/aws/aws-sdk-js-v3/blob/main/packages/s3-request-presigner/README.md#get-presigned-url-with-headers-that-cannot-be-signed)
|
|
108
|
+
for the distinction between signed query parameters and required upload headers.
|
|
109
|
+
|
|
110
|
+
Example:
|
|
111
|
+
|
|
112
|
+
```sh
|
|
113
|
+
et event appearance upload --event-id <id> --file ./background.jpg \
|
|
114
|
+
--media-type image --content-type image/jpeg
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Display pages are approved-only and newest-first. The cursor is JSON with an
|
|
118
|
+
ISO date string; the CLI decodes `createdAt` to a real `Date` before calling the
|
|
119
|
+
API. Retained IDs are sent separately so currently visible items can be
|
|
120
|
+
revalidated:
|
|
121
|
+
|
|
122
|
+
```sh
|
|
123
|
+
et event photo-wall display-items --event-id <id> --limit 30 \
|
|
124
|
+
--cursor '{"createdAt":"2026-01-03T00:00:00.000Z","id":"photo-123"}' \
|
|
125
|
+
--retained-ids '["photo-121","photo-122"]'
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`--limit` is 1–60, `--retained-ids` accepts at most 100 non-empty strings, and
|
|
129
|
+
the success data preserves the API's `items`, `nextCursor`, and
|
|
130
|
+
`retainedItems` fields.
|
|
131
|
+
|
|
70
132
|
## Lucky draw pre-draw
|
|
71
133
|
|
|
72
134
|
```sh
|
package/dist/index.js
CHANGED
|
@@ -19477,7 +19477,8 @@ var logoutCliSession = async ({
|
|
|
19477
19477
|
};
|
|
19478
19478
|
// src/cli.ts
|
|
19479
19479
|
import { readFileSync } from "fs";
|
|
19480
|
-
import {
|
|
19480
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
19481
|
+
import { basename, resolve } from "path";
|
|
19481
19482
|
|
|
19482
19483
|
// src/api-client.ts
|
|
19483
19484
|
import {
|
|
@@ -19525,6 +19526,18 @@ var createEventTechClient = (apiBaseUrl, readAccessToken, fetchImplementation =
|
|
|
19525
19526
|
})
|
|
19526
19527
|
]
|
|
19527
19528
|
});
|
|
19529
|
+
var createEventTechPublicClient = (apiBaseUrl, fetchImplementation = fetch) => createTRPCProxyClient({
|
|
19530
|
+
links: [
|
|
19531
|
+
httpBatchLink({
|
|
19532
|
+
url: `${apiBaseUrl}/api/trpc`,
|
|
19533
|
+
transformer: SuperJSON,
|
|
19534
|
+
fetch: fetchImplementation,
|
|
19535
|
+
headers: () => ({
|
|
19536
|
+
"x-trpc-source": "event-tech-cli"
|
|
19537
|
+
})
|
|
19538
|
+
})
|
|
19539
|
+
]
|
|
19540
|
+
});
|
|
19528
19541
|
var activeApiBaseUrl = DEFAULT_EVENT_TECH_API_BASE_URL;
|
|
19529
19542
|
var activeAccessToken;
|
|
19530
19543
|
var readActiveAccessToken = () => {
|
|
@@ -19534,10 +19547,12 @@ var readActiveAccessToken = () => {
|
|
|
19534
19547
|
return activeAccessToken;
|
|
19535
19548
|
};
|
|
19536
19549
|
var activeClient = createEventTechClient(activeApiBaseUrl, readActiveAccessToken);
|
|
19550
|
+
var activePublicClient = createEventTechPublicClient(activeApiBaseUrl);
|
|
19537
19551
|
var testingClient;
|
|
19538
19552
|
var configureEventTechApiBaseUrl = (value) => {
|
|
19539
19553
|
activeApiBaseUrl = normalizeApiBaseUrl(value);
|
|
19540
19554
|
activeClient = createEventTechClient(activeApiBaseUrl, readActiveAccessToken);
|
|
19555
|
+
activePublicClient = createEventTechPublicClient(activeApiBaseUrl);
|
|
19541
19556
|
return activeApiBaseUrl;
|
|
19542
19557
|
};
|
|
19543
19558
|
var configureEventTechApiFromEnvironment = () => configureEventTechApiBaseUrl(process.env.EVENT_TECH_API_URL ?? DEFAULT_EVENT_TECH_API_BASE_URL);
|
|
@@ -19546,7 +19561,7 @@ var configureEventTechAccessToken = (accessToken) => {
|
|
|
19546
19561
|
activeAccessToken = accessToken;
|
|
19547
19562
|
};
|
|
19548
19563
|
var api2 = new Proxy({}, {
|
|
19549
|
-
get: (_target, property, receiver) => Reflect.get(testingClient ?? activeClient, property, receiver)
|
|
19564
|
+
get: (_target, property, receiver) => Reflect.get(property === "public" ? testingClient ?? activePublicClient : testingClient ?? activeClient, property, receiver)
|
|
19550
19565
|
});
|
|
19551
19566
|
|
|
19552
19567
|
// src/commands/index.ts
|
|
@@ -19561,6 +19576,11 @@ Usage:
|
|
|
19561
19576
|
et organization get --organization-id <id>
|
|
19562
19577
|
et event list --organization-id <id> [--page <n>] [--per-page <n>] [--name <text>] [--status <status>] [--archive-status <status>]
|
|
19563
19578
|
et event get --event-id <id>
|
|
19579
|
+
et event appearance get --event-id <id> [--public]
|
|
19580
|
+
et event appearance update --event-id <id> --appearance-version <n> --appearance <json>
|
|
19581
|
+
et event appearance reset --event-id <id> --appearance-version <n>
|
|
19582
|
+
et event appearance upload --event-id <id> --file <path> --media-type <image|video> --content-type <mime>
|
|
19583
|
+
et event photo-wall display-items --event-id <id> [--limit <n>] [--cursor <json>] [--retained-ids <json>]
|
|
19564
19584
|
et lucky-draw pre-draw --event-id <id> --prize-count <n>
|
|
19565
19585
|
|
|
19566
19586
|
Lucky draw:
|
|
@@ -19638,6 +19658,7 @@ var booleanFlags = new Set([
|
|
|
19638
19658
|
"allow-state-change",
|
|
19639
19659
|
"help",
|
|
19640
19660
|
"no-browser",
|
|
19661
|
+
"public",
|
|
19641
19662
|
"version"
|
|
19642
19663
|
]);
|
|
19643
19664
|
var parseArguments = (args) => {
|
|
@@ -19705,6 +19726,67 @@ var parsePositiveInteger = (value, name) => {
|
|
|
19705
19726
|
}
|
|
19706
19727
|
return parsed;
|
|
19707
19728
|
};
|
|
19729
|
+
var parseNonnegativeInteger = (value, name) => {
|
|
19730
|
+
const parsed = Number(value);
|
|
19731
|
+
if (!Number.isSafeInteger(parsed) || parsed < 0) {
|
|
19732
|
+
throw new CliUsageError(`--${name} must be a non-negative integer.`);
|
|
19733
|
+
}
|
|
19734
|
+
return parsed;
|
|
19735
|
+
};
|
|
19736
|
+
var parseBoundedInteger = (value, name, minimum, maximum) => {
|
|
19737
|
+
const parsed = parsePositiveInteger(value, name);
|
|
19738
|
+
if (parsed > maximum || parsed < minimum) {
|
|
19739
|
+
throw new CliUsageError(`--${name} must be an integer from ${minimum} to ${maximum}.`);
|
|
19740
|
+
}
|
|
19741
|
+
return parsed;
|
|
19742
|
+
};
|
|
19743
|
+
var parseJsonObject = (value, name) => {
|
|
19744
|
+
let parsed;
|
|
19745
|
+
try {
|
|
19746
|
+
parsed = JSON.parse(value);
|
|
19747
|
+
} catch {
|
|
19748
|
+
throw new CliUsageError(`--${name} must be valid JSON.`);
|
|
19749
|
+
}
|
|
19750
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
19751
|
+
throw new CliUsageError(`--${name} must be a JSON object.`);
|
|
19752
|
+
}
|
|
19753
|
+
return parsed;
|
|
19754
|
+
};
|
|
19755
|
+
var parseAppearance = (value) => parseJsonObject(value, "appearance");
|
|
19756
|
+
var parseCursor = (value) => {
|
|
19757
|
+
const parsed = parseJsonObject(value, "cursor");
|
|
19758
|
+
if (typeof parsed.id !== "string" || parsed.id.length === 0) {
|
|
19759
|
+
throw new CliUsageError("--cursor.id must be a non-empty string.");
|
|
19760
|
+
}
|
|
19761
|
+
if (typeof parsed.createdAt !== "string") {
|
|
19762
|
+
throw new CliUsageError("--cursor.createdAt must be an ISO date string.");
|
|
19763
|
+
}
|
|
19764
|
+
const createdAt = new Date(parsed.createdAt);
|
|
19765
|
+
if (Number.isNaN(createdAt.getTime())) {
|
|
19766
|
+
throw new CliUsageError("--cursor.createdAt must be a valid date.");
|
|
19767
|
+
}
|
|
19768
|
+
return { createdAt, id: parsed.id };
|
|
19769
|
+
};
|
|
19770
|
+
var parseRetainedIds = (value) => {
|
|
19771
|
+
let parsed;
|
|
19772
|
+
try {
|
|
19773
|
+
parsed = JSON.parse(value);
|
|
19774
|
+
} catch {
|
|
19775
|
+
throw new CliUsageError("--retained-ids must be valid JSON.");
|
|
19776
|
+
}
|
|
19777
|
+
if (!Array.isArray(parsed)) {
|
|
19778
|
+
throw new CliUsageError("--retained-ids must be a JSON array.");
|
|
19779
|
+
}
|
|
19780
|
+
if (parsed.length > 100 || parsed.some((id) => typeof id !== "string" || id.length === 0)) {
|
|
19781
|
+
throw new CliUsageError("--retained-ids must contain at most 100 non-empty strings.");
|
|
19782
|
+
}
|
|
19783
|
+
return parsed;
|
|
19784
|
+
};
|
|
19785
|
+
var parseAppearanceMediaType = (value) => {
|
|
19786
|
+
if (value === "image" || value === "video")
|
|
19787
|
+
return value;
|
|
19788
|
+
throw new CliUsageError("--media-type must be one of: image, video.");
|
|
19789
|
+
};
|
|
19708
19790
|
var eventStatuses = ["DRAFT", "ONGOING", "COMPLETED", "ALL"];
|
|
19709
19791
|
var archiveStatuses = ["active", "archived", "all"];
|
|
19710
19792
|
var parseEventListStatus = (value) => {
|
|
@@ -19842,6 +19924,87 @@ Waiting for approval\u2026
|
|
|
19842
19924
|
}));
|
|
19843
19925
|
return;
|
|
19844
19926
|
}
|
|
19927
|
+
if (group === "event" && action === "appearance" && subAction === "get") {
|
|
19928
|
+
assertPositionalCount(parsed, 3);
|
|
19929
|
+
assertOnlyFlags(parsed, ["event-id", "public"]);
|
|
19930
|
+
const eventId = getRequiredFlag(parsed, "event-id");
|
|
19931
|
+
if (parsed.flags.get("public") === true) {
|
|
19932
|
+
writeSuccess(runtime2, await api2.public.getEventAppearance.query({ eventId }));
|
|
19933
|
+
return;
|
|
19934
|
+
}
|
|
19935
|
+
await ensureAuthentication();
|
|
19936
|
+
writeSuccess(runtime2, await api2.event.getEventAppearance.query({
|
|
19937
|
+
eventId
|
|
19938
|
+
}));
|
|
19939
|
+
return;
|
|
19940
|
+
}
|
|
19941
|
+
if (group === "event" && action === "appearance" && (subAction === "update" || subAction === "reset")) {
|
|
19942
|
+
assertPositionalCount(parsed, 3);
|
|
19943
|
+
assertOnlyFlags(parsed, [
|
|
19944
|
+
"event-id",
|
|
19945
|
+
"appearance-version",
|
|
19946
|
+
"appearance",
|
|
19947
|
+
"reset"
|
|
19948
|
+
]);
|
|
19949
|
+
const reset = subAction === "reset" || parsed.flags.get("reset") === true;
|
|
19950
|
+
const appearanceValue = getOptionalFlag(parsed, "appearance");
|
|
19951
|
+
if (reset && appearanceValue !== undefined) {
|
|
19952
|
+
throw new CliUsageError("Use either --appearance or --reset, not both.");
|
|
19953
|
+
}
|
|
19954
|
+
if (!reset && appearanceValue === undefined) {
|
|
19955
|
+
throw new CliUsageError("--appearance is required unless --reset is provided.");
|
|
19956
|
+
}
|
|
19957
|
+
const input2 = {
|
|
19958
|
+
eventId: getRequiredFlag(parsed, "event-id"),
|
|
19959
|
+
appearanceVersion: parseNonnegativeInteger(getRequiredFlag(parsed, "appearance-version"), "appearance-version"),
|
|
19960
|
+
appearance: reset ? null : parseAppearance(appearanceValue)
|
|
19961
|
+
};
|
|
19962
|
+
await ensureAuthentication();
|
|
19963
|
+
writeSuccess(runtime2, await api2.event.updateEventAppearance.mutate(input2));
|
|
19964
|
+
return;
|
|
19965
|
+
}
|
|
19966
|
+
if (group === "event" && action === "appearance" && subAction === "upload") {
|
|
19967
|
+
assertPositionalCount(parsed, 3);
|
|
19968
|
+
assertOnlyFlags(parsed, ["event-id", "file", "content-type", "media-type"]);
|
|
19969
|
+
const eventId = getRequiredFlag(parsed, "event-id");
|
|
19970
|
+
const filePath = getRequiredFlag(parsed, "file");
|
|
19971
|
+
const contentType = getRequiredFlag(parsed, "content-type");
|
|
19972
|
+
const mediaType = parseAppearanceMediaType(getRequiredFlag(parsed, "media-type"));
|
|
19973
|
+
const file2 = await readFile2(filePath);
|
|
19974
|
+
await ensureAuthentication();
|
|
19975
|
+
const signed = await api2.attachment.getPresignedUrlToUpload.mutate({
|
|
19976
|
+
folder: `event-appearance/${mediaType === "image" ? "images" : "videos"}`,
|
|
19977
|
+
fileName: basename(filePath),
|
|
19978
|
+
contentType,
|
|
19979
|
+
size: file2.byteLength,
|
|
19980
|
+
eventId
|
|
19981
|
+
});
|
|
19982
|
+
const uploadResponse = await fetch(signed.uploadUrl, {
|
|
19983
|
+
method: "PUT",
|
|
19984
|
+
headers: { "content-type": contentType },
|
|
19985
|
+
body: file2
|
|
19986
|
+
});
|
|
19987
|
+
if (!uploadResponse.ok) {
|
|
19988
|
+
throw new Error(`Appearance media upload failed with HTTP ${uploadResponse.status}.`);
|
|
19989
|
+
}
|
|
19990
|
+
writeSuccess(runtime2, signed);
|
|
19991
|
+
return;
|
|
19992
|
+
}
|
|
19993
|
+
if (group === "event" && action === "photo-wall" && (subAction === "display-items" || subAction === "display")) {
|
|
19994
|
+
assertPositionalCount(parsed, 3);
|
|
19995
|
+
assertOnlyFlags(parsed, ["event-id", "limit", "cursor", "retained-ids"]);
|
|
19996
|
+
const cursorValue = getOptionalFlag(parsed, "cursor");
|
|
19997
|
+
const retainedIdsValue = getOptionalFlag(parsed, "retained-ids");
|
|
19998
|
+
const input2 = {
|
|
19999
|
+
eventId: getRequiredFlag(parsed, "event-id"),
|
|
20000
|
+
limit: parseBoundedInteger(getOptionalFlag(parsed, "limit") ?? "30", "limit", 1, 60),
|
|
20001
|
+
...cursorValue === undefined ? {} : { cursor: parseCursor(cursorValue) },
|
|
20002
|
+
retainedIds: retainedIdsValue === undefined ? [] : parseRetainedIds(retainedIdsValue)
|
|
20003
|
+
};
|
|
20004
|
+
await ensureAuthentication();
|
|
20005
|
+
writeSuccess(runtime2, await api2.event.photoWall.getPhotoWallDisplayItems.query(input2));
|
|
20006
|
+
return;
|
|
20007
|
+
}
|
|
19845
20008
|
if (group === "lucky-draw" && action === "pre-draw" && subAction === undefined) {
|
|
19846
20009
|
assertPositionalCount(parsed, 2);
|
|
19847
20010
|
assertOnlyFlags(parsed, ["event-id", "prize-count"]);
|