addTopics to self hosted channels

This commit is contained in:
Roland Osborne 2022-04-14 14:13:08 -07:00
parent 343aa21e3e
commit 6e2801d705
16 changed files with 125 additions and 39 deletions

View File

@ -2113,7 +2113,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/ChannelParams' $ref: '#/components/schemas/ChannelParams'
/content/channels/{channelId}: /content/channels/{channelId}/detail:
get: get:
tags: tags:
- content - content
@ -2143,6 +2143,8 @@ paths:
description: account disabled description: account disabled
'500': '500':
description: internal server error description: internal server error
/content/channels/{channelId}:
delete: delete:
tags: tags:
- content - content
@ -2869,7 +2871,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
type: boolean type: string
/content/channels/{channelId}/topics/{topicId}/tags: /content/channels/{channelId}/topics/{topicId}/tags:

View File

@ -16,16 +16,16 @@ func GetChannel(w http.ResponseWriter, r *http.Request) {
var guid string var guid string
var act *store.Account var act *store.Account
tokenType := r.Header.Get("TokenType") tokenType := ParamTokenType(r)
if tokenType == APP_TOKENAGENT { if tokenType == APP_TOKENAGENT {
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
} }
act = account act = account
} else if tokenType == APP_TOKENCONTACT { } else if tokenType == APP_TOKENCONTACT {
card, code, err := BearerContactToken(r, true) card, code, err := ParamContactToken(r, true)
if err != nil { if err != nil {
ErrResponse(w, code, err) ErrResponse(w, code, err)
return return

View File

@ -62,16 +62,16 @@ func getChannelSlot(r *http.Request, member bool) (slot store.ChannelSlot, guid
// validate contact access // validate contact access
var account *store.Account var account *store.Account
tokenType := r.Header.Get("TokenType") tokenType := ParamTokenType(r);
if tokenType == APP_TOKENAGENT { if tokenType == APP_TOKENAGENT {
account, code, err = BearerAppToken(r, false); account, code, err = ParamAgentToken(r, false);
if err != nil { if err != nil {
return return
} }
guid = account.Guid guid = account.Guid
} else if tokenType == APP_TOKENCONTACT { } else if tokenType == APP_TOKENCONTACT {
var card *store.Card var card *store.Card
card, code, err = BearerContactToken(r, true) card, code, err = ParamContactToken(r, true)
if err != nil { if err != nil {
return return
} }

View File

@ -29,7 +29,7 @@ func SetChannelTopicSubject(w http.ResponseWriter, r *http.Request) {
// load topic // load topic
var topicSlot store.TopicSlot var topicSlot store.TopicSlot
if err = store.DB.Where("channel_id = ? AND topic_slot_id = ?", channelSlot.Channel.ID, topicId).First(&topicSlot).Error; err != nil { if err = store.DB.Preload("Topic").Where("channel_id = ? AND topic_slot_id = ?", channelSlot.Channel.ID, topicId).First(&topicSlot).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
ErrResponse(w, http.StatusNotFound, err) ErrResponse(w, http.StatusNotFound, err)
} else { } else {

View File

@ -541,7 +541,7 @@ var routes = Routes{
Route{ Route{
"GetChannel", "GetChannel",
strings.ToUpper("Get"), strings.ToUpper("Get"),
"/content/channels/{channelId}", "/content/channels/{channelId}/detail",
GetChannel, GetChannel,
}, },

View File

@ -196,18 +196,28 @@ func ApiTestUpload(
return return
} }
if tokenType == APP_TOKENAGENT {
if !strings.Contains(name, "?") {
name += "?"
} else {
name += "&"
}
name += "agent=" + token
} else if tokenType == APP_TOKENCONTACT {
if !strings.Contains(name, "?") {
name += "?"
} else {
name += "&"
}
name += "contact=" + token
}
w := httptest.NewRecorder() w := httptest.NewRecorder()
r := httptest.NewRequest(requestType, name, &data) r := httptest.NewRequest(requestType, name, &data)
if params != nil { if params != nil {
r = mux.SetURLVars(r, *params) r = mux.SetURLVars(r, *params)
} }
if tokenType != "" {
r.Header.Add("TokenType", tokenType)
}
if token != "" {
SetBearerAuth(r, token)
}
r.Header.Set("Content-Type", writer.FormDataContentType()) r.Header.Set("Content-Type", writer.FormDataContentType())
endpoint(w, r) endpoint(w, r)

View File

@ -0,0 +1,24 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function addChannelTopic(token, channelId, message, assets ) {
let topic = await fetchWithTimeout(`/content/channels/${channelId}/topics?agent=${token}`,
{ method: 'POST', body: JSON.stringify({}) });
checkResponse(topic);
let slot = await topic.json();
// add each asset
let subject = { data: JSON.stringify(message, (key, value) => {
if (value !== null) return value
}), datatype: 'superbasictopic' };
let unconfirmed = await fetchWithTimeout(`/content/channels/${channelId}/topics/${slot.id}/subject?agent=${token}`,
{ method: 'PUT', body: JSON.stringify(subject) });
checkResponse(unconfirmed);
let confirmed = await fetchWithTimeout(`/content/channels/${channelId}/topics/${slot.id}/confirmed?agent=${token}`,
{ method: 'PUT', body: JSON.stringify('confirmed') });
checkResponse(confirmed);
return;
}

View File

@ -0,0 +1,8 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function getChannel(token, channelId) {
let channel = await fetchWithTimeout(`/content/channels/${channelId}/detail?agent=${token}`, { method: 'GET' });
checkResponse(channel)
return await channel.json()
}

View File

@ -3,7 +3,7 @@ import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function getChannels(token, revision) { export async function getChannels(token, revision) {
let param = "?agent=" + token let param = "?agent=" + token
if (revision != null) { if (revision != null) {
param += '&revision=' + revision param += '&channelRevision=' + revision
} }
let channels = await fetchWithTimeout('/content/channels' + param, { method: 'GET' }); let channels = await fetchWithTimeout('/content/channels' + param, { method: 'GET' });
checkResponse(channels) checkResponse(channels)

View File

@ -26,7 +26,8 @@ function App() {
<Route path="/user" element={ <User /> }> <Route path="/user" element={ <User /> }>
<Route path="profile" element={<Profile />} /> <Route path="profile" element={<Profile />} />
<Route path="contact/:guid" element={<Contact />} /> <Route path="contact/:guid" element={<Contact />} />
<Route path="conversation/:id" element={<Conversation />} /> <Route path="conversation/:contact/:channel" element={<Conversation />} />
<Route path="conversation/:channel" element={<Conversation />} />
</Route> </Route>
</Routes> </Routes>
</Router> </Router>

View File

@ -1,6 +1,7 @@
import { useEffect, useState, useRef } from 'react'; import { useEffect, useState, useRef } from 'react';
import { getContactProfile, setCardProfile, getCards, getCardImageUrl, getCardProfile, getCardDetail, getListingImageUrl, getListing, setProfileImage, setProfileData, getProfileImageUrl, getAccountStatus, setAccountSearchable, getProfile, getGroups, getAvailable, getUsername, setLogin, createAccount } from './fetchUtil'; import { getContactProfile, setCardProfile, getCards, getCardImageUrl, getCardProfile, getCardDetail, getListingImageUrl, getListing, setProfileImage, setProfileData, getProfileImageUrl, getAccountStatus, setAccountSearchable, getProfile, getGroups, getAvailable, getUsername, setLogin, createAccount } from './fetchUtil';
import { getChannels } from '../Api/getChannels'; import { getChannels } from '../Api/getChannels';
import { getChannel } from '../Api/getChannel';
import { getContactChannels } from '../Api/getContactChannels'; import { getContactChannels } from '../Api/getContactChannels';
async function updateAccount(token, updateData) { async function updateAccount(token, updateData) {
@ -30,7 +31,22 @@ async function updateChannels(token, revision, channelMap, mergeChannels) {
let channels = await getChannels(token, revision); let channels = await getChannels(token, revision);
for (let channel of channels) { for (let channel of channels) {
if (channel.data) { if (channel.data) {
channelMap.set(channel.id, channel); let cur = channelMap.get(channel.id);
if (cur == null) {
cur = { id: channel.id, data: { } }
}
if (cur.data.detailRevision != channel.data.detailRevision) {
if (channel.data.channelDetail != null) {
cur.data.channelDetail = channel.data.channelDetail;
cur.data.detailRevision = channel.data.detailRevision;
}
else {
let slot = await getChannel(token, channel.id);
cur.data.channelDetail = slot.data.channelDetail;
cur.data.detailRevision = slot.data.detailRevision;
}
}
channelMap.set(channel.id, cur);
} }
else { else {
channelMap.delete(channel.id); channelMap.delete(channel.id);

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { Button, Dropdown, Input, Tooltip, Menu } from 'antd'; import { Button, Dropdown, Input, Tooltip, Menu } from 'antd';
import { AddTopicWrapper } from './AddTopic.styled'; import { AddTopicWrapper, BusySpin } from './AddTopic.styled';
import { AddCarousel } from './AddCarousel/AddCarousel'; import { AddCarousel } from './AddCarousel/AddCarousel';
import { useAddTopic } from './useAddTopic.hook'; import { useAddTopic } from './useAddTopic.hook';
import { BgColorsOutlined, FontColorsOutlined, FontSizeOutlined, PaperClipOutlined, SendOutlined } from '@ant-design/icons'; import { BgColorsOutlined, FontColorsOutlined, FontSizeOutlined, PaperClipOutlined, SendOutlined } from '@ant-design/icons';
@ -9,11 +9,6 @@ export function AddTopic() {
const { state, actions } = useAddTopic(); const { state, actions } = useAddTopic();
const onAttach = () => {
console.log("ADD IMAGE");
actions.addImage(null);
}
const menu = ( const menu = (
<Menu> <Menu>
<Menu.Item key="0"> <Menu.Item key="0">
@ -28,6 +23,10 @@ export function AddTopic() {
</Menu> </Menu>
); );
const onSend = () => {
actions.addTopic();
}
return ( return (
<AddTopicWrapper> <AddTopicWrapper>
<div class="container noselect"> <div class="container noselect">
@ -52,7 +51,8 @@ export function AddTopic() {
<Button icon={<BgColorsOutlined />} size="large" /> <Button icon={<BgColorsOutlined />} size="large" />
</div> </div>
<div class="send"> <div class="send">
<Button icon={<SendOutlined />} size="large" /> <Button icon={<SendOutlined />} onClick={onSend} size="large" />
<BusySpin spinning={state.busy} />
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,3 +1,4 @@
import { Spin } from 'antd';
import styled from 'styled-components'; import styled from 'styled-components';
export const AddTopicWrapper = styled.div` export const AddTopicWrapper = styled.div`
@ -35,7 +36,14 @@ export const AddTopicWrapper = styled.div`
display: flex; display: flex;
flex-grow: 1; flex-grow: 1;
justify-content: flex-end; justify-content: flex-end;
align-items: center;
padding-top: 4px; padding-top: 4px;
} }
`; `;
export const BusySpin = styled(Spin)`
position: absolute;
margin-right: 12px;
x-index: 10;
`;

View File

@ -1,5 +1,7 @@
import { useContext, useState, useEffect } from 'react'; import { useContext, useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { addChannelTopic } from '../../../Api/addChannelTopic';
import { AppContext } from '../../../AppContext/AppContext';
export function useAddTopic() { export function useAddTopic() {
@ -7,11 +9,14 @@ export function useAddTopic() {
assets: [], assets: [],
messageText: null, messageText: null,
messageColor: null, messageColor: null,
messageWeight: null,
messageSize: null, messageSize: null,
backgroundColor: null, backgroundColor: null,
busy: false,
}); });
const { contact, channel } = useParams();
const app = useContext(AppContext);
const updateState = (value) => { const updateState = (value) => {
setState((s) => ({ ...s, ...value })); setState((s) => ({ ...s, ...value }));
} }
@ -23,8 +28,6 @@ export function useAddTopic() {
}); });
} }
const navigate = useNavigate();
const actions = { const actions = {
addImage: (image) => { addAsset(image) }, addImage: (image) => { addAsset(image) },
addVideo: (video) => { addAsset(video) }, addVideo: (video) => { addAsset(video) },
@ -46,7 +49,22 @@ export function useAddTopic() {
setBackgroundColor: (value) => { setBackgroundColor: (value) => {
updateState({ backgroundColor: value }); updateState({ backgroundColor: value });
}, },
addTopic: () => {}, addTopic: async () => {
if (!state.busy) {
updateState({ busy: true });
try {
if (!contact) {
let message = { text: state.messageText, textColor: state.messageColor,
textSize: state.messageSize, backgroundColor: state.backgroundColor };
await addChannelTopic(app.state.token, channel, message, []);
}
}
catch(err) {
window.alert(err);
}
updateState({ busy: false });
}
},
}; };
return { state, actions }; return { state, actions };

View File

@ -8,7 +8,7 @@ export function useConversation() {
}); });
const data = useLocation(); const data = useLocation();
const { id } = useParams(); const { contact, channel } = useParams();
const navigate = useNavigate(); const navigate = useNavigate();
const app = useContext(AppContext); const app = useContext(AppContext);
@ -22,11 +22,5 @@ export function useConversation() {
}, },
}; };
useEffect(() => {
if (app?.state?.access === 'user') {
console.log("CONVERSATION:", id);
}
}, [app, id])
return { state, actions }; return { state, actions };
} }

View File

@ -14,7 +14,12 @@ export function useChannelItem() {
const actions = { const actions = {
select: (item) => { select: (item) => {
if (item.guid) {
navigate(`/user/conversation/${item.guid}/${item.channel.id}`);
}
else {
navigate(`/user/conversation/${item.channel.id}`); navigate(`/user/conversation/${item.channel.id}`);
}
}, },
}; };