mirror of
https://github.com/Sonny93/my-links.git
synced 2025-12-09 15:05:35 +00:00
feat: add user last seen field
This commit is contained in:
@@ -16,10 +16,9 @@ class UserWithRelationCountDto {
|
|||||||
isAdmin: this.user.isAdmin,
|
isAdmin: this.user.isAdmin,
|
||||||
createdAt: this.user.createdAt,
|
createdAt: this.user.createdAt,
|
||||||
updatedAt: this.user.updatedAt,
|
updatedAt: this.user.updatedAt,
|
||||||
count: {
|
lastSeenAt: this.user.lastSeenAt,
|
||||||
link: Number(this.user.$extras.totalLinks),
|
linksCount: Number(this.user.$extras.totalLinks),
|
||||||
collection: Number(this.user.$extras.totalCollections),
|
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 { column, computed, hasMany } from '@adonisjs/lucid/orm';
|
||||||
import type { HasMany } from '@adonisjs/lucid/types/relations';
|
import type { HasMany } from '@adonisjs/lucid/types/relations';
|
||||||
import AppBaseModel from './app_base_model.js';
|
import AppBaseModel from './app_base_model.js';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
|
||||||
export default class User extends AppBaseModel {
|
export default class User extends AppBaseModel {
|
||||||
@column()
|
@column()
|
||||||
@@ -44,4 +45,10 @@ export default class User extends AppBaseModel {
|
|||||||
get fullname() {
|
get fullname() {
|
||||||
return this.nickName || this.name;
|
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,
|
getPaginationRowModel,
|
||||||
getSortedRowModel,
|
getSortedRowModel,
|
||||||
PaginationState,
|
PaginationState,
|
||||||
|
SortingState,
|
||||||
useReactTable,
|
useReactTable,
|
||||||
} from '@tanstack/react-table';
|
} from '@tanstack/react-table';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@@ -50,13 +51,19 @@ const Resizer = styled.div<{ isResizing: boolean }>(
|
|||||||
type TableProps<T> = {
|
type TableProps<T> = {
|
||||||
columns: ColumnDef<T>[];
|
columns: ColumnDef<T>[];
|
||||||
data: 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>({
|
const [pagination, setPagination] = useState<PaginationState>({
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
});
|
});
|
||||||
|
const [sorting, setSorting] = useState<SortingState>(defaultSorting);
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data,
|
data,
|
||||||
@@ -65,11 +72,13 @@ export default function Table<T>({ columns, data }: TableProps<T>) {
|
|||||||
columnResizeMode: 'onChange',
|
columnResizeMode: 'onChange',
|
||||||
state: {
|
state: {
|
||||||
pagination,
|
pagination,
|
||||||
|
sorting,
|
||||||
},
|
},
|
||||||
onPaginationChange: setPagination,
|
onPaginationChange: setPagination,
|
||||||
getCoreRowModel: getCoreRowModel(),
|
getCoreRowModel: getCoreRowModel(),
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
getSortedRowModel: getSortedRowModel(),
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
onSortingChange: setSorting,
|
||||||
debugTable: true,
|
debugTable: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"role": "Role",
|
"role": "Role",
|
||||||
"created_at": "Created at",
|
"created_at": "Created at",
|
||||||
"updated_at": "Updated at",
|
"last_seen_at": "Last seen at",
|
||||||
"admin": "Administrator",
|
"admin": "Administrator",
|
||||||
"user": "User",
|
"user": "User",
|
||||||
"users": "Users",
|
"users": "Users",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"role": "Rôle",
|
"role": "Rôle",
|
||||||
"created_at": "Création",
|
"created_at": "Création",
|
||||||
"updated_at": "Mise à jour",
|
"last_seen_at": "Dernière connexion",
|
||||||
"admin": "Administrateur",
|
"admin": "Administrateur",
|
||||||
"user": "Utilisateur",
|
"user": "Utilisateur",
|
||||||
"users": "Utilisateurs",
|
"users": "Utilisateurs",
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ function AdminDashboard({
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
accessorKey: 'id',
|
accessorKey: 'id',
|
||||||
header: (
|
header: () => (
|
||||||
<>
|
<>
|
||||||
# <span css={{ color: theme.colors.grey }}>({users.length})</span>
|
# <span css={{ color: theme.colors.grey }}>({users.length})</span>
|
||||||
</>
|
</>
|
||||||
@@ -69,8 +69,8 @@ function AdminDashboard({
|
|||||||
cell: (info) => info.getValue(),
|
cell: (info) => info.getValue(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'count',
|
accessorKey: 'collectionsCount',
|
||||||
header: (
|
header: () => (
|
||||||
<>
|
<>
|
||||||
{t('common:collection.collections', { count: totalCollections })}{' '}
|
{t('common:collection.collections', { count: totalCollections })}{' '}
|
||||||
<span css={{ color: theme.colors.grey }}>
|
<span css={{ color: theme.colors.grey }}>
|
||||||
@@ -78,17 +78,17 @@ function AdminDashboard({
|
|||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
cell: (info) => (info.getValue() as any)?.collection,
|
cell: (info) => info.getValue(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'count',
|
accessorKey: 'linksCount',
|
||||||
header: (
|
header: () => (
|
||||||
<>
|
<>
|
||||||
{t('common:link.links', { count: totalLinks })}{' '}
|
{t('common:link.links', { count: totalLinks })}{' '}
|
||||||
<span css={{ color: theme.colors.grey }}>({totalLinks})</span>
|
<span css={{ color: theme.colors.grey }}>({totalLinks})</span>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
cell: (info: any) => info.getValue()?.link,
|
cell: (info: any) => info.getValue(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'isAdmin',
|
accessorKey: 'isAdmin',
|
||||||
@@ -110,12 +110,23 @@ function AdminDashboard({
|
|||||||
cell: RenderDateCell,
|
cell: RenderDateCell,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'updatedAt',
|
accessorKey: 'lastSeenAt',
|
||||||
header: t('admin:updated_at'),
|
header: t('admin:last_seen_at'),
|
||||||
cell: RenderDateCell,
|
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;
|
fullname: string;
|
||||||
avatarUrl: string;
|
avatarUrl: string;
|
||||||
isAdmin: string;
|
isAdmin: string;
|
||||||
count: {
|
linksCount: number;
|
||||||
link: number;
|
collectionsCount: number;
|
||||||
collection: number;
|
lastSeenAt: string;
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type Link = CommonBase & {
|
type Link = CommonBase & {
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ router.use([
|
|||||||
() => import('@adonisjs/session/session_middleware'),
|
() => import('@adonisjs/session/session_middleware'),
|
||||||
() => import('@adonisjs/shield/shield_middleware'),
|
() => import('@adonisjs/shield/shield_middleware'),
|
||||||
() => import('@adonisjs/auth/initialize_auth_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