<Plan>\nWe will update three layers of the application:\n\n1. Backend – In the GetBountiesLeaderboard function (db/db.go), we will extend the raw SQL query to include a new aggregation (daily_bounty) that sums bounty prices for records with a completion_date equal to the current date. We also update the merging loop so that the daily_bounty field is initialized for new leaders and summed appropriately when merging multiple rows.\n\n2. Store – In the fetchLeaders function (src/pages/leaderboard/store.ts) we modify the reduce aggregation to initialize daily_bounty to 0 and sum the daily_bounty property from each LeaderItem. This guarantees that both individual leader objects and the total summary include this new field.\n\n3. Frontend – In the LeaderboardPage and its child components (src/pages/leaderboard/index.tsx, src/pages/leaderboard/top3/index.tsx, and src/pages/leaderboard/leaderboardItem/index.tsx) we update the UI to render the daily bounty metric. In the Summary component call we pass a “daily” prop, and within the Top3 and LeaderboardItem components we add new PriceOuterContainer blocks based on the existing styled components to display the daily bounty using the same DollarConverter formatting.\n\nAdditionally, we create tests for the backend (db/db_test.go), for the store (src/pages/leaderboard/store.test.ts) and integration tests for LeaderboardPage (src/pages/leaderboard/LeaderboardPage.test.tsx) to cover normal, edge-case and merging scenarios.\n</Plan>\n\n<file path=\"db/db.go\" action=\"modify\">\n <change>\n <description>Update GetBountiesLeaderboard query and merging loop to include daily_bounty column aggregation</description>\n <search>\n===\nfunc (db database) GetBountiesLeaderboard() []LeaderData {\n\tms := []BountyLeaderboard{}\n\tvar users = []LeaderData{}\n\n\tdb.db.Raw(`SELECT t1.owner_pubkey, total_bounties_completed, total_sats_earned FROM\n(SELECT assignee as owner_pubkey, \nCOUNT(assignee) as total_bounties_completed\nFrom bounty \nwhere paid=true and assignee != '' \nGROUP BY assignee) t1\n Right Join\n(SELECT assignee as owner_pubkey, \nSUM(CAST(price as integer)) as total_sats_earned\nFrom bounty\nwhere paid=true and assignee != ''\nGROUP BY assignee) t2\nON t1.owner_pubkey = t2.owner_pubkey\nORDER by total_sats_earned DESC`).Find(&ms)\n===\n </search>\n <content>\n===\nfunc (db database) GetBountiesLeaderboard() []LeaderData {\n\tms := []BountyLeaderboard{}\n\tvar users = []LeaderData{}\n\n\tdb.db.Raw(`SELECT t1.owner_pubkey, t1.total_bounties_completed, t2.total_sats_earned, COALESCE(t3.daily_bounty, 0) as daily_bounty FROM\n(SELECT assignee as owner_pubkey, COUNT(assignee) as total_bounties_completed\n From bounty \n where paid=true and assignee != '' \n GROUP BY assignee) t1\nRight Join\n(SELECT assignee as owner_pubkey, SUM(CAST(price as integer)) as total_sats_earned\n From bounty\n where paid=true and assignee != ''\n GROUP BY assignee) t2\nON t1.owner_pubkey = t2.owner_pubkey\nLeft Join \n(SELECT assignee as owner_pubkey, SUM(CAST(price as integer)) as daily_bounty\n From bounty\n where paid=true and assignee != '' and DATE(completion_date) = CURRENT_DATE\n GROUP BY assignee) t3\nON t2.owner_pubkey = t3.owner_pubkey\nORDER by t2.total_sats_earned DESC`).Find(&ms)\n\n\tfor _, val := range ms {\n\t\tvar newLeader = make(map[string]interface{})\n\t\tfound, index := GetLeaderData(users, val.Owner_pubkey)\n\n\t\tif found == -1 {\n\t\t\tnewLeader[\"owner_pubkey\"] = val.Owner_pubkey\n\t\t\tnewLeader[\"total_bounties_completed\"] = val.Total_bounties_completed\n\t\t\tnewLeader[\"total_sats_earned\"] = val.Total_sats_earned\n\t\t\tnewLeader[\"daily_bounty\"] = val.Daily_bounty\n\t\t\tusers = append(users, newLeader)\n\t\t} else {\n\t\t\ttotal_bounties := users[index][\"total_bounties_completed\"].(uint)\n\t\t\ttotal_sats := users[index][\"total_sats_earned\"].(uint)\n\t\t\ttotal_daily := users[index][\"daily_bounty\"].(uint)\n\t\t\tusers[index][\"total_bounties_completed\"] = total_bounties + val.Total_bounties_completed\n\t\t\tusers[index][\"total_sats_earned\"] = total_sats + val.Total_sats_earned\n\t\t\tusers[index][\"daily_bounty\"] = total_daily + val.Daily_bounty\n\t\t}\n\t}\n\treturn users\n}\n===\n </content>\n </change>\n</file>\n\n<file path=\"src/pages/leaderboard/store.ts\" action=\"modify\">\n <change>\n <description>Extend the reduce function to aggregate daily_bounty and initialize the total summary with daily_bounty</description>\n <search>\n===\nthis.total = resp.reduce(\n (partialSum: LeaderItem, assigneeStats: LeaderItem) => {\n partialSum.total_bounties_completed += assigneeStats.total_bounties_completed;\n partialSum.total_sats_earned += assigneeStats.total_sats_earned;\n\n return partialSum;\n },\n { owner_pubkey: '', total_bounties_completed: 0, total_sats_earned: 0 }\n );\n===\n </search>\n <content>\n===\nthis.total = resp.reduce(\n (partialSum: LeaderItem, assigneeStats: LeaderItem) => {\n partialSum.total_bounties_completed += assigneeStats.total_bounties_completed;\n partialSum.total_sats_earned += assigneeStats.total_sats_earned;\n partialSum.daily_bounty = (partialSum.daily_bounty || 0) + (assigneeStats.daily_bounty || 0);\n\n return partialSum;\n },\n { owner_pubkey: '', total_bounties_completed: 0, total_sats_earned: 0, daily_bounty: 0 }\n );\n===\n </content>\n </change>\n</file>\n\n<file path=\"src/pages/leaderboard/index.tsx\" action=\"modify\">\n <change>\n <description>Pass the daily_bounty value into the Summary component in LeaderboardPage</description>\n <search>\n===\n<Summary\n className=\"summary\"\n bounties={leaderboard.total?.total_bounties_completed}\n sats={leaderboard.total?.total_sats_earned}\n />\n===\n </search>\n <content>\n===\n<Summary\n className=\"summary\"\n bounties={leaderboard.total?.total_bounties_completed}\n sats={leaderboard.total?.total_sats_earned}\n daily={leaderboard.total?.daily_bounty}\n />\n===\n </content>\n </change>\n</file>\n\n<file path=\"src/pages/leaderboard/top3/index.tsx\" action=\"modify\">\n <change>\n <description>Update Top3 Item to accept daily_bounty and display it below the sats earned using PriceOuterContainer</description>\n <search>\n===\nItem = ({ owner_pubkey, total_sats_earned, place }: ItemProps) => {\n const { main } = useStores();\n const [person, setPerson] = useState<Person>();\n\n useEffect(() => {\n main.getPersonByPubkey(owner_pubkey).then(setPerson);\n }, [owner_pubkey, main]);\n\n return (\n <ItemContainer\n style={{\n order: place === 1 ? 2 : place === 3 ? 3 : 1\n }}\n >\n <EuiAvatar\n size=\"xl\"\n name={person?.owner_alias || ''}\n imageUrl={person?.img || '/static/person_placeholder.png'}\n />\n <div>\n <EuiText textAlign=\"center\" className=\"name\">\n {!!person?.owner_alias && (\n <Link className=\"name\" to={`/p/${person.uuid}`}>\n {person.owner_alias}\n <MaterialIcon className=\"icon\" icon=\"link\" />\n </Link>\n )}\n </EuiText>\n <PriceOuterContainer\n price_Text_Color={color.primaryColor.P300}\n priceBackground={color.primaryColor.P100}\n >\n <div className=\"Price_inner_Container\">\n <EuiText className=\"Price_Dynamic_Text\">{DollarConverter(total_sats_earned)}</EuiText>\n </div>\n <div className=\"Price_SAT_Container\">\n <EuiText className=\"Price_SAT_Text\">SAT</EuiText>\n </div>\n </PriceOuterContainer>\n </div>\n <Podium place={place} />\n </ItemContainer>\n );\n}\n===\n </search>\n <content>\n===\nItem = ({ owner_pubkey, total_sats_earned, daily_bounty, place }: ItemProps) => {\n const { main } = useStores();\n const [person, setPerson] = useState<Person>();\n\n useEffect(() => {\n main.getPersonByPubkey(owner_pubkey).then(setPerson);\n }, [owner_pubkey, main]);\n\n return (\n <ItemContainer\n style={{\n order: place === 1 ? 2 : place === 3 ? 3 : 1\n }}\n >\n <EuiAvatar\n size=\"xl\"\n name={person?.owner_alias || ''}\n imageUrl={person?.img || '/static/person_placeholder.png'}\n />\n <div>\n <EuiText textAlign=\"center\" className=\"name\">\n {!!person?.owner_alias && (\n <Link className=\"name\" to={`/p/${person.uuid}`}>\n {person.owner_alias}\n <MaterialIcon className=\"icon\" icon=\"link\" />\n </Link>\n )}\n </EuiText>\n <PriceOuterContainer\n price_Text_Color={color.primaryColor.P300}\n priceBackground={color.primaryColor.P100}\n >\n <div className=\"Price_inner_Container\">\n <EuiText className=\"Price_Dynamic_Text\">{DollarConverter(total_sats_earned)}</EuiText>\n </div>\n <div className=\"Price_SAT_Container\">\n <EuiText className=\"Price_SAT_Text\">SAT</EuiText>\n </div>\n </PriceOuterContainer>\n <PriceOuterContainer\n price_Text_Color={color.primaryColor.P300}\n priceBackground={color.primaryColor.P100}\n >\n <div className=\"Price_inner_Container\">\n <EuiText className=\"Price_Dynamic_Text\">{DollarConverter(daily_bounty)}</EuiText>\n </div>\n <div className=\"Price_SAT_Container\">\n <EuiText className=\"Price_SAT_Text\">Daily Bounty</EuiText>\n </div>\n </PriceOuterContainer>\n </div>\n <Podium place={place} />\n </ItemContainer>\n );\n}\n===\n </content>\n </change>\n</file>\n\n<file path=\"src/pages/leaderboard/leaderboardItem/index.tsx\" action=\"modify\">\n <change>\n <description>Add a new PriceOuterContainer to display daily bounty in LeaerboardItem component</description>\n <search>\n===\nLeaerboardItem = ({ owner_pubkey, total_sats_earned, position }: Props) => (\n <ItemContainer>\n <EuiText color={colors.light.text2} className=\"position\">\n #{position}\n </EuiText>\n <UserInfo id={owner_pubkey} />\n <div className=\"userSummary\">\n <div className=\"sats\">\n <PriceOuterContainer\n price_Text_Color={color.primaryColor.P300}\n priceBackground={color.primaryColor.P100}\n >\n <div className=\"Price_inner_Container\">\n <EuiText className=\"Price_Dynamic_Text\">{DollarConverter(total_sats_earned)}</EuiText>\n </div>\n <div className=\"Price_SAT_Container\">\n <EuiText className=\"Price_SAT_Text\">SAT</EuiText>\n </div>\n </PriceOuterContainer>\n </div>\n </div>\n </ItemContainer>\n)\n===\n </search>\n <content>\n===\nLeaerboardItem = ({ owner_pubkey, total_sats_earned, daily_bounty, position }: Props) => (\n <ItemContainer>\n <EuiText color={colors.light.text2} className=\"position\">\n #{position}\n </EuiText>\n <UserInfo id={owner_pubkey} />\n <div className=\"userSummary\">\n <div className=\"sats\">\n <PriceOuterContainer\n price_Text_Color={color.primaryColor.P300}\n priceBackground={color.primaryColor.P100}\n >\n <div className=\"Price_inner_Container\">\n <EuiText className=\"Price_Dynamic_Text\">{DollarConverter(total_sats_earned)}</EuiText>\n </div>\n <div className=\"Price_SAT_Container\">\n <EuiText className=\"Price_SAT_Text\">SAT</EuiText>\n </div>\n </PriceOuterContainer>\n </div>\n <div className=\"daily\">\n <PriceOuterContainer\n price_Text_Color={color.primaryColor.P300}\n priceBackground={color.primaryColor.P100}\n >\n <div className=\"Price_inner_Container\">\n <EuiText className=\"Price_Dynamic_Text\">{DollarConverter(daily_bounty)}</EuiText>\n </div>\n <div className=\"Price_SAT_Container\">\n <EuiText className=\"Price_SAT_Text\">Daily Bounty</EuiText>\n </div>\n </PriceOuterContainer>\n </div>\n </div>\n </ItemContainer>\n)\n===\n </content>\n </change>\n</file>\n\n<file path=\"db/db_test.go\" action=\"create\">\n <change>\n <description>Create unit tests for GetBountiesLeaderboard to verify daily_bounty aggregation</description>\n <content>\n===\npackage db\n\nimport (\n\t\"database/sql\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\t\n\t_ \"github.com/mattn/go-sqlite3\"\n)\n\ntype LeaderData map[string]interface{}\n\nfunc setupTestDB(t *testing.T) *sql.DB {\n\tdb, err := sql.Open(\"sqlite3\", \":memory:\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to open test db: %v\", err)\n\t}\n\t// Create minimal bounty table for testing.\n\t_, err = db.Exec(`\n\tCREATE TABLE bounty (\n\t\tassignee TEXT,\n\t\tprice TEXT,\n\t\tpaid BOOLEAN,\n\t\tcompletion_date DATE\n\t);\n\t`)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create table: %v\", err)\n\t}\n\treturn db\n}\n\nfunc TestGetBountiesLeaderboard_DailyBounty(t *testing.T) {\n\tdbConn := setupTestDB(t)\n\tdefer dbConn.Close()\n\n\t// Insert test data: bounties for today and previous date.\n\ttoday := time.Now().Format(\"2006-01-02\")\n\tyesterday := time.Now().AddDate(0, 0, -1).Format(\"2006-01-02\")\n\n\t// Insert sample records\n\trecords := []struct {\n\t\tassignee string\n\t\tprice string\n\t\tpaid bool\n\t\tcompletion_date string\n\t}{\n\t\t{\"leader1\", \"100\", true, today},\n\t\t{\"leader1\", \"200\", true, yesterday},\n\t\t{\"leader2\", \"150\", true, today},\n\t\t{\"leader2\", \"50\", true, today},\n\t\t{\"leader3\", \"300\", true, yesterday},\n\t}\n\n\tstmt, err := dbConn.Prepare(\"INSERT INTO bounty (assignee, price, paid, completion_date) VALUES (?, ?, ?, ?)\")\n\tif err != nil {\n\t\tt.Fatalf(\"Prepare failed: %v\", err)\n\t}\n\tdefer stmt.Close()\n\n\tfor _, rec := range records {\n\t\t_, err = stmt.Exec(rec.assignee, rec.price, rec.paid, rec.completion_date)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Insert failed: %v\", err)\n\t\t}\n\t}\n\n\t// Initialize our database struct and use our test db connection.\n\ttestDB := database{db: dbConn}\n\n\tleaders := testDB.GetBountiesLeaderboard()\n\n\t// Validate that daily_bounty is calculated only for today's bounties.\n\tfor _, leader := range leaders {\n\t\tswitch leader[\"owner_pubkey\"] {\n\t\tcase \"leader1\":\n\t\t\tif leader[\"daily_bounty\"].(uint) != 100 {\n\t\t\t\tt.Errorf(\"Expected leader1 daily_bounty to be 100, got %d\", leader[\"daily_bounty\"].(uint))\n\t\t\t}\n\t\tcase \"leader2\":\n\t\t\tif leader[\"daily_bounty\"].(uint) != 200 {\n\t\t\t\tt.Errorf(\"Expected leader2 daily_bounty to be 200, got %d\", leader[\"daily_bounty\"].(uint))\n\t\t\t}\n\t\tcase \"leader3\":\n\t\t\tif leader[\"daily_bounty\"].(uint) != 0 {\n\t\t\t\tt.Errorf(\"Expected leader3 daily_bounty to be 0, got %d\", leader[\"daily_bounty\"].(uint))\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestMain(m *testing.M) {\n\tos.Exit(m.Run())\n}\n===\n </content>\n </change>\n</file>\n\n<file path=\"src/pages/leaderboard/store.test.ts\" action=\"create\">\n <change>\n <description>Create unit tests for fetchLeaders to ensure daily_bounty is handled in store total aggregation</description>\n <content>\n===\n// This is a basic Jest test for the store fetchLeaders functionality\nimport { LeaderItem } from './store';\n\ndescribe('fetchLeaders reducer', () => {\n it('should aggregate daily_bounty in the total summary', () => {\n const apiResponse: LeaderItem[] = [\n { owner_pubkey: 'a', total_bounties_completed: 2, total_sats_earned: 300, daily_bounty: 50 },\n { owner_pubkey: 'b', total_bounties_completed: 3, total_sats_earned: 450, daily_bounty: 100 },\n { owner_pubkey: 'c', total_bounties_completed: 1, total_sats_earned: 150, daily_bounty: 0 }\n ];\n\n const total = apiResponse.reduce(\n (partialSum: LeaderItem, assigneeStats: LeaderItem) => {\n partialSum.total_bounties_completed += assigneeStats.total_bounties_completed;\n partialSum.total_sats_earned += assigneeStats.total_sats_earned;\n partialSum.daily_bounty = (partialSum.daily_bounty || 0) + (assigneeStats.daily_bounty || 0);\n return partialSum;\n },\n { owner_pubkey: '', total_bounties_completed: 0, total_sats_earned: 0, daily_bounty: 0 }\n );\n \n expect(total.total_bounties_completed).toBe(6);\n expect(total.total_sats_earned).toBe(900);\n expect(total.daily_bounty).toBe(150);\n });\n\n it('should handle missing daily_bounty values gracefully', () => {\n const apiResponse: any[] = [\n { owner_pubkey: 'a', total_bounties_completed: 2, total_sats_earned: 300 },\n { owner_pubkey: 'b', total_bounties_completed: 3, total_sats_earned: 450, daily_bounty: 100 }\n ];\n\n const total = apiResponse.reduce(\n (partialSum: any, assigneeStats: any) => {\n partialSum.total_bounties_completed += assigneeStats.total_bounties_completed;\n partialSum.total_sats_earned += assigneeStats.total_sats_earned;\n partialSum.daily_bounty = (partialSum.daily_bounty || 0) + (assigneeStats.daily_bounty || 0);\n return partialSum;\n },\n { owner_pubkey: '', total_bounties_completed: 0, total_sats_earned: 0, daily_bounty: 0 }\n );\n \n expect(total.daily_bounty).toBe(100);\n });\n});\n===\n </content>\n </change>\n</file>\n\n<file path=\"src/pages/leaderboard/LeaderboardPage.test.tsx\" action=\"create\">\n <change>\n <description>Create integration tests for LeaderboardPage ensuring that daily_bounty UI elements render when provided in the store</description>\n <content>\n===\nimport React from 'react';\nimport { render } from '@testing-library/react';\nimport LeaderboardPage from './index';\nimport { Provider } from 'mobx-react';\nimport { observable } from 'mobx';\n\nconst dummyLeaderboardStore = observable({\n isLoading: false,\n total: {\n total_bounties_completed: 10,\n total_sats_earned: 1000,\n daily_bounty: 250\n },\n top3: [\n { owner_pubkey: 'a', total_sats_earned: 500, daily_bounty: 150 },\n { owner_pubkey: 'b', total_sats_earned: 300, daily_bounty: 50 },\n { owner_pubkey: 'c', total_sats_earned: 200, daily_bounty: 50 },\n ],\n others: []\n});\n\nconst dummyStores = {\n leaderboard: dummyLeaderboardStore,\n main: {\n getPersonByPubkey: jest.fn().mockResolvedValue({ owner_alias: 'TestUser', img: '', uuid: '123' }),\n getUserAvatarPlaceholder: jest.fn().mockReturnValue('/static/person_placeholder.png')\n }\n};\n\ndescribe('LeaderboardPage', () => {\n it('renders daily bounty elements in Summary and Top3 items', () => {\n const { getByText } = render(\n <Provider {...dummyStores}>\n <LeaderboardPage />\n </Provider>\n );\n\n // Check for total daily bounty in Summary component (assumed label \"Daily Bounty\")\n expect(getByText(/Daily Bounty/i)).toBeTruthy();\n\n // Check that one of the Top3 items displays the daily bounty value (using DollarConverter format, e.g. \"150\")\n expect(getByText(/150/)).toBeTruthy();\n });\n\n it('renders gracefully when daily_bounty is 0', () => {\n dummyLeaderboardStore.total.daily_bounty = 0;\n const { queryByText } = render(\n <Provider {...dummyStores}>\n <LeaderboardPage />\n </Provider>\n );\n // The label may still render or show 0; adjust this test according to desired behavior.\n expect(queryByText(/0/)).toBeTruthy();\n });\n});\n===\n </content>\n </change>\n</file>
Stakwork Run: https://jobs.stakwork.com/admin/projects/101144927
One shot:
Multi-step: