mirror of
https://github.com/Jinnrry/PMail.git
synced 2025-02-20 11:43:09 +08:00
parent
76bc24d2bd
commit
01cbdc9875
18
.github/workflows/unitTest.yml
vendored
18
.github/workflows/unitTest.yml
vendored
@ -19,6 +19,14 @@ jobs:
|
|||||||
test:
|
test:
|
||||||
name: Docker tests
|
name: Docker tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
services:
|
||||||
|
mysql:
|
||||||
|
image: mysql
|
||||||
|
env:
|
||||||
|
MYSQL_DATABASE: pmail
|
||||||
|
MYSQL_ROOT_PASSWORD: githubTest
|
||||||
|
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||||
|
|
||||||
container:
|
container:
|
||||||
image: golang
|
image: golang
|
||||||
env:
|
env:
|
||||||
@ -51,3 +59,13 @@ jobs:
|
|||||||
|
|
||||||
- name: Run Test
|
- name: Run Test
|
||||||
run: make test
|
run: make test
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: dbfile
|
||||||
|
path: server/config/pmail_temp.db
|
||||||
|
|
||||||
|
|
||||||
|
- name: Run Test Mysql
|
||||||
|
run: make test_mysql
|
||||||
|
|
||||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -4,4 +4,6 @@ dist
|
|||||||
output
|
output
|
||||||
pmail.db
|
pmail.db
|
||||||
server/plugins
|
server/plugins
|
||||||
config
|
config
|
||||||
|
*_KEY
|
||||||
|
AURORA_SECRET
|
5
Makefile
5
Makefile
@ -49,4 +49,7 @@ package: clean
|
|||||||
cp README.md output/
|
cp README.md output/
|
||||||
|
|
||||||
test:
|
test:
|
||||||
export setup_port=17888 && cd server && go test -v ./...
|
export setup_port=17888 && cd server && go test -v ./...
|
||||||
|
|
||||||
|
test_mysql:
|
||||||
|
export setup_port=17888 && cd server && go test -args "mysql" -v ./...
|
@ -146,3 +146,6 @@ The code is in `server` folder.
|
|||||||
|
|
||||||
[go to wiki](https://github.com/Jinnrry/PMail/wiki/%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91%E8%AF%B4%E6%98%8E)
|
[go to wiki](https://github.com/Jinnrry/PMail/wiki/%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91%E8%AF%B4%E6%98%8E)
|
||||||
|
|
||||||
|
# Thanks
|
||||||
|
|
||||||
|
A special thanks to [Jetbrains](http://jetbrains.com/) for donating licenses to the project.
|
@ -83,7 +83,7 @@ PMail是一个追求极简部署流程、极致资源占用的个人域名邮箱
|
|||||||
"domain": "domain.com", // 你的域名
|
"domain": "domain.com", // 你的域名
|
||||||
"webDomain": "mail.domain.com", // web域名
|
"webDomain": "mail.domain.com", // web域名
|
||||||
"dkimPrivateKeyPath": "config/dkim/dkim.priv", // dkim 私钥地址
|
"dkimPrivateKeyPath": "config/dkim/dkim.priv", // dkim 私钥地址
|
||||||
"sslType": "0", // ssl证书更新模式,0自动,1手动
|
"sslType": "0", // ssl证书更新模式,0自动,HTTP模式,1手动、2自动,DNS模式
|
||||||
"SSLPrivateKeyPath": "config/ssl/private.key", // ssl 证书地址
|
"SSLPrivateKeyPath": "config/ssl/private.key", // ssl 证书地址
|
||||||
"SSLPublicKeyPath": "config/ssl/public.crt", // ssl 证书地址
|
"SSLPublicKeyPath": "config/ssl/public.crt", // ssl 证书地址
|
||||||
"dbDSN": "./config/pmail.db", // 数据库连接DSN
|
"dbDSN": "./config/pmail.db", // 数据库连接DSN
|
||||||
@ -149,4 +149,6 @@ SMTP端口: 25/465(SSL)
|
|||||||
|
|
||||||
[go to wiki](https://github.com/Jinnrry/PMail/wiki/%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91%E8%AF%B4%E6%98%8E)
|
[go to wiki](https://github.com/Jinnrry/PMail/wiki/%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91%E8%AF%B4%E6%98%8E)
|
||||||
|
|
||||||
|
# 致谢
|
||||||
|
|
||||||
|
感谢 [Jetbrains](http://jetbrains.com/) 为本项目免费提供开发工具。
|
@ -17,11 +17,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import $http from "../http/http";
|
import { reactive, ref, getCurrentInstance } from 'vue'
|
||||||
import { reactive, ref } from 'vue'
|
|
||||||
import lang from '../i18n/i18n';
|
import lang from '../i18n/i18n';
|
||||||
|
|
||||||
const data = reactive([])
|
const data = reactive([])
|
||||||
|
const app = getCurrentInstance()
|
||||||
|
const $http = app.appContext.config.globalProperties.$http
|
||||||
|
|
||||||
$http.get('/api/group').then(res => {
|
$http.get('/api/group').then(res => {
|
||||||
data.push(...res.data)
|
data.push(...res.data)
|
||||||
@ -29,7 +30,7 @@ $http.get('/api/group').then(res => {
|
|||||||
|
|
||||||
const del = function (node, data) {
|
const del = function (node, data) {
|
||||||
if (data.id != -1) {
|
if (data.id != -1) {
|
||||||
$http.post("/api/group/del", { "id": data.id }).then(res => {
|
this.$axios.post("/api/group/del", { "id": data.id }).then(res => {
|
||||||
if (res.errorNo != 0) {
|
if (res.errorNo != 0) {
|
||||||
ElMessage({
|
ElMessage({
|
||||||
message: res.errorMsg,
|
message: res.errorMsg,
|
||||||
@ -79,14 +80,14 @@ const addRoot = function () {
|
|||||||
|
|
||||||
const onInputBlur = function (item) {
|
const onInputBlur = function (item) {
|
||||||
if (item.label != "") {
|
if (item.label != "") {
|
||||||
$http.post("/api/group/add", { "name": item.label, "parent_id": item.parent_id }).then(res => {
|
this.$axios.post("/api/group/add", { "name": item.label, "parent_id": item.parent_id }).then(res => {
|
||||||
if (res.errorNo != 0) {
|
if (res.errorNo != 0) {
|
||||||
ElMessage({
|
ElMessage({
|
||||||
message: res.errorMsg,
|
message: res.errorMsg,
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
$http.get('/api/group').then(res => {
|
this.$axios.get('/api/group').then(res => {
|
||||||
data.splice(0, data.length)
|
data.splice(0, data.length)
|
||||||
data.push(...res.data)
|
data.push(...res.data)
|
||||||
})
|
})
|
||||||
|
@ -8,10 +8,12 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import $http from "../http/http";
|
|
||||||
import { reactive, ref } from 'vue'
|
import { reactive, ref } from 'vue'
|
||||||
import useGroupStore from '../stores/group'
|
import useGroupStore from '../stores/group'
|
||||||
import lang from '../i18n/i18n';
|
import lang from '../i18n/i18n';
|
||||||
|
import { getCurrentInstance } from 'vue'
|
||||||
|
const app = getCurrentInstance()
|
||||||
|
const $http = app.appContext.config.globalProperties.$http
|
||||||
|
|
||||||
const groupStore = useGroupStore()
|
const groupStore = useGroupStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@ -19,6 +21,7 @@ const router = useRouter()
|
|||||||
|
|
||||||
const data = ref([])
|
const data = ref([])
|
||||||
|
|
||||||
|
|
||||||
$http.get('/api/group').then(res => {
|
$http.get('/api/group').then(res => {
|
||||||
data.value = res.data
|
data.value = res.data
|
||||||
})
|
})
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<div id="logo">
|
<div id="logo">
|
||||||
<span style="padding-left: 20px;">PMail</span>
|
<span style="padding-left: 20px;">PMail</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="settings" @click="settings">
|
<div id="settings" @click="settings" v-if="$isLogin">
|
||||||
<el-icon style="font-size: 25px;">
|
<el-icon style="font-size: 25px;">
|
||||||
<Setting style="color:#FFFFFF" />
|
<Setting style="color:#FFFFFF" />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
@ -21,6 +21,10 @@
|
|||||||
<el-tab-pane :label="lang.rule_setting">
|
<el-tab-pane :label="lang.rule_setting">
|
||||||
<RuleSettings />
|
<RuleSettings />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
|
||||||
|
<el-tab-pane v-if="$userInfos.is_admin" :label="lang.user_management">
|
||||||
|
<UserManagement />
|
||||||
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
|
|
||||||
@ -30,15 +34,39 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { Setting } from '@element-plus/icons-vue';
|
import { Setting } from '@element-plus/icons-vue';
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { ElMessageBox } from 'element-plus'
|
import { ElMessage } 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';
|
import GroupSettings from './GroupSettings.vue';
|
||||||
import RuleSettings from './RuleSettings.vue';
|
import RuleSettings from './RuleSettings.vue';
|
||||||
|
import UserManagement from './UserManagement.vue';
|
||||||
|
import { getCurrentInstance } from 'vue'
|
||||||
|
const app = getCurrentInstance()
|
||||||
|
const $http = app.appContext.config.globalProperties.$http
|
||||||
|
const $isLogin = app.appContext.config.globalProperties.$isLogin
|
||||||
|
const $userInfos = app.appContext.config.globalProperties.$userInfos
|
||||||
|
|
||||||
const openSettings = ref(false)
|
const openSettings = ref(false)
|
||||||
const settings = function () {
|
const settings = function () {
|
||||||
openSettings.value = true;
|
if (Object.keys($userInfos.value).length == 0) {
|
||||||
|
$http.post("/api/user/info", {}).then(res => {
|
||||||
|
if (res.errorNo == 0) {
|
||||||
|
$userInfos.value = res.data
|
||||||
|
openSettings.value = true;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
ElMessage({
|
||||||
|
type: 'error',
|
||||||
|
message: res.errorMsg,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}else{
|
||||||
|
openSettings.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -98,7 +98,6 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive } from 'vue';
|
import { ref, reactive } from 'vue';
|
||||||
import $http from "../http/http";
|
|
||||||
import lang from '../i18n/i18n';
|
import lang from '../i18n/i18n';
|
||||||
import {
|
import {
|
||||||
Plus,
|
Plus,
|
||||||
@ -106,6 +105,12 @@ import {
|
|||||||
Edit,
|
Edit,
|
||||||
InfoFilled
|
InfoFilled
|
||||||
} from '@element-plus/icons-vue'
|
} from '@element-plus/icons-vue'
|
||||||
|
|
||||||
|
|
||||||
|
import { getCurrentInstance } from 'vue'
|
||||||
|
const app = getCurrentInstance()
|
||||||
|
const $http = app.appContext.config.globalProperties.$http
|
||||||
|
|
||||||
const data = ref([])
|
const data = ref([])
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
const READ = 1
|
const READ = 1
|
||||||
@ -134,10 +139,13 @@ const groupData = reactive({
|
|||||||
|
|
||||||
const reflushGroupInfos = function () {
|
const reflushGroupInfos = function () {
|
||||||
$http.get('/api/group/list').then(res => {
|
$http.get('/api/group/list').then(res => {
|
||||||
groupData.list = res.data
|
if (res.data != null) {
|
||||||
for (let i = 0; i < groupData.list.length; i++) {
|
groupData.list = res.data
|
||||||
groupData.list[i].id += ""
|
for (let i = 0; i < groupData.list.length; i++) {
|
||||||
|
groupData.list[i].id += ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-form :model="ruleForm" :rules="rules" status-icon>
|
<el-form :model="ruleForm" :rules="rules" status-icon>
|
||||||
|
|
||||||
|
<el-divider content-position="left">{{lang.modify_pwd}}</el-divider>
|
||||||
|
|
||||||
<el-form-item :label="lang.modify_pwd" prop="new_pwd">
|
<el-form-item :label="lang.modify_pwd" prop="new_pwd">
|
||||||
<el-input type="password" v-model="ruleForm.new_pwd" />
|
<el-input type="password" v-model="ruleForm.new_pwd" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@ -13,14 +16,25 @@
|
|||||||
{{ lang.submit }}
|
{{ lang.submit }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-divider content-position="left">{{lang.logout}}</el-divider>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="logout">
|
||||||
|
{{ lang.logout }}
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { reactive, ref } from 'vue'
|
import { reactive, ref } from 'vue'
|
||||||
import { ElNotification } from 'element-plus'
|
import { ElNotification } from 'element-plus'
|
||||||
import $http from "../http/http";
|
|
||||||
import lang from '../i18n/i18n';
|
import lang from '../i18n/i18n';
|
||||||
|
|
||||||
|
import { getCurrentInstance } from 'vue'
|
||||||
|
const app = getCurrentInstance()
|
||||||
|
const $http = app.appContext.config.globalProperties.$http
|
||||||
|
|
||||||
const ruleForm = reactive({
|
const ruleForm = reactive({
|
||||||
new_pwd: "",
|
new_pwd: "",
|
||||||
new_pwd2: ""
|
new_pwd2: ""
|
||||||
@ -32,6 +46,12 @@ const rules = reactive({
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const logout = function(){
|
||||||
|
$http.post("/api/logout", { }).then(res => {
|
||||||
|
location.reload();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const submit = function () {
|
const submit = function () {
|
||||||
if (ruleForm.new_pwd == ""){
|
if (ruleForm.new_pwd == ""){
|
||||||
return
|
return
|
||||||
|
181
fe/src/components/UserManagement.vue
Normal file
181
fe/src/components/UserManagement.vue
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
<template>
|
||||||
|
<div id="main">
|
||||||
|
<el-table :data="userList" style="width: 100%">
|
||||||
|
<el-table-column label="ID" prop="ID" />
|
||||||
|
<el-table-column :label="lang.account" prop="Account" />
|
||||||
|
<el-table-column :label="lang.user_name" prop="Name" />
|
||||||
|
<el-table-column :label="lang.disabled" prop="Disabled">
|
||||||
|
<template #default="scope">
|
||||||
|
<span>{{ scope.row.Disabled == 1 ? lang.disabled : lang.enabled }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align="right">
|
||||||
|
<template #header>
|
||||||
|
<el-button type="primary" size="small" @click="createUser">
|
||||||
|
New
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button size="small" @click="handleEdit(scope.$index, scope.row)">
|
||||||
|
Edit
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<div id="paginationBox">
|
||||||
|
<el-pagination v-model:current-page="currentPage" small background layout="prev, pager, next"
|
||||||
|
:page-count="totalPage" class="mt-4" @current-change="reflushList" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<el-dialog v-model="userInfoDialog" :title="title" width="500">
|
||||||
|
<el-form>
|
||||||
|
<el-form-item label-width="100px" :label="lang.account">
|
||||||
|
<el-input :disabled="editModel == 'edit'" v-model="editUserInfo.account" autocomplete="off" />
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label-width="100px" :label="lang.user_name">
|
||||||
|
<el-input v-model="editUserInfo.name" autocomplete="off" />
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label-width="100px" :label="lang.password">
|
||||||
|
<el-input :placeholder="lang.resetPwd" v-model="editUserInfo.password" autocomplete="off" />
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<div style="display: flex;">
|
||||||
|
<div
|
||||||
|
style="display: inline-flex;justify-content: flex-end;align-items: flex-start;flex: 0 0 auto;font-size: var(--el-form-label-font-size); height: 32px;line-height: 32px;padding: 0 12px 0 60px;box-sizing: border-box; ">
|
||||||
|
<el-switch v-model="editUserInfo.disabled" class="ml-2" :active-text="lang.disabled"
|
||||||
|
:inactive-text="lang.enabled" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="userInfoDialog = false">Cancel</el-button>
|
||||||
|
<el-button type="primary" @click="submit">
|
||||||
|
Confirm
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { reactive, ref, getCurrentInstance } from 'vue'
|
||||||
|
import lang from '../i18n/i18n';
|
||||||
|
const userList = reactive([])
|
||||||
|
const app = getCurrentInstance()
|
||||||
|
const $http = app.appContext.config.globalProperties.$http
|
||||||
|
const currentPage = ref(1)
|
||||||
|
const totalPage = ref(1)
|
||||||
|
const userInfoDialog = ref(false)
|
||||||
|
const editModel = ref("edit")
|
||||||
|
const editUserInfo = reactive({
|
||||||
|
"account": "",
|
||||||
|
"name": "",
|
||||||
|
"password": "",
|
||||||
|
"disabled": false
|
||||||
|
})
|
||||||
|
const title = ref(lang.editUser)
|
||||||
|
|
||||||
|
const reflushList = function () {
|
||||||
|
$http.post('/api/user/list', { "current_page": currentPage.value, "page_size": 10 }).then(res => {
|
||||||
|
userList.length = 0
|
||||||
|
|
||||||
|
totalPage.value = res.data.total_page
|
||||||
|
userList.push(...res.data["list"])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const handleEdit = function (idx, row) {
|
||||||
|
editUserInfo.account = row.Account
|
||||||
|
editUserInfo.name = row.Name
|
||||||
|
editUserInfo.disabled = row.Disabled == 1
|
||||||
|
editUserInfo.password = ""
|
||||||
|
editModel.value = "edit"
|
||||||
|
title.value = lang.editUser
|
||||||
|
userInfoDialog.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const createUser = function(){
|
||||||
|
editUserInfo.account = ""
|
||||||
|
editUserInfo.name = ""
|
||||||
|
editUserInfo.disabled = false
|
||||||
|
editUserInfo.password = ""
|
||||||
|
editModel.value = "create"
|
||||||
|
title.value = lang.newUser
|
||||||
|
userInfoDialog.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const submit = function () {
|
||||||
|
if (editModel.value == 'edit') {
|
||||||
|
|
||||||
|
let newData = {
|
||||||
|
"account": editUserInfo.account,
|
||||||
|
"username": editUserInfo.name,
|
||||||
|
"disabled": editUserInfo.disabled ? 1 : 0
|
||||||
|
}
|
||||||
|
if (editUserInfo.password != "") {
|
||||||
|
newData["password"] = editUserInfo.password
|
||||||
|
}
|
||||||
|
|
||||||
|
$http.post('/api/user/edit', newData).then(res => {
|
||||||
|
ElNotification({
|
||||||
|
title: res.errorNo == 0 ? lang.succ : lang.fail,
|
||||||
|
message: res.errorNo == 0 ? "" : res.data,
|
||||||
|
type: res.errorNo == 0 ? 'success' : 'error',
|
||||||
|
})
|
||||||
|
if (res.errorNo == 0) {
|
||||||
|
reflushList()
|
||||||
|
userInfoDialog.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}else{
|
||||||
|
let newData = {
|
||||||
|
"account": editUserInfo.account,
|
||||||
|
"username": editUserInfo.name,
|
||||||
|
"disabled": editUserInfo.disabled ? 1 : 0,
|
||||||
|
"password":editUserInfo.password
|
||||||
|
}
|
||||||
|
|
||||||
|
$http.post('/api/user/create', newData).then(res => {
|
||||||
|
ElNotification({
|
||||||
|
title: res.errorNo == 0 ? lang.succ : lang.fail,
|
||||||
|
message: res.errorNo == 0 ? "" : res.data,
|
||||||
|
type: res.errorNo == 0 ? 'success' : 'error',
|
||||||
|
})
|
||||||
|
if (res.errorNo == 0) {
|
||||||
|
reflushList()
|
||||||
|
userInfoDialog.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
reflushList()
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
#paginationBox {
|
||||||
|
margin-top: 10px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,101 +0,0 @@
|
|||||||
// http/index.js
|
|
||||||
import axios from 'axios'
|
|
||||||
import router from "@/router"; //根路由对象
|
|
||||||
import lang from '../i18n/i18n';
|
|
||||||
|
|
||||||
//创建axios的一个实例
|
|
||||||
var $http = axios.create({
|
|
||||||
baseURL: import.meta.env.VITE_APP_URL, //接口统一域名
|
|
||||||
timeout: 60000, //设置超时
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json;charset=UTF-8;',
|
|
||||||
'Lang': lang.lang
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
//请求拦截器
|
|
||||||
$http.interceptors.request.use((config) => {
|
|
||||||
//若请求方式为post,则将data参数转为JSON字符串
|
|
||||||
if (config.method === 'POST') {
|
|
||||||
config.data = JSON.stringify(config.data);
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
}, (error) =>
|
|
||||||
// 对请求错误做些什么
|
|
||||||
Promise.reject(error));
|
|
||||||
|
|
||||||
//响应拦截器
|
|
||||||
$http.interceptors.response.use((response) => {
|
|
||||||
//响应成功
|
|
||||||
if (response.data.errorNo == 403) {
|
|
||||||
router.replace({
|
|
||||||
path: '/login',
|
|
||||||
query: {
|
|
||||||
redirect: router.currentRoute.fullPath
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
//响应成功
|
|
||||||
if (response.data.errorNo == 402) {
|
|
||||||
router.replace({
|
|
||||||
path: '/setup',
|
|
||||||
query: {
|
|
||||||
redirect: router.currentRoute.fullPath
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return response.data;
|
|
||||||
}, (error) => {
|
|
||||||
//响应错误
|
|
||||||
if (error.response && error.response.status) {
|
|
||||||
const status = error.response.status
|
|
||||||
let message = ""
|
|
||||||
switch (status) {
|
|
||||||
case 400:
|
|
||||||
message = '请求错误';
|
|
||||||
break;
|
|
||||||
case 401:
|
|
||||||
message = '请求错误';
|
|
||||||
break;
|
|
||||||
case 403:
|
|
||||||
router.replace({
|
|
||||||
path: '/login',
|
|
||||||
query: {
|
|
||||||
redirect: router.currentRoute.fullPath
|
|
||||||
}
|
|
||||||
})
|
|
||||||
break;
|
|
||||||
case 404:
|
|
||||||
message = '请求地址出错';
|
|
||||||
break;
|
|
||||||
case 408:
|
|
||||||
message = '请求超时';
|
|
||||||
break;
|
|
||||||
case 500:
|
|
||||||
message = '服务器内部错误!';
|
|
||||||
break;
|
|
||||||
case 501:
|
|
||||||
message = '服务未实现!';
|
|
||||||
break;
|
|
||||||
case 502:
|
|
||||||
message = '网关错误!';
|
|
||||||
break;
|
|
||||||
case 503:
|
|
||||||
message = '服务不可用!';
|
|
||||||
break;
|
|
||||||
case 504:
|
|
||||||
message = '网关超时!';
|
|
||||||
break;
|
|
||||||
case 505:
|
|
||||||
message = 'HTTP版本不受支持';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
message = '请求失败'
|
|
||||||
}
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
return Promise.reject(error);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
export default $http;
|
|
@ -1,4 +1,12 @@
|
|||||||
var lang = {
|
var lang = {
|
||||||
|
"logout": "Logout",
|
||||||
|
"resetPwd": "Reset the account password",
|
||||||
|
"disabled": "Disabled",
|
||||||
|
"enabled": "Enabled",
|
||||||
|
"newUser": "Create Account",
|
||||||
|
"editUser": "Edit Account",
|
||||||
|
"user_name": "User Name",
|
||||||
|
"user_management": "user management",
|
||||||
"lang": "en",
|
"lang": "en",
|
||||||
"submit": "submit",
|
"submit": "submit",
|
||||||
"compose": "compose",
|
"compose": "compose",
|
||||||
@ -55,6 +63,12 @@ var lang = {
|
|||||||
"web_domain": "Web Domain",
|
"web_domain": "Web Domain",
|
||||||
"dns_desc": "Please add the following information to your DNS records",
|
"dns_desc": "Please add the following information to your DNS records",
|
||||||
"ssl_auto": "Automatically configure SSL certificates (recommended)",
|
"ssl_auto": "Automatically configure SSL certificates (recommended)",
|
||||||
|
"wait_desc":"HTTP challenge mode completes in approximately 1 minute and DNS API challenge mode completes in approximately 10 minutes.",
|
||||||
|
"ssl_challenge_type":"Challenge Type",
|
||||||
|
"ssl_auto_http":"Http Request",
|
||||||
|
"ssl_auto_dns":"DNS Records",
|
||||||
|
"challenge_typ_desc":"If PMail uses port 80 directly, it is recommended that you use the HTTP challenge method. If PMail does not use port 80 directly, it is recommended to use DNS challenge method, DNS API key to ask your domain name service provider to apply.",
|
||||||
|
"oomain_service_provider":"Domain Name Service Provider",
|
||||||
"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",
|
||||||
@ -92,11 +106,19 @@ var lang = {
|
|||||||
|
|
||||||
|
|
||||||
var zhCN = {
|
var zhCN = {
|
||||||
|
"logout": "注销",
|
||||||
|
"resetPwd": "重置账号密码",
|
||||||
|
"disabled": "禁用",
|
||||||
|
"enabled": "启用",
|
||||||
|
"user_name": "用户名",
|
||||||
|
"newUser": "新增用户",
|
||||||
|
"editUser": "编辑用户",
|
||||||
|
"user_management": "用户管理",
|
||||||
"lang": "zhCn",
|
"lang": "zhCn",
|
||||||
"submit": "提交",
|
"submit": "提交",
|
||||||
"compose": "发件",
|
"compose": "发件",
|
||||||
"new": "新",
|
"new": "新",
|
||||||
"account": "用户名",
|
"account": "账号",
|
||||||
"password": "密码",
|
"password": "密码",
|
||||||
"login": "登录",
|
"login": "登录",
|
||||||
"search": "搜索邮件",
|
"search": "搜索邮件",
|
||||||
@ -148,7 +170,13 @@ var zhCN = {
|
|||||||
"web_domain": "Web域名地址",
|
"web_domain": "Web域名地址",
|
||||||
"dns_desc": "请将以下信息添加到DNS记录中",
|
"dns_desc": "请将以下信息添加到DNS记录中",
|
||||||
"ssl_auto": "自动配置SSL证书(推荐)",
|
"ssl_auto": "自动配置SSL证书(推荐)",
|
||||||
|
"oomain_service_provider":"域名服务商",
|
||||||
|
"ssl_auto_http":"HTTP请求",
|
||||||
|
"ssl_auto_dns":"DNS记录",
|
||||||
|
"ssl_challenge_type":"验证方式",
|
||||||
"ssl_manuallyf": "手动配置SSL证书",
|
"ssl_manuallyf": "手动配置SSL证书",
|
||||||
|
"challenge_typ_desc":"如果PMail直接使用80端口,建议使用HTTP验证方式。如果PMail没有直接使用80端口,建议使用DNS验证方式,DNS API Key找你的域名服务商申请",
|
||||||
|
"wait_desc":"HTTP验证模式大约1分钟完成,DNS API验证模式大约15分钟完成。",
|
||||||
"ssl_key_path": "ssl key文件位置",
|
"ssl_key_path": "ssl key文件位置",
|
||||||
"ssl_crt_path": "ssl crt文件位置",
|
"ssl_crt_path": "ssl crt文件位置",
|
||||||
"group_settings": "分组",
|
"group_settings": "分组",
|
||||||
|
109
fe/src/main.js
109
fe/src/main.js
@ -3,14 +3,119 @@ import 'element-plus/dist/index.css'
|
|||||||
|
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
|
import {ref} from 'vue'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
app.config.globalProperties.$isLogin = ref(true)
|
||||||
|
app.config.globalProperties.$userInfos = ref({})
|
||||||
app.use(createPinia())
|
app.use(createPinia())
|
||||||
app.use(router)
|
app.use(router)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
import lang from './i18n/i18n';
|
||||||
|
//创建axios的一个实例
|
||||||
|
var $http = axios.create({
|
||||||
|
baseURL: import.meta.env.VITE_APP_URL, //接口统一域名
|
||||||
|
timeout: 60000, //设置超时
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json;charset=UTF-8;',
|
||||||
|
'Lang': lang.lang
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
//请求拦截器
|
||||||
|
$http.interceptors.request.use((config) => {
|
||||||
|
//若请求方式为post,则将data参数转为JSON字符串
|
||||||
|
if (config.method === 'POST') {
|
||||||
|
config.data = JSON.stringify(config.data);
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}, (error) =>
|
||||||
|
// 对请求错误做些什么
|
||||||
|
Promise.reject(error));
|
||||||
|
|
||||||
|
//响应拦截器
|
||||||
|
$http.interceptors.response.use((response) => {
|
||||||
|
//响应成功
|
||||||
|
if (response.data.errorNo == 403) {
|
||||||
|
app.config.globalProperties.$isLogin.value = false
|
||||||
|
|
||||||
|
router.replace({
|
||||||
|
path: '/login',
|
||||||
|
query: {
|
||||||
|
redirect: router.currentRoute.fullPath
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
//响应成功
|
||||||
|
if (response.data.errorNo == 402) {
|
||||||
|
router.replace({
|
||||||
|
path: '/setup',
|
||||||
|
query: {
|
||||||
|
redirect: router.currentRoute.fullPath
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return response.data;
|
||||||
|
}, (error) => {
|
||||||
|
//响应错误
|
||||||
|
if (error.response && error.response.status) {
|
||||||
|
const status = error.response.status
|
||||||
|
let message = ""
|
||||||
|
switch (status) {
|
||||||
|
case 400:
|
||||||
|
message = '请求错误';
|
||||||
|
break;
|
||||||
|
case 401:
|
||||||
|
message = '请求错误';
|
||||||
|
break;
|
||||||
|
case 403:
|
||||||
|
router.replace({
|
||||||
|
path: '/login',
|
||||||
|
query: {
|
||||||
|
redirect: router.currentRoute.fullPath
|
||||||
|
}
|
||||||
|
})
|
||||||
|
break;
|
||||||
|
case 404:
|
||||||
|
message = '请求地址出错';
|
||||||
|
break;
|
||||||
|
case 408:
|
||||||
|
message = '请求超时';
|
||||||
|
break;
|
||||||
|
case 500:
|
||||||
|
message = '服务器内部错误!';
|
||||||
|
break;
|
||||||
|
case 501:
|
||||||
|
message = '服务未实现!';
|
||||||
|
break;
|
||||||
|
case 502:
|
||||||
|
message = '网关错误!';
|
||||||
|
break;
|
||||||
|
case 503:
|
||||||
|
message = '服务不可用!';
|
||||||
|
break;
|
||||||
|
case 504:
|
||||||
|
message = '网关超时!';
|
||||||
|
break;
|
||||||
|
case 505:
|
||||||
|
message = 'HTTP版本不受支持';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
message = '请求失败'
|
||||||
|
}
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
return Promise.reject(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// 注册到全局
|
||||||
|
app.config.globalProperties.$http = $http
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|
||||||
|
@ -42,4 +42,8 @@ const router = createRouter({
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div id="main">
|
<div id="main">
|
||||||
<el-form label-width="100px" :rules="rules" ref="ruleFormRef" :model="ruleForm" status-icon>
|
<el-form label-width="100px" :rules="rules" ref="ruleFormRef" :model="ruleForm" status-icon>
|
||||||
<el-form-item :label="lang.sender" prop="sender">
|
<el-form-item :label="lang.sender" prop="sender">
|
||||||
<el-input v-model="ruleForm.sender" :placeholder="lang.sender_desc"></el-input>
|
<el-input :disabled="!$userInfos.is_admin" v-model="ruleForm.sender" :placeholder="lang.sender_desc"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
|
||||||
@ -76,8 +76,6 @@
|
|||||||
|
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import $http from '../http/http';
|
|
||||||
|
|
||||||
import '@wangeditor/editor/dist/css/style.css' // 引入 css
|
import '@wangeditor/editor/dist/css/style.css' // 引入 css
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { onBeforeUnmount, ref, shallowRef, reactive, onMounted } from 'vue'
|
import { onBeforeUnmount, ref, shallowRef, reactive, onMounted } from 'vue'
|
||||||
@ -85,10 +83,15 @@ import { Close } from '@element-plus/icons-vue';
|
|||||||
import lang from '../i18n/i18n';
|
import lang from '../i18n/i18n';
|
||||||
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
|
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
|
||||||
import { i18nChangeLanguage } from '@wangeditor/editor'
|
import { i18nChangeLanguage } from '@wangeditor/editor'
|
||||||
import router from "@/router"; //根路由对象
|
import { useRouter } from 'vue-router';
|
||||||
|
const router = useRouter();
|
||||||
import useGroupStore from '../stores/group'
|
import useGroupStore from '../stores/group'
|
||||||
const groupStore = useGroupStore()
|
const groupStore = useGroupStore()
|
||||||
|
import { getCurrentInstance } from 'vue'
|
||||||
|
const app = getCurrentInstance()
|
||||||
|
const $http = app.appContext.config.globalProperties.$http
|
||||||
|
const $isLogin = app.appContext.config.globalProperties.$isLogin
|
||||||
|
const $userInfos = app.appContext.config.globalProperties.$userInfos
|
||||||
|
|
||||||
if (lang.lang == "zhCn"){
|
if (lang.lang == "zhCn"){
|
||||||
i18nChangeLanguage('zh-CN')
|
i18nChangeLanguage('zh-CN')
|
||||||
@ -124,6 +127,24 @@ const ruleForm = reactive({
|
|||||||
const fileList = reactive([]);
|
const fileList = reactive([]);
|
||||||
|
|
||||||
|
|
||||||
|
const init =function(){
|
||||||
|
if (Object.keys($userInfos.value).length == 0) {
|
||||||
|
$http.post("/api/user/info", {}).then(res => {
|
||||||
|
if (res.errorNo == 0) {
|
||||||
|
$userInfos.value = res.data
|
||||||
|
} else {
|
||||||
|
ElMessage({
|
||||||
|
type: 'error',
|
||||||
|
message: res.errorMsg,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
init()
|
||||||
|
ruleForm.sender = $userInfos.value.account
|
||||||
|
|
||||||
|
|
||||||
const validateSender = function (rule, value, callback) {
|
const validateSender = function (rule, value, callback) {
|
||||||
if (typeof ruleForm.sender === "undefined" || ruleForm.sender === null || ruleForm.sender.trim() === "") {
|
if (typeof ruleForm.sender === "undefined" || ruleForm.sender === null || ruleForm.sender.trim() === "") {
|
||||||
callback(new Error(lang.err_sender_must))
|
callback(new Error(lang.err_sender_must))
|
||||||
|
@ -35,15 +35,17 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import $http from "../http/http";
|
|
||||||
|
|
||||||
import { RouterLink } from 'vue-router'
|
|
||||||
import { reactive, ref } from 'vue'
|
import { reactive, ref } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import router from "@/router"; //根路由对象
|
|
||||||
import { Document } from '@element-plus/icons-vue';
|
import { Document } from '@element-plus/icons-vue';
|
||||||
import lang from '../i18n/i18n';
|
import lang from '../i18n/i18n';
|
||||||
|
|
||||||
|
import { getCurrentInstance } from 'vue'
|
||||||
|
const app = getCurrentInstance()
|
||||||
|
const $http = app.appContext.config.globalProperties.$http
|
||||||
|
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const detailData = ref({
|
const detailData = ref({
|
||||||
attachments:[]
|
attachments:[]
|
||||||
|
@ -81,13 +81,22 @@
|
|||||||
|
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import $http from "../http/http";
|
|
||||||
import { ArrowDown } from '@element-plus/icons-vue'
|
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 router from "@/router"; //根路由对象
|
|
||||||
import useGroupStore from '../stores/group'
|
import useGroupStore from '../stores/group'
|
||||||
import lang from '../i18n/i18n';
|
import lang from '../i18n/i18n';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
|
||||||
|
import { getCurrentInstance } from 'vue'
|
||||||
|
const app = getCurrentInstance()
|
||||||
|
const $http = app.appContext.config.globalProperties.$http
|
||||||
|
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
|
||||||
const groupStore = useGroupStore()
|
const groupStore = useGroupStore()
|
||||||
|
|
||||||
|
@ -20,11 +20,14 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
|
||||||
import { reactive } from 'vue'
|
import { reactive } from 'vue'
|
||||||
import $http from "../http/http";
|
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import router from "@/router"; //根路由对象
|
import router from "@/router"; //根路由对象
|
||||||
import lang from '../i18n/i18n';
|
import lang from '../i18n/i18n';
|
||||||
|
import { getCurrentInstance } from 'vue'
|
||||||
|
const app = getCurrentInstance()
|
||||||
|
const $http = app.appContext.config.globalProperties.$http
|
||||||
|
const $isLogin = app.appContext.config.globalProperties.$isLogin
|
||||||
|
const $userInfos = app.appContext.config.globalProperties.$userInfos
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
account: '',
|
account: '',
|
||||||
@ -36,6 +39,8 @@ const onSubmit = () => {
|
|||||||
if (res.errorNo != 0) {
|
if (res.errorNo != 0) {
|
||||||
ElMessage.error(res.errorMsg)
|
ElMessage.error(res.errorMsg)
|
||||||
} else {
|
} else {
|
||||||
|
$isLogin.value = true
|
||||||
|
$userInfos.value = res.data
|
||||||
router.replace({
|
router.replace({
|
||||||
path: '/',
|
path: '/',
|
||||||
query: {
|
query: {
|
||||||
|
@ -28,7 +28,8 @@
|
|||||||
<div class="form" style="width: 400px;">
|
<div class="form" style="width: 400px;">
|
||||||
<el-form label-width="120px">
|
<el-form label-width="120px">
|
||||||
<el-form-item :label="lang.type">
|
<el-form-item :label="lang.type">
|
||||||
<el-select :placeholder="lang.db_select_ph" v-model="dbSettings.type" @change="dbSettings.dsn=''">
|
<el-select :placeholder="lang.db_select_ph" v-model="dbSettings.type"
|
||||||
|
@change="dbSettings.dsn = ''">
|
||||||
<el-option label="MySQL" value="mysql" />
|
<el-option label="MySQL" value="mysql" />
|
||||||
<el-option label="SQLite3" value="sqlite" />
|
<el-option label="SQLite3" value="sqlite" />
|
||||||
</el-select>
|
</el-select>
|
||||||
@ -121,9 +122,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-alert :closable="false" title="Warning!" type="error" center v-if="active == 5 && sslSettings.type == 0 && port != 80 "
|
<el-alert :closable="false" title="Warning!" type="error" center
|
||||||
:description="lang.autoSSLWarn"
|
v-if="active == 5 && sslSettings.type == 0 && port != 80" :description="lang.autoSSLWarn" />
|
||||||
/>
|
|
||||||
|
|
||||||
<div v-if="active == 5" class="ctn">
|
<div v-if="active == 5" class="ctn">
|
||||||
<div class="desc">
|
<div class="desc">
|
||||||
@ -139,6 +139,166 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item :label="lang.ssl_challenge_type" v-if="sslSettings.type == '0'">
|
||||||
|
<el-select :placeholder="lang.ssl_auto_http" v-model="sslSettings.challenge">
|
||||||
|
<el-option :label="lang.ssl_auto_http" value="http" />
|
||||||
|
<el-option :label="lang.ssl_auto_dns" value="dns" />
|
||||||
|
</el-select>
|
||||||
|
|
||||||
|
<el-tooltip class="box-item" effect="dark" :content="lang.challenge_typ_desc"
|
||||||
|
placement="top-start">
|
||||||
|
<span style="margin-left: 6px; font-size:18px; font-weight: bolder;">?</span>
|
||||||
|
</el-tooltip>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item :label="lang.oomain_service_provider"
|
||||||
|
v-if="sslSettings.type == '0' && sslSettings.challenge == 'dns'">
|
||||||
|
<el-select @change="provide_change" :placeholder="lang.oomain_service_provider"
|
||||||
|
v-model="sslSettings.provider">
|
||||||
|
<el-option label="acme-dns" value="acme-dns" />
|
||||||
|
<el-option label="alidns" value="alidns" />
|
||||||
|
<el-option label="allinkl" value="allinkl" />
|
||||||
|
<el-option label="arvancloud" value="arvancloud" />
|
||||||
|
<el-option label="azure" value="azure" />
|
||||||
|
<el-option label="azuredns" value="azuredns" />
|
||||||
|
<el-option label="auroradns" value="auroradns" />
|
||||||
|
<el-option label="autodns" value="autodns" />
|
||||||
|
<el-option label="bindman" value="bindman" />
|
||||||
|
<el-option label="bluecat" value="bluecat" />
|
||||||
|
<el-option label="brandit" value="brandit" />
|
||||||
|
<el-option label="bunny" value="bunny" />
|
||||||
|
<el-option label="checkdomain" value="checkdomain" />
|
||||||
|
<el-option label="civo" value="civo" />
|
||||||
|
<el-option label="clouddns" value="clouddns" />
|
||||||
|
<el-option label="cloudflare" value="cloudflare" />
|
||||||
|
<el-option label="cloudns" value="cloudns" />
|
||||||
|
<el-option label="cloudru" value="cloudru" />
|
||||||
|
<el-option label="cloudxns" value="cloudxns" />
|
||||||
|
<el-option label="conoha" value="conoha" />
|
||||||
|
<el-option label="constellix" value="constellix" />
|
||||||
|
<el-option label="cpanel" value="cpanel" />
|
||||||
|
<el-option label="derak" value="derak" />
|
||||||
|
<el-option label="desec" value="desec" />
|
||||||
|
<el-option label="designate" value="designate" />
|
||||||
|
<el-option label="digitalocean" value="digitalocean" />
|
||||||
|
<el-option label="dnshomede" value="dnshomede" />
|
||||||
|
<el-option label="dnsimple" value="dnsimple" />
|
||||||
|
<el-option label="dnsmadeeasy" value="dnsmadeeasy" />
|
||||||
|
<el-option label="dnspod" value="dnspod" />
|
||||||
|
<el-option label="dode" value="dode" />
|
||||||
|
<el-option label="domeneshop" value="domeneshop" />
|
||||||
|
<el-option label="domainnameshop" value="domainnameshop" />
|
||||||
|
<el-option label="dreamhost" value="dreamhost" />
|
||||||
|
<el-option label="duckdns" value="duckdns" />
|
||||||
|
<el-option label="dyn" value="dyn" />
|
||||||
|
<el-option label="dynu" value="dynu" />
|
||||||
|
<el-option label="easydns" value="easydns" />
|
||||||
|
<el-option label="edgedns" value="edgedns" />
|
||||||
|
<el-option label="fastdns" value="fastdns" />
|
||||||
|
<el-option label="efficientip" value="efficientip" />
|
||||||
|
<el-option label="epik" value="epik" />
|
||||||
|
<el-option label="exec" value="exec" />
|
||||||
|
<el-option label="exoscale" value="exoscale" />
|
||||||
|
<el-option label="freemyip" value="freemyip" />
|
||||||
|
<el-option label="gandi" value="gandi" />
|
||||||
|
<el-option label="gandiv5" value="gandiv5" />
|
||||||
|
<el-option label="gcloud" value="gcloud" />
|
||||||
|
<el-option label="gcore" value="gcore" />
|
||||||
|
<el-option label="glesys" value="glesys" />
|
||||||
|
<el-option label="godaddy" value="godaddy" />
|
||||||
|
<el-option label="googledomains" value="googledomains" />
|
||||||
|
<el-option label="hetzner" value="hetzner" />
|
||||||
|
<el-option label="hostingde" value="hostingde" />
|
||||||
|
<el-option label="hosttech" value="hosttech" />
|
||||||
|
<el-option label="httpreq" value="httpreq" />
|
||||||
|
<el-option label="hurricane" value="hurricane" />
|
||||||
|
<el-option label="hyperone" value="hyperone" />
|
||||||
|
<el-option label="ibmcloud" value="ibmcloud" />
|
||||||
|
<el-option label="iij" value="iij" />
|
||||||
|
<el-option label="iijdpf" value="iijdpf" />
|
||||||
|
<el-option label="infoblox" value="infoblox" />
|
||||||
|
<el-option label="infomaniak" value="infomaniak" />
|
||||||
|
<el-option label="internetbs" value="internetbs" />
|
||||||
|
<el-option label="inwx" value="inwx" />
|
||||||
|
<el-option label="ionos" value="ionos" />
|
||||||
|
<el-option label="ipv64" value="ipv64" />
|
||||||
|
<el-option label="iwantmyname" value="iwantmyname" />
|
||||||
|
<el-option label="joker" value="joker" />
|
||||||
|
<el-option label="liara" value="liara" />
|
||||||
|
<el-option label="lightsail" value="lightsail" />
|
||||||
|
<el-option label="linode" value="linode" />
|
||||||
|
<el-option label="linodev4" value="linodev4" />
|
||||||
|
<el-option label="liquidweb" value="liquidweb" />
|
||||||
|
<el-option label="loopia" value="loopia" />
|
||||||
|
<el-option label="luadns" value="luadns" />
|
||||||
|
<el-option label="mailinabox" value="mailinabox" />
|
||||||
|
<el-option label="manual" value="manual" />
|
||||||
|
<el-option label="metaname" value="metaname" />
|
||||||
|
<el-option label="mydnsjp" value="mydnsjp" />
|
||||||
|
<el-option label="mythicbeasts" value="mythicbeasts" />
|
||||||
|
<el-option label="namecheap" value="namecheap" />
|
||||||
|
<el-option label="namedotcom" value="namedotcom" />
|
||||||
|
<el-option label="namesilo" value="namesilo" />
|
||||||
|
<el-option label="nearlyfreespeech" value="nearlyfreespeech" />
|
||||||
|
<el-option label="netcup" value="netcup" />
|
||||||
|
<el-option label="netlify" value="netlify" />
|
||||||
|
<el-option label="nicmanager" value="nicmanager" />
|
||||||
|
<el-option label="nifcloud" value="nifcloud" />
|
||||||
|
<el-option label="njalla" value="njalla" />
|
||||||
|
<el-option label="nodion" value="nodion" />
|
||||||
|
<el-option label="ns1" value="ns1" />
|
||||||
|
<el-option label="oraclecloud" value="oraclecloud" />
|
||||||
|
<el-option label="otc" value="otc" />
|
||||||
|
<el-option label="ovh" value="ovh" />
|
||||||
|
<el-option label="pdns" value="pdns" />
|
||||||
|
<el-option label="plesk" value="plesk" />
|
||||||
|
<el-option label="porkbun" value="porkbun" />
|
||||||
|
<el-option label="rackspace" value="rackspace" />
|
||||||
|
<el-option label="rcodezero" value="rcodezero" />
|
||||||
|
<el-option label="regru" value="regru" />
|
||||||
|
<el-option label="rfc2136" value="rfc2136" />
|
||||||
|
<el-option label="rimuhosting" value="rimuhosting" />
|
||||||
|
<el-option label="route53" value="route53" />
|
||||||
|
<el-option label="safedns" value="safedns" />
|
||||||
|
<el-option label="sakuracloud" value="sakuracloud" />
|
||||||
|
<el-option label="scaleway" value="scaleway" />
|
||||||
|
<el-option label="selectel" value="selectel" />
|
||||||
|
<el-option label="servercow" value="servercow" />
|
||||||
|
<el-option label="shellrent" value="shellrent" />
|
||||||
|
<el-option label="simply" value="simply" />
|
||||||
|
<el-option label="sonic" value="sonic" />
|
||||||
|
<el-option label="stackpath" value="stackpath" />
|
||||||
|
<el-option label="tencentcloud" value="tencentcloud" />
|
||||||
|
<el-option label="transip" value="transip" />
|
||||||
|
<el-option label="ultradns" value="ultradns" />
|
||||||
|
<el-option label="variomedia" value="variomedia" />
|
||||||
|
<el-option label="vegadns" value="vegadns" />
|
||||||
|
<el-option label="vercel" value="vercel" />
|
||||||
|
<el-option label="versio" value="versio" />
|
||||||
|
<el-option label="vinyldns" value="vinyldns" />
|
||||||
|
<el-option label="vkcloud" value="vkcloud" />
|
||||||
|
<el-option label="vscale" value="vscale" />
|
||||||
|
<el-option label="vultr" value="vultr" />
|
||||||
|
<el-option label="webnames" value="webnames" />
|
||||||
|
<el-option label="websupport" value="websupport" />
|
||||||
|
<el-option label="wedos" value="wedos" />
|
||||||
|
<el-option label="yandex" value="yandex" />
|
||||||
|
<el-option label="yandex360" value="yandex360" />
|
||||||
|
<el-option label="yandexcloud" value="yandexcloud" />
|
||||||
|
<el-option label="zoneee" value="zoneee" />
|
||||||
|
<el-option label="zonomi" value="zonomi" />
|
||||||
|
</el-select>
|
||||||
|
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
|
||||||
|
<el-form-item :label="item"
|
||||||
|
v-if="sslSettings.paramsList.length != 0 && sslSettings.type == 0 && sslSettings.challenge == 'dns'"
|
||||||
|
v-for="item in sslSettings.paramsList">
|
||||||
|
<el-input style="width: 240px" :placeholder="item" v-model="dnsApiParams[item]" />
|
||||||
|
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item :label="lang.ssl_key_path" v-if="sslSettings.type == '1'">
|
<el-form-item :label="lang.ssl_key_path" v-if="sslSettings.type == '1'">
|
||||||
<el-input placeholder="./config/ssl/private.key" v-model="sslSettings.key_path"></el-input>
|
<el-input placeholder="./config/ssl/private.key" v-model="sslSettings.key_path"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@ -153,19 +313,21 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-button v-loading.fullscreen.lock="fullscreenLoading" id="next" style="margin-top: 12px" @click="next">{{
|
<el-button :element-loading-text="lang.wait_desc" v-loading.fullscreen.lock="fullscreenLoading" id="next" style="margin-top: 12px" @click="next">{{
|
||||||
lang.next }}</el-button>
|
lang.next }}</el-button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import $http from "../http/http";
|
|
||||||
|
|
||||||
import { reactive, ref } from 'vue'
|
import { reactive, ref } from 'vue'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import router from "@/router"; //根路由对象
|
|
||||||
import lang from '../i18n/i18n';
|
import lang from '../i18n/i18n';
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
import { getCurrentInstance } from 'vue'
|
||||||
|
const app = getCurrentInstance()
|
||||||
|
const $http = app.appContext.config.globalProperties.$http
|
||||||
|
|
||||||
const adminSettings = reactive({
|
const adminSettings = reactive({
|
||||||
"account": "admin",
|
"account": "admin",
|
||||||
@ -187,10 +349,15 @@ const domainSettings = reactive({
|
|||||||
|
|
||||||
const sslSettings = reactive({
|
const sslSettings = reactive({
|
||||||
"type": "0",
|
"type": "0",
|
||||||
|
"provider": "",
|
||||||
|
"challenge": "http",
|
||||||
"key_path": "./config/ssl/private.key",
|
"key_path": "./config/ssl/private.key",
|
||||||
"crt_path": "./config/ssl/public.crt"
|
"crt_path": "./config/ssl/public.crt",
|
||||||
|
"paramsList": {},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const dnsApiParams = reactive({})
|
||||||
|
|
||||||
const active = ref(0)
|
const active = ref(0)
|
||||||
const fullscreenLoading = ref(false)
|
const fullscreenLoading = ref(false)
|
||||||
|
|
||||||
@ -288,6 +455,12 @@ const getSSLConfig = () => {
|
|||||||
ElMessage.error(res.errorMsg)
|
ElMessage.error(res.errorMsg)
|
||||||
} else {
|
} else {
|
||||||
sslSettings.type = res.data.type
|
sslSettings.type = res.data.type
|
||||||
|
if (sslSettings.type == "2"){
|
||||||
|
sslSettings.type = "0"
|
||||||
|
sslSettings.challenge="dns"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
port.value = res.data.port
|
port.value = res.data.port
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -296,20 +469,68 @@ const getSSLConfig = () => {
|
|||||||
|
|
||||||
const setSSLConfig = () => {
|
const setSSLConfig = () => {
|
||||||
fullscreenLoading.value = true;
|
fullscreenLoading.value = true;
|
||||||
$http.post("/api/setup", { "action": "set", "step": "ssl", "ssl_type": sslSettings.type, "key_path": sslSettings.key_path, "crt_path": sslSettings.crt_path }).then((res) => {
|
|
||||||
|
let sslType = sslSettings.type;
|
||||||
|
if (sslType == "0" && sslSettings.challenge == "dns") {
|
||||||
|
sslType = "2"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sslType == "2") {
|
||||||
|
|
||||||
|
let params = { "action": "setParams", "step": "ssl", };
|
||||||
|
|
||||||
|
params = Object.assign(params, dnsApiParams);
|
||||||
|
|
||||||
|
// dns验证方式先提交DNS api Key
|
||||||
|
$http.post("/api/setup", params).then((res) => {
|
||||||
|
if (res.errorNo != 0) {
|
||||||
|
fullscreenLoading.value = false;
|
||||||
|
ElMessage.error(res.errorMsg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$http.post("/api/setup", {
|
||||||
|
"action": "set",
|
||||||
|
"step": "ssl",
|
||||||
|
"ssl_type": sslType,
|
||||||
|
"key_path": sslSettings.key_path,
|
||||||
|
"crt_path": sslSettings.crt_path,
|
||||||
|
"serviceName": sslSettings.provider
|
||||||
|
}).then((res) => {
|
||||||
if (res.errorNo != 0) {
|
if (res.errorNo != 0) {
|
||||||
fullscreenLoading.value = false;
|
fullscreenLoading.value = false;
|
||||||
ElMessage.error(res.errorMsg)
|
ElMessage.error(res.errorMsg)
|
||||||
} else {
|
} else {
|
||||||
setTimeout(function () {
|
checkStatus();
|
||||||
window.location.href = "https://" + domainSettings.web_domain;
|
|
||||||
}, 10000);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const checkStatus = () => {
|
||||||
|
axios.post("/api/ping", {}).then((res) => {
|
||||||
|
if (res.data.errorNo != 0) {
|
||||||
|
setTimeout(function () {
|
||||||
|
checkStatus()
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
window.location.href = "https://" + domainSettings.web_domain;
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
setTimeout(function () {
|
||||||
|
checkStatus()
|
||||||
|
}, 1000);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const setDomainConfig = () => {
|
const setDomainConfig = () => {
|
||||||
$http.post("/api/setup", { "action": "set", "step": "domain", "web_domain": domainSettings.web_domain, "smtp_domain": domainSettings.smtp_domain }).then((res) => {
|
$http.post("/api/setup", { "action": "set", "step": "domain", "web_domain": domainSettings.web_domain, "smtp_domain": domainSettings.smtp_domain }).then((res) => {
|
||||||
if (res.errorNo != 0) {
|
if (res.errorNo != 0) {
|
||||||
@ -321,6 +542,19 @@ const setDomainConfig = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const provide_change = () => {
|
||||||
|
console.log(sslSettings.provider)
|
||||||
|
$http.post("/api/setup", { "action": "getParams", "step": "ssl", "serverName": sslSettings.provider }).then((res) => {
|
||||||
|
if (res.errorNo != 0) {
|
||||||
|
ElMessage.error(res.errorMsg)
|
||||||
|
} else {
|
||||||
|
sslSettings.paramsList = res.data
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const next = () => {
|
const next = () => {
|
||||||
switch (active.value) {
|
switch (active.value) {
|
||||||
@ -377,5 +611,6 @@ const next = () => {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#next {}
|
#next {}
|
||||||
</style>
|
</style>
|
@ -13,7 +13,8 @@ type Config struct {
|
|||||||
Domains []string `json:"domains"` //多域名设置,把所有收信域名都填进去
|
Domains []string `json:"domains"` //多域名设置,把所有收信域名都填进去
|
||||||
WebDomain string `json:"webDomain"`
|
WebDomain string `json:"webDomain"`
|
||||||
DkimPrivateKeyPath string `json:"dkimPrivateKeyPath"`
|
DkimPrivateKeyPath string `json:"dkimPrivateKeyPath"`
|
||||||
SSLType string `json:"sslType"` // 0表示自动生成证书,1表示用户上传证书
|
SSLType string `json:"sslType"` // 0表示自动生成证书,HTTP挑战模式,1表示用户上传证书,2表示自动-DNS挑战模式
|
||||||
|
DomainServiceName string `json:"domainServerName"` // 域名服务商名称
|
||||||
SSLPrivateKeyPath string `json:"SSLPrivateKeyPath"`
|
SSLPrivateKeyPath string `json:"SSLPrivateKeyPath"`
|
||||||
SSLPublicKeyPath string `json:"SSLPublicKeyPath"`
|
SSLPublicKeyPath string `json:"SSLPublicKeyPath"`
|
||||||
DbDSN string `json:"dbDSN"`
|
DbDSN string `json:"dbDSN"`
|
||||||
@ -46,8 +47,9 @@ func (c *Config) SetSetupPort(setupPort int) {
|
|||||||
|
|
||||||
const DBTypeMySQL = "mysql"
|
const DBTypeMySQL = "mysql"
|
||||||
const DBTypeSQLite = "sqlite"
|
const DBTypeSQLite = "sqlite"
|
||||||
const SSLTypeAuto = "0" //自动生成证书
|
const SSLTypeAutoHTTP = "0" //自动生成证书
|
||||||
const SSLTypeUser = "1" //用户上传证书
|
const SSLTypeAutoDNS = "2" //自动生成证书,DNS api验证
|
||||||
|
const SSLTypeUser = "1" //用户上传证书
|
||||||
|
|
||||||
var DBTypes []string = []string{DBTypeMySQL, DBTypeSQLite}
|
var DBTypes []string = []string{DBTypeMySQL, DBTypeSQLite}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
"webDomain": "mail.domain.com",
|
"webDomain": "mail.domain.com",
|
||||||
"dkimPrivateKeyPath": "config/dkim/dkim.priv",
|
"dkimPrivateKeyPath": "config/dkim/dkim.priv",
|
||||||
"sslType": "0",
|
"sslType": "0",
|
||||||
|
"domainServerName": "",
|
||||||
"SSLPrivateKeyPath": "config/ssl/private.key",
|
"SSLPrivateKeyPath": "config/ssl/private.key",
|
||||||
"SSLPublicKeyPath": "config/ssl/public.crt",
|
"SSLPublicKeyPath": "config/ssl/public.crt",
|
||||||
"dbDSN": "./config/pmail.db",
|
"dbDSN": "./config/pmail.db",
|
||||||
|
20
server/consts/consts.go
Normal file
20
server/consts/consts.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package consts
|
||||||
|
|
||||||
|
const (
|
||||||
|
// EmailTypeSend 发信
|
||||||
|
EmailTypeSend int8 = 1
|
||||||
|
// EmailTypeReceive 收信
|
||||||
|
EmailTypeReceive int8 = 0
|
||||||
|
|
||||||
|
//EmailStatusWait 0未发送
|
||||||
|
EmailStatusWait int8 = 0
|
||||||
|
|
||||||
|
//EmailStatusSent 1已发送
|
||||||
|
EmailStatusSent int8 = 1
|
||||||
|
|
||||||
|
//EmailStatusFail 2发送失败
|
||||||
|
EmailStatusFail int8 = 2
|
||||||
|
|
||||||
|
//EmailStatusDel 3删除
|
||||||
|
EmailStatusDel int8 = 3
|
||||||
|
)
|
@ -6,7 +6,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"pmail/dto/response"
|
"pmail/dto/response"
|
||||||
"pmail/services/auth"
|
|
||||||
"pmail/services/detail"
|
"pmail/services/detail"
|
||||||
"pmail/utils/context"
|
"pmail/utils/context"
|
||||||
)
|
)
|
||||||
@ -37,13 +36,6 @@ func EmailDetail(ctx *context.Context, w http.ResponseWriter, req *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否有权限
|
|
||||||
hasAuth := auth.HasAuth(ctx, email)
|
|
||||||
if !hasAuth {
|
|
||||||
response.NewErrorResponse(response.ParamsError, "", "").FPrint(w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
response.NewSuccessResponse(email).FPrint(w)
|
response.NewSuccessResponse(email).FPrint(w)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"pmail/dto"
|
||||||
"pmail/dto/response"
|
"pmail/dto/response"
|
||||||
"pmail/services/list"
|
"pmail/services/list"
|
||||||
"pmail/utils/context"
|
"pmail/utils/context"
|
||||||
@ -61,7 +62,14 @@ func EmailList(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
|
|||||||
retData.PageSize = 15
|
retData.PageSize = 15
|
||||||
}
|
}
|
||||||
|
|
||||||
emailList, total := list.GetEmailList(ctx, retData.Tag, retData.Keyword, offset, retData.PageSize)
|
var tagInfo dto.SearchTag = dto.SearchTag{
|
||||||
|
Type: -1,
|
||||||
|
Status: -1,
|
||||||
|
GroupId: -1,
|
||||||
|
}
|
||||||
|
_ = json.Unmarshal([]byte(retData.Tag), &tagInfo)
|
||||||
|
|
||||||
|
emailList, total := list.GetEmailList(ctx, tagInfo, retData.Keyword, false, offset, retData.PageSize)
|
||||||
|
|
||||||
for _, email := range emailList {
|
for _, email := range emailList {
|
||||||
var sender User
|
var sender User
|
||||||
|
@ -64,6 +64,11 @@ func Send(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !ctx.IsAdmin && reqData.From.Name != ctx.UserAccount {
|
||||||
|
response.NewErrorResponse(response.ParamsError, "params error", "").FPrint(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if reqData.From.Email == "" && reqData.From.Name != "" {
|
if reqData.From.Email == "" && reqData.From.Name != "" {
|
||||||
reqData.From.Email = reqData.From.Name + "@" + config.Instance.Domain
|
reqData.From.Email = reqData.From.Name + "@" + config.Instance.Domain
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"pmail/models"
|
"pmail/models"
|
||||||
"pmail/session"
|
"pmail/session"
|
||||||
"pmail/utils/context"
|
"pmail/utils/context"
|
||||||
|
"pmail/utils/errors"
|
||||||
"pmail/utils/password"
|
"pmail/utils/password"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -35,17 +36,25 @@ func Login(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
|
|||||||
var user models.User
|
var user models.User
|
||||||
|
|
||||||
encodePwd := password.Encode(reqData.Password)
|
encodePwd := password.Encode(reqData.Password)
|
||||||
|
_, err = db.Instance.Where("account =? and password =? and disabled=0", reqData.Account, encodePwd).Get(&user)
|
||||||
_, err = db.Instance.Where("account =? and password =?", reqData.Account, encodePwd).Get(&user)
|
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||||
if err != nil && err != sql.ErrNoRows {
|
|
||||||
log.Errorf("%+v", err)
|
log.Errorf("%+v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.ID != 0 {
|
if user.ID != 0 {
|
||||||
userStr, _ := json.Marshal(user)
|
userStr, _ := json.Marshal(user)
|
||||||
session.Instance.Put(req.Context(), "user", string(userStr))
|
session.Instance.Put(req.Context(), "user", string(userStr))
|
||||||
response.NewSuccessResponse("").FPrint(w)
|
response.NewSuccessResponse(map[string]any{
|
||||||
|
"account": user.Account,
|
||||||
|
"name": user.Name,
|
||||||
|
"is_admin": user.IsAdmin,
|
||||||
|
}).FPrint(w)
|
||||||
} else {
|
} else {
|
||||||
response.NewErrorResponse(response.ParamsError, i18n.GetText(ctx.Lang, "aperror"), "").FPrint(w)
|
response.NewErrorResponse(response.ParamsError, i18n.GetText(ctx.Lang, "aperror"), "").FPrint(w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Logout(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
|
||||||
|
session.Instance.Clear(ctx.Context)
|
||||||
|
response.NewSuccessResponse("Success").FPrint(w)
|
||||||
|
}
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"pmail/dto/response"
|
"pmail/dto/response"
|
||||||
"pmail/utils/context"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Ping(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
|
func Ping(w http.ResponseWriter, req *http.Request) {
|
||||||
response.NewSuccessResponse("pong").FPrint(w)
|
response.NewSuccessResponse("pong").FPrint(w)
|
||||||
log.WithContext(ctx).Info("pong")
|
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func GetRule(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
|
func GetRule(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
|
||||||
res := rule.GetAllRules(ctx)
|
res := rule.GetAllRules(ctx, ctx.UserID)
|
||||||
response.NewSuccessResponse(res).FPrint(w)
|
response.NewSuccessResponse(res).FPrint(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,14 +134,39 @@ func Setup(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if reqData["step"] == "ssl" && reqData["action"] == "getParams" {
|
||||||
|
params, err := ssl.GetServerParamsList(reqData["serverName"])
|
||||||
|
if err != nil {
|
||||||
|
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.NewSuccessResponse(params).FPrint(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if reqData["step"] == "ssl" && reqData["action"] == "setParams" {
|
||||||
|
for key, v := range reqData {
|
||||||
|
if key != "step" && key != "action" {
|
||||||
|
ssl.SetDomainServerParams(key, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response.NewSuccessResponse("Succ").FPrint(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if reqData["step"] == "ssl" && reqData["action"] == "set" {
|
if reqData["step"] == "ssl" && reqData["action"] == "set" {
|
||||||
err := ssl.SetSSL(reqData["ssl_type"], reqData["key_path"], reqData["crt_path"])
|
|
||||||
|
serviceName, ok := reqData["serviceName"]
|
||||||
|
if !ok {
|
||||||
|
serviceName = ""
|
||||||
|
}
|
||||||
|
err := ssl.SetSSL(reqData["ssl_type"], reqData["key_path"], reqData["crt_path"], serviceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
|
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if reqData["ssl_type"] == config.SSLTypeAuto {
|
if reqData["ssl_type"] == config.SSLTypeAutoHTTP || reqData["ssl_type"] == config.SSLTypeAutoDNS {
|
||||||
err = ssl.GenSSL(false)
|
err = ssl.GenSSL(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
|
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
|
||||||
@ -150,7 +175,10 @@ func Setup(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
response.NewSuccessResponse("Succ").FPrint(w)
|
response.NewSuccessResponse("Succ").FPrint(w)
|
||||||
setup.Finish(ctx)
|
|
||||||
|
if reqData["ssl_type"] == config.SSLTypeUser {
|
||||||
|
setup.Finish()
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
174
server/controllers/user.go
Normal file
174
server/controllers/user.go
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cast"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"net/http"
|
||||||
|
"pmail/db"
|
||||||
|
"pmail/dto/response"
|
||||||
|
"pmail/models"
|
||||||
|
"pmail/utils/context"
|
||||||
|
"pmail/utils/password"
|
||||||
|
)
|
||||||
|
|
||||||
|
type userCreateRequest struct {
|
||||||
|
Id int `json:"id"`
|
||||||
|
Account string `json:"account"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Disabled int `json:"disabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateUser(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
|
if !ctx.IsAdmin {
|
||||||
|
response.NewErrorResponse(response.NoAccessPrivileges, "No Access Privileges", "").FPrint(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBytes, err := io.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("%+v", err)
|
||||||
|
}
|
||||||
|
var reqData userCreateRequest
|
||||||
|
err = json.Unmarshal(reqBytes, &reqData)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("%+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if reqData.Username == "" || reqData.Password == "" || reqData.Account == "" {
|
||||||
|
response.NewErrorResponse(response.ParamsError, "Params Error", "").FPrint(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var user models.User
|
||||||
|
user.Name = reqData.Username
|
||||||
|
user.Password = password.Encode(reqData.Password)
|
||||||
|
user.Account = reqData.Account
|
||||||
|
|
||||||
|
_, err = db.Instance.Insert(&user)
|
||||||
|
if err != nil {
|
||||||
|
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.NewSuccessResponse(user).FPrint(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
type userListRequest struct {
|
||||||
|
CurrentPage int `json:"current_page"`
|
||||||
|
PageSize int `json:"page_size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserList(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
|
||||||
|
if !ctx.IsAdmin {
|
||||||
|
response.NewErrorResponse(response.NoAccessPrivileges, "No Access Privileges", "").FPrint(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBytes, err := io.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("%+v", err)
|
||||||
|
}
|
||||||
|
var reqData userListRequest
|
||||||
|
err = json.Unmarshal(reqBytes, &reqData)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("%+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := 0
|
||||||
|
if reqData.CurrentPage >= 1 {
|
||||||
|
offset = (reqData.CurrentPage - 1) * reqData.PageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
if reqData.PageSize == 0 {
|
||||||
|
reqData.PageSize = 15
|
||||||
|
}
|
||||||
|
|
||||||
|
var users []models.User
|
||||||
|
|
||||||
|
totalNum, err := db.Instance.Table(&models.User{}).Limit(reqData.PageSize, offset).FindAndCount(&users)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("%+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
response.NewSuccessResponse(map[string]any{
|
||||||
|
"current_page": reqData.CurrentPage,
|
||||||
|
"total_page": cast.ToInt(math.Ceil(cast.ToFloat64(totalNum) / cast.ToFloat64(reqData.PageSize))),
|
||||||
|
"list": users,
|
||||||
|
}).FPrint(w)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func Info(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
|
||||||
|
response.NewSuccessResponse(map[string]any{
|
||||||
|
"account": ctx.UserAccount,
|
||||||
|
"name": ctx.UserName,
|
||||||
|
"is_admin": ctx.IsAdmin,
|
||||||
|
}).FPrint(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func EditUser(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
|
if !ctx.IsAdmin {
|
||||||
|
response.NewErrorResponse(response.NoAccessPrivileges, "No Access Privileges", "").FPrint(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBytes, err := io.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("%+v", err)
|
||||||
|
}
|
||||||
|
var reqData userCreateRequest
|
||||||
|
err = json.Unmarshal(reqBytes, &reqData)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("%+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if reqData.Id == 0 && reqData.Account == "" {
|
||||||
|
response.NewErrorResponse(response.ParamsError, "Params Error", "").FPrint(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var user models.User
|
||||||
|
if reqData.Id != 0 {
|
||||||
|
_, err = db.Instance.Where("id=?", reqData.Id).Get(&user)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("SQL Error: %+v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_, err = db.Instance.Where("account=?", reqData.Account).Get(&user)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("SQL Error: %+v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if user.ID == 0 {
|
||||||
|
response.NewErrorResponse(response.ParamsError, "User not found", "").FPrint(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if reqData.Username != "" && reqData.Username != user.Name {
|
||||||
|
user.Name = reqData.Username
|
||||||
|
}
|
||||||
|
|
||||||
|
if reqData.Disabled != user.Disabled {
|
||||||
|
user.Disabled = reqData.Disabled
|
||||||
|
}
|
||||||
|
if reqData.Password != "" {
|
||||||
|
user.Password = password.Encode(reqData.Password)
|
||||||
|
}
|
||||||
|
|
||||||
|
num, err := db.Instance.ID(user.ID).Cols("name", "password", "disabled").Update(&user)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if num == 0 {
|
||||||
|
response.NewErrorResponse(response.ServerError, "No Data Update", "").FPrint(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.NewSuccessResponse(user).FPrint(w)
|
||||||
|
}
|
@ -3,6 +3,7 @@ package db
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
_ "github.com/go-sql-driver/mysql"
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
_ "modernc.org/sqlite"
|
_ "modernc.org/sqlite"
|
||||||
"pmail/config"
|
"pmail/config"
|
||||||
"pmail/models"
|
"pmail/models"
|
||||||
@ -13,27 +14,45 @@ import (
|
|||||||
|
|
||||||
var Instance *xorm.Engine
|
var Instance *xorm.Engine
|
||||||
|
|
||||||
func Init() error {
|
func Init(version string) error {
|
||||||
dsn := config.Instance.DbDSN
|
dsn := config.Instance.DbDSN
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
switch config.Instance.DbType {
|
switch config.Instance.DbType {
|
||||||
case "mysql":
|
case "mysql":
|
||||||
Instance, err = xorm.NewEngine("mysql", dsn)
|
Instance, err = xorm.NewEngine("mysql", dsn)
|
||||||
|
Instance.SetMaxOpenConns(100)
|
||||||
|
Instance.SetMaxIdleConns(10)
|
||||||
case "sqlite":
|
case "sqlite":
|
||||||
Instance, err = xorm.NewEngine("sqlite", dsn)
|
Instance, err = xorm.NewEngine("sqlite", dsn)
|
||||||
|
Instance.SetMaxOpenConns(1)
|
||||||
|
Instance.SetMaxIdleConns(1)
|
||||||
default:
|
default:
|
||||||
return errors.New("Database Type Error!")
|
return errors.New("Database Type Error!")
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err)
|
return errors.Wrap(err)
|
||||||
}
|
}
|
||||||
Instance.SetMaxOpenConns(100)
|
|
||||||
Instance.SetMaxIdleConns(10)
|
|
||||||
|
|
||||||
|
Instance.ShowSQL(false)
|
||||||
// 同步表结构
|
// 同步表结构
|
||||||
syncTables()
|
syncTables()
|
||||||
|
|
||||||
|
// 更新历史数据
|
||||||
|
fixHistoryData()
|
||||||
|
|
||||||
|
// 在数据库中记录程序版本
|
||||||
|
var v models.Version
|
||||||
|
_, err = Instance.Get(&v)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if version != "" && v.Info != version {
|
||||||
|
v.Info = version
|
||||||
|
Instance.Update(&v)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,13 +81,75 @@ func syncTables() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
err = Instance.Sync2(&models.UserAuth{})
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
err = Instance.Sync2(&models.Sessions{})
|
err = Instance.Sync2(&models.Sessions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
err = Instance.Sync2(&models.UserEmail{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
err = Instance.Sync2(&models.Version{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fixHistoryData() {
|
||||||
|
var ueNum int
|
||||||
|
_, err := Instance.Table(&models.UserEmail{}).Select("count(1)").Get(&ueNum)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if ueNum > 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只有一个管理员用户
|
||||||
|
var user []models.User
|
||||||
|
err = Instance.Table(&models.User{}).OrderBy("id asc").Find(&user)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只有一个账号,且不是管理员账号,将账号提权为管理员
|
||||||
|
if len(user) == 1 && user[0].IsAdmin == 0 {
|
||||||
|
u := user[0]
|
||||||
|
u.IsAdmin = 1
|
||||||
|
_, err = Instance.Update(&u)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(user) != 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 以前有邮件
|
||||||
|
var emails []*models.Email
|
||||||
|
err = Instance.Table(&models.Email{}).Select("id,status").OrderBy("id asc").Find(&emails)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if len(emails) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Sync History Data!Please Wait!")
|
||||||
|
|
||||||
|
// 把以前的邮件,全部分到管理员账号下面去
|
||||||
|
for _, email := range emails {
|
||||||
|
ue := models.UserEmail{
|
||||||
|
UserID: user[0].ID,
|
||||||
|
EmailID: email.Id,
|
||||||
|
Status: email.Status,
|
||||||
|
}
|
||||||
|
_, err = Instance.Insert(&ue)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("SQL Error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Infof("Sync History Data Finished. Num: %d", len(emails))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -52,9 +52,7 @@ type Email struct {
|
|||||||
Attachments []*Attachment
|
Attachments []*Attachment
|
||||||
ReadReceipt []string
|
ReadReceipt []string
|
||||||
Date string
|
Date string
|
||||||
IsRead int
|
|
||||||
Status int // 0未发送,1已发送,2发送失败,3删除
|
Status int // 0未发送,1已发送,2发送失败,3删除
|
||||||
GroupId int // 分组id
|
|
||||||
MessageId int64
|
MessageId int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
8
server/dto/response/email.go
Normal file
8
server/dto/response/email.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package response
|
||||||
|
|
||||||
|
import "pmail/models"
|
||||||
|
|
||||||
|
type EmailResponseData struct {
|
||||||
|
models.Email `xorm:"extends"`
|
||||||
|
IsRead int8 `json:"is_read"`
|
||||||
|
}
|
@ -6,10 +6,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
NeedSetup = 402
|
NeedSetup = 402
|
||||||
NeedLogin = 403
|
NeedLogin = 403
|
||||||
ParamsError = 100
|
NoAccessPrivileges = 405
|
||||||
ServerError = 500
|
ParamsError = 100
|
||||||
|
ServerError = 500
|
||||||
)
|
)
|
||||||
|
|
||||||
type Response struct {
|
type Response struct {
|
||||||
|
@ -17,6 +17,7 @@ var (
|
|||||||
|
|
||||||
type Rule struct {
|
type Rule struct {
|
||||||
Id int `json:"id"`
|
Id int `json:"id"`
|
||||||
|
UserId int `json:"user_id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Rules []*Value `json:"rules"`
|
Rules []*Value `json:"rules"`
|
||||||
Action RuleType `json:"action"`
|
Action RuleType `json:"action"`
|
||||||
@ -37,6 +38,7 @@ func (p *Rule) Decode(data *models.Rule) *Rule {
|
|||||||
p.Action = RuleType(data.Action)
|
p.Action = RuleType(data.Action)
|
||||||
p.Sort = data.Sort
|
p.Sort = data.Sort
|
||||||
p.Params = data.Params
|
p.Params = data.Params
|
||||||
|
p.UserId = data.UserId
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,9 +3,9 @@ package dto
|
|||||||
import "encoding/json"
|
import "encoding/json"
|
||||||
|
|
||||||
type SearchTag struct {
|
type SearchTag struct {
|
||||||
Type int `json:"type"` // -1 不限
|
Type int8 `json:"type"` // -1 不限
|
||||||
Status int `json:"status"` // -1 不限
|
Status int8 `json:"status"` // -1 不限
|
||||||
GroupId int `json:"group_id"` // -1 不限
|
GroupId int `json:"group_id"` // -1 不限
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t SearchTag) ToString() string {
|
func (t SearchTag) ToString() string {
|
||||||
|
238
server/go.mod
238
server/go.mod
@ -1,6 +1,6 @@
|
|||||||
module pmail
|
module pmail
|
||||||
|
|
||||||
go 1.22
|
go 1.22.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Jinnrry/gopop v0.0.0-20231113115125-fbdf52ae39ea
|
github.com/Jinnrry/gopop v0.0.0-20231113115125-fbdf52ae39ea
|
||||||
@ -10,45 +10,255 @@ require (
|
|||||||
github.com/emersion/go-message v0.18.1
|
github.com/emersion/go-message v0.18.1
|
||||||
github.com/emersion/go-msgauth v0.6.8
|
github.com/emersion/go-msgauth v0.6.8
|
||||||
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43
|
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43
|
||||||
github.com/emersion/go-smtp v0.21.0
|
github.com/emersion/go-smtp v0.21.2
|
||||||
github.com/go-acme/lego/v4 v4.16.1
|
github.com/go-acme/lego/v4 v4.17.3
|
||||||
github.com/go-sql-driver/mysql v1.8.1
|
github.com/go-sql-driver/mysql v1.8.1
|
||||||
github.com/mileusna/spf v0.9.5
|
github.com/mileusna/spf v0.9.5
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/spf13/cast v1.6.0
|
github.com/spf13/cast v1.6.0
|
||||||
golang.org/x/crypto v0.22.0
|
golang.org/x/crypto v0.24.0
|
||||||
golang.org/x/text v0.14.0
|
golang.org/x/text v0.16.0
|
||||||
modernc.org/sqlite v1.29.6
|
modernc.org/sqlite v1.30.0
|
||||||
xorm.io/builder v0.3.13
|
xorm.io/builder v0.3.13
|
||||||
xorm.io/xorm v1.3.9
|
xorm.io/xorm v1.3.9
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
cloud.google.com/go/auth v0.5.1 // indirect
|
||||||
|
cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
|
||||||
|
cloud.google.com/go/compute/metadata v0.3.0 // indirect
|
||||||
filippo.io/edwards25519 v1.1.0 // indirect
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
|
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.2.0 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
|
||||||
|
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||||
|
github.com/Azure/go-autorest/autorest v0.11.29 // indirect
|
||||||
|
github.com/Azure/go-autorest/autorest/adal v0.9.24 // indirect
|
||||||
|
github.com/Azure/go-autorest/autorest/azure/auth v0.5.13 // indirect
|
||||||
|
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect
|
||||||
|
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||||
|
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
|
||||||
|
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||||
|
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||||
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
|
||||||
|
github.com/BurntSushi/toml v1.4.0 // indirect
|
||||||
|
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect
|
||||||
|
github.com/CloudyKit/jet/v6 v6.2.0 // indirect
|
||||||
|
github.com/Joker/jade v1.1.3 // indirect
|
||||||
|
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
|
||||||
|
github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 // indirect
|
||||||
|
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 // indirect
|
||||||
|
github.com/aliyun/alibaba-cloud-sdk-go v1.62.758 // indirect
|
||||||
|
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||||
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.27.1 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/config v1.27.17 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/credentials v1.17.17 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.4 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.8 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.8 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.10 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/lightsail v1.38.2 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/route53 v1.40.9 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sso v1.20.10 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.4 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.28.11 // indirect
|
||||||
|
github.com/aws/smithy-go v1.20.2 // indirect
|
||||||
|
github.com/aymerick/douceur v0.2.0 // indirect
|
||||||
|
github.com/benbjohnson/clock v1.3.5 // indirect
|
||||||
|
github.com/boombuler/barcode v1.0.1 // indirect
|
||||||
|
github.com/bytedance/sonic v1.11.8 // indirect
|
||||||
|
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
|
github.com/civo/civogo v0.3.70 // indirect
|
||||||
|
github.com/cloudflare/cloudflare-go v0.97.0 // indirect
|
||||||
|
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||||
|
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||||
|
github.com/cpu/goacmedns v0.1.1 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/deepmap/oapi-codegen v1.16.3 // indirect
|
||||||
|
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||||
|
github.com/dnsimple/dnsimple-go v1.7.0 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
|
github.com/exoscale/egoscale v0.102.3 // indirect
|
||||||
github.com/goccy/go-json v0.10.2 // indirect
|
github.com/fatih/structs v1.1.0 // indirect
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
|
github.com/flosch/pongo2/v4 v4.0.2 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.4 // indirect
|
||||||
|
github.com/ghodss/yaml v1.0.0 // indirect
|
||||||
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
|
github.com/gin-gonic/gin v1.10.0 // indirect
|
||||||
|
github.com/go-errors/errors v1.5.1 // indirect
|
||||||
|
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
|
||||||
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.21.0 // indirect
|
||||||
|
github.com/go-resty/resty/v2 v2.13.1 // indirect
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.0.0 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.3 // indirect
|
||||||
|
github.com/gofrs/flock v0.8.1 // indirect
|
||||||
|
github.com/gofrs/uuid v4.4.0+incompatible // indirect
|
||||||
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||||
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
|
github.com/gomarkdown/markdown v0.0.0-20240419095408-642f0ee99ae2 // indirect
|
||||||
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
|
github.com/google/gofuzz v1.2.0 // indirect
|
||||||
|
github.com/google/s2a-go v0.1.7 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||||
|
github.com/googleapis/gax-go/v2 v2.12.4 // indirect
|
||||||
|
github.com/gophercloud/gophercloud v1.12.0 // indirect
|
||||||
|
github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 // indirect
|
||||||
|
github.com/gorilla/css v1.0.1 // indirect
|
||||||
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
|
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
|
||||||
|
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||||
|
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
|
||||||
|
github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
|
||||||
|
github.com/iris-contrib/schema v0.0.6 // indirect
|
||||||
|
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||||
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect
|
||||||
|
github.com/kataras/blocks v0.0.8 // indirect
|
||||||
|
github.com/kataras/golog v0.1.12 // indirect
|
||||||
|
github.com/kataras/iris/v12 v12.2.11 // indirect
|
||||||
|
github.com/kataras/pio v0.0.13 // indirect
|
||||||
|
github.com/kataras/sitemap v0.0.6 // indirect
|
||||||
|
github.com/kataras/tunnel v0.0.4 // indirect
|
||||||
|
github.com/klauspost/compress v1.17.8 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||||
|
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
|
||||||
|
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||||
|
github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect
|
||||||
|
github.com/labbsr0x/goh v1.0.1 // indirect
|
||||||
|
github.com/labstack/echo/v4 v4.12.0 // indirect
|
||||||
|
github.com/labstack/gommon v0.4.2 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/linode/linodego v1.35.0 // indirect
|
||||||
|
github.com/liquidweb/liquidweb-cli v0.7.0 // indirect
|
||||||
|
github.com/liquidweb/liquidweb-go v1.6.4 // indirect
|
||||||
|
github.com/mailgun/raymond/v2 v2.0.48 // indirect
|
||||||
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/miekg/dns v1.1.58 // indirect
|
github.com/microcosm-cc/bluemonday v1.0.26 // indirect
|
||||||
|
github.com/miekg/dns v1.1.59 // indirect
|
||||||
|
github.com/mimuret/golang-iij-dpf v0.9.1 // indirect
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
|
||||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||||
|
github.com/nrdcg/auroradns v1.1.0 // indirect
|
||||||
|
github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3 // indirect
|
||||||
|
github.com/nrdcg/desec v0.8.0 // indirect
|
||||||
|
github.com/nrdcg/dnspod-go v0.4.0 // indirect
|
||||||
|
github.com/nrdcg/freemyip v0.2.0 // indirect
|
||||||
|
github.com/nrdcg/goinwx v0.10.0 // indirect
|
||||||
|
github.com/nrdcg/mailinabox v0.2.0 // indirect
|
||||||
|
github.com/nrdcg/namesilo v0.2.1 // indirect
|
||||||
|
github.com/nrdcg/nodion v0.1.0 // indirect
|
||||||
|
github.com/nrdcg/porkbun v0.3.0 // indirect
|
||||||
|
github.com/nzdjb/go-metaname v1.0.0 // indirect
|
||||||
|
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
|
||||||
|
github.com/oracle/oci-go-sdk/v65 v65.67.0 // indirect
|
||||||
|
github.com/ovh/go-ovh v1.5.1 // indirect
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/pquerna/otp v1.4.0 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
|
github.com/sacloud/api-client-go v0.2.10 // indirect
|
||||||
|
github.com/sacloud/go-http v0.1.8 // indirect
|
||||||
|
github.com/sacloud/iaas-api-go v1.12.0 // indirect
|
||||||
|
github.com/sacloud/packages-go v0.0.10 // indirect
|
||||||
|
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.27 // indirect
|
||||||
|
github.com/schollz/closestmatch v2.1.0+incompatible // indirect
|
||||||
|
github.com/selectel/domains-go v1.1.0 // indirect
|
||||||
|
github.com/selectel/go-selvpcclient/v3 v3.1.1 // indirect
|
||||||
|
github.com/shopspring/decimal v1.4.0 // indirect
|
||||||
|
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
|
||||||
|
github.com/softlayer/softlayer-go v1.1.5 // indirect
|
||||||
|
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
|
||||||
|
github.com/sony/gobreaker v1.0.0 // indirect
|
||||||
|
github.com/stretchr/objx v0.5.2 // indirect
|
||||||
|
github.com/stretchr/testify v1.9.0 // indirect
|
||||||
github.com/syndtr/goleveldb v1.0.0 // indirect
|
github.com/syndtr/goleveldb v1.0.0 // indirect
|
||||||
golang.org/x/mod v0.17.0 // indirect
|
github.com/tdewolff/minify/v2 v2.20.32 // indirect
|
||||||
golang.org/x/net v0.24.0 // indirect
|
github.com/tdewolff/parse/v2 v2.7.14 // indirect
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.938 // indirect
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.938 // indirect
|
||||||
|
github.com/transip/gotransip/v6 v6.24.0 // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
|
github.com/ultradns/ultradns-go-sdk v1.6.2-20240501171831-432d643 // indirect
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||||
|
github.com/vinyldns/go-vinyldns v0.9.16 // indirect
|
||||||
|
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
||||||
|
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||||
|
github.com/vultr/govultr/v2 v2.17.2 // indirect
|
||||||
|
github.com/yandex-cloud/go-genproto v0.0.0-20240529120826-df2b24336f42 // indirect
|
||||||
|
github.com/yandex-cloud/go-sdk v0.0.0-20240529122015-8b0dc5b8bcbf // indirect
|
||||||
|
github.com/yosssi/ace v0.0.5 // indirect
|
||||||
|
go.opencensus.io v0.24.0 // indirect
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.27.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/metric v1.27.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.27.0 // indirect
|
||||||
|
go.uber.org/ratelimit v0.3.1 // indirect
|
||||||
|
golang.org/x/arch v0.8.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 // indirect
|
||||||
|
golang.org/x/mod v0.18.0 // indirect
|
||||||
|
golang.org/x/net v0.26.0 // indirect
|
||||||
|
golang.org/x/oauth2 v0.21.0 // indirect
|
||||||
golang.org/x/sync v0.7.0 // indirect
|
golang.org/x/sync v0.7.0 // indirect
|
||||||
golang.org/x/sys v0.19.0 // indirect
|
golang.org/x/sys v0.21.0 // indirect
|
||||||
golang.org/x/tools v0.20.0 // indirect
|
golang.org/x/time v0.5.0 // indirect
|
||||||
|
golang.org/x/tools v0.22.0 // indirect
|
||||||
|
google.golang.org/api v0.183.0 // indirect
|
||||||
|
google.golang.org/genproto v0.0.0-20240604185151-ef581f913117 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect
|
||||||
|
google.golang.org/grpc v1.64.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.34.1 // indirect
|
||||||
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
|
gopkg.in/ns1/ns1-go.v2 v2.11.0 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
k8s.io/api v0.30.1 // indirect
|
||||||
|
k8s.io/apimachinery v0.30.1 // indirect
|
||||||
|
k8s.io/klog/v2 v2.120.1 // indirect
|
||||||
|
k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 // indirect
|
||||||
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b // indirect
|
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b // indirect
|
||||||
modernc.org/libc v1.49.3 // indirect
|
modernc.org/libc v1.52.1 // indirect
|
||||||
modernc.org/mathutil v1.6.0 // indirect
|
modernc.org/mathutil v1.6.0 // indirect
|
||||||
modernc.org/memory v1.8.0 // indirect
|
modernc.org/memory v1.8.0 // indirect
|
||||||
modernc.org/strutil v1.2.0 // indirect
|
modernc.org/strutil v1.2.0 // indirect
|
||||||
modernc.org/token v1.1.0 // indirect
|
modernc.org/token v1.1.0 // indirect
|
||||||
|
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||||
|
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||||
)
|
)
|
||||||
|
2403
server/go.sum
2403
server/go.sum
File diff suppressed because it is too large
Load Diff
@ -12,6 +12,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"pmail/dto/parsemail"
|
"pmail/dto/parsemail"
|
||||||
"pmail/hooks/framework"
|
"pmail/hooks/framework"
|
||||||
|
"pmail/models"
|
||||||
"pmail/utils/context"
|
"pmail/utils/context"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -26,12 +27,13 @@ type HookSender struct {
|
|||||||
socket string
|
socket string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HookSender) ReceiveSaveAfter(ctx *context.Context, email *parsemail.Email) {
|
func (h *HookSender) ReceiveSaveAfter(ctx *context.Context, email *parsemail.Email, ue []*models.UserEmail) {
|
||||||
log.WithContext(ctx).Debugf("[%s]Plugin ReceiveSaveAfter Start", h.name)
|
log.WithContext(ctx).Debugf("[%s]Plugin ReceiveSaveAfter Start", h.name)
|
||||||
|
|
||||||
dto := framework.HookDTO{
|
dto := framework.HookDTO{
|
||||||
Ctx: ctx,
|
Ctx: ctx,
|
||||||
Email: email,
|
Email: email,
|
||||||
|
UserEmail: ue,
|
||||||
}
|
}
|
||||||
body, _ := json.Marshal(dto)
|
body, _ := json.Marshal(dto)
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"pmail/dto/parsemail"
|
"pmail/dto/parsemail"
|
||||||
|
"pmail/models"
|
||||||
"pmail/utils/context"
|
"pmail/utils/context"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -25,7 +26,7 @@ type EmailHook interface {
|
|||||||
// ReceiveParseAfter 接收到邮件,解析之后的结构化数据 (收信规则前,写数据库前执行) 同步执行
|
// ReceiveParseAfter 接收到邮件,解析之后的结构化数据 (收信规则前,写数据库前执行) 同步执行
|
||||||
ReceiveParseAfter(ctx *context.Context, email *parsemail.Email)
|
ReceiveParseAfter(ctx *context.Context, email *parsemail.Email)
|
||||||
// ReceiveSaveAfter 邮件落库以后执行(收信规则后执行) 异步执行
|
// ReceiveSaveAfter 邮件落库以后执行(收信规则后执行) 异步执行
|
||||||
ReceiveSaveAfter(ctx *context.Context, email *parsemail.Email)
|
ReceiveSaveAfter(ctx *context.Context, email *parsemail.Email, ue []*models.UserEmail)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HookDTO PMail 主程序和插件通信的结构体
|
// HookDTO PMail 主程序和插件通信的结构体
|
||||||
@ -35,6 +36,7 @@ type HookDTO struct {
|
|||||||
Email *parsemail.Email // 邮件内容
|
Email *parsemail.Email // 邮件内容
|
||||||
EmailByte *[]byte // 未解析前的文件内容
|
EmailByte *[]byte // 未解析前的文件内容
|
||||||
ErrMap map[string]error // 错误信息
|
ErrMap map[string]error // 错误信息
|
||||||
|
UserEmail []*models.UserEmail
|
||||||
}
|
}
|
||||||
|
|
||||||
type Plugin struct {
|
type Plugin struct {
|
||||||
@ -153,7 +155,7 @@ func (p *Plugin) Run() {
|
|||||||
log.Errorf("params error %+v", err)
|
log.Errorf("params error %+v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
p.hook.ReceiveSaveAfter(hookDTO.Ctx, hookDTO.Email)
|
p.hook.ReceiveSaveAfter(hookDTO.Ctx, hookDTO.Email, hookDTO.UserEmail)
|
||||||
body, _ = json.Marshal(hookDTO)
|
body, _ = json.Marshal(hookDTO)
|
||||||
writer.Write(body)
|
writer.Write(body)
|
||||||
log.Debugf("[%s] ReceiveSaveAfter End", p.name)
|
log.Debugf("[%s] ReceiveSaveAfter End", p.name)
|
||||||
|
@ -3,15 +3,15 @@ package main
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"pmail/config"
|
"pmail/config"
|
||||||
"pmail/dto/parsemail"
|
"pmail/dto/parsemail"
|
||||||
"pmail/hooks/framework"
|
"pmail/hooks/framework"
|
||||||
|
"pmail/models"
|
||||||
"pmail/utils/context"
|
"pmail/utils/context"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type TelegramPushHook struct {
|
type TelegramPushHook struct {
|
||||||
@ -21,15 +21,18 @@ type TelegramPushHook struct {
|
|||||||
webDomain string
|
webDomain string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TelegramPushHook) ReceiveSaveAfter(ctx *context.Context, email *parsemail.Email) {
|
func (w *TelegramPushHook) ReceiveSaveAfter(ctx *context.Context, email *parsemail.Email, ue []*models.UserEmail) {
|
||||||
if w.chatId == "" || w.botToken == "" {
|
if w.chatId == "" || w.botToken == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 被标记为已读,或者是已删除,或是垃圾邮件 就不处理了
|
|
||||||
if email.IsRead == 1 || email.Status == 3 || email.MessageId <= 0 {
|
for _, u := range ue {
|
||||||
return
|
// 管理员(Uid=1)收到邮件且非已读、非已删除 触发通知
|
||||||
|
if u.UserID == 1 && u.IsRead == 0 && u.Status != 3 && email.MessageId > 0 {
|
||||||
|
w.sendUserMsg(nil, email)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
w.sendUserMsg(nil, email)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TelegramPushHook) SendBefore(ctx *context.Context, email *parsemail.Email) {
|
func (w *TelegramPushHook) SendBefore(ctx *context.Context, email *parsemail.Email) {
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"pmail/config"
|
"pmail/config"
|
||||||
"pmail/dto/parsemail"
|
"pmail/dto/parsemail"
|
||||||
"pmail/hooks/framework"
|
"pmail/hooks/framework"
|
||||||
|
"pmail/models"
|
||||||
"pmail/utils/context"
|
"pmail/utils/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -17,7 +18,7 @@ type WebPushHook struct {
|
|||||||
token string
|
token string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WebPushHook) ReceiveSaveAfter(ctx *context.Context, email *parsemail.Email) {
|
func (w *WebPushHook) ReceiveSaveAfter(ctx *context.Context, email *parsemail.Email, ue []*models.UserEmail) {
|
||||||
if w.url == "" {
|
if w.url == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -77,7 +78,8 @@ func (w *WebPushHook) ReceiveParseBefore(ctx *context.Context, email *[]byte) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WebPushHook) ReceiveParseAfter(ctx *context.Context, email *parsemail.Email) {}
|
func (w *WebPushHook) ReceiveParseAfter(ctx *context.Context, email *parsemail.Email) {
|
||||||
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
WebPushUrl string `json:"webPushUrl"`
|
WebPushUrl string `json:"webPushUrl"`
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
## How To Ues
|
## How To Ues / 如何使用
|
||||||
|
|
||||||
|
|
||||||
Copy plugin binary file to `/plugins`
|
Copy plugin binary file to `/plugins`
|
||||||
|
|
||||||
|
复制插件二进制文件到`/plugins`文件夹
|
||||||
|
|
||||||
add config.json to `/plugins/config.com` like this:
|
add config.json to `/plugins/config.com` like this:
|
||||||
|
|
||||||
|
新建配置文件`/plugins/config.com`,内容如下
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"weChatPushAppId": "", // wechat appid
|
"weChatPushAppId": "", // wechat appid
|
||||||
@ -12,4 +16,16 @@ add config.json to `/plugins/config.com` like this:
|
|||||||
"weChatPushTemplateId": "", // weChat TemplateId
|
"weChatPushTemplateId": "", // weChat TemplateId
|
||||||
"weChatPushUserId": "", // weChat UserId
|
"weChatPushUserId": "", // weChat UserId
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
WeChat Message Template :
|
||||||
|
|
||||||
|
微信推送模板设置:
|
||||||
|
|
||||||
|
Template Title: New Email Notice
|
||||||
|
|
||||||
|
模板标题:新邮件提醒
|
||||||
|
|
||||||
|
Template Content: {{Content.DATA}}
|
||||||
|
|
||||||
|
模板内容:{{Content.DATA}}
|
@ -11,6 +11,7 @@ import (
|
|||||||
"pmail/config"
|
"pmail/config"
|
||||||
"pmail/dto/parsemail"
|
"pmail/dto/parsemail"
|
||||||
"pmail/hooks/framework"
|
"pmail/hooks/framework"
|
||||||
|
"pmail/models"
|
||||||
"pmail/utils/context"
|
"pmail/utils/context"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -31,23 +32,20 @@ type WeChatPushHook struct {
|
|||||||
mainConfig *config.Config
|
mainConfig *config.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WeChatPushHook) ReceiveSaveAfter(ctx *context.Context, email *parsemail.Email) {
|
func (w *WeChatPushHook) ReceiveSaveAfter(ctx *context.Context, email *parsemail.Email, ue []*models.UserEmail) {
|
||||||
if w.appId == "" || w.secret == "" || w.pushUser == "" {
|
if w.appId == "" || w.secret == "" || w.pushUser == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 被标记为已读,或者是已删除,或是垃圾邮件 就不处理了
|
for _, u := range ue {
|
||||||
if email.IsRead == 1 || email.Status == 3 || email.MessageId <= 0 {
|
// 管理员(Uid=1)收到邮件且非已读、非已删除 触发通知
|
||||||
return
|
if u.UserID == 1 && u.IsRead == 0 && u.Status != 3 && email.MessageId > 0 {
|
||||||
|
content := "<<" + email.Subject + ">> " + string(email.Text)
|
||||||
|
|
||||||
|
w.sendUserMsg(nil, w.pushUser, content)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
content := string(email.Text)
|
|
||||||
|
|
||||||
if content == "" {
|
|
||||||
content = email.Subject
|
|
||||||
}
|
|
||||||
|
|
||||||
w.sendUserMsg(nil, w.pushUser, content)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WeChatPushHook) SendBefore(ctx *context.Context, email *parsemail.Email) {
|
func (w *WeChatPushHook) SendBefore(ctx *context.Context, email *parsemail.Email) {
|
||||||
@ -132,7 +130,7 @@ func NewWechatPushHook() *WeChatPushHook {
|
|||||||
var cfgData []byte
|
var cfgData []byte
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
cfgData, err = os.ReadFile("../../config/config.json")
|
cfgData, err = os.ReadFile("./config/config.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,40 @@ func HttpStop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func router(mux *http.ServeMux) {
|
||||||
|
fe, err := fs.Sub(local, "dist")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
mux.Handle("/", http.FileServer(http.FS(fe)))
|
||||||
|
// 挑战请求类似这样 /.well-known/acme-challenge/QPyMAyaWw9s5JvV1oruyqWHG7OqkHMJEHPoUz2046KM
|
||||||
|
mux.HandleFunc("/.well-known/", controllers.AcmeChallenge)
|
||||||
|
mux.HandleFunc("/api/ping", controllers.Ping)
|
||||||
|
mux.HandleFunc("/api/login", contextIterceptor(controllers.Login))
|
||||||
|
mux.HandleFunc("/api/logout", contextIterceptor(controllers.Logout))
|
||||||
|
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/del", contextIterceptor(email.EmailDelete))
|
||||||
|
mux.HandleFunc("/api/email/read", contextIterceptor(email.MarkRead))
|
||||||
|
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/settings/modify_password", contextIterceptor(controllers.ModifyPassword))
|
||||||
|
mux.HandleFunc("/api/rule/get", contextIterceptor(controllers.GetRule))
|
||||||
|
mux.HandleFunc("/api/rule/add", contextIterceptor(controllers.UpsertRule))
|
||||||
|
mux.HandleFunc("/api/rule/update", contextIterceptor(controllers.UpsertRule))
|
||||||
|
mux.HandleFunc("/api/rule/del", contextIterceptor(controllers.DelRule))
|
||||||
|
mux.HandleFunc("/attachments/", contextIterceptor(controllers.GetAttachments))
|
||||||
|
mux.HandleFunc("/attachments/download/", contextIterceptor(controllers.Download))
|
||||||
|
mux.HandleFunc("/api/user/create", contextIterceptor(controllers.CreateUser))
|
||||||
|
mux.HandleFunc("/api/user/edit", contextIterceptor(controllers.EditUser))
|
||||||
|
mux.HandleFunc("/api/user/info", contextIterceptor(controllers.Info))
|
||||||
|
mux.HandleFunc("/api/user/list", contextIterceptor(controllers.UserList))
|
||||||
|
}
|
||||||
|
|
||||||
func HttpStart() {
|
func HttpStart() {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
@ -31,6 +65,7 @@ func HttpStart() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if config.Instance.HttpsEnabled != 2 {
|
if config.Instance.HttpsEnabled != 2 {
|
||||||
|
mux.HandleFunc("/api/ping", controllers.Ping)
|
||||||
mux.HandleFunc("/", controllers.Interceptor)
|
mux.HandleFunc("/", controllers.Interceptor)
|
||||||
httpServer = &http.Server{
|
httpServer = &http.Server{
|
||||||
Addr: fmt.Sprintf(":%d", HttpPort),
|
Addr: fmt.Sprintf(":%d", HttpPort),
|
||||||
@ -39,32 +74,9 @@ func HttpStart() {
|
|||||||
WriteTimeout: time.Second * 90,
|
WriteTimeout: time.Second * 90,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fe, err := fs.Sub(local, "dist")
|
|
||||||
if err != nil {
|
router(mux)
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
mux.Handle("/", http.FileServer(http.FS(fe)))
|
|
||||||
// 挑战请求类似这样 /.well-known/acme-challenge/QPyMAyaWw9s5JvV1oruyqWHG7OqkHMJEHPoUz2046KM
|
|
||||||
mux.HandleFunc("/.well-known/", controllers.AcmeChallenge)
|
|
||||||
mux.HandleFunc("/api/ping", contextIterceptor(controllers.Ping))
|
|
||||||
mux.HandleFunc("/api/login", contextIterceptor(controllers.Login))
|
|
||||||
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/del", contextIterceptor(email.EmailDelete))
|
|
||||||
mux.HandleFunc("/api/email/read", contextIterceptor(email.MarkRead))
|
|
||||||
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/settings/modify_password", contextIterceptor(controllers.ModifyPassword))
|
|
||||||
mux.HandleFunc("/api/rule/get", contextIterceptor(controllers.GetRule))
|
|
||||||
mux.HandleFunc("/api/rule/add", contextIterceptor(controllers.UpsertRule))
|
|
||||||
mux.HandleFunc("/api/rule/update", contextIterceptor(controllers.UpsertRule))
|
|
||||||
mux.HandleFunc("/api/rule/del", contextIterceptor(controllers.DelRule))
|
|
||||||
mux.HandleFunc("/attachments/", contextIterceptor(controllers.GetAttachments))
|
|
||||||
mux.HandleFunc("/attachments/download/", contextIterceptor(controllers.Download))
|
|
||||||
log.Infof("HttpServer Start On Port :%d", HttpPort)
|
log.Infof("HttpServer Start On Port :%d", HttpPort)
|
||||||
httpServer = &http.Server{
|
httpServer = &http.Server{
|
||||||
Addr: fmt.Sprintf(":%d", HttpPort),
|
Addr: fmt.Sprintf(":%d", HttpPort),
|
||||||
|
@ -4,12 +4,10 @@ import (
|
|||||||
"embed"
|
"embed"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
|
||||||
olog "log"
|
olog "log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"pmail/config"
|
"pmail/config"
|
||||||
"pmail/controllers"
|
"pmail/controllers"
|
||||||
"pmail/controllers/email"
|
|
||||||
"pmail/dto/response"
|
"pmail/dto/response"
|
||||||
"pmail/i18n"
|
"pmail/i18n"
|
||||||
"pmail/models"
|
"pmail/models"
|
||||||
@ -38,32 +36,7 @@ func HttpsStart() {
|
|||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
fe, err := fs.Sub(local, "dist")
|
router(mux)
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
mux.Handle("/", http.FileServer(http.FS(fe)))
|
|
||||||
// 挑战请求类似这样 /.well-known/acme-challenge/QPyMAyaWw9s5JvV1oruyqWHG7OqkHMJEHPoUz2046KM
|
|
||||||
mux.HandleFunc("/.well-known/", controllers.AcmeChallenge)
|
|
||||||
mux.HandleFunc("/api/ping", contextIterceptor(controllers.Ping))
|
|
||||||
mux.HandleFunc("/api/login", contextIterceptor(controllers.Login))
|
|
||||||
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/read", contextIterceptor(email.MarkRead))
|
|
||||||
mux.HandleFunc("/api/email/del", contextIterceptor(email.EmailDelete))
|
|
||||||
mux.HandleFunc("/api/email/detail", contextIterceptor(email.EmailDetail))
|
|
||||||
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/rule/get", contextIterceptor(controllers.GetRule))
|
|
||||||
mux.HandleFunc("/api/rule/add", contextIterceptor(controllers.UpsertRule))
|
|
||||||
mux.HandleFunc("/api/rule/update", contextIterceptor(controllers.UpsertRule))
|
|
||||||
mux.HandleFunc("/api/rule/del", contextIterceptor(controllers.DelRule))
|
|
||||||
mux.HandleFunc("/attachments/", contextIterceptor(controllers.GetAttachments))
|
|
||||||
mux.HandleFunc("/attachments/download/", contextIterceptor(controllers.Download))
|
|
||||||
|
|
||||||
// go http server会打一堆没用的日志,写一个空的日志处理器,屏蔽掉日志输出
|
// go http server会打一堆没用的日志,写一个空的日志处理器,屏蔽掉日志输出
|
||||||
nullLog := olog.New(&nullWrite{}, "", olog.Ldate)
|
nullLog := olog.New(&nullWrite{}, "", olog.Ldate)
|
||||||
@ -82,7 +55,7 @@ func HttpsStart() {
|
|||||||
WriteTimeout: time.Second * 90,
|
WriteTimeout: time.Second * 90,
|
||||||
ErrorLog: nullLog,
|
ErrorLog: nullLog,
|
||||||
}
|
}
|
||||||
err = httpsServer.ListenAndServeTLS("config/ssl/public.crt", "config/ssl/private.key")
|
err := httpsServer.ListenAndServeTLS("config/ssl/public.crt", "config/ssl/private.key")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -121,6 +94,7 @@ func contextIterceptor(h controllers.HandlerFunc) http.HandlerFunc {
|
|||||||
ctx.UserID = userInfo.ID
|
ctx.UserID = userInfo.ID
|
||||||
ctx.UserName = userInfo.Name
|
ctx.UserName = userInfo.Name
|
||||||
ctx.UserAccount = userInfo.Account
|
ctx.UserAccount = userInfo.Account
|
||||||
|
ctx.IsAdmin = userInfo.IsAdmin == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.UserID == 0 {
|
if ctx.UserID == 0 {
|
||||||
|
@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
"io"
|
"io"
|
||||||
@ -14,6 +15,7 @@ import (
|
|||||||
"pmail/models"
|
"pmail/models"
|
||||||
"pmail/services/setup"
|
"pmail/services/setup"
|
||||||
"pmail/signal"
|
"pmail/signal"
|
||||||
|
"pmail/utils/array"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@ -32,7 +34,7 @@ func TestMain(m *testing.M) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
httpClient = &http.Client{Jar: cookeieJar, Timeout: 5 * time.Second}
|
httpClient = &http.Client{Jar: cookeieJar, Timeout: 5 * time.Minute}
|
||||||
os.Remove("config/config.json")
|
os.Remove("config/config.json")
|
||||||
os.Remove("config/pmail_temp.db")
|
os.Remove("config/pmail_temp.db")
|
||||||
go func() {
|
go func() {
|
||||||
@ -63,12 +65,201 @@ func TestMaster(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
t.Run("testSSLSet", testSSLSet)
|
t.Run("testSSLSet", testSSLSet)
|
||||||
time.Sleep(3 * time.Second)
|
time.Sleep(8 * time.Second)
|
||||||
t.Run("testLogin", testLogin)
|
t.Run("testLogin", testLogin) // 登录管理员账号
|
||||||
|
t.Run("testCreateUser", testCreateUser) // 创建3个测试用户
|
||||||
|
t.Run("testEditUser", testEditUser) // 编辑user2,封禁user3
|
||||||
t.Run("testSendEmail", testSendEmail)
|
t.Run("testSendEmail", testSendEmail)
|
||||||
time.Sleep(3 * time.Second)
|
time.Sleep(8 * time.Second)
|
||||||
t.Run("testEmailList", testEmailList)
|
t.Run("testEmailList", testEmailList)
|
||||||
t.Run("testDelEmail", testDelEmail)
|
t.Run("testDelEmail", testDelEmail)
|
||||||
|
|
||||||
|
t.Run("testSendEmail2User1", testSendEmail2User1)
|
||||||
|
t.Run("testSendEmail2User2", testSendEmail2User2)
|
||||||
|
t.Run("testSendEmail2User3", testSendEmail2User3)
|
||||||
|
time.Sleep(8 * time.Second)
|
||||||
|
|
||||||
|
t.Run("testLoginUser3", testLoginUser3) // 测试登录被封禁账号
|
||||||
|
|
||||||
|
t.Run("testLoginUser2", testLoginUser2) // 测试登录普通账号
|
||||||
|
|
||||||
|
t.Run("testUser2EmailList", testUser2EmailList)
|
||||||
|
|
||||||
|
// 创建group
|
||||||
|
t.Run("testCreateGroup", testCreateGroup)
|
||||||
|
|
||||||
|
// 创建rule
|
||||||
|
t.Run("testCreateRule", testCreateRule)
|
||||||
|
|
||||||
|
// 再次发邮件
|
||||||
|
t.Run("testMoverEmailSend", testSendEmail2User2ForMove)
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
|
||||||
|
// 检查规则执行
|
||||||
|
t.Run("testCheckRule", testCheckRule)
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCheckRule(t *testing.T) {
|
||||||
|
var ue models.UserEmail
|
||||||
|
db.Instance.Where("group_id!=0").Get(&ue)
|
||||||
|
if ue.GroupId == 0 {
|
||||||
|
t.Error("邮件规则执行失败!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCreateRule(t *testing.T) {
|
||||||
|
ret, err := httpClient.Post(TestHost+"/api/rule/add", "application/json", strings.NewReader(`{
|
||||||
|
"name":"Move Group",
|
||||||
|
"rules":[{"field":"Subject","type":"contains","rule":"Move"}],
|
||||||
|
"action":4,
|
||||||
|
"params":"1",
|
||||||
|
"sort":1
|
||||||
|
}`))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
data, err := readResponse(ret.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if data.ErrorNo != 0 {
|
||||||
|
t.Error("CreateRule Api Error!", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err = httpClient.Post(TestHost+"/api/rule/get", "application/json", strings.NewReader(`{}`))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
data, err = readResponse(ret.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if data.ErrorNo != 0 {
|
||||||
|
t.Error("CreateRule Api Error!", data)
|
||||||
|
}
|
||||||
|
dt := data.Data.([]any)
|
||||||
|
if len(dt) != 1 {
|
||||||
|
t.Error("Rule List Is Empty!")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCreateGroup(t *testing.T) {
|
||||||
|
ret, err := httpClient.Post(TestHost+"/api/group/add", "application/json", strings.NewReader(`{
|
||||||
|
"name":"TestGroup"
|
||||||
|
}`))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
data, err := readResponse(ret.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if data.ErrorNo != 0 {
|
||||||
|
t.Error("CreateGroup Api Error!", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err = httpClient.Post(TestHost+"/api/group/list", "application/json", strings.NewReader(`{}`))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
data, err = readResponse(ret.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if data.ErrorNo != 0 {
|
||||||
|
t.Error("CreateGroup Api Error!", data)
|
||||||
|
}
|
||||||
|
dt := data.Data.([]any)
|
||||||
|
if len(dt) != 1 {
|
||||||
|
t.Error("Group List Is Empty!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testEditUser(t *testing.T) {
|
||||||
|
ret, err := httpClient.Post(TestHost+"/api/user/edit", "application/json", strings.NewReader(`{
|
||||||
|
"account":"user2",
|
||||||
|
"username":"user2New",
|
||||||
|
"password":"user2New"
|
||||||
|
}`))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
data, err := readResponse(ret.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if data.ErrorNo != 0 {
|
||||||
|
t.Error("Edit User Api Error!", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err = httpClient.Post(TestHost+"/api/user/edit", "application/json", strings.NewReader(`{
|
||||||
|
"account":"user3",
|
||||||
|
"disabled": 1
|
||||||
|
}`))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
data, err = readResponse(ret.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if data.ErrorNo != 0 {
|
||||||
|
t.Error("Edit User Api Error!", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCreateUser(t *testing.T) {
|
||||||
|
ret, err := httpClient.Post(TestHost+"/api/user/create", "application/json", strings.NewReader(`{
|
||||||
|
"account":"user1",
|
||||||
|
"username":"user1",
|
||||||
|
"password":"user1"
|
||||||
|
}`))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
data, err := readResponse(ret.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if data.ErrorNo != 0 {
|
||||||
|
t.Error("Create User Api Error!")
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err = httpClient.Post(TestHost+"/api/user/create", "application/json", strings.NewReader(`{
|
||||||
|
"account":"user2",
|
||||||
|
"username":"user2",
|
||||||
|
"password":"user2"
|
||||||
|
}`))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
data, err = readResponse(ret.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if data.ErrorNo != 0 {
|
||||||
|
t.Error("Create User Api Error!")
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err = httpClient.Post(TestHost+"/api/user/create", "application/json", strings.NewReader(`{
|
||||||
|
"account":"user3",
|
||||||
|
"username":"user3",
|
||||||
|
"password":"user3"
|
||||||
|
}`))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
data, err = readResponse(ret.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if data.ErrorNo != 0 {
|
||||||
|
t.Error("Create User Api Error!")
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testPort(t *testing.T) {
|
func testPort(t *testing.T) {
|
||||||
@ -92,10 +283,21 @@ func testDataBaseSet(t *testing.T) {
|
|||||||
if data.ErrorNo != 0 {
|
if data.ErrorNo != 0 {
|
||||||
t.Error("Get Database Config Api Error!")
|
t.Error("Get Database Config Api Error!")
|
||||||
}
|
}
|
||||||
// 设置配置
|
|
||||||
ret, err = http.Post(TestHost+"/api/setup", "application/json", strings.NewReader(`
|
argList := flag.Args()
|
||||||
|
|
||||||
|
configData := `
|
||||||
{"action":"set","step":"database","db_type":"sqlite","db_dsn":"./config/pmail_temp.db"}
|
{"action":"set","step":"database","db_type":"sqlite","db_dsn":"./config/pmail_temp.db"}
|
||||||
`))
|
`
|
||||||
|
|
||||||
|
if array.InArray("mysql", argList) {
|
||||||
|
configData = `
|
||||||
|
{"action":"set","step":"database","db_type":"mysql","db_dsn":"root:githubTest@tcp(mysql:3306)/pmail?parseTime=True"}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置配置
|
||||||
|
ret, err = http.Post(TestHost+"/api/setup", "application/json", strings.NewReader(configData))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@ -120,7 +322,7 @@ func testDataBaseSet(t *testing.T) {
|
|||||||
t.Error("Get Database Config Api Error!")
|
t.Error("Get Database Config Api Error!")
|
||||||
}
|
}
|
||||||
dt := data.Data.(map[string]interface{})
|
dt := data.Data.(map[string]interface{})
|
||||||
if cast.ToString(dt["db_dsn"]) != "./config/pmail_temp.db" {
|
if cast.ToString(dt["db_dsn"]) == "" {
|
||||||
t.Error("Check Database Config Api Error!")
|
t.Error("Check Database Config Api Error!")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,6 +356,7 @@ func testPwdSet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
if data.ErrorNo != 0 {
|
if data.ErrorNo != 0 {
|
||||||
t.Error("Set Password Config Api Error!")
|
t.Error("Set Password Config Api Error!")
|
||||||
|
t.Error(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取配置
|
// 获取配置
|
||||||
@ -241,6 +444,7 @@ func testDNSSet(t *testing.T) {
|
|||||||
if data.ErrorNo != 0 {
|
if data.ErrorNo != 0 {
|
||||||
t.Error("Get domain Config Api Error!")
|
t.Error("Get domain Config Api Error!")
|
||||||
}
|
}
|
||||||
|
t.Log("DNS Set Success!")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSSLSet(t *testing.T) {
|
func testSSLSet(t *testing.T) {
|
||||||
@ -284,8 +488,39 @@ func testLogin(t *testing.T) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
if data.ErrorNo != 0 {
|
if data.ErrorNo != 0 {
|
||||||
t.Error("Get domain Config Api Error!")
|
t.Error("Login Api Error!")
|
||||||
}
|
}
|
||||||
|
t.Logf("testLogin Success! Response: %+v", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLoginUser2(t *testing.T) {
|
||||||
|
ret, err := httpClient.Post(TestHost+"/api/login", "application/json", strings.NewReader("{\"account\":\"user2\",\"password\":\"user2New\"}"))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
data, err := readResponse(ret.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if data.ErrorNo != 0 {
|
||||||
|
t.Error("Login User2 Api Error!", data)
|
||||||
|
}
|
||||||
|
t.Logf("testLoginUser2 Success! Response: %+v", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLoginUser3(t *testing.T) {
|
||||||
|
ret, err := httpClient.Post(TestHost+"/api/login", "application/json", strings.NewReader("{\"account\":\"user3\",\"password\":\"user3\"}"))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
data, err := readResponse(ret.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if data.ErrorNo != 100 {
|
||||||
|
t.Error("Login User3 Api Error!", data)
|
||||||
|
}
|
||||||
|
t.Logf("testLoginUser3 Success! Response: %+v", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSendEmail(t *testing.T) {
|
func testSendEmail(t *testing.T) {
|
||||||
@ -320,8 +555,154 @@ func testSendEmail(t *testing.T) {
|
|||||||
if data.ErrorNo != 0 {
|
if data.ErrorNo != 0 {
|
||||||
t.Error("Send Email Api Error!")
|
t.Error("Send Email Api Error!")
|
||||||
}
|
}
|
||||||
|
t.Logf("testSendEmail Success! Response: %+v", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testSendEmail2User2ForMove(t *testing.T) {
|
||||||
|
ret, err := httpClient.Post(TestHost+"/api/email/send", "application/json", strings.NewReader(`
|
||||||
|
{
|
||||||
|
"from": {
|
||||||
|
"name": "user2",
|
||||||
|
"email": "user2@test.domain"
|
||||||
|
},
|
||||||
|
"to": [
|
||||||
|
{
|
||||||
|
"name": "y",
|
||||||
|
"email": "user2@test.domain"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cc": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"subject": "MovePlease",
|
||||||
|
"text": "NeedMove",
|
||||||
|
"html": "<div>text</div>"
|
||||||
|
}
|
||||||
|
|
||||||
|
`))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
data, err := readResponse(ret.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if data.ErrorNo != 0 {
|
||||||
|
t.Error("Send Email Api Error!")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("testSendEmail2User2ForMove Success! Response: %+v", data)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSendEmail2User1(t *testing.T) {
|
||||||
|
ret, err := httpClient.Post(TestHost+"/api/email/send", "application/json", strings.NewReader(`
|
||||||
|
{
|
||||||
|
"from": {
|
||||||
|
"name": "i",
|
||||||
|
"email": "i@test.domain"
|
||||||
|
},
|
||||||
|
"to": [
|
||||||
|
{
|
||||||
|
"name": "y",
|
||||||
|
"email": "user1@test.domain"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cc": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"subject": "HelloUser1",
|
||||||
|
"text": "text",
|
||||||
|
"html": "<div>text</div>"
|
||||||
|
}
|
||||||
|
|
||||||
|
`))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
data, err := readResponse(ret.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if data.ErrorNo != 0 {
|
||||||
|
t.Error("Send Email Api Error!")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("testSendEmail2User1 Success! Response: %+v", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSendEmail2User2(t *testing.T) {
|
||||||
|
ret, err := httpClient.Post(TestHost+"/api/email/send", "application/json", strings.NewReader(`
|
||||||
|
{
|
||||||
|
"from": {
|
||||||
|
"name": "i",
|
||||||
|
"email": "i@test.domain"
|
||||||
|
},
|
||||||
|
"to": [
|
||||||
|
{
|
||||||
|
"name": "y",
|
||||||
|
"email": "user2@test.domain"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cc": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"subject": "HelloUser2",
|
||||||
|
"text": "text",
|
||||||
|
"html": "<div>text</div>"
|
||||||
|
}
|
||||||
|
|
||||||
|
`))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
data, err := readResponse(ret.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if data.ErrorNo != 0 {
|
||||||
|
t.Error("Send Email Api Error!")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("testSendEmail2User2 Success! Response: %+v", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSendEmail2User3(t *testing.T) {
|
||||||
|
ret, err := httpClient.Post(TestHost+"/api/email/send", "application/json", strings.NewReader(`
|
||||||
|
{
|
||||||
|
"from": {
|
||||||
|
"name": "i",
|
||||||
|
"email": "i@test.domain"
|
||||||
|
},
|
||||||
|
"to": [
|
||||||
|
{
|
||||||
|
"name": "y",
|
||||||
|
"email": "user3@test.domain"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cc": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"subject": "HelloUser3",
|
||||||
|
"text": "text",
|
||||||
|
"html": "<div>text</div>"
|
||||||
|
}
|
||||||
|
|
||||||
|
`))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
data, err := readResponse(ret.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if data.ErrorNo != 0 {
|
||||||
|
t.Error("Send Email Api Error!")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("testSendEmail2User3 Success! Response: %+v", data)
|
||||||
|
|
||||||
|
}
|
||||||
func testEmailList(t *testing.T) {
|
func testEmailList(t *testing.T) {
|
||||||
ret, err := httpClient.Post(TestHost+"/api/email/list", "application/json", strings.NewReader(`{}`))
|
ret, err := httpClient.Post(TestHost+"/api/email/list", "application/json", strings.NewReader(`{}`))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -338,6 +719,37 @@ func testEmailList(t *testing.T) {
|
|||||||
if len(dt["list"].([]interface{})) == 0 {
|
if len(dt["list"].([]interface{})) == 0 {
|
||||||
t.Error("Email List Is Empty!")
|
t.Error("Email List Is Empty!")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lst := dt["list"].([]interface{})
|
||||||
|
item := lst[0].(map[string]interface{})
|
||||||
|
id := cast.ToInt(item["id"])
|
||||||
|
if id == 0 {
|
||||||
|
t.Error("Email List Data Error!")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("testEmailList Success! Response: %+v", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testUser2EmailList(t *testing.T) {
|
||||||
|
ret, err := httpClient.Post(TestHost+"/api/email/list", "application/json", strings.NewReader(`{}`))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
data, err := readResponse(ret.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if data.ErrorNo != 0 {
|
||||||
|
t.Error("Get Email List Api Error!")
|
||||||
|
}
|
||||||
|
dt := data.Data.(map[string]interface{})
|
||||||
|
|
||||||
|
if dt["list"] == nil || len(dt["list"].([]interface{})) != 1 {
|
||||||
|
t.Error("Email List Is Empty!")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("testUser2EmailList Success! Response: %+v", data)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testDelEmail(t *testing.T) {
|
func testDelEmail(t *testing.T) {
|
||||||
@ -373,12 +785,13 @@ func testDelEmail(t *testing.T) {
|
|||||||
if data.ErrorNo != 0 {
|
if data.ErrorNo != 0 {
|
||||||
t.Error("Email Delete Api Error!")
|
t.Error("Email Delete Api Error!")
|
||||||
}
|
}
|
||||||
var mail models.Email
|
var mail models.UserEmail
|
||||||
db.Instance.Where("id = ?", id).Get(&mail)
|
db.Instance.Where("email_id = ?", id).Get(&mail)
|
||||||
if mail.Status != 3 {
|
if mail.Status != 3 {
|
||||||
t.Error("Email Delete Api Error!")
|
t.Error("Email Delete Api Error!")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.Logf("testDelEmail Success! Response: %+v", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// portCheck 检查端口是占用
|
// portCheck 检查端口是占用
|
||||||
|
@ -4,7 +4,9 @@ type User struct {
|
|||||||
ID int `xorm:"id unsigned int not null pk autoincr"`
|
ID int `xorm:"id unsigned int not null pk autoincr"`
|
||||||
Account string `xorm:"varchar(20) notnull unique comment('账号登陆名')"`
|
Account string `xorm:"varchar(20) notnull unique comment('账号登陆名')"`
|
||||||
Name string `xorm:"varchar(10) notnull comment('用户名')"`
|
Name string `xorm:"varchar(10) notnull comment('用户名')"`
|
||||||
Password string `xorm:"char(32) notnull comment('登陆密码,两次md5加盐,md5(md5(password+pmail) +pmail2023)')"`
|
Password string `xorm:"char(32) notnull comment('登陆密码,两次md5加盐,md5(md5(password+pmail) +pmail2023)')" json:"-"`
|
||||||
|
Disabled int `xorm:"disabled unsigned int not null default(0) comment('0启用,1禁用')"`
|
||||||
|
IsAdmin int `xorm:"is_admin unsigned int not null default(0) comment('0不是管理员,1是管理员')"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p User) TableName() string {
|
func (p User) TableName() string {
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
type UserAuth struct {
|
|
||||||
ID int `xorm:"id int unsigned not null pk autoincr"`
|
|
||||||
UserID int `xorm:"user_id int not null unique('uid_account') index comment('用户id')"`
|
|
||||||
EmailAccount string `xorm:"email_account not null unique('uid_account') index comment('收信人前缀')"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p UserAuth) TableName() string {
|
|
||||||
return "user_auth"
|
|
||||||
}
|
|
@ -10,7 +10,6 @@ import (
|
|||||||
type Email struct {
|
type Email struct {
|
||||||
Id int `xorm:"id pk unsigned int autoincr notnull" json:"id"`
|
Id int `xorm:"id pk unsigned int autoincr notnull" json:"id"`
|
||||||
Type int8 `xorm:"type tinyint(4) notnull default(0) comment('邮件类型,0:收到的邮件,1:发送的邮件')" json:"type"`
|
Type int8 `xorm:"type tinyint(4) notnull default(0) comment('邮件类型,0:收到的邮件,1:发送的邮件')" json:"type"`
|
||||||
GroupId int `xorm:"group_id int notnull default(0) comment('分组id')'" json:"group_id"`
|
|
||||||
Subject string `xorm:"subject varchar(1000) notnull default('') comment('邮件标题')" json:"subject"`
|
Subject string `xorm:"subject varchar(1000) notnull default('') comment('邮件标题')" json:"subject"`
|
||||||
ReplyTo string `xorm:"reply_to text comment('回复人')" json:"reply_to"`
|
ReplyTo string `xorm:"reply_to text comment('回复人')" json:"reply_to"`
|
||||||
FromName string `xorm:"from_name varchar(50) notnull default('') comment('发件人名称')" json:"from_name"`
|
FromName string `xorm:"from_name varchar(50) notnull default('') comment('发件人名称')" json:"from_name"`
|
||||||
@ -24,17 +23,17 @@ type Email struct {
|
|||||||
Attachments string `xorm:"attachments longtext comment('附件')" json:"attachments"`
|
Attachments string `xorm:"attachments longtext comment('附件')" json:"attachments"`
|
||||||
SPFCheck int8 `xorm:"spf_check tinyint(1) comment('spf校验是否通过')" json:"spf_check"`
|
SPFCheck int8 `xorm:"spf_check tinyint(1) comment('spf校验是否通过')" json:"spf_check"`
|
||||||
DKIMCheck int8 `xorm:"dkim_check tinyint(1) comment('dkim校验是否通过')" json:"dkim_check"`
|
DKIMCheck int8 `xorm:"dkim_check tinyint(1) comment('dkim校验是否通过')" json:"dkim_check"`
|
||||||
Status int8 `xorm:"status tinyint(4) notnull default(0) comment('0未发送,1已发送,2发送失败,3删除')" json:"status"` // 0未发送,1已发送,2发送失败,3删除
|
Status int8 `xorm:"status tinyint(4) notnull default(0) comment('0未发送,1已发送,2发送失败')" json:"status"` // 0未发送,1已发送,2发送失败
|
||||||
CronSendTime time.Time `xorm:"cron_send_time comment('定时发送时间')" json:"cron_send_time"`
|
CronSendTime time.Time `xorm:"cron_send_time comment('定时发送时间')" json:"cron_send_time"`
|
||||||
UpdateTime time.Time `xorm:"update_time updated comment('更新时间')" json:"update_time"`
|
UpdateTime time.Time `xorm:"update_time updated comment('更新时间')" json:"update_time"`
|
||||||
SendUserID int `xorm:"send_user_id unsigned int notnull default(0) comment('发件人用户id')" json:"send_user_id"`
|
SendUserID int `xorm:"send_user_id unsigned int notnull default(0) comment('发件人用户id')" json:"send_user_id"`
|
||||||
IsRead int8 `xorm:"is_read tinyint(1) comment('是否已读')" json:"is_read"`
|
Size int `xorm:"size unsigned int notnull default(1000) comment('邮件大小')" json:"size"`
|
||||||
Error sql.NullString `xorm:"error text comment('投递错误信息')" json:"error"`
|
Error sql.NullString `xorm:"error text comment('投递错误信息')" json:"error"`
|
||||||
SendDate time.Time `xorm:"send_date comment('投递时间')" json:"send_date"`
|
SendDate time.Time `xorm:"send_date comment('投递时间')" json:"send_date"`
|
||||||
CreateTime time.Time `xorm:"create_time created" json:"create_time"`
|
CreateTime time.Time `xorm:"create_time created" json:"create_time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d Email) TableName() string {
|
func (d *Email) TableName() string {
|
||||||
return "email"
|
return "email"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,43 +44,43 @@ type attachments struct {
|
|||||||
//Content []byte
|
//Content []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d Email) GetTos() []*parsemail.User {
|
func (d *Email) GetTos() []*parsemail.User {
|
||||||
var ret []*parsemail.User
|
var ret []*parsemail.User
|
||||||
json.Unmarshal([]byte(d.To), &ret)
|
json.Unmarshal([]byte(d.To), &ret)
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d Email) GetReplyTo() []*parsemail.User {
|
func (d *Email) GetReplyTo() []*parsemail.User {
|
||||||
var ret []*parsemail.User
|
var ret []*parsemail.User
|
||||||
json.Unmarshal([]byte(d.ReplyTo), &ret)
|
json.Unmarshal([]byte(d.ReplyTo), &ret)
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d Email) GetSender() *parsemail.User {
|
func (d *Email) GetSender() *parsemail.User {
|
||||||
var ret *parsemail.User
|
var ret *parsemail.User
|
||||||
json.Unmarshal([]byte(d.Sender), &ret)
|
json.Unmarshal([]byte(d.Sender), &ret)
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d Email) GetBcc() []*parsemail.User {
|
func (d *Email) GetBcc() []*parsemail.User {
|
||||||
var ret []*parsemail.User
|
var ret []*parsemail.User
|
||||||
json.Unmarshal([]byte(d.Bcc), &ret)
|
json.Unmarshal([]byte(d.Bcc), &ret)
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d Email) GetCc() []*parsemail.User {
|
func (d *Email) GetCc() []*parsemail.User {
|
||||||
var ret []*parsemail.User
|
var ret []*parsemail.User
|
||||||
json.Unmarshal([]byte(d.Cc), &ret)
|
json.Unmarshal([]byte(d.Cc), &ret)
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d Email) GetAttachments() []*parsemail.Attachment {
|
func (d *Email) GetAttachments() []*parsemail.Attachment {
|
||||||
var ret []*parsemail.Attachment
|
var ret []*parsemail.Attachment
|
||||||
json.Unmarshal([]byte(d.Attachments), &ret)
|
json.Unmarshal([]byte(d.Attachments), &ret)
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d Email) MarshalJSON() ([]byte, error) {
|
func (d *Email) MarshalJSON() ([]byte, error) {
|
||||||
type Alias Email
|
type Alias Email
|
||||||
|
|
||||||
var allAtt = []attachments{}
|
var allAtt = []attachments{}
|
||||||
@ -108,7 +107,7 @@ func (d Email) MarshalJSON() ([]byte, error) {
|
|||||||
Error string `json:"error"`
|
Error string `json:"error"`
|
||||||
Attachments []attachments `json:"attachments"`
|
Attachments []attachments `json:"attachments"`
|
||||||
}{
|
}{
|
||||||
Alias: (Alias)(d),
|
Alias: (Alias)(*d),
|
||||||
CronSendTime: d.CronSendTime.Format("2006-01-02 15:04:05"),
|
CronSendTime: d.CronSendTime.Format("2006-01-02 15:04:05"),
|
||||||
UpdateTime: d.UpdateTime.Format("2006-01-02 15:04:05"),
|
UpdateTime: d.UpdateTime.Format("2006-01-02 15:04:05"),
|
||||||
CreateTime: d.CreateTime.Format("2006-01-02 15:04:05"),
|
CreateTime: d.CreateTime.Format("2006-01-02 15:04:05"),
|
||||||
@ -120,7 +119,7 @@ func (d Email) MarshalJSON() ([]byte, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d Email) ToTransObj() *parsemail.Email {
|
func (d *Email) ToTransObj() *parsemail.Email {
|
||||||
|
|
||||||
return &parsemail.Email{
|
return &parsemail.Email{
|
||||||
From: &parsemail.User{
|
From: &parsemail.User{
|
||||||
|
14
server/models/user_email.go
Normal file
14
server/models/user_email.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
type UserEmail struct {
|
||||||
|
ID int `xorm:"id int unsigned not null pk autoincr"`
|
||||||
|
UserID int `xorm:"user_id int not null unique('uid_eid') index comment('用户id')"`
|
||||||
|
EmailID int `xorm:"email_id not null unique('uid_eid') index comment('信件id')"`
|
||||||
|
IsRead int8 `xorm:"is_read tinyint(1) comment('是否已读')" json:"is_read"`
|
||||||
|
GroupId int `xorm:"group_id int notnull default(0) comment('分组id')'" json:"group_id"`
|
||||||
|
Status int8 `xorm:"status tinyint(4) notnull default(0) comment('0未发送,1已发送,2发送失败,3删除')" json:"status"` // 0未发送,1已发送,2发送失败 3删除
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p UserEmail) TableName() string {
|
||||||
|
return "user_email"
|
||||||
|
}
|
10
server/models/version.go
Normal file
10
server/models/version.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
type Version struct {
|
||||||
|
Id int `xorm:"id int unsigned not null pk autoincr" json:"id"`
|
||||||
|
Info string `xorm:"varchar(255) notnull" json:"info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Version) TableName() string {
|
||||||
|
return "version"
|
||||||
|
}
|
@ -5,9 +5,13 @@ import (
|
|||||||
"github.com/Jinnrry/gopop"
|
"github.com/Jinnrry/gopop"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
|
"pmail/consts"
|
||||||
"pmail/db"
|
"pmail/db"
|
||||||
|
"pmail/dto"
|
||||||
"pmail/models"
|
"pmail/models"
|
||||||
|
"pmail/services/del_email"
|
||||||
"pmail/services/detail"
|
"pmail/services/detail"
|
||||||
|
"pmail/services/list"
|
||||||
"pmail/utils/array"
|
"pmail/utils/array"
|
||||||
"pmail/utils/context"
|
"pmail/utils/context"
|
||||||
"pmail/utils/errors"
|
"pmail/utils/errors"
|
||||||
@ -100,7 +104,7 @@ func (a action) Pass(session *gopop.Session, pwd string) error {
|
|||||||
|
|
||||||
encodePwd := password.Encode(pwd)
|
encodePwd := password.Encode(pwd)
|
||||||
|
|
||||||
_, err := db.Instance.Where("account =? and password =?", session.User, encodePwd).Get(&user)
|
_, err := db.Instance.Where("account =? and password =? and disabled = 0", session.User, encodePwd).Get(&user)
|
||||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||||
log.WithContext(session.Ctx.(*context.Context)).Errorf("%+v", err)
|
log.WithContext(session.Ctx.(*context.Context)).Errorf("%+v", err)
|
||||||
}
|
}
|
||||||
@ -136,7 +140,7 @@ func (a action) Apop(session *gopop.Session, username, digest string) error {
|
|||||||
|
|
||||||
var user models.User
|
var user models.User
|
||||||
|
|
||||||
_, err := db.Instance.Where("account =? ", username).Get(&user)
|
_, err := db.Instance.Where("account =? and disabled = 0", username).Get(&user)
|
||||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||||
log.WithContext(session.Ctx.(*context.Context)).Errorf("%+v", err)
|
log.WithContext(session.Ctx.(*context.Context)).Errorf("%+v", err)
|
||||||
}
|
}
|
||||||
@ -156,26 +160,13 @@ func (a action) Apop(session *gopop.Session, username, digest string) error {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type statInfo struct {
|
|
||||||
Num int64 `json:"num"`
|
|
||||||
Size int64 `json:"size"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stat 查询邮件数量
|
// Stat 查询邮件数量
|
||||||
func (a action) Stat(session *gopop.Session) (msgNum, msgSize int64, err error) {
|
func (a action) Stat(session *gopop.Session) (msgNum, msgSize int64, err error) {
|
||||||
log.WithContext(session.Ctx).Debugf("POP3 CMD: STAT")
|
log.WithContext(session.Ctx).Debugf("POP3 CMD: STAT")
|
||||||
|
|
||||||
var si statInfo
|
num, size := list.Stat(session.Ctx.(*context.Context))
|
||||||
_, err = db.Instance.Select("count(1) as `num`, sum(length(text)+length(html)) as `size`").Table("email").Where("type=0 and status=0").Get(&si)
|
log.WithContext(session.Ctx).Debugf("POP3 STAT RETURT : %d,%d", num, size)
|
||||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
return num, size, nil
|
||||||
log.WithContext(session.Ctx.(*context.Context)).Errorf("%+v", err)
|
|
||||||
err = nil
|
|
||||||
log.WithContext(session.Ctx).Debugf("POP3 STAT RETURT :0,0")
|
|
||||||
return 0, 0, nil
|
|
||||||
}
|
|
||||||
log.WithContext(session.Ctx).Debugf("POP3 STAT RETURT : %d,%d", si.Num, si.Size)
|
|
||||||
|
|
||||||
return si.Num, si.Size, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Uidl 查询某封邮件的唯一标志符
|
// Uidl 查询某封邮件的唯一标志符
|
||||||
@ -223,26 +214,31 @@ type listItem struct {
|
|||||||
func (a action) List(session *gopop.Session, msg string) ([]gopop.MailInfo, error) {
|
func (a action) List(session *gopop.Session, msg string) ([]gopop.MailInfo, error) {
|
||||||
log.WithContext(session.Ctx).Debugf("POP3 CMD: LIST ,Args:%s", msg)
|
log.WithContext(session.Ctx).Debugf("POP3 CMD: LIST ,Args:%s", msg)
|
||||||
var res []listItem
|
var res []listItem
|
||||||
var listId int64
|
var listId int
|
||||||
if msg != "" {
|
if msg != "" {
|
||||||
listId = cast.ToInt64(msg)
|
listId = cast.ToInt(msg)
|
||||||
if listId == 0 {
|
if listId == 0 {
|
||||||
return nil, errors.New("params error")
|
return nil, errors.New("params error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var err error
|
|
||||||
var ssql string
|
|
||||||
|
|
||||||
if listId != 0 {
|
if listId != 0 {
|
||||||
err = db.Instance.Select("id, ifnull(LENGTH(TEXT) , 0) + ifnull(LENGTH(html) , 0) AS `size`").Table("email").Where("id=?", listId).Find(&res)
|
info, err := detail.GetEmailDetail(session.Ctx.(*context.Context), listId, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res = append(res, listItem{
|
||||||
|
Id: cast.ToInt64(info.Id),
|
||||||
|
Size: cast.ToInt64(info.Size),
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
err = db.Instance.Select("id, ifnull(LENGTH(TEXT) , 0) + ifnull(LENGTH(html) , 0) AS `size`").Table("email").Where("type=0 and status=0").Find(&res)
|
emailList, _ := list.GetEmailList(session.Ctx.(*context.Context), dto.SearchTag{Type: consts.EmailTypeReceive, Status: -1, GroupId: -1}, "", true, 0, 99999)
|
||||||
}
|
for _, info := range emailList {
|
||||||
|
res = append(res, listItem{
|
||||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
Id: cast.ToInt64(info.Id),
|
||||||
log.WithContext(session.Ctx.(*context.Context)).Errorf("SQL:%s Error: %+v", ssql, err)
|
Size: cast.ToInt64(info.Size),
|
||||||
err = nil
|
})
|
||||||
return []gopop.MailInfo{}, nil
|
}
|
||||||
}
|
}
|
||||||
ret := []gopop.MailInfo{}
|
ret := []gopop.MailInfo{}
|
||||||
for _, re := range res {
|
for _, re := range res {
|
||||||
@ -316,11 +312,7 @@ func (a action) Noop(session *gopop.Session) error {
|
|||||||
func (a action) Quit(session *gopop.Session) error {
|
func (a action) Quit(session *gopop.Session) error {
|
||||||
log.WithContext(session.Ctx).Debugf("POP3 CMD: QUIT ")
|
log.WithContext(session.Ctx).Debugf("POP3 CMD: QUIT ")
|
||||||
if len(session.DeleteIds) > 0 {
|
if len(session.DeleteIds) > 0 {
|
||||||
|
del_email.DelEmailI64(session.Ctx.(*context.Context), session.DeleteIds)
|
||||||
_, err := db.Instance.Exec(db.WithContext(session.Ctx.(*context.Context), "UPDATE email SET status=3 WHERE id in ?"), session.DeleteIds)
|
|
||||||
if err != nil {
|
|
||||||
log.WithContext(session.Ctx.(*context.Context)).Errorf("%+v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -52,6 +52,11 @@ func Start() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Stop() {
|
func Stop() {
|
||||||
instance.Stop()
|
if instance != nil {
|
||||||
instanceTls.Stop()
|
instance.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
if instanceTls != nil {
|
||||||
|
instanceTls.Stop()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ func Init(serverVersion string) {
|
|||||||
// 启动前检查一遍证书
|
// 启动前检查一遍证书
|
||||||
ssl.Update(false)
|
ssl.Update(false)
|
||||||
parsemail.Init()
|
parsemail.Init()
|
||||||
err := db.Init()
|
err := db.Init(serverVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -17,26 +17,17 @@ import (
|
|||||||
|
|
||||||
// HasAuth 检查当前用户是否有某个邮件的auth
|
// HasAuth 检查当前用户是否有某个邮件的auth
|
||||||
func HasAuth(ctx *context.Context, email *models.Email) bool {
|
func HasAuth(ctx *context.Context, email *models.Email) bool {
|
||||||
// 获取当前用户的auth
|
if ctx.IsAdmin {
|
||||||
var auth []models.UserAuth
|
return true
|
||||||
err := db.Instance.Where("user_id = ?", ctx.UserID).Find(&auth)
|
}
|
||||||
|
var ue *models.UserEmail
|
||||||
|
err := db.Instance.Where("email_id = ?", email.Id).Find(&ue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithContext(ctx).Errorf("SQL error:%+v", err)
|
log.Errorf("Error while checking user: %v", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasAuth bool
|
return ue != nil
|
||||||
for _, userAuth := range auth {
|
|
||||||
if userAuth.EmailAccount == "*" {
|
|
||||||
hasAuth = true
|
|
||||||
break
|
|
||||||
} else if strings.Contains(email.Bcc, ctx.UserAccount) || strings.Contains(email.Cc, ctx.UserAccount) || strings.Contains(email.To, ctx.UserAccount) {
|
|
||||||
hasAuth = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return hasAuth
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func DkimGen() string {
|
func DkimGen() string {
|
||||||
|
@ -1,35 +1,50 @@
|
|||||||
package del_email
|
package del_email
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"pmail/consts"
|
||||||
"pmail/db"
|
"pmail/db"
|
||||||
"pmail/models"
|
"pmail/models"
|
||||||
"pmail/services/auth"
|
|
||||||
"pmail/utils/context"
|
"pmail/utils/context"
|
||||||
"pmail/utils/errors"
|
|
||||||
"xorm.io/builder"
|
|
||||||
)
|
)
|
||||||
|
import . "xorm.io/builder"
|
||||||
|
|
||||||
func DelEmail(ctx *context.Context, ids []int) error {
|
func DelEmail(ctx *context.Context, ids []int) error {
|
||||||
var emails []*models.Email
|
|
||||||
|
|
||||||
err := db.Instance.Table("email").Where(builder.In("id", ids)).Find(&emails)
|
if len(ids) == 0 {
|
||||||
if err != nil {
|
return nil
|
||||||
return errors.Wrap(err)
|
|
||||||
}
|
|
||||||
for _, email := range emails {
|
|
||||||
// 检查是否有权限
|
|
||||||
hasAuth := auth.HasAuth(ctx, email)
|
|
||||||
if !hasAuth {
|
|
||||||
return errors.New("No Auth!")
|
|
||||||
}
|
|
||||||
email.Status = 3
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = db.Instance.Table("email").Where(builder.In("id", ids)).Cols("status").Update(map[string]interface{}{"status": 3})
|
where, params, err := ToSQL(Eq{"user_id": ctx.UserID}.And(Eq{"email_id": ids}))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err)
|
log.Errorf("del email err: %v", err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
_, err = db.Instance.Table(&models.UserEmail{}).Where(where, params...).Update(map[string]interface{}{"status": consts.EmailStatusDel})
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("del email err: %v", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func DelEmailI64(ctx *context.Context, ids []int64) error {
|
||||||
|
|
||||||
|
if len(ids) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
where, params, err := ToSQL(Eq{"user_id": ctx.UserID}.And(Eq{"email_id": ids}))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("del email err: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.Instance.Table(&models.UserEmail{}).Where(where, params...).Update(map[string]interface{}{"status": consts.EmailStatusDel})
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("del email err: %v", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
@ -7,22 +7,37 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"pmail/db"
|
"pmail/db"
|
||||||
"pmail/dto/parsemail"
|
"pmail/dto/parsemail"
|
||||||
|
"pmail/dto/response"
|
||||||
"pmail/models"
|
"pmail/models"
|
||||||
"pmail/utils/context"
|
"pmail/utils/context"
|
||||||
|
"pmail/utils/errors"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetEmailDetail(ctx *context.Context, id int, markRead bool) (*models.Email, error) {
|
func GetEmailDetail(ctx *context.Context, id int, markRead bool) (*response.EmailResponseData, error) {
|
||||||
// 获取邮件内容
|
// 先查是否是本人的邮件
|
||||||
var email models.Email
|
var ue models.UserEmail
|
||||||
_, err := db.Instance.ID(id).Get(&email)
|
_, err := db.Instance.Where("email_id = ?", id).Get(&ue)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
if ue.ID == 0 && !ctx.IsAdmin {
|
||||||
|
return nil, errors.New("Not authorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取邮件内容
|
||||||
|
var email response.EmailResponseData
|
||||||
|
_, err = db.Instance.ID(id).Get(&email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithContext(ctx).Errorf("SQL error:%+v", err)
|
log.WithContext(ctx).Errorf("SQL error:%+v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if markRead && email.IsRead == 0 {
|
email.IsRead = ue.IsRead
|
||||||
_, err = db.Instance.Exec(db.WithContext(ctx, "update email set is_read =1 where id =?"), email.Id)
|
|
||||||
|
if markRead && ue.IsRead == 0 {
|
||||||
|
ue.IsRead = 1
|
||||||
|
_, err = db.Instance.Update(&ue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithContext(ctx).Errorf("SQL error:%+v", err)
|
log.WithContext(ctx).Errorf("SQL error:%+v", err)
|
||||||
}
|
}
|
||||||
|
@ -1,49 +1,62 @@
|
|||||||
package list
|
package list
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"fmt"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"pmail/db"
|
"pmail/db"
|
||||||
"pmail/dto"
|
"pmail/dto"
|
||||||
"pmail/models"
|
"pmail/dto/response"
|
||||||
"pmail/utils/context"
|
"pmail/utils/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetEmailList(ctx *context.Context, tag string, keyword string, offset, limit int) (emailList []*models.Email, total int64) {
|
func GetEmailList(ctx *context.Context, tagInfo dto.SearchTag, keyword string, pop3List bool, offset, limit int) (emailList []*response.EmailResponseData, total int64) {
|
||||||
|
return getList(ctx, tagInfo, keyword, pop3List, offset, limit)
|
||||||
querySQL, queryParams := genSQL(ctx, tag, keyword)
|
|
||||||
|
|
||||||
total, err := db.Instance.Table("email").Where(querySQL, queryParams...).Desc("id").Limit(limit, offset).FindAndCount(&emailList)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("SQL ERROR: %s ,Error:%s", querySQL, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func genSQL(ctx *context.Context, tag, keyword string) (string, []any) {
|
func getList(ctx *context.Context, tagInfo dto.SearchTag, keyword string, pop3List bool, offset, limit int) (emailList []*response.EmailResponseData, total int64) {
|
||||||
|
querySQL, queryParams := genSQL(ctx, false, tagInfo, keyword, pop3List, offset, limit)
|
||||||
|
|
||||||
sql := "1=1 "
|
err := db.Instance.SQL(querySQL, queryParams...).Find(&emailList)
|
||||||
|
if err != nil {
|
||||||
|
log.WithContext(ctx).Errorf("SQL ERROR: %s ,Error:%s", querySQL, err)
|
||||||
|
}
|
||||||
|
|
||||||
sqlParams := []any{}
|
totalSQL, totalParams := genSQL(ctx, true, tagInfo, keyword, pop3List, offset, limit)
|
||||||
|
|
||||||
var tagInfo dto.SearchTag
|
_, err = db.Instance.SQL(totalSQL, totalParams...).Get(&total)
|
||||||
_ = json.Unmarshal([]byte(tag), &tagInfo)
|
if err != nil {
|
||||||
|
log.WithContext(ctx).Errorf("SQL ERROR: %s ,Error:%s", querySQL, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return emailList, total
|
||||||
|
}
|
||||||
|
|
||||||
|
func genSQL(ctx *context.Context, count bool, tagInfo dto.SearchTag, keyword string, pop3List bool, offset, limit int) (string, []any) {
|
||||||
|
sqlParams := []any{ctx.UserID}
|
||||||
|
sql := "select "
|
||||||
|
|
||||||
|
if count {
|
||||||
|
sql += `count(1) from email e left join user_email ue on e.id=ue.email_id where ue.user_id = ? `
|
||||||
|
} else if pop3List {
|
||||||
|
sql += `e.id,e.size from email e left join user_email ue on e.id=ue.email_id where ue.user_id = ? `
|
||||||
|
} else {
|
||||||
|
sql += `e.*,ue.is_read from email e left join user_email ue on e.id=ue.email_id where ue.user_id = ? `
|
||||||
|
}
|
||||||
|
|
||||||
|
if tagInfo.Status != -1 {
|
||||||
|
sql += " and ue.status =? "
|
||||||
|
sqlParams = append(sqlParams, tagInfo.Status)
|
||||||
|
} else {
|
||||||
|
sql += " and ue.status != 3"
|
||||||
|
}
|
||||||
|
|
||||||
if tagInfo.Type != -1 {
|
if tagInfo.Type != -1 {
|
||||||
sql += " and type =? "
|
sql += " and type =? "
|
||||||
sqlParams = append(sqlParams, tagInfo.Type)
|
sqlParams = append(sqlParams, tagInfo.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tagInfo.Status != -1 {
|
|
||||||
sql += " and status =? "
|
|
||||||
sqlParams = append(sqlParams, tagInfo.Status)
|
|
||||||
} else {
|
|
||||||
sql += " and status != 3"
|
|
||||||
}
|
|
||||||
|
|
||||||
if tagInfo.GroupId != -1 {
|
if tagInfo.GroupId != -1 {
|
||||||
sql += " and group_id=? "
|
sql += " and ue.group_id=? "
|
||||||
sqlParams = append(sqlParams, tagInfo.GroupId)
|
sqlParams = append(sqlParams, tagInfo.GroupId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,5 +65,32 @@ func genSQL(ctx *context.Context, tag, keyword string) (string, []any) {
|
|||||||
sqlParams = append(sqlParams, "%"+keyword+"%", "%"+keyword+"%")
|
sqlParams = append(sqlParams, "%"+keyword+"%", "%"+keyword+"%")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if limit == 0 {
|
||||||
|
limit = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
sql += " order by e.id desc"
|
||||||
|
|
||||||
|
if limit < 10000 {
|
||||||
|
sql += fmt.Sprintf(" limit %d,%d ", offset, limit)
|
||||||
|
}
|
||||||
|
|
||||||
return sql, sqlParams
|
return sql, sqlParams
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type statRes struct {
|
||||||
|
Total int64
|
||||||
|
Size int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stat 查询邮件总数和大小
|
||||||
|
func Stat(ctx *context.Context) (int64, int64) {
|
||||||
|
sql := `select count(1) as total,sum(size) as size from email e left join user_email ue on e.id=ue.email_id where ue.user_id = ? and e.type = 0 and ue.status != 3`
|
||||||
|
var ret statRes
|
||||||
|
_, err := db.Instance.SQL(sql, ctx.UserID).Get(&ret)
|
||||||
|
if err != nil {
|
||||||
|
log.WithContext(ctx).Errorf("SQL ERROR: %s ,Error:%s", sql, err)
|
||||||
|
}
|
||||||
|
return ret.Total, ret.Size
|
||||||
}
|
}
|
||||||
|
54
server/services/list/list_test.go
Normal file
54
server/services/list/list_test.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package list
|
||||||
|
|
||||||
|
import (
|
||||||
|
"pmail/dto"
|
||||||
|
"pmail/utils/context"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_genSQL(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
ctx *context.Context
|
||||||
|
count bool
|
||||||
|
tagInfo dto.SearchTag
|
||||||
|
keyword string
|
||||||
|
pop3List bool
|
||||||
|
offset int
|
||||||
|
limit int
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want string
|
||||||
|
want1 []any
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Group搜索",
|
||||||
|
args: args{
|
||||||
|
ctx: &context.Context{
|
||||||
|
UserID: 1,
|
||||||
|
},
|
||||||
|
count: false,
|
||||||
|
tagInfo: dto.SearchTag{-1, -1, 2},
|
||||||
|
keyword: "",
|
||||||
|
pop3List: false,
|
||||||
|
offset: 0,
|
||||||
|
limit: 0,
|
||||||
|
},
|
||||||
|
want: "select e.*,ue.is_read from email e left join user_email ue on e.id=ue.email_id where ue.user_id = ? and ue.status != 3 and ue.group_id=? order by e.id desc limit 0,10 ",
|
||||||
|
want1: []any{1, 2},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, got1 := genSQL(tt.args.ctx, tt.args.count, tt.args.tagInfo, tt.args.keyword, tt.args.pop3List, tt.args.offset, tt.args.limit)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("genSQL() got = \n%v, want \n%v", got, tt.want)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got1, tt.want1) {
|
||||||
|
t.Errorf("genSQL() got1 = \n%v, want \n%v", got1, tt.want1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
"pmail/config"
|
"pmail/config"
|
||||||
|
"pmail/consts"
|
||||||
"pmail/db"
|
"pmail/db"
|
||||||
"pmail/dto"
|
"pmail/dto"
|
||||||
"pmail/dto/parsemail"
|
"pmail/dto/parsemail"
|
||||||
@ -14,13 +15,13 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetAllRules(ctx *context.Context) []*dto.Rule {
|
func GetAllRules(ctx *context.Context, userId int) []*dto.Rule {
|
||||||
var res []*models.Rule
|
var res []*models.Rule
|
||||||
var err error
|
var err error
|
||||||
if ctx == nil || ctx.UserID == 0 {
|
if userId == 0 {
|
||||||
err = db.Instance.Decr("sort").Find(&res)
|
return nil
|
||||||
} else {
|
} else {
|
||||||
err = db.Instance.Where("user_id=?", ctx.UserID).Decr("sort").Find(&res)
|
err = db.Instance.Where("user_id=?", userId).Decr("sort").Find(&res)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -64,14 +65,16 @@ func DoRule(ctx *context.Context, rule *dto.Rule, email *parsemail.Email) {
|
|||||||
|
|
||||||
switch rule.Action {
|
switch rule.Action {
|
||||||
case dto.READ:
|
case dto.READ:
|
||||||
email.IsRead = 1
|
|
||||||
if email.MessageId > 0 {
|
if email.MessageId > 0 {
|
||||||
db.Instance.Exec(db.WithContext(ctx, "update email set is_read=1 where id =?"), email.MessageId)
|
_, err := db.Instance.Table(&models.UserEmail{}).Where("email_id=? and user_id=?", email.MessageId, rule.UserId).Cols("is_read").Update(map[string]interface{}{"is_read": 1})
|
||||||
|
if err != nil {
|
||||||
|
log.WithContext(ctx).Errorf("sqlERror :%v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case dto.DELETE:
|
case dto.DELETE:
|
||||||
email.Status = 3
|
_, err := db.Instance.Table(&models.UserEmail{}).Where("email_id=? and user_id=?", email.MessageId, rule.UserId).Cols("status").Update(map[string]interface{}{"status": consts.EmailStatusDel})
|
||||||
if email.MessageId > 0 {
|
if err != nil {
|
||||||
db.Instance.Exec(db.WithContext(ctx, "update email set status=3 where id =?"), email.MessageId)
|
log.WithContext(ctx).Errorf("sqlERror :%v", err)
|
||||||
}
|
}
|
||||||
case dto.FORWARD:
|
case dto.FORWARD:
|
||||||
if strings.Contains(rule.Params, config.Instance.Domain) {
|
if strings.Contains(rule.Params, config.Instance.Domain) {
|
||||||
@ -83,9 +86,9 @@ func DoRule(ctx *context.Context, rule *dto.Rule, email *parsemail.Email) {
|
|||||||
log.WithContext(ctx).Errorf("Forward Error:%v", err)
|
log.WithContext(ctx).Errorf("Forward Error:%v", err)
|
||||||
}
|
}
|
||||||
case dto.MOVE:
|
case dto.MOVE:
|
||||||
email.GroupId = cast.ToInt(rule.Params)
|
_, err := db.Instance.Table(&models.UserEmail{}).Where("email_id=? and user_id=?", email.MessageId, rule.UserId).Cols("group_id").Update(map[string]interface{}{"group_id": cast.ToInt(rule.Params)})
|
||||||
if email.MessageId > 0 {
|
if err != nil {
|
||||||
db.Instance.Exec(db.WithContext(ctx, "update email set group_id=? where id =?"), email.GroupId, email.MessageId)
|
log.WithContext(ctx).Errorf("sqlERror :%v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,18 +43,11 @@ func GetAdminPassword(ctx *context.Context) (string, error) {
|
|||||||
|
|
||||||
func SetAdminPassword(ctx *context.Context, account, pwd string) error {
|
func SetAdminPassword(ctx *context.Context, account, pwd string) error {
|
||||||
encodePwd := password.Encode(pwd)
|
encodePwd := password.Encode(pwd)
|
||||||
res, err := db.Instance.Exec(db.WithContext(ctx, "INSERT INTO user (account, name, password) VALUES (?, 'admin',?)"), account, encodePwd)
|
_, err := db.Instance.Exec(db.WithContext(ctx, "INSERT INTO user (account, name, password,is_admin) VALUES (?, 'admin',?,1)"), account, encodePwd)
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err)
|
|
||||||
}
|
|
||||||
id, err := res.LastInsertId()
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err)
|
|
||||||
}
|
|
||||||
_, err = db.Instance.Exec(db.WithContext(ctx, "INSERT INTO user_auth (user_id, email_account) VALUES (?, '*')"), id)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err)
|
return errors.Wrap(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,7 +74,7 @@ func SetDatabaseSettings(ctx *context.Context, dbType, dbDSN string) error {
|
|||||||
}
|
}
|
||||||
config.Init()
|
config.Init()
|
||||||
// 检查数据库是否能正确连接
|
// 检查数据库是否能正确连接
|
||||||
err = db.Init()
|
err = db.Init("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err)
|
return errors.Wrap(err)
|
||||||
}
|
}
|
||||||
|
@ -26,9 +26,9 @@ func GetDNSSettings(ctx *context.Context) ([]*DNSItem, error) {
|
|||||||
ret := []*DNSItem{
|
ret := []*DNSItem{
|
||||||
{Type: "A", Host: "smtp", Value: ip.GetIp(), TTL: 3600, Tips: i18n.GetText(ctx.Lang, "ip_taps")},
|
{Type: "A", Host: "smtp", Value: ip.GetIp(), TTL: 3600, Tips: i18n.GetText(ctx.Lang, "ip_taps")},
|
||||||
{Type: "A", Host: "pop", Value: ip.GetIp(), TTL: 3600, Tips: i18n.GetText(ctx.Lang, "ip_taps")},
|
{Type: "A", Host: "pop", Value: ip.GetIp(), TTL: 3600, Tips: i18n.GetText(ctx.Lang, "ip_taps")},
|
||||||
{Type: "A", Host: "-", Value: ip.GetIp(), TTL: 3600, Tips: i18n.GetText(ctx.Lang, "ip_taps")},
|
{Type: "A", Host: "", Value: ip.GetIp(), TTL: 3600, Tips: i18n.GetText(ctx.Lang, "ip_taps")},
|
||||||
{Type: "MX", Host: "-", Value: fmt.Sprintf("smtp.%s", configData.Domain), TTL: 3600},
|
{Type: "MX", Host: "", Value: fmt.Sprintf("smtp.%s", configData.Domain), TTL: 3600},
|
||||||
{Type: "TXT", Host: "-", Value: "v=spf1 a mx ~all", TTL: 3600},
|
{Type: "TXT", Host: "", Value: "v=spf1 a mx ~all", TTL: 3600},
|
||||||
{Type: "TXT", Host: "default._domainkey", Value: auth.DkimGen(), TTL: 3600},
|
{Type: "TXT", Host: "default._domainkey", Value: auth.DkimGen(), TTL: 3600},
|
||||||
}
|
}
|
||||||
return ret, nil
|
return ret, nil
|
||||||
|
@ -2,12 +2,11 @@ package setup
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"pmail/signal"
|
"pmail/signal"
|
||||||
"pmail/utils/context"
|
|
||||||
"pmail/utils/errors"
|
"pmail/utils/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Finish 标记初始化完成
|
// Finish 标记初始化完成
|
||||||
func Finish(ctx *context.Context) error {
|
func Finish() error {
|
||||||
cfg, err := ReadConfig()
|
cfg, err := ReadConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err)
|
return errors.Wrap(err)
|
||||||
|
67
server/services/setup/ssl/dnsProvide.go
Normal file
67
server/services/setup/ssl/dnsProvide.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package ssl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns"
|
||||||
|
"os"
|
||||||
|
"pmail/utils/errors"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetServerParamsList(serverName string) ([]string, error) {
|
||||||
|
var serverParams []string
|
||||||
|
|
||||||
|
infos, err := os.ReadDir("./")
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
upperServerName := strings.ToUpper(serverName)
|
||||||
|
|
||||||
|
for _, info := range infos {
|
||||||
|
if strings.HasPrefix(info.Name(), upperServerName) {
|
||||||
|
serverParams = append(serverParams, info.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(serverParams) != 0 {
|
||||||
|
return serverParams, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = dns.NewDNSChallengeProviderByName(serverName)
|
||||||
|
if err == nil {
|
||||||
|
return nil, errors.New(serverName + " Not Support")
|
||||||
|
}
|
||||||
|
if strings.Contains(err.Error(), "unrecognized DNS provider") {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
re := regexp.MustCompile(`missing: (.+)`)
|
||||||
|
// namesilo: some credentials information are missing: NAMESILO_API_KEY
|
||||||
|
estr := err.Error()
|
||||||
|
name := re.FindStringSubmatch(estr)
|
||||||
|
|
||||||
|
if len(name) == 2 {
|
||||||
|
names := strings.Split(name[1], ",")
|
||||||
|
|
||||||
|
for _, s := range names {
|
||||||
|
serverParams = append(serverParams, s)
|
||||||
|
SetDomainServerParams(s, "empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
_, err = dns.NewDNSChallengeProviderByName(serverName)
|
||||||
|
|
||||||
|
return serverParams, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetDomainServerParams(name, value string) {
|
||||||
|
key := name
|
||||||
|
err := os.WriteFile(key, []byte(value), 0644)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
err = os.Setenv(name+"_FILE", key)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
36
server/services/setup/ssl/dnsProvide_test.go
Normal file
36
server/services/setup/ssl/dnsProvide_test.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package ssl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetServerParamsList(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
serverName string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want []string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{name: "namesilo", args: args{serverName: "namesilo"}, want: []string{"NAMESILO_API_KEY"}, wantErr: false},
|
||||||
|
{name: "namesiloAgain", args: args{serverName: "namesilo"}, want: []string{"NAMESILO_API_KEY"}, wantErr: false},
|
||||||
|
{name: "auroradns", args: args{serverName: "auroradns"}, want: []string{"AURORA_API_KEY", "AURORA_SECRET"}, wantErr: false},
|
||||||
|
{name: "alidns", args: args{serverName: "alidns"}, want: []string{"ALICLOUD_ACCESS_KEY", "ALICLOUD_SECRET_KEY"}, wantErr: false},
|
||||||
|
{name: "null", args: args{serverName: "null"}, want: nil, wantErr: true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := GetServerParamsList(tt.args.serverName)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("GetServerParamsList() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("GetServerParamsList() got = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -8,13 +8,17 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"github.com/go-acme/lego/v4/certificate"
|
"github.com/go-acme/lego/v4/certificate"
|
||||||
|
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
"os"
|
"os"
|
||||||
"pmail/config"
|
"pmail/config"
|
||||||
"pmail/services/setup"
|
"pmail/services/setup"
|
||||||
"pmail/signal"
|
"pmail/signal"
|
||||||
|
"pmail/utils/async"
|
||||||
"pmail/utils/errors"
|
"pmail/utils/errors"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
@ -44,19 +48,20 @@ func GetSSL() string {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if cfg.SSLType == "" {
|
if cfg.SSLType == "" {
|
||||||
return config.SSLTypeAuto
|
return config.SSLTypeAutoHTTP
|
||||||
}
|
}
|
||||||
|
|
||||||
return cfg.SSLType
|
return cfg.SSLType
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetSSL(sslType, priKey, crtKey string) error {
|
func SetSSL(sslType, priKey, crtKey, serviceName string) error {
|
||||||
cfg, err := setup.ReadConfig()
|
cfg, err := setup.ReadConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if sslType == config.SSLTypeAuto || sslType == config.SSLTypeUser {
|
if sslType == config.SSLTypeAutoHTTP || sslType == config.SSLTypeUser || sslType == config.SSLTypeAutoDNS {
|
||||||
cfg.SSLType = sslType
|
cfg.SSLType = sslType
|
||||||
|
cfg.DomainServiceName = serviceName
|
||||||
} else {
|
} else {
|
||||||
return errors.New("SSL Type Error!")
|
return errors.New("SSL Type Error!")
|
||||||
}
|
}
|
||||||
@ -101,19 +106,34 @@ func GenSSL(update bool) error {
|
|||||||
key: privateKey,
|
key: privateKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
config := lego.NewConfig(&myUser)
|
conf := lego.NewConfig(&myUser)
|
||||||
|
conf.UserAgent = "PMail"
|
||||||
config.Certificate.KeyType = certcrypto.RSA2048
|
conf.Certificate.KeyType = certcrypto.RSA2048
|
||||||
|
|
||||||
// A client facilitates communication with the CA server.
|
// A client facilitates communication with the CA server.
|
||||||
client, err := lego.NewClient(config)
|
client, err := lego.NewClient(conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err)
|
return errors.Wrap(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = client.Challenge.SetHTTP01Provider(GetHttpChallengeInstance())
|
if cfg.SSLType == "0" {
|
||||||
if err != nil {
|
err = client.Challenge.SetHTTP01Provider(GetHttpChallengeInstance())
|
||||||
return errors.Wrap(err)
|
if err != nil {
|
||||||
|
return errors.Wrap(err)
|
||||||
|
}
|
||||||
|
} else if cfg.SSLType == "2" {
|
||||||
|
err = os.Setenv(strings.ToUpper(cfg.DomainServiceName)+"_PROPAGATION_TIMEOUT", "900")
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Set ENV Variable Error: %s", err.Error())
|
||||||
|
}
|
||||||
|
dnspodProvider, err := dns.NewDNSChallengeProviderByName(cfg.DomainServiceName)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err)
|
||||||
|
}
|
||||||
|
err = client.Challenge.SetDNS01Provider(dnspodProvider, dns01.AddDNSTimeout(15*time.Minute))
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||||
@ -126,25 +146,34 @@ func GenSSL(update bool) error {
|
|||||||
Domains: []string{"smtp." + cfg.Domain, cfg.WebDomain, "pop." + cfg.Domain},
|
Domains: []string{"smtp." + cfg.Domain, cfg.WebDomain, "pop." + cfg.Domain},
|
||||||
Bundle: true,
|
Bundle: true,
|
||||||
}
|
}
|
||||||
certificates, err := client.Certificate.Obtain(request)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.WriteFile("./config/ssl/private.key", certificates.PrivateKey, 0666)
|
as := async.New(nil)
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.WriteFile("./config/ssl/public.crt", certificates.Certificate, 0666)
|
as.Process(func(params any) {
|
||||||
if err != nil {
|
log.Infof("wait ssl")
|
||||||
return errors.Wrap(err)
|
certificates, err := client.Certificate.Obtain(request)
|
||||||
}
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
err = os.WriteFile("./config/ssl/issuerCert.crt", certificates.IssuerCertificate, 0666)
|
err = os.WriteFile("./config/ssl/private.key", certificates.PrivateKey, 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = os.WriteFile("./config/ssl/public.crt", certificates.Certificate, 0666)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.WriteFile("./config/ssl/issuerCert.crt", certificates.IssuerCertificate, 0666)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
setup.Finish()
|
||||||
|
|
||||||
|
}, nil)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -19,9 +19,11 @@ import (
|
|||||||
"pmail/services/rule"
|
"pmail/services/rule"
|
||||||
"pmail/utils/async"
|
"pmail/utils/async"
|
||||||
"pmail/utils/context"
|
"pmail/utils/context"
|
||||||
|
"pmail/utils/errors"
|
||||||
"pmail/utils/send"
|
"pmail/utils/send"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
. "xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Session) Data(r io.Reader) error {
|
func (s *Session) Data(r io.Reader) error {
|
||||||
@ -61,8 +63,12 @@ func (s *Session) Data(r io.Reader) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 判断是收信还是转发,只要是登陆了,都当成转发处理
|
// 判断是收信还是转发,只要是登陆了,都当成转发处理
|
||||||
//account, domain := email.From.GetDomainAccount()
|
|
||||||
if s.Ctx.UserID > 0 {
|
if s.Ctx.UserID > 0 {
|
||||||
|
account, _ := email.From.GetDomainAccount()
|
||||||
|
if account != ctx.UserAccount && !ctx.IsAdmin {
|
||||||
|
return errors.New("No Auth")
|
||||||
|
}
|
||||||
|
|
||||||
log.WithContext(ctx).Debugf("开始执行插件SendBefore!")
|
log.WithContext(ctx).Debugf("开始执行插件SendBefore!")
|
||||||
for _, hook := range hooks.HookList {
|
for _, hook := range hooks.HookList {
|
||||||
if hook == nil {
|
if hook == nil {
|
||||||
@ -77,7 +83,7 @@ func (s *Session) Data(r io.Reader) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 转发
|
// 转发
|
||||||
err := saveEmail(ctx, email, s.Ctx.UserID, 1, true, true)
|
_, err := saveEmail(ctx, len(emailData), email, s.Ctx.UserID, 1, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithContext(ctx).Errorf("Email Save Error %v", err)
|
log.WithContext(ctx).Errorf("Email Save Error %v", err)
|
||||||
}
|
}
|
||||||
@ -105,11 +111,20 @@ func (s *Session) Data(r io.Reader) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithContext(ctx).Errorf("sql Error :%+v", err)
|
log.WithContext(ctx).Errorf("sql Error :%+v", err)
|
||||||
}
|
}
|
||||||
|
_, err = db.Instance.Exec(db.WithContext(ctx, "update user_email set status =2 where email_id = ? "), email.MessageId)
|
||||||
|
if err != nil {
|
||||||
|
log.WithContext(ctx).Errorf("sql Error :%+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
_, err := db.Instance.Exec(db.WithContext(ctx, "update email set status =1 where id = ? "), email.MessageId)
|
_, err := db.Instance.Exec(db.WithContext(ctx, "update email set status =1 where id = ? "), email.MessageId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithContext(ctx).Errorf("sql Error :%+v", err)
|
log.WithContext(ctx).Errorf("sql Error :%+v", err)
|
||||||
}
|
}
|
||||||
|
_, err = db.Instance.Exec(db.WithContext(ctx, "update user_email set status =1 where email_id = ? "), email.MessageId)
|
||||||
|
if err != nil {
|
||||||
|
log.WithContext(ctx).Errorf("sql Error :%+v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@ -120,10 +135,6 @@ func (s *Session) Data(r io.Reader) error {
|
|||||||
// DKIM校验
|
// DKIM校验
|
||||||
dkimStatus = parsemail.Check(bytes.NewReader(emailData))
|
dkimStatus = parsemail.Check(bytes.NewReader(emailData))
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.WithContext(ctx).Errorf("邮件内容解析失败! Error : %v \n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
SPFStatus = spfCheck(s.RemoteAddress.String(), email.Sender, email.Sender.EmailAddress)
|
SPFStatus = spfCheck(s.RemoteAddress.String(), email.Sender, email.Sender.EmailAddress)
|
||||||
|
|
||||||
log.WithContext(ctx).Debugf("开始执行插件ReceiveParseAfter!")
|
log.WithContext(ctx).Debugf("开始执行插件ReceiveParseAfter!")
|
||||||
@ -135,10 +146,6 @@ func (s *Session) Data(r io.Reader) error {
|
|||||||
}
|
}
|
||||||
log.WithContext(ctx).Debugf("开始执行插件ReceiveParseAfter!End")
|
log.WithContext(ctx).Debugf("开始执行插件ReceiveParseAfter!End")
|
||||||
|
|
||||||
if email == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 垃圾过滤
|
// 垃圾过滤
|
||||||
if config.Instance.SpamFilterLevel == 1 && !SPFStatus && !dkimStatus {
|
if config.Instance.SpamFilterLevel == 1 && !SPFStatus && !dkimStatus {
|
||||||
log.WithContext(ctx).Infoln("垃圾邮件,拒信")
|
log.WithContext(ctx).Infoln("垃圾邮件,拒信")
|
||||||
@ -150,27 +157,34 @@ func (s *Session) Data(r io.Reader) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
saveEmail(ctx, email, 0, 0, SPFStatus, dkimStatus)
|
users, _ := saveEmail(ctx, len(emailData), email, 0, 0, SPFStatus, dkimStatus)
|
||||||
|
|
||||||
if email.MessageId > 0 {
|
if email.MessageId > 0 {
|
||||||
log.WithContext(ctx).Debugf("开始执行邮件规则!")
|
log.WithContext(ctx).Debugf("开始执行邮件规则!")
|
||||||
// 执行邮件规则
|
for _, user := range users {
|
||||||
rs := rule.GetAllRules(ctx)
|
// 执行邮件规则
|
||||||
for _, r := range rs {
|
rs := rule.GetAllRules(ctx, user.ID)
|
||||||
if rule.MatchRule(ctx, r, email) {
|
for _, r := range rs {
|
||||||
rule.DoRule(ctx, r, email)
|
if rule.MatchRule(ctx, r, email) {
|
||||||
|
rule.DoRule(ctx, r, email)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.WithContext(ctx).Debugf("开始执行插件ReceiveSaveAfter!")
|
log.WithContext(ctx).Debugf("开始执行插件ReceiveSaveAfter!")
|
||||||
|
var ue []*models.UserEmail
|
||||||
|
err = db.Instance.Table(&models.UserEmail{}).Where("email_id=?", email.MessageId).Find(&ue)
|
||||||
|
if err != nil {
|
||||||
|
log.WithContext(ctx).Errorf("sql Error :%+v", err)
|
||||||
|
}
|
||||||
as3 := async.New(ctx)
|
as3 := async.New(ctx)
|
||||||
for _, hook := range hooks.HookList {
|
for _, hook := range hooks.HookList {
|
||||||
if hook == nil {
|
if hook == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
as3.WaitProcess(func(hk any) {
|
as3.WaitProcess(func(hk any) {
|
||||||
hk.(framework.EmailHook).ReceiveSaveAfter(ctx, email)
|
hk.(framework.EmailHook).ReceiveSaveAfter(ctx, email, ue)
|
||||||
}, hook)
|
}, hook)
|
||||||
}
|
}
|
||||||
as3.Wait()
|
as3.Wait()
|
||||||
@ -181,7 +195,7 @@ func (s *Session) Data(r io.Reader) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveEmail(ctx *context.Context, email *parsemail.Email, sendUserID int, emailType int, SPFStatus, dkimStatus bool) error {
|
func saveEmail(ctx *context.Context, size int, email *parsemail.Email, sendUserID int, emailType int, SPFStatus, dkimStatus bool) ([]*models.User, error) {
|
||||||
var dkimV, spfV int8
|
var dkimV, spfV int8
|
||||||
if dkimStatus {
|
if dkimStatus {
|
||||||
dkimV = 1
|
dkimV = 1
|
||||||
@ -193,12 +207,11 @@ func saveEmail(ctx *context.Context, email *parsemail.Email, sendUserID int, ema
|
|||||||
log.WithContext(ctx).Debugf("开始入库!")
|
log.WithContext(ctx).Debugf("开始入库!")
|
||||||
|
|
||||||
if email == nil {
|
if email == nil {
|
||||||
return nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
modelEmail := models.Email{
|
modelEmail := models.Email{
|
||||||
Type: cast.ToInt8(emailType),
|
Type: cast.ToInt8(emailType),
|
||||||
GroupId: email.GroupId,
|
|
||||||
Subject: email.Subject,
|
Subject: email.Subject,
|
||||||
ReplyTo: json2string(email.ReplyTo),
|
ReplyTo: json2string(email.ReplyTo),
|
||||||
FromName: email.From.Name,
|
FromName: email.From.Name,
|
||||||
@ -227,8 +240,55 @@ func saveEmail(ctx *context.Context, email *parsemail.Email, sendUserID int, ema
|
|||||||
if modelEmail.Id > 0 {
|
if modelEmail.Id > 0 {
|
||||||
email.MessageId = cast.ToInt64(modelEmail.Id)
|
email.MessageId = cast.ToInt64(modelEmail.Id)
|
||||||
}
|
}
|
||||||
|
// 收信人信息
|
||||||
|
var users []*models.User
|
||||||
|
|
||||||
return nil
|
// 如果是收信
|
||||||
|
if emailType == 0 {
|
||||||
|
// 找到收信人id
|
||||||
|
accounts := []string{}
|
||||||
|
for _, user := range append(append(email.To, email.Cc...), email.Bcc...) {
|
||||||
|
account, _ := user.GetDomainAccount()
|
||||||
|
if account != "" {
|
||||||
|
accounts = append(accounts, account)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
where, params, _ := ToSQL(In("account", accounts))
|
||||||
|
|
||||||
|
err = db.Instance.Table(&models.User{}).Where(where, params...).Find(&users)
|
||||||
|
if err != nil {
|
||||||
|
log.WithContext(ctx).Errorf("db Select error:%+v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(users) > 0 {
|
||||||
|
for _, user := range users {
|
||||||
|
ue := models.UserEmail{EmailID: modelEmail.Id, UserID: user.ID}
|
||||||
|
_, err = db.Instance.Insert(&ue)
|
||||||
|
if err != nil {
|
||||||
|
log.WithContext(ctx).Errorf("db insert error:%+v", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
users = append(users, &models.User{ID: 1})
|
||||||
|
// 当邮件找不到收件人的时候,邮件全部丢给管理员账号
|
||||||
|
// id = 1的账号直接当成管理员账号处理
|
||||||
|
ue := models.UserEmail{EmailID: modelEmail.Id, UserID: 1}
|
||||||
|
_, err = db.Instance.Insert(&ue)
|
||||||
|
if err != nil {
|
||||||
|
log.WithContext(ctx).Errorf("db insert error:%+v", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
ue := models.UserEmail{EmailID: modelEmail.Id, UserID: ctx.UserID}
|
||||||
|
_, err = db.Instance.Insert(&ue)
|
||||||
|
if err != nil {
|
||||||
|
log.WithContext(ctx).Errorf("db insert error:%+v", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return users, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func json2string(d any) string {
|
func json2string(d any) string {
|
||||||
|
@ -43,7 +43,7 @@ func testInit() {
|
|||||||
config.Instance.DbDSN = "../config/pmail_temp.db"
|
config.Instance.DbDSN = "../config/pmail_temp.db"
|
||||||
|
|
||||||
parsemail2.Init()
|
parsemail2.Init()
|
||||||
db.Init()
|
db.Init("")
|
||||||
session.Init()
|
session.Init()
|
||||||
hooks.Init("dev")
|
hooks.Init("dev")
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ func (s *Session) AuthPlain(username, pwd string) error {
|
|||||||
username = infos[0]
|
username = infos[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := db.Instance.Where("account =? and password =?", username, encodePwd).Get(&user)
|
_, err := db.Instance.Where("account =? and password =? and disabled=0", username, encodePwd).Get(&user)
|
||||||
if err != nil && err != sql.ErrNoRows {
|
if err != nil && err != sql.ErrNoRows {
|
||||||
log.Errorf("%+v", err)
|
log.Errorf("%+v", err)
|
||||||
}
|
}
|
||||||
@ -90,6 +90,7 @@ func (s *Session) AuthPlain(username, pwd string) error {
|
|||||||
s.Ctx.UserAccount = user.Account
|
s.Ctx.UserAccount = user.Account
|
||||||
s.Ctx.UserID = user.ID
|
s.Ctx.UserID = user.ID
|
||||||
s.Ctx.UserName = user.Name
|
s.Ctx.UserName = user.Name
|
||||||
|
s.Ctx.IsAdmin = user.IsAdmin == 1
|
||||||
|
|
||||||
log.WithContext(s.Ctx).Debugf("Auth Success %+v", user)
|
log.WithContext(s.Ctx).Debugf("Auth Success %+v", user)
|
||||||
return nil
|
return nil
|
||||||
|
@ -15,6 +15,7 @@ type Context struct {
|
|||||||
UserName string
|
UserName string
|
||||||
Values map[string]any
|
Values map[string]any
|
||||||
Lang string
|
Lang string
|
||||||
|
IsAdmin bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) SetValue(key string, value any) {
|
func (c *Context) SetValue(key string, value any) {
|
||||||
|
11
server/utils/password/encode_test.go
Normal file
11
server/utils/password/encode_test.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEncode(t *testing.T) {
|
||||||
|
fmt.Println(Encode("user2"))
|
||||||
|
fmt.Println(Encode("user2New"))
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user