'use server'; import { z } from 'zod'; import { getDbClient } from '../dbClient'; import { BillingLocation, YearMonth } from '../db-types'; import { ObjectId } from 'mongodb'; import { withUser } from '@/app/lib/auth'; import { AuthenticatedUser } from '../types/next-auth'; import { gotoHome } from './navigationActions'; import { unstable_noStore as noStore } from 'next/cache'; import { IntlTemplateFn } from '@/app/i18n'; import { getTranslations } from "next-intl/server"; export type State = { errors?: { locationName?: string[]; locationNotes?: string[], }; message?:string | null; }; /** * Schema for validating location form fields * @description this is defined as factory function so that it can be used with the next-intl library */ const FormSchema = (t:IntlTemplateFn) => z.object({ _id: z.string(), locationName: z.coerce.string().min(1, t("location-name-required")), locationNotes: z.string(), }) // dont include the _id field in the response .omit({ _id: true }); /** * Server-side action which adds or updates a bill * @param locationId location of the bill * @param prevState previous state of the form * @param formData form data * @returns */ export const updateOrAddLocation = withUser(async (user:AuthenticatedUser, locationId: string | undefined, yearMonth: YearMonth | undefined, prevState:State, formData: FormData) => { noStore(); const t = await getTranslations("location-edit-form.validation"); const validatedFields = FormSchema(t).safeParse({ locationName: formData.get('locationName'), locationNotes: formData.get('locationNotes'), }); // If form validation fails, return errors early. Otherwise, continue... if(!validatedFields.success) { return({ errors: validatedFields.error.flatten().fieldErrors, message: "Missing Fields", }); } const { locationName, locationNotes, } = validatedFields.data; // update the bill in the mongodb const dbClient = await getDbClient(); const { id: userId, email: userEmail } = user; if(locationId) { await dbClient.collection("lokacije").updateOne( { _id: locationId, // find a location with the given locationID userId // make sure the location belongs to the user }, { $set: { name: locationName, notes: locationNotes, } }); } else if(yearMonth) { await dbClient.collection("lokacije").insertOne({ _id: (new ObjectId()).toHexString(), userId, userEmail, name: locationName, notes: locationNotes, yearMonth: yearMonth, bills: [], }); } if(yearMonth) await gotoHome(yearMonth); return { message: null, errors: undefined, }; }); export const fetchAllLocations = withUser(async (user:AuthenticatedUser, year:number) => { noStore(); const dbClient = await getDbClient(); const { id: userId } = user; // fetch all locations for the given year const locations = await dbClient.collection("lokacije") .aggregate([ { $match: { userId, "yearMonth.year": year, }, }, { $addFields: { bills: { $map: { input: "$bills", as: "bill", in: { _id: "$$bill._id", name: "$$bill.name", paid: "$$bill.paid", payedAmount: "$$bill.payedAmount", hasAttachment: { $ne: ["$$bill.attachment", null] }, }, }, }, }, }, { $project: { "_id": 1, // "userId": 0, // "userEmail": 0, "name": 1, // "notes": 0, // "yearMonth": 1, "yearMonth.year": 1, "yearMonth.month": 1, // "bills": 1, "bills._id": 1, "bills.name": 1, "bills.paid": 1, "bills.payedAmount": 1, "bills.hasAttachment": 1, // "bills.attachment": 0, // "bills.notes": 0, // "bills.barcodeImage": 1, }, }, { $sort: { "yearMonth.year": -1, "yearMonth.month": -1, name: 1, }, }, ]) .toArray(); return(locations) }) /* ova metoda je zamijenjena sa jednostavnijom `fetchLocationById`, koja brže radi jer ne provjerava korisnika export const fetchLocationByUserAndId = withUser(async (user:AuthenticatedUser, locationID:string) => { noStore(); const dbClient = await getDbClient(); const { id: userId } = user; // find a location with the given locationID const billLocation = await dbClient.collection("lokacije") .findOne( { _id: locationID, userId }, { projection: { // don't include the attachment binary data in the response "bills.attachment.fileContentsBase64": 0, }, } ); if(!billLocation) { console.log(`Location ${locationID} not found`); return(null); } return(billLocation); }); */ export const fetchLocationById = async (locationID:string) => { noStore(); const dbClient = await getDbClient(); // find a location with the given locationID const billLocation = await dbClient.collection("lokacije") .findOne( { _id: locationID }, { projection: { // don't include the attachment binary data in the response "bills.attachment.fileContentsBase64": 0, }, } ); if(!billLocation) { console.log(`Location ${locationID} not found`); return(null); } return(billLocation); }; export const deleteLocationById = withUser(async (user:AuthenticatedUser, locationID:string, yearMonth:YearMonth) => { noStore(); const dbClient = await getDbClient(); const { id: userId } = user; // find a location with the given locationID const post = await dbClient.collection("lokacije").deleteOne({ _id: locationID, userId }); await gotoHome(yearMonth) })