// Users Collection
{
_id: ObjectId,
email: String,
name: String,
subscription_tier: String, // 'free', 'premium'
created_at: Date
}
// Exams Collection
{
_id: ObjectId,
title: String,
content: Object,
author_id: ObjectId,
created_at: Date,
updated_at: Date
}
// Results Collection
{
_id: ObjectId,
exam_id: ObjectId,
student_id: ObjectId,
answers: Array,
score: Number,
completed_at: Date
}{
_id: ObjectId,
exam_id: ObjectId, // Reference to the exam
owner_id: ObjectId, // Original exam author
shared_with_id: ObjectId, // User receiving access
permission_level: String, // 'view', 'edit'
status: String, // 'pending', 'accepted', 'declined', 'revoked'
created_at: Date,
accepted_at: Date,
expires_at: Date, // Optional expiration
share_token: String, // Unique token for invite links
metadata: {
shared_by_name: String,
shared_by_email: String,
exam_title: String
}
}{
_id: ObjectId,
title: String,
content: Object,
author_id: ObjectId,
created_at: Date,
updated_at: Date,
// New fields
sharing_enabled: Boolean, // Author can disable sharing
shared_count: Number, // Track how many times shared
collaboration_history: [{ // Track changes when multiple editors
user_id: ObjectId,
action: String,
timestamp: Date,
changes: Object
}]
}# Create a share invitation
POST /api/exams/{exam_id}/share
Body: {
"email": "colleague@example.com",
"permission_level": "edit",
"expires_in_days": 30,
"message": "Please help me review this exam"
}
# List all shares for an exam (owner only)
GET /api/exams/{exam_id}/shares
# Accept/decline share invitation
POST /api/shares/{share_id}/respond
Body: {"action": "accept|decline"}
# Revoke a share (owner only)
DELETE /api/shares/{share_id}
# Get user's received shares
GET /api/user/shares/received
# Get user's sent shares
GET /api/user/shares/sent# Check user's permission for specific exam
GET /api/exams/{exam_id}/permissions
# Get all exams user has access to
GET /api/user/exams?include_shared=truefrom functools import wraps
from flask import request, jsonify
def require_exam_permission(permission_level):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
exam_id = kwargs.get('exam_id') or request.view_args.get('exam_id')
user_id = get_current_user_id()
if not has_exam_permission(user_id, exam_id, permission_level):
return jsonify({'error': 'Insufficient permissions'}), 403
return f(*args, **kwargs)
return decorated_function
return decorator
# Usage example
@app.route('/api/exams/<exam_id>/edit', methods=['PUT'])
@require_exam_permission('edit')
def update_exam(exam_id):
# Implementation here
passconst ShareModal = ({ examId, onClose }) => {
const [shareData, setShareData] = useState({
email: '',
permission: 'view',
expiresInDays: 30,
message: ''
});
const handleShare = async () => {
try {
await api.post(`/exams/${examId}/share`, shareData);
// Show success message
onClose();
} catch (error) {
// Handle premium requirement or other errors
}
};
return (
<Modal>
<form onSubmit={handleShare}>
<input
type="email"
placeholder="Colleague's email"
value={shareData.email}
onChange={(e) => setShareData({...shareData, email: e.target.value})}
/>
<select
value={shareData.permission}
onChange={(e) => setShareData({...shareData, permission: e.target.value})}
>
<option value="view">View Only</option>
<option value="edit">Edit Access</option>
</select>
<textarea
placeholder="Optional message"
value={shareData.message}
onChange={(e) => setShareData({...shareData, message: e.target.value})}
/>
<button type="submit">Send Invitation</button>
</form>
</Modal>
);
};const SharedExamsList = () => {
const [receivedShares, setReceivedShares] = useState([]);
const [sentShares, setSentShares] = useState([]);
return (
<div className="shares-dashboard">
<div className="received-shares">
<h3>Exams Shared With Me</h3>
{receivedShares.map(share => (
<ShareCard
key={share._id}
share={share}
type="received"
onAction={handleShareAction}
/>
))}
</div>
<div className="sent-shares">
<h3>My Shared Exams</h3>
{sentShares.map(share => (
<ShareCard
key={share._id}
share={share}
type="sent"
onAction={handleShareAction}
/>
))}
</div>
</div>
);
};class PermissionService:
@staticmethod
def has_exam_permission(user_id, exam_id, required_permission):
# Check if user is the owner
exam = db.exams.find_one({"_id": ObjectId(exam_id)})
if exam and exam['author_id'] == ObjectId(user_id):
return True
# Check shared permissions
share = db.exam_shares.find_one({
"exam_id": ObjectId(exam_id),
"shared_with_id": ObjectId(user_id),
"status": "accepted"
})
if not share:
return False
# Check permission hierarchy
user_permission = share['permission_level']
permission_hierarchy = {
'view': 1,
'edit': 2,
'owner': 3
}
return permission_hierarchy.get(user_permission, 0) >= permission_hierarchy.get(required_permission, 0)
@staticmethod
def create_share_invitation(owner_id, exam_id, email, permission_level, expires_in_days=30):
# Verify owner has premium subscription
owner = db.users.find_one({"_id": ObjectId(owner_id)})
if owner['subscription_tier'] != 'premium':
raise Exception("Premium subscription required for sharing")
# Find user by email
target_user = db.users.find_one({"email": email})
if not target_user:
raise Exception("User not found")
# Check if already shared
existing_share = db.exam_shares.find_one({
"exam_id": ObjectId(exam_id),
"shared_with_id": target_user['_id'],
"status": {"$in": ["pending", "accepted"]}
})
if existing_share:
raise Exception("Exam already shared with this user")
# Create share invitation
share_data = {
"exam_id": ObjectId(exam_id),
"owner_id": ObjectId(owner_id),
"shared_with_id": target_user['_id'],
"permission_level": permission_level,
"status": "pending",
"created_at": datetime.utcnow(),
"expires_at": datetime.utcnow() + timedelta(days=expires_in_days),
"share_token": generate_secure_token(),
"metadata": {
"shared_by_name": owner['name'],
"shared_by_email": owner['email'],
"exam_title": db.exams.find_one({"_id": ObjectId(exam_id)})['title']
}
}
result = db.exam_shares.insert_one(share_data)
# Send notification email
NotificationService.send_share_invitation(share_data)
return result.inserted_iddef validate_premium_feature(feature_name):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
user_id = get_current_user_id()
user = db.users.find_one({"_id": ObjectId(user_id)})
if user['subscription_tier'] != 'premium':
return jsonify({
'error': 'Premium subscription required',
'feature': feature_name,
'upgrade_url': '/upgrade'
}), 402 # Payment Required
return f(*args, **kwargs)
return decorated_function
return decorator
# Usage
@app.route('/api/exams/<exam_id>/share', methods=['POST'])
@validate_premium_feature('exam_sharing')
def create_exam_share(exam_id):
# Implementation
pass// Exam Shares Collection Indexes
db.exam_shares.createIndex({"exam_id": 1, "shared_with_id": 1});
db.exam_shares.createIndex({"shared_with_id": 1, "status": 1});
db.exam_shares.createIndex({"owner_id": 1});
db.exam_shares.createIndex({"expires_at": 1});
// Exams Collection Additional Index
db.exams.createIndex({"author_id": 1, "created_at": -1});This design provides a robust, scalable sharing system that integrates seamlessly with your existing platform while driving premium subscriptions through collaborative features.