From 445fc5fe9a76663e1ce421708da535a3d3520fc4 Mon Sep 17 00:00:00 2001 From: kristof de spiegeleer Date: Sat, 14 Sep 2024 06:59:42 +0300 Subject: [PATCH] s --- examples/heroweb_example_base/css | 1 + examples/heroweb_example_base/js | 1 + herowebserver/main.py | 263 ++++++------------ herowebserver/run.sh | 10 +- .../static}/css/heroweb.css | 0 .../static}/js/heroweb.js | 0 .../static}/js/not_used.js | 0 install.sh | 6 +- lib/heroserver | 1 - lib/webcomponents/main/model_view.py | 56 ++++ lib/webcomponents/main/render.py | 27 ++ .../main/templates/example_main.md | 38 +++ .../main/templates/example_nav.md | 7 + lib/webcomponents/main/templates/index.html | 60 ++++ myenv.sh | 11 +- 15 files changed, 296 insertions(+), 185 deletions(-) create mode 120000 examples/heroweb_example_base/css create mode 120000 examples/heroweb_example_base/js rename {examples/heroweb_example_base => herowebserver/static}/css/heroweb.css (100%) rename {examples/heroweb_example_base => herowebserver/static}/js/heroweb.js (100%) rename {examples/heroweb_example_base => herowebserver/static}/js/not_used.js (100%) delete mode 120000 lib/heroserver create mode 100644 lib/webcomponents/main/model_view.py create mode 100644 lib/webcomponents/main/render.py create mode 100644 lib/webcomponents/main/templates/example_main.md create mode 100644 lib/webcomponents/main/templates/example_nav.md create mode 100644 lib/webcomponents/main/templates/index.html diff --git a/examples/heroweb_example_base/css b/examples/heroweb_example_base/css new file mode 120000 index 0000000..2d71371 --- /dev/null +++ b/examples/heroweb_example_base/css @@ -0,0 +1 @@ +../../herowebserver/static/css \ No newline at end of file diff --git a/examples/heroweb_example_base/js b/examples/heroweb_example_base/js new file mode 120000 index 0000000..814a3ca --- /dev/null +++ b/examples/heroweb_example_base/js @@ -0,0 +1 @@ +../../herowebserver/static/js \ No newline at end of file diff --git a/herowebserver/main.py b/herowebserver/main.py index bc1ffe7..7054046 100644 --- a/herowebserver/main.py +++ b/herowebserver/main.py @@ -1,22 +1,51 @@ +import logging import os -from fastapi import FastAPI, Form, HTTPException, Request +from fastapi import FastAPI, Request +from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import ( - FileResponse, - HTMLResponse, - JSONResponse, RedirectResponse, ) -from fastapi.templating import Jinja2Templates -from fastapi_mail import ConnectionConfig, FastMail, MessageSchema, MessageType -from infoserver.db import DB -from infoserver.dbmem import DBMem -from infoserver.model import ACE, InfoPointer, ObjNotFound +from infoserver.dependencies import Dependencies +from infoserver.routers.router_index import router_index +from infoserver.routers.router_login import router_login +from infoserver.routers.router_pdf_preso import router_pdf +from infoserver.routers.router_static import router_static +from infoserver.routers.router_template import router_template from jwt.exceptions import PyJWTError from starlette.middleware.sessions import SessionMiddleware -from web.auth import JWTHandler -# Initialize FastAPI app +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger(__name__) + + +# Set your paths here +DB_PATH = '~/code/git.ourworld.tf/freeflowuniverse/heroweb/authdb_example' +TEMPLATES_DIR = '~/code/git.ourworld.tf/freeflowuniverse/heroweb/poc/out' +STATIC_DIR = '~/code/git.ourworld.tf/freeflowuniverse/heroweb/poc/static' +HEROWEB_DIR = '~/code/git.ourworld.tf/tfgrid/info_tfgrid/heroweb' +COLLECTIONS_DIR = '~/hero/var/collections' + +DB_PATH = os.path.abspath(os.path.expanduser(DB_PATH)) +TEMPLATES_DIR = os.path.abspath(os.path.expanduser(TEMPLATES_DIR)) +STATIC_DIR = os.path.abspath(os.path.expanduser(STATIC_DIR)) +STATIC_DIR2 = os.path.abspath(os.path.join(os.path.dirname(__file__), 'static')) +HEROWEB_DIR = os.path.abspath(os.path.expanduser(HEROWEB_DIR)) +COLLECTIONS_DIR = os.path.abspath(os.path.expanduser(COLLECTIONS_DIR)) + +SERVERHOST = 'http://localhost:8000' + +if not os.path.exists(DB_PATH): + raise FileNotFoundError(f'Database path does not exist: {DB_PATH}') +if not os.path.exists(TEMPLATES_DIR): + raise FileNotFoundError( + f'Templates directory does not exist: {TEMPLATES_DIR}' + ) +if not os.path.exists(STATIC_DIR): + raise FileNotFoundError(f'Static directory does not exist: {STATIC_DIR}') +if not os.path.exists(STATIC_DIR2): + raise FileNotFoundError(f'Static directory does not exist: {STATIC_DIR}') + app = FastAPI() # Add session middleware for cookie management @@ -25,195 +54,71 @@ if not jwt_secret_key: raise EnvironmentError('JWT_SECRET_KEY environment variable is not set') app.add_middleware(SessionMiddleware, secret_key=jwt_secret_key) -# Email configuration -conf = ConnectionConfig( - MAIL_USERNAME=os.getenv('MAIL_USERNAME'), - MAIL_PASSWORD=os.getenv('MAIL_PASSWORD'), - MAIL_FROM=os.getenv('MAIL_FROM'), - MAIL_PORT=int(os.getenv('MAIL_PORT', 587)), - MAIL_SERVER=os.getenv('MAIL_SERVER'), - MAIL_STARTTLS=True, # This replaces MAIL_TLS - MAIL_SSL_TLS=False, # This replaces MAIL_SSL - USE_CREDENTIALS=True, +# Include your routers here +app.include_router(router_static) +app.include_router(router_login) +app.include_router(router_pdf) +app.include_router(router_index) +app.include_router(router_template) + +deps = Dependencies( + DB_PATH, + TEMPLATES_DIR, + STATIC_DIR, + STATIC_DIR2, + HEROWEB_DIR, + COLLECTIONS_DIR, + SERVERHOST, ) +app.deps = deps -# Check if all required environment variables are set -required_env_vars = [ - 'MAIL_USERNAME', - 'MAIL_PASSWORD', - 'MAIL_FROM', - 'MAIL_PORT', - 'MAIL_SERVER', -] -missing_vars = [var for var in required_env_vars if not os.getenv(var)] -if missing_vars: - raise EnvironmentError( - f"Missing required environment variables: {', '.join(missing_vars)}" - ) - -# Jinja2 templates for rendering HTML -templates = Jinja2Templates(directory='templates') - -# Initialize JWTHandler -jwt_handler = JWTHandler() - -db = DB( - '~/code/git.ourworld.tf/freeflowuniverse/heroweb/authdb_example', - reset=False, +app.add_middleware( + CORSMiddleware, + allow_origins=['*'], # Allows all origins + allow_credentials=True, + allow_methods=['*'], # Allows all methods + allow_headers=['*'], # Allows all headers ) -dbmem = DBMem(db, 'kristof@incubaid.com') -# print('get users from group moreusers') -# print(dbmem_kristof.group_get_users('moreusers')) @app.middleware('http') -async def check_authentication(request: Request, call_next): +async def check_authentication( + request: Request, + call_next, +): + logger.debug(f'Received request for URL: {request.url.path}') + + # BYPASS + return await call_next(request) + if request.url.path in ['/signup', '/loginsubmit', '/register']: + logger.debug( + 'Skipping authentication for signup, loginsubmit, or register path' + ) return await call_next(request) token = request.cookies.get('access_token') if not token: + logger.debug('No access token found, redirecting to /signup') return RedirectResponse(url='/signup') + jwt_handler = request.app.deps.jwt_handler try: email = jwt_handler.verify_access_token(token) - except PyJWTError: + except PyJWTError as e: + logger.error(f'Token verification failed: {e}') return RedirectResponse(url='/signup') request.state.email = email - return await call_next(request) + logger.debug(f'Authenticated user: {email}') - -# Step 1: Render login page to accept email -@app.get('/', response_class=HTMLResponse) -async def myroot(request: Request): - return RedirectResponse(url='/signup') - - -@app.get('/signup', response_class=HTMLResponse) -async def login_form(request: Request): - return templates.TemplateResponse('login.html', {'request': request}) - - -# Step 2: Handle form submission, generate token, send login email -@app.post('/loginsubmit') -async def send_login_email(request: Request, email: str = Form(...)): - if not email: - # Redirect to signup page if email is not provided - return RedirectResponse(url='/signup') - # Generate the access token and email link - access_token = jwt_handler.create_access_token({'sub': email}) - email_link = f'http://verse.tf:8000/register?token={access_token}' - - # Render the email template with the email link - rendered_template = templates.get_template('email.html').render( - {'email_link': email_link} - ) - - # Create the email message - message = MessageSchema( - subject='Login to your account', - recipients=[email], # List of recipient emails - body=rendered_template, # The rendered HTML content - subtype=MessageType.html, # Specify the subtype as HTML - ) - - # Send the email - fm = FastMail(conf) - await fm.send_message(message) - - return {'message': 'Login link has been sent to your email.'} - - -# Step 3: Handle email link redirection and set JWT in cookies -@app.get('/register') -async def register(request: Request, token: str): - try: - jwt_handler.verify_access_token(token) - except PyJWTError: - raise HTTPException(status_code=401, detail='Invalid token') - - response = RedirectResponse(url='/info') - response.set_cookie(key='access_token', value=token) + response = await call_next(request) + # logger.debug(f'Response status code: {response.status_code}') return response -# Step 4: User info page, read JWT from cookies -@app.get('/info', response_class=HTMLResponse) -async def get_user_info(request: Request): - token = request.cookies.get('access_token') - if not token: - # raise HTTPException(status_code=401, detail="Not authenticated") - return RedirectResponse(url='/signup') +if __name__ == '__main__': + import uvicorn - try: - email = jwt_handler.verify_access_token(token) - except PyJWTError: - # raise HTTPException(status_code=401, detail="Invalid token") - return RedirectResponse(url='/signup') - - return templates.TemplateResponse( - 'info.html', {'request': request, 'email': email} - ) - - -@app.get('/{infoname}', response_class=HTMLResponse) -async def list_dir(request: Request, infoname: str): - try: - info = fs_db.info_get(infoname) - except ObjNotFound: - raise HTTPException(status_code=404, detail='Info not found') - - user_email = request.state.email - try: - user_ace = get_user_ace(info, user_email) - # no need to check for access rights since this is reading - - except Exception: - raise HTTPException(status_code=401, detail='Unauthorized') - - list_dir = os.listdir(info.path) - return JSONResponse({'dir': list_dir}) - - -def get_user_ace(info: InfoPointer, user: str) -> ACE: - for acl_str in info.acl: - acl = fs_db.acl_get(acl_str) - for entry in acl.entries: - if entry.user == user: - return entry - - raise Exception(f'user {user} not authorized') - - -@app.get('/{infoname}/{relative_path:path}', response_class=HTMLResponse) -async def serve_file(request: Request, infoname: str, relative_path: str): - try: - info = fs_db.info_get(infoname) - except ObjNotFound: - raise HTTPException(status_code=404, detail='Info not found') - - user_email = request.state.email - try: - user_ace = get_user_ace(info, user_email) - # no need to check for access rights since this is reading - - except Exception: - raise HTTPException(status_code=401, detail='Unauthorized') - - new_path = os.path.join(info.path, relative_path) - if not os.path.exists(new_path): - raise HTTPException(status_code=404, detail='File not found') - - if os.path.isdir(new_path): - list_dir = os.listdir(new_path) - return JSONResponse({'dir': list_dir}) - - if new_path.endswith('.pdf'): - return templates.TemplateResponse( - 'pdf_viewer.html', - {'request': request, 'pdf_url': f'/static/{relative_path}'}, - ) - else: - return FileResponse(new_path) + uvicorn.run(app, host='0.0.0.0', port=8000) diff --git a/herowebserver/run.sh b/herowebserver/run.sh index 05dd693..bb1697e 100755 --- a/herowebserver/run.sh +++ b/herowebserver/run.sh @@ -1,3 +1,9 @@ -pushd /root/code/git.ourworld.tf/freeflowuniverse/heroweb/authserver +#!/bin/bash +set -e +SERVER_DIR="$(cd "$(dirname "$0")" && pwd)" +if [ -z "$BASE_DIR" ]; then + source ~/code/git.ourworld.tf/freeflowuniverse/heroweb/myenv.sh +fi +pushd $SERVER_DIR uvicorn main:app --host 0.0.0.0 --port 8000 --reload -popd \ No newline at end of file +popd > /dev/null diff --git a/examples/heroweb_example_base/css/heroweb.css b/herowebserver/static/css/heroweb.css similarity index 100% rename from examples/heroweb_example_base/css/heroweb.css rename to herowebserver/static/css/heroweb.css diff --git a/examples/heroweb_example_base/js/heroweb.js b/herowebserver/static/js/heroweb.js similarity index 100% rename from examples/heroweb_example_base/js/heroweb.js rename to herowebserver/static/js/heroweb.js diff --git a/examples/heroweb_example_base/js/not_used.js b/herowebserver/static/js/not_used.js similarity index 100% rename from examples/heroweb_example_base/js/not_used.js rename to herowebserver/static/js/not_used.js diff --git a/install.sh b/install.sh index 95f86fc..5010108 100755 --- a/install.sh +++ b/install.sh @@ -1,7 +1,9 @@ #!/bin/bash set -ex -BASE_DIR="$(cd "$(dirname "$0")" && pwd)" -source ${BASE_DIR}/myenv.sh +BASE_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)" +if [ -z "$CONTEXTROOT" ]; then + source ${BASE_DIR}/myenv.sh +fi cd $BASE_DIR python3 -m pip install -r "$BASE_DIR/requirements.txt" diff --git a/lib/heroserver b/lib/heroserver deleted file mode 120000 index 608b44e..0000000 --- a/lib/heroserver +++ /dev/null @@ -1 +0,0 @@ -../../../projectmycelium/hero_server/lib \ No newline at end of file diff --git a/lib/webcomponents/main/model_view.py b/lib/webcomponents/main/model_view.py new file mode 100644 index 0000000..f01ac10 --- /dev/null +++ b/lib/webcomponents/main/model_view.py @@ -0,0 +1,56 @@ +from dataclasses import dataclass +from typing import List, Optional + + +@dataclass +class NavItem: + href: str + text: str + class_name: Optional[str] = None + + +@dataclass +class Navbar: + brand: NavItem + items: List[NavItem] + + +@dataclass +class MarkdownContent: + nav: str + content: str + title: str = 'MyDoc' + + +@dataclass +class Doc: + navbar: Navbar + markdown: MarkdownContent + title: str = 'An Example Index Page' + + +def example() -> Doc: + import os + + base_dir = os.path.dirname(__file__) + templates_dir = os.path.join(base_dir, 'templates') + + with open(os.path.join(templates_dir, 'example_main.md'), 'r') as f: + example_main = f.read() + + with open(os.path.join(templates_dir, 'example_nav.md'), 'r') as f: + example_nav = f.read() + + navbar = Navbar( + brand=NavItem(href='#', text='MyWebsite', class_name='brand'), + items=[ + NavItem(href='#home', text='Home'), + NavItem(href='#about', text='About'), + NavItem(href='#services', text='Services'), + NavItem(href='#contact', text='Contact'), + ], + ) + + markdown_content = MarkdownContent(nav=example_nav, content=example_main) + + return Doc(navbar=navbar, markdown=markdown_content) diff --git a/lib/webcomponents/main/render.py b/lib/webcomponents/main/render.py new file mode 100644 index 0000000..da6c6c3 --- /dev/null +++ b/lib/webcomponents/main/render.py @@ -0,0 +1,27 @@ +import os + +from jinja2 import Environment, FileSystemLoader +from webcomponents.main.model_view import example + + +def render(timeline_id: int = 0) -> str: + # Create an agenda instance + + mydoc = example() + + # Set up Jinja2 environment and load the template + base_dir = os.path.dirname(__file__) + env = Environment( + loader=FileSystemLoader(searchpath=f'{base_dir}/templates') + ) + + # pudb.set_trace() + + template = env.get_template('index.html') + + # Render the template with the agenda data + output = template.render(doc=mydoc) + + print(output) + + return output diff --git a/lib/webcomponents/main/templates/example_main.md b/lib/webcomponents/main/templates/example_main.md new file mode 100644 index 0000000..598db30 --- /dev/null +++ b/lib/webcomponents/main/templates/example_main.md @@ -0,0 +1,38 @@ +# Welcome to the Example + +This is a **Markdown** example. + +# Section 1 +Content for section 1. + +## Section 2 +Content for section 2. + +### Section 3 + +Content for section 3. + +- something +- yes +- incredible + +> This is a blockquote. + +| Name | Email | Description | +|------------|-------------------|-------------------| +| John Doe | john@example.com | Developer | +| Jane Smith | jane@example.com | Designer | +| Bob Brown | bob@example.com | Manager | +| Alice Blue | alice@example.com | Engineer | +| Eve White | eve@example.com | Analyst | +| Tom Black | tom@example.com | Consultant | + +### Section 4 + +Content for section 4. + +- something +- yes +- incredible + +> This is a blockquote. diff --git a/lib/webcomponents/main/templates/example_nav.md b/lib/webcomponents/main/templates/example_nav.md new file mode 100644 index 0000000..5e760d5 --- /dev/null +++ b/lib/webcomponents/main/templates/example_nav.md @@ -0,0 +1,7 @@ +- [intro](#intro) + - [products](#products) + - [car](#car) + - [plane](#plane) +- [features](#features) + - [know more](#know-more) +- [documentation](#documentation) diff --git a/lib/webcomponents/main/templates/index.html b/lib/webcomponents/main/templates/index.html new file mode 100644 index 0000000..32631c4 --- /dev/null +++ b/lib/webcomponents/main/templates/index.html @@ -0,0 +1,60 @@ + + + + + + {{ doc.title }} + + + + + + {% if doc.markdown.nav %} + + {% endif %} + + {% if doc.markdown.content %} + + {% endif %} + + +
+ +
+ +
+
{{ doc.markdown.title }}
+
    +
    +
    + + + diff --git a/myenv.sh b/myenv.sh index a7a4d58..b28ed67 100755 --- a/myenv.sh +++ b/myenv.sh @@ -1,7 +1,6 @@ #!/bin/bash SCRIPT_PATH="${BASH_SOURCE[0]:-$0}" export BASE_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)" -#export VENV_DIR="${HOME}/.venv" export VENV_DIR="${BASE_DIR}/.venv" python3 -m venv "$VENV_DIR" @@ -25,4 +24,14 @@ if [ -f "$SECRET_FILE" ]; then source "$SECRET_FILE" fi +PYTHON_VERSION=$(python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')") + +WEBLIB_PATH="${VENV_DIR}/lib/python${PYTHON_VERSION}/site-packages/weblib.pth" +HEROLIB_PATH="${VENV_DIR}/lib/python${PYTHON_VERSION}/site-packages/herolib.pth" + +if [ ! -f "$WEBLIB_PATH" ] || [ ! -f "$HEROLIB_PATH" ]; then + echo "One or both of the required .pth files are missing. Running install.sh." + source "${BASE_DIR}/install.sh" +fi + echo "We're good to go"