7 changed files with 158 additions and 53 deletions
-
41photoapp/api.py
-
55photoapp/daemon.py
-
55photoapp/dbutils.py
-
2photoapp/ingest.py
-
5photoapp/types.py
-
47photoapp/utils.py
-
6requirements.txt
@ -0,0 +1,41 @@ |
|||
import os |
|||
import cherrypy |
|||
import logging |
|||
from datetime import datetime, timedelta |
|||
from photoapp.library import PhotoLibrary |
|||
from photoapp.types import Photo, PhotoSet, Tag, TagItem, PhotoStatus, User |
|||
from jinja2 import Environment, FileSystemLoader, select_autoescape |
|||
from sqlalchemy import desc |
|||
from sqlalchemy import func, and_, or_ |
|||
from photoapp.common import pwhash |
|||
import math |
|||
from urllib.parse import urlparse |
|||
from photoapp.utils import mime2ext, auth, require_auth, photo_auth_filter, slugify |
|||
from photoapp.dbutils import db |
|||
|
|||
|
|||
class PhotosApi(object): |
|||
def __init__(self): |
|||
self.v1 = PhotosApiV1() |
|||
|
|||
|
|||
class PhotosApiV1(object): |
|||
def __init__(self): |
|||
# self.tpl.filters.update(mime2ext=mime2ext, |
|||
# basename=os.path.basename, |
|||
# ceil=math.ceil, |
|||
# statusstr=lambda x: str(x).split(".")[-1]) |
|||
pass |
|||
|
|||
@cherrypy.expose |
|||
def index(self): |
|||
yield f"<plaintext>hello, this is the api. my database is: {db()}\n" |
|||
|
|||
@cherrypy.expose |
|||
def upload(self, files, meta): |
|||
pass |
|||
|
|||
@cherrypy.expose |
|||
@cherrypy.tools.json_out() |
|||
def findbysha(self, sha): |
|||
pass |
@ -0,0 +1,55 @@ |
|||
import sqlalchemy |
|||
import cherrypy |
|||
from cherrypy.process import plugins |
|||
from sqlalchemy.ext.declarative import declarative_base |
|||
|
|||
|
|||
Base = declarative_base() |
|||
|
|||
|
|||
def db(): |
|||
return cherrypy.request.db |
|||
|
|||
|
|||
class SAEnginePlugin(plugins.SimplePlugin): |
|||
def __init__(self, bus, dbcon): |
|||
plugins.SimplePlugin.__init__(self, bus) |
|||
self.sa_engine = dbcon |
|||
self.bus.subscribe("bind", self.bind) |
|||
|
|||
def start(self): |
|||
Base.metadata.create_all(self.sa_engine) |
|||
|
|||
def bind(self, session): |
|||
session.configure(bind=self.sa_engine) |
|||
|
|||
|
|||
class SATool(cherrypy.Tool): |
|||
def __init__(self): |
|||
cherrypy.Tool.__init__(self, 'before_request_body', |
|||
self.bind_session, |
|||
priority=100) |
|||
|
|||
self.session = sqlalchemy.orm.scoped_session( |
|||
sqlalchemy.orm.sessionmaker(autoflush=True, autocommit=False)) |
|||
|
|||
def _setup(self): |
|||
cherrypy.Tool._setup(self) |
|||
cherrypy.request.hooks.attach('on_end_resource', self.commit_transaction, priority=80) |
|||
|
|||
def bind_session(self): |
|||
cherrypy.engine.publish('bind', self.session) |
|||
cherrypy.request.db = self.session |
|||
con = cherrypy.request.db.connection().connection.connection |
|||
if hasattr(con, "ping"): # not available on sqlite |
|||
con.ping() |
|||
|
|||
def commit_transaction(self): |
|||
cherrypy.request.db = None |
|||
try: |
|||
self.session.commit() |
|||
except Exception: |
|||
self.session.rollback() |
|||
raise |
|||
finally: |
|||
self.session.remove() |
@ -0,0 +1,47 @@ |
|||
import cherrypy |
|||
from photoapp.types import PhotoSet, PhotoStatus |
|||
|
|||
|
|||
def mime2ext(mime): |
|||
""" |
|||
Given a mime type return the canonical file extension |
|||
""" |
|||
return {"image/png": "png", |
|||
"image/jpeg": "jpg", |
|||
"image/gif": "gif", |
|||
"application/octet-stream-xmp": "xmp", |
|||
"image/x-canon-cr2": "cr2", |
|||
"video/mp4": "mp4", |
|||
"video/quicktime": "mov"}[mime] |
|||
|
|||
|
|||
def auth(): |
|||
""" |
|||
Return the currently authorized username (per request) or None |
|||
""" |
|||
return cherrypy.session.get('authed', None) |
|||
|
|||
|
|||
def require_auth(func): |
|||
""" |
|||
Decorator: raise 403 unless session is authed |
|||
""" |
|||
def wrapped(*args, **kwargs): |
|||
if not auth(): |
|||
raise cherrypy.HTTPError(403) |
|||
return func(*args, **kwargs) |
|||
return wrapped |
|||
|
|||
|
|||
def photo_auth_filter(query): |
|||
""" |
|||
Sqlalchemy helper: filter the given PhotoSet query to items that match the authorized user's PhotoStatus access |
|||
level. Currently, authed users can access ALL photos, and unauthed users can access only PhotoStatus.public |
|||
status items. |
|||
""" |
|||
return query.filter(PhotoSet.status == PhotoStatus.public) if not auth() else query |
|||
|
|||
|
|||
def slugify(words): |
|||
return ''.join(letter for letter in '-'.join(words.lower().split()) |
|||
if ('a' <= letter <= 'z') or ('0' <= letter <= '9') or letter == '-') |
Write
Preview
Loading…
Cancel
Save
Reference in new issue