Restructure project
This commit is contained in:
		@@ -18,4 +18,4 @@ COPY main.py /app/
 | 
				
			|||||||
COPY static/ /app/static/
 | 
					COPY static/ /app/static/
 | 
				
			||||||
COPY templates/ /app/templates/
 | 
					COPY templates/ /app/templates/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ENTRYPOINT ["poetry", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "5000", "--use-colors"]
 | 
					ENTRYPOINT ["poetry", "run", "uvicorn", "fabcal.app:app", "--host", "0.0.0.0", "--port", "5000", "--use-colors"]
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										0
									
								
								fabcal/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								fabcal/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										33
									
								
								fabcal/app.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								fabcal/app.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					import locale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from fastapi import FastAPI
 | 
				
			||||||
 | 
					from fastapi.middleware.cors import CORSMiddleware
 | 
				
			||||||
 | 
					from fastapi.staticfiles import StaticFiles
 | 
				
			||||||
 | 
					from fastapi_cache import FastAPICache
 | 
				
			||||||
 | 
					from fastapi_cache.backends.inmemory import InMemoryBackend
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from fabcal.routers import api_v1, frontend
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app = FastAPI()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.mount("/static", StaticFiles(directory="static"), name="static")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.include_router(api_v1.router, prefix="/api/v1")
 | 
				
			||||||
 | 
					app.include_router(frontend.router, prefix="")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.add_middleware(
 | 
				
			||||||
 | 
					    CORSMiddleware,
 | 
				
			||||||
 | 
					    allow_origins=[
 | 
				
			||||||
 | 
					        "https://fablab-altmuehlfranken.de",
 | 
				
			||||||
 | 
					        "https://www.fablab-altmuehlfranken.de",
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					locale.setlocale(locale.LC_TIME, locale.getlocale())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@app.on_event("startup")
 | 
				
			||||||
 | 
					async def startup():
 | 
				
			||||||
 | 
					    FastAPICache.init(InMemoryBackend())
 | 
				
			||||||
@@ -1,53 +1,16 @@
 | 
				
			|||||||
import base64
 | 
					 | 
				
			||||||
import locale
 | 
					 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from collections import OrderedDict
 | 
					from collections import OrderedDict
 | 
				
			||||||
from datetime import date, datetime, timedelta, timezone
 | 
					from datetime import date, datetime, timedelta, timezone
 | 
				
			||||||
from typing import List, NamedTuple
 | 
					from typing import List
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import aiohttp
 | 
					import aiohttp
 | 
				
			||||||
import babel.dates
 | 
					 | 
				
			||||||
import recurring_ical_events
 | 
					import recurring_ical_events
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from fastapi import FastAPI
 | 
					 | 
				
			||||||
from fastapi.middleware.cors import CORSMiddleware
 | 
					 | 
				
			||||||
from fastapi.requests import Request
 | 
					 | 
				
			||||||
from fastapi.responses import Response
 | 
					 | 
				
			||||||
from fastapi.staticfiles import StaticFiles
 | 
					 | 
				
			||||||
from fastapi.templating import Jinja2Templates
 | 
					 | 
				
			||||||
from fastapi_cache import FastAPICache
 | 
					 | 
				
			||||||
from fastapi_cache.backends.inmemory import InMemoryBackend
 | 
					 | 
				
			||||||
from fastapi_cache.decorator import cache
 | 
					from fastapi_cache.decorator import cache
 | 
				
			||||||
from icalendar import Calendar, vText
 | 
					from icalendar import Calendar, vText
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from fabcal.models import CalendarEvent
 | 
				
			||||||
app = FastAPI()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
app.mount("/static", StaticFiles(directory="static"), name="static")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
templates = Jinja2Templates(directory="templates")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
app.add_middleware(
 | 
					 | 
				
			||||||
    CORSMiddleware,
 | 
					 | 
				
			||||||
    allow_origins=[
 | 
					 | 
				
			||||||
        "https://fablab-altmuehlfranken.de",
 | 
					 | 
				
			||||||
        "https://www.fablab-altmuehlfranken.de",
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
locale.setlocale(locale.LC_TIME, locale.getlocale())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def get_calendar_url():
 | 
					 | 
				
			||||||
    url = os.environ["CALENDAR_URL"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # convenience feature
 | 
					 | 
				
			||||||
    url = url.replace("webcal://", "https://")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return url
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def sanitize(data: str):
 | 
					def sanitize(data: str):
 | 
				
			||||||
@@ -60,6 +23,15 @@ def sanitize(data: str):
 | 
				
			|||||||
    return cal.to_ical()
 | 
					    return cal.to_ical()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_calendar_url():
 | 
				
			||||||
 | 
					    url = os.environ["CALENDAR_URL"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # convenience feature
 | 
				
			||||||
 | 
					    url = url.replace("webcal://", "https://")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return url
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# caching strings works better than caching calendar objects
 | 
					# caching strings works better than caching calendar objects
 | 
				
			||||||
@cache(expire=120)
 | 
					@cache(expire=120)
 | 
				
			||||||
async def get_data() -> str:
 | 
					async def get_data() -> str:
 | 
				
			||||||
@@ -71,18 +43,6 @@ async def get_data() -> str:
 | 
				
			|||||||
            return sanitize(await response.text())
 | 
					            return sanitize(await response.text())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CalendarEvent(NamedTuple):
 | 
					 | 
				
			||||||
    start: datetime
 | 
					 | 
				
			||||||
    end: datetime
 | 
					 | 
				
			||||||
    # just a convenience thing, we want to keep start/date as datetime and save the client from guessing this themselves
 | 
					 | 
				
			||||||
    all_day_event: bool
 | 
					 | 
				
			||||||
    summary: str
 | 
					 | 
				
			||||||
    description: str
 | 
					 | 
				
			||||||
    location: str
 | 
					 | 
				
			||||||
    color: str
 | 
					 | 
				
			||||||
    uid: str
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def get_tzinfo():
 | 
					def get_tzinfo():
 | 
				
			||||||
    return timezone(timedelta(hours=1))
 | 
					    return timezone(timedelta(hours=1))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -163,16 +123,6 @@ async def get_future_events():
 | 
				
			|||||||
    return future_events
 | 
					    return future_events
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@app.get("/events.ics")
 | 
					 | 
				
			||||||
async def ics():
 | 
					 | 
				
			||||||
    return Response(
 | 
					 | 
				
			||||||
        await get_data(),
 | 
					 | 
				
			||||||
        headers={
 | 
					 | 
				
			||||||
            "content-type": "text/calendar",
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def group_by_date(events: List[CalendarEvent]):
 | 
					def group_by_date(events: List[CalendarEvent]):
 | 
				
			||||||
    grouped_events: OrderedDict[date, List[CalendarEvent]] = OrderedDict()
 | 
					    grouped_events: OrderedDict[date, List[CalendarEvent]] = OrderedDict()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -185,41 +135,3 @@ def group_by_date(events: List[CalendarEvent]):
 | 
				
			|||||||
        grouped_events[start_date].append(event)
 | 
					        grouped_events[start_date].append(event)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return grouped_events
 | 
					    return grouped_events
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@app.get("/embed-sidebar.html")
 | 
					 | 
				
			||||||
async def embed(request: Request, max_width: str = None):
 | 
					 | 
				
			||||||
    # await asyncio.sleep(1)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    events = await get_future_events()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    grouped_events = list(group_by_date(events).items())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # couple of helpers
 | 
					 | 
				
			||||||
    def localized_abbreviated_month(dt: datetime):
 | 
					 | 
				
			||||||
        return babel.dates.format_datetime(dt, format="%b", locale="de_DE")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # couple of helpers
 | 
					 | 
				
			||||||
    def localized_abbreviated_weekday(dt: datetime):
 | 
					 | 
				
			||||||
        return babel.dates.format_datetime(dt, format="%b", locale="de_DE")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def base64_encode(s: str):
 | 
					 | 
				
			||||||
        return base64.b64encode(s.encode()).decode()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return templates.TemplateResponse(
 | 
					 | 
				
			||||||
        "embed-sidebar.html",
 | 
					 | 
				
			||||||
        context={
 | 
					 | 
				
			||||||
            "request": request,
 | 
					 | 
				
			||||||
            "grouped_events": grouped_events,
 | 
					 | 
				
			||||||
            "dir": dir,
 | 
					 | 
				
			||||||
            "localized_abbreviated_month": localized_abbreviated_month,
 | 
					 | 
				
			||||||
            "localized_abbreviated_weekday": localized_abbreviated_weekday,
 | 
					 | 
				
			||||||
            "base64_encode": base64_encode,
 | 
					 | 
				
			||||||
            "max_width": max_width,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@app.on_event("startup")
 | 
					 | 
				
			||||||
async def startup():
 | 
					 | 
				
			||||||
    FastAPICache.init(InMemoryBackend())
 | 
					 | 
				
			||||||
							
								
								
									
										14
									
								
								fabcal/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								fabcal/models.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					from datetime import datetime
 | 
				
			||||||
 | 
					from typing import NamedTuple
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CalendarEvent(NamedTuple):
 | 
				
			||||||
 | 
					    start: datetime
 | 
				
			||||||
 | 
					    end: datetime
 | 
				
			||||||
 | 
					    # just a convenience thing, we want to keep start/date as datetime and save the client from guessing this themselves
 | 
				
			||||||
 | 
					    all_day_event: bool
 | 
				
			||||||
 | 
					    summary: str
 | 
				
			||||||
 | 
					    description: str
 | 
				
			||||||
 | 
					    location: str
 | 
				
			||||||
 | 
					    color: str
 | 
				
			||||||
 | 
					    uid: str
 | 
				
			||||||
							
								
								
									
										4
									
								
								fabcal/routers/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								fabcal/routers/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					from starlette.templating import Jinja2Templates
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					templates = Jinja2Templates(directory="templates")
 | 
				
			||||||
							
								
								
									
										17
									
								
								fabcal/routers/api_v1.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								fabcal/routers/api_v1.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					from fastapi import APIRouter
 | 
				
			||||||
 | 
					from starlette.responses import Response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from fabcal.calendar_client import get_data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					router = APIRouter()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router.get("/events.ics")
 | 
				
			||||||
 | 
					async def events():
 | 
				
			||||||
 | 
					    return Response(
 | 
				
			||||||
 | 
					        await get_data(),
 | 
				
			||||||
 | 
					        headers={
 | 
				
			||||||
 | 
					            "content-type": "text/calendar",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
							
								
								
									
										45
									
								
								fabcal/routers/frontend.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								fabcal/routers/frontend.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					import base64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from datetime import datetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import babel.dates
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from fastapi import APIRouter
 | 
				
			||||||
 | 
					from fastapi.requests import Request
 | 
				
			||||||
 | 
					from fastapi.responses import HTMLResponse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from fabcal.calendar_client import get_future_events, group_by_date
 | 
				
			||||||
 | 
					from fabcal.routers import templates
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					router = APIRouter()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router.get("/embed-sidebar.html", response_class=HTMLResponse)
 | 
				
			||||||
 | 
					async def embed(request: Request, max_width: str = None):
 | 
				
			||||||
 | 
					    events = await get_future_events()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    grouped_events = list(group_by_date(events).items())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # couple of helpers
 | 
				
			||||||
 | 
					    def localized_abbreviated_month(dt: datetime):
 | 
				
			||||||
 | 
					        return babel.dates.format_datetime(dt, format="%b", locale="de_DE")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # couple of helpers
 | 
				
			||||||
 | 
					    def localized_abbreviated_weekday(dt: datetime):
 | 
				
			||||||
 | 
					        return babel.dates.format_datetime(dt, format="%b", locale="de_DE")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def base64_encode(s: str):
 | 
				
			||||||
 | 
					        return base64.b64encode(s.encode()).decode()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return templates.TemplateResponse(
 | 
				
			||||||
 | 
					        "embed-sidebar.html",
 | 
				
			||||||
 | 
					        context={
 | 
				
			||||||
 | 
					            "request": request,
 | 
				
			||||||
 | 
					            "grouped_events": grouped_events,
 | 
				
			||||||
 | 
					            "dir": dir,
 | 
				
			||||||
 | 
					            "localized_abbreviated_month": localized_abbreviated_month,
 | 
				
			||||||
 | 
					            "localized_abbreviated_weekday": localized_abbreviated_weekday,
 | 
				
			||||||
 | 
					            "base64_encode": base64_encode,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
		Reference in New Issue
	
	Block a user