7 Commits

Author SHA1 Message Date
06e24e35e1 Changed 127.0.0.1 to 0.0.0.0
All checks were successful
Build and Push Docker Image / build-and-push (release) Successful in 1m59s
2025-09-15 14:36:54 +03:00
523ac2228d Implemented force exit on connection failure 2025-09-09 17:37:36 +03:00
95a232fb78 Merge branch 'main' of https://git.frik.su/n0one/habr-article-API 2025-09-09 15:24:21 +03:00
355aff8cf3 Table and schema names are now no longer pulled from .env 2025-09-09 15:22:20 +03:00
2ecf7ae56d Update README.MD 2025-09-06 03:37:55 +03:00
a2ab535256 Add README.MD 2025-09-06 03:37:07 +03:00
c3788356c7 Something i'm too ashamed to even write 2025-09-06 03:24:44 +03:00
6 changed files with 247 additions and 8 deletions

238
README.MD Normal file
View 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}"
}
```

View File

@ -17,6 +17,7 @@ def set_connection():
logger.info('Connection to PostreSQL DB set successfully') logger.info('Connection to PostreSQL DB set successfully')
except psycopg2.Error as e: except psycopg2.Error as e:
logger.error(f'Failed to set connection to the PostgreSQL DB: {e.pgerror}') logger.error(f'Failed to set connection to the PostgreSQL DB: {e.pgerror}')
exit()
def close_connection(connection): def close_connection(connection):

View File

@ -12,12 +12,15 @@ if config.enable_api_docs:
else: else:
docs_url = None docs_url = None
schema_name = 'harticle'
table_name = 'articles'
@asynccontextmanager @asynccontextmanager
async def lifespan(app: FastAPI): async def lifespan(app: FastAPI):
DBwork.set_connection() DBwork.set_connection()
DBwork.schema_creator(config.schema_name, db.connection) DBwork.schema_creator(schema_name, db.connection)
DBwork.table_creator(config.schema_name, config.table_name, db.connection) DBwork.table_creator(schema_name, table_name, db.connection)
yield yield
DBwork.close_connection(db.connection) DBwork.close_connection(db.connection)

View File

@ -6,8 +6,6 @@ postgres_user = config('POSTGRES_USER')
postgres_password = config('POSTGRES_PASSWORD') postgres_password = config('POSTGRES_PASSWORD')
host_name = config('HOST_NAME') host_name = config('HOST_NAME')
port = config('PG_PORT') port = config('PG_PORT')
schema_name = config('SCHEMA_NAME')
table_name = config('TABLE_NAME')
enable_api_docs = config('ENABLE_API_DOCS', cast=bool) enable_api_docs = config('ENABLE_API_DOCS', cast=bool)

View File

@ -5,4 +5,4 @@ from app_creator import create_app
if __name__ == '__main__': if __name__ == '__main__':
app = create_app() 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())

View File

@ -4,7 +4,6 @@ import scraper
from fastapi import Response, status, APIRouter from fastapi import Response, status, APIRouter
from pydantic import BaseModel from pydantic import BaseModel
import psycopg2 import psycopg2
from json import dumps
import base64 import base64
@ -69,14 +68,14 @@ async def remove_rating(entry: Entry, response: Response):
async def get_article_html(article: Article, response: Response = None): async def get_article_html(article: Article, response: Response = None):
html_string = await scraper.get_article_html(article.url) html_string = await scraper.get_article_html(article.url)
b64_string = base64.b64encode(html_string.encode('utf-8')).decode('utf-8') 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') @router.post('/article/get/md')
async def get_article_md(article: Article, response: Response = None): async def get_article_md(article: Article, response: Response = None):
md_string = await scraper.get_article_html(article.url, md=True) md_string = await scraper.get_article_html(article.url, md=True)
b64_string = base64.b64encode(md_string.encode('utf-8')).decode('utf-8') 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') @router.post('/articles/get/html')