mirror of
https://github.com/hay-kot/mealie.git
synced 2025-08-22 14:33:33 -07:00
Merge branch 'feature/authentication' of https://github.com/hay-kot/mealie into feature/authentication
This commit is contained in:
commit
0e157341bc
35 changed files with 357 additions and 142 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -153,3 +153,4 @@ node_modules/
|
|||
mealie/data/debug/last_recipe.json
|
||||
*.sqlite
|
||||
app_data/db/test.db
|
||||
scratch.py
|
||||
|
|
|
@ -2,7 +2,9 @@ import uvicorn
|
|||
from fastapi import FastAPI
|
||||
|
||||
# import utils.startup as startup
|
||||
from app_config import APP_VERSION, PORT, SECRET, docs_url, redoc_url
|
||||
from core.config import APP_VERSION, PORT, SECRET, docs_url, redoc_url
|
||||
from db.db_setup import sql_exists
|
||||
from db.init_db import init_db
|
||||
from routes import (
|
||||
backup_routes,
|
||||
debug_routes,
|
||||
|
@ -18,7 +20,6 @@ from routes.recipe import (
|
|||
tag_routes,
|
||||
)
|
||||
from routes.users import users
|
||||
from services.settings_services import default_settings_init
|
||||
from utils.logger import logger
|
||||
|
||||
app = FastAPI(
|
||||
|
@ -30,16 +31,14 @@ app = FastAPI(
|
|||
)
|
||||
|
||||
|
||||
def data_base_first_run():
|
||||
init_db()
|
||||
|
||||
|
||||
def start_scheduler():
|
||||
import services.scheduler.scheduled_jobs
|
||||
|
||||
|
||||
def init_settings():
|
||||
default_settings_init()
|
||||
import services.theme_services
|
||||
import services.users.users
|
||||
|
||||
|
||||
def api_routers():
|
||||
# Authentication
|
||||
app.include_router(users.router)
|
||||
|
@ -60,9 +59,11 @@ def api_routers():
|
|||
app.include_router(debug_routes.router)
|
||||
|
||||
|
||||
if not sql_exists:
|
||||
data_base_first_run()
|
||||
|
||||
api_routers()
|
||||
start_scheduler()
|
||||
init_settings()
|
||||
|
||||
if __name__ == "__main__":
|
||||
logger.info("-----SYSTEM STARTUP-----")
|
||||
|
|
|
@ -33,7 +33,7 @@ else:
|
|||
redoc_url = None
|
||||
|
||||
# Helpful Globals
|
||||
DATA_DIR = CWD.parent.joinpath("app_data")
|
||||
DATA_DIR = CWD.parent.parent.joinpath("app_data")
|
||||
if PRODUCTION:
|
||||
DATA_DIR = Path("/app/data")
|
||||
|
29
mealie/core/security.py
Normal file
29
mealie/core/security.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
from passlib.context import CryptContext
|
||||
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
|
||||
|
||||
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
||||
"""Compares a plain string to a hashed password
|
||||
|
||||
Args:
|
||||
plain_password (str): raw password string
|
||||
hashed_password (str): hashed password from the database
|
||||
|
||||
Returns:
|
||||
bool: Returns True if a match return False
|
||||
"""
|
||||
return pwd_context.verify(plain_password, hashed_password)
|
||||
|
||||
|
||||
def get_password_hash(password: str) -> str:
|
||||
"""Takes in a raw password and hashes it. Used prior to saving
|
||||
a new password to the database.
|
||||
|
||||
Args:
|
||||
password (str): Password String
|
||||
|
||||
Returns:
|
||||
str: Hashed Password
|
||||
"""
|
||||
return pwd_context.hash(password)
|
|
@ -1,4 +1,4 @@
|
|||
from app_config import SQLITE_FILE, USE_SQL
|
||||
from core.config import SQLITE_FILE, USE_SQL
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from db.sql.db_session import sql_global_init
|
||||
|
|
64
mealie/db/init_db.py
Normal file
64
mealie/db/init_db.py
Normal file
|
@ -0,0 +1,64 @@
|
|||
from core.security import get_password_hash
|
||||
from fastapi.logger import logger
|
||||
from models.settings_models import SiteSettings, Webhooks
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.orm.session import Session
|
||||
from utils.logger import logger
|
||||
|
||||
from db.database import db
|
||||
from db.db_setup import create_session
|
||||
|
||||
|
||||
def init_db(db: Session = None) -> None:
|
||||
if not db:
|
||||
db = create_session()
|
||||
|
||||
default_settings_init(db)
|
||||
default_theme_init(db)
|
||||
default_user_init(db)
|
||||
|
||||
db.close()
|
||||
|
||||
|
||||
def default_theme_init(session: Session):
|
||||
default_theme = {
|
||||
"name": "default",
|
||||
"colors": {
|
||||
"primary": "#E58325",
|
||||
"accent": "#00457A",
|
||||
"secondary": "#973542",
|
||||
"success": "#5AB1BB",
|
||||
"info": "#4990BA",
|
||||
"warning": "#FF4081",
|
||||
"error": "#EF5350",
|
||||
},
|
||||
}
|
||||
|
||||
try:
|
||||
db.themes.create(session, default_theme)
|
||||
logger.info("Generating default theme...")
|
||||
except:
|
||||
logger.info("Default Theme Exists.. skipping generation")
|
||||
|
||||
|
||||
def default_settings_init(session: Session):
|
||||
try:
|
||||
webhooks = Webhooks()
|
||||
default_entry = SiteSettings(name="main", webhooks=webhooks)
|
||||
document = db.settings.create(session, default_entry.dict())
|
||||
logger.info(f"Created Site Settings: \n {document}")
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def default_user_init(session: Session):
|
||||
default_user = {
|
||||
"full_name": "Change Me",
|
||||
"email": "changeme@email.com",
|
||||
"password": get_password_hash("MyPassword"),
|
||||
"family": "public",
|
||||
"is_superuser": True,
|
||||
}
|
||||
|
||||
logger.info("Generating Default User")
|
||||
db.users.create(session, default_user)
|
|
@ -1,7 +1,7 @@
|
|||
import operator
|
||||
import shutil
|
||||
|
||||
from app_config import BACKUP_DIR, TEMPLATE_DIR
|
||||
from core.config import BACKUP_DIR, TEMPLATE_DIR
|
||||
from db.db_setup import generate_session
|
||||
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
|
||||
from models.backup_models import BackupJob, ImportJob, Imports, LocalBackup
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import json
|
||||
|
||||
from app_config import APP_VERSION, DEBUG_DIR
|
||||
from core.config import APP_VERSION, DEBUG_DIR
|
||||
from fastapi import APIRouter
|
||||
from utils.logger import LOGGER_FILE
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from app_config import SECRET
|
||||
from core.config import SECRET
|
||||
from db.database import db
|
||||
from db.db_setup import create_session
|
||||
from fastapi_login import LoginManager
|
||||
|
|
|
@ -2,7 +2,7 @@ import operator
|
|||
import shutil
|
||||
from typing import List
|
||||
|
||||
from app_config import MIGRATION_DIR
|
||||
from core.config import MIGRATION_DIR
|
||||
from db.db_setup import generate_session
|
||||
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
|
||||
from models.migration_models import MigrationFile, Migrations
|
||||
|
|
|
@ -2,7 +2,6 @@ from db.database import db
|
|||
from db.db_setup import generate_session
|
||||
from fastapi import APIRouter, Depends
|
||||
from models.settings_models import SiteSettings
|
||||
from services.settings_services import default_settings_init
|
||||
from sqlalchemy.orm.session import Session
|
||||
from utils.post_webhooks import post_webhooks
|
||||
from utils.snackbar import SnackResponse
|
||||
|
@ -17,8 +16,7 @@ def get_main_settings(session: Session = Depends(generate_session)):
|
|||
try:
|
||||
data = db.settings.get(session, "main")
|
||||
except:
|
||||
default_settings_init(session)
|
||||
data = db.settings.get(session, "main")
|
||||
return
|
||||
return data
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from core.security import verify_password
|
||||
from db.db_setup import generate_session
|
||||
from fastapi import APIRouter, Depends
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
|
@ -17,9 +18,10 @@ def token(
|
|||
password = data.password
|
||||
|
||||
user = query_user(email, session)
|
||||
print(user)
|
||||
if not user:
|
||||
raise InvalidCredentialsException # you can also use your own HTTPException
|
||||
elif password != user["password"]:
|
||||
elif not verify_password(password, user["password"]):
|
||||
raise InvalidCredentialsException
|
||||
|
||||
access_token = manager.create_access_token(data=dict(sub=email))
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
from core.security import get_password_hash
|
||||
from db.database import db
|
||||
from db.db_setup import generate_session
|
||||
from fastapi import APIRouter, Depends
|
||||
from models.user_models import CreateUser, UserResponse
|
||||
from routes.deps import manager, query_user
|
||||
from routes.deps import manager
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
router = APIRouter(prefix="/api/users", tags=["Users"])
|
||||
|
||||
|
||||
@router.post("/", response_model=UserResponse)
|
||||
@router.post("", response_model=UserResponse, status_code=201)
|
||||
async def create_user(
|
||||
new_user: CreateUser,
|
||||
current_user=Depends(manager),
|
||||
|
@ -16,16 +17,21 @@ async def create_user(
|
|||
):
|
||||
""" Returns a list of all user in the Database """
|
||||
|
||||
new_user.password = get_password_hash(new_user.password)
|
||||
|
||||
data = db.users.create(session, new_user.dict())
|
||||
print(data)
|
||||
return data
|
||||
|
||||
|
||||
@router.get("/", response_model=list[UserResponse])
|
||||
@router.get("", response_model=list[UserResponse])
|
||||
async def get_all_users(
|
||||
current_user=Depends(manager), session: Session = Depends(generate_session)
|
||||
):
|
||||
|
||||
if current_user.get("is_superuser"):
|
||||
return db.users.get_all(session)
|
||||
else:
|
||||
return {"details": "user not authorized"}
|
||||
|
||||
|
||||
@router.get("/{id}", response_model=UserResponse)
|
||||
|
@ -43,6 +49,7 @@ async def update_user(
|
|||
session: Session = Depends(generate_session),
|
||||
):
|
||||
current_user_id = current_user.get("id")
|
||||
new_data.password = get_password_hash(new_data.password)
|
||||
is_superuser = current_user.get("is_superuser")
|
||||
if current_user_id == id or is_superuser:
|
||||
return db.users.update(session, id, new_data.dict())
|
||||
|
|
|
@ -3,7 +3,7 @@ import shutil
|
|||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from app_config import BACKUP_DIR, IMG_DIR, TEMP_DIR, TEMPLATE_DIR
|
||||
from core.config import BACKUP_DIR, IMG_DIR, TEMP_DIR, TEMPLATE_DIR
|
||||
from db.database import db
|
||||
from db.db_setup import create_session
|
||||
from jinja2 import Template
|
||||
|
|
|
@ -4,7 +4,7 @@ import zipfile
|
|||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
from app_config import BACKUP_DIR, IMG_DIR, TEMP_DIR
|
||||
from core.config import BACKUP_DIR, IMG_DIR, TEMP_DIR
|
||||
from db.database import db
|
||||
from models.import_models import RecipeImport, SettingsImport, ThemeImport
|
||||
from models.theme_models import SiteTheme
|
||||
|
|
|
@ -2,7 +2,7 @@ import shutil
|
|||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
from app_config import IMG_DIR
|
||||
from core.config import IMG_DIR
|
||||
from utils.logger import logger
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import shutil
|
|||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
from app_config import IMG_DIR, TEMP_DIR
|
||||
from core.config import IMG_DIR, TEMP_DIR
|
||||
from services.recipe_services import Recipe
|
||||
from sqlalchemy.orm.session import Session
|
||||
from utils.unzip import unpack_zip
|
||||
|
|
|
@ -4,10 +4,10 @@ import shutil
|
|||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
from app_config import IMG_DIR, MIGRATION_DIR, TEMP_DIR
|
||||
from core.config import IMG_DIR, MIGRATION_DIR, TEMP_DIR
|
||||
from services.recipe_services import Recipe
|
||||
from services.scraper.cleaner import Cleaner
|
||||
from app_config import IMG_DIR, TEMP_DIR
|
||||
from core.config import IMG_DIR, TEMP_DIR
|
||||
|
||||
|
||||
def process_selection(selection: Path) -> Path:
|
||||
|
@ -81,7 +81,7 @@ def migrate(session, selection: str):
|
|||
successful_imports.append(recipe.name)
|
||||
except:
|
||||
logging.error(f"Failed Nextcloud Import: {dir.name}")
|
||||
logging.exception('')
|
||||
logging.exception("")
|
||||
failed_imports.append(dir.name)
|
||||
|
||||
cleanup()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from typing import Tuple
|
||||
|
||||
import extruct
|
||||
from app_config import DEBUG_DIR
|
||||
from core.config import DEBUG_DIR
|
||||
from slugify import slugify
|
||||
from w3lib.html import get_base_url
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ from typing import List
|
|||
|
||||
import requests
|
||||
import scrape_schema_recipe
|
||||
from app_config import DEBUG_DIR
|
||||
from core.config import DEBUG_DIR
|
||||
from services.image_services import scrape_image
|
||||
from services.recipe_services import Recipe
|
||||
from services.scraper import open_graph
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
from db.database import db
|
||||
from db.db_setup import create_session
|
||||
from models.settings_models import SiteSettings, Webhooks
|
||||
from sqlalchemy.orm.session import Session
|
||||
from utils.logger import logger
|
||||
|
||||
|
||||
def default_settings_init(session: Session = None):
|
||||
if session == None:
|
||||
session = create_session()
|
||||
try:
|
||||
webhooks = Webhooks()
|
||||
default_entry = SiteSettings(name="main", webhooks=webhooks)
|
||||
document = db.settings.create(session, default_entry.dict())
|
||||
logger.info(f"Created Site Settings: \n {document}")
|
||||
except:
|
||||
pass
|
|
@ -1,28 +0,0 @@
|
|||
from db.database import db
|
||||
from db.db_setup import create_session, sql_exists
|
||||
from utils.logger import logger
|
||||
|
||||
|
||||
def default_theme_init():
|
||||
default_theme = {
|
||||
"name": "default",
|
||||
"colors": {
|
||||
"primary": "#E58325",
|
||||
"accent": "#00457A",
|
||||
"secondary": "#973542",
|
||||
"success": "#5AB1BB",
|
||||
"info": "#4990BA",
|
||||
"warning": "#FF4081",
|
||||
"error": "#EF5350",
|
||||
},
|
||||
}
|
||||
session = create_session()
|
||||
try:
|
||||
db.themes.create(session, default_theme)
|
||||
logger.info("Generating default theme...")
|
||||
except:
|
||||
logger.info("Default Theme Exists.. skipping generation")
|
||||
|
||||
|
||||
if not sql_exists:
|
||||
default_theme_init()
|
|
@ -1,25 +0,0 @@
|
|||
from db.database import db
|
||||
from db.db_setup import create_session, sql_exists
|
||||
from fastapi.logger import logger
|
||||
|
||||
|
||||
def init_super_user():
|
||||
session = create_session()
|
||||
|
||||
default_user = {
|
||||
"full_name": "Change Me",
|
||||
"email": "changeme@email.com",
|
||||
"password": "MyPassword",
|
||||
"family": "public",
|
||||
"is_superuser": True,
|
||||
}
|
||||
|
||||
logger.info("Generating Default User")
|
||||
|
||||
db.users.create(session, default_user)
|
||||
|
||||
session.close()
|
||||
|
||||
|
||||
if not sql_exists:
|
||||
init_super_user()
|
|
@ -1,27 +1,27 @@
|
|||
from pathlib import Path
|
||||
import json
|
||||
|
||||
import requests
|
||||
from app import app
|
||||
from app_config import SQLITE_DIR
|
||||
from core.config import SQLITE_DIR
|
||||
from db.db_setup import generate_session, sql_global_init
|
||||
from db.init_db import init_db
|
||||
from fastapi.testclient import TestClient
|
||||
from pytest import fixture
|
||||
from services.settings_services import default_settings_init
|
||||
from services.theme_services import default_theme_init
|
||||
|
||||
from tests.test_config import TEST_DATA
|
||||
|
||||
SQLITE_FILE = SQLITE_DIR.joinpath("test.db")
|
||||
SQLITE_FILE.unlink(missing_ok=True)
|
||||
TOKEN_URL = "/api/auth/token"
|
||||
|
||||
|
||||
TestSessionLocal = sql_global_init(SQLITE_FILE, check_thread=False)
|
||||
init_db(TestSessionLocal())
|
||||
|
||||
|
||||
def override_get_db():
|
||||
try:
|
||||
db = TestSessionLocal()
|
||||
default_theme_init()
|
||||
default_settings_init()
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
|
@ -31,11 +31,22 @@ def override_get_db():
|
|||
def api_client():
|
||||
|
||||
app.dependency_overrides[generate_session] = override_get_db
|
||||
|
||||
yield TestClient(app)
|
||||
|
||||
SQLITE_FILE.unlink()
|
||||
# SQLITE_FILE.unlink()
|
||||
|
||||
|
||||
@fixture(scope="session")
|
||||
def test_image():
|
||||
return TEST_DATA.joinpath("test_image.jpg")
|
||||
|
||||
|
||||
@fixture(scope="session")
|
||||
def token(api_client: requests):
|
||||
form_data = {"username": "changeme@email.com", "password": "MyPassword"}
|
||||
response = api_client.post(TOKEN_URL, form_data)
|
||||
|
||||
token = json.loads(response.text).get("access_token")
|
||||
|
||||
return {"Authorization": f"Bearer {token}"}
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
from pathlib import Path
|
||||
from app_config import TEMP_DIR
|
||||
from core.config import TEMP_DIR
|
||||
import pytest
|
||||
from app_config import TEMP_DIR
|
||||
from core.config import TEMP_DIR
|
||||
from services.image_services import IMG_DIR
|
||||
from services.migrations.nextcloud import (cleanup, import_recipes, prep,
|
||||
process_selection)
|
||||
from services.migrations.nextcloud import (
|
||||
cleanup,
|
||||
import_recipes,
|
||||
prep,
|
||||
process_selection,
|
||||
)
|
||||
from services.recipe_services import Recipe
|
||||
from tests.test_config import TEST_NEXTCLOUD_DIR
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import json
|
|||
import shutil
|
||||
|
||||
import pytest
|
||||
from app_config import MIGRATION_DIR
|
||||
from core.config import MIGRATION_DIR
|
||||
from tests.test_config import TEST_CHOWDOWN_DIR, TEST_NEXTCLOUD_DIR
|
||||
from tests.utils.routes import MIGRATIONS_PREFIX, RECIPES_PREFIX
|
||||
|
||||
|
|
|
@ -34,8 +34,6 @@ def default_theme(api_client):
|
|||
},
|
||||
}
|
||||
|
||||
api_client.post(THEMES_CREATE, json=default_theme)
|
||||
|
||||
return default_theme
|
||||
|
||||
|
||||
|
|
94
mealie/tests/test_routes/test_user_routes.py
Normal file
94
mealie/tests/test_routes/test_user_routes.py
Normal file
|
@ -0,0 +1,94 @@
|
|||
import json
|
||||
|
||||
import requests
|
||||
from pytest import fixture
|
||||
|
||||
BASE = "/api/users"
|
||||
TOKEN_URL = "/api/auth/token"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@fixture(scope="session")
|
||||
def default_user():
|
||||
return {
|
||||
"id": 1,
|
||||
"full_name": "Change Me",
|
||||
"email": "changeme@email.com",
|
||||
"family": "public",
|
||||
}
|
||||
|
||||
|
||||
@fixture(scope="session")
|
||||
def new_user():
|
||||
return {
|
||||
"id": 2,
|
||||
"full_name": "My New User",
|
||||
"email": "newuser@email.com",
|
||||
"family": "public",
|
||||
}
|
||||
|
||||
|
||||
def test_superuser_login(api_client: requests):
|
||||
form_data = {"username": "changeme@email.com", "password": "MyPassword"}
|
||||
response = api_client.post(TOKEN_URL, form_data)
|
||||
|
||||
assert response.status_code == 200
|
||||
token = json.loads(response.text).get("access_token")
|
||||
|
||||
return {"Authorization": f"Bearer {token}"}
|
||||
|
||||
|
||||
def test_init_superuser(api_client: requests, token, default_user):
|
||||
response = api_client.get(f"{BASE}/1", headers=token)
|
||||
assert response.status_code == 200
|
||||
|
||||
assert json.loads(response.text) == default_user
|
||||
|
||||
|
||||
def test_create_user(api_client: requests, token, new_user):
|
||||
create_data = {
|
||||
"full_name": "My New User",
|
||||
"email": "newuser@email.com",
|
||||
"password": "MyStrongPassword",
|
||||
"family": "public",
|
||||
}
|
||||
|
||||
response = api_client.post(f"{BASE}", json=create_data, headers=token)
|
||||
|
||||
assert response.status_code == 201
|
||||
assert json.loads(response.text) == new_user
|
||||
assert True
|
||||
|
||||
|
||||
def test_get_all_users(api_client: requests, token, new_user, default_user):
|
||||
response = api_client.get(f"{BASE}", headers=token)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
assert json.loads(response.text) == [default_user, new_user]
|
||||
|
||||
|
||||
def test_update_user(api_client: requests, token):
|
||||
update_data = {
|
||||
"full_name": "Updated Name",
|
||||
"email": "updated@email.com",
|
||||
"password": "MyStrongPassword",
|
||||
"family": "public",
|
||||
}
|
||||
response = api_client.put(f"{BASE}/1", headers=token, json=update_data)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert json.loads(response.text) == {
|
||||
"id": 1,
|
||||
"full_name": "Updated Name",
|
||||
"email": "updated@email.com",
|
||||
"family": "public",
|
||||
}
|
||||
|
||||
|
||||
def test_delete_user(api_client: requests, token):
|
||||
response = api_client.delete(f"{BASE}/2", headers=token)
|
||||
|
||||
assert response.status_code == 200
|
|
@ -1,10 +1,14 @@
|
|||
from pathlib import Path
|
||||
from app_config import TEMP_DIR
|
||||
from core.config import TEMP_DIR
|
||||
import pytest
|
||||
from app_config import TEMP_DIR
|
||||
from core.config import TEMP_DIR
|
||||
from services.image_services import IMG_DIR
|
||||
from services.migrations.nextcloud import (cleanup, import_recipes, prep,
|
||||
process_selection)
|
||||
from services.migrations.nextcloud import (
|
||||
cleanup,
|
||||
import_recipes,
|
||||
prep,
|
||||
process_selection,
|
||||
)
|
||||
from services.recipe_services import Recipe
|
||||
from tests.test_config import TEST_NEXTCLOUD_DIR
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import json
|
||||
|
||||
from app_config import DATA_DIR
|
||||
from core.config import DATA_DIR
|
||||
|
||||
"""Script to export the ReDoc documentation page into a standalone HTML file."""
|
||||
|
||||
|
@ -37,6 +37,3 @@ HTML_PATH = DATA_DIR.parent.joinpath("docs/docs/html/api.html")
|
|||
def generate_api_docs(app):
|
||||
with open(HTML_PATH, "w") as fd:
|
||||
print(HTML_TEMPLATE % json.dumps(app.openapi()), file=fd)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from app_config import DATA_DIR
|
||||
from core.config import DATA_DIR
|
||||
|
||||
LOGGER_LEVEL = "INFO"
|
||||
CWD = Path(__file__).parent
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
from pathlib import Path
|
||||
|
||||
from services.settings_services import default_theme_init
|
||||
|
||||
CWD = Path(__file__).parent
|
||||
|
||||
|
||||
def post_start():
|
||||
default_theme_init()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
|
@ -2,7 +2,7 @@ import tempfile
|
|||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
from app_config import TEMP_DIR
|
||||
from core.config import TEMP_DIR
|
||||
|
||||
|
||||
def unpack_zip(selection: Path) -> tempfile.TemporaryDirectory:
|
||||
|
|
89
poetry.lock
generated
89
poetry.lock
generated
|
@ -83,6 +83,22 @@ docs = ["furo", "sphinx", "zope.interface"]
|
|||
tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
|
||||
tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"]
|
||||
|
||||
[[package]]
|
||||
name = "bcrypt"
|
||||
version = "3.2.0"
|
||||
description = "Modern password hashing for your software and your servers"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
cffi = ">=1.1"
|
||||
six = ">=1.4.1"
|
||||
|
||||
[package.extras]
|
||||
tests = ["pytest (>=3.2.1,!=3.3.0)"]
|
||||
typecheck = ["mypy"]
|
||||
|
||||
[[package]]
|
||||
name = "beautifulsoup4"
|
||||
version = "4.9.3"
|
||||
|
@ -128,6 +144,17 @@ category = "main"
|
|||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "cffi"
|
||||
version = "1.14.5"
|
||||
description = "Foreign Function Interface for Python calling C code."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
pycparser = "*"
|
||||
|
||||
[[package]]
|
||||
name = "chardet"
|
||||
version = "4.0.0"
|
||||
|
@ -457,6 +484,14 @@ category = "dev"
|
|||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[[package]]
|
||||
name = "pycparser"
|
||||
version = "2.20"
|
||||
description = "C parser in Python"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "1.7.3"
|
||||
|
@ -872,7 +907,7 @@ python-versions = "*"
|
|||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.8"
|
||||
content-hash = "957b8a18406390a7bf3d02b0ccbf0af89e52d0a7d89e25adb81650f942d5108c"
|
||||
content-hash = "d952f188238a85c926a047e9741d24830fa613f3e378725045654f0277a6061e"
|
||||
|
||||
[metadata.files]
|
||||
aiofiles = [
|
||||
|
@ -903,6 +938,15 @@ attrs = [
|
|||
{file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"},
|
||||
{file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"},
|
||||
]
|
||||
bcrypt = [
|
||||
{file = "bcrypt-3.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c95d4cbebffafcdd28bd28bb4e25b31c50f6da605c81ffd9ad8a3d1b2ab7b1b6"},
|
||||
{file = "bcrypt-3.2.0-cp36-abi3-manylinux1_x86_64.whl", hash = "sha256:63d4e3ff96188e5898779b6057878fecf3f11cfe6ec3b313ea09955d587ec7a7"},
|
||||
{file = "bcrypt-3.2.0-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:cd1ea2ff3038509ea95f687256c46b79f5fc382ad0aa3664d200047546d511d1"},
|
||||
{file = "bcrypt-3.2.0-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:cdcdcb3972027f83fe24a48b1e90ea4b584d35f1cc279d76de6fc4b13376239d"},
|
||||
{file = "bcrypt-3.2.0-cp36-abi3-win32.whl", hash = "sha256:a67fb841b35c28a59cebed05fbd3e80eea26e6d75851f0574a9273c80f3e9b55"},
|
||||
{file = "bcrypt-3.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:81fec756feff5b6818ea7ab031205e1d323d8943d237303baca2c5f9c7846f34"},
|
||||
{file = "bcrypt-3.2.0.tar.gz", hash = "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29"},
|
||||
]
|
||||
beautifulsoup4 = [
|
||||
{file = "beautifulsoup4-4.9.3-py2-none-any.whl", hash = "sha256:4c98143716ef1cb40bf7f39a8e3eec8f8b009509e74904ba3a7b315431577e35"},
|
||||
{file = "beautifulsoup4-4.9.3-py3-none-any.whl", hash = "sha256:fff47e031e34ec82bf17e00da8f592fe7de69aeea38be00523c04623c04fb666"},
|
||||
|
@ -915,6 +959,45 @@ certifi = [
|
|||
{file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"},
|
||||
{file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"},
|
||||
]
|
||||
cffi = [
|
||||
{file = "cffi-1.14.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991"},
|
||||
{file = "cffi-1.14.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1"},
|
||||
{file = "cffi-1.14.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa"},
|
||||
{file = "cffi-1.14.5-cp27-cp27m-win32.whl", hash = "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3"},
|
||||
{file = "cffi-1.14.5-cp27-cp27m-win_amd64.whl", hash = "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5"},
|
||||
{file = "cffi-1.14.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482"},
|
||||
{file = "cffi-1.14.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6"},
|
||||
{file = "cffi-1.14.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045"},
|
||||
{file = "cffi-1.14.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa"},
|
||||
{file = "cffi-1.14.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406"},
|
||||
{file = "cffi-1.14.5-cp35-cp35m-win32.whl", hash = "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369"},
|
||||
{file = "cffi-1.14.5-cp35-cp35m-win_amd64.whl", hash = "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315"},
|
||||
{file = "cffi-1.14.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892"},
|
||||
{file = "cffi-1.14.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058"},
|
||||
{file = "cffi-1.14.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5"},
|
||||
{file = "cffi-1.14.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132"},
|
||||
{file = "cffi-1.14.5-cp36-cp36m-win32.whl", hash = "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53"},
|
||||
{file = "cffi-1.14.5-cp36-cp36m-win_amd64.whl", hash = "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813"},
|
||||
{file = "cffi-1.14.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73"},
|
||||
{file = "cffi-1.14.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06"},
|
||||
{file = "cffi-1.14.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1"},
|
||||
{file = "cffi-1.14.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49"},
|
||||
{file = "cffi-1.14.5-cp37-cp37m-win32.whl", hash = "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62"},
|
||||
{file = "cffi-1.14.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4"},
|
||||
{file = "cffi-1.14.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053"},
|
||||
{file = "cffi-1.14.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0"},
|
||||
{file = "cffi-1.14.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e"},
|
||||
{file = "cffi-1.14.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827"},
|
||||
{file = "cffi-1.14.5-cp38-cp38-win32.whl", hash = "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e"},
|
||||
{file = "cffi-1.14.5-cp38-cp38-win_amd64.whl", hash = "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396"},
|
||||
{file = "cffi-1.14.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea"},
|
||||
{file = "cffi-1.14.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322"},
|
||||
{file = "cffi-1.14.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c"},
|
||||
{file = "cffi-1.14.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee"},
|
||||
{file = "cffi-1.14.5-cp39-cp39-win32.whl", hash = "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396"},
|
||||
{file = "cffi-1.14.5-cp39-cp39-win_amd64.whl", hash = "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d"},
|
||||
{file = "cffi-1.14.5.tar.gz", hash = "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"},
|
||||
]
|
||||
chardet = [
|
||||
{file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"},
|
||||
{file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"},
|
||||
|
@ -1194,6 +1277,10 @@ py = [
|
|||
{file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"},
|
||||
{file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"},
|
||||
]
|
||||
pycparser = [
|
||||
{file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"},
|
||||
{file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"},
|
||||
]
|
||||
pydantic = [
|
||||
{file = "pydantic-1.7.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c59ea046aea25be14dc22d69c97bee629e6d48d2b2ecb724d7fe8806bf5f61cd"},
|
||||
{file = "pydantic-1.7.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a4143c8d0c456a093387b96e0f5ee941a950992904d88bc816b4f0e72c9a0009"},
|
||||
|
|
|
@ -26,6 +26,7 @@ extruct = "^0.12.0"
|
|||
scrape-schema-recipe = "^0.1.3"
|
||||
python-multipart = "^0.0.5"
|
||||
fastapi-login = "^1.5.3"
|
||||
bcrypt = "^3.2.0"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
pylint = "^2.6.0"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue