Skip to content

Instantly share code, notes, and snippets.

@babakfp
Created March 26, 2025 18:56
Show Gist options
  • Save babakfp/01fd726d4b5fc3d4d39c5c73d1df8bef to your computer and use it in GitHub Desktop.
Save babakfp/01fd726d4b5fc3d4d39c5c73d1df8bef to your computer and use it in GitHub Desktop.
Convex / Discord: Having TS issues with `withIndex`
import { defineSchema, defineTable } from "convex/server"
import { v } from "convex/values"
import { mutation, query } from "./_generated/server"
/**
* Schema definition for the Manhwa hosting site.
*/
export default defineSchema({
manhwa: defineTable({
title: v.string(),
author: v.string(),
artist: v.string(),
description: v.string(),
coverImageUrl: v.string(),
status: v.union(
v.literal("Ongoing"),
v.literal("Completed"),
v.literal("Hiatus"),
v.literal("Dropped"),
),
releaseDate: v.string(),
genre: v.string(),
altTitle: v.optional(v.string()),
})
.index("by_title", ["title"])
.index("by_release_date", ["releaseDate"]),
chapters: defineTable({
manhwaId: v.id("manhwa"),
chapterNumber: v.float64(),
title: v.optional(v.string()),
releaseDate: v.string(),
})
.index("by_manhwa", ["manhwaId"])
.index("by_manhwa_and_number", ["manhwaId", "chapterNumber"]),
images: defineTable({
chapterId: v.id("chapters"),
imageUrl: v.string(),
imageOrder: v.int64(),
}).index("by_chapter", ["chapterId"]),
users: defineTable({
username: v.string(),
email: v.string(),
passwordHash: v.string(), // Store the hash, not the actual password
registrationDate: v.string(),
profilePictureUrl: v.optional(v.string()),
})
.index("by_username", ["username"])
.index("by_email", ["email"]),
manhwaComments: defineTable({
manhwaId: v.id("manhwa"),
userId: v.id("users"),
commentText: v.string(),
commentDate: v.string(),
}).index("by_manhwa", ["manhwaId"]),
chapterComments: defineTable({
chapterId: v.id("chapters"),
userId: v.id("users"),
commentText: v.string(),
commentDate: v.string(),
}).index("by_chapter", ["chapterId"]),
manhwaLikes: defineTable({
manhwaId: v.id("manhwa"),
userId: v.id("users"),
likeDate: v.string(),
})
.index("by_manhwa", ["manhwaId"])
.index("by_user_and_manhwa", ["userId", "manhwaId"]), //useful for checking if user liked it
chapterLikes: defineTable({
chapterId: v.id("chapters"),
userId: v.id("users"),
likeDate: v.string(),
})
.index("by_chapter", ["chapterId"])
.index("by_user_and_chapter", ["userId", "chapterId"]), //useful for checking if user liked it.
})
/**
* Query functions
*/
/**
* Get a single manhwa by ID.
*/
export const getManhwa = query({
args: { manhwaId: v.id("manhwa") },
handler: async (ctx, args) => {
const manhwa = await ctx.db.get(args.manhwaId)
return manhwa
},
})
/**
* Get a list of manhwa, optionally sorted by release date.
*/
export const listManhwa = query({
args: { sort: v.optional(v.union(v.literal("asc"), v.literal("desc"))) },
handler: async (ctx, args) => {
const order = args.sort === "asc" ? "asc" : "desc"
return ctx.db.query("manhwa").order(order).collect()
},
})
/**
* Get chapters for a given manhwa ID, sorted by chapter number.
*/
export const getChaptersByManhwa = query({
args: { manhwaId: v.id("manhwa") },
handler: async (ctx, args) => {
return ctx.db
.query("chapters")
.withIndex("by_manhwa_and_number", (q) =>
q.eq("manhwaId", args.manhwaId),
)
.order("asc")
.collect()
},
})
/**
* Get images for a given chapter ID, sorted by image order.
*/
export const getImagesByChapter = query({
args: { chapterId: v.id("chapters") },
handler: async (ctx, args) => {
return ctx.db
.query("images")
.withIndex("by_chapter", (q) => q.eq("chapterId", args.chapterId))
.order("asc")
.collect()
},
})
/**
* Get comments for a given manhwa ID.
*/
export const getManhwaComments = query({
args: { manhwaId: v.id("manhwa") },
handler: async (ctx, args) => {
return ctx.db
.query("manhwaComments")
.withIndex("by_manhwa", (q) => q.eq("manhwaId", args.manhwaId))
.collect()
},
})
/**
* Get comments for a given chapter ID.
*/
export const getChapterComments = query({
args: { chapterId: v.id("chapters") },
handler: async (ctx, args) => {
return ctx.db
.query("chapterComments")
.withIndex("by_chapter", (q) => q.eq("chapterId", args.chapterId))
.collect()
},
})
/**
* Get likes for a given manhwa ID.
*/
export const getManhwaLikes = query({
args: { manhwaId: v.id("manhwa") },
handler: async (ctx, args) => {
return ctx.db
.query("manhwaLikes")
.withIndex("by_manhwa", (q) => q.eq("manhwaId", args.manhwaId))
.collect()
},
})
/**
* Get likes for a given chapter ID.
*/
export const getChapterLikes = query({
args: { chapterId: v.id("chapters") },
handler: async (ctx, args) => {
return ctx.db
.query("chapterLikes")
.withIndex("by_chapter", (q) => q.eq("chapterId", args.chapterId))
.collect()
},
})
/**
* Check if a user has liked a specific manhwa
*/
export const hasUserLikedManhwa = query({
args: { userId: v.id("users"), manhwaId: v.id("manhwa") },
handler: async (ctx, args) => {
const like = await ctx.db
.query("manhwaLikes")
.withIndex("by_user_and_manhwa", (q) =>
q.eq("userId", args.userId).eq("manhwaId", args.manhwaId),
)
.unique()
return like !== null
},
})
/**
* Check if a user has liked a specific chapter
*/
export const hasUserLikedChapter = query({
args: { userId: v.id("users"), chapterId: v.id("chapters") },
handler: async (ctx, args) => {
const like = await ctx.db
.query("chapterLikes")
.withIndex("by_user_and_chapter", (q) =>
q.eq("userId", args.userId).eq("chapterId", args.chapterId),
)
.unique()
return like !== null
},
})
/**
* Mutation functions
*/
/**
* Create a new manhwa.
*/
export const createManhwa = mutation({
args: {
title: v.string(),
author: v.string(),
artist: v.string(),
description: v.string(),
coverImageUrl: v.string(),
status: v.union(
v.literal("Ongoing"),
v.literal("Completed"),
v.literal("Hiatus"),
v.literal("Dropped"),
),
releaseDate: v.string(),
genre: v.string(),
altTitle: v.optional(v.string()),
},
handler: async (ctx, args) => {
const manhwaId = await ctx.db.insert("manhwa", {
title: args.title,
author: args.author,
artist: args.artist,
description: args.description,
coverImageUrl: args.coverImageUrl,
status: args.status,
releaseDate: args.releaseDate,
genre: args.genre,
altTitle: args.altTitle,
})
return manhwaId
},
})
/**
* Create a new chapter for a manhwa.
*/
export const createChapter = mutation({
args: {
manhwaId: v.id("manhwa"),
chapterNumber: v.float64(),
title: v.optional(v.string()),
releaseDate: v.string(),
},
handler: async (ctx, args) => {
const chapterId = await ctx.db.insert("chapters", {
manhwaId: args.manhwaId,
chapterNumber: args.chapterNumber,
title: args.title,
releaseDate: args.releaseDate,
})
return chapterId
},
})
/**
* Add a new image to a chapter.
*/
export const addImageToChapter = mutation({
args: {
chapterId: v.id("chapters"),
imageUrl: v.string(),
imageOrder: v.int64(),
},
handler: async (ctx, args) => {
const imageId = await ctx.db.insert("images", {
chapterId: args.chapterId,
imageUrl: args.imageUrl,
imageOrder: args.imageOrder,
})
return imageId
},
})
/**
* Create a new user.
*/
export const createUser = mutation({
args: {
username: v.string(),
email: v.string(),
passwordHash: v.string(), // Store the hashed password
registrationDate: v.string(),
profilePictureUrl: v.optional(v.string()),
},
handler: async (ctx, args) => {
const userId = await ctx.db.insert("users", {
username: args.username,
email: args.email,
passwordHash: args.passwordHash,
registrationDate: args.registrationDate,
profilePictureUrl: args.profilePictureUrl,
})
return userId
},
})
/**
* Add a comment to a manhwa.
*/
export const addManhwaComment = mutation({
args: {
manhwaId: v.id("manhwa"),
userId: v.id("users"),
commentText: v.string(),
commentDate: v.string(),
},
handler: async (ctx, args) => {
const commentId = await ctx.db.insert("manhwaComments", {
manhwaId: args.manhwaId,
userId: args.userId,
commentText: args.commentText,
commentDate: args.commentDate,
})
return commentId
},
})
/**
* Add a comment to a chapter.
*/
export const addChapterComment = mutation({
args: {
chapterId: v.id("chapters"),
userId: v.id("users"),
commentText: v.string(),
commentDate: v.string(),
},
handler: async (ctx, args) => {
const commentId = await ctx.db.insert("chapterComments", {
chapterId: args.chapterId,
userId: args.userId,
commentText: args.commentText,
commentDate: args.commentDate,
})
return commentId
},
})
/**
* Add a like to a manhwa.
*/
export const addManhwaLike = mutation({
args: {
manhwaId: v.id("manhwa"),
userId: v.id("users"),
likeDate: v.string(),
},
handler: async (ctx, args) => {
// Check if the user has already liked this manhwa
const alreadyLiked = await ctx.db
.query("manhwaLikes")
.withIndex("by_user_and_manhwa", (q) =>
q.eq("userId", args.userId).eq("manhwaId", args.manhwaId),
)
.unique()
if (alreadyLiked) {
throw new Error("User has already liked this manhwa")
}
const likeId = await ctx.db.insert("manhwaLikes", {
manhwaId: args.manhwaId,
userId: args.userId,
likeDate: args.likeDate,
})
return likeId
},
})
/**
* Add a like to a chapter.
*/
export const addChapterLike = mutation({
args: {
chapterId: v.id("chapters"),
userId: v.id("users"),
likeDate: v.string(),
},
handler: async (ctx, args) => {
const alreadyLiked = await ctx.db
.query("chapterLikes")
.withIndex("by_user_and_chapter", (q) =>
q.eq("userId", args.userId).eq("chapterId", args.chapterId),
)
.unique()
if (alreadyLiked) {
throw new Error("User has already liked this chapter")
}
const likeId = await ctx.db.insert("chapterLikes", {
chapterId: args.chapterId,
userId: args.userId,
likeDate: args.likeDate,
})
return likeId
},
})
/**
* Remove a like from a manhwa
*/
export const removeManhwaLike = mutation({
args: {
manhwaId: v.id("manhwa"),
userId: v.id("users"),
},
handler: async (ctx, args) => {
const likeToRemove = await ctx.db
.query("manhwaLikes")
.withIndex("by_user_and_manhwa", (q) =>
q.eq("userId", args.userId).eq("manhwaId", args.manhwaId),
)
.unique()
if (likeToRemove) {
await ctx.db.delete(likeToRemove._id)
}
},
})
/**
* Remove a like from a chapter
*/
export const removeChapterLike = mutation({
args: {
chapterId: v.id("chapters"),
userId: v.id("users"),
},
handler: async (ctx, args) => {
const likeToRemove = await ctx.db
.query("chapterLikes")
.withIndex("by_user_and_chapter", (q) =>
q.eq("userId", args.userId).eq("chapterId", args.chapterId),
)
.unique()
if (likeToRemove) {
await ctx.db.delete(likeToRemove._id)
}
},
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment