Skip to content

Commit

Permalink
Merge pull request #2 from junah201/develop
Browse files Browse the repository at this point in the history
V1.0.0
  • Loading branch information
junah201 authored May 24, 2023
2 parents e3c044a + 68daa9c commit 9ce4fb7
Show file tree
Hide file tree
Showing 18 changed files with 667 additions and 459 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.pyc
11 changes: 11 additions & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM python:3.9-slim-buster

WORKDIR /app
COPY . .

COPY requirements.txt requirements.txt
RUN pip3 install -r requirements.txt

EXPOSE 8000

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
1 change: 0 additions & 1 deletion backend/Procfile

This file was deleted.

3 changes: 3 additions & 0 deletions backend/app/common/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import os

DATABASE_URL = os.environ.get("DATABASE_URL")
27 changes: 27 additions & 0 deletions backend/app/database/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from app.common import config

import pymysql

pymysql.install_as_MySQLdb()

engine = create_engine(
config.DATABASE_URL
)
SessionLocal = sessionmaker(
autocommit=False,
autoflush=False,
bind=engine
)

Base = declarative_base()


def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
45 changes: 45 additions & 0 deletions backend/app/database/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from sqlalchemy import Column, String, Integer, DateTime, JSON, ForeignKey, BOOLEAN, func, VARCHAR, VARCHAR, DATE
from sqlalchemy.orm import relationship
from sqlalchemy.dialects.mysql import INTEGER
from app.database.database import Base


class Channel(Base):
__tablename__ = "channel"

id = Column(
INTEGER(unsigned=True),
primary_key=True,
index=True,
comment="채널 고유번호"
)
panel_title = Column(
VARCHAR(255),
nullable=False,
default="Naver Cafe",
comment="패널에 보일 제목"
)
cafe_name = Column(
VARCHAR(255),
nullable=False,
default="",
comment="카페 이름"
)
cafe_id = Column(
VARCHAR(255),
nullable=False,
default="",
comment="카페 고유번호"
)
cafe_menu_id = Column(
VARCHAR(255),
nullable=False,
default="",
comment="카페 메뉴 고유번호"
)
cafe_board_type = Column(
VARCHAR(255),
nullable=False,
default="L",
comment="카페 게시판 타입"
)
34 changes: 34 additions & 0 deletions backend/app/database/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from pydantic import BaseModel, validator, EmailStr, constr
from typing import Optional


class ChannelCreate(BaseModel):
id: int


class ChannelUpdate(BaseModel):
panel_title: str
cafe_name: str
cafe_id: str
cafe_menu_id: str
cafe_board_type: str


class Channel(ChannelCreate, ChannelUpdate):
class Config:
orm_mode = True


class Post(BaseModel):
title: str
link: str
writer: str
date: str


class Board(BaseModel):
cafe_name: str
board_name: str
cafe_id: str
cafe_menu_id: Optional[str] = None
cafe_board_type: str
171 changes: 171 additions & 0 deletions backend/app/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
from fastapi import FastAPI, Depends, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from app.database.database import get_db, engine
from app.database import models, schemas
from sqlalchemy.orm import Session
import datetime
from typing import Optional, List
import aiohttp
from bs4 import BeautifulSoup

models.Base.metadata.create_all(bind=engine, checkfirst=True)

app = FastAPI()

app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)


@app.get("/")
async def root():
return f"Notification API (UTC: {datetime.datetime.utcnow().strftime('%Y.%m.%d %H:%M:%S')})"


@app.get("/{id}/config", response_model=schemas.Channel)
async def get_config(id: str, db: Session = Depends(get_db)):
db_channel: Optional[models.Channel] = db.query(models.Channel).filter(
models.Channel.id == id).first()

if not db_channel:
db_channel = models.Channel(id=id)
db.add(db_channel)
db.commit()

return db_channel


@app.post("/{id}/config", response_model=schemas.Channel)
async def post_config(id: str, channel: schemas.ChannelUpdate, db: Session = Depends(get_db)):
db_channel: Optional[models.Channel] = db.query(models.Channel).filter(
models.Channel.id == id).first()

if not db_channel:
db_channel = models.Channel(id=id)
db.add(db_channel)
db.commit()

db_channel.panel_title = channel.panel_title
db_channel.cafe_name = channel.cafe_name
db_channel.cafe_id = channel.cafe_id
db_channel.cafe_menu_id = channel.cafe_menu_id
db_channel.cafe_board_type = channel.cafe_board_type

db.commit()

return db_channel


@app.get("/{id}/posts", response_model=List[schemas.Post])
async def get_posts(id: str, db: Session = Depends(get_db)):
db_channel: Optional[models.Channel] = db.query(models.Channel).filter(
models.Channel.id == id).first()

if not db_channel:
raise HTTPException(
status_code=404, detail="Channel not found"
)

result: List[schemas.Post] = []

async with aiohttp.ClientSession(
connector=aiohttp.TCPConnector(),
headers={
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36', },
trust_env=True
) as session:
async with session.get(
"https://cafe.naver.com/ArticleList.nhn",
params={
'search.clubid': db_channel.cafe_id,
'search.menuid': db_channel.cafe_menu_id,
'search.boardtype': db_channel.cafe_board_type,
},
) as response:
html = await response.text()

soup = BeautifulSoup(html, 'html.parser')
soup.prettify()

table_els = soup.select("#main-area > div.article-board.m-tcol-c")
els = table_els[1].select(
"div.article-board > table > tbody > tr")

def get_title(text: str):
text = text.replace("\n", "").replace(
"\t", "").replace(" ", "")
return text

for i in els:
result.append(
schemas.Post(
title=get_title(i.select_one(
"td.td_article > div.board-list > div > a.article").text),
link=f"https://cafe.naver.com{i.select_one('td.td_article > div.board-list > div > a.article').get('href')}",
writer=get_title(i.select_one(
'td.td_name > div.pers_nick_area').text),
date=f"{datetime.datetime.now().strftime('%Y.%m.%d')} {i.select_one('td.td_date').text}"
)
)

return result


@app.get("/{cafe_name}/boards", response_model=List[schemas.Board])
async def get_boards(cafe_name: str):
result: List[schemas.Board] = []

async with aiohttp.ClientSession(
connector=aiohttp.TCPConnector(),
headers={
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36', },
trust_env=True
) as session:
async with session.get(
f"https://cafe.naver.com/{cafe_name}",
) as response:
html = await response.text()
soup = BeautifulSoup(html, 'html.parser')
soup.prettify()

els = soup.select(
"ul.cafe-menu-list > li"
)

for i in els:
cafe_menu_id = None
print(i.select_one("a").get("href").split("menuid="))
print(len(i.select_one("a").get("href").split("menuid=")))
if len(i.select_one("a").get("href").split("menuid=")) > 1:
cafe_menu_id = i.select_one("a").get("href").split(
"menuid=")[1].split("&")[0]

cafe_board_type = None
if len(i.select_one("a").get("href").split("boardtype=")) > 1:
cafe_board_type = i.select_one("a").get("href").split(
"boardtype=")[1].split("&")[0]
else:
continue

def get_title(text: str):
text = text.replace("\n", "").replace(
"\t", "").replace(" ", "").replace(" ", "").strip()
return text

result.append(
schemas.Board(
cafe_name=cafe_name,
board_name=get_title(i.select_one("a").text),
cafe_id=i.select_one("a").get("href").split(
"clubid=")[1].split("&")[0],
cafe_menu_id=cafe_menu_id,
cafe_board_type=i.select_one("a").get("href").split(
"boardtype=")[1].split("&")[0],
)
)

return result
Loading

0 comments on commit 9ce4fb7

Please sign in to comment.