Add public clip backend persistence

This commit is contained in:
Dotta committed 2026-05-16 09:39:23 -05:00
1 parent cfb42dbb95
commit 881ada6e02
17 files changed
+22059

No files matched your search

@@ -0,0 +1,271 @@
CREATE TABLE "clip_comments" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"clip_id" uuid NOT NULL,
"revision_id" uuid,
"creator_profile_id" uuid,
"author_user_id" text,
"author_agent_id" uuid,
"scope" text DEFAULT 'clip' NOT NULL,
"category" text DEFAULT 'question' NOT NULL,
"moderation_state" text DEFAULT 'normal' NOT NULL,
"body" text NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "clip_creator_profiles" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"company_id" uuid NOT NULL,
"handle" text NOT NULL,
"display_name" text NOT NULL,
"bio" text,
"avatar_url" text,
"website_url" text,
"verification_state" text DEFAULT 'unverified' NOT NULL,
"reputation_summary" jsonb DEFAULT '{}'::jsonb NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "clip_dependencies" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"clip_id" uuid NOT NULL,
"revision_id" uuid NOT NULL,
"type" text NOT NULL,
"key" text NOT NULL,
"display_name" text,
"required" text DEFAULT 'required' NOT NULL,
"metadata" jsonb DEFAULT '{}'::jsonb NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "clip_flags" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"clip_id" uuid NOT NULL,
"revision_id" uuid,
"reporter_type" text DEFAULT 'anonymous' NOT NULL,
"reporter_id" text,
"reason" text NOT NULL,
"details" text,
"status" text DEFAULT 'open' NOT NULL,
"metadata" jsonb DEFAULT '{}'::jsonb NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "clip_imported_objects" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"clip_import_id" uuid NOT NULL,
"destination_object_type" text NOT NULL,
"destination_object_id" text NOT NULL,
"package_path" text,
"action" text NOT NULL,
"local_fingerprint" text,
"metadata" jsonb DEFAULT '{}'::jsonb NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "clip_imports" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"destination_company_id" uuid NOT NULL,
"clip_id" uuid NOT NULL,
"revision_id" uuid NOT NULL,
"revision_number" integer NOT NULL,
"manifest_checksum" text NOT NULL,
"artifact_checksum" text NOT NULL,
"source_url" text,
"revision_url" text,
"status" text DEFAULT 'previewed' NOT NULL,
"imported_by_user_id" text,
"imported_by_agent_id" uuid,
"collision_strategy" text,
"selected_options" jsonb DEFAULT '{}'::jsonb NOT NULL,
"first_run_validation_state" text DEFAULT 'not_run' NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "clip_moderation_events" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"clip_id" uuid NOT NULL,
"revision_id" uuid,
"actor_type" text NOT NULL,
"actor_id" text NOT NULL,
"from_state" text,
"to_state" text NOT NULL,
"reason" text NOT NULL,
"metadata" jsonb DEFAULT '{}'::jsonb NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "clip_public_metric_events" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"clip_id" uuid NOT NULL,
"revision_id" uuid,
"event_type" text NOT NULL,
"actor_hash" text,
"metadata" jsonb DEFAULT '{}'::jsonb NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "clip_ranking_snapshots" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"clip_id" uuid NOT NULL,
"scope" text DEFAULT 'global' NOT NULL,
"score" integer DEFAULT 0 NOT NULL,
"factors" jsonb DEFAULT '{}'::jsonb NOT NULL,
"snapshot_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "clip_revisions" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"clip_id" uuid NOT NULL,
"revision_number" integer NOT NULL,
"manifest_version" text DEFAULT 'paperclip.clip/v1' NOT NULL,
"manifest_checksum" text NOT NULL,
"artifact_checksum" text NOT NULL,
"manifest_payload" jsonb NOT NULL,
"artifact_ref" jsonb,
"dependency_graph" jsonb DEFAULT '{}'::jsonb NOT NULL,
"permissions" jsonb DEFAULT '[]'::jsonb NOT NULL,
"secrets_schema" jsonb DEFAULT '[]'::jsonb NOT NULL,
"budget_estimate" jsonb,
"redaction_report" jsonb DEFAULT '{}'::jsonb NOT NULL,
"dangerous_capabilities" jsonb DEFAULT '[]'::jsonb NOT NULL,
"security_review_state" text DEFAULT 'unreviewed' NOT NULL,
"verification_state" text DEFAULT 'not_run' NOT NULL,
"compatibility" jsonb DEFAULT '{}'::jsonb NOT NULL,
"change_summary" text,
"breaking_changes" text,
"migration_notes" text,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "clip_showcase_posts" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"clip_id" uuid NOT NULL,
"revision_id" uuid NOT NULL,
"creator_profile_id" uuid,
"author_user_id" text,
"author_agent_id" uuid,
"type" text DEFAULT 'community_example' NOT NULL,
"title" text NOT NULL,
"body" text,
"media_refs" jsonb DEFAULT '[]'::jsonb NOT NULL,
"validation_state" text DEFAULT 'not_run' NOT NULL,
"moderation_state" text DEFAULT 'normal' NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "clip_votes" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"clip_id" uuid NOT NULL,
"revision_id" uuid NOT NULL,
"actor_type" text NOT NULL,
"actor_id" text NOT NULL,
"vote" text NOT NULL,
"reason" text,
"metadata" jsonb DEFAULT '{}'::jsonb NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "clips" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"source_company_id" uuid NOT NULL,
"creator_profile_id" uuid NOT NULL,
"slug" text NOT NULL,
"type" text NOT NULL,
"title" text NOT NULL,
"summary" text NOT NULL,
"description" text,
"visibility" text DEFAULT 'unlisted' NOT NULL,
"status" text DEFAULT 'pending_review' NOT NULL,
"moderation_state" text DEFAULT 'normal' NOT NULL,
"current_revision_id" uuid,
"latest_approved_revision_id" uuid,
"tags" jsonb DEFAULT '[]'::jsonb NOT NULL,
"categories" jsonb DEFAULT '[]'::jsonb NOT NULL,
"use_cases" jsonb DEFAULT '[]'::jsonb NOT NULL,
"required_providers" jsonb DEFAULT '[]'::jsonb NOT NULL,
"compatibility" jsonb DEFAULT '{}'::jsonb NOT NULL,
"source_kind" text,
"source_object_type" text,
"source_object_id" text,
"import_count" integer DEFAULT 0 NOT NULL,
"successful_first_run_count" integer DEFAULT 0 NOT NULL,
"vote_score" integer DEFAULT 0 NOT NULL,
"upvote_count" integer DEFAULT 0 NOT NULL,
"downvote_count" integer DEFAULT 0 NOT NULL,
"comment_count" integer DEFAULT 0 NOT NULL,
"showcase_count" integer DEFAULT 0 NOT NULL,
"report_count" integer DEFAULT 0 NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
"delisted_at" timestamp with time zone
);
--> statement-breakpoint
ALTER TABLE "clip_comments" ADD CONSTRAINT "clip_comments_clip_id_clips_id_fk" FOREIGN KEY ("clip_id") REFERENCES "public"."clips"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "clip_comments" ADD CONSTRAINT "clip_comments_revision_id_clip_revisions_id_fk" FOREIGN KEY ("revision_id") REFERENCES "public"."clip_revisions"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "clip_comments" ADD CONSTRAINT "clip_comments_creator_profile_id_clip_creator_profiles_id_fk" FOREIGN KEY ("creator_profile_id") REFERENCES "public"."clip_creator_profiles"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "clip_creator_profiles" ADD CONSTRAINT "clip_creator_profiles_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "clip_dependencies" ADD CONSTRAINT "clip_dependencies_clip_id_clips_id_fk" FOREIGN KEY ("clip_id") REFERENCES "public"."clips"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "clip_dependencies" ADD CONSTRAINT "clip_dependencies_revision_id_clip_revisions_id_fk" FOREIGN KEY ("revision_id") REFERENCES "public"."clip_revisions"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "clip_flags" ADD CONSTRAINT "clip_flags_clip_id_clips_id_fk" FOREIGN KEY ("clip_id") REFERENCES "public"."clips"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "clip_flags" ADD CONSTRAINT "clip_flags_revision_id_clip_revisions_id_fk" FOREIGN KEY ("revision_id") REFERENCES "public"."clip_revisions"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "clip_imported_objects" ADD CONSTRAINT "clip_imported_objects_clip_import_id_clip_imports_id_fk" FOREIGN KEY ("clip_import_id") REFERENCES "public"."clip_imports"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "clip_imports" ADD CONSTRAINT "clip_imports_destination_company_id_companies_id_fk" FOREIGN KEY ("destination_company_id") REFERENCES "public"."companies"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "clip_imports" ADD CONSTRAINT "clip_imports_clip_id_clips_id_fk" FOREIGN KEY ("clip_id") REFERENCES "public"."clips"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "clip_imports" ADD CONSTRAINT "clip_imports_revision_id_clip_revisions_id_fk" FOREIGN KEY ("revision_id") REFERENCES "public"."clip_revisions"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "clip_moderation_events" ADD CONSTRAINT "clip_moderation_events_clip_id_clips_id_fk" FOREIGN KEY ("clip_id") REFERENCES "public"."clips"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "clip_moderation_events" ADD CONSTRAINT "clip_moderation_events_revision_id_clip_revisions_id_fk" FOREIGN KEY ("revision_id") REFERENCES "public"."clip_revisions"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "clip_public_metric_events" ADD CONSTRAINT "clip_public_metric_events_clip_id_clips_id_fk" FOREIGN KEY ("clip_id") REFERENCES "public"."clips"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "clip_public_metric_events" ADD CONSTRAINT "clip_public_metric_events_revision_id_clip_revisions_id_fk" FOREIGN KEY ("revision_id") REFERENCES "public"."clip_revisions"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "clip_ranking_snapshots" ADD CONSTRAINT "clip_ranking_snapshots_clip_id_clips_id_fk" FOREIGN KEY ("clip_id") REFERENCES "public"."clips"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "clip_revisions" ADD CONSTRAINT "clip_revisions_clip_id_clips_id_fk" FOREIGN KEY ("clip_id") REFERENCES "public"."clips"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "clip_showcase_posts" ADD CONSTRAINT "clip_showcase_posts_clip_id_clips_id_fk" FOREIGN KEY ("clip_id") REFERENCES "public"."clips"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "clip_showcase_posts" ADD CONSTRAINT "clip_showcase_posts_revision_id_clip_revisions_id_fk" FOREIGN KEY ("revision_id") REFERENCES "public"."clip_revisions"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "clip_showcase_posts" ADD CONSTRAINT "clip_showcase_posts_creator_profile_id_clip_creator_profiles_id_fk" FOREIGN KEY ("creator_profile_id") REFERENCES "public"."clip_creator_profiles"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "clip_votes" ADD CONSTRAINT "clip_votes_clip_id_clips_id_fk" FOREIGN KEY ("clip_id") REFERENCES "public"."clips"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "clip_votes" ADD CONSTRAINT "clip_votes_revision_id_clip_revisions_id_fk" FOREIGN KEY ("revision_id") REFERENCES "public"."clip_revisions"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "clips" ADD CONSTRAINT "clips_source_company_id_companies_id_fk" FOREIGN KEY ("source_company_id") REFERENCES "public"."companies"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "clips" ADD CONSTRAINT "clips_creator_profile_id_clip_creator_profiles_id_fk" FOREIGN KEY ("creator_profile_id") REFERENCES "public"."clip_creator_profiles"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "clip_comments_clip_created_idx" ON "clip_comments" USING btree ("clip_id","created_at");--> statement-breakpoint
CREATE INDEX "clip_comments_revision_idx" ON "clip_comments" USING btree ("revision_id");--> statement-breakpoint
CREATE INDEX "clip_creator_profiles_company_idx" ON "clip_creator_profiles" USING btree ("company_id");--> statement-breakpoint
CREATE UNIQUE INDEX "clip_creator_profiles_handle_uq" ON "clip_creator_profiles" USING btree ("handle");--> statement-breakpoint
CREATE INDEX "clip_dependencies_revision_idx" ON "clip_dependencies" USING btree ("revision_id");--> statement-breakpoint
CREATE INDEX "clip_dependencies_type_key_idx" ON "clip_dependencies" USING btree ("type","key");--> statement-breakpoint
CREATE UNIQUE INDEX "clip_dependencies_revision_type_key_uq" ON "clip_dependencies" USING btree ("revision_id","type","key");--> statement-breakpoint
CREATE INDEX "clip_flags_clip_status_idx" ON "clip_flags" USING btree ("clip_id","status");--> statement-breakpoint
CREATE INDEX "clip_flags_revision_idx" ON "clip_flags" USING btree ("revision_id");--> statement-breakpoint
CREATE INDEX "clip_imported_objects_import_idx" ON "clip_imported_objects" USING btree ("clip_import_id");--> statement-breakpoint
CREATE INDEX "clip_imported_objects_destination_idx" ON "clip_imported_objects" USING btree ("destination_object_type","destination_object_id");--> statement-breakpoint
CREATE INDEX "clip_imports_destination_idx" ON "clip_imports" USING btree ("destination_company_id","created_at");--> statement-breakpoint
CREATE INDEX "clip_imports_revision_idx" ON "clip_imports" USING btree ("revision_id");--> statement-breakpoint
CREATE INDEX "clip_moderation_events_clip_created_idx" ON "clip_moderation_events" USING btree ("clip_id","created_at");--> statement-breakpoint
CREATE INDEX "clip_public_metric_events_clip_event_idx" ON "clip_public_metric_events" USING btree ("clip_id","event_type","created_at");--> statement-breakpoint
CREATE INDEX "clip_ranking_snapshots_scope_score_idx" ON "clip_ranking_snapshots" USING btree ("scope","score");--> statement-breakpoint
CREATE UNIQUE INDEX "clip_ranking_snapshots_clip_scope_uq" ON "clip_ranking_snapshots" USING btree ("clip_id","scope");--> statement-breakpoint
CREATE UNIQUE INDEX "clip_revisions_clip_revision_uq" ON "clip_revisions" USING btree ("clip_id","revision_number");--> statement-breakpoint
CREATE INDEX "clip_revisions_clip_created_idx" ON "clip_revisions" USING btree ("clip_id","created_at");--> statement-breakpoint
CREATE INDEX "clip_revisions_manifest_checksum_idx" ON "clip_revisions" USING btree ("manifest_checksum");--> statement-breakpoint
CREATE INDEX "clip_showcase_posts_clip_created_idx" ON "clip_showcase_posts" USING btree ("clip_id","created_at");--> statement-breakpoint
CREATE INDEX "clip_showcase_posts_revision_idx" ON "clip_showcase_posts" USING btree ("revision_id");--> statement-breakpoint
CREATE INDEX "clip_votes_revision_idx" ON "clip_votes" USING btree ("revision_id");--> statement-breakpoint
CREATE UNIQUE INDEX "clip_votes_revision_actor_uq" ON "clip_votes" USING btree ("revision_id","actor_type","actor_id");--> statement-breakpoint
CREATE UNIQUE INDEX "clips_slug_uq" ON "clips" USING btree ("slug");--> statement-breakpoint
CREATE INDEX "clips_source_company_idx" ON "clips" USING btree ("source_company_id");--> statement-breakpoint
CREATE INDEX "clips_creator_idx" ON "clips" USING btree ("creator_profile_id");--> statement-breakpoint
CREATE INDEX "clips_public_browse_idx" ON "clips" USING btree ("visibility","status","moderation_state","updated_at");--> statement-breakpoint
CREATE INDEX "clips_type_idx" ON "clips" USING btree ("type");--> statement-breakpoint
CREATE OR REPLACE FUNCTION prevent_clip_revision_update()
RETURNS trigger AS $$
BEGIN
RAISE EXCEPTION 'clip_revisions are immutable; publish a new revision instead';
END;
$$ LANGUAGE plpgsql;--> statement-breakpoint
CREATE TRIGGER prevent_clip_revision_update_trigger
BEFORE UPDATE ON "clip_revisions"
FOR EACH ROW EXECUTE FUNCTION prevent_clip_revision_update();
File diff suppressed because it is too large. Load diff
@@ -603,6 +603,13 @@
"when": 1778787362162,
"tag": "0085_tranquil_the_executioner",
"breakpoints": true
},
{
"idx": 86,
"version": "7",
"when": 1778937700331,
"tag": "0086_clean_red_hulk",
"breakpoints": true
}
]
}
+304
View File
@@ -0,0 +1,304 @@
import { integer, jsonb, pgTable, text, timestamp, uniqueIndex, uuid, index } from "drizzle-orm/pg-core";
import { companies } from "./companies.js";
export const clipCreatorProfiles = pgTable(
"clip_creator_profiles",
{
id: uuid("id").primaryKey().defaultRandom(),
companyId: uuid("company_id").notNull().references(() => companies.id),
handle: text("handle").notNull(),
displayName: text("display_name").notNull(),
bio: text("bio"),
avatarUrl: text("avatar_url"),
websiteUrl: text("website_url"),
verificationState: text("verification_state").notNull().default("unverified"),
reputationSummary: jsonb("reputation_summary").$type<Record<string, unknown>>().notNull().default({}),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => ({
companyIdx: index("clip_creator_profiles_company_idx").on(table.companyId),
handleUq: uniqueIndex("clip_creator_profiles_handle_uq").on(table.handle),
}),
);
export const clips = pgTable(
"clips",
{
id: uuid("id").primaryKey().defaultRandom(),
sourceCompanyId: uuid("source_company_id").notNull().references(() => companies.id),
creatorProfileId: uuid("creator_profile_id").notNull().references(() => clipCreatorProfiles.id),
slug: text("slug").notNull(),
type: text("type").notNull(),
title: text("title").notNull(),
summary: text("summary").notNull(),
description: text("description"),
visibility: text("visibility").notNull().default("unlisted"),
status: text("status").notNull().default("pending_review"),
moderationState: text("moderation_state").notNull().default("normal"),
currentRevisionId: uuid("current_revision_id"),
latestApprovedRevisionId: uuid("latest_approved_revision_id"),
tags: jsonb("tags").$type<string[]>().notNull().default([]),
categories: jsonb("categories").$type<string[]>().notNull().default([]),
useCases: jsonb("use_cases").$type<string[]>().notNull().default([]),
requiredProviders: jsonb("required_providers").$type<string[]>().notNull().default([]),
compatibility: jsonb("compatibility").$type<Record<string, unknown>>().notNull().default({}),
sourceKind: text("source_kind"),
sourceObjectType: text("source_object_type"),
sourceObjectId: text("source_object_id"),
importCount: integer("import_count").notNull().default(0),
successfulFirstRunCount: integer("successful_first_run_count").notNull().default(0),
voteScore: integer("vote_score").notNull().default(0),
upvoteCount: integer("upvote_count").notNull().default(0),
downvoteCount: integer("downvote_count").notNull().default(0),
commentCount: integer("comment_count").notNull().default(0),
showcaseCount: integer("showcase_count").notNull().default(0),
reportCount: integer("report_count").notNull().default(0),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
delistedAt: timestamp("delisted_at", { withTimezone: true }),
},
(table) => ({
slugUq: uniqueIndex("clips_slug_uq").on(table.slug),
sourceCompanyIdx: index("clips_source_company_idx").on(table.sourceCompanyId),
creatorIdx: index("clips_creator_idx").on(table.creatorProfileId),
publicBrowseIdx: index("clips_public_browse_idx").on(table.visibility, table.status, table.moderationState, table.updatedAt),
typeIdx: index("clips_type_idx").on(table.type),
}),
);
export const clipRevisions = pgTable(
"clip_revisions",
{
id: uuid("id").primaryKey().defaultRandom(),
clipId: uuid("clip_id").notNull().references(() => clips.id),
revisionNumber: integer("revision_number").notNull(),
manifestVersion: text("manifest_version").notNull().default("paperclip.clip/v1"),
manifestChecksum: text("manifest_checksum").notNull(),
artifactChecksum: text("artifact_checksum").notNull(),
manifestPayload: jsonb("manifest_payload").$type<Record<string, unknown>>().notNull(),
artifactRef: jsonb("artifact_ref").$type<Record<string, unknown> | null>(),
dependencyGraph: jsonb("dependency_graph").$type<Record<string, unknown>>().notNull().default({}),
permissions: jsonb("permissions").$type<Record<string, unknown>[]>().notNull().default([]),
secretsSchema: jsonb("secrets_schema").$type<Record<string, unknown>[]>().notNull().default([]),
budgetEstimate: jsonb("budget_estimate").$type<Record<string, unknown> | null>(),
redactionReport: jsonb("redaction_report").$type<Record<string, unknown>>().notNull().default({}),
dangerousCapabilities: jsonb("dangerous_capabilities").$type<string[]>().notNull().default([]),
securityReviewState: text("security_review_state").notNull().default("unreviewed"),
verificationState: text("verification_state").notNull().default("not_run"),
compatibility: jsonb("compatibility").$type<Record<string, unknown>>().notNull().default({}),
changeSummary: text("change_summary"),
breakingChanges: text("breaking_changes"),
migrationNotes: text("migration_notes"),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => ({
clipRevisionUq: uniqueIndex("clip_revisions_clip_revision_uq").on(table.clipId, table.revisionNumber),
clipCreatedIdx: index("clip_revisions_clip_created_idx").on(table.clipId, table.createdAt),
manifestChecksumIdx: index("clip_revisions_manifest_checksum_idx").on(table.manifestChecksum),
}),
);
export const clipDependencies = pgTable(
"clip_dependencies",
{
id: uuid("id").primaryKey().defaultRandom(),
clipId: uuid("clip_id").notNull().references(() => clips.id),
revisionId: uuid("revision_id").notNull().references(() => clipRevisions.id),
type: text("type").notNull(),
key: text("key").notNull(),
displayName: text("display_name"),
required: text("required").notNull().default("required"),
metadata: jsonb("metadata").$type<Record<string, unknown>>().notNull().default({}),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => ({
revisionIdx: index("clip_dependencies_revision_idx").on(table.revisionId),
typeKeyIdx: index("clip_dependencies_type_key_idx").on(table.type, table.key),
revisionTypeKeyUq: uniqueIndex("clip_dependencies_revision_type_key_uq").on(table.revisionId, table.type, table.key),
}),
);
export const clipComments = pgTable(
"clip_comments",
{
id: uuid("id").primaryKey().defaultRandom(),
clipId: uuid("clip_id").notNull().references(() => clips.id),
revisionId: uuid("revision_id").references(() => clipRevisions.id),
creatorProfileId: uuid("creator_profile_id").references(() => clipCreatorProfiles.id),
authorUserId: text("author_user_id"),
authorAgentId: uuid("author_agent_id"),
scope: text("scope").notNull().default("clip"),
category: text("category").notNull().default("question"),
moderationState: text("moderation_state").notNull().default("normal"),
body: text("body").notNull(),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => ({
clipCreatedIdx: index("clip_comments_clip_created_idx").on(table.clipId, table.createdAt),
revisionIdx: index("clip_comments_revision_idx").on(table.revisionId),
}),
);
export const clipVotes = pgTable(
"clip_votes",
{
id: uuid("id").primaryKey().defaultRandom(),
clipId: uuid("clip_id").notNull().references(() => clips.id),
revisionId: uuid("revision_id").notNull().references(() => clipRevisions.id),
actorType: text("actor_type").notNull(),
actorId: text("actor_id").notNull(),
vote: text("vote").notNull(),
reason: text("reason"),
metadata: jsonb("metadata").$type<Record<string, unknown>>().notNull().default({}),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => ({
revisionIdx: index("clip_votes_revision_idx").on(table.revisionId),
actorUq: uniqueIndex("clip_votes_revision_actor_uq").on(table.revisionId, table.actorType, table.actorId),
}),
);
export const clipShowcasePosts = pgTable(
"clip_showcase_posts",
{
id: uuid("id").primaryKey().defaultRandom(),
clipId: uuid("clip_id").notNull().references(() => clips.id),
revisionId: uuid("revision_id").notNull().references(() => clipRevisions.id),
creatorProfileId: uuid("creator_profile_id").references(() => clipCreatorProfiles.id),
authorUserId: text("author_user_id"),
authorAgentId: uuid("author_agent_id"),
type: text("type").notNull().default("community_example"),
title: text("title").notNull(),
body: text("body"),
mediaRefs: jsonb("media_refs").$type<Record<string, unknown>[]>().notNull().default([]),
validationState: text("validation_state").notNull().default("not_run"),
moderationState: text("moderation_state").notNull().default("normal"),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => ({
clipCreatedIdx: index("clip_showcase_posts_clip_created_idx").on(table.clipId, table.createdAt),
revisionIdx: index("clip_showcase_posts_revision_idx").on(table.revisionId),
}),
);
export const clipFlags = pgTable(
"clip_flags",
{
id: uuid("id").primaryKey().defaultRandom(),
clipId: uuid("clip_id").notNull().references(() => clips.id),
revisionId: uuid("revision_id").references(() => clipRevisions.id),
reporterType: text("reporter_type").notNull().default("anonymous"),
reporterId: text("reporter_id"),
reason: text("reason").notNull(),
details: text("details"),
status: text("status").notNull().default("open"),
metadata: jsonb("metadata").$type<Record<string, unknown>>().notNull().default({}),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => ({
clipStatusIdx: index("clip_flags_clip_status_idx").on(table.clipId, table.status),
revisionIdx: index("clip_flags_revision_idx").on(table.revisionId),
}),
);
export const clipModerationEvents = pgTable(
"clip_moderation_events",
{
id: uuid("id").primaryKey().defaultRandom(),
clipId: uuid("clip_id").notNull().references(() => clips.id),
revisionId: uuid("revision_id").references(() => clipRevisions.id),
actorType: text("actor_type").notNull(),
actorId: text("actor_id").notNull(),
fromState: text("from_state"),
toState: text("to_state").notNull(),
reason: text("reason").notNull(),
metadata: jsonb("metadata").$type<Record<string, unknown>>().notNull().default({}),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => ({
clipCreatedIdx: index("clip_moderation_events_clip_created_idx").on(table.clipId, table.createdAt),
}),
);
export const clipRankingSnapshots = pgTable(
"clip_ranking_snapshots",
{
id: uuid("id").primaryKey().defaultRandom(),
clipId: uuid("clip_id").notNull().references(() => clips.id),
scope: text("scope").notNull().default("global"),
score: integer("score").notNull().default(0),
factors: jsonb("factors").$type<Record<string, unknown>>().notNull().default({}),
snapshotAt: timestamp("snapshot_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => ({
scopeScoreIdx: index("clip_ranking_snapshots_scope_score_idx").on(table.scope, table.score),
clipScopeUq: uniqueIndex("clip_ranking_snapshots_clip_scope_uq").on(table.clipId, table.scope),
}),
);
export const clipImports = pgTable(
"clip_imports",
{
id: uuid("id").primaryKey().defaultRandom(),
destinationCompanyId: uuid("destination_company_id").notNull().references(() => companies.id),
clipId: uuid("clip_id").notNull().references(() => clips.id),
revisionId: uuid("revision_id").notNull().references(() => clipRevisions.id),
revisionNumber: integer("revision_number").notNull(),
manifestChecksum: text("manifest_checksum").notNull(),
artifactChecksum: text("artifact_checksum").notNull(),
sourceUrl: text("source_url"),
revisionUrl: text("revision_url"),
status: text("status").notNull().default("previewed"),
importedByUserId: text("imported_by_user_id"),
importedByAgentId: uuid("imported_by_agent_id"),
collisionStrategy: text("collision_strategy"),
selectedOptions: jsonb("selected_options").$type<Record<string, unknown>>().notNull().default({}),
firstRunValidationState: text("first_run_validation_state").notNull().default("not_run"),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => ({
destinationIdx: index("clip_imports_destination_idx").on(table.destinationCompanyId, table.createdAt),
revisionIdx: index("clip_imports_revision_idx").on(table.revisionId),
}),
);
export const clipImportedObjects = pgTable(
"clip_imported_objects",
{
id: uuid("id").primaryKey().defaultRandom(),
clipImportId: uuid("clip_import_id").notNull().references(() => clipImports.id),
destinationObjectType: text("destination_object_type").notNull(),
destinationObjectId: text("destination_object_id").notNull(),
packagePath: text("package_path"),
action: text("action").notNull(),
localFingerprint: text("local_fingerprint"),
metadata: jsonb("metadata").$type<Record<string, unknown>>().notNull().default({}),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => ({
importIdx: index("clip_imported_objects_import_idx").on(table.clipImportId),
destinationIdx: index("clip_imported_objects_destination_idx").on(table.destinationObjectType, table.destinationObjectId),
}),
);
export const clipPublicMetricEvents = pgTable(
"clip_public_metric_events",
{
id: uuid("id").primaryKey().defaultRandom(),
clipId: uuid("clip_id").notNull().references(() => clips.id),
revisionId: uuid("revision_id").references(() => clipRevisions.id),
eventType: text("event_type").notNull(),
actorHash: text("actor_hash"),
metadata: jsonb("metadata").$type<Record<string, unknown>>().notNull().default({}),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => ({
clipEventIdx: index("clip_public_metric_events_clip_event_idx").on(table.clipId, table.eventType, table.createdAt),
}),
);
+15
View File
@@ -57,6 +57,21 @@ export { heartbeatRunEvents } from "./heartbeat_run_events.js";
export { heartbeatRunWatchdogDecisions } from "./heartbeat_run_watchdog_decisions.js";
export { costEvents } from "./cost_events.js";
export { financeEvents } from "./finance_events.js";
export {
clipCreatorProfiles,
clips,
clipRevisions,
clipDependencies,
clipComments,
clipVotes,
clipShowcasePosts,
clipFlags,
clipModerationEvents,
clipRankingSnapshots,
clipImports,
clipImportedObjects,
clipPublicMetricEvents,
} from "./clips.js";
export { approvals } from "./approvals.js";
export { approvalComments } from "./approval_comments.js";
export { activityLog } from "./activity_log.js";
+2
View File
@@ -11,6 +11,8 @@ export const API = {
goals: `${API_PREFIX}/goals`,
approvals: `${API_PREFIX}/approvals`,
secrets: `${API_PREFIX}/secrets`,
clips: `${API_PREFIX}/clips`,
publicClips: `${API_PREFIX}/public/clips`,
secretProviderConfigs: `${API_PREFIX}/secret-provider-configs`,
costs: `${API_PREFIX}/costs`,
activity: `${API_PREFIX}/activity`,
+48
View File
@@ -701,6 +701,36 @@ export {
FEEDBACK_VOTE_VALUES,
DEFAULT_FEEDBACK_DATA_SHARING_TERMS_VERSION,
} from "./types/feedback.js";
export {
CLIP_TYPES,
CLIP_VISIBILITIES,
CLIP_STATUSES,
CLIP_MODERATION_STATES,
CLIP_SECURITY_REVIEW_STATES,
CLIP_VERIFICATION_STATES,
CLIP_DEPENDENCY_TYPES,
CLIP_DEPENDENCY_REQUIREMENTS,
CLIP_COMMENT_CATEGORIES,
CLIP_VOTE_VALUES,
CLIP_REPORT_REASONS,
CLIP_IMPORT_STATUSES,
type ClipType,
type ClipVisibility,
type ClipStatus,
type ClipModerationState,
type ClipSecurityReviewState,
type ClipVerificationState,
type ClipDependencyType,
type ClipDependencyRequirement,
type ClipCommentCategory,
type ClipVoteValue,
type ClipReportReason,
type ClipImportStatus,
type ClipCreatorProfile,
type ClipDependency,
type ClipRevision,
type PublicClip,
} from "./types/clip.js";
export {
DAILY_RETENTION_PRESETS,
@@ -740,6 +770,24 @@ export {
type UpdateCompany,
type UpdateCompanyBranding,
type UpsertIssueFeedbackVote,
createClipCreatorProfileSchema,
publishClipSchema,
createClipRevisionSchema,
updateClipSchema,
createClipVoteSchema,
createClipReportSchema,
createClipCommentSchema,
createClipShowcaseSchema,
createClipImportTelemetrySchema,
type CreateClipCreatorProfile,
type PublishClip,
type CreateClipRevision,
type UpdateClip,
type CreateClipVote,
type CreateClipReport,
type CreateClipComment,
type CreateClipShowcase,
type CreateClipImportTelemetry,
environmentDriverSchema,
environmentStatusSchema,
environmentLeaseStatusSchema,
+144
View File
@@ -0,0 +1,144 @@
export const CLIP_TYPES = ["team", "agent", "skill", "routine", "bundle"] as const;
export type ClipType = (typeof CLIP_TYPES)[number];
export const CLIP_VISIBILITIES = ["private_share", "unlisted", "public"] as const;
export type ClipVisibility = (typeof CLIP_VISIBILITIES)[number];
export const CLIP_STATUSES = ["draft", "pending_review", "published", "delisted", "archived"] as const;
export type ClipStatus = (typeof CLIP_STATUSES)[number];
export const CLIP_MODERATION_STATES = ["normal", "under_review", "warning", "limited", "blocked", "delisted"] as const;
export type ClipModerationState = (typeof CLIP_MODERATION_STATES)[number];
export const CLIP_SECURITY_REVIEW_STATES = ["unreviewed", "automated_checked", "security_reviewed", "blocked"] as const;
export type ClipSecurityReviewState = (typeof CLIP_SECURITY_REVIEW_STATES)[number];
export const CLIP_VERIFICATION_STATES = ["not_run", "passed", "failed", "stale"] as const;
export type ClipVerificationState = (typeof CLIP_VERIFICATION_STATES)[number];
export const CLIP_DEPENDENCY_TYPES = ["adapter", "plugin", "skill", "secret", "permission", "runtime", "workspace"] as const;
export type ClipDependencyType = (typeof CLIP_DEPENDENCY_TYPES)[number];
export const CLIP_DEPENDENCY_REQUIREMENTS = ["required", "optional"] as const;
export type ClipDependencyRequirement = (typeof CLIP_DEPENDENCY_REQUIREMENTS)[number];
export const CLIP_COMMENT_CATEGORIES = [
"question",
"bug",
"integration_help",
"use_case_report",
"security_concern",
"maintainer_note",
] as const;
export type ClipCommentCategory = (typeof CLIP_COMMENT_CATEGORIES)[number];
export const CLIP_VOTE_VALUES = ["up", "down"] as const;
export type ClipVoteValue = (typeof CLIP_VOTE_VALUES)[number];
export const CLIP_REPORT_REASONS = [
"malicious_instructions",
"credential_theft",
"unsafe_automation",
"spam",
"copyright",
"misleading_metadata",
"broken_install",
"impersonation",
"other",
] as const;
export type ClipReportReason = (typeof CLIP_REPORT_REASONS)[number];
export const CLIP_IMPORT_STATUSES = [
"previewed",
"applied",
"failed",
"rolled_back",
"update_available",
"superseded",
] as const;
export type ClipImportStatus = (typeof CLIP_IMPORT_STATUSES)[number];
export interface ClipCreatorProfile {
id: string;
companyId: string;
handle: string;
displayName: string;
bio: string | null;
avatarUrl: string | null;
websiteUrl: string | null;
verificationState: string;
reputationSummary: Record<string, unknown>;
createdAt: Date | string;
updatedAt: Date | string;
}
export interface ClipDependency {
id: string;
clipId: string;
revisionId: string;
type: ClipDependencyType;
key: string;
displayName: string | null;
required: ClipDependencyRequirement;
metadata: Record<string, unknown>;
createdAt: Date | string;
}
export interface ClipRevision {
id: string;
clipId: string;
revisionNumber: number;
manifestVersion: string;
manifestChecksum: string;
artifactChecksum: string;
manifestPayload: Record<string, unknown>;
artifactRef: Record<string, unknown> | null;
dependencyGraph: Record<string, unknown>;
permissions: Record<string, unknown>[];
secretsSchema: Record<string, unknown>[];
budgetEstimate: Record<string, unknown> | null;
redactionReport: Record<string, unknown>;
dangerousCapabilities: string[];
securityReviewState: ClipSecurityReviewState;
verificationState: ClipVerificationState;
compatibility: Record<string, unknown>;
changeSummary: string | null;
breakingChanges: string | null;
migrationNotes: string | null;
createdAt: Date | string;
}
export interface PublicClip {
id: string;
slug: string;
type: ClipType;
title: string;
summary: string;
description: string | null;
visibility: ClipVisibility;
status: ClipStatus;
moderationState: ClipModerationState;
currentRevisionId: string | null;
latestApprovedRevisionId: string | null;
creatorProfileId: string;
creator: Pick<ClipCreatorProfile, "id" | "handle" | "displayName" | "avatarUrl" | "verificationState" | "reputationSummary">;
tags: string[];
categories: string[];
useCases: string[];
requiredProviders: string[];
compatibility: Record<string, unknown>;
metrics: {
importCount: number;
successfulFirstRunCount: number;
voteScore: number;
upvoteCount: number;
downvoteCount: number;
commentCount: number;
showcaseCount: number;
reportCount: number;
};
currentRevision?: ClipRevision | null;
dependencies?: ClipDependency[];
createdAt: Date | string;
updatedAt: Date | string;
}
+30
View File
@@ -1,4 +1,34 @@
export type { Company } from "./company.js";
export {
CLIP_TYPES,
CLIP_VISIBILITIES,
CLIP_STATUSES,
CLIP_MODERATION_STATES,
CLIP_SECURITY_REVIEW_STATES,
CLIP_VERIFICATION_STATES,
CLIP_DEPENDENCY_TYPES,
CLIP_DEPENDENCY_REQUIREMENTS,
CLIP_COMMENT_CATEGORIES,
CLIP_VOTE_VALUES,
CLIP_REPORT_REASONS,
CLIP_IMPORT_STATUSES,
type ClipType,
type ClipVisibility,
type ClipStatus,
type ClipModerationState,
type ClipSecurityReviewState,
type ClipVerificationState,
type ClipDependencyType,
type ClipDependencyRequirement,
type ClipCommentCategory,
type ClipVoteValue,
type ClipReportReason,
type ClipImportStatus,
type ClipCreatorProfile,
type ClipDependency,
type ClipRevision,
type PublicClip,
} from "./clip.js";
export type {
Environment,
EnvironmentLease,
+180
View File
@@ -0,0 +1,180 @@
import { z } from "zod";
import {
CLIP_COMMENT_CATEGORIES,
CLIP_DEPENDENCY_REQUIREMENTS,
CLIP_DEPENDENCY_TYPES,
CLIP_IMPORT_STATUSES,
CLIP_MODERATION_STATES,
CLIP_REPORT_REASONS,
CLIP_SECURITY_REVIEW_STATES,
CLIP_STATUSES,
CLIP_TYPES,
CLIP_VERIFICATION_STATES,
CLIP_VISIBILITIES,
CLIP_VOTE_VALUES,
} from "../types/clip.js";
const slugSchema = z
.string()
.min(3)
.max(96)
.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/);
const publicTextSchema = z.string().trim().min(1).max(12_000);
const shortPublicTextSchema = z.string().trim().min(1).max(280);
const tagArraySchema = z.array(z.string().trim().min(1).max(48)).max(24).default([]);
const recordSchema = z.record(z.string(), z.unknown());
const optionalRecordSchema = recordSchema.nullable().optional();
export const clipTypeSchema = z.enum(CLIP_TYPES);
export const clipVisibilitySchema = z.enum(CLIP_VISIBILITIES);
export const clipStatusSchema = z.enum(CLIP_STATUSES);
export const clipModerationStateSchema = z.enum(CLIP_MODERATION_STATES);
export const clipSecurityReviewStateSchema = z.enum(CLIP_SECURITY_REVIEW_STATES);
export const clipVerificationStateSchema = z.enum(CLIP_VERIFICATION_STATES);
export const clipDependencyTypeSchema = z.enum(CLIP_DEPENDENCY_TYPES);
export const clipVoteValueSchema = z.enum(CLIP_VOTE_VALUES);
export const clipReportReasonSchema = z.enum(CLIP_REPORT_REASONS);
export const createClipCreatorProfileSchema = z.object({
handle: slugSchema,
displayName: z.string().trim().min(1).max(96),
bio: z.string().trim().max(1_000).nullable().optional(),
avatarUrl: z.string().url().nullable().optional(),
websiteUrl: z.string().url().nullable().optional(),
verificationState: z.string().trim().min(1).max(48).optional(),
reputationSummary: recordSchema.optional(),
});
export type CreateClipCreatorProfile = z.infer<typeof createClipCreatorProfileSchema>;
export const clipDependencyInputSchema = z.object({
type: clipDependencyTypeSchema,
key: z.string().trim().min(1).max(160),
displayName: z.string().trim().min(1).max(160).nullable().optional(),
required: z.enum(CLIP_DEPENDENCY_REQUIREMENTS).optional(),
metadata: recordSchema.optional(),
});
export type ClipDependencyInput = z.infer<typeof clipDependencyInputSchema>;
export const clipRevisionInputSchema = z.object({
manifestVersion: z.string().trim().min(1).max(64).optional(),
manifestChecksum: z.string().trim().min(8).max(160),
artifactChecksum: z.string().trim().min(8).max(160),
manifestPayload: recordSchema,
artifactRef: optionalRecordSchema,
dependencyGraph: recordSchema.optional(),
dependencies: z.array(clipDependencyInputSchema).max(200).optional(),
permissions: z.array(recordSchema).max(200).optional(),
secretsSchema: z.array(recordSchema).max(200).optional(),
budgetEstimate: optionalRecordSchema,
redactionReport: recordSchema.optional(),
dangerousCapabilities: z.array(z.string().trim().min(1).max(80)).max(100).optional(),
securityReviewState: clipSecurityReviewStateSchema.optional(),
verificationState: clipVerificationStateSchema.optional(),
compatibility: recordSchema.optional(),
changeSummary: z.string().trim().max(4_000).nullable().optional(),
breakingChanges: z.string().trim().max(4_000).nullable().optional(),
migrationNotes: z.string().trim().max(4_000).nullable().optional(),
});
export type ClipRevisionInput = z.infer<typeof clipRevisionInputSchema>;
export const publishClipSchema = z.object({
creatorProfileId: z.string().uuid().optional(),
creatorProfile: createClipCreatorProfileSchema.optional(),
slug: slugSchema,
type: clipTypeSchema,
title: z.string().trim().min(1).max(160),
summary: shortPublicTextSchema,
description: publicTextSchema.nullable().optional(),
visibility: clipVisibilitySchema.optional(),
status: clipStatusSchema.optional(),
tags: tagArraySchema.optional(),
categories: tagArraySchema.optional(),
useCases: tagArraySchema.optional(),
requiredProviders: tagArraySchema.optional(),
compatibility: recordSchema.optional(),
sourceKind: z.string().trim().min(1).max(80).nullable().optional(),
sourceObjectType: z.string().trim().min(1).max(80).nullable().optional(),
sourceObjectId: z.string().trim().min(1).max(160).nullable().optional(),
revision: clipRevisionInputSchema,
}).refine((value) => value.creatorProfileId || value.creatorProfile, {
message: "creatorProfileId or creatorProfile is required",
path: ["creatorProfileId"],
});
export type PublishClip = z.infer<typeof publishClipSchema>;
export const createClipRevisionSchema = clipRevisionInputSchema;
export type CreateClipRevision = z.infer<typeof createClipRevisionSchema>;
export const updateClipSchema = z
.object({
title: z.string().trim().min(1).max(160).optional(),
summary: shortPublicTextSchema.optional(),
description: publicTextSchema.nullable().optional(),
visibility: clipVisibilitySchema.optional(),
status: clipStatusSchema.optional(),
moderationState: clipModerationStateSchema.optional(),
latestApprovedRevisionId: z.string().uuid().nullable().optional(),
tags: tagArraySchema.optional(),
categories: tagArraySchema.optional(),
useCases: tagArraySchema.optional(),
requiredProviders: tagArraySchema.optional(),
compatibility: recordSchema.optional(),
moderationReason: z.string().trim().max(1_000).optional(),
})
.strict();
export type UpdateClip = z.infer<typeof updateClipSchema>;
export const createClipVoteSchema = z.object({
revisionNumber: z.number().int().positive().optional(),
vote: clipVoteValueSchema,
reason: z.string().trim().max(400).nullable().optional(),
metadata: recordSchema.optional(),
});
export type CreateClipVote = z.infer<typeof createClipVoteSchema>;
export const createClipReportSchema = z.object({
revisionNumber: z.number().int().positive().optional(),
reason: clipReportReasonSchema,
details: z.string().trim().max(4_000).nullable().optional(),
metadata: recordSchema.optional(),
});
export type CreateClipReport = z.infer<typeof createClipReportSchema>;
export const createClipCommentSchema = z.object({
revisionNumber: z.number().int().positive().optional(),
scope: z.enum(["clip", "revision"]).optional(),
category: z.enum(CLIP_COMMENT_CATEGORIES).optional(),
body: publicTextSchema,
});
export type CreateClipComment = z.infer<typeof createClipCommentSchema>;
export const createClipShowcaseSchema = z.object({
revisionNumber: z.number().int().positive().optional(),
type: z.string().trim().min(1).max(80).optional(),
title: z.string().trim().min(1).max(160),
body: publicTextSchema.nullable().optional(),
mediaRefs: z.array(recordSchema).max(12).optional(),
validationState: clipVerificationStateSchema.optional(),
});
export type CreateClipShowcase = z.infer<typeof createClipShowcaseSchema>;
export const createClipImportTelemetrySchema = z.object({
revisionNumber: z.number().int().positive().optional(),
destinationCompanyId: z.string().uuid().optional(),
status: z.enum(CLIP_IMPORT_STATUSES).optional(),
sourceUrl: z.string().url().nullable().optional(),
revisionUrl: z.string().url().nullable().optional(),
metadata: recordSchema.optional(),
});
export type CreateClipImportTelemetry = z.infer<typeof createClipImportTelemetrySchema>;
+33
View File
@@ -26,6 +26,39 @@ export {
type UpdateCompany,
type UpdateCompanyBranding,
} from "./company.js";
export {
clipTypeSchema,
clipVisibilitySchema,
clipStatusSchema,
clipModerationStateSchema,
clipSecurityReviewStateSchema,
clipVerificationStateSchema,
clipDependencyTypeSchema,
clipVoteValueSchema,
clipReportReasonSchema,
createClipCreatorProfileSchema,
clipDependencyInputSchema,
clipRevisionInputSchema,
publishClipSchema,
createClipRevisionSchema,
updateClipSchema,
createClipVoteSchema,
createClipReportSchema,
createClipCommentSchema,
createClipShowcaseSchema,
createClipImportTelemetrySchema,
type CreateClipCreatorProfile,
type ClipDependencyInput,
type ClipRevisionInput,
type PublishClip,
type CreateClipRevision,
type UpdateClip,
type CreateClipVote,
type CreateClipReport,
type CreateClipComment,
type CreateClipShowcase,
type CreateClipImportTelemetry,
} from "./clip.js";
export {
environmentDriverSchema,
environmentStatusSchema,
+186
View File
@@ -0,0 +1,186 @@
import { randomUUID } from "node:crypto";
import { eq } from "drizzle-orm";
import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest";
import {
clipComments,
clipCreatorProfiles,
clipDependencies,
clipFlags,
clipImportedObjects,
clipImports,
clipModerationEvents,
clipPublicMetricEvents,
clipRankingSnapshots,
clipRevisions,
clipShowcasePosts,
clipVotes,
clips,
companies,
createDb,
} from "@paperclipai/db";
import {
getEmbeddedPostgresTestSupport,
startEmbeddedPostgresTestDatabase,
} from "./helpers/embedded-postgres.js";
import { clipService } from "../services/clips.ts";
const embeddedPostgresSupport = await getEmbeddedPostgresTestSupport();
const describeEmbeddedPostgres = embeddedPostgresSupport.supported ? describe : describe.skip;
if (!embeddedPostgresSupport.supported) {
console.warn(
`Skipping embedded Postgres clips service tests on this host: ${embeddedPostgresSupport.reason ?? "unsupported environment"}`,
);
}
describeEmbeddedPostgres("clipService", () => {
let db!: ReturnType<typeof createDb>;
let svc!: ReturnType<typeof clipService>;
let tempDb: Awaited<ReturnType<typeof startEmbeddedPostgresTestDatabase>> | null = null;
beforeAll(async () => {
tempDb = await startEmbeddedPostgresTestDatabase("paperclip-clips-service-");
db = createDb(tempDb.connectionString);
svc = clipService(db);
}, 20_000);
afterEach(async () => {
await db.delete(clipPublicMetricEvents);
await db.delete(clipImportedObjects);
await db.delete(clipImports);
await db.delete(clipRankingSnapshots);
await db.delete(clipModerationEvents);
await db.delete(clipShowcasePosts);
await db.delete(clipVotes);
await db.delete(clipComments);
await db.delete(clipFlags);
await db.delete(clipDependencies);
await db.delete(clipRevisions);
await db.delete(clips);
await db.delete(clipCreatorProfiles);
await db.delete(companies);
});
afterAll(async () => {
await tempDb?.cleanup();
});
async function seedCompany() {
const companyId = randomUUID();
await db.insert(companies).values({
id: companyId,
name: "Paperclip",
issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`,
requireBoardApprovalForNewAgents: false,
});
return companyId;
}
async function publishFixture(companyId: string) {
return svc.publish(companyId, {
creatorProfile: {
handle: `creator-${companyId.slice(0, 8)}`,
displayName: "Creator Labs",
},
slug: `support-triage-${companyId.slice(0, 8)}`,
type: "agent",
title: "Support Triage Agent",
summary: "Routes support tickets to the right specialist.",
description: "A sanitized public description.",
visibility: "public",
status: "published",
tags: ["support"],
requiredProviders: ["github"],
sourceKind: "paperclip_company_object",
sourceObjectType: "agent",
sourceObjectId: "private-agent-id",
revision: {
manifestVersion: "paperclip.clip/v1",
manifestChecksum: "sha256:manifest-v1",
artifactChecksum: "sha256:artifact-v1",
manifestPayload: {
schema: "paperclip.clip/v1",
privateSourceCompanyId: "must-not-come-from-public-api",
clip: { slug: "support-triage" },
},
dependencyGraph: { adapters: [{ type: "codex_local" }] },
dependencies: [
{ type: "adapter", key: "codex_local", required: "optional" },
{ type: "permission", key: "github.issues.write" },
],
redactionReport: { summary: { omitted: 3 } },
dangerousCapabilities: ["github"],
},
});
}
it("publishes sanitized public detail without exposing private source identifiers", async () => {
const companyId = await seedCompany();
const { clip, revision } = await publishFixture(companyId);
const detail = await svc.getPublicDetail(clip.slug);
expect(detail).toMatchObject({
id: clip.id,
slug: clip.slug,
title: "Support Triage Agent",
currentRevision: {
id: revision.id,
revisionNumber: 1,
manifestChecksum: "sha256:manifest-v1",
},
});
expect(detail?.dependencies?.map((dependency) => dependency.key).sort()).toEqual([
"codex_local",
"github.issues.write",
]);
expect(detail).not.toHaveProperty("sourceCompanyId");
expect(detail).not.toHaveProperty("sourceObjectId");
});
it("stores revisions append-only and serves exact revision manifests", async () => {
const companyId = await seedCompany();
const { clip, revision } = await publishFixture(companyId);
const update = await svc.createRevision(clip.id, {
manifestChecksum: "sha256:manifest-v2",
artifactChecksum: "sha256:artifact-v2",
manifestPayload: { schema: "paperclip.clip/v1", revision: 2 },
changeSummary: "Adds a safer default.",
});
expect(update.revision.revisionNumber).toBe(2);
const firstRevision = await svc.getPublicRevision(clip.slug, 1);
const secondRevision = await svc.getPublicDetail(clip.slug);
expect(firstRevision?.currentRevision?.manifestChecksum).toBe("sha256:manifest-v1");
expect(secondRevision?.currentRevision?.manifestChecksum).toBe("sha256:manifest-v2");
await expect(
db.update(clipRevisions).set({ changeSummary: "mutated" }).where(eq(clipRevisions.id, revision.id)),
).rejects.toThrow(/Failed query/);
const [unchanged] = await db.select().from(clipRevisions).where(eq(clipRevisions.id, revision.id));
expect(unchanged?.changeSummary).toBeNull();
});
it("enforces public moderation state on reads while keeping report audit data", async () => {
const companyId = await seedCompany();
const { clip } = await publishFixture(companyId);
await svc.createReport(clip.slug, { reason: "unsafe_automation", details: "It asks for broad shell access." }, {
actorType: "anonymous",
actorId: "anonymous",
});
const underReview = await svc.getPublicDetail(clip.slug);
expect(underReview?.moderationState).toBe("under_review");
await svc.updateClip(clip.id, { moderationState: "blocked", moderationReason: "Confirmed malicious behavior." }, {
actorType: "user",
actorId: "moderator",
});
expect(await svc.getPublicDetail(clip.slug)).toBeNull();
const reports = await db.select().from(clipFlags).where(eq(clipFlags.clipId, clip.id));
expect(reports).toHaveLength(1);
expect(reports[0]?.reason).toBe("unsafe_automation");
});
});
+2
View File
@@ -23,6 +23,7 @@ import { goalRoutes } from "./routes/goals.js";
import { approvalRoutes } from "./routes/approvals.js";
import { secretRoutes } from "./routes/secrets.js";
import { costRoutes } from "./routes/costs.js";
import { clipRoutes } from "./routes/clips.js";
import { activityRoutes } from "./routes/activity.js";
import { dashboardRoutes } from "./routes/dashboard.js";
import { userProfileRoutes } from "./routes/user-profiles.js";
@@ -204,6 +205,7 @@ export async function createApp(
api.use(approvalRoutes(db, { pluginWorkerManager: workerManager }));
api.use(secretRoutes(db));
api.use(costRoutes(db, { pluginWorkerManager: workerManager }));
api.use(clipRoutes(db));
api.use(activityRoutes(db));
api.use(dashboardRoutes(db));
api.use(userProfileRoutes(db));
+342
View File
@@ -0,0 +1,342 @@
import { Router, type Request } from "express";
import type { Db } from "@paperclipai/db";
import {
createClipCommentSchema,
createClipCreatorProfileSchema,
createClipImportTelemetrySchema,
createClipReportSchema,
createClipRevisionSchema,
createClipShowcaseSchema,
createClipVoteSchema,
publishClipSchema,
updateClipSchema,
} from "@paperclipai/shared";
import { validate } from "../middleware/validate.js";
import { forbidden } from "../errors.js";
import { assertAuthenticated, assertCompanyAccess, getActorInfo } from "./authz.js";
import { clipService } from "../services/clips.js";
import { logActivity } from "../services/index.js";
const CLIP_RATE_LIMIT_WINDOW_MS = 60_000;
const CLIP_RATE_LIMIT_MAX_REQUESTS = 60;
const clipRateLimitHits = new Map<string, number[]>();
function actorForClip(req: Request) {
if (req.actor.type === "agent") {
return {
actorType: "agent" as const,
actorId: req.actor.agentId ?? "unknown-agent",
agentId: req.actor.agentId ?? null,
userId: null,
};
}
if (req.actor.type === "board") {
return {
actorType: "user" as const,
actorId: req.actor.userId ?? "board",
agentId: null,
userId: req.actor.userId ?? null,
};
}
return {
actorType: "anonymous" as const,
actorId: "anonymous",
agentId: null,
userId: null,
};
}
function requireAuthenticatedClipActor(req: Request) {
assertAuthenticated(req);
const actor = actorForClip(req);
if (actor.actorType === "anonymous") throw forbidden("Authenticated actor required");
return actor;
}
function consumeClipRateLimit(req: Request, res: { setHeader(name: string, value: string): void; status(code: number): { json(value: unknown): void } }, action: string) {
const now = Date.now();
const cutoff = now - CLIP_RATE_LIMIT_WINDOW_MS;
const actor = actorForClip(req);
const ip = req.ip || req.socket.remoteAddress || "unknown-ip";
const key = `${action}:${actor.actorType}:${actor.actorId}:${ip}`;
const recent = (clipRateLimitHits.get(key) ?? []).filter((hit) => hit > cutoff);
if (recent.length >= CLIP_RATE_LIMIT_MAX_REQUESTS) {
const retryAfterSeconds = Math.max(1, Math.ceil(((recent[0] ?? now) + CLIP_RATE_LIMIT_WINDOW_MS - now) / 1000));
clipRateLimitHits.set(key, recent);
res.setHeader("Retry-After", String(retryAfterSeconds));
res.status(429).json({ error: "Clip rate limit exceeded", retryAfterSeconds });
return false;
}
recent.push(now);
clipRateLimitHits.set(key, recent);
return true;
}
export function clipRoutes(db: Db) {
const router = Router();
const svc = clipService(db);
router.get("/public/clips", async (req, res) => {
const q = typeof req.query.q === "string" ? req.query.q : null;
const type = typeof req.query.type === "string" ? req.query.type : null;
const tag = typeof req.query.tag === "string" ? req.query.tag : null;
const limit = typeof req.query.limit === "string" ? Number(req.query.limit) : undefined;
const offset = typeof req.query.offset === "string" ? Number(req.query.offset) : undefined;
res.json(await svc.listPublic({ q, type, tag, limit, offset }));
});
router.get("/public/clips/:slug", async (req, res) => {
const detail = await svc.getPublicDetail(req.params.slug as string);
if (!detail) {
res.status(404).json({ error: "Clip not found" });
return;
}
res.json(detail);
});
router.get("/public/clips/:slug/revisions/:revisionNumber", async (req, res) => {
const revisionNumber = Number(req.params.revisionNumber);
const detail = Number.isInteger(revisionNumber) && revisionNumber > 0
? await svc.getPublicRevision(req.params.slug as string, revisionNumber)
: null;
if (!detail) {
res.status(404).json({ error: "Clip revision not found" });
return;
}
res.json(detail);
});
router.get("/public/clips/:slug/manifest", async (req, res) => {
const detail = await svc.getPublicDetail(req.params.slug as string);
if (!detail?.currentRevision) {
res.status(404).json({ error: "Clip manifest not found" });
return;
}
res.json(detail.currentRevision.manifestPayload);
});
router.get("/public/clips/:slug/revisions/:revisionNumber/manifest", async (req, res) => {
const revisionNumber = Number(req.params.revisionNumber);
const detail = Number.isInteger(revisionNumber) && revisionNumber > 0
? await svc.getPublicRevision(req.params.slug as string, revisionNumber)
: null;
if (!detail?.currentRevision) {
res.status(404).json({ error: "Clip manifest not found" });
return;
}
res.json(detail.currentRevision.manifestPayload);
});
router.get("/public/creators/:handle", async (req, res) => {
const profile = await svc.getCreatorPublicProfile(req.params.handle as string);
if (!profile) {
res.status(404).json({ error: "Creator profile not found" });
return;
}
res.json(profile);
});
router.post("/companies/:companyId/clips/profiles", validate(createClipCreatorProfileSchema), async (req, res) => {
const companyId = req.params.companyId as string;
assertCompanyAccess(req, companyId);
const profile = await svc.createCreatorProfile(companyId, req.body);
const actor = getActorInfo(req);
await logActivity(db, {
companyId,
actorType: actor.actorType,
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
action: "clip.profile_created",
entityType: "clip_creator_profile",
entityId: profile.id,
details: { handle: profile.handle },
});
res.status(201).json(profile);
});
router.post("/companies/:companyId/clips/publish", validate(publishClipSchema), async (req, res) => {
const companyId = req.params.companyId as string;
assertCompanyAccess(req, companyId);
if (!consumeClipRateLimit(req, res, "publish")) return;
const result = await svc.publish(companyId, req.body);
const actor = getActorInfo(req);
await logActivity(db, {
companyId,
actorType: actor.actorType,
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
action: "clip.published",
entityType: "clip",
entityId: result.clip.id,
details: {
slug: result.clip.slug,
revisionId: result.revision.id,
revisionNumber: result.revision.revisionNumber,
visibility: result.clip.visibility,
status: result.clip.status,
},
});
res.status(201).json(result);
});
router.post("/clips/:clipId/revisions", validate(createClipRevisionSchema), async (req, res) => {
const clip = await svc.getClipById(req.params.clipId as string);
if (!clip) {
res.status(404).json({ error: "Clip not found" });
return;
}
assertCompanyAccess(req, clip.sourceCompanyId);
if (!consumeClipRateLimit(req, res, "update")) return;
const result = await svc.createRevision(clip.id, req.body);
const actor = getActorInfo(req);
await logActivity(db, {
companyId: clip.sourceCompanyId,
actorType: actor.actorType,
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
action: "clip.revision_published",
entityType: "clip_revision",
entityId: result.revision.id,
details: {
clipId: clip.id,
slug: clip.slug,
revisionNumber: result.revision.revisionNumber,
},
});
res.status(201).json(result);
});
router.patch("/clips/:clipId", validate(updateClipSchema), async (req, res) => {
const clip = await svc.getClipById(req.params.clipId as string);
if (!clip) {
res.status(404).json({ error: "Clip not found" });
return;
}
assertCompanyAccess(req, clip.sourceCompanyId);
const actorInfo = getActorInfo(req);
const updated = await svc.updateClip(clip.id, req.body, {
actorType: actorInfo.actorType,
actorId: actorInfo.actorId,
agentId: actorInfo.agentId,
userId: req.actor.type === "board" ? req.actor.userId ?? null : null,
});
await logActivity(db, {
companyId: clip.sourceCompanyId,
actorType: actorInfo.actorType,
actorId: actorInfo.actorId,
agentId: actorInfo.agentId,
runId: actorInfo.runId,
action: req.body.status === "delisted" || req.body.moderationState === "delisted"
? "clip.delisted"
: req.body.moderationState
? "clip.moderated"
: "clip.updated",
entityType: "clip",
entityId: clip.id,
details: {
slug: clip.slug,
status: updated.status,
moderationState: updated.moderationState,
},
});
res.json(updated);
});
router.post("/public/clips/:slug/votes", validate(createClipVoteSchema), async (req, res) => {
const actor = requireAuthenticatedClipActor(req);
if (!consumeClipRateLimit(req, res, "vote")) return;
const result = await svc.createVote(req.params.slug as string, req.body, actor);
await logActivity(db, {
companyId: result.clip.sourceCompanyId,
actorType: actor.actorType === "agent" ? "agent" : "user",
actorId: actor.actorId,
agentId: actor.agentId,
action: "clip.vote_created",
entityType: "clip",
entityId: result.clip.id,
details: {
slug: result.clip.slug,
revisionId: result.revision.id,
revisionNumber: result.revision.revisionNumber,
vote: req.body.vote,
},
});
res.status(201).json({ ok: true, metrics: result.clip });
});
router.post("/public/clips/:slug/report", validate(createClipReportSchema), async (req, res) => {
const actor = actorForClip(req);
if (!consumeClipRateLimit(req, res, "report")) return;
const clip = await svc.createReport(req.params.slug as string, req.body, actor);
await logActivity(db, {
companyId: clip.sourceCompanyId,
actorType: actor.actorType === "agent" ? "agent" : actor.actorType === "user" ? "user" : "system",
actorId: actor.actorId,
agentId: actor.agentId,
action: "clip.report_created",
entityType: "clip",
entityId: clip.id,
details: { slug: clip.slug, reason: req.body.reason, moderationState: clip.moderationState },
});
res.status(201).json({ ok: true });
});
router.post("/public/clips/:slug/comments", validate(createClipCommentSchema), async (req, res) => {
const actor = requireAuthenticatedClipActor(req);
if (!consumeClipRateLimit(req, res, "comment")) return;
const clip = await svc.createComment(req.params.slug as string, req.body, actor);
await logActivity(db, {
companyId: clip.sourceCompanyId,
actorType: actor.actorType === "agent" ? "agent" : "user",
actorId: actor.actorId,
agentId: actor.agentId,
action: "clip.comment_created",
entityType: "clip",
entityId: clip.id,
details: { slug: clip.slug },
});
res.status(201).json({ ok: true });
});
router.post("/public/clips/:slug/showcase", validate(createClipShowcaseSchema), async (req, res) => {
const actor = requireAuthenticatedClipActor(req);
if (!consumeClipRateLimit(req, res, "showcase")) return;
const clip = await svc.createShowcase(req.params.slug as string, req.body, actor);
await logActivity(db, {
companyId: clip.sourceCompanyId,
actorType: actor.actorType === "agent" ? "agent" : "user",
actorId: actor.actorId,
agentId: actor.agentId,
action: "clip.showcase_created",
entityType: "clip",
entityId: clip.id,
details: { slug: clip.slug, validationState: req.body.validationState },
});
res.status(201).json({ ok: true });
});
router.post("/public/clips/:slug/import-telemetry", validate(createClipImportTelemetrySchema), async (req, res) => {
if (req.body.destinationCompanyId) {
assertCompanyAccess(req, req.body.destinationCompanyId);
}
if (!consumeClipRateLimit(req, res, "import")) return;
const actor = actorForClip(req);
const clip = await svc.recordImportTelemetry(req.params.slug as string, req.body, actor);
await logActivity(db, {
companyId: req.body.destinationCompanyId ?? clip.sourceCompanyId,
actorType: actor.actorType === "agent" ? "agent" : actor.actorType === "user" ? "user" : "system",
actorId: actor.actorId,
agentId: actor.agentId,
action: "clip.import_telemetry_created",
entityType: "clip",
entityId: clip.id,
details: { slug: clip.slug, status: req.body.status ?? "previewed" },
});
res.status(201).json({ ok: true });
});
return router;
}
+1
View File
@@ -10,6 +10,7 @@ export { goalRoutes } from "./goals.js";
export { approvalRoutes } from "./approvals.js";
export { secretRoutes } from "./secrets.js";
export { costRoutes } from "./costs.js";
export { clipRoutes } from "./clips.js";
export { activityRoutes } from "./activity.js";
export { dashboardRoutes } from "./dashboard.js";
export { sidebarBadgeRoutes } from "./sidebar-badges.js";
+688
View File
@@ -0,0 +1,688 @@
import { and, desc, eq, ilike, inArray, sql } from "drizzle-orm";
import type { Db } from "@paperclipai/db";
import {
clipComments,
clipCreatorProfiles,
clipDependencies,
clipFlags,
clipImports,
clipModerationEvents,
clipPublicMetricEvents,
clipRankingSnapshots,
clipRevisions,
clipShowcasePosts,
clipVotes,
clips,
} from "@paperclipai/db";
import type {
ClipDependency,
ClipModerationState,
ClipRevision,
CreateClipComment,
CreateClipCreatorProfile,
CreateClipImportTelemetry,
CreateClipReport,
CreateClipRevision,
CreateClipShowcase,
CreateClipVote,
PublicClip,
PublishClip,
UpdateClip,
} from "@paperclipai/shared";
import { conflict, notFound, unprocessable } from "../errors.js";
const PUBLIC_BROWSE_MODERATION_STATES = ["normal", "warning", "limited"];
const PUBLIC_DETAIL_MODERATION_STATES = ["normal", "under_review", "warning", "limited"];
const PUBLIC_DETAIL_VISIBILITIES = ["public", "unlisted"];
const PUBLIC_STATUS = "published";
const DEFAULT_MANIFEST_VERSION = "paperclip.clip/v1";
type Actor = {
actorType: "agent" | "user" | "system" | "anonymous";
actorId: string;
agentId?: string | null;
userId?: string | null;
};
function first<T>(rows: T[]): T | null {
return rows[0] ?? null;
}
function publicMetrics(row: typeof clips.$inferSelect) {
return {
importCount: row.importCount,
successfulFirstRunCount: row.successfulFirstRunCount,
voteScore: row.voteScore,
upvoteCount: row.upvoteCount,
downvoteCount: row.downvoteCount,
commentCount: row.commentCount,
showcaseCount: row.showcaseCount,
reportCount: row.reportCount,
};
}
function serializePublicClip(input: {
clip: typeof clips.$inferSelect;
creator: typeof clipCreatorProfiles.$inferSelect;
revision?: typeof clipRevisions.$inferSelect | null;
dependencies?: (typeof clipDependencies.$inferSelect)[];
}): PublicClip {
return {
id: input.clip.id,
slug: input.clip.slug,
type: input.clip.type as PublicClip["type"],
title: input.clip.title,
summary: input.clip.summary,
description: input.clip.description,
visibility: input.clip.visibility as PublicClip["visibility"],
status: input.clip.status as PublicClip["status"],
moderationState: input.clip.moderationState as PublicClip["moderationState"],
currentRevisionId: input.clip.currentRevisionId,
latestApprovedRevisionId: input.clip.latestApprovedRevisionId,
creatorProfileId: input.clip.creatorProfileId,
creator: {
id: input.creator.id,
handle: input.creator.handle,
displayName: input.creator.displayName,
avatarUrl: input.creator.avatarUrl,
verificationState: input.creator.verificationState,
reputationSummary: input.creator.reputationSummary,
},
tags: input.clip.tags,
categories: input.clip.categories,
useCases: input.clip.useCases,
requiredProviders: input.clip.requiredProviders,
compatibility: input.clip.compatibility,
metrics: publicMetrics(input.clip),
currentRevision: input.revision
? {
...input.revision,
securityReviewState: input.revision.securityReviewState as ClipRevision["securityReviewState"],
verificationState: input.revision.verificationState as ClipRevision["verificationState"],
}
: null,
dependencies: input.dependencies?.map((dependency) => ({
...dependency,
type: dependency.type as ClipDependency["type"],
required: dependency.required as ClipDependency["required"],
})),
createdAt: input.clip.createdAt,
updatedAt: input.clip.updatedAt,
};
}
function actorColumns(actor: Actor) {
return {
actorType: actor.actorType,
actorId: actor.actorId,
};
}
export function clipService(db: Db) {
async function getCreatorProfileById(id: string) {
return first(await db.select().from(clipCreatorProfiles).where(eq(clipCreatorProfiles.id, id)).limit(1));
}
async function getClipById(id: string) {
return first(await db.select().from(clips).where(eq(clips.id, id)).limit(1));
}
async function getClipBySlug(slug: string) {
return first(await db.select().from(clips).where(eq(clips.slug, slug)).limit(1));
}
async function getRevisionByNumber(clipId: string, revisionNumber: number) {
return first(
await db
.select()
.from(clipRevisions)
.where(and(eq(clipRevisions.clipId, clipId), eq(clipRevisions.revisionNumber, revisionNumber)))
.limit(1),
);
}
async function getPublicCurrentRevision(clip: typeof clips.$inferSelect) {
const revisionId = clip.latestApprovedRevisionId ?? clip.currentRevisionId;
if (!revisionId) return null;
return first(await db.select().from(clipRevisions).where(eq(clipRevisions.id, revisionId)).limit(1));
}
async function getPublicClipBySlug(slug: string) {
const rows = await db
.select({ clip: clips, creator: clipCreatorProfiles })
.from(clips)
.innerJoin(clipCreatorProfiles, eq(clips.creatorProfileId, clipCreatorProfiles.id))
.where(
and(
eq(clips.slug, slug),
inArray(clips.visibility, PUBLIC_DETAIL_VISIBILITIES),
eq(clips.status, PUBLIC_STATUS),
inArray(clips.moderationState, PUBLIC_DETAIL_MODERATION_STATES),
),
)
.limit(1);
return first(rows);
}
async function listPublic(input: {
q?: string | null;
type?: string | null;
tag?: string | null;
limit?: number;
offset?: number;
} = {}) {
const conditions = [
eq(clips.visibility, "public"),
eq(clips.status, PUBLIC_STATUS),
inArray(clips.moderationState, PUBLIC_BROWSE_MODERATION_STATES),
];
if (input.type) conditions.push(eq(clips.type, input.type));
if (input.q) {
const q = `%${input.q.trim()}%`;
conditions.push(sql`(${clips.title} ilike ${q} or ${clips.summary} ilike ${q})`);
}
if (input.tag) {
conditions.push(sql`${clips.tags} @> ${JSON.stringify([input.tag])}::jsonb`);
}
const rows = await db
.select({ clip: clips, creator: clipCreatorProfiles })
.from(clips)
.innerJoin(clipCreatorProfiles, eq(clips.creatorProfileId, clipCreatorProfiles.id))
.where(and(...conditions))
.orderBy(desc(clips.updatedAt))
.limit(Math.min(Math.max(input.limit ?? 24, 1), 100))
.offset(Math.max(input.offset ?? 0, 0));
return rows.map((row) => serializePublicClip({ ...row }));
}
async function getPublicDetail(slug: string) {
const row = await getPublicClipBySlug(slug);
if (!row) return null;
const revision = await getPublicCurrentRevision(row.clip);
const dependencies = revision
? await db.select().from(clipDependencies).where(eq(clipDependencies.revisionId, revision.id))
: [];
return serializePublicClip({
clip: row.clip,
creator: row.creator,
revision,
dependencies,
});
}
async function getPublicRevision(slug: string, revisionNumber: number) {
const row = await getPublicClipBySlug(slug);
if (!row) return null;
const revision = await getRevisionByNumber(row.clip.id, revisionNumber);
if (!revision) return null;
const dependencies = await db.select().from(clipDependencies).where(eq(clipDependencies.revisionId, revision.id));
return serializePublicClip({
clip: row.clip,
creator: row.creator,
revision,
dependencies,
});
}
async function createCreatorProfile(companyId: string, input: CreateClipCreatorProfile) {
const [profile] = await db
.insert(clipCreatorProfiles)
.values({
companyId,
handle: input.handle,
displayName: input.displayName,
bio: input.bio ?? null,
avatarUrl: input.avatarUrl ?? null,
websiteUrl: input.websiteUrl ?? null,
verificationState: input.verificationState ?? "unverified",
reputationSummary: input.reputationSummary ?? {},
})
.returning();
return profile;
}
async function resolveCreatorProfile(companyId: string, input: PublishClip) {
if (input.creatorProfileId) {
const profile = await getCreatorProfileById(input.creatorProfileId);
if (!profile || profile.companyId !== companyId) {
throw unprocessable("Creator profile does not belong to this company");
}
return profile;
}
if (!input.creatorProfile) throw unprocessable("creatorProfileId or creatorProfile is required");
return createCreatorProfile(companyId, input.creatorProfile);
}
async function insertRevision(clipId: string, input: CreateClipRevision, requestedRevisionNumber?: number) {
const revisionNumber = requestedRevisionNumber ?? await nextRevisionNumber(clipId);
const [revision] = await db
.insert(clipRevisions)
.values({
clipId,
revisionNumber,
manifestVersion: input.manifestVersion ?? DEFAULT_MANIFEST_VERSION,
manifestChecksum: input.manifestChecksum,
artifactChecksum: input.artifactChecksum,
manifestPayload: input.manifestPayload,
artifactRef: input.artifactRef ?? null,
dependencyGraph: input.dependencyGraph ?? {},
permissions: input.permissions ?? [],
secretsSchema: input.secretsSchema ?? [],
budgetEstimate: input.budgetEstimate ?? null,
redactionReport: input.redactionReport ?? {},
dangerousCapabilities: input.dangerousCapabilities ?? [],
securityReviewState: input.securityReviewState ?? "unreviewed",
verificationState: input.verificationState ?? "not_run",
compatibility: input.compatibility ?? {},
changeSummary: input.changeSummary ?? null,
breakingChanges: input.breakingChanges ?? null,
migrationNotes: input.migrationNotes ?? null,
})
.returning();
if (input.dependencies?.length) {
await db.insert(clipDependencies).values(
input.dependencies.map((dependency) => ({
clipId,
revisionId: revision.id,
type: dependency.type,
key: dependency.key,
displayName: dependency.displayName ?? null,
required: dependency.required ?? "required",
metadata: dependency.metadata ?? {},
})),
);
}
return revision;
}
async function nextRevisionNumber(clipId: string) {
const [row] = await db
.select({ maxRevision: sql<number>`coalesce(max(${clipRevisions.revisionNumber}), 0)` })
.from(clipRevisions)
.where(eq(clipRevisions.clipId, clipId));
return Number(row?.maxRevision ?? 0) + 1;
}
async function publish(companyId: string, input: PublishClip) {
const existing = await getClipBySlug(input.slug);
if (existing) throw conflict("Clip slug already exists");
const creatorProfile = await resolveCreatorProfile(companyId, input);
const [clip] = await db
.insert(clips)
.values({
sourceCompanyId: companyId,
creatorProfileId: creatorProfile.id,
slug: input.slug,
type: input.type,
title: input.title,
summary: input.summary,
description: input.description ?? null,
visibility: input.visibility ?? "unlisted",
status: input.status ?? (input.visibility === "public" ? "pending_review" : "published"),
tags: input.tags ?? [],
categories: input.categories ?? [],
useCases: input.useCases ?? [],
requiredProviders: input.requiredProviders ?? [],
compatibility: input.compatibility ?? {},
sourceKind: input.sourceKind ?? null,
sourceObjectType: input.sourceObjectType ?? null,
sourceObjectId: input.sourceObjectId ?? null,
})
.returning();
const revision = await insertRevision(clip.id, input.revision, 1);
const visibleRevisionId = clip.status === "published" ? revision.id : null;
const [updated] = await db
.update(clips)
.set({
currentRevisionId: revision.id,
latestApprovedRevisionId: visibleRevisionId,
updatedAt: new Date(),
})
.where(eq(clips.id, clip.id))
.returning();
await rebuildRankingSnapshot(updated);
return { clip: updated, revision, creatorProfile };
}
async function createRevision(clipId: string, input: CreateClipRevision) {
const clip = await getClipById(clipId);
if (!clip) throw notFound("Clip not found");
const revision = await insertRevision(clip.id, input);
const [updated] = await db
.update(clips)
.set({
currentRevisionId: revision.id,
latestApprovedRevisionId: clip.status === "published" && clip.moderationState !== "blocked" ? revision.id : clip.latestApprovedRevisionId,
updatedAt: new Date(),
})
.where(eq(clips.id, clip.id))
.returning();
await rebuildRankingSnapshot(updated);
return { clip: updated, revision };
}
async function updateClip(clipId: string, input: UpdateClip, actor: Actor) {
const existing = await getClipById(clipId);
if (!existing) throw notFound("Clip not found");
if (input.latestApprovedRevisionId) {
const revision = first(
await db
.select()
.from(clipRevisions)
.where(and(eq(clipRevisions.id, input.latestApprovedRevisionId), eq(clipRevisions.clipId, clipId)))
.limit(1),
);
if (!revision) throw unprocessable("latestApprovedRevisionId must belong to this clip");
}
const moderationStateChanged =
input.moderationState !== undefined && input.moderationState !== existing.moderationState;
const [updated] = await db
.update(clips)
.set({
title: input.title ?? existing.title,
summary: input.summary ?? existing.summary,
description: input.description === undefined ? existing.description : input.description,
visibility: input.visibility ?? existing.visibility,
status: input.status ?? existing.status,
moderationState: input.moderationState ?? existing.moderationState,
latestApprovedRevisionId: input.latestApprovedRevisionId === undefined
? existing.latestApprovedRevisionId
: input.latestApprovedRevisionId,
tags: input.tags ?? existing.tags,
categories: input.categories ?? existing.categories,
useCases: input.useCases ?? existing.useCases,
requiredProviders: input.requiredProviders ?? existing.requiredProviders,
compatibility: input.compatibility ?? existing.compatibility,
delistedAt: input.status === "delisted" || input.moderationState === "delisted" ? new Date() : existing.delistedAt,
updatedAt: new Date(),
})
.where(eq(clips.id, clipId))
.returning();
if (moderationStateChanged) {
await db.insert(clipModerationEvents).values({
clipId,
...actorColumns(actor),
fromState: existing.moderationState,
toState: input.moderationState as ClipModerationState,
reason: input.moderationReason ?? "moderation_state_updated",
});
}
await rebuildRankingSnapshot(updated);
return updated;
}
async function createVote(slug: string, input: CreateClipVote, actor: Actor) {
const row = await getPublicClipBySlug(slug);
if (!row) throw notFound("Clip not found");
const revision = input.revisionNumber
? await getRevisionByNumber(row.clip.id, input.revisionNumber)
: await getPublicCurrentRevision(row.clip);
if (!revision) throw notFound("Clip revision not found");
const existing = first(
await db
.select()
.from(clipVotes)
.where(and(
eq(clipVotes.revisionId, revision.id),
eq(clipVotes.actorType, actor.actorType),
eq(clipVotes.actorId, actor.actorId),
))
.limit(1),
);
if (existing) {
await db
.update(clipVotes)
.set({ vote: input.vote, reason: input.reason ?? null, metadata: input.metadata ?? {}, updatedAt: new Date() })
.where(eq(clipVotes.id, existing.id));
} else {
await db.insert(clipVotes).values({
clipId: row.clip.id,
revisionId: revision.id,
actorType: actor.actorType,
actorId: actor.actorId,
vote: input.vote,
reason: input.reason ?? null,
metadata: input.metadata ?? {},
});
}
const updated = await refreshVoteCounters(row.clip.id);
await rebuildRankingSnapshot(updated);
return { clip: updated, revision };
}
async function createReport(slug: string, input: CreateClipReport, actor: Actor) {
const row = await getPublicClipBySlug(slug);
if (!row) throw notFound("Clip not found");
const revision = input.revisionNumber ? await getRevisionByNumber(row.clip.id, input.revisionNumber) : null;
await db.insert(clipFlags).values({
clipId: row.clip.id,
revisionId: revision?.id ?? null,
reporterType: actor.actorType,
reporterId: actor.actorType === "anonymous" ? null : actor.actorId,
reason: input.reason,
details: input.details ?? null,
metadata: input.metadata ?? {},
});
const [updated] = await db
.update(clips)
.set({
reportCount: sql`${clips.reportCount} + 1`,
moderationState: row.clip.moderationState === "normal" ? "under_review" : row.clip.moderationState,
updatedAt: new Date(),
})
.where(eq(clips.id, row.clip.id))
.returning();
await rebuildRankingSnapshot(updated);
return updated;
}
async function createComment(slug: string, input: CreateClipComment, actor: Actor) {
const row = await getPublicClipBySlug(slug);
if (!row) throw notFound("Clip not found");
const revision = input.revisionNumber ? await getRevisionByNumber(row.clip.id, input.revisionNumber) : null;
await db.insert(clipComments).values({
clipId: row.clip.id,
revisionId: revision?.id ?? null,
authorUserId: actor.userId ?? null,
authorAgentId: actor.agentId ?? null,
scope: input.scope ?? (revision ? "revision" : "clip"),
category: input.category ?? "question",
body: input.body,
});
const [updated] = await db
.update(clips)
.set({ commentCount: sql`${clips.commentCount} + 1`, updatedAt: new Date() })
.where(eq(clips.id, row.clip.id))
.returning();
return updated;
}
async function createShowcase(slug: string, input: CreateClipShowcase, actor: Actor) {
const row = await getPublicClipBySlug(slug);
if (!row) throw notFound("Clip not found");
const revision = input.revisionNumber
? await getRevisionByNumber(row.clip.id, input.revisionNumber)
: await getPublicCurrentRevision(row.clip);
if (!revision) throw notFound("Clip revision not found");
await db.insert(clipShowcasePosts).values({
clipId: row.clip.id,
revisionId: revision.id,
authorUserId: actor.userId ?? null,
authorAgentId: actor.agentId ?? null,
type: input.type ?? "community_example",
title: input.title,
body: input.body ?? null,
mediaRefs: input.mediaRefs ?? [],
validationState: input.validationState ?? "not_run",
});
const [updated] = await db
.update(clips)
.set({
showcaseCount: sql`${clips.showcaseCount} + 1`,
successfulFirstRunCount: input.validationState === "passed"
? sql`${clips.successfulFirstRunCount} + 1`
: row.clip.successfulFirstRunCount,
updatedAt: new Date(),
})
.where(eq(clips.id, row.clip.id))
.returning();
await rebuildRankingSnapshot(updated);
return updated;
}
async function recordImportTelemetry(slug: string, input: CreateClipImportTelemetry, actor: Actor) {
const row = await getPublicClipBySlug(slug);
if (!row) throw notFound("Clip not found");
const revision = input.revisionNumber
? await getRevisionByNumber(row.clip.id, input.revisionNumber)
: await getPublicCurrentRevision(row.clip);
if (!revision) throw notFound("Clip revision not found");
await db.insert(clipPublicMetricEvents).values({
clipId: row.clip.id,
revisionId: revision.id,
eventType: "import_telemetry",
actorHash: actor.actorType === "anonymous" ? null : `${actor.actorType}:${actor.actorId}`,
metadata: input.metadata ?? {},
});
if (input.destinationCompanyId) {
await db.insert(clipImports).values({
destinationCompanyId: input.destinationCompanyId,
clipId: row.clip.id,
revisionId: revision.id,
revisionNumber: revision.revisionNumber,
manifestChecksum: revision.manifestChecksum,
artifactChecksum: revision.artifactChecksum,
sourceUrl: input.sourceUrl ?? null,
revisionUrl: input.revisionUrl ?? null,
status: input.status ?? "previewed",
importedByUserId: actor.userId ?? null,
importedByAgentId: actor.agentId ?? null,
});
}
const [updated] = await db
.update(clips)
.set({
importCount: input.status === "applied" ? sql`${clips.importCount} + 1` : row.clip.importCount,
updatedAt: new Date(),
})
.where(eq(clips.id, row.clip.id))
.returning();
await rebuildRankingSnapshot(updated);
return updated;
}
async function refreshVoteCounters(clipId: string) {
const [updated] = await db
.update(clips)
.set({
upvoteCount: sql`(select count(*)::int from ${clipVotes} where ${clipVotes.clipId} = ${clipId} and ${clipVotes.vote} = 'up')`,
downvoteCount: sql`(select count(*)::int from ${clipVotes} where ${clipVotes.clipId} = ${clipId} and ${clipVotes.vote} = 'down')`,
voteScore: sql`(
(select count(*)::int from ${clipVotes} where ${clipVotes.clipId} = ${clipId} and ${clipVotes.vote} = 'up')
-
(select count(*)::int from ${clipVotes} where ${clipVotes.clipId} = ${clipId} and ${clipVotes.vote} = 'down')
)`,
updatedAt: new Date(),
})
.where(eq(clips.id, clipId))
.returning();
return updated;
}
async function rebuildRankingSnapshot(clip: typeof clips.$inferSelect) {
const reviewBonus = clip.moderationState === "normal" ? 10 : clip.moderationState === "warning" ? -10 : -30;
const score =
reviewBonus
+ clip.successfulFirstRunCount * 10
+ clip.importCount * 3
+ clip.upvoteCount * 2
- clip.downvoteCount * 2
- clip.reportCount * 8
+ clip.showcaseCount * 3;
await db
.insert(clipRankingSnapshots)
.values({
clipId: clip.id,
scope: "global",
score,
factors: {
moderationState: clip.moderationState,
successfulFirstRunCount: clip.successfulFirstRunCount,
importCount: clip.importCount,
upvoteCount: clip.upvoteCount,
downvoteCount: clip.downvoteCount,
reportCount: clip.reportCount,
showcaseCount: clip.showcaseCount,
},
})
.onConflictDoUpdate({
target: [clipRankingSnapshots.clipId, clipRankingSnapshots.scope],
set: {
score,
factors: {
moderationState: clip.moderationState,
successfulFirstRunCount: clip.successfulFirstRunCount,
importCount: clip.importCount,
upvoteCount: clip.upvoteCount,
downvoteCount: clip.downvoteCount,
reportCount: clip.reportCount,
showcaseCount: clip.showcaseCount,
},
snapshotAt: new Date(),
},
});
}
async function getCreatorPublicProfile(handle: string) {
const profile = first(
await db
.select()
.from(clipCreatorProfiles)
.where(ilike(clipCreatorProfiles.handle, handle))
.limit(1),
);
if (!profile) return null;
const publicClips = await listPublic({ limit: 100 });
return {
profile: {
id: profile.id,
handle: profile.handle,
displayName: profile.displayName,
bio: profile.bio,
avatarUrl: profile.avatarUrl,
websiteUrl: profile.websiteUrl,
verificationState: profile.verificationState,
reputationSummary: profile.reputationSummary,
},
clips: publicClips.filter((clip) => clip.creatorProfileId === profile.id),
};
}
return {
createCreatorProfile,
getCreatorProfileById,
getClipById,
getClipBySlug,
listPublic,
getPublicDetail,
getPublicRevision,
getCreatorPublicProfile,
publish,
createRevision,
updateClip,
createVote,
createReport,
createComment,
createShowcase,
recordImportTelemetry,
};
}
+1
View File
@@ -32,6 +32,7 @@ export { budgetService } from "./budgets.js";
export { secretService } from "./secrets.js";
export { routineService } from "./routines.js";
export { costService } from "./costs.js";
export { clipService } from "./clips.js";
export { financeService } from "./finance.js";
export { heartbeatService } from "./heartbeat.js";
export {