hello-aditya commited on
Commit
74fee83
·
verified ·
1 Parent(s): 85c9d16

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +36 -78
app.py CHANGED
@@ -1,7 +1,7 @@
1
  from fastapi import FastAPI, HTTPException, Header, Depends
2
  from fastapi.middleware.cors import CORSMiddleware
3
  from fastapi.staticfiles import StaticFiles
4
- from fastapi.responses import FileResponse, HTMLResponse
5
  from pydantic import BaseModel
6
  from typing import Optional, List
7
  from datetime import datetime
@@ -9,82 +9,65 @@ import json
9
  import os
10
  from pathlib import Path
11
 
12
- # Load Secret from Environment Variable (Set this in HF Spaces Settings)
13
- ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD", "default_secure_password")
 
14
 
15
  DATA_DIR = Path("data")
16
  DATA_DIR.mkdir(exist_ok=True)
17
 
18
  ARTICLES_FILE = DATA_DIR / "articles.json"
19
- SNIPPETS_FILE = DATA_DIR / "snippets.json"
20
  COMMENTS_FILE = DATA_DIR / "comments.json"
21
 
22
- for file in [ARTICLES_FILE, SNIPPETS_FILE, COMMENTS_FILE]:
23
  if not file.exists():
24
  file.write_text("[]")
25
 
26
  def read_json(file_path):
27
- with open(file_path, 'r') as f:
28
- return json.load(f)
 
 
 
29
 
30
  def write_json(file_path, data):
31
  with open(file_path, 'w') as f:
32
  json.dump(data, f, indent=2, default=str)
33
 
34
- # Security Dependency
35
  async def verify_admin(x_admin_password: Optional[str] = Header(None)):
36
  if x_admin_password != ADMIN_PASSWORD:
37
  raise HTTPException(status_code=401, detail="Unauthorized Access")
38
  return True
39
 
40
- # Pydantic models
41
  class ArticleCreate(BaseModel):
42
  title: str
43
  slug: str
44
  content: str
45
- excerpt: Optional[str] = None
46
- tags: Optional[List[str]] = []
47
- published: bool = False
48
 
49
  class ArticleResponse(BaseModel):
50
  id: int
51
  title: str
52
  slug: str
53
  content: str
54
- excerpt: Optional[str]
55
- tags: Optional[List[str]]
56
  published: bool
57
  created_at: str
58
- updated_at: str
59
-
60
- class SnippetCreate(BaseModel):
61
- title: str
62
- description: Optional[str] = None
63
- code: str
64
- language: str
65
- tags: Optional[List[str]] = []
66
-
67
- class CommentCreate(BaseModel):
68
- author_name: str
69
- content: str
70
-
71
- class CommentResponse(BaseModel):
72
- id: int
73
- article_id: int
74
- author_name: str
75
- content: str
76
- created_at: str
77
 
78
  app = FastAPI(title="Aditya's DevLog API")
79
 
80
  app.add_middleware(
81
  CORSMiddleware,
82
  allow_origins=["*"],
83
- allow_credentials=True,
84
  allow_methods=["*"],
85
  allow_headers=["*"],
86
  )
87
 
 
88
  app.mount("/static", StaticFiles(directory="static"), name="static")
89
 
90
  @app.get("/")
@@ -95,14 +78,25 @@ async def read_root():
95
  async def read_admin():
96
  return FileResponse("static/admin.html")
97
 
98
- # --- ARTICLES ---
 
 
 
 
 
 
 
 
 
 
 
99
  @app.get("/api/articles/{slug}", response_model=ArticleResponse)
100
  async def get_article(slug: str):
101
  articles = read_json(ARTICLES_FILE)
102
- # Ensure we strip any whitespace and handle case sensitivity
103
- article = next((a for a in articles if a['slug'].strip().lower() == slug.strip().lower()), None)
104
  if not article:
105
- raise HTTPException(status_code=404, detail=f"Article with slug '{slug}' not found")
 
106
  return article
107
 
108
  @app.post("/api/articles", response_model=ArticleResponse)
@@ -112,10 +106,10 @@ async def create_article(article: ArticleCreate, auth: bool = Depends(verify_adm
112
  raise HTTPException(status_code=400, detail="Slug already exists")
113
 
114
  new_id = max([a['id'] for a in articles], default=0) + 1
115
- now = datetime.utcnow().isoformat()
116
  new_article = {
117
- "id": new_id, **article.dict(),
118
- "created_at": now, "updated_at": now
 
119
  }
120
  articles.append(new_article)
121
  write_json(ARTICLES_FILE, articles)
@@ -126,43 +120,7 @@ async def delete_article(article_id: int, auth: bool = Depends(verify_admin)):
126
  articles = read_json(ARTICLES_FILE)
127
  articles = [a for a in articles if a['id'] != article_id]
128
  write_json(ARTICLES_FILE, articles)
129
- return {"message": "Purged"}
130
-
131
- # --- SNIPPETS ---
132
- @app.get("/api/snippets")
133
- async def get_snippets():
134
- snippets = read_json(SNIPPETS_FILE)
135
- return sorted(snippets, key=lambda x: x['created_at'], reverse=True)
136
-
137
- @app.post("/api/snippets")
138
- async def create_snippet(snippet: SnippetCreate, auth: bool = Depends(verify_admin)):
139
- snippets = read_json(SNIPPETS_FILE)
140
- new_id = max([s['id'] for s in snippets], default=0) + 1
141
- new_snippet = {
142
- "id": new_id, **snippet.dict(),
143
- "created_at": datetime.utcnow().isoformat()
144
- }
145
- snippets.append(new_snippet)
146
- write_json(SNIPPETS_FILE, snippets)
147
- return new_snippet
148
-
149
- # --- COMMENTS ---
150
- @app.get("/api/articles/{article_id}/comments", response_model=List[CommentResponse])
151
- async def get_comments(article_id: int):
152
- comments = read_json(COMMENTS_FILE)
153
- return [c for c in comments if c['article_id'] == article_id]
154
-
155
- @app.post("/api/articles/{article_id}/comments", response_model=CommentResponse)
156
- async def create_comment(article_id: int, comment: CommentCreate):
157
- comments = read_json(COMMENTS_FILE)
158
- new_id = max([c['id'] for c in comments], default=0) + 1
159
- new_comment = {
160
- "id": new_id, "article_id": article_id, **comment.dict(),
161
- "created_at": datetime.utcnow().isoformat()
162
- }
163
- comments.append(new_comment)
164
- write_json(COMMENTS_FILE, comments)
165
- return new_comment
166
 
167
  if __name__ == "__main__":
168
  import uvicorn
 
1
  from fastapi import FastAPI, HTTPException, Header, Depends
2
  from fastapi.middleware.cors import CORSMiddleware
3
  from fastapi.staticfiles import StaticFiles
4
+ from fastapi.responses import FileResponse
5
  from pydantic import BaseModel
6
  from typing import Optional, List
7
  from datetime import datetime
 
9
  import os
10
  from pathlib import Path
11
 
12
+ # Load Secret from Environment Variable
13
+ # On HF Spaces, set 'ADMIN_PASSWORD' in the Settings > Secrets tab
14
+ ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD")
15
 
16
  DATA_DIR = Path("data")
17
  DATA_DIR.mkdir(exist_ok=True)
18
 
19
  ARTICLES_FILE = DATA_DIR / "articles.json"
 
20
  COMMENTS_FILE = DATA_DIR / "comments.json"
21
 
22
+ for file in [ARTICLES_FILE, COMMENTS_FILE]:
23
  if not file.exists():
24
  file.write_text("[]")
25
 
26
  def read_json(file_path):
27
+ try:
28
+ with open(file_path, 'r') as f:
29
+ return json.load(f)
30
+ except:
31
+ return []
32
 
33
  def write_json(file_path, data):
34
  with open(file_path, 'w') as f:
35
  json.dump(data, f, indent=2, default=str)
36
 
 
37
  async def verify_admin(x_admin_password: Optional[str] = Header(None)):
38
  if x_admin_password != ADMIN_PASSWORD:
39
  raise HTTPException(status_code=401, detail="Unauthorized Access")
40
  return True
41
 
42
+ # --- MODELS ---
43
  class ArticleCreate(BaseModel):
44
  title: str
45
  slug: str
46
  content: str
47
+ excerpt: Optional[str] = ""
48
+ tags: List[str] = []
49
+ published: bool = True
50
 
51
  class ArticleResponse(BaseModel):
52
  id: int
53
  title: str
54
  slug: str
55
  content: str
56
+ excerpt: str
57
+ tags: List[str]
58
  published: bool
59
  created_at: str
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
  app = FastAPI(title="Aditya's DevLog API")
62
 
63
  app.add_middleware(
64
  CORSMiddleware,
65
  allow_origins=["*"],
 
66
  allow_methods=["*"],
67
  allow_headers=["*"],
68
  )
69
 
70
+ # Serve UI
71
  app.mount("/static", StaticFiles(directory="static"), name="static")
72
 
73
  @app.get("/")
 
78
  async def read_admin():
79
  return FileResponse("static/admin.html")
80
 
81
+ # --- API ---
82
+ @app.get("/api/articles", response_model=List[ArticleResponse])
83
+ async def get_articles(published_only: bool = True):
84
+ articles = read_json(ARTICLES_FILE)
85
+ if published_only:
86
+ articles = [a for a in articles if a.get('published', True)]
87
+ # Ensure nested fields exist to avoid frontend crashes
88
+ for a in articles:
89
+ if "tags" not in a or a["tags"] is None: a["tags"] = []
90
+ if "excerpt" not in a or a["excerpt"] is None: a["excerpt"] = ""
91
+ return sorted(articles, key=lambda x: x['created_at'], reverse=True)
92
+
93
  @app.get("/api/articles/{slug}", response_model=ArticleResponse)
94
  async def get_article(slug: str):
95
  articles = read_json(ARTICLES_FILE)
96
+ article = next((a for a in articles if a['slug'].lower() == slug.lower()), None)
 
97
  if not article:
98
+ raise HTTPException(status_code=404, detail="Article not found")
99
+ if "tags" not in article or article["tags"] is None: article["tags"] = []
100
  return article
101
 
102
  @app.post("/api/articles", response_model=ArticleResponse)
 
106
  raise HTTPException(status_code=400, detail="Slug already exists")
107
 
108
  new_id = max([a['id'] for a in articles], default=0) + 1
 
109
  new_article = {
110
+ "id": new_id,
111
+ **article.dict(),
112
+ "created_at": datetime.utcnow().isoformat()
113
  }
114
  articles.append(new_article)
115
  write_json(ARTICLES_FILE, articles)
 
120
  articles = read_json(ARTICLES_FILE)
121
  articles = [a for a in articles if a['id'] != article_id]
122
  write_json(ARTICLES_FILE, articles)
123
+ return {"message": "Data Purged"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
 
125
  if __name__ == "__main__":
126
  import uvicorn