159 lines
4.0 KiB
TypeScript
159 lines
4.0 KiB
TypeScript
|
|
/**
|
||
|
|
* Repository model for Stack.Gallery Registry
|
||
|
|
*/
|
||
|
|
|
||
|
|
import * as plugins from '../plugins.ts';
|
||
|
|
import type { IRepository, TRepositoryVisibility, TRegistryProtocol } from '../interfaces/auth.interfaces.ts';
|
||
|
|
import { getDb } from './db.ts';
|
||
|
|
|
||
|
|
@plugins.smartdata.Collection(() => getDb())
|
||
|
|
export class Repository
|
||
|
|
extends plugins.smartdata.SmartDataDbDoc<Repository, Repository>
|
||
|
|
implements IRepository
|
||
|
|
{
|
||
|
|
@plugins.smartdata.unI()
|
||
|
|
public id: string = '';
|
||
|
|
|
||
|
|
@plugins.smartdata.svDb()
|
||
|
|
@plugins.smartdata.index()
|
||
|
|
public organizationId: string = '';
|
||
|
|
|
||
|
|
@plugins.smartdata.svDb()
|
||
|
|
@plugins.smartdata.searchable()
|
||
|
|
public name: string = '';
|
||
|
|
|
||
|
|
@plugins.smartdata.svDb()
|
||
|
|
public description?: string;
|
||
|
|
|
||
|
|
@plugins.smartdata.svDb()
|
||
|
|
@plugins.smartdata.index()
|
||
|
|
public protocol: TRegistryProtocol = 'npm';
|
||
|
|
|
||
|
|
@plugins.smartdata.svDb()
|
||
|
|
@plugins.smartdata.index()
|
||
|
|
public visibility: TRepositoryVisibility = 'private';
|
||
|
|
|
||
|
|
@plugins.smartdata.svDb()
|
||
|
|
public storageNamespace: string = '';
|
||
|
|
|
||
|
|
@plugins.smartdata.svDb()
|
||
|
|
public downloadCount: number = 0;
|
||
|
|
|
||
|
|
@plugins.smartdata.svDb()
|
||
|
|
public starCount: number = 0;
|
||
|
|
|
||
|
|
@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 = '';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create a new repository
|
||
|
|
*/
|
||
|
|
public static async createRepository(data: {
|
||
|
|
organizationId: string;
|
||
|
|
name: string;
|
||
|
|
description?: string;
|
||
|
|
protocol: TRegistryProtocol;
|
||
|
|
visibility?: TRepositoryVisibility;
|
||
|
|
createdById: string;
|
||
|
|
}): Promise<Repository> {
|
||
|
|
// Validate name
|
||
|
|
const nameRegex = /^[a-z0-9]([a-z0-9._-]*[a-z0-9])?$/;
|
||
|
|
if (!nameRegex.test(data.name.toLowerCase())) {
|
||
|
|
throw new Error('Repository name must be lowercase alphanumeric with optional dots, hyphens, or underscores');
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check for duplicate name in org + protocol
|
||
|
|
const existing = await Repository.getInstance({
|
||
|
|
organizationId: data.organizationId,
|
||
|
|
name: data.name.toLowerCase(),
|
||
|
|
protocol: data.protocol,
|
||
|
|
});
|
||
|
|
|
||
|
|
if (existing) {
|
||
|
|
throw new Error('Repository with this name and protocol already exists');
|
||
|
|
}
|
||
|
|
|
||
|
|
const repo = new Repository();
|
||
|
|
repo.id = await Repository.getNewId();
|
||
|
|
repo.organizationId = data.organizationId;
|
||
|
|
repo.name = data.name.toLowerCase();
|
||
|
|
repo.description = data.description;
|
||
|
|
repo.protocol = data.protocol;
|
||
|
|
repo.visibility = data.visibility || 'private';
|
||
|
|
repo.storageNamespace = `${data.protocol}/${data.organizationId}/${data.name.toLowerCase()}`;
|
||
|
|
repo.createdById = data.createdById;
|
||
|
|
repo.createdAt = new Date();
|
||
|
|
repo.updatedAt = new Date();
|
||
|
|
await repo.save();
|
||
|
|
return repo;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Find repository by org, name, and protocol
|
||
|
|
*/
|
||
|
|
public static async findByName(
|
||
|
|
organizationId: string,
|
||
|
|
name: string,
|
||
|
|
protocol: TRegistryProtocol
|
||
|
|
): Promise<Repository | null> {
|
||
|
|
return await Repository.getInstance({
|
||
|
|
organizationId,
|
||
|
|
name: name.toLowerCase(),
|
||
|
|
protocol,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get all repositories in an organization
|
||
|
|
*/
|
||
|
|
public static async getOrgRepositories(organizationId: string): Promise<Repository[]> {
|
||
|
|
return await Repository.getInstances({
|
||
|
|
organizationId,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get all public repositories
|
||
|
|
*/
|
||
|
|
public static async getPublicRepositories(protocol?: TRegistryProtocol): Promise<Repository[]> {
|
||
|
|
const query: Record<string, unknown> = { visibility: 'public' };
|
||
|
|
if (protocol) {
|
||
|
|
query.protocol = protocol;
|
||
|
|
}
|
||
|
|
return await Repository.getInstances(query);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Increment download count
|
||
|
|
*/
|
||
|
|
public async incrementDownloads(): Promise<void> {
|
||
|
|
this.downloadCount += 1;
|
||
|
|
await this.save();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get full path (org/repo)
|
||
|
|
*/
|
||
|
|
public getFullPath(orgName: string): string {
|
||
|
|
return `${orgName}/${this.name}`;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Lifecycle hook
|
||
|
|
*/
|
||
|
|
public async beforeSave(): Promise<void> {
|
||
|
|
this.updatedAt = new Date();
|
||
|
|
if (!this.id) {
|
||
|
|
this.id = await Repository.getNewId();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|