From c8fb5af44d8150972cde4ed60267e00140cac40b Mon Sep 17 00:00:00 2001 From: Sonny Date: Mon, 7 Oct 2024 02:07:39 +0200 Subject: [PATCH] feat: add user last seen field --- app/controllers/admin_controller.ts | 7 ++-- app/middleware/silent_auth_middleware.ts | 9 +++++ .../update_user_last_seen_middleware.ts | 16 +++++++++ app/models/user.ts | 7 ++++ ...8257868871_update_user_last_seens_table.ts | 15 +++++++++ inertia/components/common/table.tsx | 11 ++++++- inertia/i18n/locales/en/admin.json | 16 ++++----- inertia/i18n/locales/fr/admin.json | 16 ++++----- inertia/pages/admin/dashboard.tsx | 33 ++++++++++++------- inertia/types/app.d.ts | 7 ++-- start/kernel.ts | 2 ++ 11 files changed, 103 insertions(+), 36 deletions(-) create mode 100644 app/middleware/silent_auth_middleware.ts create mode 100644 app/middleware/update_user_last_seen_middleware.ts create mode 100644 database/migrations/1728257868871_update_user_last_seens_table.ts diff --git a/app/controllers/admin_controller.ts b/app/controllers/admin_controller.ts index 022920d..060f382 100644 --- a/app/controllers/admin_controller.ts +++ b/app/controllers/admin_controller.ts @@ -16,10 +16,9 @@ class UserWithRelationCountDto { isAdmin: this.user.isAdmin, createdAt: this.user.createdAt, updatedAt: this.user.updatedAt, - count: { - link: Number(this.user.$extras.totalLinks), - collection: Number(this.user.$extras.totalCollections), - }, + lastSeenAt: this.user.lastSeenAt, + linksCount: Number(this.user.$extras.totalLinks), + collectionsCount: Number(this.user.$extras.totalCollections), }); } diff --git a/app/middleware/silent_auth_middleware.ts b/app/middleware/silent_auth_middleware.ts new file mode 100644 index 0000000..cda4883 --- /dev/null +++ b/app/middleware/silent_auth_middleware.ts @@ -0,0 +1,9 @@ +import type { HttpContext } from '@adonisjs/core/http'; +import type { NextFn } from '@adonisjs/core/types/http'; + +export default class SilentAuthMiddleware { + async handle(ctx: HttpContext, next: NextFn) { + await ctx.auth.check(); + return next(); + } +} diff --git a/app/middleware/update_user_last_seen_middleware.ts b/app/middleware/update_user_last_seen_middleware.ts new file mode 100644 index 0000000..6da515e --- /dev/null +++ b/app/middleware/update_user_last_seen_middleware.ts @@ -0,0 +1,16 @@ +import type { HttpContext } from '@adonisjs/core/http'; +import type { NextFn } from '@adonisjs/core/types/http'; +import { DateTime } from 'luxon'; + +export default class UpdateUserLastSeenMiddleware { + async handle(ctx: HttpContext, next: NextFn) { + const user = ctx.auth.user; + if (user) { + user.lastSeenAt = DateTime.local(); + await user.save(); + } + + const output = await next(); + return output; + } +} diff --git a/app/models/user.ts b/app/models/user.ts index 8afe92b..ebc8ce7 100644 --- a/app/models/user.ts +++ b/app/models/user.ts @@ -4,6 +4,7 @@ import type { GoogleToken } from '@adonisjs/ally/types'; import { column, computed, hasMany } from '@adonisjs/lucid/orm'; import type { HasMany } from '@adonisjs/lucid/types/relations'; import AppBaseModel from './app_base_model.js'; +import { DateTime } from 'luxon'; export default class User extends AppBaseModel { @column() @@ -44,4 +45,10 @@ export default class User extends AppBaseModel { get fullname() { return this.nickName || this.name; } + + @column.dateTime({ + autoCreate: true, + autoUpdate: true, + }) + declare lastSeenAt: DateTime; } diff --git a/database/migrations/1728257868871_update_user_last_seens_table.ts b/database/migrations/1728257868871_update_user_last_seens_table.ts new file mode 100644 index 0000000..70ff582 --- /dev/null +++ b/database/migrations/1728257868871_update_user_last_seens_table.ts @@ -0,0 +1,15 @@ +import { BaseSchema } from '@adonisjs/lucid/schema'; + +export default class extends BaseSchema { + protected tableName = 'users'; + + async up() { + this.schema.alterTable(this.tableName, (table) => { + table.timestamp('last_seen_at'); + }); + } + + async down() { + this.schema.dropTable(this.tableName); + } +} diff --git a/inertia/components/common/table.tsx b/inertia/components/common/table.tsx index 0e3170b..3948a5c 100644 --- a/inertia/components/common/table.tsx +++ b/inertia/components/common/table.tsx @@ -6,6 +6,7 @@ import { getPaginationRowModel, getSortedRowModel, PaginationState, + SortingState, useReactTable, } from '@tanstack/react-table'; import { useState } from 'react'; @@ -50,13 +51,19 @@ const Resizer = styled.div<{ isResizing: boolean }>( type TableProps = { columns: ColumnDef[]; data: T[]; + defaultSorting?: SortingState; }; -export default function Table({ columns, data }: TableProps) { +export default function Table({ + columns, + data, + defaultSorting = [], +}: TableProps) { const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10, }); + const [sorting, setSorting] = useState(defaultSorting); const table = useReactTable({ data, @@ -65,11 +72,13 @@ export default function Table({ columns, data }: TableProps) { columnResizeMode: 'onChange', state: { pagination, + sorting, }, onPaginationChange: setPagination, getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), getSortedRowModel: getSortedRowModel(), + onSortingChange: setSorting, debugTable: true, }); diff --git a/inertia/i18n/locales/en/admin.json b/inertia/i18n/locales/en/admin.json index 56fd234..33ac3bc 100644 --- a/inertia/i18n/locales/en/admin.json +++ b/inertia/i18n/locales/en/admin.json @@ -1,9 +1,9 @@ { - "role": "Role", - "created_at": "Created at", - "updated_at": "Updated at", - "admin": "Administrator", - "user": "User", - "users": "Users", - "stats": "Statistics" -} + "role": "Role", + "created_at": "Created at", + "last_seen_at": "Last seen at", + "admin": "Administrator", + "user": "User", + "users": "Users", + "stats": "Statistics" +} \ No newline at end of file diff --git a/inertia/i18n/locales/fr/admin.json b/inertia/i18n/locales/fr/admin.json index fc15ec6..0811c1e 100644 --- a/inertia/i18n/locales/fr/admin.json +++ b/inertia/i18n/locales/fr/admin.json @@ -1,9 +1,9 @@ { - "role": "Rôle", - "created_at": "Création", - "updated_at": "Mise à jour", - "admin": "Administrateur", - "user": "Utilisateur", - "users": "Utilisateurs", - "stats": "Statistiques" -} + "role": "Rôle", + "created_at": "Création", + "last_seen_at": "Dernière connexion", + "admin": "Administrateur", + "user": "Utilisateur", + "users": "Utilisateurs", + "stats": "Statistiques" +} \ No newline at end of file diff --git a/inertia/pages/admin/dashboard.tsx b/inertia/pages/admin/dashboard.tsx index 99e3b2d..9324de5 100644 --- a/inertia/pages/admin/dashboard.tsx +++ b/inertia/pages/admin/dashboard.tsx @@ -51,7 +51,7 @@ function AdminDashboard({ [ { accessorKey: 'id', - header: ( + header: () => ( <> # ({users.length}) @@ -69,8 +69,8 @@ function AdminDashboard({ cell: (info) => info.getValue(), }, { - accessorKey: 'count', - header: ( + accessorKey: 'collectionsCount', + header: () => ( <> {t('common:collection.collections', { count: totalCollections })}{' '} @@ -78,17 +78,17 @@ function AdminDashboard({ ), - cell: (info) => (info.getValue() as any)?.collection, + cell: (info) => info.getValue(), }, { - accessorKey: 'count', - header: ( + accessorKey: 'linksCount', + header: () => ( <> {t('common:link.links', { count: totalLinks })}{' '} ({totalLinks}) ), - cell: (info: any) => info.getValue()?.link, + cell: (info: any) => info.getValue(), }, { accessorKey: 'isAdmin', @@ -110,12 +110,23 @@ function AdminDashboard({ cell: RenderDateCell, }, { - accessorKey: 'updatedAt', - header: t('admin:updated_at'), + accessorKey: 'lastSeenAt', + header: t('admin:last_seen_at'), cell: RenderDateCell, }, - ] as ColumnDef[], + ] satisfies ColumnDef[], [] ); - return ; + return ( +
+ ); } diff --git a/inertia/types/app.d.ts b/inertia/types/app.d.ts index 2f9ce2d..ddeb579 100644 --- a/inertia/types/app.d.ts +++ b/inertia/types/app.d.ts @@ -24,10 +24,9 @@ type UserWithRelationCount = CommonBase & { fullname: string; avatarUrl: string; isAdmin: string; - count: { - link: number; - collection: number; - }; + linksCount: number; + collectionsCount: number; + lastSeenAt: string; }; type Link = CommonBase & { diff --git a/start/kernel.ts b/start/kernel.ts index 9e072b7..e3c9522 100644 --- a/start/kernel.ts +++ b/start/kernel.ts @@ -41,6 +41,8 @@ router.use([ () => import('@adonisjs/session/session_middleware'), () => import('@adonisjs/shield/shield_middleware'), () => import('@adonisjs/auth/initialize_auth_middleware'), + () => import('#middleware/silent_auth_middleware'), + () => import('#middleware/update_user_last_seen_middleware'), ]); /**