feature: add /user_api/mails with filter params address and keyword (#639)

This commit is contained in:
Dream Hunter
2025-04-24 02:01:21 +08:00
committed by GitHub
parent c6afc5d425
commit 95f361743b
19 changed files with 1124 additions and 887 deletions

View File

@@ -0,0 +1,39 @@
import { Context } from "hono";
import { HonoCustomType } from "../types";
import { handleListQuery } from "../common";
export default {
getMails: async (c: Context<HonoCustomType>) => {
const { address, limit, offset, keyword } = 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 finalQuery = filterQuerys.length > 0 ? `where ${filterQuerys}` : "";
const filterParams = [...addressParams, ...keywordParams]
return await handleListQuery(c,
`SELECT * FROM raw_mails ${finalQuery}`,
`SELECT count(*) as count FROM raw_mails ${finalQuery}`,
filterParams, limit, offset
);
},
getUnknowMails: async (c: Context<HonoCustomType>) => {
const { limit, offset } = c.req.query();
return await handleListQuery(c,
`SELECT * FROM raw_mails where address NOT IN (select name from address) `,
`SELECT count(*) as count FROM raw_mails`
+ ` where address NOT IN (select name from address) `,
[], limit, offset
);
},
deleteMail: async (c: Context<HonoCustomType>) => {
const { id } = c.req.param();
const { success } = await c.env.DB.prepare(
`DELETE FROM raw_mails WHERE id = ? `
).bind(id).run();
return c.json({
success: success
})
}
}

View File

@@ -6,6 +6,7 @@ import { UserSettings, GeoData, UserInfo } from "../models";
import { handleListQuery } from '../common'
import { HonoCustomType } from '../types';
import UserBindAddressModule from '../user_api/bind_address';
import i18n from '../i18n';
export default {
getSetting: async (c: Context<HonoCustomType>) => {
@@ -90,7 +91,8 @@ export default {
},
deleteUser: async (c: Context<HonoCustomType>) => {
const { user_id } = c.req.param();
if (!user_id) return c.text("Invalid user_id", 400);
const msgs = i18n.getMessagesbyContext(c);
if (!user_id) return c.text(msgs.UserNotFoundMsg, 400);
const { success } = await c.env.DB.prepare(
`DELETE FROM users WHERE id = ?`
).bind(user_id).run();
@@ -105,7 +107,8 @@ export default {
resetPassword: async (c: Context<HonoCustomType>) => {
const { user_id } = c.req.param();
const { password } = await c.req.json();
if (!user_id) return c.text("Invalid user_id", 400);
const msgs = i18n.getMessagesbyContext(c);
if (!user_id) return c.text(msgs.UserNotFoundMsg, 400);
try {
checkUserPassword(password);
const { success } = await c.env.DB.prepare(
@@ -159,6 +162,9 @@ export default {
},
getBindedAddresses: async (c: Context<HonoCustomType>) => {
const { user_id } = c.req.param();
return await UserBindAddressModule.getBindedAddressesById(c, user_id);
const results = await UserBindAddressModule.getBindedAddressesById(c, user_id);
return c.json({
results: results,
});
},
}

View File

@@ -12,6 +12,7 @@ import webhook_settings from './webhook_settings'
import mail_webhook_settings from './mail_webhook_settings'
import oauth2_settings from './oauth2_settings'
import worker_config from './worker_config'
import admin_mail_api from './admin_mail_api'
import { sendMailbyAdmin } from './send_mail'
export const api = new Hono<HonoCustomType>()
@@ -101,54 +102,10 @@ api.get('/admin/show_password/:id', async (c) => {
})
})
api.get('/admin/mails', async (c) => {
const { address, limit, offset, keyword } = c.req.query();
if (address && keyword) {
return await handleListQuery(c,
`SELECT * FROM raw_mails where address = ? and raw like ? `,
`SELECT count(*) as count FROM raw_mails where address = ? and raw like ? `,
[address, `%${keyword}%`], limit, offset
);
} else if (keyword) {
return await handleListQuery(c,
`SELECT * FROM raw_mails where raw like ? `,
`SELECT count(*) as count FROM raw_mails where raw like ? `,
[`%${keyword}%`], limit, offset
);
} else if (address) {
return await handleListQuery(c,
`SELECT * FROM raw_mails where address = ? `,
`SELECT count(*) as count FROM raw_mails where address = ? `,
[address], limit, offset
);
} else {
return await handleListQuery(c,
`SELECT * FROM raw_mails `,
`SELECT count(*) as count FROM raw_mails `,
[], limit, offset
);
}
});
api.get('/admin/mails_unknow', async (c) => {
const { limit, offset } = c.req.query();
return await handleListQuery(c,
`SELECT * FROM raw_mails where address NOT IN (select name from address) `,
`SELECT count(*) as count FROM raw_mails`
+ ` where address NOT IN (select name from address) `,
[], limit, offset
);
});
api.delete('/admin/mails/:id', async (c) => {
const { id } = c.req.param();
const { success } = await c.env.DB.prepare(
`DELETE FROM raw_mails WHERE id = ? `
).bind(id).run();
return c.json({
success: success
})
})
// mail api
api.get('/admin/mails', admin_mail_api.getMails);
api.get('/admin/mails_unknow', admin_mail_api.getUnknowMails);
api.delete('/admin/mails/:id', admin_mail_api.deleteMail)
api.get('/admin/address_sender', async (c) => {
const { address, limit, offset } = c.req.query();

View File

@@ -1,5 +1,5 @@
export const CONSTANTS = {
VERSION: 'v' + '0.9.1',
VERSION: 'v' + '0.10.9',
// DB settings
ADDRESS_BLOCK_LIST_KEY: 'address_block_list',

View File

@@ -1,6 +1,8 @@
import { LocaleMessages } from "./type";
import zh from "./zh";
import en from "./en";
import { HonoCustomType } from "../types";
import { Context } from "hono";
export default {
getMessages: (
@@ -10,6 +12,17 @@ export default {
if (locale === "en") return en;
if (locale === "zh") return zh;
// fallback language
return en;
},
getMessagesbyContext: (
c: Context<HonoCustomType>
): LocaleMessages => {
const locale = c.get("lang") || c.env.DEFAULT_LANG;
// multi-language support
if (locale === "en") return en;
if (locale === "zh") return zh;
// fallback language
return en;
}

View File

@@ -6,6 +6,7 @@ import { UserSettings } from "../models";
import { getJsonSetting } from "../utils"
import { CONSTANTS } from "../constants";
import { unbindTelegramByAddress } from '../telegram_api/common';
import i18n from '../i18n';
const UserBindAddressModule = {
bind: async (c: Context<HonoCustomType>) => {
@@ -102,13 +103,30 @@ const UserBindAddressModule = {
},
getBindedAddresses: async (c: Context<HonoCustomType>) => {
const { user_id } = c.get("userPayload");
return await UserBindAddressModule.getBindedAddressesById(c, user_id);
const results = await UserBindAddressModule.getBindedAddressesById(c, user_id);
return c.json({
results: results,
});
},
getBindedAddressListById: async (
c: Context<HonoCustomType>, user_id: number | string
): Promise<string[]> => {
const bindedAddressList = await UserBindAddressModule.getBindedAddressesById(c, user_id);
return bindedAddressList.map((item) => item.name);
},
getBindedAddressesById: async (
c: Context<HonoCustomType>, user_id: number | string
) => {
): Promise<{
id: number;
name: string;
mail_count: number;
send_count: number;
created_at: string;
updated_at: string;
}[]> => {
const msgs = i18n.getMessagesbyContext(c);
if (!user_id) {
return c.text("No user token", 400)
throw new Error(msgs.UserNotFoundMsg);
}
// select binded address
const { results } = await c.env.DB.prepare(
@@ -120,10 +138,15 @@ const UserBindAddressModule = {
+ ` ON ua.address_id = a.id `
+ ` WHERE ua.user_id = ?`
+ ` ORDER BY a.id DESC`
).bind(user_id).all();
return c.json({
results: results,
})
).bind(user_id).all<{
id: number;
name: string;
mail_count: number;
send_count: number;
created_at: string;
updated_at: string;
}>();
return results || [];
},
getBindedAddressJwt: async (c: Context<HonoCustomType>) => {
const { address_id } = c.req.param();
@@ -216,7 +239,7 @@ const UserBindAddressModule = {
throw new Error("Failed to create address")
}
// find new address id
let new_address_id = await c.env.DB.prepare(
const new_address_id = await c.env.DB.prepare(
`SELECT id FROM address WHERE name = ?`
).bind(address).first<number | null | undefined>("id");
if (!new_address_id) {

View File

@@ -6,6 +6,7 @@ import user from './user';
import bind_address from './bind_address';
import passkey from './passkey';
import oauth2 from './oauth2';
import user_mail_api from './user_mail_api';
export const api = new Hono<HonoCustomType>();
@@ -13,6 +14,10 @@ export const api = new Hono<HonoCustomType>();
api.get('/user_api/open_settings', settings.openSettings);
api.get('/user_api/settings', settings.settings);
// mail api
api.get('/user_api/mails', user_mail_api.getMails);
api.delete('/user_api/mails/:id', user_mail_api.deleteMail);
// user api
api.post('/user_api/login', user.login);
api.post('/user_api/verify_code', user.verifyCode);

View File

@@ -0,0 +1,43 @@
import { Context } from "hono";
import { handleListQuery } from "../common";
import { HonoCustomType } from "../types";
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 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 finalQuery = filterQuerys.length > 0 ? `where ${filterQuerys}` : "";
const filterParams = [...addressParams, ...keywordParams]
return await handleListQuery(c,
`SELECT * FROM raw_mails ${finalQuery}`,
`SELECT count(*) as count FROM raw_mails ${finalQuery}`,
filterParams, limit, offset
);
},
deleteMail: async (c: Context<HonoCustomType>) => {
const { id } = c.req.param();
const { user_id } = c.get("userPayload");
const bindedAddressList = await UserBindAddressModule.getBindedAddressListById(c, user_id);
const { success } = await c.env.DB.prepare(
`DELETE FROM raw_mails WHERE id = ?`
+ ` and address IN (${bindedAddressList.map(() => "?").join(",")})`
).bind(id, ...bindedAddressList).run();
return c.json({
success: success
})
}
}