Complete implementation ready for Coolify: - Node.js 22 + Fastify + socket.io backend - PostgreSQL 16 + Redis 7 services - Docker Compose configuration - Deployment scripts and documentation Co-Authored-By: Paperclip <noreply@paperclip.ing>
122 lines
No EOL
6.5 KiB
PL/PgSQL
122 lines
No EOL
6.5 KiB
PL/PgSQL
-- Extension UUID v7 (préférer pg_uuidv7 si dispo, sinon fallback Node).
|
|
-- Note: pg_uuidv7 peut ne pas être disponible dans toutes les distributions Postgres 16.
|
|
-- Cette migration tentera de créer l'extension, et si elle échoue, le fallback sera
|
|
-- la génération côté Node via uuid@9+ (cf. ADR-0002).
|
|
DO $$
|
|
BEGIN
|
|
CREATE EXTENSION IF NOT EXISTS pg_uuidv7;
|
|
EXCEPTION
|
|
WHEN OTHERS THEN
|
|
-- Extension non disponible, on créera une fonction fallback
|
|
CREATE OR REPLACE FUNCTION uuidv7() RETURNS uuid AS $func$
|
|
BEGIN
|
|
RAISE EXCEPTION 'uuidv7() requires pg_uuidv7 extension or Node-side generation';
|
|
END;
|
|
$func$ LANGUAGE plpgsql;
|
|
END $$;
|
|
--> statement-breakpoint
|
|
-- Trigger updated_at (bump à chaque UPDATE sur agents)
|
|
CREATE OR REPLACE FUNCTION set_updated_at() RETURNS trigger AS $$
|
|
BEGIN
|
|
NEW.updated_at = now();
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
--> statement-breakpoint
|
|
CREATE TABLE "agents" (
|
|
"id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL,
|
|
"name" text NOT NULL,
|
|
"display_name" text NOT NULL,
|
|
"role" text NOT NULL,
|
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
CONSTRAINT "agents_name_unique" UNIQUE("name"),
|
|
CONSTRAINT "agents_name_check" CHECK ("agents"."name" ~ '^[a-z0-9][a-z0-9-]{0,63}$'),
|
|
CONSTRAINT "agents_display_name_check" CHECK (length("agents"."display_name") BETWEEN 1 AND 128),
|
|
CONSTRAINT "agents_role_check" CHECK ("agents"."role" IN ('admin', 'agent'))
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE TRIGGER agents_set_updated_at
|
|
BEFORE UPDATE ON agents
|
|
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
|
--> statement-breakpoint
|
|
CREATE TABLE "api_tokens" (
|
|
"id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL,
|
|
"agent_id" uuid NOT NULL,
|
|
"hash_argon2id" text NOT NULL,
|
|
"prefix" text NOT NULL,
|
|
"scopes" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
|
"status" text DEFAULT 'active' NOT NULL,
|
|
"expires_at" timestamp with time zone,
|
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
"revoked_at" timestamp with time zone,
|
|
CONSTRAINT "api_tokens_prefix_unique" UNIQUE("prefix"),
|
|
CONSTRAINT "api_tokens_prefix_check" CHECK ("api_tokens"."prefix" ~ '^ah_live_[a-zA-Z0-9]{4}$'),
|
|
CONSTRAINT "api_tokens_status_check" CHECK ("api_tokens"."status" IN ('active', 'rotating', 'revoked')),
|
|
CONSTRAINT "api_tokens_revoked_at_check" CHECK ("api_tokens"."revoked_at" IS NULL OR "api_tokens"."status" = 'revoked'),
|
|
CONSTRAINT "api_tokens_expires_at_check" CHECK ("api_tokens"."expires_at" IS NULL OR "api_tokens"."expires_at" > "api_tokens"."created_at")
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE TABLE "audit_events" (
|
|
"id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL,
|
|
"type" text NOT NULL,
|
|
"agent_id" uuid,
|
|
"payload_hash" "bytea" NOT NULL,
|
|
"ts" timestamp with time zone DEFAULT now() NOT NULL,
|
|
CONSTRAINT "audit_events_type_check" CHECK ("audit_events"."type" IN (
|
|
'login',
|
|
'token-issued',
|
|
'token-rotated',
|
|
'token-revoked',
|
|
'jwt-issued',
|
|
'agent-created',
|
|
'agent-deleted',
|
|
'room-created',
|
|
'room-deleted',
|
|
'message-sent'
|
|
)),
|
|
CONSTRAINT "audit_events_payload_hash_check" CHECK (length("audit_events"."payload_hash") = 32)
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE TABLE "messages" (
|
|
"id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL,
|
|
"room_id" uuid NOT NULL,
|
|
"author_agent_id" uuid NOT NULL,
|
|
"body" text NOT NULL,
|
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
CONSTRAINT "messages_body_check" CHECK (length("messages"."body") BETWEEN 1 AND 16384)
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE TABLE "room_members" (
|
|
"room_id" uuid NOT NULL,
|
|
"agent_id" uuid NOT NULL,
|
|
"joined_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
CONSTRAINT "room_members_room_id_agent_id_pk" PRIMARY KEY("room_id","agent_id")
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE TABLE "rooms" (
|
|
"id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL,
|
|
"slug" text NOT NULL,
|
|
"name" text NOT NULL,
|
|
"created_by" uuid,
|
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
CONSTRAINT "rooms_slug_unique" UNIQUE("slug"),
|
|
CONSTRAINT "rooms_slug_check" CHECK ("rooms"."slug" ~ '^[a-z0-9][a-z0-9-]{0,63}$'),
|
|
CONSTRAINT "rooms_name_check" CHECK (length("rooms"."name") BETWEEN 1 AND 128)
|
|
);
|
|
--> statement-breakpoint
|
|
ALTER TABLE "api_tokens" ADD CONSTRAINT "api_tokens_agent_id_agents_id_fk" FOREIGN KEY ("agent_id") REFERENCES "public"."agents"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
ALTER TABLE "audit_events" ADD CONSTRAINT "audit_events_agent_id_agents_id_fk" FOREIGN KEY ("agent_id") REFERENCES "public"."agents"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
|
ALTER TABLE "messages" ADD CONSTRAINT "messages_room_id_rooms_id_fk" FOREIGN KEY ("room_id") REFERENCES "public"."rooms"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
ALTER TABLE "messages" ADD CONSTRAINT "messages_author_agent_id_agents_id_fk" FOREIGN KEY ("author_agent_id") REFERENCES "public"."agents"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
|
|
ALTER TABLE "room_members" ADD CONSTRAINT "room_members_room_id_rooms_id_fk" FOREIGN KEY ("room_id") REFERENCES "public"."rooms"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
ALTER TABLE "room_members" ADD CONSTRAINT "room_members_agent_id_agents_id_fk" FOREIGN KEY ("agent_id") REFERENCES "public"."agents"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
ALTER TABLE "rooms" ADD CONSTRAINT "rooms_created_by_agents_id_fk" FOREIGN KEY ("created_by") REFERENCES "public"."agents"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
|
|
CREATE INDEX "agents_role_idx" ON "agents" USING btree ("role");--> statement-breakpoint
|
|
CREATE INDEX "api_tokens_agent_id_idx" ON "api_tokens" USING btree ("agent_id");--> statement-breakpoint
|
|
CREATE INDEX "api_tokens_active_prefix_idx" ON "api_tokens" USING btree ("prefix") WHERE "api_tokens"."status" = 'active';--> statement-breakpoint
|
|
CREATE INDEX "audit_events_ts_idx" ON "audit_events" USING btree ("ts");--> statement-breakpoint
|
|
CREATE INDEX "audit_events_type_ts_idx" ON "audit_events" USING btree ("type","ts");--> statement-breakpoint
|
|
CREATE INDEX "audit_events_agent_ts_idx" ON "audit_events" USING btree ("agent_id","ts") WHERE "audit_events"."agent_id" IS NOT NULL;--> statement-breakpoint
|
|
CREATE INDEX "messages_room_created_at_idx" ON "messages" USING btree ("room_id","created_at" DESC,"id" DESC);--> statement-breakpoint
|
|
CREATE INDEX "room_members_agent_id_idx" ON "room_members" USING btree ("agent_id"); |