From 2d98ac4807fee2ec819cf2c6a07d4a93719a4805 Mon Sep 17 00:00:00 2001 From: Roland Osborne Date: Tue, 5 Apr 2022 11:39:18 -0700 Subject: [PATCH] adding and removing contacts --- doc/api.oa3 | 26 ++++++++ net/server/internal/api_addCard.go | 64 ++++++-------------- net/server/internal/api_removeCard.go | 2 +- net/server/internal/routers.go | 7 +++ net/server/internal/store/schema.go | 1 - net/server/internal/testUtil.go | 3 +- net/web/src/Api/addCard.js | 8 +++ net/web/src/Api/fetchUtil.js | 17 ++++++ net/web/src/Api/getListingMessage.js | 8 +++ net/web/src/Api/removeCard.js | 8 +++ net/web/src/AppContext/useAppContext.hook.js | 1 + net/web/src/User/Contact/Contact.jsx | 9 +-- net/web/src/User/Contact/Contact.styled.js | 15 ++--- net/web/src/User/Contact/useContact.hook.js | 60 ++++++++++++++++-- net/web/src/User/Profile/Profile.styled.js | 3 +- 15 files changed, 165 insertions(+), 67 deletions(-) create mode 100644 net/web/src/Api/addCard.js create mode 100644 net/web/src/Api/fetchUtil.js create mode 100644 net/web/src/Api/getListingMessage.js create mode 100644 net/web/src/Api/removeCard.js diff --git a/doc/api.oa3 b/doc/api.oa3 index e2e431cb..a83c6fa1 100644 --- a/doc/api.oa3 +++ b/doc/api.oa3 @@ -363,7 +363,33 @@ paths: description: permission denied '500': description: internal server error + + /account/listing/{guid}/message: + get: + tags: + - account + description: Get profile message of searchable account. Endpoint is publically accessible. + operationId: get-account-listing-message + parameters: + - name: guid + in: path + description: filter for specified guid + required: true + schema: + type: string + responses: + '200': + description: success + content: + application/json: + schema: + $ref: '#/components/schemas/DataMessage' + '401': + description: permission denied + '500': + description: internal server error + /account/token: get: tags: diff --git a/net/server/internal/api_addCard.go b/net/server/internal/api_addCard.go index 856aa5e5..60616c32 100644 --- a/net/server/internal/api_addCard.go +++ b/net/server/internal/api_addCard.go @@ -12,7 +12,7 @@ import ( func AddCard(w http.ResponseWriter, r *http.Request) { - account, code, err := BearerAppToken(r, false) + account, code, err := ParamAgentToken(r, false) if err != nil { ErrResponse(w, code, err) return @@ -61,53 +61,27 @@ func AddCard(w http.ResponseWriter, r *http.Request) { AccountID: account.Guid, } - // create new card or update existing - if err = store.DB.Where("account_id = ? AND card_id = 0", account.ID).First(slot).Error; err != nil { - if !errors.Is(err, gorm.ErrRecordNotFound) { - ErrResponse(w, http.StatusInternalServerError, err) - return + // save new card + err = store.DB.Transaction(func(tx *gorm.DB) error { + if res := tx.Save(card).Error; res != nil { + return res } - err = store.DB.Transaction(func(tx *gorm.DB) error { - if res := tx.Save(card).Error; res != nil { - return res - } - slot.CardSlotId = uuid.New().String() - slot.AccountID = account.ID - slot.Revision = account.CardRevision + 1 - slot.CardID = card.ID - slot.Card = card - if res := tx.Save(slot).Error; res != nil { - return res - } - if res := tx.Model(&account).Update("card_revision", account.CardRevision + 1).Error; res != nil { - return res - } - return nil - }) - if err != nil { - ErrResponse(w, http.StatusInternalServerError, err) - return + slot.CardSlotId = uuid.New().String() + slot.AccountID = account.ID + slot.Revision = account.CardRevision + 1 + slot.CardID = card.ID + slot.Card = card + if res := tx.Save(slot).Error; res != nil { + return res } - } else { - err = store.DB.Transaction(func(tx *gorm.DB) error { - if res := tx.Save(&card).Error; res != nil { - return res - } - slot.Revision = account.CardRevision + 1 - slot.CardID = card.ID - slot.Card = card - if res := tx.Save(&slot).Error; res != nil { - return res - } - if res := tx.Model(&account).Update("card_revision", account.CardRevision + 1).Error; res != nil { - return res - } - return nil - }) - if err != nil { - ErrResponse(w, http.StatusInternalServerError, err) - return + if res := tx.Model(&account).Update("card_revision", account.CardRevision + 1).Error; res != nil { + return res } + return nil + }) + if err != nil { + ErrResponse(w, http.StatusInternalServerError, err) + return } } else { diff --git a/net/server/internal/api_removeCard.go b/net/server/internal/api_removeCard.go index c33259b6..932ef13b 100644 --- a/net/server/internal/api_removeCard.go +++ b/net/server/internal/api_removeCard.go @@ -10,7 +10,7 @@ import ( func RemoveCard(w http.ResponseWriter, r *http.Request) { - account, code, err := BearerAppToken(r, false); + account, code, err := ParamAgentToken(r, false); if err != nil { ErrResponse(w, code, err) return diff --git a/net/server/internal/routers.go b/net/server/internal/routers.go index b881f534..eeeb3b61 100644 --- a/net/server/internal/routers.go +++ b/net/server/internal/routers.go @@ -104,6 +104,13 @@ var routes = Routes{ GetAccountListingImage, }, + Route{ + "GetAccountListingMessage", + strings.ToUpper("Get"), + "/account/listing/{guid}/message", + GetAccountListingMessage, + }, + Route{ "GetAccountStatus", strings.ToUpper("Get"), diff --git a/net/server/internal/store/schema.go b/net/server/internal/store/schema.go index c1d17719..a60b3195 100644 --- a/net/server/internal/store/schema.go +++ b/net/server/internal/store/schema.go @@ -148,7 +148,6 @@ type Card struct { Location string Image string Version string `gorm:"not null"` - Revision string `gorm:"not null"` Node string `gorm:"not null"` ProfileRevision int64 `gorm:"not null"` DetailRevision int64 `gorm:"not null;default:1"` diff --git a/net/server/internal/testUtil.go b/net/server/internal/testUtil.go index 57b98201..965b9ed5 100644 --- a/net/server/internal/testUtil.go +++ b/net/server/internal/testUtil.go @@ -586,10 +586,9 @@ func AddTestCard(account string, contact string) (cardId string, err error) { } // add A card in B - if r, w, err = NewRequest("POST", "/contact/cards", &msg); err != nil { + if r, w, err = NewRequest("POST", "/contact/cards?agent=" + account, &msg); err != nil { return } - SetBearerAuth(r, account) AddCard(w, r) if err = ReadResponse(w, &card); err != nil { return diff --git a/net/web/src/Api/addCard.js b/net/web/src/Api/addCard.js new file mode 100644 index 00000000..8109c94b --- /dev/null +++ b/net/web/src/Api/addCard.js @@ -0,0 +1,8 @@ +import { checkResponse, fetchWithTimeout } from './fetchUtil'; + +export async function addCard(token, message) { + let card = await fetchWithTimeout(`/contact/cards?agent=${token}`, { method: 'POST', body: JSON.stringify(message)} ); + checkResponse(card); + return await card.json(); +} + diff --git a/net/web/src/Api/fetchUtil.js b/net/web/src/Api/fetchUtil.js new file mode 100644 index 00000000..6ea98813 --- /dev/null +++ b/net/web/src/Api/fetchUtil.js @@ -0,0 +1,17 @@ +const TIMEOUT = 15000; + +//await new Promise(r => setTimeout(r, 2000)); + +export function checkResponse(response) { + if(response.status >= 400 && response.status < 600) { + throw new Error(response.url + " failed"); + } +} + +export async function fetchWithTimeout(url, options) { + return Promise.race([ + fetch(url, options).catch(err => { throw new Error(url + ' failed'); }), + new Promise((_, reject) => setTimeout(() => reject(new Error(url + ' timeout')), TIMEOUT)) + ]); +} + diff --git a/net/web/src/Api/getListingMessage.js b/net/web/src/Api/getListingMessage.js new file mode 100644 index 00000000..aebad94b --- /dev/null +++ b/net/web/src/Api/getListingMessage.js @@ -0,0 +1,8 @@ +import { checkResponse, fetchWithTimeout } from './fetchUtil'; + +export async function getListingMessage(server, guid) { + let listing = await fetchWithTimeout(`https://${server}/account/listing/${guid}/message`, { method: 'GET' }); + checkResponse(listing); + return await listing.json(); +} + diff --git a/net/web/src/Api/removeCard.js b/net/web/src/Api/removeCard.js new file mode 100644 index 00000000..3effc5c8 --- /dev/null +++ b/net/web/src/Api/removeCard.js @@ -0,0 +1,8 @@ +import { checkResponse, fetchWithTimeout } from './fetchUtil'; + +export async function removeCard(token, cardId) { + let card = await fetchWithTimeout(`/contact/cards/${cardId}?agent=${token}`, { method: 'DELETE' } ); + checkResponse(card); + return await card.json(); +} + diff --git a/net/web/src/AppContext/useAppContext.hook.js b/net/web/src/AppContext/useAppContext.hook.js index af372a50..0e1d3cd2 100644 --- a/net/web/src/AppContext/useAppContext.hook.js +++ b/net/web/src/AppContext/useAppContext.hook.js @@ -143,6 +143,7 @@ export function useAppContext() { const resetData = () => { revision.current = null; + accountRevision.current = null; profileRevision.current = null; groupRevision.current = null; cardRevision.current = null; diff --git a/net/web/src/User/Contact/Contact.jsx b/net/web/src/User/Contact/Contact.jsx index b3896d1b..70e42f3f 100644 --- a/net/web/src/User/Contact/Contact.jsx +++ b/net/web/src/User/Contact/Contact.jsx @@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef } from 'react' import { CloseOutlined, UserOutlined } from '@ant-design/icons'; import { useContact } from './useContact.hook'; import { Button, Checkbox, Modal } from 'antd' -import { ContactWrapper, ProfileButton, CloseButton } from './Contact.styled'; +import { ContactWrapper, ProfileButton, CloseButton, ContactSpin } from './Contact.styled'; export function Contact() { @@ -48,7 +48,7 @@ export function Contact() { const Remove = () => { if (state.showButtons.remove) { - return Remove Contact + return actions.remove()}>Remove Contact } return <> } @@ -69,7 +69,7 @@ export function Contact() { const Save = () => { if (state.showButtons.save) { - return Save Contact + return actions.save()}>Save Contact } return <> } @@ -107,6 +107,7 @@ export function Contact() {
{ state.handle }
+ @@ -125,7 +126,7 @@ export function Contact() {
- details: + status: { state.status }
diff --git a/net/web/src/User/Contact/Contact.styled.js b/net/web/src/User/Contact/Contact.styled.js index d52c9bcc..506a67d0 100644 --- a/net/web/src/User/Contact/Contact.styled.js +++ b/net/web/src/User/Contact/Contact.styled.js @@ -1,5 +1,5 @@ import styled from 'styled-components'; -import { Button } from 'antd'; +import { Button, Spin } from 'antd'; export const ContactWrapper = styled.div` display: flex; @@ -31,7 +31,7 @@ export const ContactWrapper = styled.div` align-items: center; justify-content: flex-begin; color: white; - padding-left: 32px; + padding-left: 16px; } .close { @@ -57,9 +57,7 @@ export const ContactWrapper = styled.div` } .status { - color: white; - font-weight: bold; - line-height: 1; + color: #444444; } .buttons { @@ -109,9 +107,8 @@ export const ContactWrapper = styled.div` } .label { - padding-right: 8px; + padding-right: 4; font-size: 1em; - font-weight: bold; color: #888888; } @@ -163,3 +160,7 @@ export const ProfileButton = styled(Button)` margin-right: 8px; `; +export const ContactSpin = styled(Spin)` + padding-right: 32px; +`; + diff --git a/net/web/src/User/Contact/useContact.hook.js b/net/web/src/User/Contact/useContact.hook.js index d51fb20b..bace5684 100644 --- a/net/web/src/User/Contact/useContact.hook.js +++ b/net/web/src/User/Contact/useContact.hook.js @@ -1,6 +1,9 @@ import { useContext, useState, useEffect } from 'react'; import { AppContext } from '../../AppContext/AppContext'; import { useNavigate, useLocation, useParams } from "react-router-dom"; +import { getListingMessage } from '../../Api/getListingMessage'; +import { addCard } from '../../Api/addCard'; +import { removeCard } from '../../Api/removeCard'; export function useContact() { @@ -11,7 +14,10 @@ export function useContact() { location: '', description: '', imageUrl: null, + node: '', + cardId: '', showButtons: {}, + busy: false, }); const data = useLocation(); @@ -27,16 +33,57 @@ export function useContact() { close: () => { navigate('/user') }, + save: async () => { + if (!state.busy) { + updateState({ busy: true }); + try { + let message = await getListingMessage(state.node, guid); + let card = await addCard(app.state.token, message); + } + catch (err) { + window.alert(err); + } + updateState({ busy: false }); + } + }, + connect: () => { + }, + disconnect: () => { + }, + ignore: () => { + }, + cancel: () => { + }, + remove: async () => { + if (!state.busy) { + updateState({ busy: true }); + try { + await removeCard(app.state.token, state.cardId); + navigate('/user'); + } + catch (err) { + window.alert(err); + } + updateState({ busy: false }); + } + }, + saveRequest: () => { + }, + saveAccept: () => { + }, }; useEffect(() => { if (app?.state?.access === 'user') { let card = app.actions.getCard(guid); if (card) { - updateState({ handle: card.data.cardProfile.handle }); - updateState({ name: card.data.cardProfile.name }); - updateState({ description: card.data.cardProfile.description }); - updateState({ location: card.data.cardProfile.location }); + let profile = card.data.cardProfile; + updateState({ cardId: card.id }); + updateState({ handle: profile.handle }); + updateState({ name: profile.name }); + updateState({ description: profile.description }); + updateState({ location: profile.location }); + updateState({ node: profile.node }); if (card.data.cardProfile.imageSet) { updateState({ imageUrl: app.actions.getCardImageUrl(card.id, card.revision) }); } @@ -57,7 +104,7 @@ export function useContact() { updateState({ showButtons: { ignore: true, save: true, saveAccept: true }}); } if (status === 'confirmed') { - updateState({ status: 'saved' }); + updateState({ status: 'disconnected' }); updateState({ showButtons: { remove: true, connect: true }}); } if (status === 'requested') { @@ -70,13 +117,14 @@ export function useContact() { updateState({ name: data.state.name }); updateState({ description: data.state.description }); updateState({ location: data.state.location }); + updateState({ node: data.state.node }); if (data.state.imageSet) { updateState({ imageUrl: app.actions.getRegistryImageUrl(data.state.node, guid, data.state.revision) }); } else { updateState({ imageUrl: '' }); } - updateState({ status: null }); + updateState({ status: 'unsaved' }); updateState({ showButtons: { save: true, saveRequest: true }}); } } diff --git a/net/web/src/User/Profile/Profile.styled.js b/net/web/src/User/Profile/Profile.styled.js index cc35b029..42f86133 100644 --- a/net/web/src/User/Profile/Profile.styled.js +++ b/net/web/src/User/Profile/Profile.styled.js @@ -31,7 +31,8 @@ export const ProfileWrapper = styled.div` font-weight: bold; display: flex; align-items: center; - justify-content: center; + justify-content: flex-begin; + padding-left: 16px; } .close {