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} +
+ + + + + + +
+{:else} +

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} +
+

{post.id}

+

{post.title}

+

{post.slug}

+ {post.title} +
{@html post.content}
+
+ {/each} +{/if} diff --git a/frontend/src/lib/components/TinyEditor.svelte b/frontend/src/lib/components/TinyEditor.svelte index a259c58..172851f 100644 --- a/frontend/src/lib/components/TinyEditor.svelte +++ b/frontend/src/lib/components/TinyEditor.svelte @@ -3,7 +3,6 @@ import { onMount, onDestroy } from 'svelte'; let editorId = 'my-tinymce-editor'; - let title: string = ''; export let content: string = ''; export let height: number = 300; @@ -54,76 +53,6 @@ }); } }); - - function handleSubmit() { - console.log('Submitted Title:', title); - console.log('Submitted Content:', content); - fetch('https://jsonplaceholder.typicode.com/posts', { - method: 'POST', - headers: { - 'Content-type': 'application/json; charset=UTF-8' - }, - body: JSON.stringify({ - title: title, - content: content - }) - }) - .then((response) => response.json()) - .then((data) => { - console.log('Response from server:', data); - }) - .catch((error) => { - console.error('Error submitting content:', error); - }); - } -{#if browser} -
- - - - - -
-{:else} -

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 @@

My Editor

- + +