feat: implement account settings and API tokens management
- 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.
This commit is contained in:
158
ts/models/repository.ts
Normal file
158
ts/models/repository.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user