From bf21af0a7d7b8e49601da597ced6bc9fb009440f Mon Sep 17 00:00:00 2001 From: Roland Osborne Date: Fri, 25 Mar 2022 00:33:22 -0700 Subject: [PATCH] support registry listing option --- doc/api.oa3 | 18 +++++++++--- net/server/internal/api_getAccountStatus.go | 2 +- .../internal/api_setAccountSearchable.go | 16 +++++++++-- net/server/internal/api_status.go | 1 + net/server/internal/models.go | 4 ++- net/server/internal/store/schema.go | 1 + net/web/src/AppContext/fetchUtil.js | 11 ++++++++ net/web/src/AppContext/useAppContext.hook.js | 28 ++++++++++++++----- net/web/src/User/Profile/Profile.jsx | 10 ++++++- net/web/src/User/Profile/Profile.styled.js | 10 +++++++ net/web/src/User/Profile/useProfile.hook.js | 19 +++++++++++-- .../User/SideBar/Identity/Identity.styled.js | 2 ++ 12 files changed, 103 insertions(+), 19 deletions(-) diff --git a/doc/api.oa3 b/doc/api.oa3 index 810f4916..27908ac1 100644 --- a/doc/api.oa3 +++ b/doc/api.oa3 @@ -390,8 +390,13 @@ paths: - account description: Get disabled status of account. Authorized to account username and password. operationId: get-account-status - security: - - bearerAuth: [] + parameters: + - name: agent + in: query + description: agent token + required: false + schema: + type: string responses: '200': description: successful operation @@ -410,8 +415,13 @@ paths: - account description: Set whether account is publicly listed. operationId: set-account-seachable - security: - - bearerAuth: [] + parameters: + - name: agent + in: query + description: agent token + required: false + schema: + type: string responses: '201': description: success diff --git a/net/server/internal/api_getAccountStatus.go b/net/server/internal/api_getAccountStatus.go index 8b1c4172..e27b5b59 100644 --- a/net/server/internal/api_getAccountStatus.go +++ b/net/server/internal/api_getAccountStatus.go @@ -7,7 +7,7 @@ import ( func GetAccountStatus(w http.ResponseWriter, r *http.Request) { - account, code, err := BearerAppToken(r, false) + account, code, err := ParamAgentToken(r, true); if err != nil { ErrResponse(w, code, err) return diff --git a/net/server/internal/api_setAccountSearchable.go b/net/server/internal/api_setAccountSearchable.go index cc5d8532..b8f80e03 100644 --- a/net/server/internal/api_setAccountSearchable.go +++ b/net/server/internal/api_setAccountSearchable.go @@ -2,12 +2,13 @@ package databag import ( "net/http" + "gorm.io/gorm" "databag/internal/store" ) func SetAccountSearchable(w http.ResponseWriter, r *http.Request) { - account, code, err := BearerAppToken(r, false) + account, code, err := ParamAgentToken(r, true); if err != nil { ErrResponse(w, code, err) return @@ -19,11 +20,22 @@ func SetAccountSearchable(w http.ResponseWriter, r *http.Request) { 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) return } + SetStatus(account) WriteResponse(w, nil) } diff --git a/net/server/internal/api_status.go b/net/server/internal/api_status.go index 697c2f78..f4525acb 100644 --- a/net/server/internal/api_status.go +++ b/net/server/internal/api_status.go @@ -104,6 +104,7 @@ func Status(w http.ResponseWriter, r *http.Request) { func getRevision(account *store.Account) Revision { var r Revision + r.Account = account.AccountRevision r.Profile = account.ProfileRevision r.Article = account.ArticleRevision r.Channel = account.ChannelRevision diff --git a/net/server/internal/models.go b/net/server/internal/models.go index 377c95d7..98d14660 100644 --- a/net/server/internal/models.go +++ b/net/server/internal/models.go @@ -361,7 +361,9 @@ type ProfileData struct { type Revision struct { - Profile int64 `json:"profile"` + Account int64 `json:"account"` + + Profile int64 `json:"profile"` Article int64 `json:"article"` diff --git a/net/server/internal/store/schema.go b/net/server/internal/store/schema.go index b64fa7af..a60b3195 100644 --- a/net/server/internal/store/schema.go +++ b/net/server/internal/store/schema.go @@ -60,6 +60,7 @@ type Account struct { Guid string `gorm:"not null;uniqueIndex"` Username string `gorm:"not null;uniqueIndex"` Password []byte `gorm:"not null"` + AccountRevision int64 `gorm:"not null;default:1"` ProfileRevision int64 `gorm:"not null;default:1"` ArticleRevision int64 `gorm:"not null;default:1"` GroupRevision int64 `gorm:"not null;default:1"` diff --git a/net/web/src/AppContext/fetchUtil.js b/net/web/src/AppContext/fetchUtil.js index 2266d1ab..d70bc25d 100644 --- a/net/web/src/AppContext/fetchUtil.js +++ b/net/web/src/AppContext/fetchUtil.js @@ -40,6 +40,17 @@ export async function setLogin(username, password) { 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) { let headers = new Headers() headers.append('Credentials', 'Basic ' + base64.encode(username + ":" + password)); diff --git a/net/web/src/AppContext/useAppContext.hook.js b/net/web/src/AppContext/useAppContext.hook.js index 73c46c58..2ede0f39 100644 --- a/net/web/src/AppContext/useAppContext.hook.js +++ b/net/web/src/AppContext/useAppContext.hook.js @@ -1,5 +1,11 @@ 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) { let profile = await getProfile(token); @@ -41,10 +47,10 @@ export function useAppContext() { const [state, setState] = useState(null); const groupRevision = useRef(null); - const groups = useRef(new Map()); - + const accountRevision = useRef(null); const profileRevision = useRef(null); - const profile = useRef({}); + + const groups = useRef(new Map()); const ws = useRef(null); const revision = useRef(null); @@ -60,10 +66,10 @@ export function useAppContext() { const resetData = () => { revision.current = null; - profile.current = {}; profileRevision.current = null; - groups.current = new Map(); groupRevision.current = null; + groups.current = new Map(); + setState(null); } const userActions = { @@ -77,6 +83,9 @@ export function useAppContext() { setProfileImage: async (image) => { await setProfileImage(state.token, image); }, + setAccountSearchable: async (flag) => { + await setAccountSearchable(state.token, flag); + }, profileImageUrl: () => getProfileImageUrl(state.token, state.Data?.profile?.revision) } @@ -114,6 +123,12 @@ export function useAppContext() { 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 if (rev == revision.current) { revision.current = null @@ -165,7 +180,6 @@ export function useAppContext() { const session = JSON.parse(storage) if (session?.access === 'admin') { setState({ token: session.token, access: session.access }) - setWebsocket(session.token); } else if (session?.access === 'user') { setState({ token: session.token, access: session.access }) setWebsocket(session.token); diff --git a/net/web/src/User/Profile/Profile.jsx b/net/web/src/User/Profile/Profile.jsx index ca9e9186..cc4c4e31 100644 --- a/net/web/src/User/Profile/Profile.jsx +++ b/net/web/src/User/Profile/Profile.jsx @@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef } from 'react' import { EditIcon, ProfileWrapper, CloseButton, ModalFooter, SelectButton } from './Profile.styled'; import { UserOutlined, CloseOutlined, EditOutlined } from '@ant-design/icons'; import { useProfile } from './useProfile.hook'; -import { Button, Modal } from 'antd' +import { Button, Checkbox, Modal } from 'antd' import { ProfileInfo } from './ProfileInfo/ProfileInfo'; import { ProfileImage } from './ProfileImage/ProfileImage'; @@ -68,6 +68,10 @@ export function Profile(props) { reader.readAsDataURL(e.target.files[0]); } + const onSearchable = (flag) => { + actions.setSearchable(flag); + } + const Footer = ( selected(e)} style={{display: 'none'}}/> @@ -87,6 +91,10 @@ export function Profile(props) {
+
+ Listed in Registry + onSearchable(e.target.checked)} /> +
setLogoVisible(true)}>
diff --git a/net/web/src/User/Profile/Profile.styled.js b/net/web/src/User/Profile/Profile.styled.js index 2a26544d..0d586aed 100644 --- a/net/web/src/User/Profile/Profile.styled.js +++ b/net/web/src/User/Profile/Profile.styled.js @@ -56,6 +56,16 @@ export const ProfileWrapper = styled.div` flex: 3 } + .registry { + display: flex; + flex-direction: row; + padding-bottom: 8px; + } + + .search { + padding-right: 16px; + } + .logo { width: 100%; height: 100%; diff --git a/net/web/src/User/Profile/useProfile.hook.js b/net/web/src/User/Profile/useProfile.hook.js index 5ca62637..e3d3538f 100644 --- a/net/web/src/User/Profile/useProfile.hook.js +++ b/net/web/src/User/Profile/useProfile.hook.js @@ -10,6 +10,7 @@ export function useProfile() { description: '', location: '', imageUrl: null, + searchable: false, modalBusy: false, modalName: '', modalLocation: '', @@ -59,6 +60,14 @@ export function useProfile() { } return set }, + setSearchable: async (flag) => { + try { + await app.actions.setAccountSearchable(flag); + } + catch (err) { + window.alert(err); + } + }, setProfileImage: async () => { let set = false if(!state.modalBusy) { @@ -70,10 +79,10 @@ export function useProfile() { img.onload = () => { var canvas = document.createElement("canvas"); var context = canvas.getContext('2d'); - canvas.width = state.crop.w; - canvas.height = state.crop.h; + canvas.width = 256; + canvas.height = 256; 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()); } img.onerror = reject; @@ -112,6 +121,10 @@ export function useProfile() { updateState({ location: profile.location }); updateState({ modalLocation: profile.location }); } + if (app?.state?.Data?.status) { + let status = app.state.Data.status; + updateState({ searchable: status.searchable }); + } }, [app]) return { state, actions }; diff --git a/net/web/src/User/SideBar/Identity/Identity.styled.js b/net/web/src/User/SideBar/Identity/Identity.styled.js index ee6f8cb6..06c6a16d 100644 --- a/net/web/src/User/SideBar/Identity/Identity.styled.js +++ b/net/web/src/User/SideBar/Identity/Identity.styled.js @@ -29,6 +29,8 @@ export const IdentityWrapper = styled.div` } .username { + white-space: nowrap; + overflow: hidden; flex-grow: 1; display: flex; flex-direction: column;