mirror of
https://github.com/xtr-dev/rondevu-server.git
synced 2025-12-13 04:13:25 +00:00
v0.5.0: Service discovery and FQN format refactoring
- Changed service FQN format: service:version@username (colon instead of @) - Added service discovery: direct lookup, random selection, paginated queries - Updated parseServiceFqn to handle optional username for discovery - Removed UUID privacy layer (service_index table) - Updated storage interface with discovery methods (discoverServices, getRandomService, getServiceByFqn) - Removed deprecated methods (getServiceByUuid, queryService, listServicesForUsername, findServicesByName, touchUsername, batchCreateServices) - Updated API routes: /services/:fqn with three modes (direct, random, paginated) - Changed offer/answer/ICE routes to offer-specific: /services/:fqn/offers/:offerId/* - Added extracted fields to services table (service_name, version, username) for efficient discovery - Created migration 0007 to update schema and migrate existing data - Added discovery indexes for performance 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -192,31 +192,32 @@ export function validateUsername(username: string): { valid: boolean; error?: st
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates service FQN format (service-name@version)
|
||||
* Service name: reverse domain notation (com.example.service)
|
||||
* Validates service FQN format (service:version@username or service:version)
|
||||
* Service name: lowercase alphanumeric with dots/dashes (e.g., chat, file-share, com.example.chat)
|
||||
* Version: semantic versioning (1.0.0, 2.1.3-beta, etc.)
|
||||
* Username: optional, lowercase alphanumeric with dashes
|
||||
*/
|
||||
export function validateServiceFqn(fqn: string): { valid: boolean; error?: string } {
|
||||
if (typeof fqn !== 'string') {
|
||||
return { valid: false, error: 'Service FQN must be a string' };
|
||||
}
|
||||
|
||||
// Split into service name and version
|
||||
const parts = fqn.split('@');
|
||||
if (parts.length !== 2) {
|
||||
return { valid: false, error: 'Service FQN must be in format: service-name@version' };
|
||||
// Parse the FQN
|
||||
const parsed = parseServiceFqn(fqn);
|
||||
if (!parsed) {
|
||||
return { valid: false, error: 'Service FQN must be in format: service:version[@username]' };
|
||||
}
|
||||
|
||||
const [serviceName, version] = parts;
|
||||
const { serviceName, version, username } = parsed;
|
||||
|
||||
// Validate service name (reverse domain notation)
|
||||
const serviceNameRegex = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/;
|
||||
// Validate service name (alphanumeric with dots/dashes)
|
||||
const serviceNameRegex = /^[a-z0-9]([a-z0-9.-]*[a-z0-9])?$/;
|
||||
if (!serviceNameRegex.test(serviceName)) {
|
||||
return { valid: false, error: 'Service name must be reverse domain notation (e.g., com.example.service)' };
|
||||
return { valid: false, error: 'Service name must be lowercase alphanumeric with optional dots/dashes' };
|
||||
}
|
||||
|
||||
if (serviceName.length < 3 || serviceName.length > 128) {
|
||||
return { valid: false, error: 'Service name must be 3-128 characters' };
|
||||
if (serviceName.length < 1 || serviceName.length > 128) {
|
||||
return { valid: false, error: 'Service name must be 1-128 characters' };
|
||||
}
|
||||
|
||||
// Validate version (semantic versioning)
|
||||
@@ -225,6 +226,14 @@ export function validateServiceFqn(fqn: string): { valid: boolean; error?: strin
|
||||
return { valid: false, error: 'Version must be semantic versioning (e.g., 1.0.0, 2.1.3-beta)' };
|
||||
}
|
||||
|
||||
// Validate username if present
|
||||
if (username) {
|
||||
const usernameCheck = validateUsername(username);
|
||||
if (!usernameCheck.valid) {
|
||||
return usernameCheck;
|
||||
}
|
||||
}
|
||||
|
||||
return { valid: true };
|
||||
}
|
||||
|
||||
@@ -270,15 +279,41 @@ export function isVersionCompatible(requested: string, available: string): boole
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse service FQN into service name and version
|
||||
* Parse service FQN into components
|
||||
* Formats supported:
|
||||
* - service:version@username (e.g., "chat:1.0.0@alice")
|
||||
* - service:version (e.g., "chat:1.0.0") for discovery
|
||||
*/
|
||||
export function parseServiceFqn(fqn: string): { serviceName: string; version: string } | null {
|
||||
const parts = fqn.split('@');
|
||||
if (parts.length !== 2) return null;
|
||||
export function parseServiceFqn(fqn: string): { serviceName: string; version: string; username: string | null } | null {
|
||||
if (!fqn || typeof fqn !== 'string') return null;
|
||||
|
||||
// Check if username is present
|
||||
const atIndex = fqn.lastIndexOf('@');
|
||||
let serviceVersion: string;
|
||||
let username: string | null = null;
|
||||
|
||||
if (atIndex > 0) {
|
||||
// Format: service:version@username
|
||||
serviceVersion = fqn.substring(0, atIndex);
|
||||
username = fqn.substring(atIndex + 1);
|
||||
} else {
|
||||
// Format: service:version (no username)
|
||||
serviceVersion = fqn;
|
||||
}
|
||||
|
||||
// Split service:version
|
||||
const colonIndex = serviceVersion.indexOf(':');
|
||||
if (colonIndex <= 0) return null; // No colon or colon at start
|
||||
|
||||
const serviceName = serviceVersion.substring(0, colonIndex);
|
||||
const version = serviceVersion.substring(colonIndex + 1);
|
||||
|
||||
if (!serviceName || !version) return null;
|
||||
|
||||
return {
|
||||
serviceName: parts[0],
|
||||
version: parts[1],
|
||||
serviceName,
|
||||
version,
|
||||
username,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user