mirror of
https://github.com/balzack/databag.git
synced 2025-02-12 03:29:16 +00:00
support registry listing option
This commit is contained in:
parent
318ea81e7e
commit
bf21af0a7d
18
doc/api.oa3
18
doc/api.oa3
@ -390,8 +390,13 @@ paths:
|
|||||||
- account
|
- account
|
||||||
description: Get disabled status of account. Authorized to account username and password.
|
description: Get disabled status of account. Authorized to account username and password.
|
||||||
operationId: get-account-status
|
operationId: get-account-status
|
||||||
security:
|
parameters:
|
||||||
- bearerAuth: []
|
- name: agent
|
||||||
|
in: query
|
||||||
|
description: agent token
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: successful operation
|
description: successful operation
|
||||||
@ -410,8 +415,13 @@ paths:
|
|||||||
- account
|
- account
|
||||||
description: Set whether account is publicly listed.
|
description: Set whether account is publicly listed.
|
||||||
operationId: set-account-seachable
|
operationId: set-account-seachable
|
||||||
security:
|
parameters:
|
||||||
- bearerAuth: []
|
- name: agent
|
||||||
|
in: query
|
||||||
|
description: agent token
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
responses:
|
responses:
|
||||||
'201':
|
'201':
|
||||||
description: success
|
description: success
|
||||||
|
@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
func GetAccountStatus(w http.ResponseWriter, r *http.Request) {
|
func GetAccountStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
account, code, err := BearerAppToken(r, false)
|
account, code, err := ParamAgentToken(r, true);
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrResponse(w, code, err)
|
ErrResponse(w, code, err)
|
||||||
return
|
return
|
||||||
|
@ -2,12 +2,13 @@ package databag
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"gorm.io/gorm"
|
||||||
"databag/internal/store"
|
"databag/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SetAccountSearchable(w http.ResponseWriter, r *http.Request) {
|
func SetAccountSearchable(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
account, code, err := BearerAppToken(r, false)
|
account, code, err := ParamAgentToken(r, true);
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrResponse(w, code, err)
|
ErrResponse(w, code, err)
|
||||||
return
|
return
|
||||||
@ -19,11 +20,22 @@ func SetAccountSearchable(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = store.DB.Model(account).Update("searchable", flag).Error; err != nil {
|
err = store.DB.Transaction(func(tx *gorm.DB) error {
|
||||||
|
if res := tx.Model(account).Update("searchable", flag).Error; res != nil {
|
||||||
|
ErrResponse(w, http.StatusInternalServerError, res)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
if res := tx.Model(&account).Update("account_revision", account.AccountRevision + 1).Error; res != nil {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
ErrResponse(w, http.StatusInternalServerError, err)
|
ErrResponse(w, http.StatusInternalServerError, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SetStatus(account)
|
||||||
WriteResponse(w, nil)
|
WriteResponse(w, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,6 +104,7 @@ func Status(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
func getRevision(account *store.Account) Revision {
|
func getRevision(account *store.Account) Revision {
|
||||||
var r Revision
|
var r Revision
|
||||||
|
r.Account = account.AccountRevision
|
||||||
r.Profile = account.ProfileRevision
|
r.Profile = account.ProfileRevision
|
||||||
r.Article = account.ArticleRevision
|
r.Article = account.ArticleRevision
|
||||||
r.Channel = account.ChannelRevision
|
r.Channel = account.ChannelRevision
|
||||||
|
@ -361,6 +361,8 @@ type ProfileData struct {
|
|||||||
|
|
||||||
type Revision struct {
|
type Revision struct {
|
||||||
|
|
||||||
|
Account int64 `json:"account"`
|
||||||
|
|
||||||
Profile int64 `json:"profile"`
|
Profile int64 `json:"profile"`
|
||||||
|
|
||||||
Article int64 `json:"article"`
|
Article int64 `json:"article"`
|
||||||
|
@ -60,6 +60,7 @@ type Account struct {
|
|||||||
Guid string `gorm:"not null;uniqueIndex"`
|
Guid string `gorm:"not null;uniqueIndex"`
|
||||||
Username string `gorm:"not null;uniqueIndex"`
|
Username string `gorm:"not null;uniqueIndex"`
|
||||||
Password []byte `gorm:"not null"`
|
Password []byte `gorm:"not null"`
|
||||||
|
AccountRevision int64 `gorm:"not null;default:1"`
|
||||||
ProfileRevision int64 `gorm:"not null;default:1"`
|
ProfileRevision int64 `gorm:"not null;default:1"`
|
||||||
ArticleRevision int64 `gorm:"not null;default:1"`
|
ArticleRevision int64 `gorm:"not null;default:1"`
|
||||||
GroupRevision int64 `gorm:"not null;default:1"`
|
GroupRevision int64 `gorm:"not null;default:1"`
|
||||||
|
@ -40,6 +40,17 @@ export async function setLogin(username, password) {
|
|||||||
return await login.json()
|
return await login.json()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function setAccountSearchable(token, flag) {
|
||||||
|
let res = await fetchWithTimeout('/account/searchable?agent=' + token, { method: 'PUT', body: JSON.stringify(flag), timeout: FETCH_TIMEOUT })
|
||||||
|
checkResponse(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAccountStatus(token) {
|
||||||
|
let status = await fetchWithTimeout('/account/status?agent=' + token, { method: 'GET', timeout: FETCH_TIMEOUT });
|
||||||
|
checkResponse(status);
|
||||||
|
return await status.json()
|
||||||
|
}
|
||||||
|
|
||||||
export async function createAccount(username, password) {
|
export async function createAccount(username, password) {
|
||||||
let headers = new Headers()
|
let headers = new Headers()
|
||||||
headers.append('Credentials', 'Basic ' + base64.encode(username + ":" + password));
|
headers.append('Credentials', 'Basic ' + base64.encode(username + ":" + password));
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
import { useEffect, useState, useRef } from 'react';
|
import { useEffect, useState, useRef } from 'react';
|
||||||
import { setProfileImage, setProfileData, getProfileImageUrl, getProfile, getGroups, getAvailable, getUsername, setLogin, createAccount } from './fetchUtil';
|
import { setProfileImage, setProfileData, getProfileImageUrl, getAccountStatus, setAccountSearchable, getProfile, getGroups, getAvailable, getUsername, setLogin, createAccount } from './fetchUtil';
|
||||||
|
|
||||||
|
async function updateAccount(token, updateData) {
|
||||||
|
let status = await getAccountStatus(token);
|
||||||
|
console.log(status);
|
||||||
|
updateData({ status: status });
|
||||||
|
}
|
||||||
|
|
||||||
async function updateProfile(token, updateData) {
|
async function updateProfile(token, updateData) {
|
||||||
let profile = await getProfile(token);
|
let profile = await getProfile(token);
|
||||||
@ -41,10 +47,10 @@ export function useAppContext() {
|
|||||||
const [state, setState] = useState(null);
|
const [state, setState] = useState(null);
|
||||||
|
|
||||||
const groupRevision = useRef(null);
|
const groupRevision = useRef(null);
|
||||||
const groups = useRef(new Map());
|
const accountRevision = useRef(null);
|
||||||
|
|
||||||
const profileRevision = useRef(null);
|
const profileRevision = useRef(null);
|
||||||
const profile = useRef({});
|
|
||||||
|
const groups = useRef(new Map());
|
||||||
|
|
||||||
const ws = useRef(null);
|
const ws = useRef(null);
|
||||||
const revision = useRef(null);
|
const revision = useRef(null);
|
||||||
@ -60,10 +66,10 @@ export function useAppContext() {
|
|||||||
|
|
||||||
const resetData = () => {
|
const resetData = () => {
|
||||||
revision.current = null;
|
revision.current = null;
|
||||||
profile.current = {};
|
|
||||||
profileRevision.current = null;
|
profileRevision.current = null;
|
||||||
groups.current = new Map();
|
|
||||||
groupRevision.current = null;
|
groupRevision.current = null;
|
||||||
|
groups.current = new Map();
|
||||||
|
setState(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
const userActions = {
|
const userActions = {
|
||||||
@ -77,6 +83,9 @@ export function useAppContext() {
|
|||||||
setProfileImage: async (image) => {
|
setProfileImage: async (image) => {
|
||||||
await setProfileImage(state.token, image);
|
await setProfileImage(state.token, image);
|
||||||
},
|
},
|
||||||
|
setAccountSearchable: async (flag) => {
|
||||||
|
await setAccountSearchable(state.token, flag);
|
||||||
|
},
|
||||||
profileImageUrl: () => getProfileImageUrl(state.token, state.Data?.profile?.revision)
|
profileImageUrl: () => getProfileImageUrl(state.token, state.Data?.profile?.revision)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,6 +123,12 @@ export function useAppContext() {
|
|||||||
profileRevision.current = rev.profile
|
profileRevision.current = rev.profile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update account status if revision changed
|
||||||
|
if (rev.account != accountRevision.current) {
|
||||||
|
await updateAccount(token, updateData)
|
||||||
|
accountRevision.current = rev.account
|
||||||
|
}
|
||||||
|
|
||||||
// check if new revision was received during processing
|
// check if new revision was received during processing
|
||||||
if (rev == revision.current) {
|
if (rev == revision.current) {
|
||||||
revision.current = null
|
revision.current = null
|
||||||
@ -165,7 +180,6 @@ export function useAppContext() {
|
|||||||
const session = JSON.parse(storage)
|
const session = JSON.parse(storage)
|
||||||
if (session?.access === 'admin') {
|
if (session?.access === 'admin') {
|
||||||
setState({ token: session.token, access: session.access })
|
setState({ token: session.token, access: session.access })
|
||||||
setWebsocket(session.token);
|
|
||||||
} else if (session?.access === 'user') {
|
} else if (session?.access === 'user') {
|
||||||
setState({ token: session.token, access: session.access })
|
setState({ token: session.token, access: session.access })
|
||||||
setWebsocket(session.token);
|
setWebsocket(session.token);
|
||||||
|
@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef } from 'react'
|
|||||||
import { EditIcon, ProfileWrapper, CloseButton, ModalFooter, SelectButton } from './Profile.styled';
|
import { EditIcon, ProfileWrapper, CloseButton, ModalFooter, SelectButton } from './Profile.styled';
|
||||||
import { UserOutlined, CloseOutlined, EditOutlined } from '@ant-design/icons';
|
import { UserOutlined, CloseOutlined, EditOutlined } from '@ant-design/icons';
|
||||||
import { useProfile } from './useProfile.hook';
|
import { useProfile } from './useProfile.hook';
|
||||||
import { Button, Modal } from 'antd'
|
import { Button, Checkbox, Modal } from 'antd'
|
||||||
import { ProfileInfo } from './ProfileInfo/ProfileInfo';
|
import { ProfileInfo } from './ProfileInfo/ProfileInfo';
|
||||||
import { ProfileImage } from './ProfileImage/ProfileImage';
|
import { ProfileImage } from './ProfileImage/ProfileImage';
|
||||||
|
|
||||||
@ -68,6 +68,10 @@ export function Profile(props) {
|
|||||||
reader.readAsDataURL(e.target.files[0]);
|
reader.readAsDataURL(e.target.files[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onSearchable = (flag) => {
|
||||||
|
actions.setSearchable(flag);
|
||||||
|
}
|
||||||
|
|
||||||
const Footer = (
|
const Footer = (
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<input type='file' id='file' accept="image/*" ref={imageFile} onChange={e => selected(e)} style={{display: 'none'}}/>
|
<input type='file' id='file' accept="image/*" ref={imageFile} onChange={e => selected(e)} style={{display: 'none'}}/>
|
||||||
@ -87,6 +91,10 @@ export function Profile(props) {
|
|||||||
</div>
|
</div>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="profile">
|
<div class="profile">
|
||||||
|
<div class="registry">
|
||||||
|
<span class="search">Listed in Registry</span>
|
||||||
|
<Checkbox checked={state.searchable} onChange={(e) => onSearchable(e.target.checked)} />
|
||||||
|
</div>
|
||||||
<div class="avatar" onClick={() => setLogoVisible(true)}>
|
<div class="avatar" onClick={() => setLogoVisible(true)}>
|
||||||
<Logo />
|
<Logo />
|
||||||
<div class="logoedit">
|
<div class="logoedit">
|
||||||
|
@ -56,6 +56,16 @@ export const ProfileWrapper = styled.div`
|
|||||||
flex: 3
|
flex: 3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.registry {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search {
|
||||||
|
padding-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -10,6 +10,7 @@ export function useProfile() {
|
|||||||
description: '',
|
description: '',
|
||||||
location: '',
|
location: '',
|
||||||
imageUrl: null,
|
imageUrl: null,
|
||||||
|
searchable: false,
|
||||||
modalBusy: false,
|
modalBusy: false,
|
||||||
modalName: '',
|
modalName: '',
|
||||||
modalLocation: '',
|
modalLocation: '',
|
||||||
@ -59,6 +60,14 @@ export function useProfile() {
|
|||||||
}
|
}
|
||||||
return set
|
return set
|
||||||
},
|
},
|
||||||
|
setSearchable: async (flag) => {
|
||||||
|
try {
|
||||||
|
await app.actions.setAccountSearchable(flag);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
window.alert(err);
|
||||||
|
}
|
||||||
|
},
|
||||||
setProfileImage: async () => {
|
setProfileImage: async () => {
|
||||||
let set = false
|
let set = false
|
||||||
if(!state.modalBusy) {
|
if(!state.modalBusy) {
|
||||||
@ -70,10 +79,10 @@ export function useProfile() {
|
|||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
var canvas = document.createElement("canvas");
|
var canvas = document.createElement("canvas");
|
||||||
var context = canvas.getContext('2d');
|
var context = canvas.getContext('2d');
|
||||||
canvas.width = state.crop.w;
|
canvas.width = 256;
|
||||||
canvas.height = state.crop.h;
|
canvas.height = 256;
|
||||||
context.drawImage(img, state.crop.x, state.crop.y, state.crop.w, state.crop.h,
|
context.drawImage(img, state.crop.x, state.crop.y, state.crop.w, state.crop.h,
|
||||||
0, 0, state.crop.w, state.crop.h);
|
0, 0, 256, 256);
|
||||||
resolve(canvas.toDataURL());
|
resolve(canvas.toDataURL());
|
||||||
}
|
}
|
||||||
img.onerror = reject;
|
img.onerror = reject;
|
||||||
@ -112,6 +121,10 @@ export function useProfile() {
|
|||||||
updateState({ location: profile.location });
|
updateState({ location: profile.location });
|
||||||
updateState({ modalLocation: profile.location });
|
updateState({ modalLocation: profile.location });
|
||||||
}
|
}
|
||||||
|
if (app?.state?.Data?.status) {
|
||||||
|
let status = app.state.Data.status;
|
||||||
|
updateState({ searchable: status.searchable });
|
||||||
|
}
|
||||||
}, [app])
|
}, [app])
|
||||||
|
|
||||||
return { state, actions };
|
return { state, actions };
|
||||||
|
@ -29,6 +29,8 @@ export const IdentityWrapper = styled.div`
|
|||||||
}
|
}
|
||||||
|
|
||||||
.username {
|
.username {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
Loading…
Reference in New Issue
Block a user