feat: add user last seen field

This commit is contained in:
Sonny
2024-10-07 02:07:39 +02:00
parent 24cea2b0b2
commit c8fb5af44d
11 changed files with 103 additions and 36 deletions

View File

@@ -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),
});
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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<T> = {
columns: ColumnDef<T>[];
data: T[];
defaultSorting?: SortingState;
};
export default function Table<T>({ columns, data }: TableProps<T>) {
export default function Table<T>({
columns,
data,
defaultSorting = [],
}: TableProps<T>) {
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
pageSize: 10,
});
const [sorting, setSorting] = useState<SortingState>(defaultSorting);
const table = useReactTable({
data,
@@ -65,11 +72,13 @@ export default function Table<T>({ columns, data }: TableProps<T>) {
columnResizeMode: 'onChange',
state: {
pagination,
sorting,
},
onPaginationChange: setPagination,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
onSortingChange: setSorting,
debugTable: true,
});

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -51,7 +51,7 @@ function AdminDashboard({
[
{
accessorKey: 'id',
header: (
header: () => (
<>
# <span css={{ color: theme.colors.grey }}>({users.length})</span>
</>
@@ -69,8 +69,8 @@ function AdminDashboard({
cell: (info) => info.getValue(),
},
{
accessorKey: 'count',
header: (
accessorKey: 'collectionsCount',
header: () => (
<>
{t('common:collection.collections', { count: totalCollections })}{' '}
<span css={{ color: theme.colors.grey }}>
@@ -78,17 +78,17 @@ function AdminDashboard({
</span>
</>
),
cell: (info) => (info.getValue() as any)?.collection,
cell: (info) => info.getValue(),
},
{
accessorKey: 'count',
header: (
accessorKey: 'linksCount',
header: () => (
<>
{t('common:link.links', { count: totalLinks })}{' '}
<span css={{ color: theme.colors.grey }}>({totalLinks})</span>
</>
),
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<UserWithRelationCount>[],
] satisfies ColumnDef<UserWithRelationCount>[],
[]
);
return <Table columns={columns} data={users} />;
return (
<Table
columns={columns}
data={users}
defaultSorting={[
{
id: 'lastSeenAt',
desc: true,
},
]}
/>
);
}

View File

@@ -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 & {

View File

@@ -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'),
]);
/**