Files
cnpmcore/app/port/controller/DownloadController.ts
MK (fengmk2) e5162f20aa fix: improve TypeScript type definitions across codebase (#844)
This commit enhances type safety and fixes type-related issues
throughout the project including:
- Updated type definitions in entities, repositories, and models
- Improved type annotations in services and controllers
- Fixed type issues in adapters and utilities
- Enhanced test file type definitions
- Added typings/index.d.ts for global type declarations

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-10-23 00:58:59 +08:00

141 lines
4.5 KiB
TypeScript

import {
HTTPController,
HTTPMethod,
HTTPMethodEnum,
HTTPParam,
Inject,
} from '@eggjs/tegg';
import { NotFoundError, UnprocessableEntityError } from 'egg-errors';
import { AbstractController } from './AbstractController.ts';
import {
FULLNAME_REG_STRING,
getScopeAndName,
} from '../../common/PackageUtil.ts';
import dayjs from '../../common/dayjs.ts';
import type { PackageVersionDownloadRepository } from '../../repository/PackageVersionDownloadRepository.ts';
const DATE_FORMAT = 'YYYY-MM-DD';
@HTTPController()
export class DownloadController extends AbstractController {
@Inject()
private packageVersionDownloadRepository: PackageVersionDownloadRepository;
@HTTPMethod({
path: `/downloads/range/:range/:fullname(${FULLNAME_REG_STRING})`,
method: HTTPMethodEnum.GET,
})
async showPackageDownloads(
@HTTPParam() fullname: string,
@HTTPParam() range: string
) {
const [startDate, endDate] = this.checkAndGetRange(range);
const [scope, name] = getScopeAndName(fullname);
const pkg = await this.packageRepository.findPackage(scope, name);
if (!pkg) throw new NotFoundError(`${fullname} not found`);
const entities = await this.packageVersionDownloadRepository.query(
pkg.packageId,
startDate.toDate(),
endDate.toDate()
);
const days: Record<string, number> = {};
const versions: Record<string, { day: string; downloads: number }[]> = {};
for (const entity of entities) {
const yearMonth = String(entity.yearMonth);
const prefix = `${yearMonth.slice(0, 4)}-${yearMonth.slice(4, 6)}`;
for (let i = 1; i <= 31; i++) {
const day = String(i).padStart(2, '0');
const field = `d${day}` as keyof typeof entity;
const counter = entity[field] as number;
if (!counter) continue;
const date = `${prefix}-${day}`;
days[date] = (days[date] || 0) + counter;
if (entity.version === '*') {
// ignore sync data to versions
continue;
}
if (!versions[entity.version]) versions[entity.version] = [];
versions[entity.version].push({ day: date, downloads: counter });
}
}
const downloads: { day: string; downloads: number }[] = [];
for (const day of Object.keys(days).sort()) {
downloads.push({ day, downloads: days[day] });
}
return {
downloads,
versions,
};
}
@HTTPMethod({
path: '/downloads/:scope/:range',
method: HTTPMethodEnum.GET,
})
async showTotalDownloads(
@HTTPParam() scope: string,
@HTTPParam() range: string
) {
const [startDate, endDate] = this.checkAndGetRange(range);
const entities = await this.packageVersionDownloadRepository.query(
scope,
startDate.toDate(),
endDate.toDate()
);
const days: Record<string, number> = {};
for (const entity of entities) {
const yearMonth = String(entity.yearMonth);
const prefix = `${yearMonth.slice(0, 4)}-${yearMonth.slice(4, 6)}`;
for (let i = 1; i <= 31; i++) {
const day = String(i).padStart(2, '0');
const field = `d${day}` as keyof typeof entity;
const counter = entity[field] as number;
if (!counter) continue;
const date = `${prefix}-${day}`;
days[date] = (days[date] || 0) + counter;
}
}
const downloads: { day: string; downloads: number }[] = [];
for (const day of Object.keys(days).sort()) {
downloads.push({ day, downloads: days[day] });
}
return {
downloads,
};
}
private checkAndGetRange(range: string) {
const matchs = /^(\d{4}-\d{2}-\d{2}):(\d{4}-\d{2}-\d{2})$/.exec(range);
if (!matchs) {
throw new UnprocessableEntityError(
`range(${range}) format invalid, must be "${DATE_FORMAT}:${DATE_FORMAT}" style`
);
}
const start = matchs[1];
const end = matchs[2];
let startDate = dayjs(start, DATE_FORMAT, true);
let endDate = dayjs(end, DATE_FORMAT, true);
if (!startDate.isValid() || !endDate.isValid()) {
throw new UnprocessableEntityError(
`range(${range}) format invalid, must be "${DATE_FORMAT}:${DATE_FORMAT}" style`
);
}
if (endDate.isBefore(startDate)) {
const tmp = startDate;
startDate = endDate;
endDate = tmp;
}
// max range for one year
const maxDate = startDate.add(1, 'year');
if (endDate.isAfter(maxDate)) {
throw new UnprocessableEntityError(
`range(${range}) beyond the processable range, max up to "${maxDate.format(DATE_FORMAT)}"`
);
}
return [startDate, endDate];
}
}