Prefer text fields in technical-form AI and add invoice payment methods.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
f8b865b0aa
commit
503b70a690
@@ -1379,7 +1379,7 @@ export class LegacyMigrateService {
|
||||
.filter((m) => Number(m.size) > 0)
|
||||
.slice(0, 12)
|
||||
.map((m) => ({ id: m.id, fileName: m.file_name }));
|
||||
const storagePrefix = await this.legacySourceS3.resolveBusinessPrefix(
|
||||
const storagePrefix = await this.tryResolveStoragePrefix(
|
||||
oldBizId,
|
||||
oldSlug,
|
||||
probeMedia,
|
||||
@@ -1417,7 +1417,7 @@ export class LegacyMigrateService {
|
||||
|
||||
for (const media of articleMedia) {
|
||||
const size = Number(media.size) || 0;
|
||||
if (size <= 0) {
|
||||
if (size <= 0 || !storagePrefix) {
|
||||
imagesFailed += 1;
|
||||
continue;
|
||||
}
|
||||
@@ -1611,7 +1611,7 @@ export class LegacyMigrateService {
|
||||
.filter((m) => Number(m.size) > 0)
|
||||
.slice(0, 12)
|
||||
.map((m) => ({ id: m.id, fileName: m.file_name }));
|
||||
const storagePrefix = await this.legacySourceS3.resolveBusinessPrefix(
|
||||
const storagePrefix = await this.tryResolveStoragePrefix(
|
||||
oldBizId,
|
||||
oldSlug,
|
||||
probeMedia,
|
||||
@@ -1648,7 +1648,7 @@ export class LegacyMigrateService {
|
||||
|
||||
for (const media of itemMedia) {
|
||||
const size = Number(media.size) || 0;
|
||||
if (size <= 0) {
|
||||
if (size <= 0 || !storagePrefix) {
|
||||
imagesFailed += 1;
|
||||
continue;
|
||||
}
|
||||
@@ -1978,6 +1978,27 @@ export class LegacyMigrateService {
|
||||
};
|
||||
}
|
||||
|
||||
private async tryResolveStoragePrefix(
|
||||
oldBizId: number,
|
||||
oldSlug: string | null,
|
||||
probeMedia: Array<{ id: number; fileName: string }>,
|
||||
): Promise<string | null> {
|
||||
if (!probeMedia.length) return null;
|
||||
try {
|
||||
return await this.legacySourceS3.resolveBusinessPrefix(
|
||||
oldBizId,
|
||||
oldSlug,
|
||||
probeMedia,
|
||||
);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
console.warn(
|
||||
`Legacy migrate: skipping image copy for business ${oldBizId}: ${message}`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async copyLegacyMedia(input: {
|
||||
businessId: bigint;
|
||||
oldMedia: OldMediaRow;
|
||||
|
||||
@@ -126,10 +126,12 @@ export class CategoryTechnicalFormAiService {
|
||||
|
||||
قوانین:
|
||||
- بین ۵ تا ۱۱ فیلد کاربردی تولید کن که خریداران برای محصولات این دسته نیاز دارند (سیستم همیشه «سایر ویژگیها» را اضافه میکند).
|
||||
- از ترکیب text، textarea، select، multi_select و multi_value استفاده کن.
|
||||
- فیلدهای select و multi_select باید ۳ تا ۸ گزینه واقعی داشته باشند.
|
||||
- ترجیحاً از type=text استفاده کن تا فروشنده بتواند هر مقدار دلخواهی را آزادانه وارد کند.
|
||||
- از select و multi_select حتیالمقدور استفاده نکن. فقط وقتی مجموعه گزینهها واقعاً ثابت، کوتاه و استاندارد است (مثلاً بله/خیر یا حداکثر چند گزینه قطعی) مجازند؛ برای ظرفیت، توان، ابعاد، مدل، جنس، رنگ آزاد و مشابه آنها همیشه text بگذار.
|
||||
- اگر بین text و select مردد هستی، حتماً text انتخاب کن.
|
||||
- فیلدهای select و multi_select (در صورت استفاده) باید ۳ تا ۸ گزینه واقعی داشته باشند.
|
||||
- multi_value یک لیست تکراری از مقادیر متنی آزاد است (بدون گزینه) برای مقادیر کوتاه چندتایی.
|
||||
- text برای مقادیر کوتاه (ابعاد، وزن، سال مدل).
|
||||
- text برای مقادیر کوتاه (ابعاد، وزن، سال مدل، ظرفیت، توان).
|
||||
- textarea برای مشخصات طولانیتر (ویژگیها، دستورالعمل نگهداری).
|
||||
- اگر برای این دسته مناسب است، فیلدهای «ابعاد» و «وزن» را بهصورت text اضافه کن (واحد متریک رایج ایران را در برچسب بیاور، مثلاً سانتیمتر / کیلوگرم). اگر برای دسته بیمعناست، اضافه نکن.
|
||||
- مشخصات مهم خریدار را الزامی علامت بزن.
|
||||
|
||||
@@ -57,6 +57,12 @@ export class InvoiceKeyPointInputDto {
|
||||
text!: string;
|
||||
}
|
||||
|
||||
export class InvoicePaymentMethodInputDto {
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
text!: string;
|
||||
}
|
||||
|
||||
export class InvoiceAccountInputDto {
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
@@ -114,6 +120,12 @@ export class CreateInvoiceDto {
|
||||
@Type(() => InvoiceKeyPointInputDto)
|
||||
keyPoints?: InvoiceKeyPointInputDto[];
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => InvoicePaymentMethodInputDto)
|
||||
paymentMethods?: InvoicePaymentMethodInputDto[];
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@@ -134,7 +146,7 @@ export class UpdateInvoiceStatusDto {
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
/** Full content replace for an existing invoice (items / key points / accounts). */
|
||||
/** Full content replace for an existing invoice (items / key points / payment methods / accounts). */
|
||||
export class UpdateInvoiceDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@@ -169,6 +181,12 @@ export class UpdateInvoiceDto {
|
||||
@Type(() => InvoiceKeyPointInputDto)
|
||||
keyPoints?: InvoiceKeyPointInputDto[];
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => InvoicePaymentMethodInputDto)
|
||||
paymentMethods?: InvoicePaymentMethodInputDto[];
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@@ -337,6 +355,12 @@ export class CreateInvoiceTemplateDto {
|
||||
@Type(() => InvoiceKeyPointInputDto)
|
||||
keyPoints?: InvoiceKeyPointInputDto[];
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => InvoicePaymentMethodInputDto)
|
||||
paymentMethods?: InvoicePaymentMethodInputDto[];
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@@ -373,6 +397,12 @@ export class UpdateInvoiceTemplateDto {
|
||||
@Type(() => InvoiceKeyPointInputDto)
|
||||
keyPoints?: InvoiceKeyPointInputDto[];
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => InvoicePaymentMethodInputDto)
|
||||
paymentMethods?: InvoicePaymentMethodInputDto[];
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
InvoiceAccountInputDto,
|
||||
InvoiceItemInputDto,
|
||||
InvoiceKeyPointInputDto,
|
||||
InvoicePaymentMethodInputDto,
|
||||
InvoiceTemplateItemInputDto,
|
||||
ListInvoicesDto,
|
||||
UpdateInvoiceDto,
|
||||
@@ -250,12 +251,14 @@ export class InvoicesService {
|
||||
user: { select: { id: true, firstName: true, lastName: true, cellNumber: true } },
|
||||
items: { orderBy: [{ sortOrder: 'asc' as const }, { id: 'asc' as const }] },
|
||||
keyPoints: { orderBy: [{ sortOrder: 'asc' as const }, { id: 'asc' as const }] },
|
||||
paymentMethods: { orderBy: [{ sortOrder: 'asc' as const }, { id: 'asc' as const }] },
|
||||
accounts: { orderBy: [{ sortOrder: 'asc' as const }, { id: 'asc' as const }] },
|
||||
};
|
||||
|
||||
private readonly invoiceTemplateInclude = {
|
||||
items: { orderBy: [{ sortOrder: 'asc' as const }, { id: 'asc' as const }] },
|
||||
keyPoints: { orderBy: [{ sortOrder: 'asc' as const }, { id: 'asc' as const }] },
|
||||
paymentMethods: { orderBy: [{ sortOrder: 'asc' as const }, { id: 'asc' as const }] },
|
||||
accounts: { orderBy: [{ sortOrder: 'asc' as const }, { id: 'asc' as const }] },
|
||||
};
|
||||
|
||||
@@ -271,6 +274,18 @@ export class InvoicesService {
|
||||
};
|
||||
}
|
||||
|
||||
private serializePaymentMethod(row: {
|
||||
id: bigint;
|
||||
text: string;
|
||||
sortOrder: number;
|
||||
}) {
|
||||
return {
|
||||
id: row.id.toString(),
|
||||
text: row.text,
|
||||
sortOrder: row.sortOrder,
|
||||
};
|
||||
}
|
||||
|
||||
private serializeAccount(row: {
|
||||
id: bigint;
|
||||
bankName: string;
|
||||
@@ -343,6 +358,11 @@ export class InvoicesService {
|
||||
text: string;
|
||||
sortOrder: number;
|
||||
}>;
|
||||
paymentMethods?: Array<{
|
||||
id: bigint;
|
||||
text: string;
|
||||
sortOrder: number;
|
||||
}>;
|
||||
accounts?: Array<{
|
||||
id: bigint;
|
||||
bankName: string;
|
||||
@@ -372,6 +392,10 @@ export class InvoicesService {
|
||||
includeNested && row.keyPoints
|
||||
? row.keyPoints.map((kp) => this.serializeKeyPoint(kp))
|
||||
: undefined,
|
||||
paymentMethods:
|
||||
includeNested && row.paymentMethods
|
||||
? row.paymentMethods.map((pm) => this.serializePaymentMethod(pm))
|
||||
: undefined,
|
||||
accounts:
|
||||
includeNested && row.accounts
|
||||
? row.accounts.map((acc) => this.serializeAccount(acc))
|
||||
@@ -488,6 +512,11 @@ export class InvoicesService {
|
||||
text: string;
|
||||
sortOrder: number;
|
||||
}>;
|
||||
paymentMethods?: Array<{
|
||||
id: bigint;
|
||||
text: string;
|
||||
sortOrder: number;
|
||||
}>;
|
||||
accounts?: Array<{
|
||||
id: bigint;
|
||||
bankName: string;
|
||||
@@ -564,6 +593,10 @@ export class InvoicesService {
|
||||
includeNested && row.keyPoints
|
||||
? row.keyPoints.map((kp) => this.serializeKeyPoint(kp))
|
||||
: undefined,
|
||||
paymentMethods:
|
||||
includeNested && row.paymentMethods
|
||||
? row.paymentMethods.map((pm) => this.serializePaymentMethod(pm))
|
||||
: undefined,
|
||||
accounts:
|
||||
includeNested && row.accounts
|
||||
? row.accounts.map((acc) => this.serializeAccount(acc))
|
||||
@@ -581,6 +614,14 @@ export class InvoicesService {
|
||||
return { text };
|
||||
}
|
||||
|
||||
private normalizePaymentMethodInput(paymentMethod: InvoicePaymentMethodInputDto) {
|
||||
const text = paymentMethod.text.trim();
|
||||
if (!text) {
|
||||
throw new BadRequestException('Each payment method requires text');
|
||||
}
|
||||
return { text };
|
||||
}
|
||||
|
||||
private normalizeAccountInput(account: InvoiceAccountInputDto) {
|
||||
const bankName = account.bankName.trim();
|
||||
if (!bankName) {
|
||||
@@ -793,6 +834,9 @@ export class InvoicesService {
|
||||
|
||||
const items = dto.items.map((item) => this.normalizeTemplateItemInput(item));
|
||||
const keyPoints = (dto.keyPoints ?? []).map((kp) => this.normalizeKeyPointInput(kp));
|
||||
const paymentMethods = (dto.paymentMethods ?? []).map((pm) =>
|
||||
this.normalizePaymentMethodInput(pm),
|
||||
);
|
||||
const accounts = (dto.accounts ?? []).map((acc) => this.normalizeAccountInput(acc));
|
||||
|
||||
const row = await this.prisma.invoiceTemplate.create({
|
||||
@@ -819,6 +863,12 @@ export class InvoicesService {
|
||||
sortOrder: index,
|
||||
})),
|
||||
},
|
||||
paymentMethods: {
|
||||
create: paymentMethods.map((pm, index) => ({
|
||||
text: pm.text,
|
||||
sortOrder: index,
|
||||
})),
|
||||
},
|
||||
accounts: {
|
||||
create: accounts.map((acc, index) => ({
|
||||
bankName: acc.bankName,
|
||||
@@ -856,6 +906,10 @@ export class InvoicesService {
|
||||
dto.keyPoints !== undefined
|
||||
? dto.keyPoints.map((kp) => this.normalizeKeyPointInput(kp))
|
||||
: null;
|
||||
const replacePaymentMethods =
|
||||
dto.paymentMethods !== undefined
|
||||
? dto.paymentMethods.map((pm) => this.normalizePaymentMethodInput(pm))
|
||||
: null;
|
||||
const replaceAccounts =
|
||||
dto.accounts !== undefined
|
||||
? dto.accounts.map((acc) => this.normalizeAccountInput(acc))
|
||||
@@ -868,6 +922,9 @@ export class InvoicesService {
|
||||
if (replaceKeyPoints !== null) {
|
||||
await tx.invoiceTemplateKeyPoint.deleteMany({ where: { templateId } });
|
||||
}
|
||||
if (replacePaymentMethods !== null) {
|
||||
await tx.invoiceTemplatePaymentMethod.deleteMany({ where: { templateId } });
|
||||
}
|
||||
if (replaceAccounts !== null) {
|
||||
await tx.invoiceTemplateAccount.deleteMany({ where: { templateId } });
|
||||
}
|
||||
@@ -905,6 +962,16 @@ export class InvoicesService {
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(replacePaymentMethods !== null
|
||||
? {
|
||||
paymentMethods: {
|
||||
create: replacePaymentMethods.map((pm, index) => ({
|
||||
text: pm.text,
|
||||
sortOrder: index,
|
||||
})),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(replaceAccounts !== null
|
||||
? {
|
||||
accounts: {
|
||||
@@ -1116,6 +1183,9 @@ export class InvoicesService {
|
||||
|
||||
const items = dto.items.map((item) => this.normalizeTemplateItemInput(item));
|
||||
const keyPoints = (dto.keyPoints ?? []).map((kp) => this.normalizeKeyPointInput(kp));
|
||||
const paymentMethods = (dto.paymentMethods ?? []).map((pm) =>
|
||||
this.normalizePaymentMethodInput(pm),
|
||||
);
|
||||
const accounts = (dto.accounts ?? []).map((acc) => this.normalizeAccountInput(acc));
|
||||
|
||||
const row = await this.prisma.invoiceTemplate.create({
|
||||
@@ -1143,6 +1213,12 @@ export class InvoicesService {
|
||||
sortOrder: index,
|
||||
})),
|
||||
},
|
||||
paymentMethods: {
|
||||
create: paymentMethods.map((pm, index) => ({
|
||||
text: pm.text,
|
||||
sortOrder: index,
|
||||
})),
|
||||
},
|
||||
accounts: {
|
||||
create: accounts.map((acc, index) => ({
|
||||
bankName: acc.bankName,
|
||||
@@ -1183,6 +1259,10 @@ export class InvoicesService {
|
||||
dto.keyPoints !== undefined
|
||||
? dto.keyPoints.map((kp) => this.normalizeKeyPointInput(kp))
|
||||
: null;
|
||||
const replacePaymentMethods =
|
||||
dto.paymentMethods !== undefined
|
||||
? dto.paymentMethods.map((pm) => this.normalizePaymentMethodInput(pm))
|
||||
: null;
|
||||
const replaceAccounts =
|
||||
dto.accounts !== undefined
|
||||
? dto.accounts.map((acc) => this.normalizeAccountInput(acc))
|
||||
@@ -1195,6 +1275,9 @@ export class InvoicesService {
|
||||
if (replaceKeyPoints !== null) {
|
||||
await tx.invoiceTemplateKeyPoint.deleteMany({ where: { templateId } });
|
||||
}
|
||||
if (replacePaymentMethods !== null) {
|
||||
await tx.invoiceTemplatePaymentMethod.deleteMany({ where: { templateId } });
|
||||
}
|
||||
if (replaceAccounts !== null) {
|
||||
await tx.invoiceTemplateAccount.deleteMany({ where: { templateId } });
|
||||
}
|
||||
@@ -1232,6 +1315,16 @@ export class InvoicesService {
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(replacePaymentMethods !== null
|
||||
? {
|
||||
paymentMethods: {
|
||||
create: replacePaymentMethods.map((pm, index) => ({
|
||||
text: pm.text,
|
||||
sortOrder: index,
|
||||
})),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(replaceAccounts !== null
|
||||
? {
|
||||
accounts: {
|
||||
@@ -1304,6 +1397,7 @@ export class InvoicesService {
|
||||
: null,
|
||||
items: serialized.items,
|
||||
keyPoints: serialized.keyPoints,
|
||||
paymentMethods: serialized.paymentMethods,
|
||||
accounts: serialized.accounts,
|
||||
subtotal: serialized.subtotal,
|
||||
total: serialized.total,
|
||||
@@ -1465,6 +1559,9 @@ export class InvoicesService {
|
||||
|
||||
const items = dto.items.map((item) => this.normalizeItemInput(item));
|
||||
const keyPoints = (dto.keyPoints ?? []).map((kp) => this.normalizeKeyPointInput(kp));
|
||||
const paymentMethods = (dto.paymentMethods ?? []).map((pm) =>
|
||||
this.normalizePaymentMethodInput(pm),
|
||||
);
|
||||
const accounts = (dto.accounts ?? []).map((acc) => this.normalizeAccountInput(acc));
|
||||
const invoiceTemplateId = dto.invoiceTemplateId?.trim()
|
||||
? BigInt(dto.invoiceTemplateId)
|
||||
@@ -1514,6 +1611,12 @@ export class InvoicesService {
|
||||
sortOrder: index,
|
||||
})),
|
||||
},
|
||||
paymentMethods: {
|
||||
create: paymentMethods.map((pm, index) => ({
|
||||
text: pm.text,
|
||||
sortOrder: index,
|
||||
})),
|
||||
},
|
||||
accounts: {
|
||||
create: accounts.map((acc, index) => ({
|
||||
bankName: acc.bankName,
|
||||
@@ -1600,6 +1703,9 @@ export class InvoicesService {
|
||||
|
||||
const items = dto.items.map((item) => this.normalizeItemInput(item));
|
||||
const keyPoints = (dto.keyPoints ?? []).map((kp) => this.normalizeKeyPointInput(kp));
|
||||
const paymentMethods = (dto.paymentMethods ?? []).map((pm) =>
|
||||
this.normalizePaymentMethodInput(pm),
|
||||
);
|
||||
const accounts = (dto.accounts ?? []).map((acc) => this.normalizeAccountInput(acc));
|
||||
|
||||
let userId: bigint | undefined;
|
||||
@@ -1629,6 +1735,7 @@ export class InvoicesService {
|
||||
const row = await this.prisma.$transaction(async (tx) => {
|
||||
await tx.invoiceItem.deleteMany({ where: { invoiceId } });
|
||||
await tx.invoiceKeyPoint.deleteMany({ where: { invoiceId } });
|
||||
await tx.invoicePaymentMethod.deleteMany({ where: { invoiceId } });
|
||||
await tx.invoiceAccount.deleteMany({ where: { invoiceId } });
|
||||
|
||||
return tx.invoice.update({
|
||||
@@ -1657,6 +1764,12 @@ export class InvoicesService {
|
||||
sortOrder: index,
|
||||
})),
|
||||
},
|
||||
paymentMethods: {
|
||||
create: paymentMethods.map((pm, index) => ({
|
||||
text: pm.text,
|
||||
sortOrder: index,
|
||||
})),
|
||||
},
|
||||
accounts: {
|
||||
create: accounts.map((acc, index) => ({
|
||||
bankName: acc.bankName,
|
||||
|
||||
@@ -60,13 +60,23 @@ export class LegacySourceS3Service implements OnModuleDestroy {
|
||||
const slug = slugHint.trim();
|
||||
add(`${slug}_${oldBusinessId}`);
|
||||
add(`${slug.replace(/-/g, '')}_${oldBusinessId}`);
|
||||
add(`${slug.replace(/_/g, '')}_${oldBusinessId}`);
|
||||
add(`${slug.replace(/_/g, '-')}_${oldBusinessId}`);
|
||||
}
|
||||
|
||||
const listed = await this.listTopLevelPrefixes();
|
||||
for (const prefix of listed) {
|
||||
if (prefix.endsWith(`_${oldBusinessId}`)) {
|
||||
add(prefix);
|
||||
// ListBucket may be denied on the old bucket; slug candidates + HeadObject still work.
|
||||
try {
|
||||
const listed = await this.listTopLevelPrefixes();
|
||||
for (const prefix of listed) {
|
||||
if (prefix.endsWith(`_${oldBusinessId}`)) {
|
||||
add(prefix);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
console.warn(
|
||||
`Legacy S3 list prefixes failed for business ${oldBusinessId}; using slug candidates only: ${message}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!candidates.length) {
|
||||
@@ -75,7 +85,8 @@ export class LegacySourceS3Service implements OnModuleDestroy {
|
||||
);
|
||||
}
|
||||
|
||||
const sample = probes.slice(0, 12);
|
||||
// Spread probes so early missing files don't hide a valid prefix.
|
||||
const sample = this.pickProbeSample(probes, 12);
|
||||
let bestPrefix = candidates[0];
|
||||
let bestHits = -1;
|
||||
|
||||
@@ -96,6 +107,16 @@ export class LegacySourceS3Service implements OnModuleDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
if (bestHits <= 0 && sample.length > 0) {
|
||||
for (const prefix of candidates) {
|
||||
if (await this.prefixHasAnyObject(prefix)) {
|
||||
bestPrefix = prefix;
|
||||
bestHits = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestHits <= 0 && sample.length > 0) {
|
||||
throw new ServiceUnavailableException(
|
||||
`Old S3 files not found for business ${oldBusinessId} (tried: ${candidates.join(', ')}).`,
|
||||
@@ -106,6 +127,35 @@ export class LegacySourceS3Service implements OnModuleDestroy {
|
||||
return bestPrefix;
|
||||
}
|
||||
|
||||
private pickProbeSample<T>(items: T[], n: number): T[] {
|
||||
if (items.length <= n) return [...items];
|
||||
const out: T[] = [];
|
||||
const seen = new Set<number>();
|
||||
for (let i = 0; i < n; i++) {
|
||||
const idx = Math.floor((i * (items.length - 1)) / Math.max(n - 1, 1));
|
||||
if (seen.has(idx)) continue;
|
||||
seen.add(idx);
|
||||
out.push(items[idx]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private async prefixHasAnyObject(prefix: string): Promise<boolean> {
|
||||
const { client, bucket } = this.getClient();
|
||||
try {
|
||||
const res = await client.send(
|
||||
new ListObjectsV2Command({
|
||||
Bucket: bucket,
|
||||
Prefix: `${prefix}/`,
|
||||
MaxKeys: 1,
|
||||
}),
|
||||
);
|
||||
return (res.Contents?.length ?? 0) > 0;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async getObjectBuffer(key: string): Promise<Buffer> {
|
||||
const { client, bucket } = this.getClient();
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user