diff --git a/config/dev.env.js b/config/dev.env.js index 13ea61f7b754ded74198bc89d08714ccca08b798..f2b2a130357120c83937f6bdd4da4ca24326ef86 100644 --- a/config/dev.env.js +++ b/config/dev.env.js @@ -1,5 +1,4 @@ module.exports = { NODE_ENV: '"development"', - ENV_CONFIG: '"dev"', - BASE_API: '"http://localhost:4000"' + ENV_CONFIG: '"dev"' } diff --git a/package.json b/package.json index 81dff90cf905718205dacf0cf381d48f2f433828..097c291fb983b0ee4f3a7a71346c0ed1521d1204 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,7 @@ "eslint-loader": "2.0.0", "eslint-plugin-vue": "4.7.1", "file-loader": "1.1.11", + "flush-promises": "^1.0.2", "friendly-errors-webpack-plugin": "1.7.0", "hash-sum": "1.0.2", "html-webpack-plugin": "^3.2.0", diff --git a/src/api/__mocks__/login.js b/src/api/__mocks__/login.js new file mode 100644 index 0000000000000000000000000000000000000000..9f1bfeb1d68b6f38e41626451fc4319442e5a459 --- /dev/null +++ b/src/api/__mocks__/login.js @@ -0,0 +1,59 @@ +const users = [ + { username: 'bob', password: '123456' } +] + +export async function loginByUsername(username, password) { + const user = users.find(user => user.username === username) + const verifyPassword = user.password === password + const data = { + 'token_type': 'Bearer', + 'scope': 'read write follow', + 'refresh_token': 'foo123', + 'me': 'bob', + 'expires_in': 600, + 'access_token': 'bar123' + } + + return verifyPassword + ? Promise.resolve({ data }) + : Promise.reject({ message: 'Invalid credentials' }) +} + +export function getUserInfo(token, authHost) { + const userInfo = { + 'name_html': 'bob', + 'background_image': null, + 'friends_count': 0, + 'description_html': '', + 'followers_count': 0, + 'locked': false, + 'follows_you': true, + 'statusnet_blocking': false, + 'statusnet_profile_url': '', + 'following': true, + 'id': '10', + 'is_local': true, + 'profile_image_url': '', + 'role': 'admin', + 'profile_image_url_profile_size': '', + 'rights': { 'admin': true, 'delete_others_notice': true }, + 'token': 'foo123456', + 'no_rich_text': false, + 'statuses_count': 0, + 'cover_photo': '', + 'hide_follows': false, + 'pleroma': { 'confirmation_pending': false, 'deactivated': false, 'tags': ['force_nsfw'] }, + 'profile_image_url_original': '', + 'created_at': 'Fri Mar 01 15:15:19 +0000 2019', + 'fields': [], + 'name': 'bob', + 'description': '', + 'favourites_count': 0, + 'default_scope': 'public', + 'profile_image_url_https': '', + 'hide_followers': false, + 'show_role': true, + 'screen_name': 'bob' } + + return Promise.resolve({ data: userInfo }) +} diff --git a/src/api/__mocks__/users.js b/src/api/__mocks__/users.js index c8e6fe9459feb478b7a7507d6ebae1c985876bf5..73a93174b9b8aef7867511c1958c0b1a5b20241d 100644 --- a/src/api/__mocks__/users.js +++ b/src/api/__mocks__/users.js @@ -4,7 +4,7 @@ const users = [ { deactivated: true, id: 'abc', nickname: 'john', local: true, roles: { admin: false, moderator: false }, tags: ['strip_media'] } ] -export async function fetchUsers(showLocalUsersOnly, token, page = 1) { +export async function fetchUsers(showLocalUsersOnly, authHost, token, page = 1) { const filteredUsers = showLocalUsersOnly ? users.filter(user => user.local) : users return Promise.resolve({ data: { users: filteredUsers, @@ -13,12 +13,12 @@ export async function fetchUsers(showLocalUsersOnly, token, page = 1) { }}) } -export async function toggleUserActivation(nickname, token) { +export async function toggleUserActivation(nickname, authHost, token) { const response = users.find(user => user.nickname === nickname) return Promise.resolve({ data: { ...response, deactivated: !response.deactivated }}) } -export async function searchUsers(query, showLocalUsersOnly, token, page = 1) { +export async function searchUsers(query, showLocalUsersOnly, authHost, token, page = 1) { const filteredUsers = showLocalUsersOnly ? users.filter(user => user.local) : users const response = filteredUsers.filter(user => user.nickname === query) return Promise.resolve({ data: { @@ -28,28 +28,28 @@ export async function searchUsers(query, showLocalUsersOnly, token, page = 1) { }}) } -export async function addRight(nickname, right, token) { +export async function addRight(nickname, right, authHost, token) { return Promise.resolve({ data: { [`is_${right}`]: true } }) } -export async function deleteRight(nickname, right, token) { +export async function deleteRight(nickname, right, authHost, token) { return Promise.resolve({ data: { [`is_${right}`]: false } }) } -export async function deleteUser(nickname, token) { +export async function deleteUser(nickname, authHost, token) { return Promise.resolve({ data: nickname }) } -export async function tagUser(nickname, tag, token) { +export async function tagUser(nickname, tag, authHost, token) { return Promise.resolve() } -export async function untagUser(nickname, tag, token) { +export async function untagUser(nickname, tag, authHost, token) { return Promise.resolve() } diff --git a/src/api/login.js b/src/api/login.js index e3d990d1b06c794f5d218bac9dc5c853df66b1d7..adaabd54832fa1374a31adc28ed354bec14417b8 100644 --- a/src/api/login.js +++ b/src/api/login.js @@ -1,7 +1,9 @@ import request from '@/utils/request' +import { baseName } from './utils' -export async function loginByUsername(username, password) { +export async function loginByUsername(username, password, authHost) { const appsRequest = await request({ + baseURL: baseName(authHost), url: '/api/v1/apps', method: 'post', data: { @@ -14,6 +16,7 @@ export async function loginByUsername(username, password) { const app = appsRequest.data return request({ + baseURL: baseName(authHost), url: '/oauth/token', method: 'post', data: { @@ -26,8 +29,9 @@ export async function loginByUsername(username, password) { }) } -export function getUserInfo(token) { +export function getUserInfo(token, authHost) { return request({ + baseURL: baseName(authHost), url: '/api/account/verify_credentials', method: 'post', headers: token ? { 'Authorization': `Bearer ${token}` } : {} diff --git a/src/api/users.js b/src/api/users.js index 43ebe5e1572ec36d633eb04fbc30be9f12909123..2ec6a2abfb7c2a66189502279f41bd5ac6ae3614 100644 --- a/src/api/users.js +++ b/src/api/users.js @@ -1,56 +1,64 @@ import request from '@/utils/request' import { getToken } from '@/utils/auth' +import { baseName } from './utils' -export async function fetchUsers(showLocalUsersOnly, token, page = 1) { +export async function fetchUsers(showLocalUsersOnly, authHost, token, page = 1) { return await request({ + baseURL: baseName(authHost), url: `/api/pleroma/admin/users?page=${page}&local_only=${showLocalUsersOnly}`, method: 'get', headers: authHeaders(token) }) } -export async function toggleUserActivation(nickname, token) { +export async function toggleUserActivation(nickname, authHost, token) { return await request({ + baseURL: baseName(authHost), url: `/api/pleroma/admin/users/${nickname}/toggle_activation`, method: 'patch', headers: authHeaders(token) }) } -export async function searchUsers(query, showLocalUsersOnly, token, page = 1) { +export async function searchUsers(query, showLocalUsersOnly, authHost, token, page = 1) { return await request({ + baseURL: baseName(authHost), url: `/api/pleroma/admin/users?query=${query}&page=${page}&local_only=${showLocalUsersOnly}`, method: 'get', headers: authHeaders(token) }) } -export async function addRight(nickname, right, token) { +export async function addRight(nickname, right, authHost, token) { return await request({ + baseURL: baseName(authHost), url: `/api/pleroma/admin/permission_group/${nickname}/${right}`, method: 'post', headers: authHeaders(token) }) } -export async function deleteRight(nickname, right, token) { +export async function deleteRight(nickname, right, authHost, token) { return await request({ + baseURL: baseName(authHost), url: `/api/pleroma/admin/permission_group/${nickname}/${right}`, method: 'delete', headers: authHeaders(token) }) } -export async function deleteUser(nickname, token) { +export async function deleteUser(nickname, authHost, token) { return await request({ + baseURL: baseName(authHost), url: `/api/pleroma/admin/user.json?nickname=${nickname}`, method: 'delete', headers: authHeaders(token) }) } -export async function tagUser(nickname, tag, token) { +export async function tagUser(nickname, tag, authHost, token) { return await request({ + baseURL: baseName(authHost), url: '/api/pleroma/admin/users/tag', method: 'put', headers: authHeaders(token), @@ -58,8 +66,9 @@ export async function tagUser(nickname, tag, token) { }) } -export async function untagUser(nickname, tag, token) { +export async function untagUser(nickname, tag, authHost, token) { return await request({ + baseURL: baseName(authHost), url: '/api/pleroma/admin/users/tag', method: 'delete', headers: authHeaders(token), diff --git a/src/api/utils.js b/src/api/utils.js new file mode 100644 index 0000000000000000000000000000000000000000..4943f650835910a4bc6412bfcfd3cf7618334ed9 --- /dev/null +++ b/src/api/utils.js @@ -0,0 +1,5 @@ +const isLocalhost = (instanceName) => + instanceName.startsWith('localhost:') || instanceName.startsWith('127.0.0.1:') + +export const baseName = (instanceName) => + isLocalhost(instanceName) ? `http://${instanceName}` : `https://${instanceName}` diff --git a/src/lang/en.js b/src/lang/en.js index 655fb0ffdcfe14e8cca6aceabfd1142383cabaef..1c4097e73a01d7bcf8edc237bdc9a04d6c7c8f8b 100644 --- a/src/lang/en.js +++ b/src/lang/en.js @@ -76,8 +76,9 @@ export default { login: { title: 'Login Form', logIn: 'Log in', - username: 'Username', + username: 'Username@Host', password: 'Password', + errorMessage: 'Username must contain username and host, e.g. john@pleroma.social', any: 'any', thirdparty: 'Or connect with', thirdpartyTips: 'Can not be simulated on local, so please combine you own business simulation! ! !' diff --git a/src/permission.js b/src/permission.js index acd01b74f92d5cf3cb4b3369f95c8ff0e468785b..1439b1b976de50d83758947ce98ec0d4eab63416 100644 --- a/src/permission.js +++ b/src/permission.js @@ -16,7 +16,7 @@ function hasPermission(roles, permissionRoles) { const whiteList = ['/login', '/auth-redirect']// no redirect whitelist -router.beforeEach((to, from, next) => { +export const beforeEachRoute = (to, from, next) => { NProgress.start() // start progress bar if (getToken()) { // determine if there has token /* has token*/ @@ -24,12 +24,12 @@ router.beforeEach((to, from, next) => { next({ path: '/' }) NProgress.done() // if current page is dashboard will not trigger afterEach hook, so manually handle it } else { - if (store.getters.roles.length === 0) { // 判æ–当å‰ç”¨æˆ·æ˜¯å¦å·²æ‹‰å–完user_infoä¿¡æ¯ - store.dispatch('GetUserInfo').then(res => { // 拉å–user_info + if (store.getters.roles.length === 0) { + store.dispatch('GetUserInfo').then(res => { const roles = res.data.rights.admin ? ['admin'] : [] - store.dispatch('GenerateRoutes', { roles }).then(() => { // æ ¹æ®rolesæƒé™ç”Ÿæˆå¯è®¿é—®çš„路由表 - router.addRoutes(store.getters.addRouters) // 动æ€æ·»åŠ å¯è®¿é—®è·¯ç”±è¡¨ - next({ ...to, replace: true }) // hack方法 ç¡®ä¿addRouteså·²å®Œæˆ ,set the replace: true so the navigation will not leave a history record + store.dispatch('GenerateRoutes', { roles }).then(() => { + router.addRoutes(store.getters.addRouters) + next({ ...to, replace: true }) }) }).catch((err) => { store.dispatch('FedLogOut').then(() => { @@ -38,25 +38,24 @@ router.beforeEach((to, from, next) => { }) }) } else { - // 没有动æ€æ”¹å˜æƒé™çš„需求å¯ç›´æŽ¥next() åˆ é™¤ä¸‹æ–¹æƒé™åˆ¤æ– ↓ if (hasPermission(store.getters.roles, to.meta.roles)) { next() } else { next({ path: '/401', replace: true, query: { noGoBack: true }}) } - // å¯åˆ ↑ } } } else { /* has no token*/ - if (whiteList.indexOf(to.path) !== -1) { // 在å…登录白åå•ï¼Œç›´æŽ¥è¿›å…¥ + if (whiteList.indexOf(to.path) !== -1) { next() } else { - next(`/login?redirect=${to.path}`) // å¦åˆ™å…¨éƒ¨é‡å®šå‘到登录页 + next(`/login?redirect=${to.path}`) NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it } } -}) +} +router.beforeEach(beforeEachRoute) router.afterEach(() => { NProgress.done() // finish progress bar diff --git a/src/store/getters.js b/src/store/getters.js index b20f2a1c3394d3f2867461cc9482bd6c9ba441fc..cec3bce11d27c686b9a8aa1f63c221754de0c47f 100644 --- a/src/store/getters.js +++ b/src/store/getters.js @@ -15,6 +15,7 @@ const getters = { permission_routers: state => state.permission.routers, addRouters: state => state.permission.addRouters, errorLogs: state => state.errorLog.logs, - users: state => state.users.fetchedUsers + users: state => state.users.fetchedUsers, + authHost: state => state.user.authHost } export default getters diff --git a/src/store/modules/user.js b/src/store/modules/user.js index 1da451b667a371d7211a0ea673d9287f3cf11d4d..8c282e71c225612baecc03d2c6c384640b04bfd5 100644 --- a/src/store/modules/user.js +++ b/src/store/modules/user.js @@ -1,5 +1,5 @@ import { loginByUsername, getUserInfo } from '@/api/login' -import { getToken, setToken, removeToken } from '@/utils/auth' +import { getToken, setToken, removeToken, getAuthHost, setAuthHost, removeAuthHost } from '@/utils/auth' const user = { state: { @@ -8,6 +8,7 @@ const user = { status: '', code: '', token: getToken(), + authHost: getAuthHost(), name: '', avatar: '', introduction: '', @@ -44,19 +45,24 @@ const user = { }, SET_ID: (state, id) => { state.id = id + }, + SET_AUTH_HOST: (state, authHost) => { + state.authHost = authHost } }, actions: { - LoginByUsername({ commit }, userInfo) { - const username = userInfo.username.trim() + LoginByUsername({ commit, dispatch }, { username, authHost, password }) { return new Promise((resolve, reject) => { - loginByUsername(username, userInfo.password).then(response => { + loginByUsername(username, password, authHost).then(response => { const data = response.data commit('SET_TOKEN', data.access_token) + commit('SET_AUTH_HOST', authHost) setToken(data.access_token) + setAuthHost(authHost) resolve() }).catch(error => { + dispatch('addErrorLog', { message: error.message }) reject(error) }) }) @@ -64,7 +70,7 @@ const user = { GetUserInfo({ commit, state }) { return new Promise((resolve, reject) => { - getUserInfo(state.token).then(response => { + getUserInfo(state.token, state.authHost).then(response => { const data = response.data if (!data) { @@ -91,11 +97,13 @@ const user = { commit('SET_TOKEN', '') commit('SET_ROLES', []) removeToken() + removeAuthHost() }, FedLogOut({ commit }) { return new Promise(resolve => { commit('SET_TOKEN', '') removeToken() + removeAuthHost() resolve() }) } diff --git a/src/store/modules/users.js b/src/store/modules/users.js index e3bf6e638427f5e117d4374f9db74b0c4325e1ac..09854c596a884c65784d6ab2c4013e7e60ca10d9 100644 --- a/src/store/modules/users.js +++ b/src/store/modules/users.js @@ -43,14 +43,14 @@ const users = { }, actions: { async FetchUsers({ commit, state, getters }, { page }) { - const response = await fetchUsers(state.showLocalUsersOnly, getters.token, page) + const response = await fetchUsers(state.showLocalUsersOnly, getters.authHost, getters.token, page) commit('SET_LOADING', true) loadUsers(commit, page, response.data) }, async ToggleUserActivation({ commit, getters }, nickname) { - const response = await toggleUserActivation(nickname, getters.token) + const response = await toggleUserActivation(nickname, getters.authHost, getters.token) commit('SWAP_USER', response.data) }, @@ -62,7 +62,7 @@ const users = { commit('SET_LOADING', true) commit('SET_SEARCH_QUERY', query) - const response = await searchUsers(query, state.showLocalUsersOnly, getters.token, page) + const response = await searchUsers(query, state.showLocalUsersOnly, getters.authHost, getters.token, page) loadUsers(commit, page, response.data) } @@ -73,24 +73,24 @@ const users = { }, async ToggleRight({ commit, getters }, { user, right }) { user.roles[right] - ? await deleteRight(user.nickname, right, getters.token) - : await addRight(user.nickname, right, getters.token) + ? await deleteRight(user.nickname, right, getters.authHost, getters.token) + : await addRight(user.nickname, right, getters.authHost, getters.token) const updatedUser = { ...user, roles: { ...user.roles, [right]: !user.roles[right] }} commit('SWAP_USER', updatedUser) }, async DeleteUser({ commit, getters }, user) { - await deleteUser(user.nickname, getters.token) + await deleteUser(user.nickname, getters.authHost, getters.token) const updatedUser = { ...user, deactivated: true } commit('SWAP_USER', updatedUser) }, async ToggleTag({ commit, getters }, { user, tag }) { if (user.tags.includes(tag)) { - await untagUser(user.nickname, tag, getters.token) + await untagUser(user.nickname, tag, getters.authHost, getters.token) const updatedUser = { ...user, tags: user.tags.filter(userTag => userTag !== tag) } commit('SWAP_USER', updatedUser) } else { - await tagUser(user.nickname, tag, getters.token) + await tagUser(user.nickname, tag, getters.authHost, getters.token) const updatedUser = { ...user, tags: [...user.tags, tag] } commit('SWAP_USER', updatedUser) } diff --git a/src/utils/auth.js b/src/utils/auth.js index 08a43d6e299227a695497dd2d8a312096315958b..f8939a9ae1c3d7ea488b8a1762880719468aedae 100644 --- a/src/utils/auth.js +++ b/src/utils/auth.js @@ -1,6 +1,7 @@ import Cookies from 'js-cookie' const TokenKey = 'Admin-Token' +const AuthHostKey = 'Auth-Host' export function getToken() { return Cookies.get(TokenKey) @@ -13,3 +14,15 @@ export function setToken(token) { export function removeToken() { return Cookies.remove(TokenKey) } + +export function getAuthHost() { + return Cookies.get(AuthHostKey) +} + +export function setAuthHost(token) { + return Cookies.set(AuthHostKey, token) +} + +export function removeAuthHost() { + return Cookies.remove(AuthHostKey) +} diff --git a/src/utils/request.js b/src/utils/request.js index 0bf34b092fbf389ea3e2a4ba280e5343e460e0f3..5fdfef8016271a712d3b8bf42381fed1c75ba91d 100644 --- a/src/utils/request.js +++ b/src/utils/request.js @@ -3,7 +3,6 @@ import { Message } from 'element-ui' // create an axios instance const service = axios.create({ - baseURL: process.env.BASE_API, // api çš„ base_url timeout: 5000 // request timeout }) diff --git a/src/views/i18n-demo/index.vue b/src/views/i18n-demo/index.vue deleted file mode 100644 index 08861bca278112b66a28cb163055180e992a31df..0000000000000000000000000000000000000000 --- a/src/views/i18n-demo/index.vue +++ /dev/null @@ -1,143 +0,0 @@ -<template> - <div> - <el-card class="box-card" style="margin-top:40px;"> - <div slot="header" class="clearfix"> - <svg-icon icon-class="international" /> - <span style="margin-left:10px;">{{ $t('i18nView.title') }}</span> - </div> - <div> - <el-radio-group v-model="lang" size="small"> - <el-radio label="zh" border>简体ä¸æ–‡</el-radio> - <el-radio label="en" border>English</el-radio> - <el-radio label="es" border>Español</el-radio> - </el-radio-group> - <el-tag style="margin-top:15px;display:block;" type="info">{{ $t('i18nView.note') }}</el-tag> - </div> - </el-card> - - <el-row :gutter="20" style="margin:100px 15px 50px;"> - <el-col :span="12" :xs="24"> - <div class="block"> - <el-date-picker v-model="date" :placeholder="$t('i18nView.datePlaceholder')" type="date"/> - </div> - <div class="block"> - <el-select v-model="value" :placeholder="$t('i18nView.selectPlaceholder')"> - <el-option - v-for="item in options" - :key="item.value" - :label="item.label" - :value="item.value"/> - </el-select> - </div> - <div class="block"> - <el-button class="item-btn" size="small">{{ $t('i18nView.default') }}</el-button> - <el-button class="item-btn" size="small" type="primary">{{ $t('i18nView.primary') }}</el-button> - <el-button class="item-btn" size="small" type="success">{{ $t('i18nView.success') }}</el-button> - <el-button class="item-btn" size="small" type="info">{{ $t('i18nView.info') }}</el-button> - <el-button class="item-btn" size="small" type="warning">{{ $t('i18nView.warning') }}</el-button> - <el-button class="item-btn" size="small" type="danger">{{ $t('i18nView.danger') }}</el-button> - </div> - </el-col> - <el-col :span="12" :xs="24"> - <el-table :data="tableData" fit highlight-current-row border style="width: 100%"> - <el-table-column :label="$t('i18nView.tableName')" prop="name" width="100" align="center"/> - <el-table-column :label="$t('i18nView.tableDate')" prop="date" width="120" align="center"/> - <el-table-column :label="$t('i18nView.tableAddress')" prop="address"/> - </el-table> - </el-col> - </el-row> - </div> -</template> - -<script> -import local from './local' -const viewName = 'i18nView' - -export default { - name: 'I18n', - data: function() { - return { - date: '', - tableData: [{ - date: '2016-05-03', - name: 'Tom', - address: 'No. 189, Grove St, Los Angeles' - }, - { - date: '2016-05-02', - name: 'Tom', - address: 'No. 189, Grove St, Los Angeles' - }, - { - date: '2016-05-04', - name: 'Tom', - address: 'No. 189, Grove St, Los Angeles' - }, - { - date: '2016-05-01', - name: 'Tom', - address: 'No. 189, Grove St, Los Angeles' - }], - options: [], - value: '' - } - }, - computed: { - lang: { - get() { - return this.$store.state.app.language - }, - set(lang) { - this.$i18n.locale = lang - this.$store.dispatch('setLanguage', lang) - } - } - }, - watch: { - lang() { - this.setOptions() - } - }, - created() { - if (!this.$i18n.getLocaleMessage('en')[viewName]) { - this.$i18n.mergeLocaleMessage('en', local.en) - this.$i18n.mergeLocaleMessage('zh', local.zh) - this.$i18n.mergeLocaleMessage('es', local.es) - } - this.setOptions() // set default select options - }, - methods: { - setOptions() { - this.options = [ - { - value: '1', - label: this.$t('i18nView.one') - }, - { - value: '2', - label: this.$t('i18nView.two') - }, - { - value: '3', - label: this.$t('i18nView.three') - } - ] - } - } -} -</script> - -<style scoped> -.box-card { - width: 600px; - max-width: 100%; - margin: 20px auto; -} -.item-btn{ - margin-bottom: 15px; - margin-left: 0px; -} -.block { - padding: 25px; -} -</style> diff --git a/src/views/i18n-demo/local.js b/src/views/i18n-demo/local.js deleted file mode 100644 index 9b43e60533b3f4225a3de332583531bf59a997e6..0000000000000000000000000000000000000000 --- a/src/views/i18n-demo/local.js +++ /dev/null @@ -1,63 +0,0 @@ - -export default { - zh: { - i18nView: { - title: '切æ¢è¯è¨€', - note: '本项目国际化基于 vue-i18n', - datePlaceholder: '请选择日期', - selectPlaceholder: '请选择', - tableDate: '日期', - tableName: '姓å', - tableAddress: '地å€', - default: '默认按钮', - primary: '主è¦æŒ‰é’®', - success: 'æˆåŠŸæŒ‰é’®', - info: 'ä¿¡æ¯æŒ‰é’®', - warning: 'è¦å‘ŠæŒ‰é’®', - danger: 'å±é™©æŒ‰é’®', - one: '一', - two: '二', - three: '三' - } - }, - en: { - i18nView: { - title: 'Switch Language', - note: 'The internationalization of this project is based on vue-i18n', - datePlaceholder: 'Pick a day', - selectPlaceholder: 'Select', - tableDate: 'tableDate', - tableName: 'tableName', - tableAddress: 'tableAddress', - default: 'default:', - primary: 'primary', - success: 'success', - info: 'info', - warning: 'warning', - danger: 'danger', - one: 'One', - two: 'Two', - three: 'Three' - } - }, - es: { - i18nView: { - title: 'Switch Language', - note: 'The internationalization of this project is based on vue-i18n', - datePlaceholder: 'Pick a day', - selectPlaceholder: 'Select', - tableDate: 'tableDate', - tableName: 'tableName', - tableAddress: 'tableAddress', - default: 'default:', - primary: 'primary', - success: 'success', - info: 'info', - warning: 'warning', - danger: 'danger', - one: 'One', - two: 'Two', - three: 'Three' - } - } -} diff --git a/src/views/login/index.vue b/src/views/login/index.vue index c2a5d2028969a7224a44f54da683589dbcbb50d2..5ff5b857edb4ca2315fccd4a3105235a8a97d5b0 100644 --- a/src/views/login/index.vue +++ b/src/views/login/index.vue @@ -45,8 +45,13 @@ </template> <script> +import { Message } from 'element-ui' +import SvgIcon from '@/components/SvgIcon' +import i18n from '@/lang' + export default { name: 'Login', + components: { 'svg-icon': SvgIcon }, data: function() { return { loginForm: { @@ -77,12 +82,35 @@ export default { }, handleLogin() { this.loading = true - this.$store.dispatch('LoginByUsername', this.loginForm).then(() => { - this.loading = false - this.$router.push({ path: this.redirect || '/users/index' }) - }).catch(() => { + if (this.checkUsername()) { + const loginData = this.getLoginData() + this.$store.dispatch('LoginByUsername', loginData).then(() => { + this.loading = false + this.$router.push({ path: this.redirect || '/users/index' }) + }).catch(() => { + this.loading = false + }) + } else { + Message({ + message: i18n.t('login.errorMessage'), + type: 'error', + duration: 7000 + }) + this.$store.dispatch('addErrorLog', { message: i18n.t('login.errorMessage') }) this.loading = false - }) + } + }, + checkUsername() { + return this.loginForm.username.includes('@') + }, + getLoginData() { + const [username, authHost] = this.loginForm.username.split('@') + + return { + username: username.trim(), + authHost: authHost.trim(), + password: this.loginForm.password + } } } } diff --git a/test/views/layout/index.test.js b/test/views/layout/index.test.js new file mode 100644 index 0000000000000000000000000000000000000000..8e12783e6eeb95d24cc842162e44d8d40d0d6c3f --- /dev/null +++ b/test/views/layout/index.test.js @@ -0,0 +1,47 @@ +import Vuex from 'vuex' +import VueRouter from 'vue-router' +import { mount, createLocalVue, config } from '@vue/test-utils' +import Element from 'element-ui' +import Layout from '@/views/layout/Layout' +import storeConfig from './store.conf' +import routerConfig from './router.conf' +import { cloneDeep } from 'lodash' +import { beforeEachRoute } from '@/permission' + +config.mocks["$t"] = () => {} + +const localVue = createLocalVue() +localVue.use(Vuex) +localVue.use(VueRouter) +localVue.use(Element) + +describe('Log out', () => { + let store + let router + + beforeEach(() => { + store = new Vuex.Store(cloneDeep(storeConfig)) + router = new VueRouter(cloneDeep(routerConfig)) + router.beforeEach(beforeEachRoute) + window.location.reload = jest.fn() + }) + + it('logs out user', async (done) => { + const wrapper = mount(Layout, { + store, + router, + localVue + }) + + const logoutButton = wrapper.find('span') + expect(store.state.user.roles.length).toBe(1) + expect(store.state.user.token).toBe('foo123') + + logoutButton.trigger('click') + await wrapper.vm.$nextTick() + + expect(store.state.user.roles.length).toBe(0) + expect(store.state.user.token).toBe('') + done() + }) +}) diff --git a/test/views/layout/router.conf.js b/test/views/layout/router.conf.js new file mode 100644 index 0000000000000000000000000000000000000000..fcc4c4130b4f34017c8bc5499e8baf1df75f0d8d --- /dev/null +++ b/test/views/layout/router.conf.js @@ -0,0 +1,40 @@ +import Layout from '@/views/layout/Layout' + +export const constantRouterMap = [ + { + path: '/redirect', + component: Layout, + hidden: true, + children: [ + { + path: '/redirect/:path*', + component: () => import('@/views/redirect/index') + } + ] + }, + { + path: '/login', + component: () => import('@/views/login/index'), + hidden: true + }, + { + path: '/auth-redirect', + component: () => import('@/views/login/authredirect'), + hidden: true + }, + { + path: '/404', + component: () => import('@/views/errorPage/404'), + hidden: true + }, + { + path: '/401', + component: () => import('@/views/errorPage/401'), + hidden: true + }, + { + path: '', + component: Layout, + redirect: '/users/index' + } +] diff --git a/test/views/layout/store.conf.js b/test/views/layout/store.conf.js new file mode 100644 index 0000000000000000000000000000000000000000..1bc8bd04821e714b3ab28a647dd3239b847adda5 --- /dev/null +++ b/test/views/layout/store.conf.js @@ -0,0 +1,34 @@ +import app from '@/store/modules/app' +import errorLog from '@/store/modules/errorLog' +import permission from '@/store/modules/permission' +import tagsView from '@/store/modules/tagsView' +import user from '@/store/modules/user' +import users from '@/store/modules/users' +import getters from '@/store/getters' + +export default { + modules: { + app, + errorLog, + permission, + tagsView, + user: { + ...user, + state: { + id:"10", + status: '', + code: '', + token: "foo123", + authHost:"apple", + name:"bob", + avatar: '', + introduction: '', + roles: ['admin'], + setting: { + articlePlatform: [] + } + } + } + }, + getters +} diff --git a/test/views/login/index.test.js b/test/views/login/index.test.js new file mode 100644 index 0000000000000000000000000000000000000000..17ac438f301562da39c715184279917f6c58f09b --- /dev/null +++ b/test/views/login/index.test.js @@ -0,0 +1,106 @@ +import Vuex from 'vuex' +import VueRouter from 'vue-router' +import { mount, createLocalVue, config } from '@vue/test-utils' +import flushPromises from 'flush-promises' +import Element from 'element-ui' +import Login from '@/views/login/index' +import storeConfig from './store.conf' +import routerConfig from './router.conf' +import { cloneDeep } from 'lodash' +import { beforeEachRoute } from '@/permission' + +config.mocks["$t"] = () => {} + +const localVue = createLocalVue() +localVue.use(Vuex) +localVue.use(VueRouter) +localVue.use(Element) + +jest.mock('@/api/login') + +describe('Login', () => { + let store + let router + + const usernameInput = 'input[name="username"]' + const passwordInput = 'input[name="password"]' + + beforeEach(() => { + store = new Vuex.Store(cloneDeep(storeConfig)) + router = new VueRouter(cloneDeep(routerConfig)) + router.beforeEach(beforeEachRoute) + }) + + it('throws error if username does not contain authHost', () => { + const wrapper = mount(Login, { + store, + router, + localVue + }) + + const errorLog = store.state.errorLog.logs + expect(errorLog.length).toBe(0) + const submitButton = wrapper.find('button') + + wrapper.find(usernameInput).element.value = 'bob' + wrapper.find(usernameInput).trigger('input') + wrapper.find(passwordInput).element.value = '1234' + wrapper.find(passwordInput).trigger('input') + submitButton.trigger('click') + + const updatedErrorLog = store.state.errorLog.logs + expect(updatedErrorLog.length).toBe(1) + expect(updatedErrorLog[0].message).toEqual( + 'Username must contain username and host, e.g. john@pleroma.social' + ) + }) + + it('throws error if password is incorrect', async (done) => { + const wrapper = mount(Login, { + store, + router, + localVue + }) + + const errorLog = store.state.errorLog.logs + expect(errorLog.length).toBe(0) + const submitButton = wrapper.find('button') + + wrapper.find(usernameInput).element.value = 'bob@apple' + wrapper.find(usernameInput).trigger('input') + wrapper.find(passwordInput).element.value = '1234' + wrapper.find(passwordInput).trigger('input') + submitButton.trigger('click') + await flushPromises() + + const updatedErrorLog = store.state.errorLog.logs + expect(updatedErrorLog.length).toBe(1) + expect(updatedErrorLog[0].message).toEqual( + 'Invalid credentials' + ) + done() + }) + + it('logs user in', async (done) => { + const wrapper = mount(Login, { + store, + router, + localVue + }) + + const errorLog = store.state.errorLog.logs + const submitButton = wrapper.find('button') + expect(wrapper.vm.$route.path).toBe('/login') + + wrapper.find(usernameInput).element.value = 'bob@apple' + wrapper.find(usernameInput).trigger('input') + wrapper.find(passwordInput).element.value = '123456' + wrapper.find(passwordInput).trigger('input') + submitButton.trigger('click') + await flushPromises() + + expect(errorLog.length).toBe(0) + expect(wrapper.vm.$route.path).toBe('/') + done() + }) +}) diff --git a/test/views/login/router.conf.js b/test/views/login/router.conf.js new file mode 100644 index 0000000000000000000000000000000000000000..fcc4c4130b4f34017c8bc5499e8baf1df75f0d8d --- /dev/null +++ b/test/views/login/router.conf.js @@ -0,0 +1,40 @@ +import Layout from '@/views/layout/Layout' + +export const constantRouterMap = [ + { + path: '/redirect', + component: Layout, + hidden: true, + children: [ + { + path: '/redirect/:path*', + component: () => import('@/views/redirect/index') + } + ] + }, + { + path: '/login', + component: () => import('@/views/login/index'), + hidden: true + }, + { + path: '/auth-redirect', + component: () => import('@/views/login/authredirect'), + hidden: true + }, + { + path: '/404', + component: () => import('@/views/errorPage/404'), + hidden: true + }, + { + path: '/401', + component: () => import('@/views/errorPage/401'), + hidden: true + }, + { + path: '', + component: Layout, + redirect: '/users/index' + } +] diff --git a/test/views/login/store.conf.js b/test/views/login/store.conf.js new file mode 100644 index 0000000000000000000000000000000000000000..69bde7603f10733efc1fa3138fd0a77d019cf098 --- /dev/null +++ b/test/views/login/store.conf.js @@ -0,0 +1,16 @@ +import app from '@/store/modules/app' +import errorLog from '@/store/modules/errorLog' +import user from '@/store/modules/user' +import users from '@/store/modules/users' +import getters from '@/store/getters' + +export default { + modules: { + app, + errorLog, + user, + users + }, + getters +} + diff --git a/test/views/users/store.conf.js b/test/views/users/store.conf.js index be9aabb7f6e59813b4a57632068d08df44dd55b0..77e2b7c8b7e2265c486d6c67e0ae07150fac76d9 100644 --- a/test/views/users/store.conf.js +++ b/test/views/users/store.conf.js @@ -1,28 +1,13 @@ -import getters from '@/store/getters' import app from '@/store/modules/app' import user from '@/store/modules/user' import users from '@/store/modules/users' +import getters from '@/store/getters' export default { modules: { app, - users, - user: { - state: { - user: '', - id: '1', - status: '', - code: '', - token: 'MmwSkMgBW6lAkWCFspIjX9icmLfqSCohSi-GReAZrQw', - name: 'john', - avatar: '', - introduction: '', - roles: ["admin"], - setting: { - articlePlatform: [] - } - }, ...user - } + user, + users }, getters } diff --git a/yarn.lock b/yarn.lock index c5460595d4eb08ff7ffc355a836b8a7085f68f5b..a4c90db284ee6944511b1d130a5f67d76fd8dcf5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4181,6 +4181,11 @@ flat-cache@^1.2.1: rimraf "~2.6.2" write "^0.2.1" +flush-promises@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/flush-promises/-/flush-promises-1.0.2.tgz#4948fd58f15281fed79cbafc86293d5bb09b2ced" + integrity sha512-G0sYfLQERwKz4+4iOZYQEZVpOt9zQrlItIxQAAYAWpfby3gbHrx0osCHz5RLl/XoXevXk0xoN4hDFky/VV9TrA== + flush-write-stream@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" @@ -10164,18 +10169,6 @@ vue-jest@4.0.0-beta.2: source-map "^0.5.6" ts-jest "^23.10.5" -vue-jest@4.0.0-beta.2: - version "4.0.0-beta.2" - resolved "https://registry.yarnpkg.com/vue-jest/-/vue-jest-4.0.0-beta.2.tgz#f2120ea9d24224aad3a100c2010b0760d47ee6fe" - integrity sha512-SywBIciuIfqsCb8Eb9UQ02s06+NV8Ry8KnbyhAfnvnyFFulIuh7ujtga9eJYq720nCS4Hz4TpVtS4pD1ZbUILQ== - dependencies: - "@babel/plugin-transform-modules-commonjs" "^7.2.0" - "@vue/component-compiler-utils" "^2.4.0" - chalk "^2.1.0" - extract-from-css "^0.4.4" - source-map "^0.5.6" - ts-jest "^23.10.5" - vue-loader@15.3.0: version "15.3.0" resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.3.0.tgz#b474d10a4e93d934a78c147fc3e314b370e9fc54"