Created
March 26, 2025 18:56
-
-
Save babakfp/01fd726d4b5fc3d4d39c5c73d1df8bef to your computer and use it in GitHub Desktop.
Convex / Discord: Having TS issues with `withIndex`
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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