- Added SettingsComponent for user profile management, including display name and password change functionality. - Introduced TokensComponent for managing API tokens, including creation and revocation. - Created LayoutComponent for consistent application layout with navigation and user information. - Established main application structure in index.html and main.ts. - Integrated Tailwind CSS for styling and responsive design. - Configured TypeScript settings for strict type checking and module resolution.
196 lines
5.0 KiB
TypeScript
196 lines
5.0 KiB
TypeScript
/**
|
|
* Package model for Stack.Gallery Registry
|
|
*/
|
|
|
|
import * as plugins from '../plugins.ts';
|
|
import type {
|
|
IPackage,
|
|
IPackageVersion,
|
|
IProtocolMetadata,
|
|
} from '../interfaces/package.interfaces.ts';
|
|
import type { TRegistryProtocol } from '../interfaces/auth.interfaces.ts';
|
|
import { getDb } from './db.ts';
|
|
|
|
@plugins.smartdata.Collection(() => getDb())
|
|
export class Package extends plugins.smartdata.SmartDataDbDoc<Package, Package> implements IPackage {
|
|
@plugins.smartdata.unI()
|
|
public id: string = ''; // {protocol}:{org}:{name}
|
|
|
|
@plugins.smartdata.svDb()
|
|
@plugins.smartdata.index()
|
|
public organizationId: string = '';
|
|
|
|
@plugins.smartdata.svDb()
|
|
@plugins.smartdata.index()
|
|
public repositoryId: string = '';
|
|
|
|
@plugins.smartdata.svDb()
|
|
@plugins.smartdata.index()
|
|
public protocol: TRegistryProtocol = 'npm';
|
|
|
|
@plugins.smartdata.svDb()
|
|
@plugins.smartdata.searchable()
|
|
@plugins.smartdata.index()
|
|
public name: string = '';
|
|
|
|
@plugins.smartdata.svDb()
|
|
@plugins.smartdata.searchable()
|
|
public description?: string;
|
|
|
|
@plugins.smartdata.svDb()
|
|
public versions: Record<string, IPackageVersion> = {};
|
|
|
|
@plugins.smartdata.svDb()
|
|
public distTags: Record<string, string> = {}; // e.g., { latest: "1.0.0" }
|
|
|
|
@plugins.smartdata.svDb()
|
|
public metadata: IProtocolMetadata = { type: 'npm' };
|
|
|
|
@plugins.smartdata.svDb()
|
|
@plugins.smartdata.index()
|
|
public isPrivate: boolean = true;
|
|
|
|
@plugins.smartdata.svDb()
|
|
public storageBytes: number = 0;
|
|
|
|
@plugins.smartdata.svDb()
|
|
@plugins.smartdata.index()
|
|
public downloadCount: number = 0;
|
|
|
|
@plugins.smartdata.svDb()
|
|
public starCount: number = 0;
|
|
|
|
@plugins.smartdata.svDb()
|
|
public cacheExpiresAt?: Date;
|
|
|
|
@plugins.smartdata.svDb()
|
|
@plugins.smartdata.index()
|
|
public createdAt: Date = new Date();
|
|
|
|
@plugins.smartdata.svDb()
|
|
public updatedAt: Date = new Date();
|
|
|
|
@plugins.smartdata.svDb()
|
|
@plugins.smartdata.index()
|
|
public createdById: string = '';
|
|
|
|
/**
|
|
* Generate package ID
|
|
*/
|
|
public static generateId(protocol: TRegistryProtocol, orgName: string, name: string): string {
|
|
return `${protocol}:${orgName}:${name}`;
|
|
}
|
|
|
|
/**
|
|
* Find package by ID
|
|
*/
|
|
public static async findById(id: string): Promise<Package | null> {
|
|
return await Package.getInstance({ id });
|
|
}
|
|
|
|
/**
|
|
* Find package by protocol, org, and name
|
|
*/
|
|
public static async findByName(
|
|
protocol: TRegistryProtocol,
|
|
orgName: string,
|
|
name: string
|
|
): Promise<Package | null> {
|
|
const id = Package.generateId(protocol, orgName, name);
|
|
return await Package.findById(id);
|
|
}
|
|
|
|
/**
|
|
* Get packages in an organization
|
|
*/
|
|
public static async getOrgPackages(organizationId: string): Promise<Package[]> {
|
|
return await Package.getInstances({ organizationId });
|
|
}
|
|
|
|
/**
|
|
* Search packages
|
|
*/
|
|
public static async search(
|
|
query: string,
|
|
options?: {
|
|
protocol?: TRegistryProtocol;
|
|
organizationId?: string;
|
|
isPrivate?: boolean;
|
|
limit?: number;
|
|
offset?: number;
|
|
}
|
|
): Promise<Package[]> {
|
|
const filter: Record<string, unknown> = {};
|
|
if (options?.protocol) filter.protocol = options.protocol;
|
|
if (options?.organizationId) filter.organizationId = options.organizationId;
|
|
if (options?.isPrivate !== undefined) filter.isPrivate = options.isPrivate;
|
|
|
|
// Simple text search - in production, would use MongoDB text index
|
|
const allPackages = await Package.getInstances(filter);
|
|
|
|
// Filter by query
|
|
const lowerQuery = query.toLowerCase();
|
|
const filtered = allPackages.filter(
|
|
(pkg) =>
|
|
pkg.name.toLowerCase().includes(lowerQuery) ||
|
|
pkg.description?.toLowerCase().includes(lowerQuery)
|
|
);
|
|
|
|
// Apply pagination
|
|
const offset = options?.offset || 0;
|
|
const limit = options?.limit || 50;
|
|
return filtered.slice(offset, offset + limit);
|
|
}
|
|
|
|
/**
|
|
* Add a new version
|
|
*/
|
|
public addVersion(version: IPackageVersion): void {
|
|
this.versions[version.version] = version;
|
|
this.storageBytes += version.size;
|
|
this.updatedAt = new Date();
|
|
}
|
|
|
|
/**
|
|
* Get specific version
|
|
*/
|
|
public getVersion(version: string): IPackageVersion | undefined {
|
|
return this.versions[version];
|
|
}
|
|
|
|
/**
|
|
* Get latest version
|
|
*/
|
|
public getLatestVersion(): IPackageVersion | undefined {
|
|
const latest = this.distTags['latest'];
|
|
if (latest) {
|
|
return this.versions[latest];
|
|
}
|
|
// Fallback to most recent
|
|
const versionList = Object.keys(this.versions);
|
|
if (versionList.length === 0) return undefined;
|
|
return this.versions[versionList[versionList.length - 1]];
|
|
}
|
|
|
|
/**
|
|
* Increment download count
|
|
*/
|
|
public async incrementDownloads(version?: string): Promise<void> {
|
|
this.downloadCount += 1;
|
|
if (version && this.versions[version]) {
|
|
this.versions[version].downloads += 1;
|
|
}
|
|
await this.save();
|
|
}
|
|
|
|
/**
|
|
* Lifecycle hook
|
|
*/
|
|
public async beforeSave(): Promise<void> {
|
|
this.updatedAt = new Date();
|
|
if (!this.id) {
|
|
this.id = await Package.getNewId();
|
|
}
|
|
}
|
|
}
|