Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions packages/cli/src/sdk/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,8 @@ export class PlotLink {
validateNonEmpty("title", title);
validateNonEmpty("content", content);
validateNonEmpty("genre", genre);
validateTitle(title);
validateContentLength(content);

const metadata = JSON.stringify({ title, genre, content });
const key = `plotlink/storylines/${Date.now()}-${slugify(title)}.json`;
Expand Down Expand Up @@ -289,6 +291,7 @@ export class PlotLink {
): Promise<ChainPlotResult> {
this.requireFilebase();
validateNonEmpty("content", content);
validateContentLength(content);

const key = `plotlink/plots/${storylineId}-${Date.now()}.txt`;
const contentCid = await uploadWithRetry(content, key, this.filebase!);
Expand Down Expand Up @@ -667,6 +670,29 @@ function validateNonEmpty(name: string, value: string): void {
}
}

// Content limits — mirrored from lib/content.ts in the web app
const MAX_TITLE_LENGTH = 60;
const MIN_CONTENT_LENGTH = 500;
const MAX_CONTENT_LENGTH = 10_000;

function validateTitle(title: string): void {
const charCount = [...title].length;
if (charCount > MAX_TITLE_LENGTH) {
throw new Error(
`Title must be ${MAX_TITLE_LENGTH} characters or less (currently: ${charCount})`,
);
}
}

function validateContentLength(content: string): void {
const charCount = [...content].length;
if (charCount < MIN_CONTENT_LENGTH || charCount > MAX_CONTENT_LENGTH) {
throw new Error(
`Content must be between ${MIN_CONTENT_LENGTH} and ${MAX_CONTENT_LENGTH} characters (currently: ${charCount})`,
);
}
}

/**
* Compute keccak256 hash of content, matching the on-chain contentHash.
* Same encoding as the web app's hashContent (lib/content.ts).
Expand Down
Loading