mirror of
https://github.com/balzack/databag.git
synced 2025-02-14 20:49:16 +00:00
adding and removing contacts
This commit is contained in:
parent
e761fd678d
commit
2d98ac4807
26
doc/api.oa3
26
doc/api.oa3
@ -364,6 +364,32 @@ paths:
|
|||||||
'500':
|
'500':
|
||||||
description: internal server error
|
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:
|
/account/token:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
func AddCard(w http.ResponseWriter, r *http.Request) {
|
func AddCard(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
account, code, err := BearerAppToken(r, false)
|
account, code, err := ParamAgentToken(r, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrResponse(w, code, err)
|
ErrResponse(w, code, err)
|
||||||
return
|
return
|
||||||
@ -61,12 +61,7 @@ func AddCard(w http.ResponseWriter, r *http.Request) {
|
|||||||
AccountID: account.Guid,
|
AccountID: account.Guid,
|
||||||
}
|
}
|
||||||
|
|
||||||
// create new card or update existing
|
// save new card
|
||||||
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
|
|
||||||
}
|
|
||||||
err = store.DB.Transaction(func(tx *gorm.DB) error {
|
err = store.DB.Transaction(func(tx *gorm.DB) error {
|
||||||
if res := tx.Save(card).Error; res != nil {
|
if res := tx.Save(card).Error; res != nil {
|
||||||
return res
|
return res
|
||||||
@ -89,27 +84,6 @@ func AddCard(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
|
|
||||||
if identity.Revision > card.ProfileRevision {
|
if identity.Revision > card.ProfileRevision {
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import (
|
|||||||
|
|
||||||
func RemoveCard(w http.ResponseWriter, r *http.Request) {
|
func RemoveCard(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
account, code, err := BearerAppToken(r, false);
|
account, code, err := ParamAgentToken(r, false);
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrResponse(w, code, err)
|
ErrResponse(w, code, err)
|
||||||
return
|
return
|
||||||
|
@ -104,6 +104,13 @@ var routes = Routes{
|
|||||||
GetAccountListingImage,
|
GetAccountListingImage,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Route{
|
||||||
|
"GetAccountListingMessage",
|
||||||
|
strings.ToUpper("Get"),
|
||||||
|
"/account/listing/{guid}/message",
|
||||||
|
GetAccountListingMessage,
|
||||||
|
},
|
||||||
|
|
||||||
Route{
|
Route{
|
||||||
"GetAccountStatus",
|
"GetAccountStatus",
|
||||||
strings.ToUpper("Get"),
|
strings.ToUpper("Get"),
|
||||||
|
@ -148,7 +148,6 @@ type Card struct {
|
|||||||
Location string
|
Location string
|
||||||
Image string
|
Image string
|
||||||
Version string `gorm:"not null"`
|
Version string `gorm:"not null"`
|
||||||
Revision string `gorm:"not null"`
|
|
||||||
Node string `gorm:"not null"`
|
Node string `gorm:"not null"`
|
||||||
ProfileRevision int64 `gorm:"not null"`
|
ProfileRevision int64 `gorm:"not null"`
|
||||||
DetailRevision int64 `gorm:"not null;default:1"`
|
DetailRevision int64 `gorm:"not null;default:1"`
|
||||||
|
@ -586,10 +586,9 @@ func AddTestCard(account string, contact string) (cardId string, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add A card in B
|
// 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
|
return
|
||||||
}
|
}
|
||||||
SetBearerAuth(r, account)
|
|
||||||
AddCard(w, r)
|
AddCard(w, r)
|
||||||
if err = ReadResponse(w, &card); err != nil {
|
if err = ReadResponse(w, &card); err != nil {
|
||||||
return
|
return
|
||||||
|
8
net/web/src/Api/addCard.js
Normal file
8
net/web/src/Api/addCard.js
Normal 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();
|
||||||
|
}
|
||||||
|
|
17
net/web/src/Api/fetchUtil.js
Normal file
17
net/web/src/Api/fetchUtil.js
Normal 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))
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
8
net/web/src/Api/getListingMessage.js
Normal file
8
net/web/src/Api/getListingMessage.js
Normal 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();
|
||||||
|
}
|
||||||
|
|
8
net/web/src/Api/removeCard.js
Normal file
8
net/web/src/Api/removeCard.js
Normal 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();
|
||||||
|
}
|
||||||
|
|
@ -143,6 +143,7 @@ export function useAppContext() {
|
|||||||
|
|
||||||
const resetData = () => {
|
const resetData = () => {
|
||||||
revision.current = null;
|
revision.current = null;
|
||||||
|
accountRevision.current = null;
|
||||||
profileRevision.current = null;
|
profileRevision.current = null;
|
||||||
groupRevision.current = null;
|
groupRevision.current = null;
|
||||||
cardRevision.current = null;
|
cardRevision.current = null;
|
||||||
|
@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef } from 'react'
|
|||||||
import { CloseOutlined, UserOutlined } from '@ant-design/icons';
|
import { CloseOutlined, UserOutlined } from '@ant-design/icons';
|
||||||
import { useContact } from './useContact.hook';
|
import { useContact } from './useContact.hook';
|
||||||
import { Button, Checkbox, Modal } from 'antd'
|
import { Button, Checkbox, Modal } from 'antd'
|
||||||
import { ContactWrapper, ProfileButton, CloseButton } from './Contact.styled';
|
import { ContactWrapper, ProfileButton, CloseButton, ContactSpin } from './Contact.styled';
|
||||||
|
|
||||||
export function Contact() {
|
export function Contact() {
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ export function Contact() {
|
|||||||
|
|
||||||
const Remove = () => {
|
const Remove = () => {
|
||||||
if (state.showButtons.remove) {
|
if (state.showButtons.remove) {
|
||||||
return <ProfileButton ghost>Remove Contact</ProfileButton>
|
return <ProfileButton ghost onClick={() => actions.remove()}>Remove Contact</ProfileButton>
|
||||||
}
|
}
|
||||||
return <></>
|
return <></>
|
||||||
}
|
}
|
||||||
@ -69,7 +69,7 @@ export function Contact() {
|
|||||||
|
|
||||||
const Save = () => {
|
const Save = () => {
|
||||||
if (state.showButtons.save) {
|
if (state.showButtons.save) {
|
||||||
return <ProfileButton ghost>Save Contact</ProfileButton>
|
return <ProfileButton ghost onClick={() => actions.save()}>Save Contact</ProfileButton>
|
||||||
}
|
}
|
||||||
return <></>
|
return <></>
|
||||||
}
|
}
|
||||||
@ -107,6 +107,7 @@ export function Contact() {
|
|||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="title">{ state.handle }</div>
|
<div class="title">{ state.handle }</div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
|
<ContactSpin size="large" spinning={state.busy} />
|
||||||
<Disconnect />
|
<Disconnect />
|
||||||
<Remove />
|
<Remove />
|
||||||
<Cancel />
|
<Cancel />
|
||||||
@ -125,7 +126,7 @@ export function Contact() {
|
|||||||
<Logo />
|
<Logo />
|
||||||
</div>
|
</div>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<span class="label">details:</span>
|
<span class="label">status: { state.status }</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="details">
|
<div class="details">
|
||||||
<div class="name"><Name /></div>
|
<div class="name"><Name /></div>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { Button } from 'antd';
|
import { Button, Spin } from 'antd';
|
||||||
|
|
||||||
export const ContactWrapper = styled.div`
|
export const ContactWrapper = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -31,7 +31,7 @@ export const ContactWrapper = styled.div`
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-begin;
|
justify-content: flex-begin;
|
||||||
color: white;
|
color: white;
|
||||||
padding-left: 32px;
|
padding-left: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.close {
|
.close {
|
||||||
@ -57,9 +57,7 @@ export const ContactWrapper = styled.div`
|
|||||||
}
|
}
|
||||||
|
|
||||||
.status {
|
.status {
|
||||||
color: white;
|
color: #444444;
|
||||||
font-weight: bold;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttons {
|
.buttons {
|
||||||
@ -109,9 +107,8 @@ export const ContactWrapper = styled.div`
|
|||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
padding-right: 8px;
|
padding-right: 4;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
font-weight: bold;
|
|
||||||
color: #888888;
|
color: #888888;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,3 +160,7 @@ export const ProfileButton = styled(Button)`
|
|||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const ContactSpin = styled(Spin)`
|
||||||
|
padding-right: 32px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import { useContext, useState, useEffect } from 'react';
|
import { useContext, useState, useEffect } from 'react';
|
||||||
import { AppContext } from '../../AppContext/AppContext';
|
import { AppContext } from '../../AppContext/AppContext';
|
||||||
import { useNavigate, useLocation, useParams } from "react-router-dom";
|
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() {
|
export function useContact() {
|
||||||
|
|
||||||
@ -11,7 +14,10 @@ export function useContact() {
|
|||||||
location: '',
|
location: '',
|
||||||
description: '',
|
description: '',
|
||||||
imageUrl: null,
|
imageUrl: null,
|
||||||
|
node: '',
|
||||||
|
cardId: '',
|
||||||
showButtons: {},
|
showButtons: {},
|
||||||
|
busy: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = useLocation();
|
const data = useLocation();
|
||||||
@ -27,16 +33,57 @@ export function useContact() {
|
|||||||
close: () => {
|
close: () => {
|
||||||
navigate('/user')
|
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(() => {
|
useEffect(() => {
|
||||||
if (app?.state?.access === 'user') {
|
if (app?.state?.access === 'user') {
|
||||||
let card = app.actions.getCard(guid);
|
let card = app.actions.getCard(guid);
|
||||||
if (card) {
|
if (card) {
|
||||||
updateState({ handle: card.data.cardProfile.handle });
|
let profile = card.data.cardProfile;
|
||||||
updateState({ name: card.data.cardProfile.name });
|
updateState({ cardId: card.id });
|
||||||
updateState({ description: card.data.cardProfile.description });
|
updateState({ handle: profile.handle });
|
||||||
updateState({ location: card.data.cardProfile.location });
|
updateState({ name: profile.name });
|
||||||
|
updateState({ description: profile.description });
|
||||||
|
updateState({ location: profile.location });
|
||||||
|
updateState({ node: profile.node });
|
||||||
if (card.data.cardProfile.imageSet) {
|
if (card.data.cardProfile.imageSet) {
|
||||||
updateState({ imageUrl: app.actions.getCardImageUrl(card.id, card.revision) });
|
updateState({ imageUrl: app.actions.getCardImageUrl(card.id, card.revision) });
|
||||||
}
|
}
|
||||||
@ -57,7 +104,7 @@ export function useContact() {
|
|||||||
updateState({ showButtons: { ignore: true, save: true, saveAccept: true }});
|
updateState({ showButtons: { ignore: true, save: true, saveAccept: true }});
|
||||||
}
|
}
|
||||||
if (status === 'confirmed') {
|
if (status === 'confirmed') {
|
||||||
updateState({ status: 'saved' });
|
updateState({ status: 'disconnected' });
|
||||||
updateState({ showButtons: { remove: true, connect: true }});
|
updateState({ showButtons: { remove: true, connect: true }});
|
||||||
}
|
}
|
||||||
if (status === 'requested') {
|
if (status === 'requested') {
|
||||||
@ -70,13 +117,14 @@ export function useContact() {
|
|||||||
updateState({ name: data.state.name });
|
updateState({ name: data.state.name });
|
||||||
updateState({ description: data.state.description });
|
updateState({ description: data.state.description });
|
||||||
updateState({ location: data.state.location });
|
updateState({ location: data.state.location });
|
||||||
|
updateState({ node: data.state.node });
|
||||||
if (data.state.imageSet) {
|
if (data.state.imageSet) {
|
||||||
updateState({ imageUrl: app.actions.getRegistryImageUrl(data.state.node, guid, data.state.revision) });
|
updateState({ imageUrl: app.actions.getRegistryImageUrl(data.state.node, guid, data.state.revision) });
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
updateState({ imageUrl: '' });
|
updateState({ imageUrl: '' });
|
||||||
}
|
}
|
||||||
updateState({ status: null });
|
updateState({ status: 'unsaved' });
|
||||||
updateState({ showButtons: { save: true, saveRequest: true }});
|
updateState({ showButtons: { save: true, saveRequest: true }});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,8 @@ export const ProfileWrapper = styled.div`
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: flex-begin;
|
||||||
|
padding-left: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.close {
|
.close {
|
||||||
|
Loading…
Reference in New Issue
Block a user