implement friend group, remark and managing group
parent
f512089fd9
commit
5e104641d2
|
@ -0,0 +1,28 @@
|
|||
"""add default_group column of contact table
|
||||
|
||||
Revision ID: 86195fe53b88
|
||||
Revises: d0c7f4dd4894
|
||||
Create Date: 2023-07-31 00:27:37.445472
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '86195fe53b88'
|
||||
down_revision = 'd0c7f4dd4894'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('contact', sa.Column('default_group', sa.String(), nullable=True))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('contact', 'default_group')
|
||||
# ### end Alembic commands ###
|
|
@ -0,0 +1,111 @@
|
|||
from typing import Tuple
|
||||
|
||||
from sqlalchemy import select, delete, or_
|
||||
from sqlalchemy import ScalarResult, Result
|
||||
from sqlalchemy.orm.attributes import flag_modified
|
||||
|
||||
from ..database.db import async_session
|
||||
from ..database.models import Contact, Apply, UserAccount, UserProfile
|
||||
|
||||
|
||||
async def insert_contact_friend(
|
||||
relation: int,
|
||||
applicant: str,
|
||||
recipient: str,
|
||||
applicant_setting: dict,
|
||||
recipient_setting: dict,
|
||||
):
|
||||
session = async_session()
|
||||
try:
|
||||
await session.execute(
|
||||
delete(Apply).where(
|
||||
Apply.recipient == recipient,
|
||||
Apply.applicant == applicant,
|
||||
Apply.relation == relation,
|
||||
)
|
||||
)
|
||||
res: ScalarResult[Contact] = await session.scalars(
|
||||
select(Contact).where(
|
||||
or_(Contact.user_id == applicant, Contact.user_id == recipient)
|
||||
)
|
||||
)
|
||||
for contact in res.all():
|
||||
if contact.user_id == recipient:
|
||||
contact.friends[applicant] = recipient_setting
|
||||
flag_modified(contact, "friends")
|
||||
else:
|
||||
contact.friends[recipient] = applicant_setting
|
||||
flag_modified(contact, "friends")
|
||||
|
||||
session.add_all(res.all())
|
||||
await session.commit()
|
||||
except Exception:
|
||||
raise Exception
|
||||
finally:
|
||||
await session.close()
|
||||
|
||||
|
||||
async def select_contact_all(user_id: str) -> Contact:
|
||||
session = async_session()
|
||||
res: ScalarResult[Contact] = await session.scalars(
|
||||
select(Contact).where(Contact.user_id == user_id)
|
||||
)
|
||||
await session.close()
|
||||
return res.one()
|
||||
|
||||
|
||||
async def select_friends_group_chats(
|
||||
friend_ids: list[str],
|
||||
) -> list[Tuple[UserAccount, UserProfile]]:
|
||||
session = async_session()
|
||||
res: Result[list[Tuple[UserAccount, UserProfile]]] = await session.execute(
|
||||
select(UserAccount, UserProfile)
|
||||
.join(UserAccount.profile)
|
||||
.where(UserAccount.id.in_(friend_ids))
|
||||
)
|
||||
|
||||
return res.all()
|
||||
|
||||
|
||||
async def update_friend_setting(
|
||||
user_id: str,
|
||||
friend_id: str,
|
||||
remark: str | None,
|
||||
group: str | None,
|
||||
):
|
||||
session = async_session()
|
||||
res = await session.scalars(select(Contact).where(Contact.user_id == user_id))
|
||||
contact: Contact = res.one()
|
||||
if remark:
|
||||
contact.friends[friend_id]["friendRemark"] = remark
|
||||
else:
|
||||
contact.friends[friend_id]["friendGroup"] = group
|
||||
flag_modified(contact, "friends")
|
||||
session.add(contact)
|
||||
await session.commit()
|
||||
await session.close()
|
||||
|
||||
|
||||
async def update_groups(
|
||||
user_id: str,
|
||||
groups: list[str],
|
||||
group_name_change_pair: list[list[str]],
|
||||
default_group: str,
|
||||
):
|
||||
session = async_session()
|
||||
res = await session.scalars(select(Contact).where(Contact.user_id == user_id))
|
||||
contact = res.one()
|
||||
contact.friend_groups = groups
|
||||
contact.default_group = default_group
|
||||
|
||||
for pair in group_name_change_pair:
|
||||
if pair[1] == "":
|
||||
continue
|
||||
for friend_id, friend_setting in contact.friends.items():
|
||||
if pair[0] == friend_setting["friendGroup"]:
|
||||
contact.friends[friend_id]["friendGroup"] = pair[1]
|
||||
|
||||
flag_modified(contact, "friends")
|
||||
session.add(contact)
|
||||
await session.commit()
|
||||
await session.close()
|
|
@ -1,43 +0,0 @@
|
|||
from sqlalchemy import select, delete, update
|
||||
from sqlalchemy import Result, ScalarResult, or_
|
||||
from sqlalchemy.orm.attributes import flag_modified
|
||||
|
||||
from ..database.db import async_session
|
||||
from ..database.models import *
|
||||
|
||||
|
||||
async def insert_contact_friend(
|
||||
relation: int,
|
||||
applicant: str,
|
||||
recipient: str,
|
||||
applicant_setting: dict,
|
||||
recipient_setting: dict,
|
||||
):
|
||||
session = async_session()
|
||||
try:
|
||||
await session.execute(
|
||||
delete(Apply).where(
|
||||
Apply.recipient == recipient,
|
||||
Apply.applicant == applicant,
|
||||
Apply.relation == relation,
|
||||
)
|
||||
)
|
||||
res: ScalarResult[Contact] = await session.scalars(
|
||||
select(Contact).where(
|
||||
or_(Contact.user_id == applicant, Contact.user_id == recipient)
|
||||
)
|
||||
)
|
||||
for contact in res.all():
|
||||
if contact.user_id == recipient:
|
||||
contact.friends[applicant] = recipient_setting
|
||||
flag_modified(contact, "friends")
|
||||
else:
|
||||
contact.friends[recipient] = applicant_setting
|
||||
flag_modified(contact, "friends")
|
||||
|
||||
session.add_all(res.all())
|
||||
await session.commit()
|
||||
except Exception:
|
||||
raise Exception
|
||||
finally:
|
||||
await session.close()
|
|
@ -16,7 +16,11 @@ async def insert_user(username: str, password: str, email: str):
|
|||
id = ulid.new().str
|
||||
user = UserAccount(id=id, username=username, password=password, email=email)
|
||||
profile = UserProfile(nickname=username)
|
||||
contact = Contact(friends={id: {}}, friend_groups=["我的好友"], group_chats={})
|
||||
contact = Contact(
|
||||
friends={id: {"friendGroup": "我的好友", "friendRemark": ""}},
|
||||
friend_groups=["我的好友"],
|
||||
group_chats={},
|
||||
)
|
||||
user.profile = profile
|
||||
user.contact = contact
|
||||
session.add(user)
|
||||
|
|
|
@ -81,6 +81,7 @@ class Contact(Base):
|
|||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
friends: Mapped[dict[str, FriendSetting]] = mapped_column(JSONB, nullable=True)
|
||||
friend_groups: Mapped[list[str]] = mapped_column(ARRAY(String), nullable=True)
|
||||
default_group: Mapped[str] = mapped_column(String, default="我的好友", nullable=True)
|
||||
group_chats: Mapped[dict[str, GroupChatSetting]] = mapped_column(
|
||||
JSONB, nullable=True
|
||||
)
|
||||
|
@ -95,10 +96,19 @@ class Contact(Base):
|
|||
f"user={self.user_id}, "
|
||||
f"friends={self.friends}, "
|
||||
f"friend_group={self.friend_groups}, "
|
||||
f"default_group={self.default_group},"
|
||||
f"group_chats={self.group_chats}"
|
||||
f")"
|
||||
)
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"friends": self.friends,
|
||||
"friendGroups": self.friend_groups,
|
||||
"defaultGroup": self.default_group,
|
||||
"groupChats": self.group_chats,
|
||||
}
|
||||
|
||||
|
||||
class Apply(Base):
|
||||
__tablename__ = "apply"
|
||||
|
|
15
src/main.py
15
src/main.py
|
@ -1,5 +1,5 @@
|
|||
from fastapi import FastAPI, Depends
|
||||
from starlette.responses import FileResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from .dependencies import verify_token
|
||||
from .utils.email_code import smtp
|
||||
|
@ -9,8 +9,8 @@ from .routers.user_profile import router as user_profile_router
|
|||
from .routers.user_account import router as user_account_router
|
||||
from .routers.search import router as search_router
|
||||
from .routers.apply import router as apply_router
|
||||
from .routers.contact import router as contact_router
|
||||
|
||||
from .utils.static_file import create_zip_file
|
||||
|
||||
app = FastAPI()
|
||||
app.include_router(signup_router)
|
||||
|
@ -19,6 +19,9 @@ app.include_router(user_profile_router, dependencies=[Depends(verify_token)])
|
|||
app.include_router(user_account_router, dependencies=[Depends(verify_token)])
|
||||
app.include_router(search_router, dependencies=[Depends(verify_token)])
|
||||
app.include_router(apply_router, dependencies=[Depends(verify_token)])
|
||||
app.include_router(contact_router, dependencies=[Depends(verify_token)])
|
||||
|
||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||
|
||||
|
||||
@app.on_event("shutdown")
|
||||
|
@ -29,11 +32,3 @@ def close_smtp():
|
|||
@app.get("/")
|
||||
async def main():
|
||||
return {"code": 10000, "msg": "hello world"}
|
||||
|
||||
|
||||
@app.get("/zipfile")
|
||||
async def get_zipfile():
|
||||
file = create_zip_file(
|
||||
["luhptjjk1688921163.png", "pnjdvldw1688921358.png"], "avatars"
|
||||
)
|
||||
return FileResponse(file)
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
from pydantic import BaseModel
|
||||
|
||||
from .base import BaseResponseModel
|
||||
from .user_response import _UserAccountProfile
|
||||
|
||||
|
||||
class _Apply(BaseModel):
|
||||
relation: int
|
||||
applicant: str
|
||||
recipient: str
|
||||
groupChatId: str | None = None
|
||||
hello: str
|
||||
setting: dict
|
||||
createdAt: str
|
||||
|
||||
|
||||
class ApplyListResponse(BaseResponseModel):
|
||||
data: list[_Apply] | None = None
|
||||
|
||||
|
||||
class ApplicantProfilesResponse(BaseResponseModel):
|
||||
data: dict[str, _UserAccountProfile]
|
|
@ -0,0 +1,34 @@
|
|||
from pydantic import BaseModel
|
||||
|
||||
from .base import BaseResponseModel
|
||||
from .user_response import _UserAccountProfile
|
||||
|
||||
|
||||
class _FriendSetting(BaseModel):
|
||||
friendRemark: str | None = None
|
||||
friendGroup: str | None = None
|
||||
|
||||
|
||||
class _GroupChatSetting(BaseModel):
|
||||
groupChatRemark: str | None = None
|
||||
myRemark: str | None = None
|
||||
|
||||
|
||||
class _ContactResponseData(BaseModel):
|
||||
friends: dict[str, _FriendSetting]
|
||||
friendGroups: list[str]
|
||||
defaultGroup: str
|
||||
groupChats: dict[str, _GroupChatSetting]
|
||||
|
||||
|
||||
class _ContactAccountProfile(BaseModel):
|
||||
friends: dict[str, _UserAccountProfile]
|
||||
groupChats: dict | None = None
|
||||
|
||||
|
||||
class ContactResponse(BaseResponseModel):
|
||||
data: _ContactResponseData | None = None
|
||||
|
||||
|
||||
class ContactAccountProfileResponse(BaseResponseModel):
|
||||
data: _ContactAccountProfile | None = None
|
|
@ -22,7 +22,7 @@ class _UserProfile(BaseModel):
|
|||
|
||||
|
||||
class UserAccountResponse(BaseResponseModel):
|
||||
data: Optional[_UserAccount] = None
|
||||
data: _UserAccount | None = None
|
||||
|
||||
|
||||
class TokenCreationResponse(BaseResponseModel):
|
||||
|
@ -30,8 +30,8 @@ class TokenCreationResponse(BaseResponseModel):
|
|||
|
||||
|
||||
class TokenSigninResponse(BaseResponseModel):
|
||||
data: Optional[_UserAccount] = None
|
||||
token: Optional[str] = None
|
||||
data: _UserAccount | None = None
|
||||
token: str | None = None
|
||||
|
||||
|
||||
class UserProfileResponse(BaseResponseModel):
|
||||
|
|
|
@ -2,8 +2,12 @@ from fastapi import APIRouter, Query
|
|||
from fastapi.responses import FileResponse
|
||||
from pydantic import BaseModel
|
||||
|
||||
from src.crud import apply_crud, user_crud, multitable_crud
|
||||
from ..response_models.base import BaseResponseModel
|
||||
from src.crud import apply_crud, user_crud, contact_crud
|
||||
from ..response_models.apply_response import (
|
||||
BaseResponseModel,
|
||||
ApplyListResponse,
|
||||
ApplicantProfilesResponse,
|
||||
)
|
||||
from ..utils.static_file import create_zip_file
|
||||
|
||||
router = APIRouter(prefix="/apply", tags=["apply"])
|
||||
|
@ -39,7 +43,7 @@ async def apply_friend(apply_info: ApplyInfo):
|
|||
return {"code": 10600, "msg": "Apply Friend Successfully"}
|
||||
|
||||
|
||||
@router.get("/list")
|
||||
@router.get("/list", response_model=ApplyListResponse)
|
||||
async def get_apply_list(recipient: str):
|
||||
res = await apply_crud.select_apply_all(recipient)
|
||||
if not res:
|
||||
|
@ -56,7 +60,7 @@ async def get_apply_list(recipient: str):
|
|||
}
|
||||
|
||||
|
||||
@router.get("/applicant_profiles")
|
||||
@router.get("/applicant_profiles", response_model=ApplicantProfilesResponse)
|
||||
async def get_applicant_profiles(applicant_ids: list[str] = Query(default=None)):
|
||||
res = await user_crud.select_multiuser_info(applicant_ids)
|
||||
applicant_profiles = {}
|
||||
|
@ -77,17 +81,17 @@ async def download_applicant_avatars(avatars: list[str] = Query(default=None)):
|
|||
return FileResponse(file_path)
|
||||
|
||||
|
||||
@router.post("/accept")
|
||||
@router.post("/accept", response_model=BaseResponseModel)
|
||||
async def accept_apply(accept_info: AcceptInfo):
|
||||
try:
|
||||
await multitable_crud.insert_contact_friend(**accept_info.model_dump())
|
||||
await contact_crud.insert_contact_friend(**accept_info.model_dump())
|
||||
return {"code": 10600, "msg": "Add Friend Successfully"}
|
||||
except Exception as e:
|
||||
print(f"接受添加好友请求出错....: {e}")
|
||||
return {"code": 10601, "msg": "Something Went Wrong On the Server"}
|
||||
|
||||
|
||||
@router.post("/refuse")
|
||||
@router.post("/refuse", response_model=BaseResponseModel)
|
||||
async def refuse_apply(refuse_info: RefuseInfo):
|
||||
await apply_crud.delete_apply(**refuse_info.model_dump())
|
||||
return {"code": 10600, "msg": "Refuse Apply Successfully"}
|
||||
|
|
|
@ -1,4 +1,66 @@
|
|||
from fastapi import APIRouter
|
||||
from pydantic import BaseModel
|
||||
|
||||
router = APIRouter(prefix='/profile', tags=['profile'])
|
||||
from ..crud import contact_crud, user_crud
|
||||
from ..response_models.contact_response import (
|
||||
BaseResponseModel,
|
||||
ContactResponse,
|
||||
ContactAccountProfileResponse,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/contact", tags=["contact"])
|
||||
|
||||
|
||||
class ContactIds(BaseModel):
|
||||
friend_ids: list[str]
|
||||
group_chat_ids: list[str] | None = None
|
||||
|
||||
|
||||
class ChangeFriendSetting(BaseModel):
|
||||
user_id: str
|
||||
friend_id: str
|
||||
remark: str | None = None
|
||||
group: str | None = None
|
||||
|
||||
|
||||
class ManageGroups(BaseModel):
|
||||
user_id: str
|
||||
groups: list[str]
|
||||
group_name_change_pair: list[list[str]]
|
||||
default_group: str
|
||||
|
||||
|
||||
@router.get("", response_model=ContactResponse)
|
||||
async def get_contact(id: str):
|
||||
contact = await contact_crud.select_contact_all(id)
|
||||
print(contact.to_dict())
|
||||
return {"code": 10700, "msg": "Get Contact Successfully", "data": contact.to_dict()}
|
||||
|
||||
|
||||
@router.post("/profiles", response_model=ContactAccountProfileResponse)
|
||||
async def get_contact_account_profiles(contact_ids: ContactIds):
|
||||
res = await contact_crud.select_friends_group_chats(contact_ids.friend_ids)
|
||||
|
||||
friends_account_profiles = {}
|
||||
|
||||
for account, profile in res:
|
||||
friends_account_profiles[account.id] = account.to_dict()
|
||||
friends_account_profiles[account.id].update(profile.to_dict())
|
||||
|
||||
return {
|
||||
"code": 10700,
|
||||
"msg": "Get Contact Profiles Successfully",
|
||||
"data": {"friends": friends_account_profiles},
|
||||
}
|
||||
|
||||
|
||||
@router.post("/change/friend_setting", response_model=BaseResponseModel)
|
||||
async def change_friend_remark(friend_remark: ChangeFriendSetting):
|
||||
await contact_crud.update_friend_setting(**friend_remark.model_dump())
|
||||
return {"code": 10700, "msg": "change Friend Remark Successfully"}
|
||||
|
||||
|
||||
@router.post("/manage_groups", response_model=BaseResponseModel)
|
||||
async def manage_groups(group_info: ManageGroups):
|
||||
await contact_crud.update_groups(**group_info.model_dump())
|
||||
return {"code": 10700, "msg": "Manage Groups Successfully"}
|
||||
|
|
|
@ -7,10 +7,18 @@ from jose import ExpiredSignatureError, JWTError
|
|||
|
||||
from ..crud.user_crud import select_account_by
|
||||
from ..utils.password import verify_password
|
||||
from ..utils.token_handler import create_signin_token, oauth2_scheme, verify_signin_token
|
||||
from ..response_models.user_response import UserAccountResponse, TokenCreationResponse, TokenSigninResponse
|
||||
from ..utils.token_handler import (
|
||||
create_signin_token,
|
||||
oauth2_scheme,
|
||||
verify_signin_token,
|
||||
)
|
||||
from ..response_models.user_response import (
|
||||
UserAccountResponse,
|
||||
TokenCreationResponse,
|
||||
TokenSigninResponse,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix='/signin', tags=['signin'])
|
||||
router = APIRouter(prefix="/signin", tags=["signin"])
|
||||
|
||||
|
||||
class TokenPayload(BaseModel):
|
||||
|
@ -18,39 +26,55 @@ class TokenPayload(BaseModel):
|
|||
device_id: str
|
||||
|
||||
|
||||
@router.post('/username', response_model=UserAccountResponse)
|
||||
@router.post("/username", response_model=UserAccountResponse)
|
||||
async def signin_by_username(form_data: OAuth2PasswordRequestForm = Depends()):
|
||||
username = form_data.username
|
||||
password = form_data.password
|
||||
is_existence, user = await select_account_by('username', username)
|
||||
is_existence, user = await select_account_by("username", username)
|
||||
|
||||
if not is_existence:
|
||||
return {'code': 10201, 'msg': 'Username or Password Is Incorrect'}
|
||||
return {"code": 10201, "msg": "Username or Password Is Incorrect"}
|
||||
|
||||
is_correct = verify_password(password, user.password)
|
||||
|
||||
if is_correct:
|
||||
return {'code': 10200, 'msg': 'Sign in Successfully', 'data': user.to_dict()}
|
||||
return {"code": 10200, "msg": "Sign in Successfully", "data": user.to_dict()}
|
||||
else:
|
||||
return {'code': 10201, 'msg': 'Username or Password Is Incorrect', 'data': None}
|
||||
return {"code": 10201, "msg": "Username or Password Is Incorrect"}
|
||||
|
||||
|
||||
@router.post('/token', response_model=TokenCreationResponse)
|
||||
@router.post("/token", response_model=TokenCreationResponse)
|
||||
async def create_token(token_payload: TokenPayload):
|
||||
token = create_signin_token(**token_payload.model_dump())
|
||||
return {'code': 10200, 'msg': 'Create Token Successfully', 'token': token}
|
||||
return {"code": 10200, "msg": "Create Token Successfully", "token": token}
|
||||
|
||||
|
||||
@router.get('/token', response_model=TokenSigninResponse)
|
||||
@router.get("/token", response_model=TokenSigninResponse)
|
||||
async def signin_by_token(token: str = Depends(oauth2_scheme)):
|
||||
try:
|
||||
new_token, id = verify_signin_token(token)
|
||||
_, user = await select_account_by('id', id)
|
||||
_, user = await select_account_by("id", id)
|
||||
if new_token:
|
||||
return {'code': 10200, 'msg': 'Sign in Successfully', 'data': user.to_dict(), 'token': new_token}
|
||||
return {
|
||||
"code": 10200,
|
||||
"msg": "Sign in Successfully",
|
||||
"data": user.to_dict(),
|
||||
"token": new_token,
|
||||
}
|
||||
else:
|
||||
return {'code': 10200, 'msg': 'Sign in Successfully', 'data': user.to_dict(), 'token': token}
|
||||
return {
|
||||
"code": 10200,
|
||||
"msg": "Sign in Successfully",
|
||||
"data": user.to_dict(),
|
||||
"token": token,
|
||||
}
|
||||
except ExpiredSignatureError:
|
||||
return {'code': 9999, 'msg': 'Token has Expired', 'data': None, 'token': None}
|
||||
return {
|
||||
"code": 9999,
|
||||
"msg": "Token has Expired",
|
||||
}
|
||||
except JWTError:
|
||||
return {'code': 9998, 'msg': 'Token Is Not Right', 'data': None, 'token': None}
|
||||
return {
|
||||
"code": 9998,
|
||||
"msg": "Token Is Not Right",
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue