Add Note Sharing feature.
This commit is contained in:
107
models/note_share.py
Normal file
107
models/note_share.py
Normal file
@@ -0,0 +1,107 @@
|
||||
from datetime import datetime, timedelta
|
||||
from . import db
|
||||
import secrets
|
||||
import string
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
|
||||
|
||||
class NoteShare(db.Model):
|
||||
"""Public sharing links for notes"""
|
||||
__tablename__ = 'note_share'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
note_id = db.Column(db.Integer, db.ForeignKey('note.id', ondelete='CASCADE'), nullable=False)
|
||||
token = db.Column(db.String(64), unique=True, nullable=False, index=True)
|
||||
|
||||
# Share settings
|
||||
expires_at = db.Column(db.DateTime, nullable=True) # None means no expiry
|
||||
password_hash = db.Column(db.String(255), nullable=True) # Optional password protection
|
||||
view_count = db.Column(db.Integer, default=0)
|
||||
max_views = db.Column(db.Integer, nullable=True) # Limit number of views
|
||||
|
||||
# Metadata
|
||||
created_at = db.Column(db.DateTime, default=datetime.now)
|
||||
created_by_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
last_accessed_at = db.Column(db.DateTime, nullable=True)
|
||||
|
||||
# Relationships
|
||||
note = db.relationship('Note', backref=db.backref('shares', cascade='all, delete-orphan', lazy='dynamic'))
|
||||
created_by = db.relationship('User', foreign_keys=[created_by_id])
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(NoteShare, self).__init__(**kwargs)
|
||||
if not self.token:
|
||||
self.token = self.generate_token()
|
||||
|
||||
@staticmethod
|
||||
def generate_token():
|
||||
"""Generate a secure random token"""
|
||||
alphabet = string.ascii_letters + string.digits
|
||||
return ''.join(secrets.choice(alphabet) for _ in range(32))
|
||||
|
||||
def set_password(self, password):
|
||||
"""Set password protection for the share"""
|
||||
if password:
|
||||
self.password_hash = generate_password_hash(password)
|
||||
else:
|
||||
self.password_hash = None
|
||||
|
||||
def check_password(self, password):
|
||||
"""Check if the provided password is correct"""
|
||||
if not self.password_hash:
|
||||
return True
|
||||
return check_password_hash(self.password_hash, password)
|
||||
|
||||
def is_valid(self):
|
||||
"""Check if share link is still valid"""
|
||||
# Check expiration
|
||||
if self.expires_at and datetime.now() > self.expires_at:
|
||||
return False
|
||||
|
||||
# Check view count
|
||||
if self.max_views and self.view_count >= self.max_views:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def is_expired(self):
|
||||
"""Check if the share has expired"""
|
||||
if self.expires_at and datetime.now() > self.expires_at:
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_view_limit_reached(self):
|
||||
"""Check if view limit has been reached"""
|
||||
if self.max_views and self.view_count >= self.max_views:
|
||||
return True
|
||||
return False
|
||||
|
||||
def record_access(self):
|
||||
"""Record that the share was accessed"""
|
||||
self.view_count += 1
|
||||
self.last_accessed_at = datetime.now()
|
||||
|
||||
def get_share_url(self, _external=True):
|
||||
"""Get the full URL for this share"""
|
||||
from flask import url_for
|
||||
return url_for('notes_public.view_shared_note',
|
||||
token=self.token,
|
||||
_external=_external)
|
||||
|
||||
def to_dict(self):
|
||||
"""Convert share to dictionary for API responses"""
|
||||
return {
|
||||
'id': self.id,
|
||||
'token': self.token,
|
||||
'url': self.get_share_url(),
|
||||
'expires_at': self.expires_at.isoformat() if self.expires_at else None,
|
||||
'has_password': bool(self.password_hash),
|
||||
'max_views': self.max_views,
|
||||
'view_count': self.view_count,
|
||||
'created_at': self.created_at.isoformat(),
|
||||
'created_by': self.created_by.username,
|
||||
'last_accessed_at': self.last_accessed_at.isoformat() if self.last_accessed_at else None,
|
||||
'is_valid': self.is_valid(),
|
||||
'is_expired': self.is_expired(),
|
||||
'is_view_limit_reached': self.is_view_limit_reached()
|
||||
}
|
||||
Reference in New Issue
Block a user