feat: optimize email filtering with frontend-only search (#787)

* feat: optimize email filtering with frontend-only search

- Remove backend keyword parameter from mail APIs (breaking change)
- Implement frontend filtering on current page (20-100 items)
- Add message_id database index for UPDATE performance
- Support desktop and mobile responsive layouts
- Update API documentation and CHANGELOG

BREAKING CHANGE: /admin/mails and /user_api/mails no longer accept keyword parameter

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: restore Mail ID query input in Index.vue

- Keep showMailIdQuery UI input for querying specific mail by ID
- Triggered when URL contains mail_id parameter

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Dream Hunter
2025-12-15 02:55:50 +08:00
committed by GitHub
parent 1836f931ee
commit e5f62d4713
17 changed files with 489 additions and 546 deletions

View File

@@ -3,6 +3,8 @@
## v1.2.0(main)
- feat: |邮件过滤| 移除后端 keyword 参数,改为前端过滤当前页邮件,优化查询性能
- feat: |数据库| 为 `message_id` 字段添加索引,优化邮件更新操作性能,需执行 `db/2025-12-15-message-id-index.sql` 更新数据库
- feat: |Admin| 维护页面增加自定义 SQL 清理功能,支持定时任务执行自定义清理语句
## v1.1.0

View File

@@ -0,0 +1,4 @@
-- Add index on message_id column in raw_mails table
-- This index improves performance for queries filtering/updating by message_id
-- Example: UPDATE raw_mails SET metadata = ? WHERE message_id = ?
CREATE INDEX IF NOT EXISTS idx_raw_mails_message_id ON raw_mails(message_id);

View File

@@ -12,6 +12,8 @@ CREATE INDEX IF NOT EXISTS idx_raw_mails_address ON raw_mails(address);
CREATE INDEX IF NOT EXISTS idx_raw_mails_created_at ON raw_mails(created_at);
CREATE INDEX IF NOT EXISTS idx_raw_mails_message_id ON raw_mails(message_id);
CREATE TABLE IF NOT EXISTS address (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE,

View File

@@ -40,7 +40,7 @@
"devDependencies": {
"@vicons/fa": "^0.13.0",
"@vicons/material": "^0.13.0",
"@vitejs/plugin-vue": "^6.0.2",
"@vitejs/plugin-vue": "^6.0.3",
"unplugin-auto-import": "^20.3.0",
"unplugin-vue-components": "^30.0.0",
"vite": "^7.2.7",

182
frontend/pnpm-lock.yaml generated
View File

@@ -12,8 +12,8 @@ importers:
specifier: ^5.0.1
version: 5.0.1
'@simplewebauthn/browser':
specifier: 13.2.2
version: 13.2.2
specifier: 10.0.0
version: 10.0.0
'@unhead/vue':
specifier: ^2.0.19
version: 2.0.19(vue@3.5.25(typescript@5.4.5))
@@ -64,8 +64,8 @@ importers:
specifier: ^0.13.0
version: 0.13.0
'@vitejs/plugin-vue':
specifier: ^6.0.2
version: 6.0.2(vite@7.2.7(terser@5.44.1))(vue@3.5.25(typescript@5.4.5))
specifier: ^6.0.3
version: 6.0.3(vite@7.2.7(terser@5.44.1))(vue@3.5.25(typescript@5.4.5))
unplugin-auto-import:
specifier: ^20.3.0
version: 20.3.0(@vueuse/core@14.1.0(vue@3.5.25(typescript@5.4.5)))
@@ -1129,17 +1129,17 @@ packages:
'@juggle/resize-observer@3.4.0':
resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==}
'@poppinss/colors@4.1.5':
resolution: {integrity: sha512-FvdDqtcRCtz6hThExcFOgW0cWX+xwSMWcRuQe5ZEb2m7cVQOAVZOIMt+/v9RxGiD9/OY16qJBXK4CVKWAPalBw==}
'@poppinss/colors@4.1.6':
resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==}
'@poppinss/dumper@0.6.5':
resolution: {integrity: sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==}
'@poppinss/exception@1.2.2':
resolution: {integrity: sha512-m7bpKCD4QMlFCjA/nKTs23fuvoVFoA83brRKmObCUNmi/9tVu8Ve3w4YQAnJu4q3Tjf5fr685HYIC/IA2zHRSg==}
'@poppinss/exception@1.2.3':
resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==}
'@rolldown/pluginutils@1.0.0-beta.50':
resolution: {integrity: sha512-5e76wQiQVeL1ICOZVUg4LSOVYg9jyhGCin+icYozhsUzM+fHE7kddi1bdiE0jwVqTfkjba3jUFbEkoC9WkdvyA==}
'@rolldown/pluginutils@1.0.0-beta.53':
resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==}
'@rollup/plugin-babel@5.3.1':
resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==}
@@ -1309,8 +1309,12 @@ packages:
cpu: [x64]
os: [win32]
'@simplewebauthn/browser@13.2.2':
resolution: {integrity: sha512-FNW1oLQpTJyqG5kkDg5ZsotvWgmBaC6jCHR7Ej0qUNep36Wl9tj2eZu7J5rP+uhXgHaLk+QQ3lqcw2vS5MX1IA==}
'@simplewebauthn/browser@10.0.0':
resolution: {integrity: sha512-hG0JMZD+LiLUbpQcAjS4d+t4gbprE/dLYop/CkE01ugU/9sKXflxV5s0DRjdz3uNMFecatRfb4ZLG3XvF8m5zg==}
'@simplewebauthn/types@10.0.0':
resolution: {integrity: sha512-SFXke7xkgPRowY2E+8djKbdEznTVnD5R6GO7GPTthpHrokLvNKw8C3lFZypTxLI7KkCfGPfhtqB3d7OVGGa9jQ==}
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
'@sindresorhus/is@7.1.1':
resolution: {integrity: sha512-rO92VvpgMc3kfiTjGT52LEtJ8Yc5kCWhZjLQ3LwlA4pSgPpQO7bVpYXParOD8Jwf+cVQECJo3yP/4I8aZtUQTQ==}
@@ -1322,68 +1326,68 @@ packages:
'@surma/rollup-plugin-off-main-thread@2.2.3':
resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==}
'@swc/core-darwin-arm64@1.15.3':
resolution: {integrity: sha512-AXfeQn0CvcQ4cndlIshETx6jrAM45oeUrK8YeEY6oUZU/qzz0Id0CyvlEywxkWVC81Ajpd8TQQ1fW5yx6zQWkQ==}
'@swc/core-darwin-arm64@1.15.4':
resolution: {integrity: sha512-NU/Of+ShFGG/i0lXKsF6GaGeTBNsr9iD8uUzdXxFfGbEjTeuKNXc5CWn3/Uo4Gr4LMAGD3hsRwG2Jq5iBDMalw==}
engines: {node: '>=10'}
cpu: [arm64]
os: [darwin]
'@swc/core-darwin-x64@1.15.3':
resolution: {integrity: sha512-p68OeCz1ui+MZYG4wmfJGvcsAcFYb6Sl25H9TxWl+GkBgmNimIiRdnypK9nBGlqMZAcxngNPtnG3kEMNnvoJ2A==}
'@swc/core-darwin-x64@1.15.4':
resolution: {integrity: sha512-9oWYMZHiEfHLqjjRGrXL17I8HdAOpWK/Rps34RKQ74O+eliygi1Iyq1TDUzYqUXcNvqN2K5fHgoMLRIni41ClQ==}
engines: {node: '>=10'}
cpu: [x64]
os: [darwin]
'@swc/core-linux-arm-gnueabihf@1.15.3':
resolution: {integrity: sha512-Nuj5iF4JteFgwrai97mUX+xUOl+rQRHqTvnvHMATL/l9xE6/TJfPBpd3hk/PVpClMXG3Uvk1MxUFOEzM1JrMYg==}
'@swc/core-linux-arm-gnueabihf@1.15.4':
resolution: {integrity: sha512-I1dPxXli3N1Vr71JXogUTLcspM5ICgCYaA16RE+JKchj3XKKmxLlYjwAHAA4lh/Cy486ikzACaG6pIBcegoGkg==}
engines: {node: '>=10'}
cpu: [arm]
os: [linux]
'@swc/core-linux-arm64-gnu@1.15.3':
resolution: {integrity: sha512-2Nc/s8jE6mW2EjXWxO/lyQuLKShcmTrym2LRf5Ayp3ICEMX6HwFqB1EzDhwoMa2DcUgmnZIalesq2lG3krrUNw==}
'@swc/core-linux-arm64-gnu@1.15.4':
resolution: {integrity: sha512-iGpuS/2PDZ68ioAlhkxiN5M4+pB9uDJolTKk4mZ0JM29uFf9YIkiyk7Bbr2y1QtmD82rF0tDHhoG9jtnV8mZMg==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
'@swc/core-linux-arm64-musl@1.15.3':
resolution: {integrity: sha512-j4SJniZ/qaZ5g8op+p1G9K1z22s/EYGg1UXIb3+Cg4nsxEpF5uSIGEE4mHUfA70L0BR9wKT2QF/zv3vkhfpX4g==}
'@swc/core-linux-arm64-musl@1.15.4':
resolution: {integrity: sha512-Ly95wc+VXDhl08pjAoPUhVu5vNbuPMbURknRZa5QOZuiizJ6DkaSI0/zsEc26PpC6HTc4prNLY3ARVwZ7j/IJQ==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
'@swc/core-linux-x64-gnu@1.15.3':
resolution: {integrity: sha512-aKttAZnz8YB1VJwPQZtyU8Uk0BfMP63iDMkvjhJzRZVgySmqt/apWSdnoIcZlUoGheBrcqbMC17GGUmur7OT5A==}
'@swc/core-linux-x64-gnu@1.15.4':
resolution: {integrity: sha512-7pIG0BnaMn4zTpHeColPwyrWoTY9Drr+ISZQIgYHUKh3oaPtNCrXb289ScGbPPPjLsSfcGTeOy2pXmNczMC+yg==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
'@swc/core-linux-x64-musl@1.15.3':
resolution: {integrity: sha512-oe8FctPu1gnUsdtGJRO2rvOUIkkIIaHqsO9xxN0bTR7dFTlPTGi2Fhk1tnvXeyAvCPxLIcwD8phzKg6wLv9yug==}
'@swc/core-linux-x64-musl@1.15.4':
resolution: {integrity: sha512-oaqTV25V9H+PpSkvTcK25q6Q56FvXc6d2xBu486dv9LAPCHWgeAworE8WpBLV26g8rubcN5nGhO5HwSunXA7Ww==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
'@swc/core-win32-arm64-msvc@1.15.3':
resolution: {integrity: sha512-L9AjzP2ZQ/Xh58e0lTRMLvEDrcJpR7GwZqAtIeNLcTK7JVE+QineSyHp0kLkO1rttCHyCy0U74kDTj0dRz6raA==}
'@swc/core-win32-arm64-msvc@1.15.4':
resolution: {integrity: sha512-VcPuUJw27YbGo1HcOaAriI50dpM3ZZeDW3x2cMnJW6vtkeyzUFk1TADmTwFax0Fn+yicCxhaWjnFE3eAzGAxIQ==}
engines: {node: '>=10'}
cpu: [arm64]
os: [win32]
'@swc/core-win32-ia32-msvc@1.15.3':
resolution: {integrity: sha512-B8UtogMzErUPDWUoKONSVBdsgKYd58rRyv2sHJWKOIMCHfZ22FVXICR4O/VwIYtlnZ7ahERcjayBHDlBZpR0aw==}
'@swc/core-win32-ia32-msvc@1.15.4':
resolution: {integrity: sha512-dREjghAZEuKAK9nQzJETAiCSihSpAVS6Vk9+y2ElaoeTj68tNB1txV/m1RTPPD/+Kgbz6ITPNyXRWxPdkP5aXw==}
engines: {node: '>=10'}
cpu: [ia32]
os: [win32]
'@swc/core-win32-x64-msvc@1.15.3':
resolution: {integrity: sha512-SpZKMR9QBTecHeqpzJdYEfgw30Oo8b/Xl6rjSzBt1g0ZsXyy60KLXrp6IagQyfTYqNYE/caDvwtF2FPn7pomog==}
'@swc/core-win32-x64-msvc@1.15.4':
resolution: {integrity: sha512-o/odIBuQkoxKbRweJWOMI9LeRSOenFKN2zgPeaaNQ/cyuVk2r6DCAobKMOodvDdZWlMn6N1xJrldeCRSTZIgiQ==}
engines: {node: '>=10'}
cpu: [x64]
os: [win32]
'@swc/core@1.15.3':
resolution: {integrity: sha512-Qd8eBPkUFL4eAONgGjycZXj1jFCBW8Fd+xF0PzdTlBCWQIV1xnUT7B93wUANtW3KGjl3TRcOyxwSx/u/jyKw/Q==}
'@swc/core@1.15.4':
resolution: {integrity: sha512-fH81BPo6EiJ7BUb6Qa5SY/NLWIRVambqU3740g0XPFPEz5KFPnzRYpR6zodQNOcEb9XUtZzRO1Y0WyIJP7iBxQ==}
engines: {node: '>=10'}
peerDependencies:
'@swc/helpers': '>=0.5.17'
@@ -1397,8 +1401,8 @@ packages:
'@swc/types@0.1.25':
resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==}
'@swc/wasm@1.15.3':
resolution: {integrity: sha512-NrjGmAplk+v4wokIaLxp1oLoCMVqdQcWoBXopQg57QqyPRcJXLKe+kg5ehhW6z8XaU4Bu5cRkDxUTDY5P0Zy9Q==}
'@swc/wasm@1.15.4':
resolution: {integrity: sha512-waPWVrijKwM8qpUEJGpnQRDlpNE4nQyRmPxjjbJ+5dpmua132o1Nd9f5KQQIgVQqAKSKshKMHskIec8ussNsUg==}
'@transloadit/prettier-bytes@0.0.7':
resolution: {integrity: sha512-VeJbUb0wEKbcwaSlj5n+LscBl9IPgLPkHVGBkh00cztv6X4L/TJXK58LzFuBKX7/GAfiGhIwH67YTLTlzvIzBA==}
@@ -1458,11 +1462,11 @@ packages:
'@vicons/material@0.13.0':
resolution: {integrity: sha512-lKVxFNprM+CaBkUH3gt6VjIeiMsKQl2zARQMwTCZruQl2vRHzyeZiKeCflWS99CEfv2JzX/6y697smxlzyxcVw==}
'@vitejs/plugin-vue@6.0.2':
resolution: {integrity: sha512-iHmwV3QcVGGvSC1BG5bZ4z6iwa1SOpAPWmnjOErd4Ske+lZua5K9TtAVdx0gMBClJ28DViCbSmZitjWZsWO3LA==}
'@vitejs/plugin-vue@6.0.3':
resolution: {integrity: sha512-TlGPkLFLVOY3T7fZrwdvKpjprR3s4fxRln0ORDo1VQ7HHyxJwTlrjKU3kpVWTlaAjIEuCTokmjkZnr8Tpc925w==}
engines: {node: ^20.19.0 || >=22.12.0}
peerDependencies:
vite: ^5.0.0 || ^6.0.0 || ^7.0.0
vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0
vue: ^3.2.25
'@vue/compiler-core@3.5.25':
@@ -1680,8 +1684,8 @@ packages:
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
baseline-browser-mapping@2.9.6:
resolution: {integrity: sha512-v9BVVpOTLB59C9E7aSnmIF8h7qRsFpx+A2nugVMTszEOMcfjlZMsXRm4LF23I3Z9AJxc8ANpIvzbzONoX9VJlg==}
baseline-browser-mapping@2.9.7:
resolution: {integrity: sha512-k9xFKplee6KIio3IDbwj+uaCLpqzOwakOgmqzPezM0sFJlFKcg30vk2wOiAJtkTSfx0SSQDSe8q+mWA/fSH5Zg==}
hasBin: true
blake3-wasm@2.1.5:
@@ -1871,8 +1875,8 @@ packages:
error-stack-parser-es@1.0.5:
resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==}
es-abstract@1.24.0:
resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==}
es-abstract@1.24.1:
resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==}
engines: {node: '>= 0.4'}
es-define-property@1.0.1:
@@ -2844,8 +2848,8 @@ packages:
resolution: {integrity: sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==}
engines: {node: '>=4'}
unimport@5.5.0:
resolution: {integrity: sha512-/JpWMG9s1nBSlXJAQ8EREFTFy3oy6USFd8T6AoBaw1q2GGcF4R9yp3ofg32UODZlYEO5VD0EWE1RpI9XDWyPYg==}
unimport@5.6.0:
resolution: {integrity: sha512-8rqAmtJV8o60x46kBAJKtHpJDJWkA2xcBqWKPI14MgUb05o1pnpnCnXSxedUXyeq7p8fR5g3pTo2BaswZ9lD9A==}
engines: {node: '>=18.12.0'}
unique-string@2.0.0:
@@ -4132,19 +4136,19 @@ snapshots:
'@juggle/resize-observer@3.4.0': {}
'@poppinss/colors@4.1.5':
'@poppinss/colors@4.1.6':
dependencies:
kleur: 4.1.5
'@poppinss/dumper@0.6.5':
dependencies:
'@poppinss/colors': 4.1.5
'@poppinss/colors': 4.1.6
'@sindresorhus/is': 7.1.1
supports-color: 10.2.2
'@poppinss/exception@1.2.2': {}
'@poppinss/exception@1.2.3': {}
'@rolldown/pluginutils@1.0.0-beta.50': {}
'@rolldown/pluginutils@1.0.0-beta.53': {}
'@rollup/plugin-babel@5.3.1(@babel/core@7.28.5)(rollup@2.79.2)':
dependencies:
@@ -4264,7 +4268,11 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.53.3':
optional: true
'@simplewebauthn/browser@13.2.2': {}
'@simplewebauthn/browser@10.0.0':
dependencies:
'@simplewebauthn/types': 10.0.0
'@simplewebauthn/types@10.0.0': {}
'@sindresorhus/is@7.1.1': {}
@@ -4277,51 +4285,51 @@ snapshots:
magic-string: 0.25.9
string.prototype.matchall: 4.0.12
'@swc/core-darwin-arm64@1.15.3':
'@swc/core-darwin-arm64@1.15.4':
optional: true
'@swc/core-darwin-x64@1.15.3':
'@swc/core-darwin-x64@1.15.4':
optional: true
'@swc/core-linux-arm-gnueabihf@1.15.3':
'@swc/core-linux-arm-gnueabihf@1.15.4':
optional: true
'@swc/core-linux-arm64-gnu@1.15.3':
'@swc/core-linux-arm64-gnu@1.15.4':
optional: true
'@swc/core-linux-arm64-musl@1.15.3':
'@swc/core-linux-arm64-musl@1.15.4':
optional: true
'@swc/core-linux-x64-gnu@1.15.3':
'@swc/core-linux-x64-gnu@1.15.4':
optional: true
'@swc/core-linux-x64-musl@1.15.3':
'@swc/core-linux-x64-musl@1.15.4':
optional: true
'@swc/core-win32-arm64-msvc@1.15.3':
'@swc/core-win32-arm64-msvc@1.15.4':
optional: true
'@swc/core-win32-ia32-msvc@1.15.3':
'@swc/core-win32-ia32-msvc@1.15.4':
optional: true
'@swc/core-win32-x64-msvc@1.15.3':
'@swc/core-win32-x64-msvc@1.15.4':
optional: true
'@swc/core@1.15.3':
'@swc/core@1.15.4':
dependencies:
'@swc/counter': 0.1.3
'@swc/types': 0.1.25
optionalDependencies:
'@swc/core-darwin-arm64': 1.15.3
'@swc/core-darwin-x64': 1.15.3
'@swc/core-linux-arm-gnueabihf': 1.15.3
'@swc/core-linux-arm64-gnu': 1.15.3
'@swc/core-linux-arm64-musl': 1.15.3
'@swc/core-linux-x64-gnu': 1.15.3
'@swc/core-linux-x64-musl': 1.15.3
'@swc/core-win32-arm64-msvc': 1.15.3
'@swc/core-win32-ia32-msvc': 1.15.3
'@swc/core-win32-x64-msvc': 1.15.3
'@swc/core-darwin-arm64': 1.15.4
'@swc/core-darwin-x64': 1.15.4
'@swc/core-linux-arm-gnueabihf': 1.15.4
'@swc/core-linux-arm64-gnu': 1.15.4
'@swc/core-linux-arm64-musl': 1.15.4
'@swc/core-linux-x64-gnu': 1.15.4
'@swc/core-linux-x64-musl': 1.15.4
'@swc/core-win32-arm64-msvc': 1.15.4
'@swc/core-win32-ia32-msvc': 1.15.4
'@swc/core-win32-x64-msvc': 1.15.4
'@swc/counter@0.1.3': {}
@@ -4329,7 +4337,7 @@ snapshots:
dependencies:
'@swc/counter': 0.1.3
'@swc/wasm@1.15.3': {}
'@swc/wasm@1.15.4': {}
'@transloadit/prettier-bytes@0.0.7': {}
@@ -4392,9 +4400,9 @@ snapshots:
'@vicons/material@0.13.0': {}
'@vitejs/plugin-vue@6.0.2(vite@7.2.7(terser@5.44.1))(vue@3.5.25(typescript@5.4.5))':
'@vitejs/plugin-vue@6.0.3(vite@7.2.7(terser@5.44.1))(vue@3.5.25(typescript@5.4.5))':
dependencies:
'@rolldown/pluginutils': 1.0.0-beta.50
'@rolldown/pluginutils': 1.0.0-beta.53
vite: 7.2.7(terser@5.44.1)
vue: 3.5.25(typescript@5.4.5)
@@ -4608,7 +4616,7 @@ snapshots:
array-buffer-byte-length: 1.0.2
call-bind: 1.0.8
define-properties: 1.2.1
es-abstract: 1.24.0
es-abstract: 1.24.1
es-errors: 1.3.0
get-intrinsic: 1.3.0
is-array-buffer: 3.0.5
@@ -4661,7 +4669,7 @@ snapshots:
balanced-match@1.0.2: {}
baseline-browser-mapping@2.9.6: {}
baseline-browser-mapping@2.9.7: {}
blake3-wasm@2.1.5: {}
@@ -4671,7 +4679,7 @@ snapshots:
browserslist@4.28.1:
dependencies:
baseline-browser-mapping: 2.9.6
baseline-browser-mapping: 2.9.7
caniuse-lite: 1.0.30001760
electron-to-chromium: 1.5.267
node-releases: 2.0.27
@@ -4844,7 +4852,7 @@ snapshots:
error-stack-parser-es@1.0.5: {}
es-abstract@1.24.0:
es-abstract@1.24.1:
dependencies:
array-buffer-byte-length: 1.0.2
arraybuffer.prototype.slice: 1.0.4
@@ -5581,7 +5589,7 @@ snapshots:
dependencies:
call-bind: 1.0.8
define-properties: 1.2.1
es-abstract: 1.24.0
es-abstract: 1.24.1
es-errors: 1.3.0
es-object-atoms: 1.1.1
get-intrinsic: 1.3.0
@@ -5845,7 +5853,7 @@ snapshots:
call-bind: 1.0.8
call-bound: 1.0.4
define-properties: 1.2.1
es-abstract: 1.24.0
es-abstract: 1.24.1
es-errors: 1.3.0
es-object-atoms: 1.1.1
get-intrinsic: 1.3.0
@@ -5862,7 +5870,7 @@ snapshots:
call-bound: 1.0.4
define-data-property: 1.1.4
define-properties: 1.2.1
es-abstract: 1.24.0
es-abstract: 1.24.1
es-object-atoms: 1.1.1
has-property-descriptors: 1.0.2
@@ -6011,7 +6019,7 @@ snapshots:
unicode-property-aliases-ecmascript@2.2.0: {}
unimport@5.5.0:
unimport@5.6.0:
dependencies:
acorn: 8.15.0
escape-string-regexp: 5.0.0
@@ -6039,7 +6047,7 @@ snapshots:
local-pkg: 1.1.2
magic-string: 0.30.21
picomatch: 4.0.3
unimport: 5.5.0
unimport: 5.6.0
unplugin: 2.3.11
unplugin-utils: 0.3.1
optionalDependencies:
@@ -6104,8 +6112,8 @@ snapshots:
vite-plugin-top-level-await@1.6.0(rollup@2.79.2)(vite@7.2.7(terser@5.44.1)):
dependencies:
'@rollup/plugin-virtual': 3.0.2(rollup@2.79.2)
'@swc/core': 1.15.3
'@swc/wasm': 1.15.3
'@swc/core': 1.15.4
'@swc/wasm': 1.15.4
uuid: 10.0.0
vite: 7.2.7(terser@5.44.1)
transitivePeerDependencies:
@@ -6382,12 +6390,12 @@ snapshots:
youch-core@0.3.3:
dependencies:
'@poppinss/exception': 1.2.2
'@poppinss/exception': 1.2.3
error-stack-parser-es: 1.0.5
youch@4.1.0-beta.10:
dependencies:
'@poppinss/colors': 4.1.5
'@poppinss/colors': 4.1.6
'@poppinss/dumper': 0.6.5
'@speed-highlight/core': 1.2.12
cookie: 1.1.1

View File

@@ -49,20 +49,44 @@ const props = defineProps({
default: (mail_id, filename, blob) => { },
required: false
},
showFilterInput: {
type: Boolean,
default: false,
required: false
},
})
const localFilterKeyword = ref('')
const {
isDark, mailboxSplitSize, indexTab, loading, useUTCDate,
autoRefresh, configAutoRefreshInterval, sendMailModel
} = useGlobalState()
const autoRefreshInterval = ref(configAutoRefreshInterval.value)
const data = ref([])
const rawData = ref([])
const timer = ref(null)
const count = ref(0)
const page = ref(1)
const pageSize = ref(20)
// Computed property for filtered data (only filter current page)
const data = computed(() => {
if (!localFilterKeyword.value || localFilterKeyword.value.trim() === '') {
return rawData.value;
}
const keyword = localFilterKeyword.value.toLowerCase();
return rawData.value.filter(mail => {
// Search in subject, text, message fields
const searchFields = [
mail.subject || '',
mail.text || '',
mail.message || ''
].map(field => field.toLowerCase());
return searchFields.some(field => field.includes(keyword));
});
})
const canGoPrevMail = computed(() => {
if (!curMail.value) return false
const currentIndex = data.value.findIndex(mail => mail.id === curMail.value.id)
@@ -136,6 +160,8 @@ const { t } = useI18n({
unselectAll: 'Unselect All',
prevMail: 'Previous',
nextMail: 'Next',
keywordQueryTip: 'Filter current page',
query: 'Query',
},
zh: {
success: '成功',
@@ -158,6 +184,8 @@ const { t } = useI18n({
unselectAll: '取消全选',
prevMail: '上一封',
nextMail: '下一封',
keywordQueryTip: '过滤当前页',
query: '查询',
}
}
});
@@ -197,7 +225,7 @@ const refresh = async () => {
pageSize.value, (page.value - 1) * pageSize.value
);
loading.value = true;
data.value = await Promise.all(results.map(async (item) => {
rawData.value = await Promise.all(results.map(async (item) => {
item.checked = false;
return await processItem(item);
}));
@@ -370,7 +398,7 @@ onBeforeUnmount(() => {
<div>
<div v-if="!isMobile" class="left">
<div style="margin-bottom: 10px;">
<n-space v-if="multiActionMode">
<n-space v-if="multiActionMode" align="center">
<n-button @click="multiActionModeClick(false)" tertiary>
{{ t('cancelMultiAction') }}
</n-button>
@@ -393,7 +421,7 @@ onBeforeUnmount(() => {
{{ t('downloadMail') }}
</n-button>
</n-space>
<n-space v-else>
<n-space v-else align="center">
<n-button @click="multiActionModeClick(true)" type="primary" tertiary>
{{ t('multiAction') }}
</n-button>
@@ -410,6 +438,9 @@ onBeforeUnmount(() => {
<n-button @click="backFirstPageAndRefresh" type="primary" tertiary>
{{ t('refresh') }}
</n-button>
<n-input v-if="showFilterInput" v-model:value="localFilterKeyword"
:placeholder="t('keywordQueryTip')" style="width: 200px; display: flex; align-items: center;"
clearable />
</n-space>
</div>
<n-split class="left" direction="horizontal" :max="0.75" :min="0.25" :default-size="mailboxSplitSize"
@@ -482,10 +513,8 @@ onBeforeUnmount(() => {
</n-split>
</div>
<div class="left" v-else>
<n-space justify="center">
<div style="display: inline-block;">
<n-space justify="space-around" align="center" :wrap="false" style="display: flex; align-items: center;">
<n-pagination v-model:page="page" v-model:page-size="pageSize" :item-count="count" simple size="small" />
</div>
<n-switch v-model:value="autoRefresh" size="small" :round="false">
<template #checked>
{{ t('refreshAfter', { msg: autoRefreshInterval }) }}
@@ -498,6 +527,10 @@ onBeforeUnmount(() => {
{{ t('refresh') }}
</n-button>
</n-space>
<div v-if="showFilterInput" style="padding: 0 10px; margin-top: 8px;">
<n-input v-model:value="localFilterKeyword"
:placeholder="t('keywordQueryTip')" size="small" clearable />
</div>
<div style="overflow: auto; height: 80vh;">
<n-list hoverable clickable>
<n-list-item v-for="row in data" v-bind:key="row.id" @click="() => clickRow(row)">

View File

@@ -158,7 +158,7 @@ onMounted(() => {
</div>
<MailBox :key="mailBoxKey" :showEMailTo="false" :showReply="true" :showSaveS3="openSettings.isS3Enabled"
:saveToS3="saveToS3" :enableUserDeleteEmail="openSettings.enableUserDeleteEmail"
:fetchMailData="fetchMailData" :deleteMail="deleteMail" />
:fetchMailData="fetchMailData" :deleteMail="deleteMail" :showFilterInput="true" />
</n-tab-pane>
<n-tab-pane name="sendbox" :tab="t('sendbox')">
<SendBox :fetchMailData="fetchSenboxData" :enableUserDeleteEmail="openSettings.enableUserDeleteEmail"

View File

@@ -12,23 +12,19 @@ const { t } = useI18n({
messages: {
en: {
addressQueryTip: 'Leave blank to query all addresses',
keywordQueryTip: 'Leave blank to not query by keyword',
query: 'Query',
},
zh: {
addressQueryTip: '留空查询所有地址',
keywordQueryTip: '留空不按关键字查询',
query: '查询',
}
}
});
const mailBoxKey = ref("")
const mailKeyword = ref("")
const queryMail = () => {
adminMailTabAddress.value = adminMailTabAddress.value.trim();
mailKeyword.value = mailKeyword.value.trim();
mailBoxKey.value = Date.now();
}
@@ -38,7 +34,6 @@ const fetchMailData = async (limit, offset) => {
+ `?limit=${limit}`
+ `&offset=${offset}`
+ (adminMailTabAddress.value ? `&address=${adminMailTabAddress.value}` : '')
+ (mailKeyword.value ? `&keyword=${mailKeyword.value}` : '')
);
}
@@ -51,14 +46,13 @@ const deleteMail = async (curMailId) => {
<div style="margin-top: 10px;">
<n-input-group>
<n-input v-model:value="adminMailTabAddress" :placeholder="t('addressQueryTip')"
@keydown.enter="queryMail" />
<n-input v-model:value="mailKeyword" :placeholder="t('keywordQueryTip')" @keydown.enter="queryMail" />
@keydown.enter="queryMail" clearable />
<n-button @click="queryMail" type="primary" tertiary>
{{ t('query') }}
</n-button>
</n-input-group>
<div style="margin-top: 10px;"></div>
<MailBox :key="mailBoxKey" :enableUserDeleteEmail="true" :fetchMailData="fetchMailData"
:deleteMail="deleteMail" />
:deleteMail="deleteMail" :showFilterInput="true" />
</div>
</template>

View File

@@ -10,12 +10,10 @@ const { t } = useI18n({
messages: {
en: {
addressQueryTip: 'Leave blank to query all addresses',
keywordQueryTip: 'Leave blank to not query by keyword',
query: 'Query',
},
zh: {
addressQueryTip: '留空查询所有地址',
keywordQueryTip: '留空不按关键字查询',
query: '查询',
}
}
@@ -23,12 +21,10 @@ const { t } = useI18n({
const mailBoxKey = ref("")
const addressFilter = ref();
const mailKeyword = ref("")
const addressFilterOptions = ref([]);
const queryMail = () => {
addressFilter.value = addressFilter.value ? addressFilter.value.trim() : addressFilter.value;
mailKeyword.value = mailKeyword.value.trim();
mailBoxKey.value = Date.now();
}
@@ -38,7 +34,6 @@ const fetchMailData = async (limit, offset) => {
+ `?limit=${limit}`
+ `&offset=${offset}`
+ (addressFilter.value ? `&address=${addressFilter.value}` : '')
+ (mailKeyword.value ? `&keyword=${mailKeyword.value}` : '')
);
}
@@ -77,13 +72,12 @@ onMounted(() => {
<n-input-group>
<n-select v-model:value="addressFilter" :options="addressFilterOptions" clearable
:placeholder="t('addressQueryTip')" />
<n-input v-model:value="mailKeyword" :placeholder="t('keywordQueryTip')" @keydown.enter="queryMail" />
<n-button @click="queryMail" type="primary" tertiary>
{{ t('query') }}
</n-button>
</n-input-group>
<div style="margin-top: 10px;"></div>
<MailBox :key="mailBoxKey" :enableUserDeleteEmail="true" :fetchMailData="fetchMailData"
:deleteMail="deleteMail" />
:deleteMail="deleteMail" :showFilterInput="true" />
</div>
</template>

View File

@@ -19,7 +19,7 @@ res = requests.get(
## Admin Mail API
Supports `address` filter and `keyword` filter
Supports `address` filter
```python
import requests
@@ -29,9 +29,8 @@ url = "https://<your-worker-address>/admin/mails"
querystring = {
"limit":"20",
"offset":"0",
# address and keyword are optional parameters
"address":"xxxx@awsl.uk",
"keyword":"xxxx"
# address is optional parameter
"address":"xxxx@awsl.uk"
}
headers = {"x-admin-auth": "<your-Admin-password>"}
@@ -41,9 +40,11 @@ response = requests.get(url, headers=headers, params=querystring)
print(response.json())
```
**Note**: Keyword filtering has been removed from the backend API. If you need to filter emails by content, please use the frontend filter input in the UI, which filters the currently displayed page.
## User Mail API
Supports `address` filter and `keyword` filter
Supports `address` filter
```python
import requests
@@ -53,9 +54,8 @@ url = "https://<your-worker-address>/user_api/mails"
querystring = {
"limit":"20",
"offset":"0",
# address and keyword are optional parameters
"address":"xxxx@awsl.uk",
"keyword":"xxxx"
# address is optional parameter
"address":"xxxx@awsl.uk"
}
headers = {"x-admin-auth": "<your-Admin-password>"}
@@ -64,3 +64,5 @@ response = requests.get(url, headers=headers, params=querystring)
print(response.json())
```
**Note**: Keyword filtering has been removed from the backend API. If you need to filter emails by content, please use the frontend filter input in the UI, which filters the currently displayed page.

View File

@@ -19,7 +19,7 @@ res = requests.get(
## admin 邮件 API
支持 `address` filter 和 `keyword` filter
支持 `address` 过滤
```python
import requests
@@ -29,9 +29,8 @@ url = "https://<你的worker地址>/admin/mails"
querystring = {
"limit":"20",
"offset":"0",
# adress 和 keyword 为可选参数
"address":"xxxx@awsl.uk",
"keyword":"xxxx"
# address 为可选参数
"address":"xxxx@awsl.uk"
}
headers = {"x-admin-auth": "<你的Admin密码>"}
@@ -41,9 +40,11 @@ response = requests.get(url, headers=headers, params=querystring)
print(response.json())
```
**注意**:后端 API 已移除关键词过滤功能。如需按内容过滤邮件,请使用前端界面的过滤输入框,该功能可过滤当前显示的页面。
## user 邮件 API
支持 `address` filter 和 `keyword` filter
支持 `address` 过滤
```python
import requests
@@ -53,9 +54,8 @@ url = "https://<你的worker地址>/user_api/mails"
querystring = {
"limit":"20",
"offset":"0",
# adress 和 keyword 为可选参数
"address":"xxxx@awsl.uk",
"keyword":"xxxx"
# address 为可选参数
"address":"xxxx@awsl.uk"
}
headers = {"x-admin-auth": "<你的Admin密码>"}
@@ -64,3 +64,5 @@ response = requests.get(url, headers=headers, params=querystring)
print(response.json())
```
**注意**:后端 API 已移除关键词过滤功能。如需按内容过滤邮件,请使用前端界面的过滤输入框,该功能可过滤当前显示的页面。

View File

@@ -4,7 +4,7 @@
"version": "1.2.0",
"type": "module",
"devDependencies": {
"@types/node": "^25.0.0",
"@types/node": "^25.0.2",
"vitepress": "^1.6.4",
"wrangler": "^4.54.0"
},

View File

@@ -13,11 +13,11 @@ importers:
version: 3.10.1
devDependencies:
'@types/node':
specifier: ^25.0.0
version: 25.0.0
specifier: ^25.0.2
version: 25.0.2
vitepress:
specifier: ^1.6.4
version: 1.6.4(@algolia/client-search@5.46.0)(@types/node@25.0.0)(postcss@8.5.6)(search-insights@2.13.0)(typescript@5.4.5)
version: 1.6.4(@algolia/client-search@5.46.0)(@types/node@25.0.2)(postcss@8.5.6)(search-insights@2.13.0)(typescript@5.4.5)
wrangler:
specifier: ^4.54.0
version: 4.54.0
@@ -484,8 +484,8 @@ packages:
cpu: [x64]
os: [win32]
'@iconify-json/simple-icons@1.2.62':
resolution: {integrity: sha512-GpWQ294d4lraB3D2eBSSMROh1x9uKgpmyereLlGzVQjGZ7lbeFzby2ywXxyp4vEODmTDyf1/4WcOYs/yH4rJ5Q==}
'@iconify-json/simple-icons@1.2.63':
resolution: {integrity: sha512-xZl2UWCwE58VlqZ+pDPmaUhE2tq8MVSTJRr4/9nzzHlDdjJ0Ud1VxNXPrwTSgESKY29iCQw3S0r2nJTSNNngHw==}
'@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
@@ -605,14 +605,14 @@ packages:
'@jridgewell/trace-mapping@0.3.9':
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
'@poppinss/colors@4.1.5':
resolution: {integrity: sha512-FvdDqtcRCtz6hThExcFOgW0cWX+xwSMWcRuQe5ZEb2m7cVQOAVZOIMt+/v9RxGiD9/OY16qJBXK4CVKWAPalBw==}
'@poppinss/colors@4.1.6':
resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==}
'@poppinss/dumper@0.6.5':
resolution: {integrity: sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==}
'@poppinss/exception@1.2.2':
resolution: {integrity: sha512-m7bpKCD4QMlFCjA/nKTs23fuvoVFoA83brRKmObCUNmi/9tVu8Ve3w4YQAnJu4q3Tjf5fr685HYIC/IA2zHRSg==}
'@poppinss/exception@1.2.3':
resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==}
'@rollup/rollup-android-arm-eabi@4.53.3':
resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==}
@@ -773,8 +773,8 @@ packages:
'@types/mdurl@2.0.0':
resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==}
'@types/node@25.0.0':
resolution: {integrity: sha512-rl78HwuZlaDIUSeUKkmogkhebA+8K1Hy7tddZuJ3D0xV8pZSfsYGTsliGUol1JPzu9EKnTxPC4L1fiWouStRew==}
'@types/node@25.0.2':
resolution: {integrity: sha512-gWEkeiyYE4vqjON/+Obqcoeffmk0NF15WSBwSs7zwVA2bAbTaE0SJ7P0WNGoJn8uE7fiaV5a7dKYIJriEqOrmA==}
'@types/unist@3.0.3':
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
@@ -1645,7 +1645,7 @@ snapshots:
'@esbuild/win32-x64@0.27.0':
optional: true
'@iconify-json/simple-icons@1.2.62':
'@iconify-json/simple-icons@1.2.63':
dependencies:
'@iconify/types': 2.0.0
@@ -1735,17 +1735,17 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
'@poppinss/colors@4.1.5':
'@poppinss/colors@4.1.6':
dependencies:
kleur: 4.1.5
'@poppinss/dumper@0.6.5':
dependencies:
'@poppinss/colors': 4.1.5
'@poppinss/colors': 4.1.6
'@sindresorhus/is': 7.1.1
supports-color: 10.2.2
'@poppinss/exception@1.2.2': {}
'@poppinss/exception@1.2.3': {}
'@rollup/rollup-android-arm-eabi@4.53.3':
optional: true
@@ -1876,7 +1876,7 @@ snapshots:
'@types/mdurl@2.0.0': {}
'@types/node@25.0.0':
'@types/node@25.0.2':
dependencies:
undici-types: 7.16.0
@@ -1886,9 +1886,9 @@ snapshots:
'@ungap/structured-clone@1.3.0': {}
'@vitejs/plugin-vue@5.2.4(vite@5.4.21(@types/node@25.0.0))(vue@3.5.25(typescript@5.4.5))':
'@vitejs/plugin-vue@5.2.4(vite@5.4.21(@types/node@25.0.2))(vue@3.5.25(typescript@5.4.5))':
dependencies:
vite: 5.4.21(@types/node@25.0.0)
vite: 5.4.21(@types/node@25.0.2)
vue: 3.5.25(typescript@5.4.5)
'@vue/compiler-core@3.5.25':
@@ -2440,25 +2440,25 @@ snapshots:
'@types/unist': 3.0.3
vfile-message: 4.0.3
vite@5.4.21(@types/node@25.0.0):
vite@5.4.21(@types/node@25.0.2):
dependencies:
esbuild: 0.21.5
postcss: 8.5.6
rollup: 4.53.3
optionalDependencies:
'@types/node': 25.0.0
'@types/node': 25.0.2
fsevents: 2.3.3
vitepress@1.6.4(@algolia/client-search@5.46.0)(@types/node@25.0.0)(postcss@8.5.6)(search-insights@2.13.0)(typescript@5.4.5):
vitepress@1.6.4(@algolia/client-search@5.46.0)(@types/node@25.0.2)(postcss@8.5.6)(search-insights@2.13.0)(typescript@5.4.5):
dependencies:
'@docsearch/css': 3.8.2
'@docsearch/js': 3.8.2(@algolia/client-search@5.46.0)(search-insights@2.13.0)
'@iconify-json/simple-icons': 1.2.62
'@iconify-json/simple-icons': 1.2.63
'@shikijs/core': 2.5.0
'@shikijs/transformers': 2.5.0
'@shikijs/types': 2.5.0
'@types/markdown-it': 14.1.2
'@vitejs/plugin-vue': 5.2.4(vite@5.4.21(@types/node@25.0.0))(vue@3.5.25(typescript@5.4.5))
'@vitejs/plugin-vue': 5.2.4(vite@5.4.21(@types/node@25.0.2))(vue@3.5.25(typescript@5.4.5))
'@vue/devtools-api': 7.7.9
'@vue/shared': 3.5.25
'@vueuse/core': 12.8.2(typescript@5.4.5)
@@ -2467,7 +2467,7 @@ snapshots:
mark.js: 8.11.1
minisearch: 7.2.0
shiki: 2.5.0
vite: 5.4.21(@types/node@25.0.0)
vite: 5.4.21(@types/node@25.0.2)
vue: 3.5.25(typescript@5.4.5)
optionalDependencies:
postcss: 8.5.6
@@ -2536,12 +2536,12 @@ snapshots:
youch-core@0.3.3:
dependencies:
'@poppinss/exception': 1.2.2
'@poppinss/exception': 1.2.3
error-stack-parser-es: 1.0.5
youch@4.1.0-beta.10:
dependencies:
'@poppinss/colors': 4.1.5
'@poppinss/colors': 4.1.6
'@poppinss/dumper': 0.6.5
'@speed-highlight/core': 1.2.12
cookie: 1.1.1

View File

@@ -11,10 +11,10 @@
"build": "wrangler deploy --dry-run --outdir dist --minify"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20251211.0",
"@cloudflare/workers-types": "^4.20251213.0",
"@eslint/js": "9.39.1",
"@simplewebauthn/types": "10.0.0",
"@types/node": "^25.0.0",
"@types/node": "^25.0.2",
"eslint": "9.39.1",
"globals": "^16.5.0",
"typescript-eslint": "^8.49.0",
@@ -24,7 +24,7 @@
"@aws-sdk/client-s3": "3.888.0",
"@aws-sdk/s3-request-presigner": "3.888.0",
"@simplewebauthn/server": "10.0.1",
"hono": "^4.10.8",
"hono": "^4.11.0",
"jsonpath-plus": "^10.3.0",
"mimetext": "^3.0.27",
"postal-mime": "^2.6.1",

662
worker/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,14 +3,12 @@ import { handleListQuery } from "../common";
export default {
getMails: async (c: Context<HonoCustomType>) => {
const { address, limit, offset, keyword } = c.req.query();
const { address, limit, offset } = c.req.query();
const addressQuery = address ? `address = ?` : "";
const addressParams = address ? [address] : [];
const keywordQuery = keyword ? `raw like ?` : "";
const keywordParams = keyword ? [`%${keyword}%`] : [];
const filterQuerys = [addressQuery, keywordQuery].filter((item) => item).join(" and ");
const filterQuerys = [addressQuery].filter((item) => item).join(" and ");
const finalQuery = filterQuerys.length > 0 ? `where ${filterQuerys}` : "";
const filterParams = [...addressParams, ...keywordParams]
const filterParams = [...addressParams]
return await handleListQuery(c,
`SELECT * FROM raw_mails ${finalQuery}`,
`SELECT count(*) as count FROM raw_mails ${finalQuery}`,

View File

@@ -5,22 +5,20 @@ import UserBindAddressModule from "./bind_address";
export default {
getMails: async (c: Context<HonoCustomType>) => {
const { user_id } = c.get("userPayload");
const { address, limit, offset, keyword } = c.req.query();
const { address, limit, offset } = c.req.query();
const bindedAddressList = await UserBindAddressModule.getBindedAddressListById(c, user_id);
const addressList = address ? bindedAddressList.filter((item) => item == address) : bindedAddressList;
const addressQuery = `address IN (${addressList.map(() => "?").join(",")})`;
const addressParams = addressList;
const keywordQuery = keyword ? `raw like ?` : "";
const keywordParams = keyword ? [`%${keyword}%`] : [];
// user must have at least one binded address to query mails
if (addressList.length <= 0) {
return c.json({ results: [], count: 0 });
}
const filterQuerys = [addressQuery, keywordQuery].filter((item) => item).join(" and ");
const filterQuerys = [addressQuery].filter((item) => item).join(" and ");
const finalQuery = filterQuerys.length > 0 ? `where ${filterQuerys}` : "";
const filterParams = [...addressParams, ...keywordParams]
const filterParams = [...addressParams]
return await handleListQuery(c,
`SELECT * FROM raw_mails ${finalQuery}`,
`SELECT count(*) as count FROM raw_mails ${finalQuery}`,