diff --git a/doc/api.oa3 b/doc/api.oa3 index 3c96fbe0..e7e027cc 100644 --- a/doc/api.oa3 +++ b/doc/api.oa3 @@ -2113,7 +2113,7 @@ paths: schema: $ref: '#/components/schemas/ChannelParams' - /content/channels/{channelId}: + /content/channels/{channelId}/detail: get: tags: - content @@ -2143,6 +2143,8 @@ paths: description: account disabled '500': description: internal server error + +/content/channels/{channelId}: delete: tags: - content @@ -2869,7 +2871,7 @@ paths: content: application/json: schema: - type: boolean + type: string /content/channels/{channelId}/topics/{topicId}/tags: diff --git a/net/server/internal/api_getChannel.go b/net/server/internal/api_getChannel.go index 59332721..56d37836 100644 --- a/net/server/internal/api_getChannel.go +++ b/net/server/internal/api_getChannel.go @@ -16,16 +16,16 @@ func GetChannel(w http.ResponseWriter, r *http.Request) { var guid string var act *store.Account - tokenType := r.Header.Get("TokenType") + tokenType := ParamTokenType(r) if tokenType == APP_TOKENAGENT { - account, code, err := BearerAppToken(r, false); + account, code, err := ParamAgentToken(r, false); if err != nil { ErrResponse(w, code, err) return } act = account } else if tokenType == APP_TOKENCONTACT { - card, code, err := BearerContactToken(r, true) + card, code, err := ParamContactToken(r, true) if err != nil { ErrResponse(w, code, err) return diff --git a/net/server/internal/api_getChannelTopic.go b/net/server/internal/api_getChannelTopic.go index eea15a4c..34d25ca8 100644 --- a/net/server/internal/api_getChannelTopic.go +++ b/net/server/internal/api_getChannelTopic.go @@ -62,16 +62,16 @@ func getChannelSlot(r *http.Request, member bool) (slot store.ChannelSlot, guid // validate contact access var account *store.Account - tokenType := r.Header.Get("TokenType") + tokenType := ParamTokenType(r); if tokenType == APP_TOKENAGENT { - account, code, err = BearerAppToken(r, false); + account, code, err = ParamAgentToken(r, false); if err != nil { return } guid = account.Guid } else if tokenType == APP_TOKENCONTACT { var card *store.Card - card, code, err = BearerContactToken(r, true) + card, code, err = ParamContactToken(r, true) if err != nil { return } diff --git a/net/server/internal/api_setChannelTopicSubject.go b/net/server/internal/api_setChannelTopicSubject.go index 2dde18b6..ced184be 100644 --- a/net/server/internal/api_setChannelTopicSubject.go +++ b/net/server/internal/api_setChannelTopicSubject.go @@ -29,7 +29,7 @@ func SetChannelTopicSubject(w http.ResponseWriter, r *http.Request) { // load topic 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) { ErrResponse(w, http.StatusNotFound, err) } else { diff --git a/net/server/internal/routers.go b/net/server/internal/routers.go index eeeb3b61..dac14774 100644 --- a/net/server/internal/routers.go +++ b/net/server/internal/routers.go @@ -541,7 +541,7 @@ var routes = Routes{ Route{ "GetChannel", strings.ToUpper("Get"), - "/content/channels/{channelId}", + "/content/channels/{channelId}/detail", GetChannel, }, diff --git a/net/server/internal/testUtil.go b/net/server/internal/testUtil.go index 1991a663..e6b04c25 100644 --- a/net/server/internal/testUtil.go +++ b/net/server/internal/testUtil.go @@ -196,18 +196,28 @@ func ApiTestUpload( 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() r := httptest.NewRequest(requestType, name, &data) if params != nil { r = mux.SetURLVars(r, *params) } - if tokenType != "" { - r.Header.Add("TokenType", tokenType) - } - if token != "" { - SetBearerAuth(r, token) - } r.Header.Set("Content-Type", writer.FormDataContentType()) endpoint(w, r) diff --git a/net/web/src/Api/addChannelTopic.js b/net/web/src/Api/addChannelTopic.js new file mode 100644 index 00000000..bc41a320 --- /dev/null +++ b/net/web/src/Api/addChannelTopic.js @@ -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; +} + diff --git a/net/web/src/Api/getChannel.js b/net/web/src/Api/getChannel.js new file mode 100644 index 00000000..27d58d1a --- /dev/null +++ b/net/web/src/Api/getChannel.js @@ -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() +} + diff --git a/net/web/src/Api/getChannels.js b/net/web/src/Api/getChannels.js index 0312712c..85af261c 100644 --- a/net/web/src/Api/getChannels.js +++ b/net/web/src/Api/getChannels.js @@ -3,7 +3,7 @@ import { checkResponse, fetchWithTimeout } from './fetchUtil'; export async function getChannels(token, revision) { let param = "?agent=" + token if (revision != null) { - param += '&revision=' + revision + param += '&channelRevision=' + revision } let channels = await fetchWithTimeout('/content/channels' + param, { method: 'GET' }); checkResponse(channels) diff --git a/net/web/src/App.js b/net/web/src/App.js index e99f307c..5295d262 100644 --- a/net/web/src/App.js +++ b/net/web/src/App.js @@ -26,7 +26,8 @@ function App() { }> } /> } /> - } /> + } /> + } /> diff --git a/net/web/src/AppContext/useAppContext.hook.js b/net/web/src/AppContext/useAppContext.hook.js index 48257ee2..599d9153 100644 --- a/net/web/src/AppContext/useAppContext.hook.js +++ b/net/web/src/AppContext/useAppContext.hook.js @@ -1,6 +1,7 @@ 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 { getChannels } from '../Api/getChannels'; +import { getChannel } from '../Api/getChannel'; import { getContactChannels } from '../Api/getContactChannels'; async function updateAccount(token, updateData) { @@ -30,7 +31,22 @@ async function updateChannels(token, revision, channelMap, mergeChannels) { let channels = await getChannels(token, revision); for (let channel of channels) { 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 { channelMap.delete(channel.id); diff --git a/net/web/src/User/Conversation/AddTopic/AddTopic.jsx b/net/web/src/User/Conversation/AddTopic/AddTopic.jsx index ece3ce39..b8722a7c 100644 --- a/net/web/src/User/Conversation/AddTopic/AddTopic.jsx +++ b/net/web/src/User/Conversation/AddTopic/AddTopic.jsx @@ -1,6 +1,6 @@ import React from 'react'; 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 { useAddTopic } from './useAddTopic.hook'; import { BgColorsOutlined, FontColorsOutlined, FontSizeOutlined, PaperClipOutlined, SendOutlined } from '@ant-design/icons'; @@ -9,11 +9,6 @@ export function AddTopic() { const { state, actions } = useAddTopic(); - const onAttach = () => { - console.log("ADD IMAGE"); - actions.addImage(null); - } - const menu = ( @@ -28,6 +23,10 @@ export function AddTopic() { ); + const onSend = () => { + actions.addTopic(); + } + return (
@@ -52,7 +51,8 @@ export function AddTopic() {
-
diff --git a/net/web/src/User/Conversation/AddTopic/AddTopic.styled.js b/net/web/src/User/Conversation/AddTopic/AddTopic.styled.js index 79606ce3..af586f5c 100644 --- a/net/web/src/User/Conversation/AddTopic/AddTopic.styled.js +++ b/net/web/src/User/Conversation/AddTopic/AddTopic.styled.js @@ -1,3 +1,4 @@ +import { Spin } from 'antd'; import styled from 'styled-components'; export const AddTopicWrapper = styled.div` @@ -35,7 +36,14 @@ export const AddTopicWrapper = styled.div` display: flex; flex-grow: 1; justify-content: flex-end; + align-items: center; padding-top: 4px; } `; +export const BusySpin = styled(Spin)` + position: absolute; + margin-right: 12px; + x-index: 10; +`; + diff --git a/net/web/src/User/Conversation/AddTopic/useAddTopic.hook.js b/net/web/src/User/Conversation/AddTopic/useAddTopic.hook.js index 8c915e1a..2baade2c 100644 --- a/net/web/src/User/Conversation/AddTopic/useAddTopic.hook.js +++ b/net/web/src/User/Conversation/AddTopic/useAddTopic.hook.js @@ -1,5 +1,7 @@ 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() { @@ -7,11 +9,14 @@ export function useAddTopic() { assets: [], messageText: null, messageColor: null, - messageWeight: null, messageSize: null, backgroundColor: null, + busy: false, }); + const { contact, channel } = useParams(); + const app = useContext(AppContext); + const updateState = (value) => { setState((s) => ({ ...s, ...value })); } @@ -23,8 +28,6 @@ export function useAddTopic() { }); } - const navigate = useNavigate(); - const actions = { addImage: (image) => { addAsset(image) }, addVideo: (video) => { addAsset(video) }, @@ -46,7 +49,22 @@ export function useAddTopic() { setBackgroundColor: (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 }; diff --git a/net/web/src/User/Conversation/useConversation.hook.js b/net/web/src/User/Conversation/useConversation.hook.js index 55e7dfa0..ce6aa1a5 100644 --- a/net/web/src/User/Conversation/useConversation.hook.js +++ b/net/web/src/User/Conversation/useConversation.hook.js @@ -8,7 +8,7 @@ export function useConversation() { }); const data = useLocation(); - const { id } = useParams(); + const { contact, channel } = useParams(); const navigate = useNavigate(); 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 }; } diff --git a/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/useChannelItem.hook.jsx b/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/useChannelItem.hook.jsx index eb7912de..e5897f1c 100644 --- a/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/useChannelItem.hook.jsx +++ b/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/useChannelItem.hook.jsx @@ -14,7 +14,12 @@ export function useChannelItem() { const actions = { select: (item) => { - navigate(`/user/conversation/${item.channel.id}`); + if (item.guid) { + navigate(`/user/conversation/${item.guid}/${item.channel.id}`); + } + else { + navigate(`/user/conversation/${item.channel.id}`); + } }, };