Skip to content

Commit

Permalink
feat: add pagination for notification api
Browse files Browse the repository at this point in the history
  • Loading branch information
100gle committed Oct 11, 2023
1 parent c6dd181 commit b63e80e
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 37 deletions.
24 changes: 22 additions & 2 deletions backend/src/module/api/notification.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends, HTTPException, Query

from module.conf.config import settings
from module.notification import Notifier
Expand All @@ -14,8 +14,25 @@ def get_notifier():
)


@router.get("/total")
async def get_total_notification(notifier: Notifier = Depends(get_notifier)):
cursor = notifier.q.conn.cursor()
stmt = """SELECT COUNT(*) FROM Queue WHERE status=0"""

try:
total = cursor.execute(stmt).fetchone()[0]
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

return dict(code=0, msg="success", data=dict(total=total))


@router.get("")
async def get_notification(notifier: Notifier = Depends(get_notifier)):
async def get_notification(
page: int = Query(1, ge=1),
limit: int = Query(20, ge=10, le=20, description="max limit is 20 per page"),
notifier: Notifier = Depends(get_notifier),
):
cursor = notifier.q.conn.cursor()
stmt = r"""
SELECT message_id, data, in_time as datetime, status as has_read
Expand All @@ -24,6 +41,9 @@ async def get_notification(notifier: Notifier = Depends(get_notifier)):
ORDER BY in_time DESC
"""

offset = (page - 1) * limit
stmt += f"LIMIT {limit} OFFSET {offset}"

try:
rows = cursor.execute(stmt).fetchall()
except Exception as e:
Expand Down
39 changes: 38 additions & 1 deletion backend/src/test/api/test_notificaion.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,43 @@ class TestNotificationAPI:
def setup_class(cls):
cls.content = NotificationContent(content="fooo")

@pytest.mark.asyncio
async def test_get_total_notification(
self, aclient: AsyncClient, mocker: MockerFixture
):
mocked_db = sqlite3.connect(":memory:")
mocked_db.execute(
"CREATE TABLE Queue (message_id TEXT, data TEXT, in_time INT, status INT)"
)
mocked_db.execute(
"INSERT INTO Queue (message_id, data, in_time, status) VALUES (?, ?, ?, ?)",
("foo", "bar", 123, 0),
)
mocked_db.commit()

m = mocker.patch.object(Notifier, "q", return_value=object())
m.conn = mocked_db

resp = await aclient.get("/v1/notification/total")
assert resp.status_code == 200
assert resp.json() == dict(code=0, msg="success", data=dict(total=1))

@pytest.mark.asyncio
async def test_get_total_notification_with_exception(
self, aclient: AsyncClient, mocker: MockerFixture
):
mocked_conn = mocker.MagicMock()
mocked_cursor = mocker.MagicMock()
mocked_conn.cursor.return_value = mocked_cursor
mocked_cursor.execute.side_effect = Exception("unknown error")

m = mocker.patch.object(Notifier, "q", return_value=object())
m.conn = mocked_conn

resp = await aclient.get("/v1/notification/total")
assert resp.status_code == 500
assert resp.json() == dict(detail="unknown error")

@pytest.mark.asyncio
async def test_get_notification(self, aclient: AsyncClient, mocker: MockerFixture):
mocked_db = sqlite3.connect(":memory:")
Expand All @@ -27,7 +64,7 @@ async def test_get_notification(self, aclient: AsyncClient, mocker: MockerFixtur
m = mocker.patch.object(Notifier, "q", return_value=object())
m.conn = mocked_db

resp = await aclient.get("/v1/notification")
resp = await aclient.get("/v1/notification", params={"page": 1, "limit": 20})
assert resp.status_code == 200
assert resp.json() == dict(
code=0,
Expand Down
14 changes: 12 additions & 2 deletions webui/src/api/notification.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
export const apiNotification = {
async get() {
const { data } = await axios.get('api/v1/notification');
async getTotal() {
const { data } = await axios.get('api/v1/notification/total');
return data;
},

async get({ page, limit }: { page?: number; limit?: number }) {
const { data } = await axios.get('api/v1/notification', {
params: {
page,
limit,
},
});
return data;
},

Expand Down
2 changes: 1 addition & 1 deletion webui/src/components/ab-notification.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ onUnmounted(() => {
</template>
<NList v-if="notifications.length > 0" hoverable clickable>
<template v-for="m in notifications" :key="m.id">
<NListItem @click="m.hasRead = !m.hasRead">
<NListItem @click="m.has_read = !m.has_read">
<ab-notification-item v-bind="m"></ab-notification-item>
</NListItem>
</template>
Expand Down
65 changes: 59 additions & 6 deletions webui/src/hooks/useNotificaiton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,29 @@ import type { Notification } from '#/notification';
export const useNotification = createSharedComposable(() => {
// TODO: add auth
// const { auth } = useAuth();

const notifications = ref<Notification[]>([]);
const total = ref(0);

function getTotal() {
const { execute, onResult } = useApi(apiNotification.getTotal, {});

onResult((res) => {
total.value = res.data.total;
});
execute();
}

function getNotification() {
const { execute, onResult } = useApi(apiNotification.get, {});

onResult((res) => {
total.value = res.data.total;
notifications.value = res.data.messages.map((item, index) => {
notifications.value = res.data.messages.map((item) => {
const { content } = JSON.parse(item.data);
const value = {
key: index,
id: item.message_id,
title: 'AutoBangumi',
hasRead: false,
has_read: Boolean(item.has_read),
datetime: `${item.datetime}`,
content,
};
Expand All @@ -29,11 +37,14 @@ export const useNotification = createSharedComposable(() => {
// if (auth.value !== '') {
// execute();
// }
execute();
execute({ page: 1, limit: 10 });
}

const { pause: offUpdate, resume: onUpdate } = useIntervalFn(
getNotification,
() => {
getTotal();
getNotification();
},
5000,
{
immediate: false,
Expand All @@ -48,3 +59,45 @@ export const useNotification = createSharedComposable(() => {
offUpdate,
};
});

export const useNotificationPage = createSharedComposable(() => {
// TODO: add auth
// const { auth } = useAuth();

const { total } = useNotification();
const { execute, onResult } = useApi(apiNotification.get, {});
const notifications = ref<Notification[]>([]);
const page = ref(1);
const limit = ref(10);

onResult((res) => {
notifications.value = res.data.messages.map((item) => {
const { content } = JSON.parse(item.data);
const value = {
id: item.message_id,
title: 'AutoBangumi',
datetime: `${item.datetime}`,
content,
};
return value;
});
});

// TODO: add auth
// if (auth.value !== '') {
// execute();
// }

watch([page, limit], () => {
execute({ page: page.value, limit: limit.value });
});

execute({ page: page.value, limit: limit.value });

return {
total,
page,
limit,
notifications,
};
});
48 changes: 24 additions & 24 deletions webui/src/pages/index/notification.vue
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
<script setup lang="ts">
import { NDataTable } from 'naive-ui';
import type { PaginationProps } from 'naive-ui';
import { NDataTable, NPagination } from 'naive-ui';
definePage({
name: 'Notification',
});
const { notifications } = useNotification();
const router = useRouter();
const { total, page, limit, notifications } = useNotificationPage();
const columns = ref<{ title: string; key: string }[]>([]);
watchEffect(() => {
if (notifications.value && notifications.value.length > 0) {
columns.value = Object.keys(omit(notifications.value[0], ['hasRead'])).map(
columns.value = Object.keys(omit(notifications.value[0], ['has_read'])).map(
(key) => ({
title: key,
key,
Expand All @@ -20,29 +21,28 @@ watchEffect(() => {
}
});
const pagination = reactive<PaginationProps>({
page: 1,
pageSize: 20,
showSizePicker: false,
onChange: (page: number) => {
pagination.page = page;
},
onUpdatePageSize: (pageSize: number) => {
pagination.pageSize = pageSize;
pagination.page = 1;
},
});
function onUpdatePage(newPage: number) {
router.push({ query: { page: newPage } });
page.value = newPage;
}
</script>

<template>
<ab-container :title="$t('notification.title')" mt-12px>
<NDataTable
:columns="columns"
:data="notifications"
:pagination="pagination"
:row-key="(rowData) => rowData.id"
:max-height="600"
virtual-scroll
></NDataTable>
<div fx-cer flex-col justify-center space-y-4>
<NDataTable
:columns="columns"
:data="notifications"
:row-key="(rowData) => rowData.id"
:max-height="600"
virtual-scroll
/>
<NPagination
:page="page"
:item-count="total"
:page-size="limit"
@update:page="onUpdatePage"
/>
</div>
</ab-container>
</template>
1 change: 1 addition & 0 deletions webui/types/dts/auto-imports.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ declare global {
const useNavigatorLanguage: typeof import('@vueuse/core')['useNavigatorLanguage']
const useNetwork: typeof import('@vueuse/core')['useNetwork']
const useNotification: typeof import('../../src/hooks/useNotificaiton')['useNotification']
const useNotificationPage: typeof import('../../src/hooks/useNotificaiton')['useNotificationPage']
const useNow: typeof import('@vueuse/core')['useNow']
const useObjectUrl: typeof import('@vueuse/core')['useObjectUrl']
const useOffsetPagination: typeof import('@vueuse/core')['useOffsetPagination']
Expand Down
2 changes: 1 addition & 1 deletion webui/types/notification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ export interface Notification {
title: string;
content: string;
datetime: string;
hasRead: boolean;
has_read: boolean;
}

0 comments on commit b63e80e

Please sign in to comment.