@go-labs-sg/event-tech 0.6.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 +81 -0
- package/dist/index.js +182 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -63,6 +63,87 @@ 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.
|
|
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
|
+
|
|
132
|
+
## Lucky draw pre-draw
|
|
133
|
+
|
|
134
|
+
```sh
|
|
135
|
+
et lucky-draw pre-draw --event-id <id> --prize-count 5
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
This immediately assigns winners to the next five unassigned prizes in their
|
|
139
|
+
configured order. It calls the same backend pre-draw operation as the web UI,
|
|
140
|
+
using the live draw's random function and participant email/domain filters.
|
|
141
|
+
Previous winners are excluded, each participant can win only once per batch,
|
|
142
|
+
and the entire batch is saved in one transaction. Too few eligible participants
|
|
143
|
+
or unassigned prizes causes the request to fail.
|
|
144
|
+
|
|
145
|
+
`--prize-count` is required and must be a positive integer. The success envelope
|
|
146
|
+
contains `data.drawnCount` and `data.winners`, including each winner's participant
|
|
147
|
+
ID, name, lucky draw ID, and assigned prize. The command requires an authenticated
|
|
148
|
+
CLI session with access to the event and a deployment that includes the pre-draw
|
|
149
|
+
API.
|
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,16 @@ 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>]
|
|
19584
|
+
et lucky-draw pre-draw --event-id <id> --prize-count <n>
|
|
19585
|
+
|
|
19586
|
+
Lucky draw:
|
|
19587
|
+
pre-draw immediately saves winners for the next unassigned prizes in order.
|
|
19588
|
+
Uses the live draw random selection and participant filters in the backend.
|
|
19564
19589
|
|
|
19565
19590
|
Global options:
|
|
19566
19591
|
--api-url <url> Override EVENT_TECH_API_URL.
|
|
@@ -19633,6 +19658,7 @@ var booleanFlags = new Set([
|
|
|
19633
19658
|
"allow-state-change",
|
|
19634
19659
|
"help",
|
|
19635
19660
|
"no-browser",
|
|
19661
|
+
"public",
|
|
19636
19662
|
"version"
|
|
19637
19663
|
]);
|
|
19638
19664
|
var parseArguments = (args) => {
|
|
@@ -19695,11 +19721,72 @@ var assertPositionalCount = (parsed, expectedCount) => {
|
|
|
19695
19721
|
};
|
|
19696
19722
|
var parsePositiveInteger = (value, name) => {
|
|
19697
19723
|
const parsed = Number(value);
|
|
19698
|
-
if (!Number.
|
|
19724
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0) {
|
|
19699
19725
|
throw new CliUsageError(`--${name} must be a positive integer.`);
|
|
19700
19726
|
}
|
|
19701
19727
|
return parsed;
|
|
19702
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
|
+
};
|
|
19703
19790
|
var eventStatuses = ["DRAFT", "ONGOING", "COMPLETED", "ALL"];
|
|
19704
19791
|
var archiveStatuses = ["active", "archived", "all"];
|
|
19705
19792
|
var parseEventListStatus = (value) => {
|
|
@@ -19837,6 +19924,98 @@ Waiting for approval\u2026
|
|
|
19837
19924
|
}));
|
|
19838
19925
|
return;
|
|
19839
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
|
+
}
|
|
20008
|
+
if (group === "lucky-draw" && action === "pre-draw" && subAction === undefined) {
|
|
20009
|
+
assertPositionalCount(parsed, 2);
|
|
20010
|
+
assertOnlyFlags(parsed, ["event-id", "prize-count"]);
|
|
20011
|
+
const input2 = {
|
|
20012
|
+
eventId: getRequiredFlag(parsed, "event-id"),
|
|
20013
|
+
prizeCount: parsePositiveInteger(getRequiredFlag(parsed, "prize-count"), "prize-count")
|
|
20014
|
+
};
|
|
20015
|
+
await ensureAuthentication();
|
|
20016
|
+
writeSuccess(runtime2, await api2.event.luckyDraw.preDrawPrizes.mutate(input2));
|
|
20017
|
+
return;
|
|
20018
|
+
}
|
|
19840
20019
|
throw new CliUsageError("Unknown command. Run `et --help` for available commands.");
|
|
19841
20020
|
};
|
|
19842
20021
|
var runCli = async (args, runtime2 = processRuntime, credentialStore = createBunCredentialStore({
|