import json import os import bottle import peewee from cuizin import db from cuizin.scraping import fetch_recipe MODULE_DIR = os.path.dirname(os.path.realpath(__file__)) app = bottle.Bottle() @app.hook('before_request') def enable_cors(): """ Add CORS headers at each request. """ # The str() call is required as we import unicode_literal and WSGI # headers list should have plain str type. bottle.response.headers[str('Access-Control-Allow-Origin')] = str('*') bottle.response.headers[str('Access-Control-Allow-Methods')] = str( 'PUT, GET, POST, DELETE, OPTIONS, PATCH' ) bottle.response.headers[str('Access-Control-Allow-Headers')] = str( 'Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token' ) @app.route('/api/v1', ['GET', 'OPTIONS']) def api_v1_index(): """ API index route """ return { 'recipes': '/api/v1/recipes', 'recipe': '/api/v1/recipe/:id' } @app.route('/api/v1/recipes', ['GET', 'OPTIONS']) def api_v1_recipes(): """ List all recipes """ # CORS if bottle.request.method == 'OPTIONS': return '' return { 'recipes': [ recipe.to_dict() for recipe in db.Recipe.select().order_by(db.Recipe.id.desc()) ] } @app.post('/api/v1/recipes/by_url') def api_v1_recipes_post_by_url(): """ Create a new recipe from URL """ data = json.loads(bottle.request.body.read().decode('utf-8')) if 'url' not in data: return { 'error': 'No URL provided' } try: # Try to add return { 'recipes': [fetch_recipe(data['url']).to_dict()] } except peewee.IntegrityError: # Redirect to pre-existing recipe if already there recipe = db.Recipe.select().where( db.Recipe.url == data['url'] ).first() bottle.redirect('/api/v1/recipe/%s' % recipe.id, 301) @app.post('/api/v1/recipes/manually') def api_v1_recipes_post_manual(): """ Create a new recipe manually """ data = json.loads(bottle.request.body.read().decode('utf-8')) try: # Try to add recipe = db.Recipe() recipe.update_from_dict(data) recipe.save() return { 'recipes': [recipe.to_dict()] } except peewee.IntegrityError: return { 'error': 'Duplicate recipe.' } @app.route('/api/v1/recipe/:id', ['GET', 'OPTIONS']) def api_v1_recipe(id): """ Get a given recipe from db """ # CORS if bottle.request.method == 'OPTIONS': return '' return { 'recipes': [ recipe.to_dict() for recipe in db.Recipe.select().where( db.Recipe.id == id ) ] } @app.route('/api/v1/recipe/:id', ['POST', 'OPTIONS']) def api_v1_recipe_edit(id): """ Edit a given recipe from db """ # CORS if bottle.request.method == 'OPTIONS': return '' data = json.loads(bottle.request.body.read().decode('utf-8')) recipe = db.Recipe.select().where( db.Recipe.id == id ).first() if not recipe: return bottle.abort(400, 'No recipe with id %s.' % id) recipe.update_from_dict(data) recipe.save() return { 'recipes': [ recipe.to_dict() ] } @app.route('/api/v1/recipe/:id/refetch', ['GET', 'OPTIONS']) def api_v1_recipe_refetch(id): """ Refetch a given recipe. """ # CORS if bottle.request.method == 'OPTIONS': return '' recipe = db.Recipe.select().where( db.Recipe.id == id ).first() if not recipe: return bottle.abort(400, 'No recipe with id %s.' % id) recipe = fetch_recipe(recipe.url, recipe=recipe) return { 'recipes': [ recipe.to_dict() ] } @app.delete('/api/v1/recipe/:id', ['DELETE', 'OPTIONS']) def api_v1_recipe_delete(id): """ Delete a given recipe from db """ # CORS if bottle.request.method == 'OPTIONS': return '' db.Recipe.delete().where( db.Recipe.id == id ).execute() return { 'status': 'OK' } @app.get('/') def index(): """ Return built index.html file """ return bottle.static_file('index.html', root=os.path.join(MODULE_DIR, 'dist')) @app.get('/static/') def get_static_files(filename): """ Get Static files """ return bottle.static_file(filename, root=os.path.join(MODULE_DIR, 'dist', 'static'))