Skip to content

Commit

Permalink
Merge pull request #9 from hibiya-itchief/develop
Browse files Browse the repository at this point in the history
お知らせ、タグ、公演の一括追加の機能を本番環境で使用可能にする
  • Loading branch information
aozoraUS authored Jun 8, 2024
2 parents 67597fd + a14fd4d commit f702e7f
Show file tree
Hide file tree
Showing 19 changed files with 470 additions and 4 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ db/data
.pytest_cache
data/*
app/ga-credential.json
.prettierrc
.vscode/
161 changes: 161 additions & 0 deletions app/crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

#from hashids import Hashids
import ulid
import pandas as pd
import numpy as np
from fastapi import HTTPException, Query
from sqlalchemy import and_, or_
from sqlalchemy.exc import IntegrityError
Expand Down Expand Up @@ -381,3 +383,162 @@ def set_hebe_upnext(db:Session,hebe:schemas.HebeResponse):
db.commit()
db.refresh(db_hebe)
return db_hebe

#受け取ったpandas.DataFrameを変換する
#受け取った値についての検証はcolumnsだけ行う
def convert_df(df:pd.DataFrame) -> pd.DataFrame:
#カラムの数が正しいかの検証
if len(df.columns.values) != 12:
raise HTTPException(422, f'列の数が合いません。正しい列の数は12です。サンプルシートと比較して確認してください。')

converted_df = pd.DataFrame(columns=['group_id', 'eventname', 'lottery', 'target', 'ticket_stock', 'starts_at', 'ends_at', 'sell_starts', 'sell_ends'])

#1行ずつ取り出して変換する
for i in range(len(df)):
try:
#時間の情報を変換する
year = str(df.iat[i, 5])
month = str(df.iat[i, 6])
day = str(df.iat[i, 7])
hours = {
'starts_at':str(df.iat[i, 8]).split(':')[0],
'ends_at':str(df.iat[i, 9]).split(':')[0],
'sell_starts':str(df.iat[i, 10]).split(':')[0],
'sell_ends':str(df.iat[i, 11]).split(':')[0],
}

"""
month, day, hourの表現を変換する
上の三つを時間にくっつける上で 9 → 09 みたいにする必要がある
"""
if len(month) == 1:
month = '0' + month
if len(day) == 1:
day = '0' + day

for key in ['starts_at', 'ends_at', 'sell_starts', 'sell_ends']:
if len(hours[key]) == 1:
hours[key] = '0' + hours[key]

times = {
'starts_at':hours['starts_at'] + ':' + str(df.iat[i , 8]).split(':')[1] + ':' + str(df.iat[i , 8]).split(':')[2],
'ends_at':hours['ends_at'] + ':' + str(df.iat[i , 9]).split(':')[1] + ':' + str(df.iat[i , 9]).split(':')[2],
'sell_starts':hours['sell_starts'] + ':' + str(df.iat[i , 10]).split(':')[1] + ':' + str(df.iat[i , 10]).split(':')[2],
'sell_ends':hours['sell_ends'] + ':' + str(df.iat[i , 11]).split(':')[1] + ':' + str(df.iat[i , 11]).split(':')[2],
}

converted_df = pd.concat([converted_df, pd.DataFrame(data={
'group_id':[df.iat[i , 0]],
'eventname':[df.iat[i , 1]],
'lottery':df.iat[i , 2],
'target':[df.iat[i , 3]],
'ticket_stock':[df.iat[i , 4]],
'starts_at':[ year + '-' + month + '-' + day + 'T' + times['starts_at'] + '+09:00'],
'ends_at':[ year + '-' + month + '-' + day + 'T' + times['ends_at'] + '+09:00'],
'sell_starts':[ year + '-' + month + '-' + day + 'T' + times['sell_starts'] + '+09:00'],
'sell_ends':[ year + '-' + month + '-' + day + 'T' + times['sell_ends'] + '+09:00'],
})], ignore_index=True)
except:
raise HTTPException(422, f"pandas.DataFrameの変換に失敗しました。表記方法が正しいことを確認してください。<エラー箇所> 行番号 : { i + 1 }")

return converted_df

#受け取ったpandas.DataFrameの形式が正しいかを検証する
def check_df(db:Session, df: pd.DataFrame) -> None:
#カラム名が正しいかの検証
columns = df.columns.values
correct_columns = ['group_id', 'eventname', 'lottery', 'target', 'ticket_stock', 'starts_at', 'ends_at', 'sell_starts', 'sell_ends']
for i in range(len(columns)):
if not (columns[i] == correct_columns[i]):
raise HTTPException(422, f'カラム名が正しいことを確認してください。<エラー箇所> 表記 : {columns[i]}, 正表記 : {correct_columns[i]}')

#group_idが正しいかの検証
for i in range(len(df)):
group = db.query(models.Group).filter(models.Group.id == df.iat[i, 0]).first()

if not group:
raise HTTPException(400, f"存在しないgroup_idが含まれています。<エラー箇所> 行番号 : {i + 1}, group_id : {df.iat[i, 0]}")

#時刻の表記の仕方が正しいかの判定
for m in range(len(df)):
for n in [5,6,7,8]:
try:
time = datetime.fromisoformat(df.iat[m, n])
except:
raise HTTPException(422, f"時刻の表記方法が正しいことを確認してください。<エラー箇所> 行番号 : {m + 1}, 列番号 : {n + 1}")

#時刻の設定に問題がないかを確認
for i in range(len(df)):
starts_at = datetime.fromisoformat(df.iat[i , 5])
ends_at = datetime.fromisoformat(df.iat[i , 6])
sell_starts = datetime.fromisoformat(df.iat[i , 7])
sell_ends = datetime.fromisoformat(df.iat[i , 8])

if starts_at > ends_at:
raise HTTPException(400,f"公演の開始時刻は終了時刻よりも前である必要があります。group_id : {df.iat[i , 0]}, eventname : {df.iat[i , 1]}")
if sell_starts > sell_ends:
raise HTTPException(400,f"配布開始時刻は配布終了時刻よりも前である必要があります。group_id : {df.iat[i , 0]}, eventname : {df.iat[i , 1]}")

#表記方法に問題なし
return None

#pandas.DataFrameの情報を元にDBにeventを追加
def create_events_from_df(db:Session, df: pd.DataFrame) -> None:
for i in range(len(df)):
group_id = df.iat[i , 0]

#pandas.DataFrameの情報を読み取ってスキーマに変換
event = schemas.EventDBInput(
eventname=df.iat[i , 1],
lottery=df.iat[i , 2],
target=df.iat[i , 3],
ticket_stock=df.iat[i , 4],
starts_at=df.iat[i , 5],
ends_at=df.iat[i , 6],
sell_starts=df.iat[i , 7],
sell_ends=df.iat[i , 8],
)

db_event = models.Event(id=ulid.new().str,group_id=group_id,**event.dict())
db.add(db_event)
db.commit()
db.refresh(db_event)

return None

def get_all_news(db:Session):
return db.query(models.News).all()

def get_news(db:Session, news_id:str):
news = db.query(models.News).filter(models.News.id==news_id).first()

if not news:
raise HTTPException(404, "指定されたidを持つnewsは存在しません。")

return news

def create_news(db:Session, news:schemas.NewsUpdate):
db_news = models.News(**news.dict())
db_news.timestamp = datetime.now(timezone(timedelta(hours=9))).isoformat() #日本の時刻に修正。時間はstring型で保存
db_news.id = ulid.new()
db.add(db_news)
db.commit()
db.refresh(db_news)
return db_news

def delete_news(db:Session, news_id:str):
news = db.query(models.News).filter(models.News.id==news_id).first()
if not news:
raise HTTPException(404, "指定されたidを持つお知らせは存在しません。")
db.delete(news)
db.commit()
return news

def update_news(db:Session, news_id:str, news:schemas.NewsUpdate):
db_news:models.News = db.query(models.News).filter(models.News.id == news_id).first()
if not db_news:
raise HTTPException(404, "指定されたidを持つnewsは存在しません。")
db_news.update_dict(news.dict())
db.commit()
db.refresh(db_news)
return db_news
84 changes: 82 additions & 2 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import json
import time
import re
from datetime import datetime, timedelta, timezone
from typing import Dict, List, Optional, Union
from xml.dom.minidom import Entity

import requests
from io import StringIO
import pandas as pd
from fastapi import (Body, Depends, FastAPI, File, HTTPException, Query,
UploadFile, status)
from fastapi.middleware.cors import CORSMiddleware
Expand Down Expand Up @@ -169,8 +172,19 @@ def delete_ownership(user_oid:str,group_id:str,permission=Depends(auth.admin),db
def create_group(groups:List[schemas.GroupCreate],permission:schemas.JWTUser=Depends(auth.admin),db:Session=Depends(db.get_db)):
result=[]
for group in groups:
# idが命名規則に従っているかを確認する
type:schemas.GroupType = group.type
# クラス劇である時
if(type == schemas.GroupType.play):
if not re.fullmatch(pattern=r'[1-3][1-8]r', string=group.id):
raise HTTPException(400, '団体のタイプがplayである場合のidの命名規則に違反しています。命名規則:[1-3][1-8]r')
else:
if re.fullmatch(pattern=r'[1-3][1-8]r', string=group.id):
raise HTTPException(400, '団体のidがクラス劇のidと重複する可能性があります。クラス劇のidの命名規則から外れた値に変更してください。')

result.append(crud.create_group(db,group))
return result

@app.get(
"/groups",
response_model=List[schemas.Group],
Expand Down Expand Up @@ -676,6 +690,26 @@ def update_frontend(permission:schemas.JWTUser=Depends(auth.admin)):
else:
HTTPException(res.status_code,"Cloudflareへのデプロイに失敗しました")

@app.post(
"/support/events",
summary="公演の一括追加",
tags=["admin"],
description="csvファイルを元に公演を一斉に追加します。csvファイルについてはサンプルのエクセルと同じ書式で書いたものにしてください。正しく処理されません。"
)
async def create_all_events_from_csv(file: UploadFile = File(...), permission:schemas.JWTUser=Depends(auth.chief),db:Session = Depends(db.get_db)):
#pandasのDataFrameに読み込んだファイルを変換
content = file.file.read()
string_data = str(content, 'utf-8')
data = StringIO(string_data)
df = pd.read_csv(data)
data.close()
file.file.close()

converted_df = crud.convert_df(df)
crud.check_df(db,converted_df)
crud.create_events_from_df(db, converted_df)

return {'message' :[converted_df.iloc[i,:].to_json() for i in range(len(converted_df))]}

@app.get(
"/hebe/nowplaying",
Expand Down Expand Up @@ -709,7 +743,7 @@ def get_hebe_upnext(db:Session = Depends(db.get_db)):
tags=["chief"],
description="チーフのみ"
)
def get_hebe_nowplaying(hebe:schemas.HebeResponse,permission:schemas.JWTUser=Depends(auth.chief),db:Session = Depends(db.get_db)):
def set_hebe_nowplaying(hebe:schemas.HebeResponse,permission:schemas.JWTUser=Depends(auth.chief),db:Session = Depends(db.get_db)):
return crud.set_hebe_nowplaying(db,hebe)

@app.post(
Expand All @@ -719,5 +753,51 @@ def get_hebe_nowplaying(hebe:schemas.HebeResponse,permission:schemas.JWTUser=Dep
tags=["chief"],
description="チーフのみ"
)
def get_hebe_nowplaying(hebe:schemas.HebeResponse,permission:schemas.JWTUser=Depends(auth.chief),db:Session = Depends(db.get_db)):
def set_hebe_upnext(hebe:schemas.HebeResponse,permission:schemas.JWTUser=Depends(auth.chief),db:Session = Depends(db.get_db)):
return crud.set_hebe_upnext(db,hebe)

@app.get(
"/news",
response_model=List[schemas.NewsBase],
summary="全てのお知らせ情報を取得する",
tags=["news"]
)
def get_all_news(db:Session = Depends(db.get_db)):
return crud.get_all_news(db)

@app.get(
"/news/{news_id}",
summary="指定されたidのnewsを取得",
response_model=schemas.NewsBase,
tags=["news"]
)
def get_news(news_id:str, db:Session = Depends(db.get_db)):
return crud.get_news(db, news_id)

@app.post(
"/news/create",
response_model=schemas.NewsUpdate,
summary="お知らせ情報の作成",
tags=["news","chief","admin"]
)
def create_news(news:schemas.NewsUpdate,permission_chief:schemas.JWTUser=Depends(auth.chief),permission_admin:schemas.JWTUser=Depends(auth.admin), db:Session = Depends(db.get_db)):
return crud.create_news(db, news)

@app.delete(
"/news/{news_id}",
response_model=schemas.NewsBase,
summary="お知らせ情報の削除",
tags=["news","chief","admin"]
)
def delete_news(news_id:str, permission_chief:schemas.JWTUser=Depends(auth.chief),permission_admin:schemas.JWTUser=Depends(auth.admin), db:Session = Depends(db.get_db)):
return crud.delete_news(db, news_id)

@app.put(
"/news/{news_id}",
summary="お知らせ情報の更新",
response_model=schemas.NewsUpdate,
description="タイムスタンプとIDは変更されません。注意してください。",
tags=["news","chief","admin"]
)
def change_news(news_id:str, news:schemas.NewsUpdate, permission_chief:schemas.JWTUser=Depends(auth.chief),permission_admin:schemas.JWTUser=Depends(auth.admin), db:Session = Depends(db.get_db)):
return crud.update_news(db, news_id, news)
15 changes: 15 additions & 0 deletions app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ class Group(Base):
floor = Column(VARCHAR(255)) #何階か
place = Column(VARCHAR(255)) #どこか

type = Column(VARCHAR(255)) # クラス劇・Hebe・部活かなどの情報。この情報をもとにフロントが各団体を判別していく

def update_dict(self,dict):
print(dict)
for name, value in dict.items():
Expand Down Expand Up @@ -115,3 +117,16 @@ class HebeUpnext(Base):

group_id = Column(VARCHAR(255),ForeignKey("groups.id"),primary_key=True,index=True)

class News(Base):
__tablename__ = "news"

id = Column(VARCHAR(255), primary_key=True, unique=True)
title = Column(VARCHAR(255), nullable=False)
timestamp = Column(VARCHAR(255), nullable=False)
author = Column(VARCHAR(255), nullable=False)
detail = Column(VARCHAR(500), nullable=True)

def update_dict(self,dict):
for name, value in dict.items():
if name in self.__dict__ :
setattr(self, name, value)
24 changes: 23 additions & 1 deletion app/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ class UserRole(str,Enum):
visited_school="visited_school"
school_parents="school_parents"

class GroupType(str, Enum):
play="play" # 劇:クラスを想定
hebe="hebe" # Hebe
club="club" # 部活動
test="test" # テスト用団体。全ての機能を利用可能。
other="other" # その他


class EventBase(BaseModel):
eventname:str
Expand Down Expand Up @@ -76,11 +83,13 @@ class GroupUpdate(BaseModel):
private_page_content_url:Union[str,None] = Query(default=None,max_length=200)
floor:Union[int,None] = Query(default=None) #何階か
place:Union[str,None] = Query(default=None, max_length=200) #場所
type:GroupType # クラス劇・Hebe・部活動などの情報

class GroupBase(GroupUpdate):#userdefined idをURLにする。groupnameは表示名
id:str=Query(regex="^[a-zA-Z0-9_\-.]{3,16}$",min_length=3,max_length=16)
groupname:str = Query(max_length=200)
enable_vote:bool = True

class GroupCreate(GroupBase):
class Config:
orm_mode=True
Expand Down Expand Up @@ -171,6 +180,19 @@ class GroupLink(GroupLinkBase):
class Config:
orm_mode=True

class NewsUpdate(BaseModel):
title:str = Query(default=None,max_length=200)
author:str = Query(default=None, max_length=50)
detail:Union[str, None] = Query(default=None)

class Config:
orm_mode=True

class NewsBase(NewsUpdate):
timestamp:datetime
id:str


Event.update_forward_refs()
Group.update_forward_refs()
Tag.update_forward_refs()
Expand Down
Loading

0 comments on commit f702e7f

Please sign in to comment.