Add initial API backend scaffold
This commit is contained in:
parent
da52852fdf
commit
4f34251198
75
API_ENDPOINTS.md
Normal file
75
API_ENDPOINTS.md
Normal file
@ -0,0 +1,75 @@
|
||||
# API-Endpunkte
|
||||
|
||||
Diese Endpunkte decken die Kernfunktionen von OnlyPrompt ab.
|
||||
|
||||
## Auth
|
||||
|
||||
### `POST /api/v1/auth/register`
|
||||
- Erstellt einen neuen Benutzer.
|
||||
|
||||
### `POST /api/v1/auth/login`
|
||||
- Loggt einen Benutzer ein.
|
||||
|
||||
### `GET /api/v1/auth/me`
|
||||
- Gibt den aktuell eingeloggten Benutzer zurueck.
|
||||
|
||||
## User / Profile
|
||||
|
||||
### `GET /api/v1/users/{id}`
|
||||
- Ruft das oeffentliche Benutzerprofil anhand der ID ab.
|
||||
|
||||
### `GET /api/v1/profile/me`
|
||||
- Ruft das eigene Profil des eingeloggten Benutzers ab.
|
||||
|
||||
### `PUT /api/v1/profile/me`
|
||||
- Bearbeitet das eigene Profil des eingeloggten Benutzers.
|
||||
|
||||
## Prompts
|
||||
|
||||
### `GET /api/v1/prompts`
|
||||
- Gibt alle veroeffentlichten Prompts zurueck.
|
||||
|
||||
### `GET /api/v1/prompts/{id}`
|
||||
- Ruft einen einzelnen Prompt anhand der ID ab.
|
||||
|
||||
### `POST /api/v1/prompts`
|
||||
- Erstellt einen neuen Prompt.
|
||||
|
||||
### `PUT /api/v1/prompts/{id}`
|
||||
- Bearbeitet einen bestehenden Prompt.
|
||||
|
||||
### `DELETE /api/v1/prompts/{id}`
|
||||
- Loescht einen Prompt.
|
||||
|
||||
## Filter fuer Marketplace
|
||||
|
||||
### `GET /api/v1/prompts?category=coding`
|
||||
- Filtert Prompts nach Kategorie.
|
||||
|
||||
### `GET /api/v1/prompts?search=python`
|
||||
- Sucht Prompts ueber einen Suchbegriff.
|
||||
|
||||
### `GET /api/v1/prompts?creatorId=5`
|
||||
- Filtert Prompts nach einem bestimmten Creator.
|
||||
|
||||
## Kategorien
|
||||
|
||||
### `GET /api/v1/categories`
|
||||
- Gibt alle verfuegbaren Kategorien zurueck.
|
||||
|
||||
## Kaeufe
|
||||
|
||||
### `POST /api/v1/purchases`
|
||||
- Erstellt einen Kauf fuer einen Prompt.
|
||||
|
||||
### `GET /api/v1/purchases/me`
|
||||
- Gibt alle eigenen Kaeufe des eingeloggten Benutzers zurueck.
|
||||
|
||||
## Reviews
|
||||
|
||||
### `GET /api/v1/prompts/{id}/reviews`
|
||||
- Ruft alle Bewertungen fuer einen Prompt ab.
|
||||
|
||||
### `POST /api/v1/prompts/{id}/reviews`
|
||||
- Erstellt eine neue Bewertung fuer einen Prompt.
|
||||
|
||||
78
KERNMODELL.md
Normal file
78
KERNMODELL.md
Normal file
@ -0,0 +1,78 @@
|
||||
# Kernmodell
|
||||
|
||||
Dieses Kernmodell beschreibt die wichtigsten Klassen, die OnlyPrompt fuer Login, Profile, Marketplace und Prompt-Kaeufe benoetigt.
|
||||
|
||||
## Klassen
|
||||
|
||||
### User
|
||||
- `id: int`
|
||||
- `username: string`
|
||||
- `email: string`
|
||||
- `passwordHash: string`
|
||||
- `role: string`
|
||||
- `createdAt: datetime`
|
||||
|
||||
### Profile
|
||||
- `id: int`
|
||||
- `userId: int`
|
||||
- `displayName: string`
|
||||
- `bio: string`
|
||||
- `avatarUrl: string`
|
||||
- `specialties: string`
|
||||
|
||||
### Prompt
|
||||
- `id: int`
|
||||
- `creatorId: int`
|
||||
- `categoryId: int`
|
||||
- `title: string`
|
||||
- `description: string`
|
||||
- `content: text`
|
||||
- `price: decimal`
|
||||
- `thumbnailUrl: string`
|
||||
- `ratingAverage: float`
|
||||
- `reviewCount: int`
|
||||
- `status: string`
|
||||
- `createdAt: datetime`
|
||||
- `updatedAt: datetime`
|
||||
|
||||
### Category
|
||||
- `id: int`
|
||||
- `name: string`
|
||||
- `slug: string`
|
||||
|
||||
### Purchase
|
||||
- `id: int`
|
||||
- `buyerId: int`
|
||||
- `promptId: int`
|
||||
- `pricePaid: decimal`
|
||||
- `purchasedAt: datetime`
|
||||
|
||||
### Review
|
||||
- `id: int`
|
||||
- `promptId: int`
|
||||
- `userId: int`
|
||||
- `rating: int`
|
||||
- `comment: string`
|
||||
- `createdAt: datetime`
|
||||
|
||||
## Beziehungen
|
||||
|
||||
- Ein `User` hat genau ein `Profile`.
|
||||
- Ein `User` kann viele `Prompts` erstellen.
|
||||
- Ein `Prompt` gehoert zu genau einer `Category`.
|
||||
- Ein `User` kann viele `Prompts` ueber `Purchase` kaufen.
|
||||
- Ein `User` kann viele `Reviews` schreiben.
|
||||
- Ein `Prompt` kann viele `Reviews` erhalten.
|
||||
|
||||
## UML-Kurzform
|
||||
|
||||
```text
|
||||
User 1 --- 1 Profile
|
||||
User 1 --- * Prompt
|
||||
Category 1 --- * Prompt
|
||||
User 1 --- * Purchase
|
||||
Prompt 1 --- * Purchase
|
||||
User 1 --- * Review
|
||||
Prompt 1 --- * Review
|
||||
```
|
||||
|
||||
1
backend/__init__.py
Normal file
1
backend/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
|
||||
BIN
backend/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
backend/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
backend/__pycache__/main.cpython-313.pyc
Normal file
BIN
backend/__pycache__/main.cpython-313.pyc
Normal file
Binary file not shown.
BIN
backend/__pycache__/models.cpython-313.pyc
Normal file
BIN
backend/__pycache__/models.cpython-313.pyc
Normal file
Binary file not shown.
BIN
backend/__pycache__/schemas.cpython-313.pyc
Normal file
BIN
backend/__pycache__/schemas.cpython-313.pyc
Normal file
Binary file not shown.
BIN
backend/__pycache__/store.cpython-313.pyc
Normal file
BIN
backend/__pycache__/store.cpython-313.pyc
Normal file
Binary file not shown.
346
backend/main.py
Normal file
346
backend/main.py
Normal file
@ -0,0 +1,346 @@
|
||||
from datetime import datetime
|
||||
|
||||
from fastapi import FastAPI, HTTPException, Query, Response, status
|
||||
|
||||
from .models import ChatMessage, Favorite, Follow, Prompt, Rating, User
|
||||
from .schemas import (
|
||||
AuthResponse,
|
||||
ChatMessageCreateRequest,
|
||||
ChatMessageResponse,
|
||||
CreatorDetailResponse,
|
||||
FavoriteCreateRequest,
|
||||
FavoriteResponse,
|
||||
FollowResponse,
|
||||
LoginRequest,
|
||||
ProfileUpdateRequest,
|
||||
PromptCreateRequest,
|
||||
PromptResponse,
|
||||
PromptUpdateRequest,
|
||||
RatingCreateRequest,
|
||||
RatingResponse,
|
||||
RegisterRequest,
|
||||
UserResponse,
|
||||
)
|
||||
from .store import store
|
||||
|
||||
app = FastAPI(title="OnlyPrompt API", version="1.0.0")
|
||||
|
||||
|
||||
def get_current_user() -> User:
|
||||
user = store.users.get(store.current_user_id)
|
||||
if user is None:
|
||||
raise HTTPException(status_code=404, detail="Current user not found.")
|
||||
return user
|
||||
|
||||
|
||||
@app.post("/api/auth/register", response_model=AuthResponse, status_code=status.HTTP_201_CREATED)
|
||||
def register(payload: RegisterRequest) -> AuthResponse:
|
||||
if any(user.email == payload.email for user in store.users.values()):
|
||||
raise HTTPException(status_code=400, detail="Email already exists.")
|
||||
if any(user.username == payload.username for user in store.users.values()):
|
||||
raise HTTPException(status_code=400, detail="Username already exists.")
|
||||
|
||||
now = datetime.utcnow()
|
||||
user_id = store.next_id("user")
|
||||
user = User(
|
||||
id=user_id,
|
||||
email=payload.email,
|
||||
password_hash=payload.password,
|
||||
full_name=payload.full_name,
|
||||
username=payload.username,
|
||||
bio="",
|
||||
location="",
|
||||
avatar_url="",
|
||||
role=payload.role,
|
||||
is_verified=False,
|
||||
created_at=now,
|
||||
)
|
||||
store.users[user_id] = user
|
||||
store.current_user_id = user_id
|
||||
return AuthResponse(message="User created successfully.", user=UserResponse.model_validate(user))
|
||||
|
||||
|
||||
@app.post("/api/auth/login", response_model=AuthResponse)
|
||||
def login(payload: LoginRequest) -> AuthResponse:
|
||||
user = next((item for item in store.users.values() if item.email == payload.email), None)
|
||||
if user is None or user.password_hash != payload.password:
|
||||
raise HTTPException(status_code=401, detail="Invalid email or password.")
|
||||
|
||||
store.current_user_id = user.id
|
||||
return AuthResponse(message="Login successful.", user=UserResponse.model_validate(user))
|
||||
|
||||
|
||||
@app.get("/api/prompts", response_model=list[PromptResponse])
|
||||
def list_prompts(
|
||||
category: str | None = Query(default=None),
|
||||
search: str | None = Query(default=None),
|
||||
) -> list[PromptResponse]:
|
||||
prompts = list(store.prompts.values())
|
||||
|
||||
if category:
|
||||
prompts = [prompt for prompt in prompts if prompt.category.lower() == category.lower()]
|
||||
|
||||
if search:
|
||||
term = search.lower()
|
||||
prompts = [
|
||||
prompt
|
||||
for prompt in prompts
|
||||
if term in prompt.title.lower() or term in prompt.description.lower()
|
||||
]
|
||||
|
||||
return [PromptResponse.model_validate(prompt) for prompt in prompts]
|
||||
|
||||
|
||||
@app.get("/api/prompts/{prompt_id}", response_model=PromptResponse)
|
||||
def get_prompt(prompt_id: int) -> PromptResponse:
|
||||
prompt = store.prompts.get(prompt_id)
|
||||
if prompt is None:
|
||||
raise HTTPException(status_code=404, detail="Prompt not found.")
|
||||
return PromptResponse.model_validate(prompt)
|
||||
|
||||
|
||||
@app.post("/api/prompts", response_model=PromptResponse, status_code=status.HTTP_201_CREATED)
|
||||
def create_prompt(payload: PromptCreateRequest) -> PromptResponse:
|
||||
creator = store.users.get(payload.creator_id)
|
||||
if creator is None or creator.role != "creator":
|
||||
raise HTTPException(status_code=404, detail="Creator not found.")
|
||||
|
||||
prompt = Prompt(
|
||||
id=store.next_id("prompt"),
|
||||
title=payload.title,
|
||||
description=payload.description,
|
||||
content=payload.content,
|
||||
image_url=payload.image_url,
|
||||
category=payload.category,
|
||||
price=payload.price,
|
||||
creator_id=payload.creator_id,
|
||||
created_at=datetime.utcnow(),
|
||||
)
|
||||
store.prompts[prompt.id] = prompt
|
||||
return PromptResponse.model_validate(prompt)
|
||||
|
||||
|
||||
@app.put("/api/prompts/{prompt_id}", response_model=PromptResponse)
|
||||
def update_prompt(prompt_id: int, payload: PromptUpdateRequest) -> PromptResponse:
|
||||
prompt = store.prompts.get(prompt_id)
|
||||
if prompt is None:
|
||||
raise HTTPException(status_code=404, detail="Prompt not found.")
|
||||
|
||||
updates = payload.model_dump(exclude_unset=True)
|
||||
for field, value in updates.items():
|
||||
setattr(prompt, field, value)
|
||||
return PromptResponse.model_validate(prompt)
|
||||
|
||||
|
||||
@app.delete("/api/prompts/{prompt_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def delete_prompt(prompt_id: int) -> Response:
|
||||
if prompt_id not in store.prompts:
|
||||
raise HTTPException(status_code=404, detail="Prompt not found.")
|
||||
del store.prompts[prompt_id]
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@app.get("/api/creators", response_model=list[UserResponse])
|
||||
def list_creators(sort: str | None = Query(default=None)) -> list[UserResponse]:
|
||||
creators = [user for user in store.users.values() if user.role == "creator"]
|
||||
|
||||
if sort == "new":
|
||||
creators.sort(key=lambda item: item.created_at, reverse=True)
|
||||
elif sort == "popular":
|
||||
creators.sort(
|
||||
key=lambda item: sum(1 for follow in store.follows.values() if follow.creator_id == item.id),
|
||||
reverse=True,
|
||||
)
|
||||
elif sort == "top_rated":
|
||||
def creator_rating(user: User) -> float:
|
||||
creator_prompt_ids = [prompt.id for prompt in store.prompts.values() if prompt.creator_id == user.id]
|
||||
ratings = [rating.score for rating in store.ratings.values() if rating.prompt_id in creator_prompt_ids]
|
||||
return sum(ratings) / len(ratings) if ratings else 0
|
||||
|
||||
creators.sort(key=creator_rating, reverse=True)
|
||||
|
||||
return [UserResponse.model_validate(creator) for creator in creators]
|
||||
|
||||
|
||||
@app.get("/api/creators/{creator_id}", response_model=CreatorDetailResponse)
|
||||
def get_creator(creator_id: int) -> CreatorDetailResponse:
|
||||
creator = store.users.get(creator_id)
|
||||
if creator is None or creator.role != "creator":
|
||||
raise HTTPException(status_code=404, detail="Creator not found.")
|
||||
|
||||
prompts = [prompt for prompt in store.prompts.values() if prompt.creator_id == creator_id]
|
||||
return CreatorDetailResponse(
|
||||
creator=UserResponse.model_validate(creator),
|
||||
prompts=[PromptResponse.model_validate(prompt) for prompt in prompts],
|
||||
)
|
||||
|
||||
|
||||
@app.get("/api/prompts/{prompt_id}/ratings", response_model=list[RatingResponse])
|
||||
def list_ratings(prompt_id: int) -> list[RatingResponse]:
|
||||
if prompt_id not in store.prompts:
|
||||
raise HTTPException(status_code=404, detail="Prompt not found.")
|
||||
ratings = [rating for rating in store.ratings.values() if rating.prompt_id == prompt_id]
|
||||
return [RatingResponse.model_validate(rating) for rating in ratings]
|
||||
|
||||
|
||||
@app.post("/api/prompts/{prompt_id}/ratings", response_model=RatingResponse, status_code=status.HTTP_201_CREATED)
|
||||
def create_rating(prompt_id: int, payload: RatingCreateRequest) -> RatingResponse:
|
||||
if prompt_id not in store.prompts:
|
||||
raise HTTPException(status_code=404, detail="Prompt not found.")
|
||||
if payload.user_id not in store.users:
|
||||
raise HTTPException(status_code=404, detail="User not found.")
|
||||
|
||||
rating = Rating(
|
||||
id=store.next_id("rating"),
|
||||
prompt_id=prompt_id,
|
||||
user_id=payload.user_id,
|
||||
score=payload.score,
|
||||
comment=payload.comment,
|
||||
created_at=datetime.utcnow(),
|
||||
)
|
||||
store.ratings[rating.id] = rating
|
||||
return RatingResponse.model_validate(rating)
|
||||
|
||||
|
||||
@app.get("/api/favorites", response_model=list[FavoriteResponse])
|
||||
def list_favorites() -> list[FavoriteResponse]:
|
||||
current_user = get_current_user()
|
||||
favorites = [item for item in store.favorites.values() if item.user_id == current_user.id]
|
||||
return [FavoriteResponse.model_validate(item) for item in favorites]
|
||||
|
||||
|
||||
@app.post("/api/favorites", response_model=FavoriteResponse, status_code=status.HTTP_201_CREATED)
|
||||
def create_favorite(payload: FavoriteCreateRequest) -> FavoriteResponse:
|
||||
current_user = get_current_user()
|
||||
if payload.prompt_id not in store.prompts:
|
||||
raise HTTPException(status_code=404, detail="Prompt not found.")
|
||||
|
||||
existing = next(
|
||||
(
|
||||
item
|
||||
for item in store.favorites.values()
|
||||
if item.user_id == current_user.id and item.prompt_id == payload.prompt_id
|
||||
),
|
||||
None,
|
||||
)
|
||||
if existing is not None:
|
||||
return FavoriteResponse.model_validate(existing)
|
||||
|
||||
favorite = Favorite(
|
||||
id=store.next_id("favorite"),
|
||||
user_id=current_user.id,
|
||||
prompt_id=payload.prompt_id,
|
||||
created_at=datetime.utcnow(),
|
||||
)
|
||||
store.favorites[favorite.id] = favorite
|
||||
return FavoriteResponse.model_validate(favorite)
|
||||
|
||||
|
||||
@app.delete("/api/favorites/{prompt_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def delete_favorite(prompt_id: int) -> Response:
|
||||
current_user = get_current_user()
|
||||
favorite_id = next(
|
||||
(
|
||||
item.id
|
||||
for item in store.favorites.values()
|
||||
if item.user_id == current_user.id and item.prompt_id == prompt_id
|
||||
),
|
||||
None,
|
||||
)
|
||||
if favorite_id is None:
|
||||
raise HTTPException(status_code=404, detail="Favorite not found.")
|
||||
|
||||
del store.favorites[favorite_id]
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@app.post("/api/follows/{creator_id}", response_model=FollowResponse, status_code=status.HTTP_201_CREATED)
|
||||
def create_follow(creator_id: int) -> FollowResponse:
|
||||
current_user = get_current_user()
|
||||
creator = store.users.get(creator_id)
|
||||
if creator is None or creator.role != "creator":
|
||||
raise HTTPException(status_code=404, detail="Creator not found.")
|
||||
|
||||
existing = next(
|
||||
(
|
||||
item
|
||||
for item in store.follows.values()
|
||||
if item.follower_id == current_user.id and item.creator_id == creator_id
|
||||
),
|
||||
None,
|
||||
)
|
||||
if existing is not None:
|
||||
return FollowResponse.model_validate(existing)
|
||||
|
||||
follow = Follow(
|
||||
id=store.next_id("follow"),
|
||||
follower_id=current_user.id,
|
||||
creator_id=creator_id,
|
||||
created_at=datetime.utcnow(),
|
||||
)
|
||||
store.follows[follow.id] = follow
|
||||
return FollowResponse.model_validate(follow)
|
||||
|
||||
|
||||
@app.delete("/api/follows/{creator_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def delete_follow(creator_id: int) -> Response:
|
||||
current_user = get_current_user()
|
||||
follow_id = next(
|
||||
(
|
||||
item.id
|
||||
for item in store.follows.values()
|
||||
if item.follower_id == current_user.id and item.creator_id == creator_id
|
||||
),
|
||||
None,
|
||||
)
|
||||
if follow_id is None:
|
||||
raise HTTPException(status_code=404, detail="Follow not found.")
|
||||
|
||||
del store.follows[follow_id]
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@app.get("/api/profile", response_model=UserResponse)
|
||||
def get_profile() -> UserResponse:
|
||||
return UserResponse.model_validate(get_current_user())
|
||||
|
||||
|
||||
@app.put("/api/profile", response_model=UserResponse)
|
||||
def update_profile(payload: ProfileUpdateRequest) -> UserResponse:
|
||||
user = get_current_user()
|
||||
updates = payload.model_dump(exclude_unset=True)
|
||||
for field, value in updates.items():
|
||||
setattr(user, field, value)
|
||||
return UserResponse.model_validate(user)
|
||||
|
||||
|
||||
@app.get("/api/chats/{user_id}", response_model=list[ChatMessageResponse])
|
||||
def get_chat(user_id: int) -> list[ChatMessageResponse]:
|
||||
current_user = get_current_user()
|
||||
if user_id not in store.users:
|
||||
raise HTTPException(status_code=404, detail="User not found.")
|
||||
|
||||
messages = [
|
||||
message
|
||||
for message in store.chat_messages.values()
|
||||
if {message.sender_id, message.receiver_id} == {current_user.id, user_id}
|
||||
]
|
||||
messages.sort(key=lambda item: item.created_at)
|
||||
return [ChatMessageResponse.model_validate(message) for message in messages]
|
||||
|
||||
|
||||
@app.post("/api/chats", response_model=ChatMessageResponse, status_code=status.HTTP_201_CREATED)
|
||||
def create_chat_message(payload: ChatMessageCreateRequest) -> ChatMessageResponse:
|
||||
current_user = get_current_user()
|
||||
if payload.receiver_id not in store.users:
|
||||
raise HTTPException(status_code=404, detail="Receiver not found.")
|
||||
|
||||
message = ChatMessage(
|
||||
id=store.next_id("chat_message"),
|
||||
sender_id=current_user.id,
|
||||
receiver_id=payload.receiver_id,
|
||||
content=payload.content,
|
||||
created_at=datetime.utcnow(),
|
||||
)
|
||||
store.chat_messages[message.id] = message
|
||||
return ChatMessageResponse.model_validate(message)
|
||||
65
backend/models.py
Normal file
65
backend/models.py
Normal file
@ -0,0 +1,65 @@
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
@dataclass
|
||||
class User:
|
||||
id: int
|
||||
email: str
|
||||
password_hash: str
|
||||
full_name: str
|
||||
username: str
|
||||
bio: str
|
||||
location: str
|
||||
avatar_url: str
|
||||
role: str
|
||||
is_verified: bool
|
||||
created_at: datetime
|
||||
|
||||
|
||||
@dataclass
|
||||
class Prompt:
|
||||
id: int
|
||||
title: str
|
||||
description: str
|
||||
content: str
|
||||
image_url: str
|
||||
category: str
|
||||
price: float
|
||||
creator_id: int
|
||||
created_at: datetime
|
||||
|
||||
|
||||
@dataclass
|
||||
class Rating:
|
||||
id: int
|
||||
prompt_id: int
|
||||
user_id: int
|
||||
score: int
|
||||
comment: str
|
||||
created_at: datetime
|
||||
|
||||
|
||||
@dataclass
|
||||
class Favorite:
|
||||
id: int
|
||||
user_id: int
|
||||
prompt_id: int
|
||||
created_at: datetime
|
||||
|
||||
|
||||
@dataclass
|
||||
class Follow:
|
||||
id: int
|
||||
follower_id: int
|
||||
creator_id: int
|
||||
created_at: datetime
|
||||
|
||||
|
||||
@dataclass
|
||||
class ChatMessage:
|
||||
id: int
|
||||
sender_id: int
|
||||
receiver_id: int
|
||||
content: str
|
||||
created_at: datetime
|
||||
137
backend/schemas.py
Normal file
137
backend/schemas.py
Normal file
@ -0,0 +1,137 @@
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, EmailStr, Field
|
||||
|
||||
|
||||
class UserResponse(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
email: EmailStr
|
||||
full_name: str
|
||||
username: str
|
||||
bio: str
|
||||
location: str
|
||||
avatar_url: str
|
||||
role: str
|
||||
is_verified: bool
|
||||
created_at: datetime
|
||||
|
||||
|
||||
class RegisterRequest(BaseModel):
|
||||
email: EmailStr
|
||||
password: str = Field(min_length=6)
|
||||
full_name: str = Field(min_length=2, max_length=100)
|
||||
username: str = Field(min_length=3, max_length=50)
|
||||
role: str = Field(default="user")
|
||||
|
||||
|
||||
class LoginRequest(BaseModel):
|
||||
email: EmailStr
|
||||
password: str = Field(min_length=6)
|
||||
|
||||
|
||||
class AuthResponse(BaseModel):
|
||||
message: str
|
||||
user: UserResponse
|
||||
|
||||
|
||||
class ProfileUpdateRequest(BaseModel):
|
||||
full_name: Optional[str] = Field(default=None, min_length=2, max_length=100)
|
||||
bio: Optional[str] = Field(default=None, max_length=500)
|
||||
location: Optional[str] = Field(default=None, max_length=120)
|
||||
avatar_url: Optional[str] = Field(default=None, max_length=255)
|
||||
is_verified: Optional[bool] = None
|
||||
|
||||
|
||||
class PromptCreateRequest(BaseModel):
|
||||
title: str = Field(min_length=3, max_length=120)
|
||||
description: str = Field(min_length=10, max_length=500)
|
||||
content: str = Field(min_length=10)
|
||||
image_url: str = Field(max_length=255)
|
||||
category: str = Field(min_length=2, max_length=50)
|
||||
price: float = Field(ge=0)
|
||||
creator_id: int
|
||||
|
||||
|
||||
class PromptUpdateRequest(BaseModel):
|
||||
title: Optional[str] = Field(default=None, min_length=3, max_length=120)
|
||||
description: Optional[str] = Field(default=None, min_length=10, max_length=500)
|
||||
content: Optional[str] = Field(default=None, min_length=10)
|
||||
image_url: Optional[str] = Field(default=None, max_length=255)
|
||||
category: Optional[str] = Field(default=None, min_length=2, max_length=50)
|
||||
price: Optional[float] = Field(default=None, ge=0)
|
||||
|
||||
|
||||
class PromptResponse(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
title: str
|
||||
description: str
|
||||
content: str
|
||||
image_url: str
|
||||
category: str
|
||||
price: float
|
||||
creator_id: int
|
||||
created_at: datetime
|
||||
|
||||
|
||||
class CreatorDetailResponse(BaseModel):
|
||||
creator: UserResponse
|
||||
prompts: list[PromptResponse]
|
||||
|
||||
|
||||
class RatingCreateRequest(BaseModel):
|
||||
user_id: int
|
||||
score: int = Field(ge=1, le=5)
|
||||
comment: str = Field(default="", max_length=500)
|
||||
|
||||
|
||||
class RatingResponse(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
prompt_id: int
|
||||
user_id: int
|
||||
score: int
|
||||
comment: str
|
||||
created_at: datetime
|
||||
|
||||
|
||||
class FavoriteCreateRequest(BaseModel):
|
||||
prompt_id: int
|
||||
|
||||
|
||||
class FavoriteResponse(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
user_id: int
|
||||
prompt_id: int
|
||||
created_at: datetime
|
||||
|
||||
|
||||
class FollowResponse(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
follower_id: int
|
||||
creator_id: int
|
||||
created_at: datetime
|
||||
|
||||
|
||||
class ChatMessageCreateRequest(BaseModel):
|
||||
receiver_id: int
|
||||
content: str = Field(min_length=1, max_length=2000)
|
||||
|
||||
|
||||
class ChatMessageResponse(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
sender_id: int
|
||||
receiver_id: int
|
||||
content: str
|
||||
created_at: datetime
|
||||
103
backend/store.py
Normal file
103
backend/store.py
Normal file
@ -0,0 +1,103 @@
|
||||
from datetime import datetime
|
||||
|
||||
from .models import ChatMessage, Favorite, Follow, Prompt, Rating, User
|
||||
|
||||
|
||||
class InMemoryStore:
|
||||
def __init__(self) -> None:
|
||||
now = datetime.utcnow()
|
||||
|
||||
self.users = {
|
||||
1: User(
|
||||
id=1,
|
||||
email="jane@example.com",
|
||||
password_hash="demo-password",
|
||||
full_name="Jane Doe",
|
||||
username="janedoe",
|
||||
bio="AI creator focused on writing and coding prompts.",
|
||||
location="Zurich",
|
||||
avatar_url="/images/content/creator1.png",
|
||||
role="creator",
|
||||
is_verified=True,
|
||||
created_at=now,
|
||||
),
|
||||
2: User(
|
||||
id=2,
|
||||
email="max@example.com",
|
||||
password_hash="demo-password",
|
||||
full_name="Max Muster",
|
||||
username="maxm",
|
||||
bio="Prompt buyer and tester.",
|
||||
location="Chur",
|
||||
avatar_url="/images/content/creator2.png",
|
||||
role="user",
|
||||
is_verified=False,
|
||||
created_at=now,
|
||||
),
|
||||
}
|
||||
self.prompts = {
|
||||
1: Prompt(
|
||||
id=1,
|
||||
title="Python Code Assistant",
|
||||
description="Efficiently debug and write Python code with AI assistance.",
|
||||
content="You are an expert Python assistant...",
|
||||
image_url="/images/content/prompt2.png",
|
||||
category="Coding",
|
||||
price=19.99,
|
||||
creator_id=1,
|
||||
created_at=now,
|
||||
)
|
||||
}
|
||||
self.ratings = {
|
||||
1: Rating(
|
||||
id=1,
|
||||
prompt_id=1,
|
||||
user_id=2,
|
||||
score=5,
|
||||
comment="Very useful starter prompt.",
|
||||
created_at=now,
|
||||
)
|
||||
}
|
||||
self.favorites = {
|
||||
1: Favorite(
|
||||
id=1,
|
||||
user_id=2,
|
||||
prompt_id=1,
|
||||
created_at=now,
|
||||
)
|
||||
}
|
||||
self.follows = {
|
||||
1: Follow(
|
||||
id=1,
|
||||
follower_id=2,
|
||||
creator_id=1,
|
||||
created_at=now,
|
||||
)
|
||||
}
|
||||
self.chat_messages = {
|
||||
1: ChatMessage(
|
||||
id=1,
|
||||
sender_id=2,
|
||||
receiver_id=1,
|
||||
content="Hi, I have a question about your prompt.",
|
||||
created_at=now,
|
||||
)
|
||||
}
|
||||
|
||||
self.current_user_id = 2
|
||||
self.next_ids = {
|
||||
"user": 3,
|
||||
"prompt": 2,
|
||||
"rating": 2,
|
||||
"favorite": 2,
|
||||
"follow": 2,
|
||||
"chat_message": 2,
|
||||
}
|
||||
|
||||
def next_id(self, key: str) -> int:
|
||||
value = self.next_ids[key]
|
||||
self.next_ids[key] += 1
|
||||
return value
|
||||
|
||||
|
||||
store = InMemoryStore()
|
||||
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@ -0,0 +1,4 @@
|
||||
fastapi
|
||||
uvicorn
|
||||
pydantic
|
||||
email-validator
|
||||
Loading…
x
Reference in New Issue
Block a user