import { query, mutation } from "./_generated/server"; import { v } from "convex/values"; import { paginationOptsValidator } from "convex/server"; export const list = query({ args: {}, handler: async (ctx) => { return await ctx.db.query("ads").order("desc").collect(); }, }); export const listPaginated = query({ args: { paginationOpts: paginationOptsValidator }, handler: async (ctx, args) => { return await ctx.db.query("ads").order("desc").paginate(args.paginationOpts); }, }); export const getByCategory = query({ args: { category: v.string() }, handler: async (ctx, args) => { return await ctx.db .query("ads") .withIndex("by_category", (q) => q.eq("category", args.category)) .order("desc") .collect(); }, }); export const getByCategoryPaginated = query({ args: { category: v.string(), paginationOpts: paginationOptsValidator }, handler: async (ctx, args) => { return await ctx.db .query("ads") .withIndex("by_category", (q) => q.eq("category", args.category)) .order("desc") .paginate(args.paginationOpts); }, }); export const getById = query({ args: { id: v.id("ads") }, handler: async (ctx, args) => { return await ctx.db.get(args.id); }, }); export const search = query({ args: { query: v.string(), category: v.optional(v.string()) }, handler: async (ctx, args) => { const searchLower = args.query.toLowerCase(); let ads; if (args.category) { ads = await ctx.db .query("ads") .withIndex("by_category", (q) => q.eq("category", args.category!)) .collect(); } else { ads = await ctx.db.query("ads").order("desc").collect(); } return ads.filter( (ad) => ad.title.toLowerCase().includes(searchLower) || ad.description.toLowerCase().includes(searchLower) || ad.location.toLowerCase().includes(searchLower) ); }, }); export const getByHandyman = query({ args: { handymanId: v.id("users") }, handler: async (ctx, args) => { return await ctx.db .query("ads") .withIndex("by_handyman", (q) => q.eq("handymanId", args.handymanId)) .order("desc") .collect(); }, }); export const getByHandymanPaginated = query({ args: { handymanId: v.id("users"), paginationOpts: paginationOptsValidator }, handler: async (ctx, args) => { return await ctx.db .query("ads") .withIndex("by_handyman", (q) => q.eq("handymanId", args.handymanId)) .order("desc") .paginate(args.paginationOpts); }, }); export const create = mutation({ args: { token: v.string(), title: v.string(), description: v.string(), category: v.string(), location: v.string(), lat: v.optional(v.number()), lng: v.optional(v.number()), priceRange: v.optional(v.string()), availability: v.optional(v.string()), imageIds: v.optional(v.array(v.string())), }, handler: async (ctx, args) => { const session = await ctx.db .query("sessions") .withIndex("by_token", (q) => q.eq("token", args.token)) .first(); if (!session) throw new Error("Неавторизиран"); const user = await ctx.db.get(session.userId); if (!user) throw new Error("Корисникот не е пронајден"); if (user.role !== "handyman") throw new Error("Само мајстори можат да креираат огласи"); const now = Date.now(); const adId = await ctx.db.insert("ads", { handymanId: session.userId, title: args.title, description: args.description, category: args.category, location: args.location, lat: args.lat, lng: args.lng, priceRange: args.priceRange, availability: args.availability, imageIds: args.imageIds, ratingAvg: undefined, reviewCount: 0, createdAt: now, updatedAt: now, }); return adId; }, }); export const update = mutation({ args: { token: v.string(), adId: v.id("ads"), title: v.optional(v.string()), description: v.optional(v.string()), category: v.optional(v.string()), location: v.optional(v.string()), lat: v.optional(v.number()), lng: v.optional(v.number()), priceRange: v.optional(v.string()), availability: v.optional(v.string()), imageIds: v.optional(v.array(v.string())), }, handler: async (ctx, args) => { const session = await ctx.db .query("sessions") .withIndex("by_token", (q) => q.eq("token", args.token)) .first(); if (!session) throw new Error("Неавторизиран"); const ad = await ctx.db.get(args.adId); if (!ad) throw new Error("Огласот не е пронајден"); if (ad.handymanId !== session.userId) throw new Error("Немате дозвола да го уредите овој оглас"); const updates: Record = { updatedAt: Date.now() }; if (args.title !== undefined) updates.title = args.title; if (args.description !== undefined) updates.description = args.description; if (args.category !== undefined) updates.category = args.category; if (args.location !== undefined) updates.location = args.location; if (args.lat !== undefined) updates.lat = args.lat; if (args.lng !== undefined) updates.lng = args.lng; if (args.priceRange !== undefined) updates.priceRange = args.priceRange; if (args.availability !== undefined) updates.availability = args.availability; if (args.imageIds !== undefined) updates.imageIds = args.imageIds; await ctx.db.patch(args.adId, updates); return args.adId; }, }); export const remove = mutation({ args: { token: v.string(), adId: v.id("ads"), }, handler: async (ctx, args) => { const session = await ctx.db .query("sessions") .withIndex("by_token", (q) => q.eq("token", args.token)) .first(); if (!session) throw new Error("Неавторизиран"); const ad = await ctx.db.get(args.adId); if (!ad) throw new Error("Огласот не е пронајден"); if (ad.handymanId !== session.userId) throw new Error("Немате дозвола да го избришете овој оглас"); await ctx.db.delete(args.adId); return true; }, });