Show newest portfolios first on dashboard and website lists.
Order by sort_order descending and assign new items the next highest sort value so create and move-up stay consistent. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
ea40ae40d8
commit
af0573c089
@@ -31,7 +31,7 @@ You are building a **Meshkee business website (storefront)**. You must use the M
|
||||
### Typical bootstrap sequence
|
||||
1. `GET /tenants/{domain}` → branding + `businessId` + `specialProductsSource` (`product` or `store_item`)
|
||||
2. Homepage: business-info, **static-images**, sliders, category-groups, brand-groups, store-specials (`source` repeats the tenant setting; items are store listings when `source` is `store_item`)
|
||||
3. Catalog: categories, products (`GET /products/{slug}` includes `relatedProducts`: same category then same brand, in-stock first), store-items (`name` instant search: in-stock first, then `updatedAt`), **user-products** (customer stock listings). List filters: products/blogs/portfolios/videos accept `?tag=` (exact match on `metadata.tags`).
|
||||
3. Catalog: categories, products (`GET /products/{slug}` includes `relatedProducts`: same category then same brand, in-stock first), store-items (`name` instant search: in-stock first, then `updatedAt`), **user-products** (customer stock listings). Portfolios list newest first (`sortOrder` desc, then `publishedAt` / `createdAt` desc). List filters: products/blogs/portfolios/videos accept `?tag=` (exact match on `metadata.tags`).
|
||||
4. Auth: register/login → store tokens. Optional: `POST /auth/send-otp` then `POST /auth/login-otp` (passwordless) or `POST /auth/reset-password` (forgot password). `POST /auth/verify-otp` only marks the cell verified (no tokens).
|
||||
5. Cart checkout with `addressId` or inline `shippingAddress` + `payment`
|
||||
- For online pay: `payment.type = "e_payment_gate"`, `gatewayType` (e.g. `"mellat"` or `"zarinpal"`), and absolute `returnUrl`
|
||||
|
||||
@@ -41,6 +41,13 @@ const portfolioInclude = {
|
||||
media: true,
|
||||
} satisfies Prisma.portfoliosInclude;
|
||||
|
||||
/** Higher `sort_order` first (newest / moved-up), then newest dates. */
|
||||
const portfolioListOrderBy = [
|
||||
{ sort_order: 'desc' },
|
||||
{ published_at: 'desc' },
|
||||
{ created_at: 'desc' },
|
||||
] as const;
|
||||
|
||||
@Injectable()
|
||||
export class PortfoliosService {
|
||||
constructor(
|
||||
@@ -64,11 +71,7 @@ export class PortfoliosService {
|
||||
const [items, total] = await Promise.all([
|
||||
this.prisma.portfolios.findMany({
|
||||
where,
|
||||
orderBy: [
|
||||
{ sort_order: 'asc' },
|
||||
{ published_at: 'desc' },
|
||||
{ created_at: 'desc' },
|
||||
],
|
||||
orderBy: [...portfolioListOrderBy],
|
||||
skip,
|
||||
take: pageSize,
|
||||
include: portfolioInclude,
|
||||
@@ -143,6 +146,9 @@ export class PortfoliosService {
|
||||
);
|
||||
}
|
||||
|
||||
const sortOrder =
|
||||
dto.sortOrder ?? (await this.nextSortOrder(businessId));
|
||||
|
||||
const created = await this.prisma.$transaction(async (tx) => {
|
||||
const portfolio = await tx.portfolios.create({
|
||||
data: {
|
||||
@@ -155,7 +161,7 @@ export class PortfoliosService {
|
||||
content: this.buildContent(dto.mainTextHtml) as Prisma.InputJsonValue,
|
||||
status,
|
||||
featured_media_id: featuredMediaId,
|
||||
sort_order: dto.sortOrder ?? 0,
|
||||
sort_order: sortOrder,
|
||||
published_at: status === ContentStatus.published ? new Date() : null,
|
||||
metadata: this.buildMetadata(dto.tags) as Prisma.InputJsonValue,
|
||||
},
|
||||
@@ -419,11 +425,7 @@ export class PortfoliosService {
|
||||
const [items, total] = await Promise.all([
|
||||
this.prisma.portfolios.findMany({
|
||||
where,
|
||||
orderBy: [
|
||||
{ sort_order: 'asc' },
|
||||
{ published_at: 'desc' },
|
||||
{ created_at: 'desc' },
|
||||
],
|
||||
orderBy: [...portfolioListOrderBy],
|
||||
skip,
|
||||
take: pageSize,
|
||||
include: portfolioInclude,
|
||||
@@ -827,6 +829,14 @@ export class PortfoliosService {
|
||||
};
|
||||
}
|
||||
|
||||
private async nextSortOrder(businessId: bigint): Promise<number> {
|
||||
const result = await this.prisma.portfolios.aggregate({
|
||||
where: { business_id: businessId },
|
||||
_max: { sort_order: true },
|
||||
});
|
||||
return (result._max.sort_order ?? -1) + 1;
|
||||
}
|
||||
|
||||
private asRecord(value: Prisma.JsonValue): Record<string, unknown> {
|
||||
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
||||
return value as Record<string, unknown>;
|
||||
|
||||
@@ -31,7 +31,7 @@ You are building a **Meshkee business website (storefront)**. You must use the M
|
||||
### Typical bootstrap sequence
|
||||
1. `GET /tenants/{domain}` → branding + `businessId` + `specialProductsSource` (`product` or `store_item`)
|
||||
2. Homepage: business-info, **static-images**, sliders, category-groups, brand-groups, store-specials (`source` repeats the tenant setting; items are store listings when `source` is `store_item`)
|
||||
3. Catalog: categories, products (`GET /products/{slug}` includes `relatedProducts`: same category then same brand, in-stock first), store-items (`name` instant search: in-stock first, then `updatedAt`), **user-products** (customer stock listings). List filters: products/blogs/portfolios/videos accept `?tag=` (exact match on `metadata.tags`).
|
||||
3. Catalog: categories, products (`GET /products/{slug}` includes `relatedProducts`: same category then same brand, in-stock first), store-items (`name` instant search: in-stock first, then `updatedAt`), **user-products** (customer stock listings). Portfolios list newest first (`sortOrder` desc, then `publishedAt` / `createdAt` desc). List filters: products/blogs/portfolios/videos accept `?tag=` (exact match on `metadata.tags`).
|
||||
4. Auth: register/login → store tokens. Optional: `POST /auth/send-otp` then `POST /auth/login-otp` (passwordless) or `POST /auth/reset-password` (forgot password). `POST /auth/verify-otp` only marks the cell verified (no tokens).
|
||||
5. Cart checkout with `addressId` or inline `shippingAddress` + `payment`
|
||||
- For online pay: `payment.type = "e_payment_gate"`, `gatewayType` (e.g. `"mellat"` or `"zarinpal"`), and absolute `returnUrl`
|
||||
|
||||
Reference in New Issue
Block a user