Compare commits
13 Commits
ba1f7eb450
...
1.1.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 9424276474 | |||
| 7949312f9a | |||
| f814a1ba00 | |||
| a910bffcc9 | |||
| 06e24e35e1 | |||
| 523ac2228d | |||
| 95a232fb78 | |||
| 355aff8cf3 | |||
| 2ecf7ae56d | |||
| a2ab535256 | |||
| c3788356c7 | |||
| fa012c3161 | |||
| 1c7e95b119 |
238
README.MD
Normal file
238
README.MD
Normal file
@ -0,0 +1,238 @@
|
||||
# Habr article API
|
||||
|
||||
This is a simple API that can be deployed on your server to access habr.com's articles content, as well as keeping a record of articles and their ratings which you can manage by connecting to corresponding endpoints.
|
||||
|
||||
From here on on out we will call a pair "article_url" - "rating" an **entry**.
|
||||
|
||||
## API Reference
|
||||
|
||||
|
||||
|
||||
|
||||
### Ping
|
||||
|
||||
```http
|
||||
GET /api/ping
|
||||
```
|
||||
|
||||
A basic ping endpoint.
|
||||
|
||||
#### Response on success
|
||||
|
||||
`application/json`
|
||||
```json
|
||||
{
|
||||
"message": "pong"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
### See current entries
|
||||
|
||||
|
||||
```http
|
||||
GET /api/rates
|
||||
```
|
||||
|
||||
Returns all entries in the PostreSQL DB.
|
||||
|
||||
#### Response on success
|
||||
|
||||
`application/json`
|
||||
```json
|
||||
{
|
||||
"article_url_1": rating(0 or 1),
|
||||
"article_url_2": rating(0 or 1),
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Make a new entry
|
||||
|
||||
|
||||
```http
|
||||
POST /api/article/rate
|
||||
```
|
||||
|
||||
Save a new entry to the DB.
|
||||
|
||||
#### Request body
|
||||
|
||||
`application/json`
|
||||
```json
|
||||
{
|
||||
"url": {article_url},
|
||||
"rating": {integer, 0 or 1}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### Response on success
|
||||
|
||||
`application/json`
|
||||
```json
|
||||
{
|
||||
"message": "success",
|
||||
"url": "{article_url}",
|
||||
"rating": {integer, 0 or 1}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Delete an entry
|
||||
|
||||
|
||||
```http
|
||||
POST /api/article/remove_rate
|
||||
```
|
||||
|
||||
Delete an existing entry from the DB.
|
||||
|
||||
#### Request body
|
||||
|
||||
`application/json`
|
||||
```json
|
||||
{
|
||||
"url": "{article_url}"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### Response on success
|
||||
|
||||
`application/json`
|
||||
```json
|
||||
{
|
||||
"message": "success"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### Get article html
|
||||
|
||||
```http
|
||||
POST /api/article/api/article/get/html
|
||||
```
|
||||
|
||||
Get hmtl of a desired habr article body encoded in base64.
|
||||
|
||||
#### Request body
|
||||
|
||||
`application/json`
|
||||
```json
|
||||
{
|
||||
"url": "{article_url}"
|
||||
}
|
||||
```
|
||||
|
||||
#### Response on success
|
||||
|
||||
`text/plain`
|
||||
```
|
||||
{article_url}
|
||||
|
||||
{b64 encoded html}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### Get article MD
|
||||
|
||||
```http
|
||||
POST /api/article/api/article/get/md
|
||||
```
|
||||
|
||||
Get md of a desired habr article body encoded in base64.
|
||||
|
||||
#### Request body
|
||||
|
||||
`application/json`
|
||||
```json
|
||||
{
|
||||
"url": "{article_url}"
|
||||
}
|
||||
```
|
||||
|
||||
#### Response on success
|
||||
|
||||
`text/plain`
|
||||
```
|
||||
{article_url}
|
||||
|
||||
{b64 encoded md}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### Get html of N articles from habr.com/feed
|
||||
|
||||
```http
|
||||
POST /api/article/api/articles/get/html
|
||||
```
|
||||
|
||||
Get html bodies of N last articles from [habr.com/feed](habr.com/feed)
|
||||
|
||||
#### Request body
|
||||
|
||||
`application/json`
|
||||
```json
|
||||
{
|
||||
"amount": {articles_amount}
|
||||
}
|
||||
```
|
||||
|
||||
#### Response on success
|
||||
|
||||
`application/json`
|
||||
```json
|
||||
{
|
||||
"{article_url_1}": "{b64_encoded_html}",
|
||||
"{article_url_2}": "{b64_encoded_html}",
|
||||
...
|
||||
"{article_url_n}": "{b64_encoded_html}"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### Get MD of N articles from habr.com/feed
|
||||
|
||||
```http
|
||||
POST /api/article/api/articles/get/md
|
||||
```
|
||||
|
||||
Get MD of N last articles from [habr.com/feed](habr.com/feed)
|
||||
|
||||
#### Request body
|
||||
|
||||
`application/json`
|
||||
```json
|
||||
{
|
||||
"amount": {articles_amount}
|
||||
}
|
||||
```
|
||||
|
||||
#### Response on success
|
||||
|
||||
`application/json`
|
||||
```json
|
||||
{
|
||||
"{article_url_1}": "{b64_encoded_md}",
|
||||
"{article_url_2}": "{b64_encoded_md}",
|
||||
...
|
||||
"{article_url_n}": "{b64_encoded_md}"
|
||||
}
|
||||
```
|
||||
@ -8,8 +8,8 @@ services:
|
||||
DB_NAME: postgres
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
HOST_NAME: "localhost"
|
||||
PG_PORT: "8000"
|
||||
HOST_NAME: postgres
|
||||
PG_PORT: 5432
|
||||
LOGGING_LEVEL: "INFO"
|
||||
ENABLE_API_DOCS: "True"
|
||||
UVI_LOGGING_LEVEL: "info"
|
||||
@ -27,7 +27,7 @@ services:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
ports:
|
||||
- "4001:5432"
|
||||
- :5432
|
||||
healthcheck:
|
||||
test: ["CMD", "pg_isready", "-U", "postgres"]
|
||||
interval: 5s
|
||||
|
||||
@ -2,10 +2,12 @@ FROM python:3.13-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends libpq-dev build-essential
|
||||
|
||||
COPY requirements.txt ./
|
||||
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
CMD ["python", "src/main.py"]
|
||||
|
||||
@ -17,6 +17,7 @@ def set_connection():
|
||||
logger.info('Connection to PostreSQL DB set successfully')
|
||||
except psycopg2.Error as e:
|
||||
logger.error(f'Failed to set connection to the PostgreSQL DB: {e.pgerror}')
|
||||
exit()
|
||||
|
||||
|
||||
def close_connection(connection):
|
||||
@ -53,9 +54,14 @@ def delete_entry(article_url, connection):
|
||||
def get_all_entries(connection):
|
||||
try:
|
||||
cursor = connection.cursor()
|
||||
cursor.execute('SELECT article_url, rating FROM harticle.articles;')
|
||||
entries = cursor.fetchall()
|
||||
cursor.execute('SELECT article_url FROM harticle.articles;')
|
||||
urls = cursor.fetchall()
|
||||
cursor.execute('SELECT rating FROM harticle.articles;')
|
||||
ratings = cursor.fetchall()
|
||||
logger.info('All entry pairs have been retrieved successfully')
|
||||
entries = {}
|
||||
for i in range(len(urls)):
|
||||
entries[urls[i][0]] = ratings[i][0]
|
||||
return entries
|
||||
except psycopg2.Error as e:
|
||||
logger.error(f'Failed to fetch DB entries: {e.pgerror}')
|
||||
@ -93,4 +99,3 @@ def table_creator(schema_name, table_name, connection):
|
||||
logger.info(f'Successfully created table {table_name} in schema {schema_name} if it didn\'t exist yet')
|
||||
except psycopg2.Error as e:
|
||||
logger.error(f'Error during table creation: {e}')
|
||||
|
||||
|
||||
@ -12,12 +12,15 @@ if config.enable_api_docs:
|
||||
else:
|
||||
docs_url = None
|
||||
|
||||
schema_name = 'harticle'
|
||||
table_name = 'articles'
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
DBwork.set_connection()
|
||||
DBwork.schema_creator(config.schema_name, db.connection)
|
||||
DBwork.table_creator(config.schema_name, config.table_name, db.connection)
|
||||
DBwork.schema_creator(schema_name, db.connection)
|
||||
DBwork.table_creator(schema_name, table_name, db.connection)
|
||||
yield
|
||||
DBwork.close_connection(db.connection)
|
||||
|
||||
|
||||
@ -6,8 +6,6 @@ postgres_user = config('POSTGRES_USER')
|
||||
postgres_password = config('POSTGRES_PASSWORD')
|
||||
host_name = config('HOST_NAME')
|
||||
port = config('PG_PORT')
|
||||
schema_name = config('SCHEMA_NAME')
|
||||
table_name = config('TABLE_NAME')
|
||||
|
||||
enable_api_docs = config('ENABLE_API_DOCS', cast=bool)
|
||||
|
||||
|
||||
@ -5,4 +5,4 @@ from app_creator import create_app
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = create_app()
|
||||
uvicorn.run(app=app, host="127.0.0.1", port=8000, log_level=uvicorn_logging_level.lower())
|
||||
uvicorn.run(app=app, host="0.0.0.0", port=8000, log_level=uvicorn_logging_level.lower())
|
||||
|
||||
@ -4,7 +4,6 @@ import scraper
|
||||
from fastapi import Response, status, APIRouter
|
||||
from pydantic import BaseModel
|
||||
import psycopg2
|
||||
from json import dumps
|
||||
import base64
|
||||
|
||||
|
||||
@ -12,6 +11,7 @@ router = APIRouter(prefix='/api')
|
||||
|
||||
|
||||
class Entry(BaseModel):
|
||||
username: str
|
||||
url: str
|
||||
rating: int | None = None
|
||||
|
||||
@ -31,7 +31,7 @@ async def ping():
|
||||
|
||||
@router.get('/rates')
|
||||
async def get_rates():
|
||||
result = dumps(DBwork.get_all_entries(db.connection))
|
||||
result = DBwork.get_all_entries(db.connection)
|
||||
return result
|
||||
|
||||
|
||||
@ -48,6 +48,7 @@ async def save_rating(entry: Entry, response: Response):
|
||||
message = 'internal server error'
|
||||
finally:
|
||||
return {'message': message,
|
||||
'username': entry.username,
|
||||
'url': entry.url,
|
||||
'rating': entry.rating
|
||||
}
|
||||
@ -69,14 +70,14 @@ async def remove_rating(entry: Entry, response: Response):
|
||||
async def get_article_html(article: Article, response: Response = None):
|
||||
html_string = await scraper.get_article_html(article.url)
|
||||
b64_string = base64.b64encode(html_string.encode('utf-8')).decode('utf-8')
|
||||
return Response(content=b64_string, media_type='text/plain')
|
||||
return Response(content=article.url + '\r\n' + b64_string, media_type='text/plain')
|
||||
|
||||
|
||||
@router.post('/article/get/md')
|
||||
async def get_article_md(article: Article, response: Response = None):
|
||||
md_string = await scraper.get_article_html(article.url, md=True)
|
||||
b64_string = base64.b64encode(md_string.encode('utf-8')).decode('utf-8')
|
||||
return Response(content=b64_string, media_type='text/plain')
|
||||
return Response(content=article.url + '\r\n' + b64_string, media_type='text/plain')
|
||||
|
||||
|
||||
@router.post('/articles/get/html')
|
||||
|
||||
Reference in New Issue
Block a user