Skip to content

Commit

Permalink
Merge pull request #11 from JAY-Chan9yu/feature/special_emoji
Browse files Browse the repository at this point in the history
Special Emoji ์นด์šดํŠธ ๋กœ์ง ์ถ”๊ฐ€, Test Code ์ถ”๊ฐ€
  • Loading branch information
JAY-Chan9yu committed Feb 19, 2023
2 parents c52b44c + df439ff commit 9f176cc
Show file tree
Hide file tree
Showing 52 changed files with 2,939 additions and 197 deletions.
12 changes: 7 additions & 5 deletions .env_sample
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,22 @@ DATABASE="heymoji"
DB_USERNAME="root"
DB_PASSWORD="root"

DAY_MAX_REACTION=1000
REACTION_LIST=["pray", "heart", "eye_shaking", "+1", "๊ธฐ๋„", "๊ธฐ์จ", "kkkk"]
SLACK_TOKEN=""
SLACK_CHANNEL=""
BOT_NAME=""
ERROR_CHANNEL=""

SPECIAL_EMOJI="trophy" # ๐Ÿ†
LIMIT_GIVE_COUNT_OF_SPECIAL_EMOJI=5
ALLOWED_REACTION_LIST=["pray", "heart", "eye_shaking", "+1", "๊ธฐ๋„", "๊ธฐ์จ", "kkkk", "trophy", "ํŠธ๋กœํ”ผ"]
ALLOWED_EMOJI_TYPES='[
{"emoji":"โค๏ธ", "emoji_names": ["heart"]},
{"emoji": "โค๏ธ", "emoji_names": ["heart"]},
{"emoji": "๐Ÿคฃ", "emoji_names": ["kkkk", "๊ธฐ์จ"]},
{"emoji": "๐Ÿ™๏ธ", "emoji_names": ["pray", "๊ธฐ๋„"]},
{"emoji": "๐Ÿ‘", "emoji_names": ["+1"]},
{"emoji": "๐Ÿ‘€๏ธ", "emoji_names": ["eye_shaking"]}
{"emoji": "๐Ÿ‘€๏ธ", "emoji_names": ["eye_shaking"]},
{"emoji": "๐Ÿ†๏ธ", "emoji_names": ["trophy", "ํŠธ๋กœํ”ผ"]}
]'

RANK_URL=""
#DEFAULT_AVATAR_URL=""
DEFAULT_AVATAR_URL=""
60 changes: 60 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
aiomysql = "==0.1.1"
alembic = "==1.8.1"
anyio = "==3.6.2"
async-generator = "==1.10"
asyncmy = "==0.2.3"
attrs = "==22.2.0"
certifi = "==2022.12.7"
cffi = "==1.14.6"
charset-normalizer = "==2.0.4"
click = "==7.1.2"
configparser = "==5.2.0"
cryptography = "==3.4.7"
exceptiongroup = "==1.1.0"
fastapi = "==0.88.0"
greenlet = "==1.1.1"
h11 = "==0.14.0"
httpcore = "==0.16.3"
httpx = "==0.23.3"
idna = "==3.2"
importlib-metadata = "==4.6.3"
iniconfig = "==2.0.0"
mako = "==1.2.3"
markupsafe = "==2.1.1"
mysql = "==0.0.3"
mysqlclient = "==2.0.3"
outcome = "==1.2.0"
packaging = "==23.0"
pluggy = "==1.0.0"
pycparser = "==2.20"
pydantic = "==1.10.4"
pymysql = "==1.0.2"
python-dotenv = "==0.17.0"
requests = "==2.26.0"
rfc3986 = "==1.5.0"
sniffio = "==1.3.0"
sortedcontainers = "==2.4.0"
sqlalchemy = "==1.4.7"
starlette = "==0.22.0"
tomli = "==2.0.1"
typing-extensions = "==4.4.0"
urllib3 = "==1.26.6"
uvicorn = "==0.13.4"
zipp = "==3.5.0"

[dev-packages]
asgi-lifespan = "==2.0.0"
pytest = "==7.2.1"
pytest-asyncio = "==0.20.3"
pytest-env = "==0.8.1"
faker = "*"
pytest-cov = "*"

[requires]
python_version = "3.9"
767 changes: 767 additions & 0 deletions Pipfile.lock

Large diffs are not rendered by default.

50 changes: 30 additions & 20 deletions app/Infrastructure/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,39 +10,40 @@


Base = declarative_base()
_async_db_connection: AsyncEngine = Optional[AsyncEngine]
_db_connection: Engine = Optional[Engine]
async_db_connection: Optional[AsyncEngine] = None
db_connection: Optional[Engine] = None

db_url = f"{settings.config.DB_USERNAME}:{settings.config.DB_PASSWORD}" \
f"@{settings.config.DB_HOST}:{settings.config.DB_PORT}/{settings.config.DATABASE}"


def on_startup():
# todo: ๋™๊ธฐ, ๋น„๋™๊ธฐ ํ•˜๋‚˜๋งŒ ์‚ฌ์šฉํ•˜๋„๋ก ์กฐ๊ฑด ๋„ฃ๊ธฐ or ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์ฃผ์ž…์œผ๋กœ?
global _async_db_connection
global _db_connection
global async_db_connection
global db_connection

_async_db_connection = create_async_engine(
f'mysql+aiomysql://{settings.config.DB_USERNAME}:{settings.config.DB_PASSWORD}'
f'@{settings.config.DB_HOST}:{settings.config.DB_PORT}/{settings.config.DATABASE}',
async_db_connection = create_async_engine(
f'mysql+aiomysql://{db_url}',
echo=True if settings.config.ENV != settings.HeymojiEnv.PROD.value else False,
future=True
)

_db_connection = create_engine(
f'mysql+pymysql://{settings.config.DB_USERNAME}:{settings.config.DB_PASSWORD}'
f'@{settings.config.DB_HOST}:{settings.config.DB_PORT}/{settings.config.DATABASE}',
db_connection = create_engine(
f'mysql+pymysql://{db_url}',
echo=True if settings.config.ENV != settings.HeymojiEnv.PROD.value else False,
# connect_args={"check_same_thread": False}
)


def on_shutdown():
global _async_db_connection
global _db_connection
global async_db_connection
global db_connection

if _async_db_connection:
_async_db_connection.dispose()
if async_db_connection:
async_db_connection.dispose()

if _db_connection:
_db_connection.dispose()
if db_connection:
db_connection.dispose()


class MysqlConnectionManager:
Expand All @@ -55,14 +56,14 @@ def get_connection(cls, is_async: bool):
cls._client = scoped_session(sessionmaker(
autocommit=False,
autoflush=False,
bind=_async_db_connection,
class_=AsyncSession(bind=_async_db_connection, expire_on_commit=True)
bind=async_db_connection,
class_=AsyncSession(bind=async_db_connection, expire_on_commit=True)
))
else:
cls._client = scoped_session(sessionmaker(
autocommit=False,
autoflush=False,
bind=_db_connection,
bind=db_connection,
class_=Session
))

Expand All @@ -71,7 +72,16 @@ def get_connection(cls, is_async: bool):

@asynccontextmanager
async def async_session_manager() -> AsyncSession:
session = AsyncSession(bind=_async_db_connection, expire_on_commit=True)
global async_db_connection

if not async_db_connection:
async_db_connection = create_async_engine(
f'mysql+aiomysql://{db_url}',
echo=True if settings.config.ENV != settings.HeymojiEnv.PROD.value else False,
future=True
)

session = AsyncSession(bind=async_db_connection, expire_on_commit=True)

try:
yield session
Expand Down
3 changes: 2 additions & 1 deletion app/api/dependency/requests.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from starlette.requests import Request

from app.applications.schemas import SlackChallengeHook, SlackMentionHook, SlackEventHook, SlackBotDirectMessageHook
from app.applications.services.slack_services import SLACK_EVENT_HOOKS
from app.domains.reactions.entities import SlackEventType


async def get_slack_event(request: Request):
async def get_slack_event(request: Request) -> SLACK_EVENT_HOOKS:
request = await request.json()
if request.get('challenge'):
return SlackChallengeHook(**request)
Expand Down
2 changes: 1 addition & 1 deletion app/api/routers/slack.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@


@slack_router.post(path="/", name='์Šฌ๋ž™ ์›นํ›… api')
async def slack(slack_event=Depends(get_slack_event)):
async def slack_handler(slack_event=Depends(get_slack_event)):
response = await SlackService.slack_web_hook_handler(slack_event)
return response
34 changes: 23 additions & 11 deletions app/api/routers/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,43 @@
user_router = APIRouter()


@user_router.post("/", name="์œ ์ € ์ƒ์„ฑ api", description="""์œ ์ €๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.""")
@user_router.post(
"/",
name="์œ ์ € ์ƒ์„ฑ api",
description="์œ ์ €๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค."
)
async def create_user(user_create_schema: UserCreateSchema):
user = await UserAppService.get_user(slack_id=user_create_schema.slack_id)
if user:
raise HTTPException(status_code=400, detail="already registered")
return await UserAppService.create_user(user_create_schema.__dict__)


@user_router.get("/", name="์ „์ฒด ์œ ์ € ๋ฆฌ์ŠคํŠธ ๋ฐ˜ํ™˜ api", description="""
์œ ์ €๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ›์€ reaction ์ด ๋†’์€ ์ˆœ์œผ๋กœ ์ •๋ ฌํ•ฉ๋‹ˆ๋‹ค.
""", response_model=List[UserDetailInfo])
async def get_user(year: Optional[int] = None, month: Optional[int] = None, department: Optional[str] = None):
@user_router.get(
"/",
name="์ „์ฒด ์œ ์ € ๋ฆฌ์ŠคํŠธ ๋ฐ˜ํ™˜ api",
description="์œ ์ €๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ›์€ reaction ์ด ๋†’์€ ์ˆœ์œผ๋กœ ์ •๋ ฌํ•ฉ๋‹ˆ๋‹ค.",
response_model=List[UserDetailInfo]
)
async def get_users(year: Optional[int] = None, month: Optional[int] = None, department: Optional[str] = None):
users = await UserAppService.get_detail_user(year=year, month=month, department=department)
return users


@user_router.get("/{user_id}/reactions/", name="์œ ์ € ๋ฆฌ์•ก์…˜ ๋ฐ˜ํ™˜ api", description="""
ํŠน์ • ์œ ์ €๊ฐ€ ๋ฐ›์€ reaction ๊ณผ ์ „๋‹ฌํ•œ ์œ ์ €์ •๋ณด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
""", response_model=List[UserReceivedEmojiInfo])
@user_router.get(
"/{user_id}/reactions/",
name="์œ ์ € ๋ฆฌ์•ก์…˜ ๋ฐ˜ํ™˜ api",
description="ํŠน์ • ์œ ์ €๊ฐ€ ๋ฐ›์€ reaction ๊ณผ ์ „๋‹ฌํ•œ ์œ ์ €์ •๋ณด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.",
response_model=List[UserReceivedEmojiInfo]
)
async def get_reactions(user_id: int, year: Optional[int] = None, month: Optional[int] = None):
return await ReactionAppService.get_received_emoji_infos(user_id, year, month)


@user_router.get("/{slack_id}/my_reaction/", name="ํŠน์ • ์œ ์ €๊ฐ€ ๋ฐ›์€ ๋ฆฌ์•ก์…˜ ๋ฐ˜ํ™˜ api", description="""
ํŠน์ • ์œ ์ €๊ฐ€ ๋ฐ›์€ reaction ์ •๋ณด๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.
""")
@user_router.get(
"/{slack_id}/my_reaction/",
name="ํŠน์ • ์œ ์ €๊ฐ€ ๋ฐ›์€ ๋ฆฌ์•ก์…˜ ๋ฐ˜ํ™˜ api",
description="ํŠน์ • ์œ ์ €๊ฐ€ ๋ฐ›์€ reaction ์ •๋ณด๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค."
)
async def get_my_reaction(slack_id: str, year: Optional[int] = None, month: Optional[int] = None):
return await ReactionAppService.get_my_reaction_infos(slack_id, year, month)
2 changes: 1 addition & 1 deletion app/applications/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class SlackMentionHook(BaseSlackEventHook):


class SlackBotDirectMessageHook(BaseSlackEventHook):
"""์Šฌ๋ž™ ๋ฉ˜์…˜ ์ด๋ฒคํŠธ ์›นํ›… ์Šคํ‚ค๋งˆ"""
"""์Šฌ๋ž™ ๋ด‡ ๋‹ค์ด๋ ‰ํŠธ ๋ฉ”์„ธ์ง€ ์›นํ›… ์Šคํ‚ค๋งˆ"""
event: SlackBotEvent = Field(title='์ด๋ฒคํŠธ ์ƒ์„ธ')


Expand Down
61 changes: 31 additions & 30 deletions app/applications/services/reaction_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,20 @@ async def update_sending_reaction(cls, event: SlackEvent):
๋‹ค๋ฅธ ์œ ์ €๊ฐ€ ๋ณด๋‚ธ ์ด๋ชจ์ง€ ๋ฆฌ์•ก์…˜ ์—…๋ฐ์ดํŠธ
"""
send_user = await cls._user_app_service.get_user(slack_id=event.user)
received_user = await cls._user_app_service().get_user(slack_id=event.item_user)
received_user = await cls._user_app_service.get_user(slack_id=event.item_user)

# ๋ฆฌ์•ก์…˜์„ send, receive ํ•œ ์œ ์ € ๋ชจ๋‘ ์กด์žฌํ•ด์•ผ ํ•œ๋‹ค
if send_user is None or received_user is None:
if not all([send_user, received_user]):
return

await cls.update_or_create_reaction_of_received_user(
await cls._update_or_create_reaction_of_received_user(
event=event,
send_user=send_user,
received_user=received_user
)

@classmethod
async def update_or_create_reaction_of_received_user(
async def _update_or_create_reaction_of_received_user(
cls,
event: SlackEvent,
send_user: User,
Expand All @@ -41,30 +41,23 @@ async def update_or_create_reaction_of_received_user(
"""
๋ฆฌ์•ก์…˜์„ ๋ฐ›์€ ์œ ์ €์˜ Reaction ์„ ์—…๋ฐ์ดํŠธ ํ•˜๊ฑฐ๋‚˜ ์ƒˆ๋กœ ์ƒ์„ฑ
"""
event_type = SlackEventType(event.type)
reaction = await cls._reaction_domain_service.get_reaction_by_emoji(
emoji=event.reaction,
received_user_id=received_user.id,
send_user_id=send_user.id
)
is_updated = await cls._reaction_domain_service.update_or_create_reaction(
reaction=reaction,
event_type=event_type,
emoji=event.reaction,
send_user_id=send_user.id,
received_user_id=received_user.id
)

if is_updated:
await cls.update_reaction_handler(send_user, event_type)

@classmethod
async def update_reaction_handler(cls, send_user: User, event_type: SlackEventType):
"""๋ฆฌ์•ก์…˜ ์—…๋ฐ์ดํŠธํ›„ ์ฒ˜๋ฆฌํ•ด์•ผํ•  ์ž‘์—…๋“ค"""
await cls._user_app_service.update_today_assigned_reaction_count(
is_increase=True if event_type == SlackEventType.ADDED_REACTION else False,
user=send_user
)
if cls.is_add_reaction(SlackEventType(event.type)):
await cls._reaction_domain_service.add_reaction(
reaction=reaction,
emoji=event.reaction,
send_user_id=send_user.id,
received_user_id=received_user.id
)
elif reaction:
await cls._reaction_domain_service.remove_reaction(
reaction=reaction
)

@classmethod
async def get_received_emoji_infos(
Expand All @@ -82,7 +75,7 @@ async def get_my_reaction_infos(
year: Optional[int] = None,
month: Optional[int] = None
) -> dict:
return await cls._reaction_domain_service.count_reaction_data(slack_id, year, month)
return await cls._reaction_domain_service.get_reaction_count_data(slack_id, year, month)

@classmethod
async def get_this_month_best_users(cls, year: int, month: int) -> dict:
Expand All @@ -93,19 +86,23 @@ async def get_this_month_best_users(cls, year: int, month: int) -> dict:
best_users = {} # BEST_TYPES ์„ ํ†ตํ•ด best ๋กœ ์„ ์ •๋œ users

for user in await cls._user_app_service.get_all_users():
user_received_emoji_infos.append(cls._reaction_domain_service.get_user_received_emoji_info(
reactions=await cls._reaction_domain_service.get_by_user_id_and_date(user.id, year, month),
username=user.username
))
user_received_emoji_infos.append(
cls._reaction_domain_service.get_user_received_emoji_info(
reactions=await cls._reaction_domain_service.get_by_user_id_and_date(user.id, year, month),
username=user.username
)
)

for best_type in settings.config.ALLOWED_EMOJI_TYPES:
max_emoji_count = 0

for user_received_emoji_info in user_received_emoji_infos:
try:
emoji_info = next(filter(
lambda x: x.emoji == best_type['emoji'], user_received_emoji_info.emoji_infos
))
emoji_info = next(
filter(
lambda x: x.emoji == best_type['emoji'], user_received_emoji_info.emoji_infos
)
)

if max_emoji_count < emoji_info.count:
max_emoji_count = emoji_info.count
Expand All @@ -115,3 +112,7 @@ async def get_this_month_best_users(cls, year: int, month: int) -> dict:
continue

return best_users

@staticmethod
def is_add_reaction(event_type: SlackEventType) -> bool:
return event_type == SlackEventType.ADDED_REACTION
Loading

0 comments on commit 9f176cc

Please sign in to comment.