Skip to content

Commit

Permalink
Merge branch 'dev.passport'
Browse files Browse the repository at this point in the history
重构了登录接口,将接口的定义放在接口的实现中。
  • Loading branch information
caixw committed Dec 1, 2024
2 parents d8ede56 + 6483cbf commit 000c4ed
Show file tree
Hide file tree
Showing 90 changed files with 2,377 additions and 1,925 deletions.
13 changes: 7 additions & 6 deletions admin/src/app/context/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { JSX, createContext, createResource, createSignal, useContext } from 'so

import { Options as buildOptions } from '@/app/options';
import { NotifyType } from '@/components/notify';
import { API, Account, Config, Locale, Method, Problem, Theme, UnitStyle, notify } from '@/core';
import { API, Config, Locale, Method, Problem, Return, Theme, UnitStyle, notify } from '@/core';
import { Token } from '@/core/api/token';
import { User } from './user';

type Options = Required<buildOptions>;
Expand Down Expand Up @@ -178,16 +179,16 @@ export function buildContext(opt: Required<buildOptions>, f: API) {
},

/**
* 执行登录操作并刷新 user
* 设置登录状态并刷新 user
* @param account 账号密码信息
* @returns true 表示登录成功,其它情况表示错误信息
*/
async login(account: Account,type: string = 'password') {
const ret = await f.login(account, type);
async login(r: Return<Token,never>) {
const ret = await f.login(r);
if (ret === true) {
uid = account.username;
sessionStorage.setItem(currentKey, uid);
await userData.refetch();
uid = this.user()!.id!.toString();
sessionStorage.setItem(currentKey, uid);
await localeData.refetch();
}
return ret;
Expand Down
14 changes: 11 additions & 3 deletions admin/src/app/context/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,16 @@
*/
export interface User {
id?: number;
sex?: 'unknown' | 'male' | 'female';
name?: string;
nickname?: string;
sex: 'male' | 'female' | 'unknown';
state: 'normal' | 'locked' | 'deleted';
name: string;
nickname: string;
avatar?: string;
roles?: Array<string>;
passports?: Array<Passport>;
}

interface Passport {
id: string;
identity: string;
}
2 changes: 1 addition & 1 deletion admin/src/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ export { create as createApp } from './app';
export type { MenuItem, Options, Route, Routes } from './options';

export { useApp } from './context';
export type { AppContext } from './context';
export type { AppContext, User } from './context';

9 changes: 6 additions & 3 deletions admin/src/components/table/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@
@apply text-center w-full text-xl py-10;
}

tr {
@apply border-b border-palette-fg-low;
}

th,
td {
@apply px-2 py-1 text-left border-b border-palette-fg-low;
@apply px-2 py-1 text-left;
}

tbody tr:last-of-type th,
tbody tr:last-of-type td {
tbody tr:last-of-type {
@apply border-0;
}

Expand Down
6 changes: 5 additions & 1 deletion admin/src/core/api/api.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,11 @@ describe('API token', () => {
test('login', async () => {
const f = await API.build('http://localhost', '/login', 'application/json', 'zh-cn');
fetchMock.mockResponseOnce(JSON.stringify(Object.assign({}, token)));
const ret = await f.login({ username: 'admin', password: '123' }, 'password');
const ret = await f.login({
status: 201,
ok: true,
body: { access_token: 'access', refresh_token: 'refresh', access_exp: 12345, refresh_exp: 12345 },
});
expect(ret).toBeTruthy();

let t = await f.getToken();
Expand Down
30 changes: 14 additions & 16 deletions admin/src/core/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import { CacheImplement } from './cache';
import type { Mimetype, Serializer } from './serializer';
import { serializers } from './serializer';
import { delToken, getToken, state, Token, TokenState, writeToken } from './token';
import { Account, Method, Problem, Query, Return } from './types';
import { Method, Problem, Query, Return } from './types';

/**
* 封装了 API 访问的基本功能
*/
export class API {
readonly #baseURL: string;
readonly #loginPath: string;
readonly #tokenPath: string;
#locale: string;
#token: Token | undefined;

Expand All @@ -28,10 +28,10 @@ export class API {
*
* @param baseURL API 的基地址,不能以 / 结尾。
* @param mimetype mimetype 的类型。
* @param loginPath 相对于 baseURL 的登录地址,该地址应该包含 POST、DELETE 和 PUT 三个请求,分别代表登录、退出和刷新令牌
* @param tokenPath 相对于 baseURL 的登录地址,该地址应该包含 DELETE 和 PUT 三个请求,分别代表退出和刷新令牌
* @param locale 请求报头 accept-language 的内容。
*/
static async build(baseURL: string, loginPath: string, mimetype: Mimetype, locale: string): Promise<API> {
static async build(baseURL: string, tokenPath: string, mimetype: Mimetype, locale: string): Promise<API> {
const t = getToken();

let c: Cache;
Expand All @@ -41,17 +41,17 @@ export class API {
console.warn('非 HTTP 环境,无法启用 API 缓存功能!');
c = new CacheImplement();
}
return new API(baseURL, loginPath, mimetype, locale, c, t);
return new API(baseURL, tokenPath, mimetype, locale, c, t);
}

private constructor(baseURL: string, loginPath: string, mimetype: Mimetype, locale: string, cache: Cache, token: Token | undefined) {
private constructor(baseURL: string, tokenPath: string, mimetype: Mimetype, locale: string, cache: Cache, token: Token | undefined) {
const s = serializers.get(mimetype);
if (!s) {
throw `不支持的 contentType ${mimetype}`;
}

this.#baseURL = baseURL;
this.#loginPath = loginPath;
this.#tokenPath = tokenPath;
this.#locale = locale;
this.#token = token;

Expand Down Expand Up @@ -197,26 +197,24 @@ export class API {
}

/**
* 执行登录操作
* 设置登录状态
*
* @returns 如果返回 true,表示操作成功,否则表示错误信息。
*/
async login(account: Account, type: string): Promise<Problem<never>|undefined|true> {
const token = await this.post<Token>(this.#loginPath + '?type='+type, account, false);
if (token.ok) {
this.#token = writeToken(token.body!);
async login(ret: Return<Token, never>): Promise<Problem<never>|undefined|true> {
if (ret.ok) {
this.#token = writeToken(ret.body!);
await this.clearCache();
return true;
}

return token.body;
return ret.body;
}

/**
* 退出当前的登录状态
*/
async logout() {
await this.delete(this.#loginPath);
await this.delete(this.#tokenPath);
this.#token = undefined;
delToken();
await this.clearCache();
Expand Down Expand Up @@ -246,7 +244,7 @@ export class API {
return undefined;
case TokenState.AccessExpired: // 尝试刷新令牌
{ //大括号的作用是防止 case 内部的变量 ret 提升作用域!
const ret = await this.withArgument<Token>(this.#loginPath, 'PUT', this.#token.refresh_token, this.#contentType);
const ret = await this.withArgument<Token>(this.#tokenPath, 'PUT', this.#token.refresh_token, this.#contentType);
if (!ret.ok) {
return undefined;
}
Expand Down
8 changes: 0 additions & 8 deletions admin/src/core/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,6 @@ export type Return<R, PE> = {
ok: true;
};

/**
* 登录接口的需要用户提供的对象
*/
export interface Account {
username: string;
password: string;
}

/**
* 分页接口返回的对象
*/
Expand Down
13 changes: 8 additions & 5 deletions admin/src/messages/cmn-Hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const messages: Messages = {
login: '登录',
username: '账号',
password: '密码',
code: '验证码',
loggingOut: '正在退出...',

securitylog: '安全日志',
Expand All @@ -57,10 +58,12 @@ const messages: Messages = {
profile: '个人信息',
name: '姓名',
nickname: '昵称',
pickAvatar: '选择头像',
requestCode: '发送验证码',
delete: '删除',
changePassword: '更改密码',
oldPassword: '旧密码',
newPassword: '新密码',
confirmPassword: '确认新密码',
pickAvatar: '选择头像',
},
system: {
apis: 'API',
Expand Down Expand Up @@ -116,12 +119,12 @@ const messages: Messages = {
adminsManager: '管理员',
name: '姓名',
nickname: '昵称',
resetPassword: '重置密码',
addSuccessful: '添加成功',
areYouSureResetPassword: '确定要重置用户的登录密码?',
successfullyResetPassword: '重置密码成功',
lockUser: '锁定该用户',
unlockUser: '解锁该用户',

passport: '登录方式',
passportTtype: '类型',
},
roles: {
roles: '角色',
Expand Down
13 changes: 8 additions & 5 deletions admin/src/messages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const messages = {
login: 'login',
username: 'username',
password: 'password',
code: 'code',
loggingOut: 'logging out ...',

securitylog: 'security logs',
Expand All @@ -55,10 +56,12 @@ const messages = {
profile: 'profle',
name: 'name',
nickname: 'nickname',
pickAvatar: 'pick avatar',
requestCode: 'request code',
delete: 'delete',
changePassword: 'change password',
oldPassword: 'old password',
newPassword: 'new password',
confirmPassword: 'confirm password',
pickAvatar: 'pick avatar',
},
system: {
apis: 'API',
Expand Down Expand Up @@ -114,12 +117,12 @@ const messages = {
adminsManager: 'admin manager',
name: 'name',
nickname: 'nick name',
resetPassword: 'Reset password',
addSuccessful: 'Add user successful',
areYouSureResetPassword: 'Are you sure reset user password',
successfullyResetPassword: 'Successfully reset password',
lockUser: 'Lock user',
unlockUser: 'unlock user',

passport: 'passport',
passportTtype: 'type',
},
roles: {
roles: 'roles',
Expand Down
54 changes: 30 additions & 24 deletions admin/src/pages/admins/admins.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
import { JSX, Show } from 'solid-js';

import { useApp } from '@/app';
import {
Button, ConfirmButton, LinkButton, Page,
RemoteTable, RemoteTableRef, TextField, translateEnum
} from '@/components';
import { SexSelector, StateSelector } from './selector';
import type { Admin, Query, Sex, State } from './types';
import { sexesMap, statesMap } from './types';
import { Button, LinkButton, Page, RemoteTable, RemoteTableRef, TextField, translateEnum } from '@/components';
import { Query as QueryBase } from '@/core';
import { Sex, sexesMap, SexSelector, State, StateSelector, statesMap } from './selector';

export interface Query extends QueryBase {
text?: string;
state?: Array<State>;
sex?: Array<Sex>;
}

interface Props {
/**
Expand All @@ -32,7 +34,7 @@ export default function(props: Props): JSX.Element {
const q: Q = {
text: '',
page: 1,
state: ['normal'],
state: ['normal','locked'],
sex: ['male', 'female', 'unknown']
};

Expand Down Expand Up @@ -66,11 +68,13 @@ export default function(props: Props): JSX.Element {
{
id: 'actions', label: ctx.locale().t('_i.page.actions'), isUnexported: true, renderContent: ((_, __, obj?: Admin) => {
return <div class="flex gap-x-2">
<LinkButton icon rounded palette='tertiary'
href={`${props.routePrefix}/${obj!['id']}`}
title={ctx.locale().t('_i.page.editItem')}>edit</LinkButton>
<Show when={obj?.state !== 'deleted'}>
<LinkButton icon rounded palette='tertiary'
href={`${props.routePrefix}/${obj!['id']}`}
title={ctx.locale().t('_i.page.editItem')}>edit</LinkButton>
</Show>

<Show when={obj?.state !== 'locked'}>
<Show when={obj?.state !== 'locked' && obj?.state!=='deleted'}>
<Button icon rounded palette='error' title={ctx.locale().t('_i.page.admin.lockUser')} onClick={async()=>{
const r = await ctx.api.post(`/admins/${obj!['id']}/locked`);
if (!r.ok) {
Expand All @@ -92,21 +96,23 @@ export default function(props: Props): JSX.Element {
}}>lock_open_right</Button>
</Show>

<ConfirmButton icon rounded palette='error' title={ctx.locale().t('_i.page.admin.resetPassword')}
prompt={ctx.locale().t('_i.page.admin.areYouSureResetPassword')}
onClick={async()=>{
const r = await ctx.api.delete(`/admins/${obj!['id']}/password`);
if (!r.ok) {
ctx.outputProblem(r.body);
return;
}
ctx.notify(ctx.locale().t('_i.page.admin.successfullyResetPassword'), undefined, 'success');
}}>lock_reset</ConfirmButton>

{ref.DeleteAction(obj!.id!)}
<Show when={obj?.state !== 'deleted'}>
{ref.DeleteAction(obj!.id!)}
</Show>
</div>;
})
},
]} />
</Page>;
}

export interface Admin {
id?: number;
no?: string;
sex: Sex;
name: string;
nickname: string;
avatar?: string;
created?: string;
state: State;
}
Loading

0 comments on commit 000c4ed

Please sign in to comment.