adding and removing contacts

This commit is contained in:
Roland Osborne 2022-04-05 11:39:18 -07:00
parent e761fd678d
commit 2d98ac4807
15 changed files with 165 additions and 67 deletions

View File

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

View File

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

View File

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

View File

@ -104,6 +104,13 @@ var routes = Routes{
GetAccountListingImage,
},
Route{
"GetAccountListingMessage",
strings.ToUpper("Get"),
"/account/listing/{guid}/message",
GetAccountListingMessage,
},
Route{
"GetAccountStatus",
strings.ToUpper("Get"),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -143,6 +143,7 @@ export function useAppContext() {
const resetData = () => {
revision.current = null;
accountRevision.current = null;
profileRevision.current = null;
groupRevision.current = null;
cardRevision.current = null;

View File

@ -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 <ProfileButton ghost>Remove Contact</ProfileButton>
return <ProfileButton ghost onClick={() => actions.remove()}>Remove Contact</ProfileButton>
}
return <></>
}
@ -69,7 +69,7 @@ export function Contact() {
const Save = () => {
if (state.showButtons.save) {
return <ProfileButton ghost>Save Contact</ProfileButton>
return <ProfileButton ghost onClick={() => actions.save()}>Save Contact</ProfileButton>
}
return <></>
}
@ -107,6 +107,7 @@ export function Contact() {
<div class="header">
<div class="title">{ state.handle }</div>
<div class="buttons">
<ContactSpin size="large" spinning={state.busy} />
<Disconnect />
<Remove />
<Cancel />
@ -125,7 +126,7 @@ export function Contact() {
<Logo />
</div>
<div class="block">
<span class="label">details:</span>
<span class="label">status: { state.status }</span>
</div>
<div class="details">
<div class="name"><Name /></div>

View File

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

View File

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

View File

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