140 lines
3.2 KiB
TypeScript
140 lines
3.2 KiB
TypeScript
import { Prisma, ListingStatus } from "@prisma/client";
|
|
import { prisma } from "./prisma";
|
|
import { DEFAULT_LOCALE, SAMPLE_LISTING_SLUG } from "./sampleListing";
|
|
|
|
export type ListingWithTranslations = Prisma.ListingTranslationGetPayload<{
|
|
include: {
|
|
listing: {
|
|
include: {
|
|
images: {
|
|
select: {
|
|
id: true;
|
|
url: true;
|
|
altText: true;
|
|
order: true;
|
|
isCover: true;
|
|
size: true;
|
|
mimeType: true;
|
|
};
|
|
};
|
|
owner: true;
|
|
};
|
|
};
|
|
};
|
|
}>;
|
|
|
|
type FetchOptions = {
|
|
slug: string;
|
|
locale?: string;
|
|
includeOwnerDraftsForUserId?: string;
|
|
};
|
|
|
|
function resolveImageUrl(img: {
|
|
id: string;
|
|
url: string | null;
|
|
size: number | null;
|
|
}) {
|
|
if (img.size && img.size > 0) {
|
|
return `/api/images/${img.id}`;
|
|
}
|
|
return img.url;
|
|
}
|
|
|
|
/**
|
|
* Fetch a listing translation by slug and locale.
|
|
* Falls back to any locale if the requested locale is missing.
|
|
*/
|
|
export async function getListingBySlug({
|
|
slug,
|
|
locale,
|
|
includeOwnerDraftsForUserId,
|
|
}: FetchOptions): Promise<ListingWithTranslations | null> {
|
|
const targetLocale = locale ?? DEFAULT_LOCALE;
|
|
|
|
const listingWhere: Prisma.ListingWhereInput = includeOwnerDraftsForUserId
|
|
? {
|
|
removedAt: null,
|
|
OR: [
|
|
{ status: ListingStatus.PUBLISHED },
|
|
{
|
|
ownerId: includeOwnerDraftsForUserId,
|
|
status: { in: [ListingStatus.DRAFT, ListingStatus.PENDING] },
|
|
},
|
|
],
|
|
}
|
|
: { status: ListingStatus.PUBLISHED, removedAt: null };
|
|
|
|
const translation = await prisma.listingTranslation.findFirst({
|
|
where: { slug, locale: targetLocale, listing: listingWhere },
|
|
include: {
|
|
listing: {
|
|
include: {
|
|
images: {
|
|
orderBy: { order: "asc" },
|
|
select: {
|
|
id: true,
|
|
url: true,
|
|
altText: true,
|
|
order: true,
|
|
isCover: true,
|
|
size: true,
|
|
mimeType: true,
|
|
},
|
|
},
|
|
owner: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (translation) {
|
|
return translation;
|
|
}
|
|
|
|
// Fallback: first translation for this slug
|
|
return prisma.listingTranslation.findFirst({
|
|
where: { slug, listing: listingWhere },
|
|
include: {
|
|
listing: {
|
|
include: {
|
|
images: {
|
|
orderBy: { order: "asc" },
|
|
select: {
|
|
id: true,
|
|
url: true,
|
|
altText: true,
|
|
order: true,
|
|
isCover: true,
|
|
size: true,
|
|
mimeType: true,
|
|
},
|
|
},
|
|
owner: true,
|
|
},
|
|
},
|
|
},
|
|
orderBy: { createdAt: "asc" },
|
|
});
|
|
}
|
|
|
|
export function withResolvedListingImages(
|
|
translation: ListingWithTranslations,
|
|
): ListingWithTranslations {
|
|
const images = translation.listing.images
|
|
.map((img) => {
|
|
const url = resolveImageUrl(img);
|
|
if (!url) return null;
|
|
return { ...img, url };
|
|
})
|
|
.filter(Boolean) as ListingWithTranslations["listing"]["images"];
|
|
|
|
return {
|
|
...translation,
|
|
listing: {
|
|
...translation.listing,
|
|
images,
|
|
},
|
|
};
|
|
}
|
|
|
|
export { SAMPLE_LISTING_SLUG, DEFAULT_LOCALE };
|