From 8fabb244610f55bb50f0ad85d87dec83bf2805b8 Mon Sep 17 00:00:00 2001 From: Sharad-Pratap Date: Sun, 2 Mar 2025 00:52:19 +0530 Subject: [PATCH] Refactor user model to use JSON for interests, add user update and delete endpoints, and implement matchmaking logic with scoring --- __pycache__/database.cpython-311.pyc | Bin 0 -> 661 bytes __pycache__/main.cpython-311.pyc | Bin 0 -> 7970 bytes __pycache__/models.cpython-311.pyc | Bin 0 -> 1044 bytes __pycache__/schemas.cpython-311.pyc | Bin 0 -> 2314 bytes database.py | 4 +- main.py | 69 ++++++++++++++++++++++++++- models.py | 7 ++- schemas.py | 18 +++++-- test.db | Bin 0 -> 24576 bytes 9 files changed, 88 insertions(+), 10 deletions(-) create mode 100644 __pycache__/database.cpython-311.pyc create mode 100644 __pycache__/main.cpython-311.pyc create mode 100644 __pycache__/models.cpython-311.pyc create mode 100644 __pycache__/schemas.cpython-311.pyc create mode 100644 test.db diff --git a/__pycache__/database.cpython-311.pyc b/__pycache__/database.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..26ea9351dd55f69491645b603465ed24bd3021ec GIT binary patch literal 661 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 UGhMWWg!_T;*`y_>pK8*-0Gx&SZvX%Q literal 0 HcmV?d00001 diff --git a/__pycache__/models.cpython-311.pyc b/__pycache__/models.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..abec11b550bd97355b4aca08a41d7a59f6a32a87 GIT binary patch literal 1044 zcmZ`%%}*0S6rcTUx7`XV!2mH{grqUF2TV`K11JU)stI7?9@q?>NxNZpf!S?h<7E#X zN1Rg9GWuD(;j)z&xUZ*XtIX`~k1IotL&NYfRvY1>;(?Djkb+Ya%XOH-AJ*I+6zE<1z?G`T8V zT)}n9^*V)$&=2*n<8rm;ge{g4hYSZEiP0bozMxYuptI?+Xgw$g4a_hpudp@jFXFJ) zn10y`8yo&I4%W-i6m^5rW{VjRlQ2;{cn>T(AMwaUwD`jqYL9j@d&`F-U88bjRE~{G zG#lq{wkw^fJ++&kJjzcV=O?2lac;Oh)w#1>-YG|Oap89RRcC&Cb!Ro2kIh@{`A%i; z{%6uPXO7I7V{;~YdToy3(T?5;KbRlQXh94%-fys4G}(7ilQWJ9`pg08am!{5UC=Nebi+&>iC2;=|& literal 0 HcmV?d00001 diff --git a/__pycache__/schemas.cpython-311.pyc b/__pycache__/schemas.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2a85b88bca1e33d662192059a0fb5290c626f068 GIT binary patch literal 2314 zcmZuyy>Ht_6u%>n6sZr(uG`p7>%dK2G%^r-Xon0zhdQahYCC{qpwL3F;>8v!QSOlj zs*M6VXz-vd89JvZvUTdvq3B;A0U3x}J9U#`PoDbT(Kann)amD^`@MU7U-$f5sgx)1 z{B8Vm^p{4+zxd^y>S(P=7szHfHHi)PHLOjOl zTb+=1@J*hosW<~Tlbd5)PH{Qltjsw}aW-)I%sE?e1>lOAb9t|HKq^!J;p;1!w7+o! zzT5J6Lt4+9ZoP333fN}f)q_x4`zK+&<-3jAwYTcejqexV!_U`*lPdK{RXZeH=ggx| zVV4^qX-dL9T}dWL>~_ykQjY5jx3+;dao5X%hnBPs11``#$$YoTrS2YaX&!Ok<3bu7 zot3Os58KkN`ym%R2!lO5=QybEI5O`z&6aoCK)L8RKb^XbYeo-3A^K{cKrB7J3MarFWunBwV-wk9tEo> zZDFAS1hf?F14MT|alRC7MO&S%*e*t!(Pn2eE|#O6Xs5Fi7iOdVXuq?c>Xc`D>u2pg zn};<}2!gnG^M;YSP}s%iVhSLwi$@`wn#^Mm>w>!>->c}N3_4;OVFuwN1Z8#(C9H{p zA_xKMrXj@4ik>C9s5aulZEO4qOzOfRT#qU=0ypjy+e{h+m>e+H2%{PX7&~)DH4rd) zU)rX@XJ?CjhHJnU<4=~NZ@ZhlgR|W}TY@!UOYzcj^t}6|xB0#RTFbBoYQRiovG<1yo_1p+DtUjU56oS%xeqwUUiJaezla19iKki>*1_bLTx zowPmI59>97L8&P%&9Hq^_m7ghqpqBqy?CwFwMp`=92gyNSJuz>% literal 0 HcmV?d00001 diff --git a/database.py b/database.py index accf2f5..7d45777 100644 --- a/database.py +++ b/database.py @@ -2,10 +2,10 @@ from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker -// Free to use remote db or create a local database. Modify the URl appropriately +# Free to use remote db or create a local database. Modify the URl appropriately SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db" -engine = create_engine(SQLALCHEMY_DATABASE_URL) +engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base() diff --git a/main.py b/main.py index f83e868..585f3bf 100644 --- a/main.py +++ b/main.py @@ -2,7 +2,8 @@ from sqlalchemy.orm import Session from database import SessionLocal, engine, Base import models, schemas - +import json +from sqlalchemy import text app = FastAPI() Base.metadata.create_all(bind=engine) @@ -34,3 +35,69 @@ def read_user(user_id: int, db: Session = Depends(get_db)): raise HTTPException(status_code=404, detail="User not found") return user +# this is a update user endpoint +@app.patch("/users/{user_id}", response_model=schemas.User) +def update_user(user_id: int, user_data: schemas.UserUpdate, db: Session = Depends(get_db)): + user = db.query(models.User).filter(models.User.id == user_id).first() + if not user: + raise HTTPException(status_code=404, detail="User not found") + + update_data = user_data.dict(exclude_unset=True) + for key, value in update_data.items(): + setattr(user, key, value) + + db.commit() + db.refresh(user) + return user + + +# this is a delete user endpoint +@app.delete("/users/{user_id}", response_model=dict) +def delete_user(user_id: int, db: Session = Depends(get_db)): + user = db.query(models.User).filter(models.User.id == user_id).first() + if not user: + raise HTTPException(status_code=404, detail="User not found") + db.delete(user) + db.commit() + return {"detail": "User deleted"} + +# this is a find matches for a user endpoint +@app.get("/users/{user_id}/matches", response_model=list[schemas.User]) +def find_matches(user_id: int, db: Session = Depends(get_db)): + # Retrieve the given user's profile + user = db.query(models.User).filter(models.User.id == user_id).first() + if not user: + raise HTTPException(status_code=404, detail="User not found") + matches = [] + # Retrieve all other users + all_users = db.query(models.User).filter(models.User.id != user_id).all() + for other in all_users: + # If there's at least one common interest, consider it a match + if set(user.interests).intersection(set(other.interests)): + matches.append(other) + return matches + +# this is a upgraded matchmaking endpoint that returns a match score 😅 +@app.get("/users/{user_id}/matches_score", response_model=list[schemas.UserMatch]) +def find_matches_score(user_id: int, db: Session = Depends(get_db)): + user = db.query(models.User).filter(models.User.id == user_id).first() + if not user: + raise HTTPException(status_code=404, detail="User not found") + + matches = [] + all_users = db.query(models.User).filter(models.User.id != user_id).all() + for other in all_users: + # here we are calculating common interests score + common_interests = set(user.interests).intersection(set(other.interests)) + score = len(common_interests) + # here we are adding bonus if both users are in the same city + if user.city.lower() == other.city.lower(): + score += 1 + + # Only consider matches with a score greater than 0 + if score > 0: + matches.append({"user": other, "match_score": score}) + + # Sort matches by score in descending order + matches.sort(key=lambda x: x["match_score"], reverse=True) + return matches diff --git a/models.py b/models.py index 0e42748..0a010f6 100644 --- a/models.py +++ b/models.py @@ -1,6 +1,6 @@ -from sqlalchemy import Column, Integer, String, ARRAY +from sqlalchemy import Column, Integer, String from database import Base - +from sqlalchemy.dialects.sqlite import JSON class User(Base): __tablename__ = "users" @@ -10,5 +10,4 @@ class User(Base): gender = Column(String) email = Column(String, unique=True, index=True) city = Column(String, index=True) - interests = Column(ARRAY(String)) - + interests = Column(JSON) diff --git a/schemas.py b/schemas.py index 5872710..3f675b6 100644 --- a/schemas.py +++ b/schemas.py @@ -1,20 +1,32 @@ -from pydantic import BaseModel -from typing import List +from pydantic import BaseModel, EmailStr +from typing import List, Optional class UserBase(BaseModel): name: str age: int gender: str - email: str + email: EmailStr # EmailStr validates email itself (i dont need to write a custom validator) city: str interests: List[str] class UserCreate(UserBase): pass +class UserUpdate(BaseModel): + name: Optional[str] = None + age: Optional[int] = None + gender: Optional[str] = None + email: Optional[EmailStr] = None + city: Optional[str] = None + interests: Optional[List[str]] = None + class User(UserBase): id: int class Config: orm_mode = True +class UserMatch(BaseModel): + user: User + match_score: float + diff --git a/test.db b/test.db new file mode 100644 index 0000000000000000000000000000000000000000..1a2185b6b9f24bf814939a7937427193de29e402 GIT binary patch literal 24576 zcmeI(Z)?*)90%~bWbKy1dP<$aXGdjdU~DiS6ntX48r-yYE^E~>#z>cI4NKb5q}cgP z-;4N`_GE8BQ1HoM-~%t$RZ>M7QEf-hi=TDkUNA@ zQs$fx!sMQn`*_R9!rZtbk2F>OSCNs`_V-2En#r4lRr8nmulaph#0CKfKmY;|fB*y_ z009U<00K8dU?YN^i-}6)}->!4jgFp5? zYjZAZJ~F908KdilLiNc^kCL>f;v`ke(X*yy(r7Z02wgu=q2{haHS1eD&1>^g{Zb)S z-P+Wq&F;vLPgO{L`s|YSRH&qCeSS*kP1B@bc4M~{jFM9^w5u46%0|r|!{iL#lX4CF zh3)YAc7xZOwb}~L87lF&6{oscagw^*5#xSIwIk%E=<`&EQC_JNYI3Q%-;G6IL~+Dl z?rztUAa9(l*A-`vzq0q#CCXWgY)CX#BCN!|-Hg8;W&r^RKmY;|fB*y_009U<00I!W zF#^x%0$HX@R-xpHu*U}{o*N7AUg!oQIZ^H;_a;`4dg1W&(|)l(=yv^Xr?|q49XCu$ z2Qrhj|H$T@wm?k!V6AZcK?FhXep@d7M?