Commit ea3f8ff2 authored by ml's avatar ml

增加 资源位管理 邮件订阅 相关接口

parent 55400170
......@@ -30,6 +30,7 @@
"express-swagger-generator": "^1.1.17",
"geoip-lite": "^1.4.7",
"glob": "10.3.10",
"image-size": "1.0.2",
"isemail": "^3.2.0",
"jsonwebtoken": "^7.4.3",
"lodash": "^4.17.21",
......@@ -105,9 +106,9 @@
}
},
"node_modules/@babel/compat-data": {
"version": "7.25.2",
"resolved": "https://packages.aliyun.com/646341b481b284e28f47a25b/npm/npm-registry/@babel/compat-data/-/compat-data-7.25.2.tgz",
"integrity": "sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==",
"version": "7.25.4",
"resolved": "https://packages.aliyun.com/646341b481b284e28f47a25b/npm/npm-registry/@babel/compat-data/-/compat-data-7.25.4.tgz",
"integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==",
"dev": true,
"license": "MIT",
"engines": {
......@@ -163,13 +164,13 @@
}
},
"node_modules/@babel/generator": {
"version": "7.25.0",
"resolved": "https://packages.aliyun.com/646341b481b284e28f47a25b/npm/npm-registry/@babel/generator/-/generator-7.25.0.tgz",
"integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==",
"version": "7.25.5",
"resolved": "https://packages.aliyun.com/646341b481b284e28f47a25b/npm/npm-registry/@babel/generator/-/generator-7.25.5.tgz",
"integrity": "sha512-abd43wyLfbWoxC6ahM8xTkqLpGB2iWBVyuKC9/srhFunCd1SDNrV1s72bBpK4hLj8KLzHBBcOblvLQZBNw9r3w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.25.0",
"@babel/types": "^7.25.4",
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25",
"jsesc": "^2.5.1"
......@@ -391,13 +392,13 @@
}
},
"node_modules/@babel/parser": {
"version": "7.25.3",
"resolved": "https://packages.aliyun.com/646341b481b284e28f47a25b/npm/npm-registry/@babel/parser/-/parser-7.25.3.tgz",
"integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==",
"version": "7.25.4",
"resolved": "https://packages.aliyun.com/646341b481b284e28f47a25b/npm/npm-registry/@babel/parser/-/parser-7.25.4.tgz",
"integrity": "sha512-nq+eWrOgdtu3jG5Os4TQP3x3cLA8hR8TvJNjD8vnPa20WGycimcparWnLK4jJhElTK6SDyuJo1weMKO/5LpmLA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.25.2"
"@babel/types": "^7.25.4"
},
"bin": {
"parser": "bin/babel-parser.js"
......@@ -422,17 +423,17 @@
}
},
"node_modules/@babel/traverse": {
"version": "7.25.3",
"resolved": "https://packages.aliyun.com/646341b481b284e28f47a25b/npm/npm-registry/@babel/traverse/-/traverse-7.25.3.tgz",
"integrity": "sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==",
"version": "7.25.4",
"resolved": "https://packages.aliyun.com/646341b481b284e28f47a25b/npm/npm-registry/@babel/traverse/-/traverse-7.25.4.tgz",
"integrity": "sha512-VJ4XsrD+nOvlXyLzmLzUs/0qjFS4sK30te5yEFlvbbUNEgKaVb2BHZUpAL+ttLPQAHNrsI3zZisbfha5Cvr8vg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.24.7",
"@babel/generator": "^7.25.0",
"@babel/parser": "^7.25.3",
"@babel/generator": "^7.25.4",
"@babel/parser": "^7.25.4",
"@babel/template": "^7.25.0",
"@babel/types": "^7.25.2",
"@babel/types": "^7.25.4",
"debug": "^4.3.1",
"globals": "^11.1.0"
},
......@@ -451,9 +452,9 @@
}
},
"node_modules/@babel/types": {
"version": "7.25.2",
"resolved": "https://packages.aliyun.com/646341b481b284e28f47a25b/npm/npm-registry/@babel/types/-/types-7.25.2.tgz",
"integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==",
"version": "7.25.4",
"resolved": "https://packages.aliyun.com/646341b481b284e28f47a25b/npm/npm-registry/@babel/types/-/types-7.25.4.tgz",
"integrity": "sha512-zQ1ijeeCXVEh+aNL0RlmkPkG8HUiDcU2pzQQFjtbntgAczRASFzj4H+6+bV+dy1ntKR14I/DypeuRG1uma98iQ==",
"dev": true,
"license": "MIT",
"dependencies": {
......@@ -874,7 +875,7 @@
},
"node_modules/@madex/ex-js-common": {
"version": "1.0.0",
"resolved": "git+ssh://git@bitbucket.org/biiigle/ex-js-common.git#6aa1d7d9264b2a13e13fe6c79d33223205b9cc40",
"resolved": "git+ssh://git@bitbucket.org/biiigle/ex-js-common.git#a074015198403c5598adea7e0fd4380936a5aa2b",
"license": "ISC",
"dependencies": {
"@madex/ex-js-dao": "git+ssh://git@bitbucket.org/biiigle/ex-js-dao.git#master",
......@@ -1068,9 +1069,9 @@
}
},
"node_modules/@madex/ex-ts-dao": {
"version": "0.0.17",
"resolved": "https://packages.aliyun.com/646341b481b284e28f47a25b/npm/npm-registry/@madex/ex-ts-dao/-/@madex/ex-ts-dao-0.0.17.tgz",
"integrity": "sha512-TDR4FNGVSAv4FLqJQJZ+D8ZeJmBacQIF7AYVECutsL4PWE5LBRP3xylvuL1EB6DRdbDRt/L42k7bdChWn1TN0A==",
"version": "0.0.20",
"resolved": "https://packages.aliyun.com/646341b481b284e28f47a25b/npm/npm-registry/@madex/ex-ts-dao/-/@madex/ex-ts-dao-0.0.20.tgz",
"integrity": "sha512-c4TCZnRNc9LfdTh436vTg4DM+dxGeJIpcUo1BjKPijkdL0Ij42BkXOU3iyajViUks7kadVd7VLK9JAeqYDSaUQ==",
"license": "ISC",
"dependencies": {
"@madex/ex-js-public": "git+ssh://git@bitbucket.org/biiigle/ex-js-public.git#master",
......@@ -1696,9 +1697,9 @@
"license": "MIT"
},
"node_modules/@types/validator": {
"version": "13.12.0",
"resolved": "https://packages.aliyun.com/646341b481b284e28f47a25b/npm/npm-registry/@types/validator/-/validator-13.12.0.tgz",
"integrity": "sha512-nH45Lk7oPIJ1RVOF6JgFI6Dy0QpHEzq4QecZhvguxYPDwT8c93prCMqAtiIttm39voZ+DDR+qkNnMpJmMBRqag==",
"version": "13.12.1",
"resolved": "https://packages.aliyun.com/646341b481b284e28f47a25b/npm/npm-registry/@types/validator/-/validator-13.12.1.tgz",
"integrity": "sha512-w0URwf7BQb0rD/EuiG12KP0bailHKHP5YVviJG9zw3ykAokL0TuxU2TUqMB7EwZ59bDHYdeTIvjI5m0S7qHfOA==",
"dev": true,
"license": "MIT"
},
......@@ -3321,9 +3322,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001651",
"resolved": "https://packages.aliyun.com/646341b481b284e28f47a25b/npm/npm-registry/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz",
"integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==",
"version": "1.0.30001653",
"resolved": "https://packages.aliyun.com/646341b481b284e28f47a25b/npm/npm-registry/caniuse-lite/-/caniuse-lite-1.0.30001653.tgz",
"integrity": "sha512-XGWQVB8wFQ2+9NZwZ10GxTYC5hk0Fa+q8cSkr0tgvMhYhMHP/QC+WTgrePMDBWiWc/pV+1ik82Al20XOK25Gcw==",
"dev": true,
"funding": [
{
......@@ -5015,9 +5016,9 @@
}
},
"node_modules/eslint-module-utils": {
"version": "2.8.1",
"resolved": "https://packages.aliyun.com/646341b481b284e28f47a25b/npm/npm-registry/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz",
"integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==",
"version": "2.8.2",
"resolved": "https://packages.aliyun.com/646341b481b284e28f47a25b/npm/npm-registry/eslint-module-utils/-/eslint-module-utils-2.8.2.tgz",
"integrity": "sha512-3XnC5fDyc8M4J2E8pt8pmSVRX2M+5yWMCfI/kDZwauQeFgzQOuhcRBFKjTeJagqgk4sFKxe1mvNVnaWwImx/Tg==",
"dev": true,
"license": "MIT",
"dependencies": {
......@@ -6975,6 +6976,21 @@
"node": ">= 4"
}
},
"node_modules/image-size": {
"version": "1.0.2",
"resolved": "https://packages.aliyun.com/646341b481b284e28f47a25b/npm/npm-registry/image-size/-/image-size-1.0.2.tgz",
"integrity": "sha512-xfOoWjceHntRb3qFCrh5ZFORYH8XCdYpASltMhZ/Q0KZiOwjdE/Yl2QCiWdwD+lygV5bMCvauzgu5PxBX/Yerg==",
"license": "MIT",
"dependencies": {
"queue": "6.0.2"
},
"bin": {
"image-size": "bin/image-size.js"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/imageinfo": {
"version": "1.0.4",
"resolved": "https://packages.aliyun.com/646341b481b284e28f47a25b/npm/npm-registry/imageinfo/-/imageinfo-1.0.4.tgz",
......@@ -9448,9 +9464,9 @@
}
},
"node_modules/micromatch": {
"version": "4.0.7",
"resolved": "https://packages.aliyun.com/646341b481b284e28f47a25b/npm/npm-registry/micromatch/-/micromatch-4.0.7.tgz",
"integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
"version": "4.0.8",
"resolved": "https://packages.aliyun.com/646341b481b284e28f47a25b/npm/npm-registry/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
"license": "MIT",
"dependencies": {
......@@ -11469,6 +11485,15 @@
"license": "MIT",
"optional": true
},
"node_modules/queue": {
"version": "6.0.2",
"resolved": "https://packages.aliyun.com/646341b481b284e28f47a25b/npm/npm-registry/queue/-/queue-6.0.2.tgz",
"integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
"license": "MIT",
"dependencies": {
"inherits": "~2.0.3"
}
},
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://packages.aliyun.com/646341b481b284e28f47a25b/npm/npm-registry/queue-microtask/-/queue-microtask-1.2.3.tgz",
......@@ -13773,9 +13798,9 @@
}
},
"node_modules/tslib": {
"version": "2.6.3",
"resolved": "https://packages.aliyun.com/646341b481b284e28f47a25b/npm/npm-registry/tslib/-/tslib-2.6.3.tgz",
"integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==",
"version": "2.7.0",
"resolved": "https://packages.aliyun.com/646341b481b284e28f47a25b/npm/npm-registry/tslib/-/tslib-2.7.0.tgz",
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
"license": "0BSD"
},
"node_modules/tsscmp": {
......
......@@ -35,4 +35,6 @@ export const ErrorCode = {
USER_NOT_LOCK: '30032',//该用户未锁定
REASON_TOO_LONG: '30033',//驳回原因过长
DATA_STATUS_CHANGED: '30034',//非法操作,数据状态已经变更
UID_ILLEGALITY: '30035',//UID不合法
UID_TOO_MUCH: '30036',//UID过多
}
export const TYPE_MAIL = {
/**
* 邮件日志类型: 。
*/
TYPE_MAIL_LOG_MONEY_AND_KYC : 1,
/**
* 邮件日志类型: 。
*/
TYPE_MAIL_LOG_MONEY_AND_NO_KYC : 2,
/**
* 邮件日志类型: 。
*/
TYPE_MAIL_LOG_NO_MONEY_AND_KYC : 3,
/**
* 邮件日志类型: 。
*/
TYPE_MAIL_LOG_NO_MONEY_AND_NO_KYC : 4,
/**
* 邮件日志类型: 。
*/
TYPE_MAIL_LOG_ALL : 5,
/**
* 邮件日志类型: 。
*/
TYPE_MAIL_LOG_SINGLE : 6,
}
\ No newline at end of file
/**
* useful_link表常量。
*/
export const IS_ACTIVE = {
/**
* 是否启用: 未启用。
*/
IS_ACTIVE_NO: 0,
/**
* 是否启用: 已启用。
*/
IS_ACTIVE_YES: 1
}
export const IS_ACTIVE_ARR = [IS_ACTIVE.IS_ACTIVE_NO, IS_ACTIVE.IS_ACTIVE_YES]
export const LINK_TYPE = {
/**
* 友情链接。
*/
TYPE_NORMAL: 1,
/**
* 合作伙伴。
*/
TYPE_FRIEND: 2,
/**
* 公告。
*/
TYPE_NOTICE: 3,
/**
* 轮播图。
*/
TYPE_SLIDESHOW: 4,
/**
* app 轮播图。
*/
TYPE_SLIDESHOW_APP: 5,
/**
* 鼓励金比例管理。
*/
TYPE_BIX_LOCK_BACK_RATE: 6,
/**
* app 弹窗。
*/
TYPE_APP_POP: 8,
/**
* web 弹框。
*/
TYPE_WEB_POP: 9,
/**
* banner, 首页活动 banner。
*/
TYPE_BANNER: 10,
/**
* 活动中心。
*/
TYPE_ACTIVE_CENTER: 11,
/**
* 广告位: 红包页面。
*/
TYPE_ADVERTISE_RED_PACKET: 12,
/**
* 广告位: web交易页,币对位置。
*/
TYPE_ADVERTISE_WEB_PAIR: 13,
/**
* 广告位: 登录页面。
*/
TYPE_ADVERTISE_LOGIN: 14,
/**
* 广告位: 系统邮件。
*/
TYPE_ADVERTISE_SYS_MAIL: 15,
/**
* 标题广告: 币币交易详情页。
*/
TYPE_DETAIL_TRADE_SPOT: 16,
/**
* 标题广告: 杠杆交易详情页。
*/
TYPE_DETAIL_TRADE_CREDIT: 17,
/**
* 标题广告: 合约交易详情页。
*/
TYPE_DETAIL_TRADE_CONTRACT: 18,
/**
* 合约轮播图。
*/
TYPE_SLIDESHOW_CONTRACT: 19,
/**
* 合约公告
*/
TYPE_NOTICE_CONTRACT: 20,
}
export const LINK_TYPE_ARR = [
LINK_TYPE.TYPE_NORMAL,
LINK_TYPE.TYPE_FRIEND,
LINK_TYPE.TYPE_NOTICE,
LINK_TYPE.TYPE_SLIDESHOW,
LINK_TYPE.TYPE_SLIDESHOW_APP,
LINK_TYPE.TYPE_BIX_LOCK_BACK_RATE,
LINK_TYPE.TYPE_APP_POP,
LINK_TYPE.TYPE_WEB_POP,
LINK_TYPE.TYPE_BANNER,
LINK_TYPE.TYPE_ACTIVE_CENTER,
LINK_TYPE.TYPE_ADVERTISE_RED_PACKET,
LINK_TYPE.TYPE_ADVERTISE_WEB_PAIR,
LINK_TYPE.TYPE_ADVERTISE_LOGIN,
LINK_TYPE.TYPE_ADVERTISE_SYS_MAIL,
LINK_TYPE.TYPE_DETAIL_TRADE_SPOT,
LINK_TYPE.TYPE_DETAIL_TRADE_CREDIT,
LINK_TYPE.TYPE_DETAIL_TRADE_CONTRACT,
LINK_TYPE.TYPE_SLIDESHOW_CONTRACT,
LINK_TYPE.TYPE_NOTICE_CONTRACT,
]
import * as mUserSubscribeService from "../service/mUserSubscribe.service";
import { QueryVO } from "../service/mUserSubscribe.service";
import * as mUserMailLogService from "../service/mUserMailLog.service";
import { UserMailLogVO } from "../service/mUserMailLog.service";
let { logger, Res3Utils, optionalUtils: Optional, apiAssertUtils: ApiAssert } = require('@madex/ex-js-public');
import { ErrorCode } from "../../../constant/errorCode";
import { IS_ACTIVE, IS_ACTIVE_ARR, LINK_TYPE_ARR } from "../../../constant/usefulLinkConst";
import { getCurrentUserId } from "../../../utils/aclUserUtils";
let isIp = require('is-ip');
/**
* 订阅列表
* @param req
* @param infoVO
*/
export const list = async (req: any, queryVO: QueryVO) => {
let func_name = "mUserSubscribe.control.list";
try {
queryVO.page = Optional.opt(queryVO, 'page', 1);
queryVO.size = Optional.opt(queryVO, 'size', 20);
let res = await mUserSubscribeService.list(queryVO);
return Res3Utils.result(res);
}
catch (e) {
logger.error(`${func_name} error:${e}`);
return Res3Utils.getErrorResult(e);
}
};
/**
* 删除订阅
* TODO: 关于status 字段 从老管理后台来看 0 代表无效 1 代表有效 但是数据库备注 0 有效 1 无效 以哪个为准?
* @param req
* @param authConfigVO
*/
export const del = async (req: any, queryVO: QueryVO) => {
let func_name = "mUserSubscribe.control.delete";
try {
let ip = isIp(req.ip) ? req.ip : '*.*.*.*';
let currentUserId = await getCurrentUserId(req.cookies.session_id);
ApiAssert.notNull(ErrorCode.PARAM_MISS, queryVO.id);
let res = await mUserSubscribeService.del(Number(queryVO.id), currentUserId, ip);
return Res3Utils.result(res);
}
catch (e) {
logger.error(`${func_name} error:${e}`);
return Res3Utils.getErrorResult(e);
}
};
/**
* 订阅统计
* TODO: 关于status 字段 从老管理后台来看 0 代表无效 1 代表有效 但是数据库备注 0 有效 1 无效 以哪个为准?
* @param req
* @param authConfigVO
*/
export const count = async (req: any, queryVO: QueryVO) => {
let func_name = "mUserSubscribe.control.count";
try {
let res = await mUserSubscribeService.count();
return Res3Utils.result(res);
}
catch (e) {
logger.error(`${func_name} error:${e}`);
return Res3Utils.getErrorResult(e);
}
};
/**
* 邮件发送历史
* @param req
* @param infoVO
*/
export const history = async (req: any, userMailLogVO: UserMailLogVO) => {
let func_name = "mUserSubscribe.control.history";
try {
userMailLogVO.page = Optional.opt(userMailLogVO, 'page', 1);
userMailLogVO.size = Optional.opt(userMailLogVO, 'size', 20);
let res = await mUserMailLogService.history(userMailLogVO.page, userMailLogVO.size);
return Res3Utils.result(res);
}
catch (e) {
logger.error(`${func_name} error:${e}`);
return Res3Utils.getErrorResult(e);
}
};
/**
* 邮件详情
* @param req
* @param infoVO
*/
export const mailDetail = async (req: any, userMailLogVO: UserMailLogVO) => {
let func_name = "mUserSubscribe.control.mailDetail";
try {
if (!userMailLogVO.id) {
throw ErrorCode.PARAM_MISS
}
let res = await mUserMailLogService.mailDetail(userMailLogVO.id);
return Res3Utils.result(res);
}
catch (e) {
logger.error(`${func_name} error:${e}`);
return Res3Utils.getErrorResult(e);
}
};
/**
* 邮件发送
* TODO: 关于status 字段 从老管理后台来看 0 代表无效 1 代表有效 但是数据库备注 0 有效 1 无效 service里面又查询 status = 0 的 ???
* @param req
* @param userMailLogVO
*/
export const mailSend = async (req: any, userMailLogVO: UserMailLogVO) => {
let func_name = "mUserSubscribe.control.mailSend";
try {
if (!userMailLogVO.subject || !userMailLogVO.content) {
throw ErrorCode.PARAM_MISS
}
let ip = isIp(req.ip) ? req.ip : '*.*.*.*';
let currentUserId = await getCurrentUserId(req.cookies.session_id);
let subjectBuf = Buffer.from(String(userMailLogVO.subject), 'base64');
let contentBuf = Buffer.from(String(userMailLogVO.content), 'base64');
userMailLogVO.subject = subjectBuf.toString('utf8');
userMailLogVO.content = contentBuf.toString('utf8');
let res = await mUserMailLogService.mailSend(userMailLogVO, currentUserId, ip);
return Res3Utils.result(res);
}
catch (e) {
logger.error(`${func_name} error:${e}`);
return Res3Utils.getErrorResult(e);
}
};
/**
* 群发邮件
* @param req
* @param userMailLogVO
*/
export const maiSendGroup = async (req: any, userMailLogVO: UserMailLogVO) => {
let func_name = "mUserSubscribe.control.maiSendGroup";
try {
if (!userMailLogVO.subject || !userMailLogVO.content || !userMailLogVO.uidStr) {
throw ErrorCode.PARAM_MISS
}
let ip = isIp(req.ip) ? req.ip : '*.*.*.*';
let currentUserId = await getCurrentUserId(req.cookies.session_id);
let subjectBuf = Buffer.from(String(userMailLogVO.subject), 'base64');
let contentBuf = Buffer.from(String(userMailLogVO.content), 'base64');
userMailLogVO.subject = subjectBuf.toString('utf8');
userMailLogVO.content = contentBuf.toString('utf8');
let uids = userMailLogVO.uidStr.split(",");
if (uids.length > 1000) {
throw ErrorCode.UID_TOO_MUCH;
}
for (let uid of uids) {
if (!uid) {
throw ErrorCode.UID_ILLEGALITY;
}
}
let res = await mUserMailLogService.maiSendGroup(userMailLogVO.subject, userMailLogVO.content, currentUserId, ip, uids);
return Res3Utils.result(res);
}
catch (e) {
logger.error(`${func_name} error:${e}`);
return Res3Utils.getErrorResult(e);
}
};
\ No newline at end of file
import * as usefulLinkService from "../service/usefulLink.service";
import { UsefulLinkVO, UsefulLinkPageVO } from "../service/usefulLink.service";
let { logger, Res3Utils, optionalUtils: Optional, apiAssertUtils: ApiAssert } = require('@madex/ex-js-public');
import { ErrorCode } from "../../../constant/errorCode";
import { IS_ACTIVE, IS_ACTIVE_ARR, LINK_TYPE_ARR } from "../../../constant/usefulLinkConst";
import { getCurrentUserId } from "../../../utils/aclUserUtils";
let isIp = require('is-ip');
/**
* 分页查询链接列表
* @param req
* @param infoVO
*/
export const list = async (req: any, usefulLinkPageVO: UsefulLinkPageVO) => {
let func_name = "usefulLinkCtrl.list";
try {
usefulLinkPageVO.page = Optional.opt(usefulLinkPageVO, 'page', 1);
usefulLinkPageVO.size = Optional.opt(usefulLinkPageVO, 'size', 20);
let res = await usefulLinkService.list(usefulLinkPageVO);
return Res3Utils.result(res);
}
catch (e) {
logger.error(`${func_name} error:${e}`);
return Res3Utils.getErrorResult(e);
}
};
/**
* 添加链接
* @param req
* @param infoVO
*/
export const add = async (req: any, usefulLinkVO: UsefulLinkVO) => {
let func_name = "usefulLinkCtrl.add";
try {
let ip = isIp(req.ip) ? req.ip : '*.*.*.*';
let currentUserId = await getCurrentUserId(req.cookies.session_id);
await paramValid(usefulLinkVO);
let res = await usefulLinkService.add(usefulLinkVO, currentUserId, ip);
return Res3Utils.result(res);
}
catch (e) {
logger.error(`${func_name} error:${e}`);
return Res3Utils.getErrorResult(e);
}
};
/**
* 修改链接
* @param req
* @param infoVO
*/
export const update = async (req: any, usefulLinkVO: UsefulLinkVO) => {
let func_name = "usefulLinkCtrl.update";
try {
let ip = isIp(req.ip) ? req.ip : '*.*.*.*';
let currentUserId = await getCurrentUserId(req.cookies.session_id);
await paramValid(usefulLinkVO);
let res = await usefulLinkService.update(usefulLinkVO, currentUserId, ip);
return Res3Utils.result(res);
}
catch (e) {
logger.error(`${func_name} error:${e}`);
return Res3Utils.getErrorResult(e);
}
};
/**
* 删除链接
* @param req
* @param authConfigVO
*/
export const del = async (req: any, usefulLinkVO: UsefulLinkVO) => {
let func_name = "usefulLinkCtrl.del";
try {
let ip = isIp(req.ip) ? req.ip : '*.*.*.*';
let currentUserId = await getCurrentUserId(req.cookies.session_id);
ApiAssert.notNull(ErrorCode.PARAM_MISS, usefulLinkVO.id);
let res = await usefulLinkService.del(Number(usefulLinkVO.id), currentUserId, ip);
return Res3Utils.result(res);
}
catch (e) {
logger.error(`${func_name} error:${e}`);
return Res3Utils.getErrorResult(e);
}
};
/**
* 链接详情
* @param req
* @param authConfigVO
*/
export const detail = async (req: any, usefulLinkVO: UsefulLinkVO) => {
let func_name = "usefulLinkCtrl.detail";
try {
ApiAssert.notNull(ErrorCode.PARAM_MISS, usefulLinkVO.id);
let res = await usefulLinkService.detail(Number(usefulLinkVO.id));
return Res3Utils.result(res);
}
catch (e) {
logger.error(`${func_name} error:${e}`);
return Res3Utils.getErrorResult(e);
}
};
async function paramValid(usefulLinkVO: UsefulLinkVO) {
let type = Number(usefulLinkVO.type);
let active = Number(usefulLinkVO.is_active);
if (!usefulLinkVO.name || !usefulLinkVO.url || !type || isNaN(Number(active))
|| !usefulLinkVO.weight || !usefulLinkVO.comment) {
throw ErrorCode.PARAM_MISS;
}
if (!IS_ACTIVE_ARR.includes(active) || !LINK_TYPE_ARR.includes(type)) {
throw ErrorCode.PARAM_MISS;
}
}
\ No newline at end of file
......@@ -23,7 +23,7 @@ export const TYPE = {
//实名
KYC: 9
}
export const recordOperateLog = async function (m_user_id: number, operate_user_id: number, operate_type: number,
export const recordMUserOperateLog = async function (m_user_id: number, operate_user_id: number, operate_type: number,
comment: string, before_v: string, after_v: string) {
let aclUser = await aclUserInfo.prototype.findOne({
where: {
......
import { ormDB, usefulLink, userMailLog, userSub } from "@madex/ex-ts-dao";
import { ErrorCode } from "../../../constant/errorCode";
import { addOptLog, LogType } from "./userOptLog.service";
import { sendEmail } from "../../../utils/mUserUtils";
import { TYPE_MAIL } from "../../../constant/statusConst";
let _ = require('lodash');
let { logger } = require('@madex/ex-js-public');
export interface UserMailLogVO {
id?: number;
user_id?: number;
is_kyc?: number;
lang?: string;
have_money?: number;
subject?: string,
content?: string,
page?: number,
size?: number
uidStr?: string
}
export async function history(page: number | undefined, size: number | undefined) {
let resList = await userMailLog.prototype.findAndCount({
limit: size,
offset: (Number(page) - 1) * Number(size),
order: [["id", "desc"]],
raw: true
});
return resList;
}
export async function mailDetail(id: number | undefined) {
let dbInfo = await userMailLog.prototype.findOne({
where: {
id: id
},
raw: true
});
if (!dbInfo) {
throw ErrorCode.DATA_NOT_EXIST;
}
return dbInfo;
}
export async function mailSend(userMailLogVO: UserMailLogVO, currentUserId: any, ip: string | undefined) {
let where = {
//TODO:0 ? 1 ???
status: 0
};
if (userMailLogVO.user_id) {
where["user_id"] = userMailLogVO.user_id
}
else {
let kyc = userMailLogVO.is_kyc;
if (kyc && (kyc == 1 || kyc == 2)) {
where["is_kyc"] = kyc
}
let haveMoney = userMailLogVO.have_money;
if (haveMoney && (haveMoney == 1 || haveMoney == 2)) {
where["have_money"] = haveMoney
}
if (userMailLogVO.lang) {
where["lang"] = userMailLogVO.lang
}
}
let toAddrRes = await userSub.prototype.findAll({
where: where
});
if (!toAddrRes.length) {
throw ErrorCode.DATA_NOT_EXIST
}
let params = {
subject: userMailLogVO.subject,
content: userMailLogVO.content
}
for (let sub of toAddrRes) {
//TODO:老管理后台就没有模版 ???
sendEmail(sub.sub, sub.user_id, "", params, userMailLogVO.lang);
}
let type = userMailLogVO.user_id ? TYPE_MAIL.TYPE_MAIL_LOG_SINGLE : getType(Number(userMailLogVO.is_kyc), Number(userMailLogVO.have_money));
let insertInfo = await userMailLog.prototype.create({
subject: userMailLogVO.subject,
content: userMailLogVO.content,
user_id: userMailLogVO.user_id,
type: type,
number: toAddrRes.length,
createdAt: new Date(),
updatedAt: new Date(),
});
//管理后台操作日志
addOptLog(currentUserId, `ip:${ip},发送订阅邮件:${JSON.stringify(insertInfo)}`, LogType.SUB_MAIL_SEND);
return insertInfo;
}
export async function maiSendGroup(subject: string, content: string, currentUserId: any, ip: string | undefined, uids: string[]) {
let mailLogList: any = [];
for (let uid of uids) {
let oneUid = Number(uid);
if (!oneUid) {
throw ErrorCode.UID_ILLEGALITY
}
let insertOne = {
subject: subject,
content: content,
user_id: oneUid,
type: TYPE_MAIL.TYPE_MAIL_LOG_SINGLE,
number: 1,
status: -1,
createdAt: new Date(),
updatedAt: new Date(),
}
mailLogList.push(insertOne);
}
let partList = _.chunk(mailLogList, 100);
let tx;
try {
tx = await ormDB.transaction();
for (let onePartList of partList) {
await userMailLog.prototype.bulkCreate(onePartList, {
transaction: tx
});
}
await tx.commit();
}
catch (e) {
if (tx) {
await tx.rollback();
}
logger.error(`maiSendGroup.bulkCreate.error:` + e);
}
//管理后台操作日志
addOptLog(currentUserId, `ip:${ip},群发邮件:主题:${JSON.stringify(subject)},用户:${uids}`, LogType.SUB_MAIL_SEND_GROUP);
return "success"
}
function getType(isKyc: number, haveMoney: number) {
if (haveMoney == 1 && isKyc == 1) {
return 1;
}
if (haveMoney == 1 && isKyc == 2) {
return 2;
}
if (haveMoney == 2 && isKyc == 1) {
return 3;
}
if (haveMoney == 2 && isKyc == 2) {
return 4;
}
return 5;
}
......@@ -10,7 +10,7 @@ import { addLog, MUserLogType } from "./mUserOptLog.service";
import { addOptLog, LogType } from "./userOptLog.service";
import { checkTotp } from "../../../utils/aclUserUtils";
import { getFatherUserId, getUidsByFatherUid } from "./mUserInfoSon.service";
import { recordOperateLog, TYPE } from "./mUserAccountOperateLog.service";
import { recordMUserOperateLog, TYPE } from "./mUserAccountOperateLog.service";
let _ = require('lodash');
let { logger, datetimeUtils } = require('@madex/ex-js-public');
......@@ -195,7 +195,7 @@ export async function updateUserEmail(currentUserId: number, m_user_id: any, ema
//管理后台操作日志
addOptLog(currentUserId, `ip:${ip},修改用户:${m_user_id} 邮箱,原邮箱: ${dbEmail},新邮箱:${email}`, LogType.UPDATE_MUSER_EMAIL);
//管理后台操作Madex 用户的日志
recordOperateLog(m_user_id, currentUserId, TYPE.MAIL, `ip:${ip},修改邮箱`, dbEmail, email);
recordMUserOperateLog(m_user_id, currentUserId, TYPE.MAIL, `ip:${ip},修改邮箱`, dbEmail, email);
return 'success';
}
......@@ -245,7 +245,7 @@ export async function lockAccount(currentUserId: number, m_user_id: any, ip: any
//管理后台操作日志
addOptLog(currentUserId, comment, LogType.LOCK_MUSER);
//管理后台操作Madex 用户的日志
recordOperateLog(fatherUserId, currentUserId, TYPE.ACCOUNT_STATUS, comment, "0", "1");
recordMUserOperateLog(fatherUserId, currentUserId, TYPE.ACCOUNT_STATUS, comment, "0", "1");
return 'success';
}
......@@ -301,7 +301,7 @@ export async function unlockAccount(currentUserId: number, m_user_id: any, ip: a
//管理后台操作日志
addOptLog(currentUserId, comment, LogType.UNLOCK_MUSER);
//管理后台操作Madex 用户的日志
recordOperateLog(fatherUserId, currentUserId, TYPE.ACCOUNT_STATUS, comment, "1", "0");
recordMUserOperateLog(fatherUserId, currentUserId, TYPE.ACCOUNT_STATUS, comment, "1", "0");
return 'success';
}
......@@ -315,7 +315,7 @@ export async function clearLoginLimit(currentUserId: number, m_user_id: any, ip:
//管理后台操作日志
addOptLog(currentUserId, `ip:${ip},清除用户登陆限制:${m_user_id}`, LogType.CLEAR_LOGIN_LIMIT);
//管理后台操作Madex 用户的日志
recordOperateLog(m_user_id, currentUserId, TYPE.TWO_HOUR_LIMIT, `ip:${ip},清除用户登陆限制:${m_user_id}`, "", "");
recordMUserOperateLog(m_user_id, currentUserId, TYPE.TWO_HOUR_LIMIT, `ip:${ip},清除用户登陆限制:${m_user_id}`, "", "");
return 'success';
}
......@@ -337,7 +337,7 @@ export async function clear24WithdrawLimit(currentUserId: number, m_user_id: any
//管理后台操作日志
addOptLog(currentUserId, `ip:${ip},清除用户24小时提现限制:${m_user_id}`, LogType.WITHDRAW_24_LIMIT);
//管理后台操作Madex 用户的日志
recordOperateLog(m_user_id, currentUserId, TYPE.TWENTY_FOUR_HOUR_LIMIT, `ip:${ip},清除用户24小时提现限制:${m_user_id}`, "", "");
recordMUserOperateLog(m_user_id, currentUserId, TYPE.TWENTY_FOUR_HOUR_LIMIT, `ip:${ip},清除用户24小时提现限制:${m_user_id}`, "", "");
return 'success';
}
......
......@@ -6,7 +6,7 @@ import { getMUserInfoByUid, sendEmail } from "../../../utils/mUserUtils";
import { EMAIL_FORBID_TEMPLATE, REAL_NAME_DENY, REAL_NAME_PASS } from "../../../constant/emailTemplateType";
import { KYC_STATUS, SETTING_FLAG } from "../../../constant/mUserInfoConst";
import { addOptLog, LogType } from "./userOptLog.service";
import { recordOperateLog, TYPE } from "./mUserAccountOperateLog.service";
import { recordMUserOperateLog, TYPE } from "./mUserAccountOperateLog.service";
let { logger, } = require('@madex/ex-js-public');
......@@ -205,7 +205,7 @@ export async function audit(queryVO: QueryVO, currentUserId: any, ip: string | u
//管理后台操作日志
addOptLog(currentUserId, `ip:${ip},实名认证审核:${dbUserInfo.user_id}${remark}`, LogType.KYC_AUDIT);
//管理后台操作Madex 用户的日志
recordOperateLog(dbUserInfo.user_id, currentUserId, TYPE.KYC, `ip:${ip},实名认证审核:${dbUserInfo.user_id}${remark}`, "", "");
recordMUserOperateLog(dbUserInfo.user_id, currentUserId, TYPE.KYC, `ip:${ip},实名认证审核:${dbUserInfo.user_id}${remark}`, "", "");
return 'success';
}
......
import { ormDB, usefulLink, userSub } from "@madex/ex-ts-dao";
import { ErrorCode } from "../../../constant/errorCode";
import { addOptLog, LogType } from "./userOptLog.service";
let _ = require('lodash');
let { logger } = require('@madex/ex-js-public');
export interface QueryVO {
id?: number;
user_id?: number | any;
is_kyc?: number;
have_money?: number;
page?: number,
size?: number
}
export async function list(queryVO: QueryVO) {
let where = Object.create(null);
if (queryVO.user_id) {
where.user_id = queryVO.user_id;
}
if (queryVO.is_kyc) {
where.is_kyc = queryVO.is_kyc;
}
if (queryVO.have_money) {
where.have_money = queryVO.have_money;
}
let resList = await userSub.prototype.findAndCount({
where: where,
limit: queryVO.size,
offset: (Number(queryVO.page) - 1) * Number(queryVO.size),
order: [["createdAt", "desc"]],
raw: true
});
return resList;
}
export async function del(id: number, currentUserId: any, ip: string | undefined) {
let dbInfo = await userSub.prototype.findOne({
where: {
id: id
},
raw: true
});
if (!dbInfo) {
throw ErrorCode.DATA_NOT_EXIST;
}
await userSub.prototype.update({
status: 0,
}, {
where: {
id: Number(id)
}
});
//管理后台操作日志
addOptLog(currentUserId, `ip:${ip},删除用户订阅:${JSON.stringify(dbInfo)}`, LogType.DEL);
return 'success'
}
export async function count() {
let res = {
money_and_kyc: 0,
money_and_no_kyc: 0,
no_money_and_kyc: 0,
no_money_and_no_kyc: 0,
}
let dbInfoList = await userSub.prototype.findAll({
where: {
status: 1
},
raw: true
});
if (dbInfoList.length) {
let kycMap = _.groupBy(dbInfoList, 'is_kyc');
let throughList = kycMap["1"] ? kycMap["1"] : [];
let noThroughList = kycMap["2"] ? kycMap["2"] : [];
if (throughList.length) {
let moneyMap = _.groupBy(throughList, 'have_money');
res.money_and_kyc = moneyMap["1"] && moneyMap["1"].length ? moneyMap["1"].length : 0
res.no_money_and_kyc = moneyMap["2"] && moneyMap["2"].length ? moneyMap["2"].length : 0
}
if (noThroughList.length) {
let moneyMap = _.groupBy(noThroughList, 'have_money');
res.money_and_no_kyc = moneyMap["1"] && moneyMap["1"].length ? moneyMap["1"].length : 0
res.no_money_and_no_kyc = moneyMap["2"] && moneyMap["2"].length ? moneyMap["2"].length : 0
}
}
return res;
}
import { ormDB, usefulLink } from "@madex/ex-ts-dao";
import { ErrorCode } from "../../../constant/errorCode";
import { addOptLog, LogType } from "./userOptLog.service";
let _ = require('lodash');
let { logger } = require('@madex/ex-js-public');
export interface UsefulLinkVO {
id?: number;
name?: string | any;
url?: string | any;
type?: number | any;
weight?: number;
is_active?: number;
status?: number;
time_per_day?: number;
comment?: string | any;
createdAt?: Date | any;
updatedAt?: Date | any;
}
export interface UsefulLinkPageVO extends UsefulLinkVO {
page?: number,
size?: number
}
export async function list(usefulLinkPageVO: UsefulLinkPageVO) {
let where = Object.create(null);
if (usefulLinkPageVO.name) {
where.name = { [ormDB.Op.like]: `${usefulLinkPageVO.name}%` };
}
if (usefulLinkPageVO.url) {
where.url = { [ormDB.Op.like]: `${usefulLinkPageVO.url}%` };
}
if (usefulLinkPageVO.type) {
if (!isNaN(Number(usefulLinkPageVO.type))){
where.type = usefulLinkPageVO.type;
}else {
let typeList = usefulLinkPageVO.type.split(",");
where.type = { [ormDB.Op.in]: typeList };
}
}
if (!isNaN(Number(usefulLinkPageVO.is_active))) {
where.is_active = usefulLinkPageVO.is_active;
}
let resList = await usefulLink.prototype.findAndCount({
where: where,
limit: usefulLinkPageVO.size,
offset: (Number(usefulLinkPageVO.page) - 1) * Number(usefulLinkPageVO.size),
order: [["weight", "desc"]],
raw: true
});
return resList;
}
export async function add(usefulLinkVO: UsefulLinkVO, currentUserId: any, ip: string | undefined) {
usefulLinkVO.createdAt = new Date();
usefulLinkVO.updatedAt = new Date();
let dbInfo = await usefulLink.prototype.create(usefulLinkVO);
//日志
//管理后台操作日志
addOptLog(currentUserId, `ip:${ip},添加链接记录:${JSON.stringify(dbInfo.id)}`, LogType.ADD);
return 'success'
}
export async function update(usefulLinkVO: UsefulLinkVO, currentUserId: any, ip: string | undefined) {
let dbInfo = await usefulLink.prototype.findOne({
where: {
id: usefulLinkVO.id
},
raw: true
});
if (!dbInfo) {
throw ErrorCode.DATA_NOT_EXIST;
}
let updateInfo = {
updatedAt: new Date()
};
if (usefulLinkVO.name) {
updateInfo["name"] = usefulLinkVO.name
}
if (usefulLinkVO.url) {
updateInfo["url"] = usefulLinkVO.url;
}
if (usefulLinkVO.type) {
updateInfo["type"] = usefulLinkVO.type;
}
if (usefulLinkVO.weight) {
updateInfo["weight"] = usefulLinkVO.weight;
}
if (!isNaN(Number(usefulLinkVO.status))) {
updateInfo["status"] = usefulLinkVO.status;
}
if (!isNaN(Number(usefulLinkVO.time_per_day))) {
updateInfo["time_per_day"] = usefulLinkVO.time_per_day;
}
if (!isNaN(Number(usefulLinkVO.is_active))) {
updateInfo["is_active"] = usefulLinkVO.is_active;
}
if (usefulLinkVO.comment) {
updateInfo["comment"] = usefulLinkVO.comment;
}
await usefulLink.prototype.update(updateInfo, {
where: {
id: Number(dbInfo.id)
}
});
//管理后台操作日志
addOptLog(currentUserId, `ip:${ip},修改链接记录:原记录:${JSON.stringify(dbInfo.id)}`, LogType.UPDATE);
return 'success'
}
export async function del(id: number, currentUserId: any, ip: string | undefined) {
let dbInfo = await usefulLink.prototype.findOne({
where: {
id: id
},
raw: true
});
if (!dbInfo) {
throw ErrorCode.DATA_NOT_EXIST;
}
await usefulLink.prototype.destroy({
where: {
id: Number(id)
}
});
//管理后台操作日志
addOptLog(currentUserId, `ip:${ip},删除链接记录:${JSON.stringify(dbInfo.id)}`, LogType.DEL);
return 'success'
}
export async function detail(id: number) {
let dbInfo = await usefulLink.prototype.findOne({
where: {
id: id
},
raw: true
});
if (!dbInfo) {
throw ErrorCode.DATA_NOT_EXIST;
}
return dbInfo;
}
......@@ -18,6 +18,8 @@ export const LogType = {
CLEAR_LOGIN_LIMIT: 10,//清除用户登陆限制
WITHDRAW_24_LIMIT: 11,//清除用户24小时限制
KYC_AUDIT: 12,//用户实名认证审核
SUB_MAIL_SEND: 13,//邮件订阅发送
SUB_MAIL_SEND_GROUP: 14,//邮件订阅群发
}
export const addOptLog = async function (user_id: any, msg: any, type: any, fail_reason?: any, session_id?: any) {
......
......@@ -23,6 +23,8 @@ import * as noticeCtrl from "../../mvc/control/notice.control";
import * as mUserOptLogCtrl from "../../mvc/control/mUserOptLog.control";
import * as mUserManageCtrl from "../../mvc/control/mUserManage.control";
import * as mUserRealNameCtrl from "../../mvc/control/mUserRealName.control";
import * as usefulLinkCtrl from "../../mvc/control/usefulLink.control";
import * as mUserSubscribeCtrl from "../../mvc/control/mUserSubscribe.control";
const getFunc = {
'user/info': userController.getUserInfo,
};
......@@ -105,6 +107,24 @@ const postFunc = {
'mUser/manage/kyc/oneDetail': mUserRealNameCtrl.oneDetail,//Madex 用户管理 ->kyc详情
'mUser/manage/kyc/audit': mUserRealNameCtrl.audit,//Madex 用户管理 ->kyc审核
//资源位管理
'link/useful/list': usefulLinkCtrl.list,//链接记录列表
'link/useful/add': usefulLinkCtrl.add,//添加链接记录
'link/useful/delete': usefulLinkCtrl.del,//删除链接记录
'link/useful/update': usefulLinkCtrl.update,//修改链接记录
'link/useful/detail': usefulLinkCtrl.detail,//链接记录详情
//邮件订阅
'mUser/subscribe/list': mUserSubscribeCtrl.list,//订阅列表
'mUser/subscribe/delete': mUserSubscribeCtrl.del,//删除订阅
'mUser/subscribe/count': mUserSubscribeCtrl.count,//订阅统计
'mUser/subscribe/mail/history': mUserSubscribeCtrl.history,//发送历史
'mUser/subscribe/mail/send': mUserSubscribeCtrl.mailSend,//邮件发送
'mUser/subscribe/mail/detail': mUserSubscribeCtrl.mailDetail,//发送详情
'mUser/subscribe/mail/send/group': mUserSubscribeCtrl.maiSendGroup,//群发邮件
};
......
......@@ -65,6 +65,19 @@ let cmdWhiteList = {
'mUser/manage/kyc/list': 1,
'mUser/manage/kyc/oneDetail': 1,
'mUser/manage/kyc/audit': 1,
'link/useful/list': 1,
'link/useful/add': 1,
'link/useful/delete': 1,
'link/useful/update': 1,
'link/useful/detail': 1,
'mUser/subscribe/list': 1,
'mUser/subscribe/delete': 1,
'mUser/subscribe/count': 1,
'mUser/subscribe/mail/history': 1,
'mUser/subscribe/mail/send': 1,
'mUser/subscribe/mail/detail': 1,
'mUser/subscribe/mail/send/group': 1,
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment