์ ์ฒด ์ฝ๋
https://github.com/melli0505/monitoring_system
0. ๋ณ๊ฒฝ์ฌํญ
1ํธ์ ์ฌ๋ฆฌ๊ณ ๋ฌด๋ ค 2๋ฌ์ ์๊ฐ์ด ์ง๋ฌ๋ค. ๋๋ฌธ์ ๋ด ๋ชจ๋ํฐ๋ง ์์คํ ์๋ ๋ง์ ๋ณํ๊ฐ ์์๋๋ฐ... ์ฌ์ค ๊ทธ๋ ๊ฒ ๋ง์ ๋ณํ๋ ์๋๋ค(?). 1ํธ์์๋ ํ ์ด๋ธ์ด user, data, sensor ์ด๋ ๊ฒ ์ธ ๊ฐ์ง๋ก ๊ตฌ์ฑํด๋จ์๋๋ฐ, ์ง๊ธ์ ๋๋ฏธ๋ฐ์ดํฐ๊ฐ ์๋ ์ค์ ๋ฐ์ดํฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํด์ ๋ชจ๋ํฐ๋ง ์์คํ ์ ๊ตฌ์ถํด๋๊ธฐ ๋๋ฌธ์ sensor ์์ด data ์ ํ์ ์ ๋ฐ๋ผ user ํฌํจ ์ด 7๊ฐ์ ๋ฐ์ดํฐ ํ ์ด๋ธ์ด ์์ฑ๋์๋ค.
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
username = Column(String, index=True)
email = Column(String, unique=True, index=True)
hashed_password = Column(String, index=True)
disable = Column(Boolean, index=True, default=True)
class Voltage(Base):
__tablename__ = "voltages"
id = Column(Integer, primary_key=True, index = True)
time = Column(TIMESTAMP, index=True)
voltage = Column(Float, index=True)
class Current(Base):
__tablename__ = "currents"
id = Column(Integer, primary_key=True, index = True)
time = Column(TIMESTAMP, index=True)
current = Column(Float, index=True)
class Power(Base):
__tablename__ = "powers"
id = Column(Integer, primary_key=True, index = True)
time = Column(TIMESTAMP, index=True)
power = Column(Float, index=True)
class Energy(Base):
__tablename__ = "energies"
id = Column(Integer, primary_key=True, index = True)
time = Column(TIMESTAMP, index=True)
energy = Column(Float, index=True)
class PF(Base):
__tablename__ = "pfs"
id = Column(Integer, primary_key=True, index = True)
time = Column(TIMESTAMP, index=True)
pf = Column(Float, index=True)
class Frequency(Base):
__tablename__ = "frequencies"
id = Column(Integer, primary_key=True, index = True)
time = Column(TIMESTAMP, index=True)
frequency = Column(Float, index=True)
๋ฐ์ดํฐ์ ์ถ์ฒ๋ ์ด ๋ ์์ด๋ค. ์ด ๋ ์๊ณผ ESP32 ๊ณ์ด ์๋์ด๋ ธ๋ฅผ ์ฐ๊ฒฐํด์ MQTT ๋ฐฉ์์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์๋ฒ๋ก ๋ณด๋ด๋๋ก ํด๋์๋ค.
pzem-004t ์ ์ด๋ฅผ ์ํ ์๋์ด๋ ธ ์ฝ๋๋ ์ฌ๊ธฐ์ ๊ตฌํํด๋์๋ค. ๋น์ฅ ์ฌ๊ธฐ์ ์ค์ํ ๊ฒ์ ์๋๋ ๋์ด๊ฐ๋๋ก ํ์.
https://github.com/melli0505/ESP32_PZEM-004t
1. MQTT ๋ฐ์ดํฐ ์ ์ฌ
์ฐ์ MQTT๋ก ๋ฐ์์จ ๋ฐ์ดํฐ๋ค์ ๊ฐ ๋ฐ์ดํฐ ํ์์ ๋ง๊ฒ ํธํ์ ์์ผ์ฃผ๊ณ , ์ ์ ํ๊ฒ ์ ๋ฐ์ดํธ๋ฅผ ์์ผ์ฃผ์ด์ผํ๋ค. ๊ฑฐ๋์ ๋ฏธํ๊ณ ์ฝ๋๋ฅผ ๋ณด์.
import paho.mqtt.client as mqtt
import time
import json
from db import models
from db.base import SessionLocal
broker_address = "YOUR IP ADDRESS"
# subscriber callback
def on_message(client, userdata, message):
db = SessionLocal()
converted = str(message.payload.decode("utf-8"))
value = json.loads(converted)
print(message.topic, value['voltage'], type(value['voltage']))
timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
data = [models.Voltage(time=timestamp, frequency=value["voltage"]),
models.Energy(time=timestamp, frequency=value["energy"]),
models.Power(time=timestamp, frequency=value["power"]),
models.Current(time=timestamp, frequency=value["current"]),
models.Frequency(time=timestamp, frequency=value["frequency"]),
models.PF(time=timestamp, frequency=value["pf"])]
for db_data in data:
db.add(db_data)
db.commit()
db.refresh(db_data)
db.close()
if __name__ == "__main__":
client = mqtt.Client("client")
client.connect(broker_address, 1883)
print("client connected")
client.on_message = on_message
client.subscribe("data")
print("client loop started")
client.loop_forever()
models
์์ ๋ฏธ๋ฆฌ ์ ์ํด๋ ๋ฐ์ดํฐ ๋ชจ๋ธ์ ๋ง์ถฐ์ ๋ค์ด์จ ๋ฐ์ดํฐ๋ค์ ๊ฐ๊ณตํด์ฃผ์๋ค. ๋ ๊ฐ์ ๊ฒฝ์ฐ์๋ ์ด 6๊ฐ ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ด๋ ์ชฝ์์ Json string ํํ๋ก ๊ฐ๊ณตํด์ ๋ฐ์์๊ธฐ ๋๋ฌธ์ ์ฒ๋ฆฌ๋ฅผ ์ด๋ ๊ฒ ํด์ฃผ์๋๋ฐ, ๊ฐ์ ๋ฐ์ดํฐ ํ์
์ ๋ง๊ฒ ํด์ฃผ๋ฉด ๋๋ค.
db.add(db_data)
์ db.refresh()
๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฐ์ดํฐ๋ฅผ ์ฌ๋ฆด ๋๋ง ์ฌ์ฉํ๋ค. ์ญ์ ์ ๊ฒฝ์ฐ์๋ delete -> commit ์์ผ๋ก ์ฌ์ฉํ๋ค. ์ ์ฌ๋ผ๊ฐ๋์ง ํ์ธํด๋ณด์.
๊ตฟ! ์ ๋ค์ด์จ ๊ฒ์ ํ์ธํ ์ ์์๋ค. ์ด 6๊ฐ์ ๋ฐ์ดํฐ ํ ์ด๋ธ ๋ชจ๋ ์ ์ ์ฌ๋๊ณ ์๋ค. ๊ทธ๋ ๋ค๋ฉด ๋ค์์ผ๋ก ํด์ค ๊ฒ์, ๊ทธ๋ ๋ค. ์๋น์ค ๊ตฌํ์ด๋ค.
2. ๋ก๊ทธ์ธ ๊ธฐ๋ฅ ๊ตฌํ
๋ก๊ทธ์ธ ๊ธฐ๋ฅ์ ์ฒ์ ๊ตฌํํ ๋ ๋ง์ด ํค๋งธ๋ค. ์ด๋ป๊ฒ ํ์ฌ ์ ์ํ ์ฌ๋์ด ๋ก๊ทธ์ธํ ์ฌ๋์ธ์ง, ๋๊ตฌ์ธ์ง๋ฅผ ์ ์ ์์ง? ์ด๋ป๊ฒ ํด์ผ ๋น๋ฐ๋ฒํธ๋ฅผ ์ํธํํ ์ ์์ง? ์ด๋ฐ ์ง๋ฌธ๋ค ๋์ ๊ฐ์ฅ ๊ฐ๋จํ ๋ฐฉ๋ฒ ํ๋๋ฅผ ์ ์ฉํ๊ธฐ๋ก ํ๋ค. ์ค์ ์๋น์ค์์๋ ์ด๋ฐ ์์ผ๋ก ๋น๋ฐ๋ฒํธ๋ฅผ ๊ด๋ฆฌํ๋ฉด ์์ฃผ ์ปค๋ค๋ ๋ณด์ ๋ฌธ์ ๊ฐ ์์ฉํ ์ ์์ผ๋ฏ๋ก ์ฃผ์(...)
์ฐ์ ํ์ฌ ์ ์ํ ์ฌ๋์ ๊ถํ์ ์ฟ ํค์ ์ ์ฅ๋๋ค. ์ฟ ํค์ access token์ด๋ผ๋ ์ด๋ฆ์ผ๋ก access token์ ๋ฐํํด์ฃผ๋ฉด, ์ด๊ฑธ ๊ฐ์ง๊ณ ํ์ด์ง๋ฅผ ์ฎ๊ฒจ๋ค๋๋ฉด์ ๋ง๋ฃ ์ ๊น์ง ๋ด๊ฐ ๋๊ตฌ์์! ํ๊ณ ์๋ฒ์ ์๋ ค์ค ์ ์๋ค.
๊ทธ๋ฆฌ๊ณ ๋น๋ฐ๋ฒํธ๋ ์ํธํํด์ database์ ์ ์ฅ๋์ด์ผํ๊ณ , ๋ก๊ทธ์ธํ ๋ ์ํธํ ๋ฐฉ์์ ๋ฐ๋ผ ์ ๋ ฅ๋ ๋น๋ฐ๋ฒํธ์ ์ํธํ๋ ๋น๋ฐ๋ฒํธ๊ฐ ๋งค์นญ๋๋์ง๋ฅผ ํ์ธํ๊ณ , ๋ง๋ค๋ฉด ํ ํฐ์ ๋ฐํํ๋ฉด ๋๋ค. ํ๋์ฉ ์์ธํ ๋ณด์.
A. Hashed password ์์ฑํ๊ธฐ
์ฐ์ ๋น๋ฐ๋ฒํธ ์ํธํ๋ถํฐ ํด๋ณด์. passlib์ context์์ CryptContext๋ผ๋ ๋ชจ๋์ ํ์ฉํด์ ์์ฑํ ๊ฒ์ด๋ค.
from passlib.context import CryptContext
password_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
bcrypt๋ผ๋ ์๊ณ ๋ฆฌ์ฆ์ ํ์ฉํด์ ๋น๋ฐ๋ฒํธ ์ํธํ๋ฅผ ํด์ค ๊ฒ์ธ๋ฐ, ๊ตฌํ์ด ์ฌ์ด ๊ฒ์ ๋นํด์๋ ๊ด์ฐฎ์ ์ํธํ ์ฑ๋ฅ์ ์๋ํ๋ค๊ณ ํ๋ค. password_context๋ฅผ ์ค์ ํ์ผ๋ฉด, ์ฒ์ ๊ฐ์ ํด์ค ๋ password๋ฅผ ์ด์ชฝ์ผ๋ก ๋ณด๋ด์ ์ํธํํ ๋ค์์ database์ ๋ค์ด๊ฐ๋๋ก ํด์ฃผ์ด์ผํ๋ค. ์ ์ ์์ฑ ํจ์์ ํจ๊ป ๋ณด์.
# create user
def create_user(db: Session, user: schemas.UserCreate):
password = utils.get_hashed_password(user.password)
db_user = models.User(username=user.username, hashed_password=password, email=user.email)
# database session์ db_user ์ ๋ณด ์ถ๊ฐ
db.add(db_user)
# db์ ๋ฐ์
db.commit()
# ์ต์ db ์ ๋ณด
db.refresh(db_user)
return db_user
get_hashed_password ํจ์๋ ์๋์ ๊ฐ๋ค.
def get_hashed_password(password: str) -> str:
return password_context.hash(password)
์ง์. ์ด๊ฒ ๋์ด๋ค. ๊ทธ๋ ๋ค๋ฉด ๋์ค์ ๋ก๊ทธ์ธ์ ํ ๋, ์ด๋ป๊ฒ ๋ง๋ ๋น๋ฐ๋ฒํธ์ธ์ง๋ฅผ ํ์ธํ ๊น? ํ์ธํ ์ ์๋ ํจ์๋ ์ ๊ณตํ๋ค. ๋ก๊ทธ์ธ ํจ์๋ฅผ ๋ณด์.
@app.post("/login")
async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()], db: Session = Depends(get_db)):
user = user_crud.get_user_by_username(db=db, username=form_data.username)
if user is None:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Incorrect email or password")
hashed_pass = user.hashed_password
if not utils.verify_password(form_data.password, hashed_pass):
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Incorrect email or password")
return {
"access_token": utils.create_access_token(user.username),
"refresh_token": utils.create_refresh_token(user.username)
}
๋จผ์ ์ ๋ ฅ๋ฐ์ form data์์ ์ค๋ณต ๋ถ๊ฐ๋ฅํ username์ ๊ธฐ๋ฐ์ผ๋ก DB์์ ์ ์ ๋ฐ์ดํฐ๋ฅผ ์ฐพ๋๋ค. ๊ทธ๋ฆฌ๊ณ ๊ทธ ์ ์ ์ hashed password๋ฅผ ์ป์ด์, verify_password ํจ์๋ฅผ ํตํด ํ์ฌ ์ ๋ ฅ๋ ๋น๋ฐ๋ฒํธ์ ๊ฐ์ ๊ฐ์ธ์ง๋ฅผ ํ์ธํ๋๋ก ๋์ด์๋ค.
form data์ annotation์ด OAuth2PasswordRequestForm์ผ๋ก ๋์ด์๋๋ฐ, ๋น๋ฐ๋ฒํธ๊ฐ ํฌํจ๋... ๋ญ ๊ทธ๋ฐ ๋ณด์ ์ ๋ณด๋ฅผ form์ผ๋ก ์ฃผ๊ณ ๋ฐ์ ๋ fastapi.security์์ ์ ๊ณต๋๋ form ํ์์ธ ๊ฒ์ผ๋ก ๊ธฐ์ตํ๋ค. ์๋๋ผ๋ฉด ์๋ ค์ฃผ์ญ์์ค...
def verify_password(password:str, hashed_pass:str) -> bool:
return password_context.verify(password, hashed_pass)
verify ํจ์๋ passlib์์ ์ ๊ณตํด์ฃผ๋ฏ๋ก ์ฌ์ฉ๋ง ํ๋ฉด ๋๋ค.
B. Access Token ๋ถ์ฌํ๊ธฐ
Access token์ด ๋ฌด์์ด๋! ๋ด๊ฐ ๋๊ตฌ์ธ์ง๋ฅผ ์๋ฒ์ ์๋ ค์ค ์ ์๋ ์ผ์ข ์ ์ ๋ถ์ฆ์ด๋ค. ๋ก๊ทธ์ธํ ์ฌ์ฉ์๋ง ์ด์ฉ๊ฐ๋ฅํ ์๋น์ค๋ฅผ ๊ตฌ์ถํ๊ณ ์ถ๋ค๋ฉด ๋ก๊ทธ์ธ์ ํ๋์ง, ์ ํ๋์ง, ์ด ํ์ด์ง๋ฅผ ๋ณผ ๊ถํ์ด ์๋์ง, ์๋์ง๋ฅผ ์ฒดํฌํด์ค ์ ์๋ค.
Token ์์ฑ์๋ HS256 ์๊ณ ๋ฆฌ์ฆ์ ์จ์ค ๊ฒ์ด๋ค. ์ด ๋๋ jose์ jwt ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ ๊ฒ์ธ๋ฐ, ๋ฏธ๋ฆฌ ์ ์ธํด์ค ๊ฒ์ด 4๊ฐ์ง ์๋ค.
from jose import jwt
ACCESS_TOKEN_EXPIRE_MINUTES = 30
REFRESH_TOKEN_EXPIRE_MINUTES = 60 * 24 * 7
ALGORITHM = "HS256"
SECRET_KEY = "SECRET_KEY"
Access token ๊ณผ refresh token expire minutes๋ ๋ง ๊ทธ๋๋ก ๋ง๋ฃ ๊ธฐํ์ด๊ณ , ๋์ฒด๋ก access๋ 30๋ถ, refresh๋ ์ผ์ฃผ์ผ์ด๋ผ๊ณ ํด์ ์ผ์ฃผ์ผ๋ก ์ก์๋๋ค. ์๊ณ ๋ฆฌ์ฆ์ ์ํธํ ์๊ณ ๋ฆฌ์ฆ์ ๋งํ๋ ๊ฒ์ด๊ณ , Secret Key๋ ์ํธํํ ๋ ์ฌ์ฉํ ๊ณ ์ ํค์ธ๋ฐ, ์ธ๋ถ๋ก ๊ณต๊ฐํ์ง ์์์ผํ๋ค.(๋๋ ๋น์ฅ์ ์๊ด ์์ด์ github์๋ ์ฌ๋ ค๋จ๋ค)
์ด์ access token ์์ฑ, refresh token ์์ฑ, ๊ทธ๋ฆฌ๊ณ access token์ ํตํด์ ํ์ฌ ๋ก๊ทธ์ธํ ์ ์ ๊ฐ ๋๊ตฌ์ธ์ง ์์๋ด๋ ํจ์๊น์ง ์์ฑํด๋ณผ ์ฐจ๋ก๋ค. ๋จผ์ token ์์ฑ๋ถํฐ ๋ณด์.
def create_access_token(subject: Union[str, Any], expires_delta: int = None) -> str:
if expires_delta is not None:
expires_delta = datetime.utcnow() + expires_delta
else:
expires_delta = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode = {"exp": expires_delta, 'sub': str(subject)}
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, ALGORITHM)
return encoded_jwt
def create_refresh_token(subject: Union[str, Any], expires_delta: int = None) -> str:
if expires_delta is not None:
expires_delta = datetime.utcnow() + expires_delta
else:
expires_delta = datetime.utcnow() + timedelta(minutes=REFRESH_TOKEN_EXPIRE_MINUTES)
to_encode = {"exp": expires_delta, 'sub': str(subject)}
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, ALGORITHM)
return encoded_jwt
expires delta๋ ์ธ๋ถ์์ ๋ฐ์์ค์ง ์๋๋ก ํ์ผ๋ฏ๋ก else๋ฌธ ๋ด๋ถ์ ์ ์๋ก ์ค์ ๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ํธํ ์ธ์ฝ๋ฉํ ๋ฐ์ดํฐ๋ฅผ expires_delta์ ์ ๋ฌ๋ฐ์ subject - ์ฌ๊ธฐ์์๋ username์ json string์ผ๋ก ๋ง๋ ๋ค ํ ํฐํํด์ ๋ฆฌํดํ๋ค.
C. current user ํ์ธํ๊ธฐ
๋ค์์ ํ์ฌ ์ ์ ์ค์ธ ์ ์ ํ์ธ์ด๋ค. ์ด ๋ถ๋ถ์ access token์ ๋์ฝ๋ฉํ๊ธฐ๋ง ํ๋ฉด ๋๋ ๋ฐ๋ก ๋ณด์.
async def get_current_user(token: str = Depends(get_token), db: Session = Depends(get_db)) -> models.User:
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
token_data = user_schema.TokenPayload(**payload)
if datetime.fromtimestamp(token_data.exp) < datetime.now():
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Token Expired")
except(jwt.JWTError, ValidationError):
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Could not validate credentials")
user = user_crud.get_user_by_username(db=db, username=token_data.sub)
if user is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Could not find user")
return user
์์ธ์ฒ๋ฆฌ ๋ถ๋ถ์ ์ ํ๊ณ ๋ณธ๋ค๋ฉด ๊ฐ๋จํ๋ค. jwt์ decode๋ฅผ ํ์ฉํด์ algorithm๊ณผ secret key๋ฅผ ๋๊ฒจ์ฃผ๊ณ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์จ๋ค. ๊ทธ๋ฆฌ๊ณ TokenPayload๋ผ๋ ํด๋์ค์ ๋ฐ์ดํฐ๋ฅผ ๋งคํํด์ค๋ค. TokenPayload๋ ์๋์ ๊ฐ์ด ์ ์ธํด์ฃผ์๋ค.
class TokenPayload(BaseModel):
sub: str = None
exp: int = None
์ด ํ ํฐ ๋ฐ์ดํฐ๋ ํ ํฐ์ ์์ฑํ ๋ ์ํธํ ์ธ์ฝ๋ฉํ ๋ฐ์ดํฐ์ key๊ฐ์ ๋ง์ถฐ์ ํด๋์ค๋ฅผ ์์ฑํด์ฃผ์ด์ผํ๋ค. ๊ทธ๋ฆฌ๊ณ ์ด๊ฑธ ๊ธฐ๋ฐ์ผ๋ก username์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ฐพ์๋ด๊ณ , ์ ์ ๊ฐ ์๋์ง, ์๋์ง, ์๋ค๋ฉด ๊ทธ ์ ์ ์ ๋ฐ์ดํฐ๋ฅผ ๋ฆฌํดํด์ค๋ค.
get_token ํจ์๋ก token์ ๋ฐ์์ค๋๋ก ํด๋๋๋ฐ, ์ ์๋ ์๋์ ๊ฐ๋ค. authorization์ด๋ผ๋ ์ฟ ํค ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ค๋ฉด ๊ทธ๊ฒ์ 6๋ฒ์งธ ์ธ๋ฑ์ค๋ถํฐ๊ฐ token์ด์๋ค.(๊ฝค๋ ๋ ธ๊ฐ๋ค๋ฅผ ํ๋๋ฌ๋ค.)
def get_token(authorization: str = Header(default=None)):
print(authorization)
return authorization[6:]
์ด์ ๊ธฐ๋ฅ์ ๊ตฌํ๋๋ค!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! UI์ ์ ํ๋ณด๋๋ก ํ์.
D. ํ๋ก ํธ์๋ ๋ง๋ค๊ธฐ
์ํ๊น๊ฒ๋ UI ๋์์ธ์๋ ์ฌ๋ฅ์ด ์๋ค. ๊ทธ๋ฅ ์คํ๋ ๋ถํธ์คํธ๋ฉ ํ ํ๋ฆฟ์ ํ๋ ์ฃผ์์ฌ ๊ฒ์ด๋ค.
https://colorlib.com/wp/template/login-form-15/
์ด ๋ ์์ผ๋ก ๊ณจ๋๋ค. ๋ก๊ทธ์ธ๊ณผ ํ์๊ฐ์ ์ ์ฌ์ค์ ๊ฑฐ์ ๊ฐ์ ํ์์ ๊ฐ๊ณ ์๊ธฐ ๋๋ฌธ์, Form data๋ฅผ ์ด๋ป๊ฒ ์ api ์ชฝ์ผ๋ก ๋๊ฒจ์ฃผ๊ณ ํ์ธ์ ๋ฐ์์ค๋ฉด ๋๋ค. ๋จผ์ ํ์๊ฐ์ ์ด๋ค.
1. ํ์๊ฐ์
html ๋ถ๋ถ๋ถํฐ ์ ๊น ๋ณด์.
<form action="#" class="signup-form">
<div class="form-group mt-3">
<input
id="username"
type="text"
class="form-control"
required
/>
<label class="form-control-placeholder" for="username">
Username
</label>
</div>
<div class="form-group">
<input
id="email"
type="email"
class="form-control"
required
/>
<label class="form-control-placeholder" for="email">
email
</label>
</div>
<div class="form-group">
<input
id="password"
type="password"
class="form-control"
required
/>
<label class="form-control-placeholder" for="password">
Password
</label>
<span
toggle="#password-field"
class="fa fa-fw fa-eye field-icon toggle-password"
></span>
</div>
<div class="form-group">
<button
type="submit"
class="form-control btn btn-primary rounded submit px-3"
>
Sign Up
</button>
</div>
</form>
form ํ๊ทธ๋ก ๋๋ฌ์ธ์ฌ์๋ ๋ฐ์ดํฐ๋ค์ id๋ก ๊ตฌ๋ถ์ ํด์คฌ๋ค. ๋๋ถ๋ถ ์์๊ฒ ์ง๋ง input ํ๊ทธ ๋ด๋ถ ์์ฑ ์ค type์ ์ง์ ํ๋ฉด ํด๋นํ๋ ํ์ ์ ์ง์ ํ ์ ์๋๋ฐ, email์ email ํ์ ์ ๋ฃ์ผ๋ฉด ~~~~@~~~~.~~ ํ์์ ์ ์งํค๋ฉด ํผ๋ด์ค๋ค.
์ด์ jQuery ajax ํํธ๋ก ๋์ด๊ฐ์. ํ๋ ์ ๋นํ๋ฉด ๊ฐ๋จํ๊ธฐ ๊ทธ์ง์๋ค.
<script type="text/javascript">
$(document).ready(function ($) {
$(".signup-form").submit(function (event) {
event.preventDefault();
var body = new FormData();
body.append("username", $("#username").val());
body.append("password", $("#password").val());
body.append("email", $("#email").val());
console.log(body);
var data = new URLSearchParams(body);
$.ajax({
url: "http://localhost:8000/api/users/signup",
method: "POST",
data: data,
contentType: false,
processData: false,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
success: function (response) {
alert("Account Created");
location.href = "http://localhost:8000/";
},
error: function (response) {
alert(response["detail"]);
},
});
event.preventDefault();
});
});
</script>
๊ฝค ๊ธฐ๋๊น ๋ถ๋ถ๋ถ๋ถ ๋ฏ์ด๋ณด๊ฒ ๋ค.
$(".signup-form").submit(function (event) {
์ด form ์ ์ฒด๊ฐ ์ ์ถ์ด ๋๋ฉด ์ ํจ์๋ฅผ ๋ถ๋ฅด๊ฒ ๋ค๋ ๋ป์ด๋ค. ์ ์ถ ๋ฒํผ์ ์๋์ ๋ฐ๋ก ๋ง๋ค์ด์คฌ์๋ค.
var body = new FormData();
body.append("username", $("#username").val());
body.append("password", $("#password").val());
body.append("email", $("#email").val());
var data = new URLSearchParams(body);
key-value๋ก ๋งค์นญ์ ์์ผ์คฌ๋ค. ์ด๋ ๊ฒ ํด์ผ ์๋ฒ ์ชฝ์์ ์ํ ์ด๊ฒ ๋ฌด์จ ๋ฐ์ดํฐ๊ตฌ๋~ ํ๊ณ ์์๋ณผ ์ ์๋ค. $("#id").val() ํน์ $(".class").val() ๋ผ๊ณ ์จ์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ์ ์๋ค. ์ฐพ์๋ณด๋ URLSearchParams๋ผ๋ ๊ฐ์ฒด๋ก ๋ง๋ค์ด์ค์ผ ํ๋ค๊ณ ํด์ ๋ฌถ์ด์ฃผ์๋ค.
$.ajax({
url: "http://localhost:8000/api/users/signup",
method: "POST",
data: data,
contentType: false,
processData: false,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
success: function (response) {
alert("Account Created");
location.href = "http://localhost:8000/";
},
error: function (response) {
alert(response["detail"]);
},
});
์ด์ ajax๋ก ์ด ๋ฐ์ดํฐ๋ฅผ ๊ฐ๋ค๊ฐ api ๋ผ์ฐํฐ ์ด๋๊ฐ๋ก ๋ ๋ ค์ค๋ค. ๋ฐ๋ก signup ์ผ๋ก! post ๋ฐฉ์๊น์ง ์ง์ ํด์ ๋ณด๋ด์ฃผ๋ฉด
@router.post("/signup") #, response_model=schemas.User)
def create_user(username: Annotated[str, Form()], email: Annotated[str, Form()], password: Annotated[str, Form()], db: Session = Depends(get_db)):
db_user = crud.get_user_by_username(db=db, username=username)
if db_user:
raise HTTPException(status_code=400, detail="Email already registered.")
user = schemas.UserCreate(username=username, email=email, password=password)
return crud.create_user(db=db, user=user)
์ด ์น๊ตฌ๊ฐ ๋ฐ์๋จน๊ณ create user๋ฅผ ํด์ฃผ๋์ง, email ์ด๋ฏธ ์์ด์ ์๋๋ค๊ณ ํด์ฃผ๋์ง ํ๊ฒ ๋๋ค.
์ฑ๊ณตํ์ ๋ ํ ํ๋ฉด์ผ๋ก ๊ฐ๊ณ , ์คํจํ์ผ๋ฉด ์๋๋ค๊ณ alert๋ฅผ ๋ณด๋ด์ฃผ๊ณ ์ฌ๊ธฐ ๋จธ๋ฌด๋ฅธ๋ค. ์ ์ฉ ํ๋ฉด์ ํจ ๋ณด์.
DB๋ฅผ ๋ณด๋ฉด ๋น๋ฐ๋ฒํธ๋ ์ ์ํธํ๋ผ์ ์ ์ฅ๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
์ข๋ค. ์ด์ ๋ก๊ทธ์ธ ํ์ด์ง๋ง ํ๋ฑ ๋ง๋ค์ด๋ณธ๋ค.
2. ๋ก๊ทธ์ธ
์์งํ ๋งํ๋ฉด sign up๊ณผ ๋ค๋ฅธ ๊ฑฐ๋ผ๊ณค ๋ฐ์ดํฐ ๊ฐ์์ return๊ฐ ๋ฟ์ด๋ค. ๋ฐ๋ก jQuery๋ถํฐ ๋ณด๋๋ก ํ๊ฒ ๋ค.
<script
type="text/javascript"
src="https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js"
></script>
<script type="text/javascript">
$(document).ready(function ($) {
$.cookie();
$(".signin-form").submit(function (event) {
event.preventDefault();
var body = new FormData();
body.append("username", $("#username").val());
body.append("password", $("#password").val());
var data = new URLSearchParams(body);
$.ajax({
url: "http://localhost:8000/login",
method: "POST",
data: data,
contentType: false,
processData: false,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
success: function (response) {
$.cookie("access_token", response.access_token);
location.href = "http://localhost:8000/dashboard";
},
error: function (response) {
alert(response.responseJSON.detail);
console.log(response);
},
});
event.preventDefault();
});
$("#to-signup").on("click", function () {
location.href = "http://localhost:8000/signup";
});
});
</script>
ํ๋ ์ค์ํ ๊ฒ์, cookie ํ์ผ์ด ์์ด์ผํ๋ค.
<script
type="text/javascript"
src="https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js"
></script>
์ด๋ฐ ์์ผ๋ก ์ฝ์ ์ ํด์ฃผ๊ณ !!!!!!!! document ๋ก๋ฉ์ด ์๋ฃ๋ ํ์ cookie ์ด๊ธฐํ๋ ํด์ฃผ๊ณ !!! ๊ทธ๋ฆฌ๊ณ ๋์ ๋ก๊ทธ์ธ ํจ์๋ฅผ ๋ฃ์ด์ค๋ค. ๋ฐ์ดํฐ ๋ง๋๋ ๊ฒ์ ์์ ๊ฐ์ผ๋ ์๋ตํ๊ณ ajax๋ง ๋ณด์.
$.ajax({
url: "http://localhost:8000/login",
method: "POST",
data: data,
contentType: false,
processData: false,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
success: function (response) {
$.cookie("access_token", response.access_token);
location.href = "http://localhost:8000/dashboard";
},
error: function (response) {
alert(response.responseJSON.detail);
console.log(response);
},
});
์ฐ์ ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ด์ฃผ๋ url์ด ๋ฌ๋ผ์ก๋ค. login router ์ฃผ์๋ก ๋ณด๋ด์ฃผ๋ฉด
@app.post("/login")
async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()], db: Session = Depends(get_db)):
user = user_crud.get_user_by_username(db=db, username=form_data.username)
if user is None:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Incorrect email or password")
hashed_pass = user.hashed_password
if not utils.verify_password(form_data.password, hashed_pass):
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Incorrect email or password")
return {
"access_token": utils.create_access_token(user.username),
"refresh_token": utils.create_refresh_token(user.username)
}
์ด ์น๊ตฌ๊ฐ ๋ฐ๋๋ค. ์น ์ฒ๋ฆฌ๋ฅผ ํด์ ์ ๋ ฅ ์ ๋ณด๊ฐ ์ฌ๋ฐ๋ฅด๋ฉด ์์ฑ๋ access token๊ณผ refresh token์ ๋ฐ์์ค๊ณ , cookie์ access token์ ์ ๋ ฅํด๋๋ค. ๊ทธ๋ผ ์ด๋ป๊ฒ ๋๋๋.
๋ก๊ทธ์ธ์ ์ฑ๊ณต์ํค๊ณ F12๋ฅผ ๋๋ฌ์ ๊ฐ๋ฐ์ ๋๊ตฌ - Application - Cookies๋ฅผ ๋ณด๋ฉด
Access token์ด ์๋ค! ์ด ๋์ผ๋ก ์ด์ ๋ค์ ํฌ์คํ ์ ๋ง๋ค monitoring system์ ์ด๋ ๊ถํ์ ํ์ธํด์ค ๊ฒ์ด๋ค.
ํ ํฐ์ด ์๋ค๋ฉด ์ด๋ฐ ๋ด์ฉ์ด ๋จ๋๋ก!
ํ,,, ์ฐธ ๊ธด ํฌ์คํ ์ด์๋ค. ๋ค์ ํฌ์คํ ์์๋ Google chart๋ฅผ ํ์ฉํด์ ์์๊ฒ ๊ทธ๋ํ ๊ทธ๋ฆฌ๊ธฐ... ๊ทธ๋ฆฌ๊ณ ์ด๋๊ถํ ํ์ธํ๊ธฐ๋ฅผ ๊ฐ์ง๊ณ ๋์์ค๊ฒ ๋ค.
ํ๋ ธ๊ฑฐ๋ ๋ถ๊ฐ์ ์ผ๋ก ์ ๋ณด๋ฅผ ์ฃผ๊ณ ์ถ์ผ์ ๋ถ์ด ๊ณ์๋ค๋ฉด ์ธ์ ๋ ๋๊ธ ํผ๋๋ฐฑ์ ํ์์ ๋๋ค!