feat: add file filter chat files checkbox(use for remove unused chat files)

pull/249/head
moonrailgun 8 months ago
parent 3cea16ee60
commit c4e70efb61

@ -1653,8 +1653,8 @@ importers:
specifier: workspace:^ specifier: workspace:^
version: link:../packages/sdk version: link:../packages/sdk
tushan: tushan:
specifier: ^0.3.4 specifier: ^0.3.9
version: 0.3.4(prop-types@15.8.1)(ts-node@10.9.1) version: 0.3.9(prop-types@15.8.1)(ts-node@10.9.1)
vite-express: vite-express:
specifier: 0.8.0 specifier: 0.8.0
version: 0.8.0(patch_hash=u6touqej4dt3zxnslnszarl7vq)(express@4.18.2)(vite@4.2.0) version: 0.8.0(patch_hash=u6touqej4dt3zxnslnszarl7vq)(express@4.18.2)(vite@4.2.0)
@ -33333,8 +33333,8 @@ packages:
domino: 2.1.6 domino: 2.1.6
dev: false dev: false
/tushan@0.3.4(prop-types@15.8.1)(ts-node@10.9.1): /tushan@0.3.9(prop-types@15.8.1)(ts-node@10.9.1):
resolution: {integrity: sha512-y2z1wUaZTFO9MJj+sMJzn9xQ08VvdUwEWgVsRRiUKEaSkTOUHl2qqGDrE961X/gGnGcW/yHdzBYObwL44jUxiA==} resolution: {integrity: sha512-qKA6NdWkstPKYE0BaxLZFiPnWXCjpiO5voZxzqln2y0FRp1hG7Zb+gChBQWyrZulAmAVjBiABjOBajpuyu7bqw==}
dependencies: dependencies:
'@arco-design/web-react': 2.51.0(@types/react@18.0.20)(react-dom@18.2.0)(react@18.2.0) '@arco-design/web-react': 2.51.0(@types/react@18.0.20)(react-dom@18.2.0)(react@18.2.0)
'@tanstack/react-query': 4.33.0(react-dom@18.2.0)(react@18.2.0) '@tanstack/react-query': 4.33.0(react-dom@18.2.0)(react@18.2.0)
@ -33355,7 +33355,7 @@ packages:
immer: 9.0.21 immer: 9.0.21
jsonexport: 3.2.0 jsonexport: 3.2.0
lodash-es: 4.17.21 lodash-es: 4.17.21
postcss: 8.4.21 postcss: 8.4.27
qs: 6.11.1 qs: 6.11.1
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)

@ -29,7 +29,7 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"tailchat-server-sdk": "workspace:^", "tailchat-server-sdk": "workspace:^",
"tushan": "^0.3.4", "tushan": "^0.3.9",
"vite-express": "0.8.0" "vite-express": "0.8.0"
}, },
"devDependencies": { "devDependencies": {

@ -159,6 +159,7 @@ export const fileFields = [
createFileSizeField('size', { createFileSizeField('size', {
list: { list: {
width: 120, width: 120,
sort: true,
}, },
}), }),
createTextField('metaData.content-type'), createTextField('metaData.content-type'),
@ -170,7 +171,11 @@ export const fileFields = [
width: 80, width: 80,
}, },
}), }),
createDateTimeField('createdAt'), createDateTimeField('createdAt', {
list: {
sort: true,
},
}),
]; ];
export const mailFields = [ export const mailFields = [

@ -1,17 +1,26 @@
import filesize from 'filesize'; import filesize from 'filesize';
import React from 'react'; import React, { useState } from 'react';
import { import {
createTextField, createTextField,
ListTable, ListTable,
useAsync, useAsync,
useTranslation, useTranslation,
Typography, Typography,
styled,
Checkbox,
} from 'tushan'; } from 'tushan';
import { fileFields } from '../fields'; import { fileFields } from '../fields';
import { request } from '../request'; import { request } from '../request';
const Row = styled.div`
display: flex;
gap: 20px;
justify-content: end;
`;
export const FileList: React.FC = React.memo(() => { export const FileList: React.FC = React.memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const [isOnlyChatFiles, setIsOnlyChatFiles] = useState(false);
const { value: totalSize = 0 } = useAsync(async () => { const { value: totalSize = 0 } = useAsync(async () => {
const { data } = await request.get('/file/filesizeSum'); const { data } = await request.get('/file/filesizeSum');
@ -20,9 +29,19 @@ export const FileList: React.FC = React.memo(() => {
return ( return (
<> <>
<Typography.Paragraph style={{ textAlign: 'right' }}> <Row>
{t('custom.file.fileTotalSize')}: {filesize(totalSize)} <Checkbox
</Typography.Paragraph> checked={isOnlyChatFiles}
onClick={() => {
setIsOnlyChatFiles(!isOnlyChatFiles);
}}
>
Only show chat files
</Checkbox>
<Typography.Paragraph>
{t('custom.file.fileTotalSize')}: {filesize(totalSize)}
</Typography.Paragraph>
</Row>
<ListTable <ListTable
filter={[ filter={[
createTextField('q', { createTextField('q', {
@ -33,6 +52,7 @@ export const FileList: React.FC = React.memo(() => {
action={{ detail: true, delete: true }} action={{ detail: true, delete: true }}
batchAction={{ delete: true }} batchAction={{ delete: true }}
showSizeChanger={true} showSizeChanger={true}
meta={isOnlyChatFiles ? 'onlyChat' : undefined}
/> />
</> </>
); );

@ -10,10 +10,14 @@ import userModel from '../../../../models/user/user';
import messageModel from '../../../../models/chat/message'; import messageModel from '../../../../models/chat/message';
import fileModel from '../../../../models/file'; import fileModel from '../../../../models/file';
import groupModel from '../../../../models/group/group'; import groupModel from '../../../../models/group/group';
import { raExpressMongoose } from '../middleware/express-mongoose-ra-json-server'; import {
raExpressMongoose,
virtualId,
} from '../middleware/express-mongoose-ra-json-server';
import { cacheRouter } from './cache'; import { cacheRouter } from './cache';
import discoverModel from '../../../../plugins/com.msgbyte.discover/models/discover'; import discoverModel from '../../../../plugins/com.msgbyte.discover/models/discover';
import { analyticsRouter } from './analytics'; import { analyticsRouter } from './analytics';
import _ from 'lodash';
const router = Router(); const router = Router();
@ -292,6 +296,51 @@ router.delete('/file/:id', auth(), async (req, res) => {
router.use( router.use(
'/file', '/file',
auth(), auth(),
async (req, res, next) => {
const onlyChatFile = req.query.meta === 'onlyChat';
if (!onlyChatFile) {
return next();
}
// only return chatted file rather than all file
const result = await fileModel
.aggregate()
.lookup({
from: 'users',
localField: 'url',
foreignField: 'avatar',
as: 'avatarMatchedUser',
})
.match({
'avatarMatchedUser.0': { $exists: false },
})
.project({
avatarMatchedUser: 0,
})
.facet({
metadata: [{ $count: 'total' }],
data: [
{
$sort: {
[typeof req.query._sort === 'string'
? req.query._sort === 'id'
? '_id'
: req.query._sort
: '_id']: req.query._order === 'ASC' ? 1 : -1,
},
},
{ $skip: Number(req.query._start) },
{ $limit: Number(req.query._end) - Number(req.query._start) },
],
})
.exec();
const list = _.get(result, '0.data');
const total = _.get(result, '0.metadata.0.total');
return res.set('X-Total-Count', total).json(virtualId(list)).end();
},
raExpressMongoose(fileModel, { raExpressMongoose(fileModel, {
q: ['objectName'], q: ['objectName'],
allowedRegexFields: ['objectName'], allowedRegexFields: ['objectName'],

@ -20,6 +20,7 @@ import { User } from './user/user';
}, },
}) })
@index({ bucketName: 1, objectName: 1 }) @index({ bucketName: 1, objectName: 1 })
@index({ url: 1 })
export class File extends TimeStamps implements Base { export class File extends TimeStamps implements Base {
_id: Types.ObjectId; _id: Types.ObjectId;
id: string; id: string;

@ -5,6 +5,7 @@ import {
ReturnModelType, ReturnModelType,
modelOptions, modelOptions,
Severity, Severity,
index,
} from '@typegoose/typegoose'; } from '@typegoose/typegoose';
import { Base, TimeStamps } from '@typegoose/typegoose/lib/defaultClasses'; import { Base, TimeStamps } from '@typegoose/typegoose/lib/defaultClasses';
import type { Types } from 'mongoose'; import type { Types } from 'mongoose';
@ -32,6 +33,7 @@ export interface UserLoginRes extends User {
allowMixed: Severity.ALLOW, allowMixed: Severity.ALLOW,
}, },
}) })
@index({ avatar: 1 })
export class User extends TimeStamps implements Base { export class User extends TimeStamps implements Base {
_id: Types.ObjectId; _id: Types.ObjectId;
id: string; id: string;

Loading…
Cancel
Save