mirror of
https://github.com/Jinnrry/PMail.git
synced 2025-02-20 11:43:09 +08:00
v2.1
This commit is contained in:
parent
d18a665c79
commit
5ef5057032
97
fe/src/components/GroupSettings.vue
Normal file
97
fe/src/components/GroupSettings.vue
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
<template>
|
||||||
|
<div id="main">
|
||||||
|
<el-tree :expand-on-click-node="false" :data="data" :props="defaultProps" :defaultExpandAll="true" :class="node">
|
||||||
|
<template #default="{ node, data }">
|
||||||
|
<div>
|
||||||
|
<span v-if="data.id != -1"> {{ data.label }}</span>
|
||||||
|
<el-input v-if="data.id == -1" v-model="data.label" @blur="onInputBlur(data)"></el-input>
|
||||||
|
<el-button v-if="data.id != 0" @click="del(node, data)" size="small" type="danger" circle> -
|
||||||
|
</el-button>
|
||||||
|
<el-button v-if="data.id != 0" @click="add(data)" size="small" type="primary" circle> + </el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-tree>
|
||||||
|
|
||||||
|
<el-button @click="addRoot">{{ lang.add_group }}</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import $http from "../http/http";
|
||||||
|
import { reactive, ref } from 'vue'
|
||||||
|
import lang from '../i18n/i18n';
|
||||||
|
|
||||||
|
const data = reactive([])
|
||||||
|
|
||||||
|
$http.get('/api/group').then(res => {
|
||||||
|
data.push(...res.data)
|
||||||
|
})
|
||||||
|
|
||||||
|
const del = function (node, data) {
|
||||||
|
if (data.id != -1) {
|
||||||
|
$http.post("/api/group/del", { "id": data.id }).then(res => {
|
||||||
|
if (res.errorNo != 0) {
|
||||||
|
ElMessage({
|
||||||
|
message: res.errorMsg,
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const pc = node.parent.childNodes
|
||||||
|
for (let i = 0; i < pc.length; i++) {
|
||||||
|
if (pc[i].id == node.id) {
|
||||||
|
pc.splice(i, 1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const pc = node.parent.childNodes
|
||||||
|
for (let i = 0; i < pc.length; i++) {
|
||||||
|
if (pc[i].id == node.id) {
|
||||||
|
pc.splice(i, 1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const add = function (item) {
|
||||||
|
if (item.children == null) {
|
||||||
|
item.children = []
|
||||||
|
}
|
||||||
|
item.children.push({
|
||||||
|
"children": [],
|
||||||
|
"label": "",
|
||||||
|
"id": "-1",
|
||||||
|
"parent_id": item.id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const addRoot = function () {
|
||||||
|
data.push({
|
||||||
|
"children": [],
|
||||||
|
"label": "",
|
||||||
|
"id": "-1",
|
||||||
|
"parent_id": 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onInputBlur = function (item) {
|
||||||
|
if (item.label != "") {
|
||||||
|
$http.post("/api/group/add", { "name": item.label, "parent_id": item.parent_id }).then(res => {
|
||||||
|
if (res.errorNo != 0) {
|
||||||
|
ElMessage({
|
||||||
|
message: res.errorMsg,
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
$http.get('/api/group').then(res => {
|
||||||
|
data.splice(0, data.length)
|
||||||
|
data.push(...res.data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -61,4 +61,10 @@ const handleNodeClick = function (data) {
|
|||||||
.el-tree {
|
.el-tree {
|
||||||
background-color: #F1F1F1;
|
background-color: #F1F1F1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.add_group{
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: left;
|
||||||
|
padding-left: 15px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
@ -8,11 +8,15 @@
|
|||||||
<Setting style="color:#FFFFFF" />
|
<Setting style="color:#FFFFFF" />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
<el-drawer v-model="openSettings" :title="lang.settings">
|
<el-drawer v-model="openSettings" size="80%" :title="lang.settings">
|
||||||
<el-tabs tab-position="left" >
|
<el-tabs tab-position="left" >
|
||||||
<el-tab-pane :label="lang.security">
|
<el-tab-pane :label="lang.security">
|
||||||
<SecuritySettings/>
|
<SecuritySettings/>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
|
||||||
|
<el-tab-pane :label="lang.group_settings">
|
||||||
|
<GroupSettings/>
|
||||||
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
|
|
||||||
@ -25,6 +29,7 @@ import { ref } from 'vue'
|
|||||||
import { ElMessageBox } from 'element-plus'
|
import { ElMessageBox } from 'element-plus'
|
||||||
import SecuritySettings from '@/components/SecuritySettings.vue'
|
import SecuritySettings from '@/components/SecuritySettings.vue'
|
||||||
import lang from '../i18n/i18n';
|
import lang from '../i18n/i18n';
|
||||||
|
import GroupSettings from './GroupSettings.vue';
|
||||||
|
|
||||||
|
|
||||||
const openSettings = ref(false)
|
const openSettings = ref(false)
|
||||||
|
@ -58,6 +58,13 @@ var lang = {
|
|||||||
"ssl_manuallyf": "Manually configure an SSL certificate",
|
"ssl_manuallyf": "Manually configure an SSL certificate",
|
||||||
"ssl_key_path": "ssl key file path",
|
"ssl_key_path": "ssl key file path",
|
||||||
"ssl_crt_path": "ssl crt file path",
|
"ssl_crt_path": "ssl crt file path",
|
||||||
|
"group_settings": "Group",
|
||||||
|
"add_group": "Add Group",
|
||||||
|
"del_email_confirm": "Are you sure you want to delete them?",
|
||||||
|
"move_email_confirm": "Are you sure you want to move them?",
|
||||||
|
"del_btn": "Delete",
|
||||||
|
"move_btn": "Move",
|
||||||
|
"read_btn": "Readed"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -122,6 +129,13 @@ var zhCN = {
|
|||||||
"ssl_manuallyf": "手动配置SSL证书",
|
"ssl_manuallyf": "手动配置SSL证书",
|
||||||
"ssl_key_path": "ssl key文件位置",
|
"ssl_key_path": "ssl key文件位置",
|
||||||
"ssl_crt_path": "ssl crt文件位置",
|
"ssl_crt_path": "ssl crt文件位置",
|
||||||
|
"group_settings": "分组",
|
||||||
|
"add_group": "新建分组",
|
||||||
|
"del_email_confirm": "你确定要删除吗?",
|
||||||
|
"move_email_confirm": "你确定要移动这些邮件吗?",
|
||||||
|
"del_btn": "删除",
|
||||||
|
"move_btn": "移动",
|
||||||
|
"read_btn": "已读"
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (navigator.language) {
|
switch (navigator.language) {
|
||||||
|
@ -4,11 +4,27 @@
|
|||||||
<div id="action">
|
<div id="action">
|
||||||
<RouterLink to="/editer">+{{ lang.compose }}</RouterLink>
|
<RouterLink to="/editer">+{{ lang.compose }}</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div id="action">全部标记为已读</div> -->
|
|
||||||
</div>
|
</div>
|
||||||
<div id="title">{{ groupStore.name }}</div>
|
<div id="title">{{ groupStore.name }}</div>
|
||||||
|
<div id="action">
|
||||||
|
<el-button @click="del" size="small">{{ lang.del_btn }}</el-button>
|
||||||
|
<el-button @click="markRead" size="small">{{ lang.read_btn }}</el-button>
|
||||||
|
<el-dropdown style="margin-left: 12px;">
|
||||||
|
<el-button size="small">
|
||||||
|
{{ lang.move_btn }}
|
||||||
|
<el-icon class="el-icon--right"><arrow-down /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item @click="move(group.id)" v-for="group in groupList">{{ group.name
|
||||||
|
}}</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</div>
|
||||||
<div id="table">
|
<div id="table">
|
||||||
<el-table :data="data" :show-header="true" :border="false" @row-click="rowClick" :row-style="rowStyle">
|
<el-table ref="taskTableDataRef" @selection-change="selectionLineChange" :data="data" :show-header="true"
|
||||||
|
:border="false" @row-click="rowClick" :row-style="rowStyle">
|
||||||
<el-table-column type="selection" width="30" />
|
<el-table-column type="selection" width="30" />
|
||||||
<el-table-column prop="title" label="" width="50">
|
<el-table-column prop="title" label="" width="50">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
@ -49,7 +65,7 @@
|
|||||||
</el-table>
|
</el-table>
|
||||||
</div>
|
</div>
|
||||||
<div id="pagination">
|
<div id="pagination">
|
||||||
<el-pagination background layout="prev, pager, next" :page-count="totalPage" />
|
<el-pagination background layout="prev, pager, next" :page-count="totalPage" @current-change="pageChange" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -58,19 +74,18 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import $http from "../http/http";
|
import $http from "../http/http";
|
||||||
|
import { ArrowDown } from '@element-plus/icons-vue'
|
||||||
import { RouterLink } from 'vue-router'
|
import { RouterLink } from 'vue-router'
|
||||||
import { reactive, ref, watch } from 'vue'
|
import { reactive, ref, watch } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
|
||||||
import router from "@/router"; //根路由对象
|
import router from "@/router"; //根路由对象
|
||||||
import useGroupStore from '../stores/group'
|
import useGroupStore from '../stores/group'
|
||||||
import lang from '../i18n/i18n';
|
import lang from '../i18n/i18n';
|
||||||
|
|
||||||
const groupStore = useGroupStore()
|
const groupStore = useGroupStore()
|
||||||
|
|
||||||
const route = useRoute()
|
const groupList = ref([])
|
||||||
|
|
||||||
|
|
||||||
|
const taskTableDataRef = ref(null)
|
||||||
|
|
||||||
let tag = groupStore.tag;
|
let tag = groupStore.tag;
|
||||||
|
|
||||||
@ -96,29 +111,140 @@ watch(groupStore, async (newV, oldV) => {
|
|||||||
const data = ref([])
|
const data = ref([])
|
||||||
const totalPage = ref(0)
|
const totalPage = ref(0)
|
||||||
|
|
||||||
$http.post("/api/email/list", { tag: tag, page_size: 10 }).then(res => {
|
const updateList = function () {
|
||||||
|
$http.post("/api/email/list", { tag: tag, page_size: 10 }).then(res => {
|
||||||
data.value = res.data.list
|
data.value = res.data.list
|
||||||
totalPage.value = res.data.total_page
|
totalPage.value = res.data.total_page
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateGroupList = function () {
|
||||||
|
$http.post("/api/group/list").then(res => {
|
||||||
|
groupList.value = res.data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
updateList()
|
||||||
|
updateGroupList()
|
||||||
|
|
||||||
const rowClick = function (row, column, event) {
|
const rowClick = function (row, column, event) {
|
||||||
router.push("/detail/" + row.id)
|
router.push("/detail/" + row.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const markRead = function () {
|
||||||
|
let rows = taskTableDataRef.value?.getSelectionRows()
|
||||||
|
let ids = []
|
||||||
|
rows.forEach(element => {
|
||||||
|
ids.push(element.id)
|
||||||
|
});
|
||||||
|
|
||||||
|
$http.post("/api/email/read", { "ids": ids }).then(res => {
|
||||||
|
if (res.errorNo == 0) {
|
||||||
|
updateList()
|
||||||
|
} else {
|
||||||
|
ElMessage({
|
||||||
|
type: 'error',
|
||||||
|
message: res.errorMsg,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const move = function (group_id) {
|
||||||
|
let rows = taskTableDataRef.value?.getSelectionRows()
|
||||||
|
let ids = []
|
||||||
|
rows.forEach(element => {
|
||||||
|
ids.push(element.id)
|
||||||
|
});
|
||||||
|
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
lang.move_email_confirm,
|
||||||
|
'Warning',
|
||||||
|
{
|
||||||
|
confirmButtonText: 'OK',
|
||||||
|
cancelButtonText: 'Cancel',
|
||||||
|
type: 'warning',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
$http.post("/api/email/move", { "group_id": group_id, "ids": ids }).then(res => {
|
||||||
|
if (res.errorNo == 0) {
|
||||||
|
updateList()
|
||||||
|
ElMessage({
|
||||||
|
type: 'success',
|
||||||
|
message: 'Move completed',
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
ElMessage({
|
||||||
|
type: 'error',
|
||||||
|
message: res.errorMsg,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const del = function () {
|
||||||
|
let rows = taskTableDataRef.value?.getSelectionRows()
|
||||||
|
let ids = []
|
||||||
|
rows.forEach(element => {
|
||||||
|
ids.push(element.id)
|
||||||
|
});
|
||||||
|
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
lang.del_email_confirm,
|
||||||
|
'Warning',
|
||||||
|
{
|
||||||
|
confirmButtonText: 'OK',
|
||||||
|
cancelButtonText: 'Cancel',
|
||||||
|
type: 'warning',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
$http.post("/api/email/del", { "ids": ids }).then(res => {
|
||||||
|
if (res.errorNo == 0) {
|
||||||
|
updateList()
|
||||||
|
ElMessage({
|
||||||
|
type: 'success',
|
||||||
|
message: 'Delete completed',
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
ElMessage({
|
||||||
|
type: 'error',
|
||||||
|
message: res.errorMsg,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const rowStyle = function ({ row, rowIndwx }) {
|
const rowStyle = function ({ row, rowIndwx }) {
|
||||||
return { 'cursor': 'pointer' }
|
return { 'cursor': 'pointer' }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pageChange = function (p) {
|
||||||
|
$http.post("/api/email/list", { tag: tag, page_size: 10, current_page: p }).then(res => {
|
||||||
|
data.value = res.data.list
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
#action {
|
#action {
|
||||||
text-align: left;
|
display: flex;
|
||||||
font-size: 20px;
|
flex-direction: row;
|
||||||
line-height: 40px;
|
|
||||||
padding-left: 10px;
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="main">
|
<div id="main">
|
||||||
<div id="form">
|
<div id="form">
|
||||||
<el-form :model="form" label-width="120px">
|
<el-form :model="form" label-width="120px" @keyup.enter.native="onSubmit">
|
||||||
<el-form-item :label="lang.account">
|
<el-form-item :label="lang.account">
|
||||||
<el-input v-model="form.account" placeholder="User Name" />
|
<el-input v-model="form.account" placeholder="User Name" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
@ -26,7 +26,6 @@ export default defineConfig({
|
|||||||
proxy: {
|
proxy: {
|
||||||
"/api": "http://127.0.0.1/",
|
"/api": "http://127.0.0.1/",
|
||||||
"/attachments":"http://127.0.0.1/"
|
"/attachments":"http://127.0.0.1/"
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"logLevel": "debug",
|
"logLevel": "info",
|
||||||
"domain": "domain.com",
|
"domain": "domain.com",
|
||||||
"webDomain": "mail.domain.com",
|
"webDomain": "mail.domain.com",
|
||||||
"dkimPrivateKeyPath": "config/dkim/dkim.priv",
|
"dkimPrivateKeyPath": "config/dkim/dkim.priv",
|
||||||
@ -12,5 +12,6 @@
|
|||||||
"weChatPushSecret": "",
|
"weChatPushSecret": "",
|
||||||
"weChatPushTemplateId": "",
|
"weChatPushTemplateId": "",
|
||||||
"weChatPushUserId": "",
|
"weChatPushUserId": "",
|
||||||
"isInit": false
|
"isInit": true,
|
||||||
|
"httpsEnabled": 2
|
||||||
}
|
}
|
@ -33,7 +33,7 @@ type Config struct {
|
|||||||
//go:embed tables/*
|
//go:embed tables/*
|
||||||
var tableConfig embed.FS
|
var tableConfig embed.FS
|
||||||
|
|
||||||
const Version = "2.0.1"
|
const Version = "2.1.0"
|
||||||
|
|
||||||
const DBTypeMySQL = "mysql"
|
const DBTypeMySQL = "mysql"
|
||||||
const DBTypeSQLite = "sqlite"
|
const DBTypeSQLite = "sqlite"
|
||||||
|
@ -12,6 +12,6 @@
|
|||||||
"weChatPushSecret": "",
|
"weChatPushSecret": "",
|
||||||
"weChatPushTemplateId": "",
|
"weChatPushTemplateId": "",
|
||||||
"weChatPushUserId": "",
|
"weChatPushUserId": "",
|
||||||
"isInit": false,
|
"isInit": true,
|
||||||
"httpsEnabled": 0
|
"httpsEnabled": 2
|
||||||
}
|
}
|
17
server/config/config_mysql.json
Normal file
17
server/config/config_mysql.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"logLevel": "info",
|
||||||
|
"domain": "domain.com",
|
||||||
|
"webDomain": "mail.domain.com",
|
||||||
|
"dkimPrivateKeyPath": "config/dkim/dkim.priv",
|
||||||
|
"sslType": "0",
|
||||||
|
"SSLPrivateKeyPath": "config/ssl/private.key",
|
||||||
|
"SSLPublicKeyPath": "config/ssl/public.crt",
|
||||||
|
"dbDSN": "root:root@tcp(127.0.0.1:3306)/pmail?parseTime=True&loc=Local",
|
||||||
|
"dbType": "mysql",
|
||||||
|
"weChatPushAppId": "",
|
||||||
|
"weChatPushSecret": "",
|
||||||
|
"weChatPushTemplateId": "",
|
||||||
|
"weChatPushUserId": "",
|
||||||
|
"isInit": true,
|
||||||
|
"httpsEnabled": 2
|
||||||
|
}
|
1
server/config/dkim/README.md
Normal file
1
server/config/dkim/README.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
这里存储的秘钥仅开发测试使用,线上环境请重新生成!
|
@ -0,0 +1,16 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANgOYlTcSbWqw54y
|
||||||
|
ISRYq6keu/fPj/m7CiVaXtOc9bJhI5uBsd8Jz1eIRzNmqLITL2e5lz2vpQAmpk3z
|
||||||
|
8Qd2iGCbZBwMXcfPVhAasGH367GRnJw3QPybA0Maob8CnfKDH6Pks3bX7xeef/U3
|
||||||
|
ufHYnwgPZUi121ECvPjZLEmXpBrvAgMBAAECgYAxdeGG4cMyBnyvy3QQ2Qe7OKD5
|
||||||
|
Uxf3qJzi/jQ1J3qLsncvU1p/38QKmtUJ7Fd0JLY2faMk6P/R8AckU1L7TWRcpafY
|
||||||
|
fUU4xpuTHpBnMhglOGjEoOfSUFh9iieG8cVreozOOfihFRgRUxu6zfxycymHjGxF
|
||||||
|
WbdK1zoNLgFgM0DWEQJBAOnkLf4P+pDMCeA/60pll39gIuW+AlMpWrjuA3Yhdu4B
|
||||||
|
BvC5ea3fIVCWiksfBKihXDZkLG9PZDipy2WiNypPx0kCQQDsep7qHiBPpcp4a9OU
|
||||||
|
KXolG771iHODId2Nc3zez2xG+2pY5BzoHD2TFdW1/5v9d4q+6u6dUb8v/t2GrMIQ
|
||||||
|
wrh3AkEAiO0Dm+wA1YoN8hGZjqlhArnmVDdjpwnbyc3Viu/Wb0l8par/uDGbkFFB
|
||||||
|
Tu8uzAYDNPh6JwQEeUO2Bp7rysJ/uQJBAJKq7rsX2kRr+Gq9vaksHHS9g693ZOVU
|
||||||
|
8LuVgEIU9fwEXQ4q1P7k3Q/HwBe0JESNiwEkZsAt/l0/PpgTt/17N7sCQBQKcd7e
|
||||||
|
++RuJCiMc0vMSYAKAmiARJHv/YoxS4tLngtrhu0h5uhr+35c0kJvy6nX0VBb5KV8
|
||||||
|
hUK3axAHTSO793o=
|
||||||
|
-----END PRIVATE KEY-----
|
@ -0,0 +1 @@
|
|||||||
|
v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYDmJU3Em1qsOeMiEkWKupHrv3z4/5uwolWl7TnPWyYSObgbHfCc9XiEczZqiyEy9nuZc9r6UAJqZN8/EHdohgm2QcDF3Hz1YQGrBh9+uxkZycN0D8mwNDGqG/Ap3ygx+j5LN21+8Xnn/1N7nx2J8ID2VItdtRArz42SxJl6Qa7wIDAQAB
|
1
server/config/ssl/README.md
Normal file
1
server/config/ssl/README.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
这里存储的秘钥仅开发测试使用,线上环境请重新生成!
|
@ -0,0 +1,15 @@
|
|||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIICXQIBAAKBgQDhh1fqAdYCSifPaCGfm2CjngcPaS7XehYdojBPPp5oEorWZFo1
|
||||||
|
3Rghfh7qIXYp4nruUwfKgIPM95nYNqTnXolhUm5ywYDAhfJquuquJ7cAPhyx4SwG
|
||||||
|
o58RgcJCM04yVKdxivskMiJvgUDz1ymdf6OA7MMbgGEcdky73TdC3FkfZQIDAQAB
|
||||||
|
AoGAe5shPPj6oVChVxSccQzIv4QqHHEqoiCgpGczEQuh6CpZe72Oj7z4r8qfCPWD
|
||||||
|
/NrLQ3mwaHVdR2ZhJFZ2tPRkWDI6hlK3IrC7A9LGoYCeV0AiDgMvzhQrWFjaw92u
|
||||||
|
V9OS7tgYkewimcDaK1eF1hH+GOh2aToWMKAgCCOoXjuVhsECQQD4lc/4/Km7hrEQ
|
||||||
|
JQGLOnB4qxdFk3HflShsXjnc/ydapQ5zCzCuCO7X89Jr8KpegC1SVmFR3YRjPNG4
|
||||||
|
R36yLR8RAkEA6EF45wYmQfAi9w0AMlmnXanHcWXBauOrbqv2M9cjpsBvxcIjRvHp
|
||||||
|
fca/LjEBf74X4YBhCCN1P7zRPLO2tYJjFQJAFdsGF/wO6D/lXWgDhLw0m0dfmmxm
|
||||||
|
PKQek7iNGdMNILkWViMLuqFqbm4vd/IG6JwYX/7cO5hgRWFZhvwyNXQmIQJBAOPX
|
||||||
|
Dqb79l3rGDHpVA8Qukn8+sV4gBS+wXcxRLY4UCYOU9fZikfXmymi5fuHYaQSNFUo
|
||||||
|
XofgWO4s6co1toA7J70CQQDym7XIGA0GTtUtBpvRU2ew3d6JItMFjzQCjgKJ/4zm
|
||||||
|
VqHIzyGoikq8jvbAqGJqRU72F/jfWEZlQO1KZemhmd/S
|
||||||
|
-----END RSA PRIVATE KEY-----
|
@ -0,0 +1,16 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICcjCCAdsCFDLeR0jPO+9Q/nadNrHGI3EG2eHoMA0GCSqGSIb3DQEBCwUAMHgx
|
||||||
|
CzAJBgNVBAYTAkNOMQswCQYDVQQIDAJCSjEQMA4GA1UEBwwHQmVpSmluZzENMAsG
|
||||||
|
A1UECgwETnVsbDENMAsGA1UECwwETnVsbDEOMAwGA1UEAwwFUE1haWwxHDAaBgkq
|
||||||
|
hkiG9w0BCQEWDWlAamlubnJyeS5jb20wHhcNMjMwODIzMTMyMTM0WhcNMzMwODIw
|
||||||
|
MTMyMTM0WjB4MQswCQYDVQQGEwJDTjELMAkGA1UECAwCQkoxEDAOBgNVBAcMB0Jl
|
||||||
|
aUppbmcxDTALBgNVBAoMBE51bGwxDTALBgNVBAsMBE51bGwxDjAMBgNVBAMMBVBN
|
||||||
|
YWlsMRwwGgYJKoZIhvcNAQkBFg1pQGppbm5ycnkuY29tMIGfMA0GCSqGSIb3DQEB
|
||||||
|
AQUAA4GNADCBiQKBgQDhh1fqAdYCSifPaCGfm2CjngcPaS7XehYdojBPPp5oEorW
|
||||||
|
ZFo13Rghfh7qIXYp4nruUwfKgIPM95nYNqTnXolhUm5ywYDAhfJquuquJ7cAPhyx
|
||||||
|
4SwGo58RgcJCM04yVKdxivskMiJvgUDz1ymdf6OA7MMbgGEcdky73TdC3FkfZQID
|
||||||
|
AQABMA0GCSqGSIb3DQEBCwUAA4GBAMR6M83L2V9YFcYLxUv3Vaf7KrSSvuiGl/6H
|
||||||
|
e2bMxboC8NBdsmRRhuKamti+NOe7i+BXTZ9TSy3zLQGK5LNvNOnWHHGj4vmVXoUV
|
||||||
|
rFBMY1Vf2ZiEtO0OQjEcLOpzXhVWyZuDt2HhMRj92ESeXUSCMPZWT2UfZZTm0fhv
|
||||||
|
ORq+I8O9
|
||||||
|
-----END CERTIFICATE-----
|
@ -2,6 +2,7 @@ CREATE table email
|
|||||||
(
|
(
|
||||||
id INT unsigned AUTO_INCREMENT PRIMARY KEY COMMENT '自增id',
|
id INT unsigned AUTO_INCREMENT PRIMARY KEY COMMENT '自增id',
|
||||||
type tinyint(4) NOT NULL DEFAULT 0 COMMENT '邮件类型,0:收到的邮件,1:发送的邮件',
|
type tinyint(4) NOT NULL DEFAULT 0 COMMENT '邮件类型,0:收到的邮件,1:发送的邮件',
|
||||||
|
group_id int unsigned NOT NULL DEFAULT 0 COMMENT '分组id',
|
||||||
subject varchar(1000) NOT NULL DEFAULT '' COMMENT '邮件标题',
|
subject varchar(1000) NOT NULL DEFAULT '' COMMENT '邮件标题',
|
||||||
reply_to json COMMENT '回复人',
|
reply_to json COMMENT '回复人',
|
||||||
from_name varchar(50) NOT NULL DEFAULT '' COMMENT '发件人名称',
|
from_name varchar(50) NOT NULL DEFAULT '' COMMENT '发件人名称',
|
||||||
|
7
server/config/tables/mysql/group.sql
Normal file
7
server/config/tables/mysql/group.sql
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
CREATE TABLE `group`
|
||||||
|
(
|
||||||
|
id INT unsigned AUTO_INCREMENT PRIMARY KEY COMMENT '自增id',
|
||||||
|
name varchar(10) NOT NULL DEFAULT '' COMMENT '分组名称',
|
||||||
|
user_id INT unsigned NOT NULL DEFAULT 0 COMMENT '用户id',
|
||||||
|
parent_id INT unsigned COMMENT '父级组ID'
|
||||||
|
)COMMENT='分组信息表'
|
@ -1,17 +1,18 @@
|
|||||||
CREATE table email
|
CREATE table email
|
||||||
(
|
(
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
type tinyint(4) NOT NULL DEFAULT 0 ,
|
type tinyint(4) NOT NULL DEFAULT 0,
|
||||||
subject varchar(1000) NOT NULL DEFAULT '' ,
|
group_id INTEGER NOT NULL DEFAULT 0,
|
||||||
reply_to json ,
|
subject varchar(1000) NOT NULL DEFAULT '',
|
||||||
from_name varchar(50) NOT NULL DEFAULT '' ,
|
reply_to json,
|
||||||
from_address varchar(150) NOT NULL DEFAULT '' ,
|
from_name varchar(50) NOT NULL DEFAULT '',
|
||||||
`to` json ,
|
from_address varchar(150) NOT NULL DEFAULT '',
|
||||||
bcc json ,
|
`to` json,
|
||||||
cc json ,
|
bcc json,
|
||||||
`text` text ,
|
cc json,
|
||||||
html text ,
|
`text` text,
|
||||||
sender json ,
|
html text,
|
||||||
|
sender json,
|
||||||
attachments json ,
|
attachments json ,
|
||||||
spf_check tinyint(1) DEFAULT 0 ,
|
spf_check tinyint(1) DEFAULT 0 ,
|
||||||
dkim_check tinyint(1) DEFAULT 0 ,
|
dkim_check tinyint(1) DEFAULT 0 ,
|
||||||
|
7
server/config/tables/sqlite/group.sql
Normal file
7
server/config/tables/sqlite/group.sql
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
CREATE TABLE `group`
|
||||||
|
(
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name varchar(10) NOT NULL DEFAULT '',
|
||||||
|
parent_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
user_id INTEGER NOT NULL DEFAULT 0
|
||||||
|
)
|
40
server/controllers/email/delete.go
Normal file
40
server/controllers/email/delete.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package email
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"pmail/dto"
|
||||||
|
"pmail/dto/response"
|
||||||
|
"pmail/services/del_email"
|
||||||
|
)
|
||||||
|
|
||||||
|
type emailDeleteRequest struct {
|
||||||
|
IDs []int `json:"ids"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func EmailDelete(ctx *dto.Context, w http.ResponseWriter, req *http.Request) {
|
||||||
|
reqBytes, err := io.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.WithContext(ctx).Errorf("%+v", err)
|
||||||
|
}
|
||||||
|
var reqData emailDeleteRequest
|
||||||
|
err = json.Unmarshal(reqBytes, &reqData)
|
||||||
|
if err != nil {
|
||||||
|
log.WithContext(ctx).Errorf("%+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(reqData.IDs) <= 0 {
|
||||||
|
response.NewErrorResponse(response.ParamsError, "ID错误", "").FPrint(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = del_email.DelEmail(ctx, reqData.IDs)
|
||||||
|
if err != nil {
|
||||||
|
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.NewSuccessResponse("success").FPrint(w)
|
||||||
|
|
||||||
|
}
|
40
server/controllers/email/move.go
Normal file
40
server/controllers/email/move.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package email
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"pmail/dto"
|
||||||
|
"pmail/dto/response"
|
||||||
|
"pmail/services/group"
|
||||||
|
)
|
||||||
|
|
||||||
|
type moveRequest struct {
|
||||||
|
GroupId int `json:"group_id"`
|
||||||
|
IDs []int `json:"ids"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Move(ctx *dto.Context, w http.ResponseWriter, req *http.Request) {
|
||||||
|
reqBytes, err := io.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.WithContext(ctx).Errorf("%+v", err)
|
||||||
|
}
|
||||||
|
var reqData moveRequest
|
||||||
|
err = json.Unmarshal(reqBytes, &reqData)
|
||||||
|
if err != nil {
|
||||||
|
log.WithContext(ctx).Errorf("%+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(reqData.IDs) <= 0 {
|
||||||
|
response.NewErrorResponse(response.ParamsError, "ID错误", "").FPrint(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !group.MoveMailToGroup(ctx, reqData.IDs, reqData.GroupId) {
|
||||||
|
response.NewErrorResponse(response.ServerError, "Error", "").FPrint(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.NewSuccessResponse("success").FPrint(w)
|
||||||
|
|
||||||
|
}
|
43
server/controllers/email/read.go
Normal file
43
server/controllers/email/read.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package email
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"pmail/dto"
|
||||||
|
"pmail/dto/response"
|
||||||
|
"pmail/services/detail"
|
||||||
|
)
|
||||||
|
|
||||||
|
type markReadRequest struct {
|
||||||
|
IDs []int `json:"ids"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func MarkRead(ctx *dto.Context, w http.ResponseWriter, req *http.Request) {
|
||||||
|
reqBytes, err := io.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.WithContext(ctx).Errorf("%+v", err)
|
||||||
|
}
|
||||||
|
var reqData markReadRequest
|
||||||
|
err = json.Unmarshal(reqBytes, &reqData)
|
||||||
|
if err != nil {
|
||||||
|
log.WithContext(ctx).Errorf("%+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(reqData.IDs) <= 0 {
|
||||||
|
response.NewErrorResponse(response.ParamsError, "ID错误", "").FPrint(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, id := range reqData.IDs {
|
||||||
|
detail.GetEmailDetail(ctx, id, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.NewSuccessResponse("success").FPrint(w)
|
||||||
|
|
||||||
|
}
|
@ -1,27 +1,32 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"pmail/db"
|
||||||
"pmail/dto"
|
"pmail/dto"
|
||||||
"pmail/dto/response"
|
"pmail/dto/response"
|
||||||
"pmail/i18n"
|
"pmail/i18n"
|
||||||
|
"pmail/services/group"
|
||||||
|
"pmail/utils/array"
|
||||||
)
|
)
|
||||||
|
|
||||||
type groupItem struct {
|
func GetUserGroupList(ctx *dto.Context, w http.ResponseWriter, req *http.Request) {
|
||||||
Label string `json:"label"`
|
infos := group.GetGroupList(ctx)
|
||||||
Tag string `json:"tag"`
|
response.NewSuccessResponse(infos).FPrint(w)
|
||||||
Children []*groupItem `json:"children"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUserGroup(ctx *dto.Context, w http.ResponseWriter, req *http.Request) {
|
func GetUserGroup(ctx *dto.Context, w http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
retData := []*groupItem{
|
retData := []*group.GroupItem{
|
||||||
{
|
{
|
||||||
Label: i18n.GetText(ctx.Lang, "all_email"),
|
Label: i18n.GetText(ctx.Lang, "all_email"),
|
||||||
Children: []*groupItem{
|
Children: []*group.GroupItem{
|
||||||
{
|
{
|
||||||
Label: i18n.GetText(ctx.Lang, "inbox"),
|
Label: i18n.GetText(ctx.Lang, "inbox"),
|
||||||
Tag: dto.SearchTag{Type: 0, Status: -1}.ToString(),
|
Tag: dto.SearchTag{Type: 0, Status: -1, GroupId: 0}.ToString(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Label: i18n.GetText(ctx.Lang, "outbox"),
|
Label: i18n.GetText(ctx.Lang, "outbox"),
|
||||||
@ -35,5 +40,59 @@ func GetUserGroup(ctx *dto.Context, w http.ResponseWriter, req *http.Request) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
retData = array.Merge(retData, group.GetGroupInfoList(ctx))
|
||||||
|
|
||||||
response.NewSuccessResponse(retData).FPrint(w)
|
response.NewSuccessResponse(retData).FPrint(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type addGroupRequest struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
ParentId int `json:"parent_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddGroup(ctx *dto.Context, w http.ResponseWriter, req *http.Request) {
|
||||||
|
var reqData *addGroupRequest
|
||||||
|
reqBytes, err := io.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.WithContext(ctx).Errorf("%+v", err)
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(reqBytes, &reqData)
|
||||||
|
if err != nil {
|
||||||
|
log.WithContext(ctx).Errorf("%+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := db.Instance.Exec(db.WithContext(ctx, "insert into `group` (name,parent_id,user_id) values (?,?,?)"), reqData.Name, reqData.ParentId, ctx.UserInfo.ID)
|
||||||
|
if err != nil {
|
||||||
|
response.NewErrorResponse(response.ServerError, "DBError", err.Error()).FPrint(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
id, err := res.LastInsertId()
|
||||||
|
if err != nil {
|
||||||
|
response.NewErrorResponse(response.ServerError, "DBError", err.Error()).FPrint(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.NewSuccessResponse(id).FPrint(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
type delGroupRequest struct {
|
||||||
|
Id int `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func DelGroup(ctx *dto.Context, w http.ResponseWriter, req *http.Request) {
|
||||||
|
var reqData *delGroupRequest
|
||||||
|
reqBytes, err := io.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.WithContext(ctx).Errorf("%+v", err)
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(reqBytes, &reqData)
|
||||||
|
if err != nil {
|
||||||
|
log.WithContext(ctx).Errorf("%+v", err)
|
||||||
|
}
|
||||||
|
succ, err := group.DelGroup(ctx, reqData.Id)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
response.NewErrorResponse(response.ServerError, "DBError", err.Error()).FPrint(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.NewSuccessResponse(succ).FPrint(w)
|
||||||
|
}
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"pmail/config"
|
"pmail/config"
|
||||||
"pmail/dto"
|
"pmail/dto"
|
||||||
"pmail/utils/errors"
|
"pmail/utils/errors"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Instance *sqlx.DB
|
var Instance *sqlx.DB
|
||||||
@ -32,6 +33,8 @@ func Init() error {
|
|||||||
Instance.SetMaxIdleConns(10)
|
Instance.SetMaxIdleConns(10)
|
||||||
//showMySQLCharacterSet()
|
//showMySQLCharacterSet()
|
||||||
checkTable()
|
checkTable()
|
||||||
|
// 处理版本升级带来的数据表变更
|
||||||
|
databaseUpdate()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,21 +87,27 @@ func checkTable() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func showMySQLCharacterSet() {
|
type tableSQL struct {
|
||||||
var res []struct {
|
Table string `db:"Table"`
|
||||||
Variable_name string `db:"Variable_name"`
|
CreateTable string `db:"Create Table"`
|
||||||
Value string `db:"Value"`
|
|
||||||
}
|
|
||||||
err := Instance.Select(&res, "show variables like '%character%';")
|
|
||||||
log.Debugf("%+v %+v", res, err)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSlowLog() {
|
func databaseUpdate() {
|
||||||
var res []struct {
|
// 检查email表是否有group id
|
||||||
Value string `db:"Value"`
|
var err error
|
||||||
|
var res []tableSQL
|
||||||
|
if config.Instance.DbType == "sqlite" {
|
||||||
|
err = Instance.Select(&res, "select sql as `Create Table` from sqlite_master where type='table' and tbl_name = 'email'")
|
||||||
|
} else {
|
||||||
|
err = Instance.Select(&res, "show create table `email`")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(res) > 0 && !strings.Contains(res[0].CreateTable, "group_id") {
|
||||||
|
Instance.Exec("alter table email add group_id integer default 0 not null;")
|
||||||
}
|
}
|
||||||
err := Instance.Select(&res, "/* asddddasad */select /* this is test */ sleep(4) as Value")
|
|
||||||
log.Debugf("%+v %+v", res, err)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,9 @@ package dto
|
|||||||
import "encoding/json"
|
import "encoding/json"
|
||||||
|
|
||||||
type SearchTag struct {
|
type SearchTag struct {
|
||||||
Type int `json:"type"`
|
Type int `json:"type"` // -1 不限
|
||||||
Status int `json:"status"`
|
Status int `json:"status"` // -1 不限
|
||||||
|
GroupId int `json:"group_id"` // -1 不限
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t SearchTag) ToString() string {
|
func (t SearchTag) ToString() string {
|
||||||
|
@ -42,8 +42,14 @@ func HttpStart() {
|
|||||||
mux.HandleFunc("/api/ping", contextIterceptor(controllers.Ping))
|
mux.HandleFunc("/api/ping", contextIterceptor(controllers.Ping))
|
||||||
mux.HandleFunc("/api/login", contextIterceptor(controllers.Login))
|
mux.HandleFunc("/api/login", contextIterceptor(controllers.Login))
|
||||||
mux.HandleFunc("/api/group", contextIterceptor(controllers.GetUserGroup))
|
mux.HandleFunc("/api/group", contextIterceptor(controllers.GetUserGroup))
|
||||||
|
mux.HandleFunc("/api/group/list", contextIterceptor(controllers.GetUserGroupList))
|
||||||
|
mux.HandleFunc("/api/group/add", contextIterceptor(controllers.AddGroup))
|
||||||
|
mux.HandleFunc("/api/group/del", contextIterceptor(controllers.DelGroup))
|
||||||
mux.HandleFunc("/api/email/list", contextIterceptor(email.EmailList))
|
mux.HandleFunc("/api/email/list", contextIterceptor(email.EmailList))
|
||||||
|
mux.HandleFunc("/api/email/del", contextIterceptor(email.EmailDelete))
|
||||||
|
mux.HandleFunc("/api/email/read", contextIterceptor(email.MarkRead))
|
||||||
mux.HandleFunc("/api/email/detail", contextIterceptor(email.EmailDetail))
|
mux.HandleFunc("/api/email/detail", contextIterceptor(email.EmailDetail))
|
||||||
|
mux.HandleFunc("/api/email/move", contextIterceptor(email.Move))
|
||||||
mux.HandleFunc("/api/email/send", contextIterceptor(email.Send))
|
mux.HandleFunc("/api/email/send", contextIterceptor(email.Send))
|
||||||
mux.HandleFunc("/api/settings/modify_password", contextIterceptor(controllers.ModifyPassword))
|
mux.HandleFunc("/api/settings/modify_password", contextIterceptor(controllers.ModifyPassword))
|
||||||
mux.HandleFunc("/attachments/", contextIterceptor(controllers.GetAttachments))
|
mux.HandleFunc("/attachments/", contextIterceptor(controllers.GetAttachments))
|
||||||
|
@ -52,9 +52,15 @@ func HttpsStart() {
|
|||||||
mux.HandleFunc("/api/ping", contextIterceptor(controllers.Ping))
|
mux.HandleFunc("/api/ping", contextIterceptor(controllers.Ping))
|
||||||
mux.HandleFunc("/api/login", contextIterceptor(controllers.Login))
|
mux.HandleFunc("/api/login", contextIterceptor(controllers.Login))
|
||||||
mux.HandleFunc("/api/group", contextIterceptor(controllers.GetUserGroup))
|
mux.HandleFunc("/api/group", contextIterceptor(controllers.GetUserGroup))
|
||||||
|
mux.HandleFunc("/api/group/list", contextIterceptor(controllers.GetUserGroupList))
|
||||||
|
mux.HandleFunc("/api/group/add", contextIterceptor(controllers.AddGroup))
|
||||||
|
mux.HandleFunc("/api/group/del", contextIterceptor(controllers.DelGroup))
|
||||||
mux.HandleFunc("/api/email/list", contextIterceptor(email.EmailList))
|
mux.HandleFunc("/api/email/list", contextIterceptor(email.EmailList))
|
||||||
|
mux.HandleFunc("/api/email/read", contextIterceptor(email.MarkRead))
|
||||||
|
mux.HandleFunc("/api/email/del", contextIterceptor(email.EmailDelete))
|
||||||
mux.HandleFunc("/api/email/detail", contextIterceptor(email.EmailDetail))
|
mux.HandleFunc("/api/email/detail", contextIterceptor(email.EmailDetail))
|
||||||
mux.HandleFunc("/api/email/send", contextIterceptor(email.Send))
|
mux.HandleFunc("/api/email/send", contextIterceptor(email.Send))
|
||||||
|
mux.HandleFunc("/api/email/move", contextIterceptor(email.Move))
|
||||||
mux.HandleFunc("/api/settings/modify_password", contextIterceptor(controllers.ModifyPassword))
|
mux.HandleFunc("/api/settings/modify_password", contextIterceptor(controllers.ModifyPassword))
|
||||||
mux.HandleFunc("/attachments/", contextIterceptor(controllers.GetAttachments))
|
mux.HandleFunc("/attachments/", contextIterceptor(controllers.GetAttachments))
|
||||||
mux.HandleFunc("/attachments/download/", contextIterceptor(controllers.Download))
|
mux.HandleFunc("/attachments/download/", contextIterceptor(controllers.Download))
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"os"
|
"os"
|
||||||
"pmail/config"
|
"pmail/config"
|
||||||
"pmail/cron_server"
|
|
||||||
"pmail/dto"
|
"pmail/dto"
|
||||||
"pmail/res_init"
|
"pmail/res_init"
|
||||||
"time"
|
"time"
|
||||||
@ -80,7 +79,7 @@ func main() {
|
|||||||
log.Infoln("***************************************************")
|
log.Infoln("***************************************************")
|
||||||
|
|
||||||
// 定时任务启动
|
// 定时任务启动
|
||||||
go cron_server.Start()
|
//go cron_server.Start()
|
||||||
|
|
||||||
// 核心服务启动
|
// 核心服务启动
|
||||||
res_init.Init()
|
res_init.Init()
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
type Email struct {
|
type Email struct {
|
||||||
Id int `db:"id" json:"id"`
|
Id int `db:"id" json:"id"`
|
||||||
Type int8 `db:"type" json:"type"`
|
Type int8 `db:"type" json:"type"`
|
||||||
|
GroupId int `db:"group_id" json:"group_id"`
|
||||||
Subject string `db:"subject" json:"subject"`
|
Subject string `db:"subject" json:"subject"`
|
||||||
ReplyTo string `db:"reply_to" json:"reply_to"`
|
ReplyTo string `db:"reply_to" json:"reply_to"`
|
||||||
FromName string `db:"from_name" json:"from_name"`
|
FromName string `db:"from_name" json:"from_name"`
|
||||||
|
8
server/models/group.go
Normal file
8
server/models/group.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
type Group struct {
|
||||||
|
ID int `db:"id" json:"id"`
|
||||||
|
Name string `db:"name" json:"name"`
|
||||||
|
ParentId int `db:"parent_id" json:"parent_id"`
|
||||||
|
UserId int `db:"user_id" json:"-"`
|
||||||
|
}
|
32
server/services/del_email/del_email.go
Normal file
32
server/services/del_email/del_email.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package del_email
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"pmail/db"
|
||||||
|
"pmail/dto"
|
||||||
|
"pmail/models"
|
||||||
|
"pmail/services/auth"
|
||||||
|
"pmail/utils/array"
|
||||||
|
"pmail/utils/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DelEmail(ctx *dto.Context, ids []int) error {
|
||||||
|
var emails []*models.Email
|
||||||
|
|
||||||
|
db.Instance.Select(&emails, db.WithContext(ctx, fmt.Sprintf("select * from email where id in (%s)", array.Join(ids, ","))))
|
||||||
|
|
||||||
|
for _, email := range emails {
|
||||||
|
// 检查是否有权限
|
||||||
|
hasAuth := auth.HasAuth(ctx, email)
|
||||||
|
if !hasAuth {
|
||||||
|
return errors.New("No Auth!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := db.Instance.Exec(db.WithContext(ctx, fmt.Sprintf("delete from email where id in (%s)", array.Join(ids, ","))))
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
114
server/services/group/group.go
Normal file
114
server/services/group/group.go
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
package group
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"pmail/db"
|
||||||
|
"pmail/dto"
|
||||||
|
"pmail/models"
|
||||||
|
"pmail/utils/array"
|
||||||
|
"pmail/utils/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GroupItem struct {
|
||||||
|
Id int `json:"id"`
|
||||||
|
Label string `json:"label"`
|
||||||
|
Tag string `json:"tag"`
|
||||||
|
Children []*GroupItem `json:"children"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func DelGroup(ctx *dto.Context, groupId int) (bool, error) {
|
||||||
|
allGroupIds := getAllChildId(ctx, groupId)
|
||||||
|
allGroupIds = append(allGroupIds, groupId)
|
||||||
|
|
||||||
|
// 开启一个事务
|
||||||
|
trans, err := db.Instance.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return false, errors.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := trans.Exec(db.WithContext(ctx, fmt.Sprintf("delete from `group` where id in (%s) and user_id =?", array.Join(allGroupIds, ","))), ctx.UserInfo.ID)
|
||||||
|
if err != nil {
|
||||||
|
trans.Rollback()
|
||||||
|
return false, errors.Wrap(err)
|
||||||
|
}
|
||||||
|
num, err := res.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
trans.Rollback()
|
||||||
|
return false, errors.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = trans.Exec(db.WithContext(ctx, fmt.Sprintf("update email set group_id=0 where group_id in (%s)", array.Join(allGroupIds, ","))))
|
||||||
|
if err != nil {
|
||||||
|
trans.Rollback()
|
||||||
|
return false, errors.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
trans.Commit()
|
||||||
|
|
||||||
|
return num > 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type id struct {
|
||||||
|
Id int `db:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAllChildId(ctx *dto.Context, rootId int) []int {
|
||||||
|
var ids []id
|
||||||
|
var ret []int
|
||||||
|
db.Instance.Select(&ids, db.WithContext(ctx, "select id from `group` where parent_id=? and user_id=?"), rootId, ctx.UserInfo.ID)
|
||||||
|
for _, item := range ids {
|
||||||
|
ret = array.Merge(ret, getAllChildId(ctx, item.Id))
|
||||||
|
ret = append(ret, item.Id)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGroupInfoList 获取全部的分组
|
||||||
|
func GetGroupInfoList(ctx *dto.Context) []*GroupItem {
|
||||||
|
return buildChildren(ctx, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoveMailToGroup 将某封邮件移动到某个分组中
|
||||||
|
func MoveMailToGroup(ctx *dto.Context, mailId []int, groupId int) bool {
|
||||||
|
res, err := db.Instance.Exec(db.WithContext(ctx, fmt.Sprintf("update email set group_id=? where id in (%s)", array.Join(mailId, ","))), groupId)
|
||||||
|
if err != nil {
|
||||||
|
log.WithContext(ctx).Errorf("SQL Error:%+v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
rowNum, err := res.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
log.WithContext(ctx).Errorf("SQL Error:%+v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return rowNum > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildChildren(ctx *dto.Context, parentId int) []*GroupItem {
|
||||||
|
var ret []*GroupItem
|
||||||
|
var rootGroup []*models.Group
|
||||||
|
err := db.Instance.Select(&rootGroup, db.WithContext(ctx, "select * from `group` where parent_id=? and user_id=?"), parentId, ctx.UserInfo.ID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WithContext(ctx).Errorf("SQL Error:%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, group := range rootGroup {
|
||||||
|
ret = append(ret, &GroupItem{
|
||||||
|
Id: group.ID,
|
||||||
|
Label: group.Name,
|
||||||
|
Tag: dto.SearchTag{GroupId: group.ID, Status: -1, Type: -1}.ToString(),
|
||||||
|
Children: buildChildren(ctx, group.ID),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetGroupList(ctx *dto.Context) []*models.Group {
|
||||||
|
var ret []*models.Group
|
||||||
|
db.Instance.Select(&ret, db.WithContext(ctx, "select * from `group` where user_id=?"), ctx.UserInfo.ID)
|
||||||
|
return ret
|
||||||
|
}
|
@ -48,6 +48,11 @@ func genSQL(ctx *dto.Context, counter bool, tag, keyword string, offset, limit i
|
|||||||
sqlParams = append(sqlParams, tagInfo.Status)
|
sqlParams = append(sqlParams, tagInfo.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tagInfo.GroupId != -1 {
|
||||||
|
sql += " and group_id=? "
|
||||||
|
sqlParams = append(sqlParams, tagInfo.GroupId)
|
||||||
|
}
|
||||||
|
|
||||||
if keyword != "" {
|
if keyword != "" {
|
||||||
sql += " and (subject like ? or text like ? )"
|
sql += " and (subject like ? or text like ? )"
|
||||||
sqlParams = append(sqlParams, "%"+keyword+"%", "%"+keyword+"%")
|
sqlParams = append(sqlParams, "%"+keyword+"%", "%"+keyword+"%")
|
||||||
|
10
server/services/setup/db_test.go
Normal file
10
server/services/setup/db_test.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSetAdminPassword(t *testing.T) {
|
||||||
|
|
||||||
|
SetAdminPassword(nil, "admin", "admin")
|
||||||
|
}
|
10
server/utils/password/encode_test.go
Normal file
10
server/utils/password/encode_test.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEncode(t *testing.T) {
|
||||||
|
fmt.Println(Encode("admin"))
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user