tractatus/src/models/Resource.model.js
TheFlow 78ab5754f2 feat: add MongoDB models for core collections
Models Created (7/10):
- Document.model.js: Framework docs with quadrant classification
- BlogPost.model.js: AI-curated blog with moderation
- MediaInquiry.model.js: Press/media triage workflow
- ModerationQueue.model.js: Human oversight queue with priority
- User.model.js: Admin authentication with bcrypt
- CaseSubmission.model.js: Community case studies with AI review
- Resource.model.js: Curated directory with alignment scores

Features:
- Full CRUD operations for each model
- Tractatus quadrant integration
- AI analysis fields for curation
- Human approval workflows
- Status tracking and filtering
- Security (password hashing, sanitized returns)

Deferred (Phase 2-3):
- Citation.model.js
- Translation.model.js
- KohaDonation.model.js

Status: Core models complete, ready for Express server
2025-10-06 23:54:56 +13:00

221 lines
5.2 KiB
JavaScript

/**
* Resource Model
* Curated directory of aligned resources
*/
const { ObjectId } = require('mongodb');
const { getCollection } = require('../utils/db.util');
class Resource {
/**
* Create a new resource
*/
static async create(data) {
const collection = await getCollection('resources');
const resource = {
url: data.url,
title: data.title,
description: data.description,
category: data.category, // framework/tool/research/organization/educational
subcategory: data.subcategory,
alignment_score: data.alignment_score, // 0-1 alignment with Tractatus values
ai_analysis: {
summary: data.ai_analysis?.summary,
relevance: data.ai_analysis?.relevance,
quality_indicators: data.ai_analysis?.quality_indicators || [],
concerns: data.ai_analysis?.concerns || [],
claude_reasoning: data.ai_analysis?.claude_reasoning
},
status: data.status || 'pending', // pending/approved/rejected
reviewed_by: data.reviewed_by,
reviewed_at: data.reviewed_at,
tags: data.tags || [],
featured: data.featured || false,
added_at: new Date(),
last_checked: new Date()
};
const result = await collection.insertOne(resource);
return { ...resource, _id: result.insertedId };
}
/**
* Find resource by ID
*/
static async findById(id) {
const collection = await getCollection('resources');
return await collection.findOne({ _id: new ObjectId(id) });
}
/**
* Find resource by URL
*/
static async findByUrl(url) {
const collection = await getCollection('resources');
return await collection.findOne({ url });
}
/**
* Find approved resources
*/
static async findApproved(options = {}) {
const collection = await getCollection('resources');
const { limit = 50, skip = 0, category } = options;
const filter = { status: 'approved' };
if (category) filter.category = category;
return await collection
.find(filter)
.sort({ alignment_score: -1, added_at: -1 })
.skip(skip)
.limit(limit)
.toArray();
}
/**
* Find featured resources
*/
static async findFeatured(options = {}) {
const collection = await getCollection('resources');
const { limit = 10 } = options;
return await collection
.find({ status: 'approved', featured: true })
.sort({ alignment_score: -1 })
.limit(limit)
.toArray();
}
/**
* Find by category
*/
static async findByCategory(category, options = {}) {
const collection = await getCollection('resources');
const { limit = 30, skip = 0 } = options;
return await collection
.find({ category, status: 'approved' })
.sort({ alignment_score: -1 })
.skip(skip)
.limit(limit)
.toArray();
}
/**
* Find high-alignment pending resources
*/
static async findHighAlignment(options = {}) {
const collection = await getCollection('resources');
const { limit = 10 } = options;
return await collection
.find({
status: 'pending',
alignment_score: { $gte: 0.8 }
})
.sort({ alignment_score: -1 })
.limit(limit)
.toArray();
}
/**
* Update resource
*/
static async update(id, updates) {
const collection = await getCollection('resources');
const result = await collection.updateOne(
{ _id: new ObjectId(id) },
{ $set: updates }
);
return result.modifiedCount > 0;
}
/**
* Approve resource
*/
static async approve(id, reviewerId) {
const collection = await getCollection('resources');
const result = await collection.updateOne(
{ _id: new ObjectId(id) },
{
$set: {
status: 'approved',
reviewed_by: reviewerId,
reviewed_at: new Date()
}
}
);
return result.modifiedCount > 0;
}
/**
* Reject resource
*/
static async reject(id, reviewerId) {
const collection = await getCollection('resources');
const result = await collection.updateOne(
{ _id: new ObjectId(id) },
{
$set: {
status: 'rejected',
reviewed_by: reviewerId,
reviewed_at: new Date()
}
}
);
return result.modifiedCount > 0;
}
/**
* Mark as featured
*/
static async setFeatured(id, featured = true) {
const collection = await getCollection('resources');
const result = await collection.updateOne(
{ _id: new ObjectId(id) },
{ $set: { featured } }
);
return result.modifiedCount > 0;
}
/**
* Update last checked timestamp
*/
static async updateLastChecked(id) {
const collection = await getCollection('resources');
await collection.updateOne(
{ _id: new ObjectId(id) },
{ $set: { last_checked: new Date() } }
);
}
/**
* Count by status
*/
static async countByStatus(status) {
const collection = await getCollection('resources');
return await collection.countDocuments({ status });
}
/**
* Delete resource
*/
static async delete(id) {
const collection = await getCollection('resources');
const result = await collection.deleteOne({ _id: new ObjectId(id) });
return result.deletedCount > 0;
}
}
module.exports = Resource;