Refactor code structure for improved readability and maintainability
This commit is contained in:
14
ts/cli.ts
14
ts/cli.ts
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
import * as plugins from './plugins.ts';
|
||||
import { StackGalleryRegistry, createRegistryFromEnv } from './registry.ts';
|
||||
import { StackGalleryRegistry, createRegistryFromEnv, createRegistryFromEnvFile } from './registry.ts';
|
||||
import { initDb } from './models/db.ts';
|
||||
import { User, Organization, OrganizationMember, Repository } from './models/index.ts';
|
||||
import { AuthService } from './services/auth.service.ts';
|
||||
@@ -13,9 +13,17 @@ export async function runCli(): Promise<void> {
|
||||
|
||||
// Server command
|
||||
smartcliInstance.addCommand('server').subscribe(async (argsParsed) => {
|
||||
console.log('Starting Stack.Gallery Registry...');
|
||||
const isEphemeral = argsParsed.ephemeral || argsParsed.e;
|
||||
|
||||
const registry = createRegistryFromEnv();
|
||||
console.log('Starting Stack.Gallery Registry...');
|
||||
if (isEphemeral) {
|
||||
console.log('[CLI] Ephemeral mode enabled - loading config from .nogit/env.json');
|
||||
}
|
||||
|
||||
// Use env file in ephemeral/dev mode, otherwise use environment variables
|
||||
const registry = isEphemeral
|
||||
? await createRegistryFromEnvFile()
|
||||
: createRegistryFromEnv();
|
||||
await registry.start();
|
||||
|
||||
// Handle shutdown gracefully
|
||||
|
||||
@@ -4,13 +4,10 @@
|
||||
|
||||
import * as plugins from '../plugins.ts';
|
||||
import type { IApiToken, ITokenScope, TRegistryProtocol } from '../interfaces/auth.interfaces.ts';
|
||||
import { getDb } from './db.ts';
|
||||
import { db } from './db.ts';
|
||||
|
||||
@plugins.smartdata.Collection(() => getDb())
|
||||
export class ApiToken
|
||||
extends plugins.smartdata.SmartDataDbDoc<ApiToken, ApiToken>
|
||||
implements IApiToken
|
||||
{
|
||||
@plugins.smartdata.Collection(() => db)
|
||||
export class ApiToken extends plugins.smartdata.SmartDataDbDoc<ApiToken, ApiToken> implements IApiToken {
|
||||
@plugins.smartdata.unI()
|
||||
public id: string = '';
|
||||
|
||||
|
||||
@@ -4,13 +4,10 @@
|
||||
|
||||
import * as plugins from '../plugins.ts';
|
||||
import type { IAuditLog, TAuditAction, TAuditResourceType } from '../interfaces/audit.interfaces.ts';
|
||||
import { getDb } from './db.ts';
|
||||
import { db } from './db.ts';
|
||||
|
||||
@plugins.smartdata.Collection(() => getDb())
|
||||
export class AuditLog
|
||||
extends plugins.smartdata.SmartDataDbDoc<AuditLog, AuditLog>
|
||||
implements IAuditLog
|
||||
{
|
||||
@plugins.smartdata.Collection(() => db)
|
||||
export class AuditLog extends plugins.smartdata.SmartDataDbDoc<AuditLog, AuditLog> implements IAuditLog {
|
||||
@plugins.smartdata.unI()
|
||||
public id: string = '';
|
||||
|
||||
|
||||
@@ -1,50 +1,65 @@
|
||||
/**
|
||||
* Database connection singleton
|
||||
*
|
||||
* SmartData models need a db reference at class definition time via lazy getter.
|
||||
* The actual .init() is called later when the server starts.
|
||||
*/
|
||||
|
||||
import * as plugins from '../plugins.ts';
|
||||
import { User } from './user.ts';
|
||||
|
||||
let dbInstance: plugins.smartdata.SmartdataDb | null = null;
|
||||
// Database instance - created lazily in initDb()
|
||||
// The @Collection(() => db) decorator uses a lazy getter, so db can be undefined
|
||||
// until initDb() is called. Default admin is seeded after db.init() completes.
|
||||
export let db: plugins.smartdata.SmartdataDb;
|
||||
|
||||
let isInitialized = false;
|
||||
|
||||
/**
|
||||
* Initialize database connection
|
||||
*/
|
||||
export async function initDb(config: {
|
||||
mongoDbUrl: string;
|
||||
mongoDbName?: string;
|
||||
}): Promise<plugins.smartdata.SmartdataDb> {
|
||||
if (dbInstance) {
|
||||
return dbInstance;
|
||||
export async function initDb(
|
||||
mongoDbUrl: string,
|
||||
mongoDbName?: string
|
||||
): Promise<plugins.smartdata.SmartdataDb> {
|
||||
if (isInitialized && db) {
|
||||
return db;
|
||||
}
|
||||
|
||||
dbInstance = new plugins.smartdata.SmartdataDb({
|
||||
mongoDbUrl: config.mongoDbUrl,
|
||||
mongoDbName: config.mongoDbName || 'stackregistry',
|
||||
// Create the database instance with actual configuration
|
||||
db = new plugins.smartdata.SmartdataDb({
|
||||
mongoDbUrl: mongoDbUrl,
|
||||
mongoDbName: mongoDbName || 'stackregistry',
|
||||
});
|
||||
|
||||
await dbInstance.init();
|
||||
await db.init();
|
||||
isInitialized = true;
|
||||
console.log('Database connected successfully');
|
||||
|
||||
return dbInstance;
|
||||
// Seed default admin user if none exists
|
||||
try {
|
||||
await User.seedDefaultAdmin();
|
||||
} catch (err) {
|
||||
console.warn('[Database] Failed to seed default admin:', err);
|
||||
}
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database instance (must call initDb first)
|
||||
* Get database instance (for backward compatibility)
|
||||
*/
|
||||
export function getDb(): plugins.smartdata.SmartdataDb {
|
||||
if (!dbInstance) {
|
||||
throw new Error('Database not initialized. Call initDb() first.');
|
||||
}
|
||||
return dbInstance;
|
||||
return db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close database connection
|
||||
*/
|
||||
export async function closeDb(): Promise<void> {
|
||||
if (dbInstance) {
|
||||
await dbInstance.close();
|
||||
dbInstance = null;
|
||||
if (db && isInitialized) {
|
||||
await db.close();
|
||||
isInitialized = false;
|
||||
console.log('Database connection closed');
|
||||
}
|
||||
}
|
||||
@@ -53,5 +68,5 @@ export async function closeDb(): Promise<void> {
|
||||
* Check if database is connected
|
||||
*/
|
||||
export function isDbConnected(): boolean {
|
||||
return dbInstance !== null;
|
||||
return isInitialized;
|
||||
}
|
||||
|
||||
@@ -4,13 +4,10 @@
|
||||
|
||||
import * as plugins from '../plugins.ts';
|
||||
import type { IOrganizationMember, TOrganizationRole } from '../interfaces/auth.interfaces.ts';
|
||||
import { getDb } from './db.ts';
|
||||
import { db } from './db.ts';
|
||||
|
||||
@plugins.smartdata.Collection(() => getDb())
|
||||
export class OrganizationMember
|
||||
extends plugins.smartdata.SmartDataDbDoc<OrganizationMember, OrganizationMember>
|
||||
implements IOrganizationMember
|
||||
{
|
||||
@plugins.smartdata.Collection(() => db)
|
||||
export class OrganizationMember extends plugins.smartdata.SmartDataDbDoc<OrganizationMember, OrganizationMember> implements IOrganizationMember {
|
||||
@plugins.smartdata.unI()
|
||||
public id: string = '';
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import type {
|
||||
IOrganizationSettings,
|
||||
TOrganizationPlan,
|
||||
} from '../interfaces/auth.interfaces.ts';
|
||||
import { getDb } from './db.ts';
|
||||
import { db } from './db.ts';
|
||||
|
||||
const DEFAULT_SETTINGS: IOrganizationSettings = {
|
||||
requireMfa: false,
|
||||
@@ -17,11 +17,8 @@ const DEFAULT_SETTINGS: IOrganizationSettings = {
|
||||
allowedProtocols: ['oci', 'npm', 'maven', 'cargo', 'composer', 'pypi', 'rubygems'],
|
||||
};
|
||||
|
||||
@plugins.smartdata.Collection(() => getDb())
|
||||
export class Organization
|
||||
extends plugins.smartdata.SmartDataDbDoc<Organization, Organization>
|
||||
implements IOrganization
|
||||
{
|
||||
@plugins.smartdata.Collection(() => db)
|
||||
export class Organization extends plugins.smartdata.SmartDataDbDoc<Organization, Organization> implements IOrganization {
|
||||
@plugins.smartdata.unI()
|
||||
public id: string = '';
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@ import type {
|
||||
IProtocolMetadata,
|
||||
} from '../interfaces/package.interfaces.ts';
|
||||
import type { TRegistryProtocol } from '../interfaces/auth.interfaces.ts';
|
||||
import { getDb } from './db.ts';
|
||||
import { db } from './db.ts';
|
||||
|
||||
@plugins.smartdata.Collection(() => getDb())
|
||||
@plugins.smartdata.Collection(() => db)
|
||||
export class Package extends plugins.smartdata.SmartDataDbDoc<Package, Package> implements IPackage {
|
||||
@plugins.smartdata.unI()
|
||||
public id: string = ''; // {protocol}:{org}:{name}
|
||||
|
||||
@@ -4,13 +4,10 @@
|
||||
|
||||
import * as plugins from '../plugins.ts';
|
||||
import type { IRepositoryPermission, TRepositoryRole } from '../interfaces/auth.interfaces.ts';
|
||||
import { getDb } from './db.ts';
|
||||
import { db } from './db.ts';
|
||||
|
||||
@plugins.smartdata.Collection(() => getDb())
|
||||
export class RepositoryPermission
|
||||
extends plugins.smartdata.SmartDataDbDoc<RepositoryPermission, RepositoryPermission>
|
||||
implements IRepositoryPermission
|
||||
{
|
||||
@plugins.smartdata.Collection(() => db)
|
||||
export class RepositoryPermission extends plugins.smartdata.SmartDataDbDoc<RepositoryPermission, RepositoryPermission> implements IRepositoryPermission {
|
||||
@plugins.smartdata.unI()
|
||||
public id: string = '';
|
||||
|
||||
|
||||
@@ -4,13 +4,10 @@
|
||||
|
||||
import * as plugins from '../plugins.ts';
|
||||
import type { IRepository, TRepositoryVisibility, TRegistryProtocol } from '../interfaces/auth.interfaces.ts';
|
||||
import { getDb } from './db.ts';
|
||||
import { db } from './db.ts';
|
||||
|
||||
@plugins.smartdata.Collection(() => getDb())
|
||||
export class Repository
|
||||
extends plugins.smartdata.SmartDataDbDoc<Repository, Repository>
|
||||
implements IRepository
|
||||
{
|
||||
@plugins.smartdata.Collection(() => db)
|
||||
export class Repository extends plugins.smartdata.SmartDataDbDoc<Repository, Repository> implements IRepository {
|
||||
@plugins.smartdata.unI()
|
||||
public id: string = '';
|
||||
|
||||
|
||||
@@ -4,13 +4,10 @@
|
||||
|
||||
import * as plugins from '../plugins.ts';
|
||||
import type { ISession } from '../interfaces/auth.interfaces.ts';
|
||||
import { getDb } from './db.ts';
|
||||
import { db } from './db.ts';
|
||||
|
||||
@plugins.smartdata.Collection(() => getDb())
|
||||
export class Session
|
||||
extends plugins.smartdata.SmartDataDbDoc<Session, Session>
|
||||
implements ISession
|
||||
{
|
||||
@plugins.smartdata.Collection(() => db)
|
||||
export class Session extends plugins.smartdata.SmartDataDbDoc<Session, Session> implements ISession {
|
||||
@plugins.smartdata.unI()
|
||||
public id: string = '';
|
||||
|
||||
|
||||
@@ -4,13 +4,10 @@
|
||||
|
||||
import * as plugins from '../plugins.ts';
|
||||
import type { ITeamMember, TTeamRole } from '../interfaces/auth.interfaces.ts';
|
||||
import { getDb } from './db.ts';
|
||||
import { db } from './db.ts';
|
||||
|
||||
@plugins.smartdata.Collection(() => getDb())
|
||||
export class TeamMember
|
||||
extends plugins.smartdata.SmartDataDbDoc<TeamMember, TeamMember>
|
||||
implements ITeamMember
|
||||
{
|
||||
@plugins.smartdata.Collection(() => db)
|
||||
export class TeamMember extends plugins.smartdata.SmartDataDbDoc<TeamMember, TeamMember> implements ITeamMember {
|
||||
@plugins.smartdata.unI()
|
||||
public id: string = '';
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
|
||||
import * as plugins from '../plugins.ts';
|
||||
import type { ITeam } from '../interfaces/auth.interfaces.ts';
|
||||
import { getDb } from './db.ts';
|
||||
import { db } from './db.ts';
|
||||
|
||||
@plugins.smartdata.Collection(() => getDb())
|
||||
@plugins.smartdata.Collection(() => db)
|
||||
export class Team extends plugins.smartdata.SmartDataDbDoc<Team, Team> implements ITeam {
|
||||
@plugins.smartdata.unI()
|
||||
public id: string = '';
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
|
||||
import * as plugins from '../plugins.ts';
|
||||
import type { IUser, TUserStatus } from '../interfaces/auth.interfaces.ts';
|
||||
import { getDb } from './db.ts';
|
||||
import { db } from './db.ts';
|
||||
|
||||
@plugins.smartdata.Collection(() => getDb())
|
||||
@plugins.smartdata.Collection(() => db)
|
||||
export class User extends plugins.smartdata.SmartDataDbDoc<User, User> implements IUser {
|
||||
@plugins.smartdata.unI()
|
||||
public id: string = '';
|
||||
@@ -112,4 +112,119 @@ export class User extends plugins.smartdata.SmartDataDbDoc<User, User> implement
|
||||
this.id = await User.getNewId();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user is active (status === 'active')
|
||||
*/
|
||||
public get isActive(): boolean {
|
||||
return this.status === 'active';
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for isPlatformAdmin for backward compatibility
|
||||
*/
|
||||
public get isSystemAdmin(): boolean {
|
||||
return this.isPlatformAdmin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find user by ID
|
||||
*/
|
||||
public static async findById(id: string): Promise<User | null> {
|
||||
return await User.getInstance({ id });
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify password against stored hash
|
||||
*/
|
||||
public async verifyPassword(password: string): Promise<boolean> {
|
||||
if (!this.passwordHash) return false;
|
||||
|
||||
const [saltHex, expectedHash] = this.passwordHash.split(':');
|
||||
if (!saltHex || !expectedHash) return false;
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const data = encoder.encode(saltHex + password);
|
||||
|
||||
let hash = data;
|
||||
for (let i = 0; i < 10000; i++) {
|
||||
hash = new Uint8Array(await crypto.subtle.digest('SHA-256', hash));
|
||||
}
|
||||
|
||||
const hashHex = Array.from(hash)
|
||||
.map((b) => b.toString(16).padStart(2, '0'))
|
||||
.join('');
|
||||
|
||||
return hashHex === expectedHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash a password for storage
|
||||
*/
|
||||
public static async hashPassword(password: string): Promise<string> {
|
||||
const salt = crypto.getRandomValues(new Uint8Array(16));
|
||||
const saltHex = Array.from(salt)
|
||||
.map((b) => b.toString(16).padStart(2, '0'))
|
||||
.join('');
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const data = encoder.encode(saltHex + password);
|
||||
|
||||
let hash = data;
|
||||
for (let i = 0; i < 10000; i++) {
|
||||
hash = new Uint8Array(await crypto.subtle.digest('SHA-256', hash));
|
||||
}
|
||||
|
||||
const hashHex = Array.from(hash)
|
||||
.map((b) => b.toString(16).padStart(2, '0'))
|
||||
.join('');
|
||||
|
||||
return `${saltHex}:${hashHex}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the default admin user if no admin exists
|
||||
*/
|
||||
public static async seedDefaultAdmin(): Promise<User | null> {
|
||||
const adminEmail = Deno.env.get('ADMIN_EMAIL') || 'admin@stack.gallery';
|
||||
const adminPassword = Deno.env.get('ADMIN_PASSWORD') || 'admin';
|
||||
|
||||
// Check if any platform admin exists
|
||||
const existingAdmin = await User.getInstance({ isPlatformAdmin: true });
|
||||
if (existingAdmin) {
|
||||
console.log('[User] Platform admin already exists, skipping seed');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if admin email already exists
|
||||
const existingUser = await User.findByEmail(adminEmail);
|
||||
if (existingUser) {
|
||||
console.log('[User] User with admin email already exists, promoting to admin');
|
||||
existingUser.isPlatformAdmin = true;
|
||||
existingUser.status = 'active';
|
||||
await existingUser.save();
|
||||
return existingUser;
|
||||
}
|
||||
|
||||
// Create new admin user
|
||||
console.log('[User] Creating default admin user:', adminEmail);
|
||||
const passwordHash = await User.hashPassword(adminPassword);
|
||||
|
||||
const admin = new User();
|
||||
admin.id = await User.getNewId();
|
||||
admin.email = adminEmail.toLowerCase();
|
||||
admin.username = 'admin';
|
||||
admin.passwordHash = passwordHash;
|
||||
admin.displayName = 'System Administrator';
|
||||
admin.status = 'active';
|
||||
admin.emailVerified = true;
|
||||
admin.isPlatformAdmin = true;
|
||||
admin.createdAt = new Date();
|
||||
admin.updatedAt = new Date();
|
||||
|
||||
await admin.save();
|
||||
console.log('[User] Default admin user created successfully');
|
||||
|
||||
return admin;
|
||||
}
|
||||
}
|
||||
|
||||
107
ts/registry.ts
107
ts/registry.ts
@@ -154,18 +154,22 @@ export class StackGalleryRegistry {
|
||||
return await this.handleApiRequest(request);
|
||||
}
|
||||
|
||||
// Registry protocol endpoints
|
||||
// NPM: /-/..., /@scope/package, /package
|
||||
// Registry protocol endpoints (handled by smartregistry)
|
||||
// NPM: /-/..., /@scope/package (but not /packages which is UI route)
|
||||
// OCI: /v2/...
|
||||
// Maven: /maven2/...
|
||||
// PyPI: /simple/..., /pypi/...
|
||||
// Cargo: /api/v1/crates/...
|
||||
// Composer: /packages.json, /p/...
|
||||
// RubyGems: /api/v1/gems/..., /gems/...
|
||||
const registryPaths = ['/-/', '/v2/', '/maven2/', '/simple/', '/pypi/', '/api/v1/crates/', '/packages.json', '/p/', '/api/v1/gems/', '/gems/'];
|
||||
const isRegistryPath = registryPaths.some(p => path.startsWith(p)) ||
|
||||
(path.startsWith('/@') && !path.startsWith('/@stack'));
|
||||
|
||||
if (this.smartRegistry) {
|
||||
if (this.smartRegistry && isRegistryPath) {
|
||||
try {
|
||||
return await this.smartRegistry.handleRequest(request);
|
||||
const response = await this.smartRegistry.handleRequest(request);
|
||||
if (response) return response;
|
||||
} catch (error) {
|
||||
console.error('[StackGalleryRegistry] Request error:', error);
|
||||
return new Response(
|
||||
@@ -178,7 +182,56 @@ export class StackGalleryRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
return new Response('Not Found', { status: 404 });
|
||||
// Serve static UI files
|
||||
return await this.serveStaticFile(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serve static files from UI dist
|
||||
*/
|
||||
private async serveStaticFile(path: string): Promise<Response> {
|
||||
const uiDistPath = './ui/dist/registry-ui/browser';
|
||||
|
||||
// Map path to file
|
||||
let filePath = path === '/' ? '/index.html' : path;
|
||||
|
||||
// Content type mapping
|
||||
const contentTypes: Record<string, string> = {
|
||||
'.html': 'text/html',
|
||||
'.js': 'application/javascript',
|
||||
'.css': 'text/css',
|
||||
'.json': 'application/json',
|
||||
'.png': 'image/png',
|
||||
'.jpg': 'image/jpeg',
|
||||
'.svg': 'image/svg+xml',
|
||||
'.ico': 'image/x-icon',
|
||||
'.woff': 'font/woff',
|
||||
'.woff2': 'font/woff2',
|
||||
'.ttf': 'font/ttf',
|
||||
};
|
||||
|
||||
try {
|
||||
const fullPath = `${uiDistPath}${filePath}`;
|
||||
const file = await Deno.readFile(fullPath);
|
||||
const ext = filePath.substring(filePath.lastIndexOf('.'));
|
||||
const contentType = contentTypes[ext] || 'application/octet-stream';
|
||||
|
||||
return new Response(file, {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': contentType },
|
||||
});
|
||||
} catch {
|
||||
// For SPA routing, serve index.html for unknown paths
|
||||
try {
|
||||
const indexFile = await Deno.readFile(`${uiDistPath}/index.html`);
|
||||
return new Response(indexFile, {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'text/html' },
|
||||
});
|
||||
} catch {
|
||||
return new Response('Not Found', { status: 404 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -274,3 +327,47 @@ export function createRegistryFromEnv(): StackGalleryRegistry {
|
||||
|
||||
return new StackGalleryRegistry(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create registry from .nogit/env.json file (for local development)
|
||||
* Falls back to environment variables if file doesn't exist
|
||||
*/
|
||||
export async function createRegistryFromEnvFile(): Promise<StackGalleryRegistry> {
|
||||
const envPath = '.nogit/env.json';
|
||||
|
||||
try {
|
||||
const envText = await Deno.readTextFile(envPath);
|
||||
const env = JSON.parse(envText);
|
||||
|
||||
console.log('[StackGalleryRegistry] Loading config from .nogit/env.json');
|
||||
|
||||
// Build S3 endpoint from host/port/ssl settings
|
||||
const s3Protocol = env.S3_USESSL ? 'https' : 'http';
|
||||
const s3Endpoint = `${s3Protocol}://${env.S3_HOST || 'localhost'}:${env.S3_PORT || '9000'}`;
|
||||
|
||||
const config: IRegistryConfig = {
|
||||
mongoUrl: env.MONGODB_URL || `mongodb://${env.MONGODB_USER}:${env.MONGODB_PASS}@${env.MONGODB_HOST || 'localhost'}:${env.MONGODB_PORT || '27017'}/${env.MONGODB_NAME}?authSource=admin`,
|
||||
mongoDb: env.MONGODB_NAME || 'stackgallery',
|
||||
s3Endpoint: s3Endpoint,
|
||||
s3AccessKey: env.S3_ACCESSKEY || env.S3_ACCESS_KEY || 'minioadmin',
|
||||
s3SecretKey: env.S3_SECRETKEY || env.S3_SECRET_KEY || 'minioadmin',
|
||||
s3Bucket: env.S3_BUCKET || 'registry',
|
||||
s3Region: env.S3_REGION,
|
||||
host: env.HOST || '0.0.0.0',
|
||||
port: parseInt(env.PORT || '3000', 10),
|
||||
storagePath: env.STORAGE_PATH || 'packages',
|
||||
enableUpstreamCache: env.ENABLE_UPSTREAM_CACHE !== false,
|
||||
upstreamCacheExpiry: parseInt(env.UPSTREAM_CACHE_EXPIRY || '24', 10),
|
||||
jwtSecret: env.JWT_SECRET,
|
||||
};
|
||||
|
||||
return new StackGalleryRegistry(config);
|
||||
} catch (error) {
|
||||
if (error instanceof Deno.errors.NotFound) {
|
||||
console.log('[StackGalleryRegistry] No .nogit/env.json found, using environment variables');
|
||||
} else {
|
||||
console.warn('[StackGalleryRegistry] Error reading .nogit/env.json, falling back to env vars:', error);
|
||||
}
|
||||
return createRegistryFromEnv();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,17 @@ export class AuthService {
|
||||
});
|
||||
|
||||
// Find user by email
|
||||
const user = await User.findByEmail(email);
|
||||
let user: User | null = null;
|
||||
try {
|
||||
user = await User.findByEmail(email);
|
||||
} catch (err) {
|
||||
console.error('[AuthService] Database error finding user:', err);
|
||||
return {
|
||||
success: false,
|
||||
errorCode: 'DATABASE_ERROR',
|
||||
errorMessage: 'Unable to verify credentials. Please try again.',
|
||||
};
|
||||
}
|
||||
if (!user) {
|
||||
await auditContext.logUserLogin('', false, 'User not found');
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user