- Add .gitignore to exclude node_modules, dist, logs, and system files - Add comprehensive project documentation including README, deployment guide, and development setup - Add .kiro project specifications for amazon-product-bar-extension, appwrite-cloud-storage, appwrite-userid-repair, blacklist-feature, and enhanced-item-management - Add .kiro steering documents for product, structure, styling, and tech guidelines - Add VSCode settings configuration for consistent development environment - Add manifest.json and babel/vite configuration for extension build setup - Add complete source code implementation including AppWrite integration, storage managers, UI components, and services - Add comprehensive test suite with Jest configuration and 30+ test files covering all major modules - Add test HTML files for integration testing and validation - Add coverage reports and build validation scripts - Add AppWrite setup and repair documentation for database schema management - Add migration guides and responsive accessibility implementation documentation - Establish foundation for Amazon product bar extension with full feature set including blacklist management, enhanced item workflows, and real-time synchronization
348 lines
13 KiB
JavaScript
348 lines
13 KiB
JavaScript
/**
|
|
* Schema Analyzer for AppWrite Collections
|
|
*
|
|
* Analyzes AppWrite collections to identify missing userId attributes and permission issues.
|
|
* Validates attribute properties and provides comprehensive reporting.
|
|
*
|
|
* Requirements: 1.1, 1.5
|
|
*/
|
|
|
|
/**
|
|
* @typedef {Object} UserIdAttributeProperties
|
|
* @property {string} type - Attribute type (should be 'string')
|
|
* @property {number} size - Maximum character length (should be 255)
|
|
* @property {boolean} required - Whether the attribute is required
|
|
* @property {boolean} array - Whether the attribute is an array
|
|
*/
|
|
|
|
/**
|
|
* @typedef {Object} CollectionPermissions
|
|
* @property {string[]} create - Create permissions
|
|
* @property {string[]} read - Read permissions
|
|
* @property {string[]} update - Update permissions
|
|
* @property {string[]} delete - Delete permissions
|
|
*/
|
|
|
|
/**
|
|
* @typedef {Object} CollectionAnalysisResult
|
|
* @property {string} collectionId - Collection identifier
|
|
* @property {boolean} exists - Whether the collection exists
|
|
* @property {boolean} hasUserId - Whether userId attribute exists
|
|
* @property {UserIdAttributeProperties|null} userIdProperties - Properties of userId attribute
|
|
* @property {CollectionPermissions} permissions - Current collection permissions
|
|
* @property {string[]} issues - List of identified issues
|
|
* @property {'critical'|'warning'|'info'} severity - Issue severity level
|
|
*/
|
|
|
|
export class SchemaAnalyzer {
|
|
/**
|
|
* @param {Object} appWriteManager - AppWrite manager instance
|
|
*/
|
|
constructor(appWriteManager) {
|
|
this.appWriteManager = appWriteManager;
|
|
this.databases = appWriteManager.databases;
|
|
}
|
|
|
|
/**
|
|
* Analyzes a single collection's schema for userId attribute and permissions
|
|
* @param {string} collectionId - Collection to analyze
|
|
* @returns {Promise<CollectionAnalysisResult>} Analysis result
|
|
*/
|
|
async analyzeCollection(collectionId) {
|
|
const result = {
|
|
collectionId,
|
|
exists: false,
|
|
hasUserId: false,
|
|
userIdProperties: null,
|
|
permissions: { create: [], read: [], update: [], delete: [] },
|
|
issues: [],
|
|
severity: 'info',
|
|
analyzedAt: new Date()
|
|
};
|
|
|
|
try {
|
|
// Check if collection exists by trying to get its attributes
|
|
const collection = await this.databases.getCollection(
|
|
this.appWriteManager.config.databaseId,
|
|
collectionId
|
|
);
|
|
|
|
result.exists = true;
|
|
|
|
// Get collection attributes to check for userId
|
|
const attributes = collection.attributes || [];
|
|
const userIdAttribute = attributes.find(attr => attr.key === 'userId');
|
|
|
|
if (userIdAttribute) {
|
|
result.hasUserId = true;
|
|
result.userIdProperties = {
|
|
type: userIdAttribute.type,
|
|
size: userIdAttribute.size,
|
|
required: userIdAttribute.required,
|
|
array: userIdAttribute.array || false,
|
|
key: userIdAttribute.key,
|
|
status: userIdAttribute.status
|
|
};
|
|
|
|
// Validate attribute properties
|
|
const isValid = await this.validateAttributeProperties(userIdAttribute);
|
|
if (!isValid) {
|
|
result.issues.push('userId attribute has incorrect properties');
|
|
result.severity = 'warning';
|
|
}
|
|
} else {
|
|
result.hasUserId = false;
|
|
result.issues.push('userId attribute is missing');
|
|
result.severity = 'critical';
|
|
}
|
|
|
|
// Check permissions
|
|
result.permissions = await this.checkPermissions(collectionId);
|
|
|
|
// Validate permissions
|
|
const expectedPermissions = {
|
|
create: ['users'],
|
|
read: ['user:$userId'],
|
|
update: ['user:$userId'],
|
|
delete: ['user:$userId']
|
|
};
|
|
|
|
for (const [action, expected] of Object.entries(expectedPermissions)) {
|
|
const current = result.permissions[action] || [];
|
|
if (!this._arraysEqual(current, expected)) {
|
|
result.issues.push(`${action} permissions are incorrect`);
|
|
if (result.severity === 'info') {
|
|
result.severity = 'warning';
|
|
}
|
|
}
|
|
}
|
|
|
|
} catch (error) {
|
|
if (error.code === 404) {
|
|
result.exists = false;
|
|
result.issues.push('Collection does not exist');
|
|
result.severity = 'critical';
|
|
} else {
|
|
result.issues.push(`Analysis failed: ${error.message}`);
|
|
result.severity = 'critical';
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Analyzes all required collections for schema issues
|
|
* @returns {Promise<CollectionAnalysisResult[]>} Array of analysis results
|
|
*/
|
|
async analyzeAllCollections() {
|
|
const collectionIds = Object.values(this.appWriteManager.config.collections);
|
|
const results = [];
|
|
|
|
for (const collectionId of collectionIds) {
|
|
try {
|
|
const result = await this.analyzeCollection(collectionId);
|
|
results.push(result);
|
|
} catch (error) {
|
|
// Create error result for failed analysis
|
|
const errorResult = {
|
|
collectionId,
|
|
exists: false,
|
|
hasUserId: false,
|
|
userIdProperties: null,
|
|
permissions: { create: [], read: [], update: [], delete: [] },
|
|
issues: [`Analysis failed: ${error.message}`],
|
|
severity: 'critical',
|
|
analyzedAt: new Date()
|
|
};
|
|
results.push(errorResult);
|
|
}
|
|
}
|
|
|
|
// Sort results by severity (critical first, then warning, then info)
|
|
results.sort((a, b) => {
|
|
const severityOrder = { critical: 0, warning: 1, info: 2 };
|
|
return severityOrder[a.severity] - severityOrder[b.severity];
|
|
});
|
|
|
|
return results;
|
|
}
|
|
|
|
/**
|
|
* Validates that userId attribute has correct properties
|
|
* @param {Object} attribute - Attribute object from AppWrite
|
|
* @returns {boolean} Whether attribute properties are correct
|
|
*/
|
|
async validateAttributeProperties(attribute) {
|
|
if (!attribute) return false;
|
|
|
|
const expectedProperties = {
|
|
type: 'string',
|
|
size: 255,
|
|
required: true,
|
|
array: false
|
|
};
|
|
|
|
return (
|
|
attribute.type === expectedProperties.type &&
|
|
attribute.size === expectedProperties.size &&
|
|
attribute.required === expectedProperties.required &&
|
|
(attribute.array || false) === expectedProperties.array
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Checks collection permissions for proper configuration
|
|
* @param {string} collectionId - Collection to check
|
|
* @returns {Promise<CollectionPermissions>} Current permissions
|
|
*/
|
|
async checkPermissions(collectionId) {
|
|
try {
|
|
const collection = await this.databases.getCollection(
|
|
this.appWriteManager.config.databaseId,
|
|
collectionId
|
|
);
|
|
|
|
return {
|
|
create: collection.documentSecurity ? collection.$permissions?.create || [] : [],
|
|
read: collection.documentSecurity ? collection.$permissions?.read || [] : [],
|
|
update: collection.documentSecurity ? collection.$permissions?.update || [] : [],
|
|
delete: collection.documentSecurity ? collection.$permissions?.delete || [] : []
|
|
};
|
|
} catch (error) {
|
|
console.warn(`Failed to check permissions for collection ${collectionId}:`, error.message);
|
|
return { create: [], read: [], update: [], delete: [] };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper method to compare arrays for equality
|
|
* @param {Array} arr1 - First array
|
|
* @param {Array} arr2 - Second array
|
|
* @returns {boolean} Whether arrays are equal
|
|
* @private
|
|
*/
|
|
_arraysEqual(arr1, arr2) {
|
|
if (arr1.length !== arr2.length) return false;
|
|
|
|
const sorted1 = [...arr1].sort();
|
|
const sorted2 = [...arr2].sort();
|
|
|
|
return sorted1.every((val, index) => val === sorted2[index]);
|
|
}
|
|
|
|
/**
|
|
* Categorizes analysis results by severity level
|
|
* @param {CollectionAnalysisResult[]} results - Analysis results to categorize
|
|
* @returns {Object} Categorized results with counts
|
|
*/
|
|
categorizeIssuesBySeverity(results) {
|
|
const categorized = {
|
|
critical: [],
|
|
warning: [],
|
|
info: [],
|
|
counts: {
|
|
critical: 0,
|
|
warning: 0,
|
|
info: 0,
|
|
total: results.length
|
|
}
|
|
};
|
|
|
|
for (const result of results) {
|
|
categorized[result.severity].push(result);
|
|
categorized.counts[result.severity]++;
|
|
}
|
|
|
|
return categorized;
|
|
}
|
|
|
|
/**
|
|
* Generates comprehensive analysis report with collection names and details
|
|
* @param {CollectionAnalysisResult[]} results - Analysis results
|
|
* @returns {Object} Comprehensive analysis report
|
|
*/
|
|
generateComprehensiveReport(results) {
|
|
const categorized = this.categorizeIssuesBySeverity(results);
|
|
const timestamp = new Date();
|
|
|
|
const report = {
|
|
timestamp,
|
|
totalCollections: results.length,
|
|
summary: {
|
|
collectionsWithIssues: results.filter(r => r.issues.length > 0).length,
|
|
collectionsWithoutUserId: results.filter(r => !r.hasUserId).length,
|
|
collectionsWithIncorrectPermissions: results.filter(r =>
|
|
r.issues.some(issue => issue.includes('permissions'))
|
|
).length,
|
|
nonExistentCollections: results.filter(r => !r.exists).length
|
|
},
|
|
categorized,
|
|
detailedResults: results.map(result => ({
|
|
collectionId: result.collectionId,
|
|
status: result.severity,
|
|
exists: result.exists,
|
|
hasUserId: result.hasUserId,
|
|
issues: result.issues,
|
|
userIdProperties: result.userIdProperties,
|
|
permissions: result.permissions,
|
|
analyzedAt: result.analyzedAt
|
|
})),
|
|
recommendations: this._generateRecommendations(categorized)
|
|
};
|
|
|
|
return report;
|
|
}
|
|
|
|
/**
|
|
* Generates recommendations based on analysis results
|
|
* @param {Object} categorized - Categorized analysis results
|
|
* @returns {string[]} Array of recommendations
|
|
* @private
|
|
*/
|
|
_generateRecommendations(categorized) {
|
|
const recommendations = [];
|
|
|
|
if (categorized.counts.critical > 0) {
|
|
recommendations.push(
|
|
`${categorized.counts.critical} collection(s) have critical issues that must be addressed immediately`
|
|
);
|
|
|
|
const missingCollections = categorized.critical.filter(r => !r.exists);
|
|
if (missingCollections.length > 0) {
|
|
recommendations.push(
|
|
`Create missing collections: ${missingCollections.map(r => r.collectionId).join(', ')}`
|
|
);
|
|
}
|
|
|
|
const missingUserId = categorized.critical.filter(r => r.exists && !r.hasUserId);
|
|
if (missingUserId.length > 0) {
|
|
recommendations.push(
|
|
`Add userId attribute to collections: ${missingUserId.map(r => r.collectionId).join(', ')}`
|
|
);
|
|
}
|
|
}
|
|
|
|
if (categorized.counts.warning > 0) {
|
|
recommendations.push(
|
|
`${categorized.counts.warning} collection(s) have warnings that should be reviewed`
|
|
);
|
|
|
|
const permissionIssues = categorized.warning.filter(r =>
|
|
r.issues.some(issue => issue.includes('permissions'))
|
|
);
|
|
if (permissionIssues.length > 0) {
|
|
recommendations.push(
|
|
`Review and fix permissions for collections: ${permissionIssues.map(r => r.collectionId).join(', ')}`
|
|
);
|
|
}
|
|
}
|
|
|
|
if (categorized.counts.critical === 0 && categorized.counts.warning === 0) {
|
|
recommendations.push('All collections are properly configured');
|
|
}
|
|
|
|
return recommendations;
|
|
}
|
|
} |