mirror of
https://github.com/Sonny93/my-links.git
synced 2025-12-08 22:53:25 +00:00
feat: add user last seen field
This commit is contained in:
@@ -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),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
9
app/middleware/silent_auth_middleware.ts
Normal file
9
app/middleware/silent_auth_middleware.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
16
app/middleware/update_user_last_seen_middleware.ts
Normal file
16
app/middleware/update_user_last_seen_middleware.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
7
inertia/types/app.d.ts
vendored
7
inertia/types/app.d.ts
vendored
@@ -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 & {
|
||||
|
||||
@@ -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'),
|
||||
]);
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user