All checks were successful
Build and Release / build-and-release (push) Successful in 14m19s
246 lines
6.4 KiB
TypeScript
246 lines
6.4 KiB
TypeScript
import { query, mutation } from "./_generated/server";
|
|
import { v } from "convex/values";
|
|
import { GenericMutationCtx } from "convex/server";
|
|
import { DataModel, Id } from "./_generated/dataModel";
|
|
import { internal } from "./_generated/api";
|
|
|
|
type TableWithChannelIndex =
|
|
| "channelKeys"
|
|
| "dmParticipants"
|
|
| "typingIndicators"
|
|
| "voiceStates"
|
|
| "channelReadState";
|
|
|
|
async function deleteByChannel(
|
|
ctx: GenericMutationCtx<DataModel>,
|
|
table: TableWithChannelIndex,
|
|
channelId: Id<"channels">
|
|
) {
|
|
const docs = await (ctx.db.query(table) as any)
|
|
.withIndex("by_channel", (q: any) => q.eq("channelId", channelId))
|
|
.collect();
|
|
for (const doc of docs) {
|
|
await ctx.db.delete(doc._id);
|
|
}
|
|
}
|
|
|
|
// List all non-DM channels
|
|
export const list = query({
|
|
args: {},
|
|
returns: v.array(
|
|
v.object({
|
|
_id: v.id("channels"),
|
|
_creationTime: v.number(),
|
|
name: v.string(),
|
|
type: v.string(),
|
|
categoryId: v.optional(v.id("categories")),
|
|
topic: v.optional(v.string()),
|
|
position: v.optional(v.number()),
|
|
})
|
|
),
|
|
handler: async (ctx) => {
|
|
const channels = await ctx.db.query("channels").collect();
|
|
return channels
|
|
.filter((c) => c.type !== "dm")
|
|
.sort((a, b) => (a.position ?? 0) - (b.position ?? 0) || a.name.localeCompare(b.name));
|
|
},
|
|
});
|
|
|
|
// Get single channel by ID
|
|
export const get = query({
|
|
args: { id: v.id("channels") },
|
|
returns: v.union(
|
|
v.object({
|
|
_id: v.id("channels"),
|
|
_creationTime: v.number(),
|
|
name: v.string(),
|
|
type: v.string(),
|
|
categoryId: v.optional(v.id("categories")),
|
|
topic: v.optional(v.string()),
|
|
position: v.optional(v.number()),
|
|
}),
|
|
v.null()
|
|
),
|
|
handler: async (ctx, args) => {
|
|
return await ctx.db.get(args.id);
|
|
},
|
|
});
|
|
|
|
// Create new channel
|
|
export const create = mutation({
|
|
args: {
|
|
name: v.string(),
|
|
type: v.optional(v.string()),
|
|
categoryId: v.optional(v.id("categories")),
|
|
topic: v.optional(v.string()),
|
|
position: v.optional(v.number()),
|
|
},
|
|
returns: v.object({ id: v.id("channels") }),
|
|
handler: async (ctx, args) => {
|
|
if (!args.name.trim()) {
|
|
throw new Error("Channel name required");
|
|
}
|
|
|
|
const existing = await ctx.db
|
|
.query("channels")
|
|
.withIndex("by_name", (q) => q.eq("name", args.name))
|
|
.unique();
|
|
|
|
if (existing) {
|
|
throw new Error("Channel already exists");
|
|
}
|
|
|
|
// Auto-calculate position if not provided
|
|
let position = args.position;
|
|
if (position === undefined) {
|
|
const allChannels = await ctx.db.query("channels").collect();
|
|
const sameCategory = allChannels.filter(
|
|
(c) => c.categoryId === args.categoryId && c.type !== "dm"
|
|
);
|
|
const maxPos = sameCategory.reduce(
|
|
(max, c) => Math.max(max, c.position ?? 0),
|
|
-1000
|
|
);
|
|
position = maxPos + 1000;
|
|
}
|
|
|
|
const id = await ctx.db.insert("channels", {
|
|
name: args.name,
|
|
type: args.type || "text",
|
|
categoryId: args.categoryId,
|
|
topic: args.topic,
|
|
position,
|
|
});
|
|
|
|
return { id };
|
|
},
|
|
});
|
|
|
|
// Update channel topic
|
|
export const updateTopic = mutation({
|
|
args: {
|
|
id: v.id("channels"),
|
|
topic: v.string(),
|
|
},
|
|
returns: v.null(),
|
|
handler: async (ctx, args) => {
|
|
const channel = await ctx.db.get(args.id);
|
|
if (!channel) throw new Error("Channel not found");
|
|
await ctx.db.patch(args.id, { topic: args.topic });
|
|
return null;
|
|
},
|
|
});
|
|
|
|
// Rename channel
|
|
export const rename = mutation({
|
|
args: {
|
|
id: v.id("channels"),
|
|
name: v.string(),
|
|
},
|
|
returns: v.object({
|
|
_id: v.id("channels"),
|
|
_creationTime: v.number(),
|
|
name: v.string(),
|
|
type: v.string(),
|
|
categoryId: v.optional(v.id("categories")),
|
|
topic: v.optional(v.string()),
|
|
position: v.optional(v.number()),
|
|
}),
|
|
handler: async (ctx, args) => {
|
|
if (!args.name.trim()) {
|
|
throw new Error("Name required");
|
|
}
|
|
|
|
const channel = await ctx.db.get(args.id);
|
|
if (!channel) {
|
|
throw new Error("Channel not found");
|
|
}
|
|
|
|
await ctx.db.patch(args.id, { name: args.name });
|
|
return { ...channel, name: args.name };
|
|
},
|
|
});
|
|
|
|
// Move a channel to a different category with a new position
|
|
export const moveChannel = mutation({
|
|
args: {
|
|
id: v.id("channels"),
|
|
categoryId: v.optional(v.id("categories")),
|
|
position: v.number(),
|
|
},
|
|
returns: v.null(),
|
|
handler: async (ctx, args) => {
|
|
const channel = await ctx.db.get(args.id);
|
|
if (!channel) throw new Error("Channel not found");
|
|
await ctx.db.patch(args.id, {
|
|
categoryId: args.categoryId,
|
|
position: args.position,
|
|
});
|
|
return null;
|
|
},
|
|
});
|
|
|
|
// Batch reorder channels
|
|
export const reorderChannels = mutation({
|
|
args: {
|
|
updates: v.array(
|
|
v.object({
|
|
id: v.id("channels"),
|
|
categoryId: v.optional(v.id("categories")),
|
|
position: v.number(),
|
|
})
|
|
),
|
|
},
|
|
returns: v.null(),
|
|
handler: async (ctx, args) => {
|
|
for (const u of args.updates) {
|
|
await ctx.db.patch(u.id, {
|
|
categoryId: u.categoryId,
|
|
position: u.position,
|
|
});
|
|
}
|
|
return null;
|
|
},
|
|
});
|
|
|
|
// Delete channel + cascade messages and keys
|
|
export const remove = mutation({
|
|
args: { id: v.id("channels") },
|
|
returns: v.object({ success: v.boolean() }),
|
|
handler: async (ctx, args) => {
|
|
const channel = await ctx.db.get(args.id);
|
|
if (!channel) {
|
|
throw new Error("Channel not found");
|
|
}
|
|
|
|
// Delete reactions for all messages in this channel
|
|
const messages = await ctx.db
|
|
.query("messages")
|
|
.withIndex("by_channel", (q) => q.eq("channelId", args.id))
|
|
.collect();
|
|
for (const msg of messages) {
|
|
const reactions = await ctx.db
|
|
.query("messageReactions")
|
|
.withIndex("by_message", (q) => q.eq("messageId", msg._id))
|
|
.collect();
|
|
for (const r of reactions) {
|
|
await ctx.db.delete(r._id);
|
|
}
|
|
await ctx.db.delete(msg._id);
|
|
}
|
|
|
|
await deleteByChannel(ctx, "channelKeys", args.id);
|
|
await deleteByChannel(ctx, "dmParticipants", args.id);
|
|
await deleteByChannel(ctx, "typingIndicators", args.id);
|
|
await deleteByChannel(ctx, "voiceStates", args.id);
|
|
await deleteByChannel(ctx, "channelReadState", args.id);
|
|
|
|
// Clear AFK setting if this channel was the AFK channel
|
|
await ctx.runMutation(internal.serverSettings.clearAfkChannel, { channelId: args.id });
|
|
|
|
await ctx.db.delete(args.id);
|
|
|
|
return { success: true };
|
|
},
|
|
});
|