From 11fac4664fba758de7b16883a26f3d8c02322196 Mon Sep 17 00:00:00 2001 From: Marius Georgescu Date: Tue, 6 Jan 2026 19:35:55 +0200 Subject: [PATCH] feat(query-api): add Count and Frequency endpoints for Profiles and Promotions APIs - Add GET /profiles/count with profile_type filter - Add GET /profiles/frequency returning counts by ProfileType - Add GET /promotions/count with profile, belt, achieved_by, awarded_by filters - Add GET /promotions/frequency returning counts by BJJBelt - Add getProfileTypeTotals to Query/Projected.hs and Query/Live.hs - Add getPromotionBeltTotals to Query/Projected.hs and Query/Live.hs - All endpoints support liveprojection flag --- CHANGELOG.md | 20 +++++++ src/exe/query-api/Query/Live.hs | 14 +++++ src/exe/query-api/Query/Projected.hs | 22 +++++++ src/exe/query-api/RestAPI.hs | 88 +++++++++++++++++++++++++++- 4 files changed, 142 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 268b330..a7b3cea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,26 @@ # Revision history for Decentralized-Belt-System +## 0.2.3.0 -- 2025-01-06 + +### Query API Enhancements + +**Extended Profiles and Promotions APIs with Count and Frequency Endpoints** + +#### Profiles API Extensions +- **GET /profiles/count**: Get total count of profiles with optional `profile_type` filter +- **GET /profiles/frequency**: Get profile counts grouped by type (`Practitioner`, `Organization`) + +#### Promotions API Extensions +- **GET /promotions/count**: Get total count of promotions with optional filters (`profile`, `belt`, `achieved_by`, `awarded_by`) +- **GET /promotions/frequency**: Get promotion counts grouped by belt type + +#### Implementation Details +- Added `getProfileTypeTotals` to `Query/Projected.hs` and `Query/Live.hs` +- Added `getPromotionBeltTotals` to `Query/Projected.hs` and `Query/Live.hs` +- All new endpoints support `liveprojection` query flag for live blockchain vs projected database queries +- Follows the same pattern as existing Belts API Count and Frequency endpoints + ## 0.2.2.0 -- 2024-12-21 ### API Architecture Split diff --git a/src/exe/query-api/Query/Live.hs b/src/exe/query-api/Query/Live.hs index ed632c6..0ad6c44 100644 --- a/src/exe/query-api/Query/Live.hs +++ b/src/exe/query-api/Query/Live.hs @@ -160,3 +160,17 @@ getBeltTotals = do let allBelts = Prelude.map rankBelt allRanks let beltTotals = toOccurList . fromList $ allBelts return beltTotals + +getProfileTypeTotals :: (MonadReader QueryAppContext m, MonadIO m) => m [(ProfileType, Int)] +getProfileTypeTotals = do + allProfiles <- getProfiles Nothing Nothing Nothing + let allTypes = Prelude.map profileType allProfiles + let typeTotals = toOccurList . fromList $ allTypes + return typeTotals + +getPromotionBeltTotals :: (MonadReader QueryAppContext m, MonadIO m) => m [(BJJBelt, Int)] +getPromotionBeltTotals = do + allPromotions <- getPromotions Nothing Nothing Nothing + let allBelts = Prelude.map promotionBelt allPromotions + let beltTotals = toOccurList . fromList $ allBelts + return beltTotals diff --git a/src/exe/query-api/Query/Projected.hs b/src/exe/query-api/Query/Projected.hs index f364d14..e453c77 100644 --- a/src/exe/query-api/Query/Projected.hs +++ b/src/exe/query-api/Query/Projected.hs @@ -273,3 +273,25 @@ getBeltTotals = do let belts = Prelude.map unValue rows pure (toOccurList . fromList $ belts) ) pool + +getProfileTypeTotals :: (MonadIO m, MonadReader QueryAppContext m) => m [(ProfileType, Int)] +getProfileTypeTotals = do + pool <- asks pgPool + liftIO $ runSqlPool (do + rows <- select $ do + pp <- from $ table @ProfileProjection + pure (pp ^. ProfileProjectionProfileType) + let profileTypes = Prelude.map unValue rows + pure (toOccurList . fromList $ profileTypes) + ) pool + +getPromotionBeltTotals :: (MonadIO m, MonadReader QueryAppContext m) => m [(BJJBelt, Int)] +getPromotionBeltTotals = do + pool <- asks pgPool + liftIO $ runSqlPool (do + rows <- select $ do + pr <- from $ table @PromotionProjection + pure (pr ^. PromotionProjectionPromotionBelt) + let belts = Prelude.map unValue rows + pure (toOccurList . fromList $ belts) + ) pool diff --git a/src/exe/query-api/RestAPI.hs b/src/exe/query-api/RestAPI.hs index 20eb755..32eabb2 100644 --- a/src/exe/query-api/RestAPI.hs +++ b/src/exe/query-api/RestAPI.hs @@ -87,6 +87,25 @@ type Profiles = :> QueryFlag "liveprojection" :> Get '[JSON] [Profile] ) + :<|> + -- Get profiles count endpoint + ( Summary "Get Profiles Count" + :> Description "Get Profiles Count" + :> "profiles" + :> "count" + :> QueryParam' '[Optional] "profile_type" ProfileType + :> QueryFlag "liveprojection" + :> Get '[JSON] Int + ) + :<|> + -- Get profiles frequency endpoint + ( Summary "Get Profiles Frequency" + :> Description "Get Profiles Frequency by Type" + :> "profiles" + :> QueryFlag "liveprojection" + :> "frequency" + :> Get '[JSON] [(ProfileType, Int)] + ) -- Health handlers moved to WebAPI.Health @@ -133,8 +152,23 @@ handleGetProfiles limit offset profileRefs profileType orderBy sortOrder livePro then L.getProfiles limitOffset profileFilter order else P.getProfiles limitOffset profileFilter order +handleGetProfilesCount :: Maybe ProfileType -> Bool -> QueryAppMonad Int +handleGetProfilesCount profileType liveProjection = + if liveProjection + then L.getProfilesCount profileType + else P.getProfilesCount profileType + +handleGetProfilesFrequency :: Bool -> QueryAppMonad [(ProfileType, Int)] +handleGetProfilesFrequency liveProjection = + if liveProjection then L.getProfileTypeTotals else P.getProfileTypeTotals + profilesServer :: ServerT Profiles QueryAppMonad -profilesServer = handleGetPractitionerProfile :<|> handleGetOrganizationProfile :<|> handleGetProfiles +profilesServer = + handleGetPractitionerProfile + :<|> handleGetOrganizationProfile + :<|> handleGetProfiles + :<|> handleGetProfilesCount + :<|> handleGetProfilesFrequency ------------------------------------------------------------------------------------------------ @@ -158,6 +192,28 @@ type Promotions = :> QueryFlag "liveprojection" :> Get '[JSON] [Promotion] ) + :<|> + -- Get promotions count endpoint + ( Summary "Get Promotions Count" + :> Description "Get Promotions Count" + :> "promotions" + :> "count" + :> QueryParams "profile" ProfileRefAC + :> QueryParams "belt" BJJBelt + :> QueryParams "achieved_by" ProfileRefAC + :> QueryParams "awarded_by" ProfileRefAC + :> QueryFlag "liveprojection" + :> Get '[JSON] Int + ) + :<|> + -- Get promotions frequency endpoint + ( Summary "Get Promotions Frequency" + :> Description "Get Promotions Frequency by Belt" + :> "promotions" + :> QueryFlag "liveprojection" + :> "frequency" + :> Get '[JSON] [(BJJBelt, Int)] + ) handleGetPromotions :: Maybe Int -> @@ -193,8 +249,36 @@ handleGetPromotions limit offset profileRefs beltRefs achievedByRefs awardedByRe then L.getPromotions limitOffset promotionsFilter order else P.getPromotions limitOffset promotionsFilter order +handleGetPromotionsCount :: + [ProfileRefAC] -> + [BJJBelt] -> + [ProfileRefAC] -> + [ProfileRefAC] -> + Bool -> + QueryAppMonad Int +handleGetPromotionsCount profileRefs beltRefs achievedByRefs awardedByRefs liveProjection = do + let promotionsFilter = + Just $ + C.PromotionFilter + { C.promotionFilterId = if Prelude.null profileRefs then Nothing else Just profileRefs, + C.promotionFilterBelt = if Prelude.null beltRefs then Nothing else Just beltRefs, + C.promotionFilterAchievedByProfileId = if Prelude.null achievedByRefs then Nothing else Just achievedByRefs, + C.promotionFilterAwardedByProfileId = if Prelude.null awardedByRefs then Nothing else Just awardedByRefs, + C.promotionFilterAchievementDateInterval = (Nothing, Nothing) + } + if liveProjection + then L.getPromotionsCount promotionsFilter + else P.getPromotionsCount promotionsFilter + +handleGetPromotionsFrequency :: Bool -> QueryAppMonad [(BJJBelt, Int)] +handleGetPromotionsFrequency liveProjection = + if liveProjection then L.getPromotionBeltTotals else P.getPromotionBeltTotals + promotionsServer :: ServerT Promotions QueryAppMonad -promotionsServer = handleGetPromotions +promotionsServer = + handleGetPromotions + :<|> handleGetPromotionsCount + :<|> handleGetPromotionsFrequency ------------------------------------------------------------------------------------------------