Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions app/crud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from sqlalchemy.orm import Session
from models import Event
from schemas import EventCreate
import json

def get_events(db: Session, skip: int = 0, limit: int = 10):
return db.query(Event).order_by(Event.dates).offset(skip).limit(limit).all()

def get_featured_events(db: Session):
return db.query(Event).filter(Event.outstanding == True).all()

def get_event(db: Session, event_id: int):
return db.query(Event).filter(Event.id == event_id).first()

def create_event(db: Session, event: EventCreate):
db_event = Event(
name=event.name,
description=event.description,
dates=json.dumps(event.dates), # store as JSON string
place=event.place,
outstanding=event.outstanding,
image_url=event.image_url
)
db.add(db_event)
db.commit()
db.refresh(db_event)
return db_event
13 changes: 13 additions & 0 deletions app/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

# SQLite database
SQLALCHEMY_DATABASE_URL = "sqlite:///./events.db"

engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)

SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
118 changes: 118 additions & 0 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
from fastapi import FastAPI, Depends, Request, Form, Response
from fastapi.templating import Jinja2Templates
from fastapi.responses import RedirectResponse
from sqlalchemy.orm import Session
import crud, models, schemas
from database import SessionLocal, engine
import json
from itsdangerous import URLSafeSerializer

# Create DB tables
models.Base.metadata.create_all(bind=engine)

app = FastAPI()
templates = Jinja2Templates(directory="templates")

# Session management
SECRET_KEY = "mysecretkey123"
serializer = URLSafeSerializer(SECRET_KEY)

# Hardcoded admin credentials
ADMIN_USERNAME = "admin"
ADMIN_PASSWORD = "password123"

# Database dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()

# Get current admin
def get_current_admin(request: Request):
cookie = request.cookies.get("admin_session")
if cookie:
try:
username = serializer.loads(cookie)
if username == ADMIN_USERNAME:
return username
except:
return None
return None

# Protect admin pages
def require_admin(request: Request):
return get_current_admin(request) is not None

# Home page
@app.get("/")
def read_events(request: Request, db: Session = Depends(get_db)):
events = crud.get_events(db)
featured = crud.get_featured_events(db)
for e in events + featured:
if isinstance(e.dates, str):
e.dates = json.loads(e.dates)
return templates.TemplateResponse(
"index.html", {"request": request, "events": events, "featured": featured}
)

# Event details
@app.get("/event/{event_id}")
def event_detail(request: Request, event_id: int, db: Session = Depends(get_db)):
event = crud.get_event(db, event_id)
if event and isinstance(event.dates, str):
event.dates = json.loads(event.dates)
return templates.TemplateResponse("event_detail.html", {"request": request, "event": event})

# Admin login page
@app.get("/admin/login")
def admin_login_page(request: Request):
return templates.TemplateResponse("login.html", {"request": request, "error": None})

# Handle login
@app.post("/admin/login")
def admin_login(request: Request, response: Response, username: str = Form(...), password: str = Form(...)):
if username == ADMIN_USERNAME and password == ADMIN_PASSWORD:
session_cookie = serializer.dumps(username)
response = RedirectResponse(url="/admin/events/create", status_code=303)
response.set_cookie(key="admin_session", value=session_cookie, httponly=True)
return response
return templates.TemplateResponse("login.html", {"request": request, "error": "Invalid credentials"})

# Admin logout
@app.get("/admin/logout")
def admin_logout(response: Response):
response = RedirectResponse(url="/", status_code=303)
response.delete_cookie("admin_session")
return response

# Admin create event form
@app.get("/admin/events/create")
def create_event_form(request: Request):
if not require_admin(request):
return RedirectResponse(url="/admin/login")
return templates.TemplateResponse("create_event.html", {"request": request})

# Handle event creation
@app.post("/admin/events/create")
def create_event(
request: Request,
name: str = Form(...),
description: str = Form(None),
dates: str = Form(...),
place: str = Form(None),
outstanding: bool = Form(False),
image_url: str = Form(None),
db: Session = Depends(get_db)
):
if not require_admin(request):
return RedirectResponse(url="/admin/login")

dates_list = [d.strip() for d in dates.split(",")]
event = schemas.EventCreate(
name=name, description=description, dates=dates_list,
place=place, outstanding=outstanding, image_url=image_url
)
crud.create_event(db, event)
return RedirectResponse(url="/", status_code=303)
16 changes: 16 additions & 0 deletions app/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from sqlalchemy import Column, Integer, String, Boolean, DateTime
from sqlalchemy.sql import func
from database import Base

class Event(Base):
__tablename__ = "events"

id = Column(Integer, primary_key=True, index=True)
name = Column(String, nullable=False)
description = Column(String)
dates = Column(String, nullable=False) # JSON string of dates
place = Column(String)
outstanding = Column(Boolean, default=False)
image_url = Column(String)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
19 changes: 19 additions & 0 deletions app/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from pydantic import BaseModel
from typing import List, Optional

class EventBase(BaseModel):
name: str
description: Optional[str] = None
dates: List[str]
place: Optional[str] = None
outstanding: bool = False
image_url: Optional[str] = None

class EventCreate(EventBase):
pass

class Event(EventBase):
id: int

class Config:
from_attributes = True # Pydantic v2
18 changes: 18 additions & 0 deletions app/templates/create_event.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<title>Create Event</title>
</head>
<body>
<h1>Create New Event</h1>
<form action="/admin/events/create" method="POST">
<label>Name:</label><input type="text" name="name" required><br>
<label>Description:</label><textarea name="description"></textarea><br>
<label>Dates (comma-separated):</label><input type="text" name="dates" required><br>
<label>Place:</label><input type="text" name="place"><br>
<label>Featured:</label><input type="checkbox" name="outstanding"><br>
<label>Image URL:</label><input type="text" name="image_url"><br>
<button type="submit">Create</button>
</form>
</body>
</html>
21 changes: 21 additions & 0 deletions app/templates/event_detail.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<title>{{ event.name }}</title>
</head>
<body>
<h1>{{ event.name }}</h1>
<p>{{ event.description }}</p>
<p>Place: {{ event.place }}</p>
<p>Dates:</p>
<ul>
{% for date in event.dates %}
<li>{{ date }}</li>
{% endfor %}
</ul>
{% if event.image_url %}
<img src="{{ event.image_url }}" alt="{{ event.name }}" width="300">
{% endif %}
<a href="/">Back to Events</a>
</body>
</html>
27 changes: 27 additions & 0 deletions app/templates/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<title>Events Calendar</title>
</head>
<body>
<h1>Featured Events</h1>
<ul>
{% for event in featured %}
<li>
<a href="/event/{{ event.id }}">{{ event.name }}</a> - {{ event.dates }}
<a href="https://twitter.com/intent/tweet?text=I%20will%20go%20to%20{{ event.name }}%20@%20{{ event.dates[0] }}%20{{ request.url }}" target="_blank">Share</a>
</li>
{% endfor %}
</ul>

<h1>All Events</h1>
<ul>
{% for event in events %}
<li>
<a href="/event/{{ event.id }}">{{ event.name }}</a> - {{ event.dates }}
<a href="https://twitter.com/intent/tweet?text=I%20will%20go%20to%20{{ event.name }}%20@%20{{ event.dates[0] }}%20{{ request.url }}" target="_blank">Share</a>
</li>
{% endfor %}
</ul>
</body>
</html>
17 changes: 17 additions & 0 deletions app/templates/login.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<title>Admin Login</title>
</head>
<body>
<h1>Admin Login</h1>
{% if error %}
<p style="color:red;">{{ error }}</p>
{% endif %}
<form method="POST">
<label>Username:</label><input type="text" name="username" required><br>
<label>Password:</label><input type="password" name="password" required><br>
<button type="submit">Login</button>
</form>
</body>
</html>