diff --git a/backend/app/api/data.py b/backend/app/api/data.py index 4342132..3084e24 100644 --- a/backend/app/api/data.py +++ b/backend/app/api/data.py @@ -1,24 +1,95 @@ -from fastapi import APIRouter, File, UploadFile, Request +from fastapi import APIRouter, File, UploadFile, Request, HTTPException + import datetime import shutil +import time +import re import json from sqlmodel import Session, select, text -from app.models.yesno import YesNo +from app.models.blog_post import BlogPost, BlogPostCreate, BlogPostUpdate from app.core.database import SessionDep +from typing import List, Optional router = APIRouter() -@router.post("/upload") -async def upload_answer(request: Request, db: SessionDep): - answer = await request.body() - answer = answer.decode("utf-8") - db.add(YesNo(timestamp=datetime.datetime.now(), answer=answer)) + +def sanitise_html(html: str) -> str: + return html + + +def generate_unique_slug(title: str, db: Session) -> str: + # Slugify title (basic version; you can improve with unidecode or slugify lib) + base_slug = re.sub(r"[^a-z0-9]+", "-", title.lower()).strip("-") + slug = base_slug + counter = 1 + while db.exec(select(BlogPost).where(BlogPost.slug == slug)).first(): + slug = f"{base_slug}-{counter}" + counter += 1 + return slug + + +@router.post("/posts/create_post", response_model=BlogPost) +def create_post(post: BlogPostCreate, db: SessionDep): + created_post = BlogPost( + title=post.title, + content=sanitise_html(post.content), + slug=generate_unique_slug(post.title, db), + cover_image=post.cover_image, + ) + db.add(created_post) db.commit() + db.refresh(created_post) + return created_post + + +@router.get("/posts/", response_model=List[BlogPost]) +def read_posts(db: SessionDep): + return db.exec(select(BlogPost)).all() + + +@router.get("/posts/{post_id}", response_model=BlogPost) +def read_post(post_id: int, db: SessionDep): + post = db.get(BlogPost, post_id) + if not post: + raise HTTPException(status_code=404, detail="Post not found") + return post -@router.get(("/get")) -def get_answer(db: SessionDep): - all_answers = db.exec(select(YesNo)).all() - return all_answers \ No newline at end of file + +@router.put("/posts/{post_id}", response_model=BlogPost) +def update_post( + post_id: int, + updated_post: BlogPostUpdate, + db: SessionDep, +): + db_post = db.get(BlogPost, post_id) + if not db_post: + raise HTTPException(status_code=404, detail="Post not found") + + if updated_post.title is not None: + db_post.title = updated_post.title + db_post.slug = generate_unique_slug(updated_post.title, db) + + if updated_post.content is not None: + db_post.content = sanitise_html(updated_post.content) + + if updated_post.cover_image is not None: + db_post.cover_image = updated_post.cover_image + + # db_post.timestamp = int(time.time()) + db.add(db_post) + db.commit() + db.refresh(db_post) + return db_post + + +@router.delete("/posts/{post_id}") +def delete_post(post_id: int, db: SessionDep): + post = db.get(BlogPost, post_id) + if not post: + raise HTTPException(status_code=404, detail="Post not found") + db.delete(post) + db.commit() + return {"detail": "Post deleted"} diff --git a/backend/app/main.py b/backend/app/main.py index b8e9313..0b7055f 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -17,8 +17,6 @@ async def lifespan(app: FastAPI): # Allowing CORS from the specific origin (localhost:5173) or any origin origins = [ - "http://localhost:5173", # Frontend app URL - "http://127.0.0.1:5173", "*" # Or you can use localhost and 127.0.0.1 as the origin ] diff --git a/backend/app/models/blog_post.py b/backend/app/models/blog_post.py new file mode 100644 index 0000000..5bf84a3 --- /dev/null +++ b/backend/app/models/blog_post.py @@ -0,0 +1,26 @@ +from typing import Optional +from datetime import datetime +from sqlmodel import SQLModel, Field +from pydantic import BaseModel +import time + + +class BlogPost(SQLModel, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + timestamp: int = Field(default_factory=lambda: int(time.time())) + title: str + slug: str = Field(index=True, unique=True) + content: str + cover_image: str # Path to image file, .etc '/hamster_neutral.jpg' + + +class BlogPostCreate(BaseModel): + title: str + content: str + cover_image: str + + +class BlogPostUpdate(BaseModel): + title: Optional[str] = None + content: Optional[str] = None + cover_image: Optional[str] = None diff --git a/backend/app/models/yesno.py b/backend/app/models/yesno.py deleted file mode 100644 index 25adaf4..0000000 --- a/backend/app/models/yesno.py +++ /dev/null @@ -1,6 +0,0 @@ -from sqlmodel import Field, SQLModel - -class YesNo(SQLModel, table=True): - id: int = Field(default=None, primary_key=True) - timestamp: str - answer: str \ No newline at end of file diff --git a/frontend/src/lib/components/PostEditor.svelte b/frontend/src/lib/components/PostEditor.svelte new file mode 100644 index 0000000..3092ce6 --- /dev/null +++ b/frontend/src/lib/components/PostEditor.svelte @@ -0,0 +1,89 @@ + + +{#if browser} +
Editor loading...
+{/if} + + diff --git a/frontend/src/lib/components/PostList.svelte b/frontend/src/lib/components/PostList.svelte new file mode 100644 index 0000000..f733d58 --- /dev/null +++ b/frontend/src/lib/components/PostList.svelte @@ -0,0 +1,45 @@ + + +{#if loading} +Loading posts...
+{:else if error} +Error: {error}
+{:else} + {#each posts as post} +Editor loading...
-{/if} - - + diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index 58dc6ff..46cb2aa 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -1,10 +1,13 @@