support registry listing option

This commit is contained in:
Roland Osborne 2022-03-25 00:33:22 -07:00
parent 318ea81e7e
commit bf21af0a7d
12 changed files with 103 additions and 19 deletions

View File

@ -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

View File

@ -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

View File

@ -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)
} }

View File

@ -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

View File

@ -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"`

View File

@ -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"`

View File

@ -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));

View File

@ -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);

View File

@ -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">

View File

@ -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%;

View File

@ -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 };

View File

@ -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;