def link_google_account(google_id: str, access_token: str, refresh_token: str, expires_at: str) -> Tuple[dict, Optional[Error]]: with open_pg() as conn: with open_cursor(conn) as cur: cur.execute( "select google_id " "from users " "where google_id = %s", (google_id, )) if cur.fetchone() is None: return {}, Error( 400, f"Google account `{google_id}` does not match the registered account." ) cur.execute( "update users set " "google_access_token = %s," "google_refresh_token = %s," "google_token_expires_at = %s " "where google_id = %s", (encrypt(access_token, PASSWORD), encrypt(refresh_token, PASSWORD) if refresh_token else None, expires_at, google_id)) cur.execute("select * " "from users " "where google_id = %s", (google_id, )) user = cur.fetchone() if user is None: return {}, Error( 500, "User information could not be saved due to an unexpected error.") return dict(user), None
def link_twitter_account(twitter_id: str, twitter_name: str, access_toke: str, access_secret: str) -> Tuple[dict, Optional[Error]]: with open_pg() as conn: with open_cursor(conn) as cur: cur.execute( "select twitter_name " "from users " "where twitter_name = %s", (twitter_name, )) if cur.fetchone() is None: return {}, Error( 400, f"Twitter account `{twitter_name}` does not match the registered account." ) cur.execute( "update users set " "twitter_id = %s," "twitter_name = %s," "twitter_access_token = %s," "twitter_access_token_secret = %s " "where twitter_name = %s", (twitter_id, twitter_name, encrypt(access_toke, PASSWORD), encrypt(access_secret, PASSWORD), twitter_name)) cur.execute("select * " "from users " "where twitter_id = %s", (twitter_id, )) user = cur.fetchone() if user is None: return {}, Error( 500, "User information could not be saved due to an unexpected error." ) return dict(user), None
def fetch_twitter_info(user_id: str) -> Tuple[dict, Optional[Error]]: with open_pg() as conn: with open_cursor(conn) as cur: cur.execute( "select twitter_id," " twitter_name," " twitter_access_token," " twitter_access_token_secret," " twitter_latest_id " "from users " "where user_id = %s", (user_id, )) twitter = cur.fetchone() if twitter is None: return {}, Error(404, f"User {user_id} does not exist.") if twitter["twitter_id"] is None: return {}, Error( 403, f"User {user_id} is not linked to Twitter account.") twitter_info = { "id": twitter["twitter_id"], "name": twitter["twitter_name"], "access_token": decrypt(twitter["twitter_access_token"], PASSWORD), "access_secret": decrypt(twitter["twitter_access_token_secret"], PASSWORD), "latest_id": twitter["twitter_latest_id"], } return twitter_info, None
def sign_up(user_name: str, google: str, twitter: str) -> Tuple[dict, Optional[Error]]: user_id = google.split("@")[0] with open_pg() as conn: with open_cursor(conn) as cur: cur.execute( "select user_id " "from users " "where user_id = %s or twitter_name = %s", (user_id, twitter)) if cur.fetchone() is not None: return {}, Error( 400, f"User `{user_id}` (or `{twitter}`) already exists.") cur.execute( "insert into users " "(user_id, user_name, google_id, twitter_name) " "values (%s, %s, %s, %s)", (user_id, user_name, google, twitter)) cur.execute("select * " "from users " "where user_id = %s", (user_id, )) user = cur.fetchone() if user is None: return {}, Error( 500, "User information could not be saved due to an unexpected error.") return dict(user), None
def get_weather(lat: int = None, lng: int = None) -> HTTPResponse: if lat is None or lng is None: lat, lng, err = get_validation(request, "lat", "lng") if err: return err.response if not float_check(lat, 90): return Error(400, "lat must be a real number between -90 and 90").response if not float_check(lng, 180): return Error(400, "lng must be a real number between -180 and 180").response weather_get_url = f"http://api.openweathermap.org/data/2.5/weather?APPID={WEATHER_API_KEY}&lat={lat}&lon={lng}" req = requests.get(weather_get_url) if req.status_code != 200: return Error(req.status_code, req.json()).response body = req.json() weather_id = body['weather'][0]['id'] weather = WEATHER_TABLE.get(weather_id) return json_response( 200, { "weather": weather["main_ja"], "icon_url": f"http://openweathermap.org/img/wn/{weather['icon']}@2x.png", "temp": int(body["main"]["temp"] - 273.15), "humid": body["main"]["humidity"] })
def get_map(q: str = None) -> HTTPResponse: *_, err = get_validation(request) if err: return err.response if q is None: q = request.params.q if q == "": return Error(400, "parameter `q` is required.").response client = googlemaps.Client(key=MAP_API_KEY) req = client.places(q) if req['status'] != 'OK': return Error(400, f"Google Maps API returns `{req['status']}`").response if len(req["results"]) != 1: return Error( 500, f"Invalid number of result ({len(req['results'])}) are returned." ).response result = req["results"][0] body = {} photos = result.get("photos") if photos: body["photo_url"] = ( f"https://maps.googleapis.com/maps/api/place/photo" f"?maxwidth={PHOTO_WIDTH}" f"&photoreference={photos[0]['photo_reference']}" f"&key={MAP_API_KEY}") body["latitude"] = result["geometry"]["location"]["lat"] body["longitude"] = result["geometry"]["location"]["lng"] return json_response(200, body)
def get_twitter_today_detail() -> HTTPResponse: user = request.get_cookie("user_id") # , secret=PASSWORD) q = request.params.q *_, err = get_validation(request) if err: return err.response if user is None: return Error(400, "parameter `user` is required").response() twitter, err = fetch_twitter_info(user) if err: return err.response oauth = OAuth1Session(CK, CS, twitter["access_token"], twitter["access_secret"]) res, new_tweets = get_new_tweets(oauth, twitter["latest_id"]) if res.status_code == 401 and res.json()["errors"][0]["code"] == 89: return Error(401, "The registered token is invalid. It may have expired. Please re-login.").response elif res.status_code != 200: return json_response(res.status_code, res.json()) body = update_tweets(user, new_tweets) hashtags = list(set(hstg for tw in body for hstg in tw["hashtags"])) detail = { "event": [], "morning": [], "breakfast": [], "lunch": [], "dinner": [], "night": [] } for h in sorted(hashtags): detail[h] = [] detail["other"] = [] used = set() for idx, tw in enumerate(body): if sum([t in tw["text"] for t in MORNING]) > 0: detail["morning"].append(tw) used.add(idx) if sum([t in tw["text"] for t in NIGHT]) > 0: detail["night"].append(tw) used.add(idx) if sum([t in tw["text"] for t in BREAKFAST]) > 0: detail["breakfast"].append(tw) used.add(idx) if sum([t in tw["text"] for t in LUNCH]) > 0: detail["lunch"].append(tw) used.add(idx) if sum([t in tw["text"] for t in DINNER]) > 0: detail["dinner"].append(tw) used.add(idx) if len(tw["hashtags"]) > 0: for hs in tw["hashtags"]: detail[hs].append(tw) used.add(idx) if idx not in used: detail["other"].append(tw) if q != "": detail = detail.get(q, {}) return json_response(200, detail)
def fetch_user_info(username: str, social: str) -> Tuple[dict, Optional[Error]]: if social == "id": sql = "select * from users where user_id = %s" elif social == "google": sql = "select * from users where google_id = %s" elif social == "twitter": sql = "select * from users where twitter_name = %s" else: return {}, Error(400, "The field `social` must be id, google or twitter.") with open_pg() as conn: with open_cursor(conn) as cur: cur.execute(sql, (username, )) user = cur.fetchone() if user is None: return {}, Error(404, f"User {username} does not exist.") return dict(user), None
def fetch_google_token(user_id: str) -> Tuple[dict, Optional[Error]]: with open_pg() as conn: with open_cursor(conn) as cur: cur.execute( "select google_id, google_access_token, google_refresh_token, google_token_expires_at " "from users " "where user_id = %s", (user_id, )) token = cur.fetchone() if token is None: return {}, Error(404, f"User {user_id} does not exist.") if token["google_access_token"] is None: return {}, Error( 403, f"User {user_id} is not linked to Google account.") google_token = { "id": token["google_id"], "access_token": decrypt(token.get("google_access_token"), PASSWORD), "refresh_token": decrypt(token.get("google_refresh_token"), PASSWORD), "expires_at": token.get("google_token_expires_at") } return google_token, None
def get_events_today() -> HTTPResponse: user = request.get_cookie("user_id", secret=PASSWORD) if user is None: return Error(400, "parameter `user` is required").response token, err = fetch_google_token(user) if err: return err.response if float(token.get("expires_at")) < datetime.now().timestamp(): token, err = refresh_token_flow(user, token.get("refresh_token")) if err: return err.response today = datetime.now(timezone("Asia/Tokyo")).date() params = { "orderBy": "startTime", "singleEvents": True, "timeMin": datetime(today.year, today.month, today.day, 0, 0, 0, 0, timezone("Asia/Tokyo")).isoformat(), "timeMax": datetime(today.year, today.month, today.day + 1, 0, 0, 0, 0, timezone("Asia/Tokyo")).isoformat() } header = {"Authorization": f"Bearer {token['access_token']}"} req = requests.get(CALENDAR_EP + token["id"] + "/events", params=params, headers=header) if req.status_code != 200: return json_response(req.status_code, req.json()) result = req.json().get("items") body = [] for item in result: location = item.get("location") body.append({ "title": item.get("summary"), "start": datetime_object(item.get("start", {}).get("dateTime")), "end": datetime_object(item.get("end", {}).get("dateTime")), "location": { "name": location }, "link": item.get("htmlLink"), }) if location is not None: map_info, err = get_map(location) if err is None: body[-1]["location"]["photo"] = map_info["photo_url"] body[-1]["location"]["latitude"] = map_info["latitude"] body[-1]["location"]["longitude"] = map_info["longitude"] return json_response(200, body)
def get_twitter_today() -> HTTPResponse: user = request.get_cookie("user_id") # , secret=PASSWORD) q = request.params.q *_, err = get_validation(request) if err: return err.response if user is None: return Error(400, "parameter `user` is required").response twitter, err = fetch_twitter_info(user) if err: return err.response oauth = OAuth1Session(CK, CS, twitter["access_token"], twitter["access_secret"]) res, new_tweets = get_new_tweets(oauth, twitter["latest_id"]) if res.status_code == 401 and res.json()["errors"][0]["code"] == 89: return Error(401, "The registered token is invalid. It may have expired. Please re-login.").response elif res.status_code != 200: return json_response(res.status_code, res.json()) body = update_tweets(user, new_tweets) if q is not None: body = [b for b in body if q in b["text"]] return json_response(200, body)
def refresh_token(user_id: str, token: str, expires: int) -> Tuple[dict, Optional[Error]]: with open_pg() as conn: with open_cursor(conn) as cur: cur.execute( "select google_id " "from users " "where user_id = %s", (user_id, )) if cur.fetchone() is None: return {}, Error(404, f"User {user_id} does not exist.") cur.execute( "update users set " "google_access_token = %s," "google_token_expires_at = %s " "where user_id = %s", (encrypt(token, PASSWORD), datetime.now().timestamp() + expires, user_id)) cur.execute( "select google_id, google_access_token, google_refresh_token, google_token_expires_at " "from users " "where user_id = %s", (user_id, )) user = cur.fetchone() if user is None: return {}, Error( 500, "User information could not be saved due to an unexpected error." ) token = { "id": user["google_id"], "access_token": decrypt(user.get("google_access_token"), PASSWORD), "refresh_token": decrypt(user.get("google_refresh_token"), PASSWORD), "expires_at": user.get("google_token_expires_at") } return token, None
def refresh_token_flow(user: str, token: str) -> Tuple[dict, Optional[Error]]: data = { "refresh_token": token, "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET, "grant_type": "refresh_token" } req = requests.post(REFRESH_EP, data=json.dumps(data).encode("utf-8"), headers=JSON_HEADER) result = req.json() if req.status_code != 200: return {}, Error(req.status_code, result) return refresh_token(user, result["access_token"], result["expires_in"])
def get_map(q: str) -> Tuple[dict, Optional[Error]]: if q == "": return {}, Error(400, "parameter `q` is required.") client = googlemaps.Client(key=MAP_API_KEY) req = client.places(q) if req['status'] != 'OK': return {}, Error(400, f"Google Maps API returns `{req['status']}`") if len(req["results"]) != 1: return {}, Error( 500, f"Invalid number of result ({len(req['results'])}) are returned.") result = req["results"][0] body = {} photos = result.get("photos") if photos: body["photo_url"] = ( f"https://maps.googleapis.com/maps/api/place/photo" f"?maxwidth={PHOTO_WIDTH}" f"&photoreference={photos[0]['photo_reference']}" f"&key={MAP_API_KEY}") body["latitude"] = result["geometry"]["location"]["lat"] body["longitude"] = result["geometry"]["location"]["lng"] return body, None
def post_login() -> HTTPResponse: *_, err = post_validation(request) if err: return err.response body = request.json google = body.get("google") if google: user, err = fetch_user_info(google, "google") if err: return err.response else: twitter = body.get("twitter") if twitter is not None: user, err = fetch_user_info(twitter, "twitter") if err: return err.response else: return Error(400, "Either google or twitter is required.").response return json_response(200, user, user["user_id"])