Compare commits
10 Commits
ba1f7eb450
...
1.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 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
|
DB_NAME: postgres
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
HOST_NAME: "localhost"
|
HOST_NAME: postgres
|
||||||
PG_PORT: "8000"
|
PG_PORT: 5432
|
||||||
LOGGING_LEVEL: "INFO"
|
LOGGING_LEVEL: "INFO"
|
||||||
ENABLE_API_DOCS: "True"
|
ENABLE_API_DOCS: "True"
|
||||||
UVI_LOGGING_LEVEL: "info"
|
UVI_LOGGING_LEVEL: "info"
|
||||||
@ -27,7 +27,7 @@ services:
|
|||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
ports:
|
ports:
|
||||||
- "4001:5432"
|
- :5432
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "pg_isready", "-U", "postgres"]
|
test: ["CMD", "pg_isready", "-U", "postgres"]
|
||||||
interval: 5s
|
interval: 5s
|
||||||
|
|||||||
@ -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):
|
||||||
@ -53,9 +54,14 @@ def delete_entry(article_url, connection):
|
|||||||
def get_all_entries(connection):
|
def get_all_entries(connection):
|
||||||
try:
|
try:
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
cursor.execute('SELECT article_url, rating FROM harticle.articles;')
|
cursor.execute('SELECT article_url FROM harticle.articles;')
|
||||||
entries = cursor.fetchall()
|
urls = cursor.fetchall()
|
||||||
|
cursor.execute('SELECT rating FROM harticle.articles;')
|
||||||
|
ratings = cursor.fetchall()
|
||||||
logger.info('All entry pairs have been retrieved successfully')
|
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
|
return entries
|
||||||
except psycopg2.Error as e:
|
except psycopg2.Error as e:
|
||||||
logger.error(f'Failed to fetch DB entries: {e.pgerror}')
|
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')
|
logger.info(f'Successfully created table {table_name} in schema {schema_name} if it didn\'t exist yet')
|
||||||
except psycopg2.Error as e:
|
except psycopg2.Error as e:
|
||||||
logger.error(f'Error during table creation: {e}')
|
logger.error(f'Error during table creation: {e}')
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
|
|
||||||
|
|||||||
@ -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())
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|
||||||
@ -31,7 +30,7 @@ async def ping():
|
|||||||
|
|
||||||
@router.get('/rates')
|
@router.get('/rates')
|
||||||
async def get_rates():
|
async def get_rates():
|
||||||
result = dumps(DBwork.get_all_entries(db.connection))
|
result = DBwork.get_all_entries(db.connection)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@ -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')
|
||||||
|
|||||||
Reference in New Issue
Block a user