diff --git a/net/server/internal/api_addChannelTopicAsset.go b/net/server/internal/api_addChannelTopicAsset.go
index 42220a33..5fd39c86 100644
--- a/net/server/internal/api_addChannelTopicAsset.go
+++ b/net/server/internal/api_addChannelTopicAsset.go
@@ -68,6 +68,8 @@ func AddChannelTopicAsset(w http.ResponseWriter, r *http.Request) {
garbageSync.Lock()
defer garbageSync.Unlock()
+PrintMsg("SAVE NEW ASSET");
+
// save new file
id := uuid.New().String()
path := getStrConfigValue(CONFIG_ASSETPATH, APP_DEFAULTPATH) + "/" + channelSlot.Account.Guid + "/" + id
diff --git a/net/server/internal/api_setAccountLogin.go b/net/server/internal/api_setAccountLogin.go
new file mode 100644
index 00000000..cc59078f
--- /dev/null
+++ b/net/server/internal/api_setAccountLogin.go
@@ -0,0 +1,46 @@
+package databag
+
+import (
+ "net/http"
+ "gorm.io/gorm"
+ "databag/internal/store"
+)
+
+func SetAccountLogin(w http.ResponseWriter, r *http.Request) {
+
+ account, code, err := ParamAgentToken(r, true);
+ if err != nil {
+ ErrResponse(w, code, err)
+ return
+ }
+
+ username, password, ret := BasicCredentials(r);
+ if ret != nil {
+ ErrResponse(w, http.StatusUnauthorized, ret)
+ return
+ }
+
+ err = store.DB.Transaction(func(tx *gorm.DB) error {
+ if res := tx.Model(&account).Update("account_revision", account.AccountRevision + 1).Error; res != nil {
+ return res
+ }
+ if res := tx.Model(&account).Update("profile_revision", account.AccountRevision + 1).Error; res != nil {
+ return res
+ }
+ if res := tx.Model(&account).Update("Username", username).Error; res != nil {
+ return res
+ }
+ if res := tx.Model(&account).Update("Password", password).Error; res != nil {
+ return res
+ }
+ return nil
+ })
+ if err != nil {
+ ErrResponse(w, http.StatusInternalServerError, err)
+ return
+ }
+
+ SetStatus(account)
+ WriteResponse(w, nil)
+}
+
diff --git a/net/server/internal/routers.go b/net/server/internal/routers.go
index fd86ba45..44ef51b4 100644
--- a/net/server/internal/routers.go
+++ b/net/server/internal/routers.go
@@ -165,6 +165,13 @@ var routes = Routes{
SetAccountNode,
},
+ Route{
+ "SetAccountLogin",
+ strings.ToUpper("Put"),
+ "/account/login",
+ SetAccountLogin,
+ },
+
Route{
"SetAccountSerchable",
strings.ToUpper("Put"),
diff --git a/net/web/src/User/SideBar/Identity/Identity.jsx b/net/web/src/User/SideBar/Identity/Identity.jsx
index 7eb4c5f3..b79aa4fc 100644
--- a/net/web/src/User/SideBar/Identity/Identity.jsx
+++ b/net/web/src/User/SideBar/Identity/Identity.jsx
@@ -1,13 +1,14 @@
-import { Avatar, Image } from 'antd';
-import React from 'react'
+import { Avatar, Space, Image, Modal, Form, Input } from 'antd';
+import React, { useState } from 'react'
import { IdentityWrapper, IdentityDropdown, MenuWrapper } from './Identity.styled';
-import { RightOutlined, EditOutlined, UserOutlined } from '@ant-design/icons';
+import { RightOutlined, EditOutlined, UserOutlined, LockOutlined } from '@ant-design/icons';
import { useIdentity } from './useIdentity.hook';
import { Menu, Dropdown } from 'antd';
import { Logo } from '../../../Logo/Logo';
export function Identity() {
+ const [ showLogin, setShowLogin ] = useState(false);
const { state, actions } = useIdentity()
const menu = (
@@ -16,7 +17,7 @@ export function Identity() {
actions.editProfile()}>Edit Profile
- Change Login
+ setShowLogin(true)}>Change Login
actions.logout()}>Sign Out
@@ -24,6 +25,11 @@ export function Identity() {
);
+ const onChangeLogin = () => {
+ let saved = actions.setLogin();
+ setShowLogin(false);
+ };
+
return (
@@ -37,7 +43,24 @@ export function Identity() {
-
+
+ onChangeLogin()} onCancel={() => setShowLogin(false)}>
+
+
+ }
+ onChange={(e) => actions.setUsername(e.target.value)} defaultValue={state.handle}
+ addonAfter={state.usernameStatus} />
+
+ }
+ onChange={(e) => actions.setPassword(e.target.value)}
+ addonAfter={state.passwordStatus} />
+
+ }
+ onChange={(e) => actions.setConfirm(e.target.value)}
+ addonAfter={state.confirmStatus} />
+
+
)
}
diff --git a/net/web/src/User/SideBar/Identity/useIdentity.hook.js b/net/web/src/User/SideBar/Identity/useIdentity.hook.js
index c63ae5ff..ba90d695 100644
--- a/net/web/src/User/SideBar/Identity/useIdentity.hook.js
+++ b/net/web/src/User/SideBar/Identity/useIdentity.hook.js
@@ -1,7 +1,9 @@
-import { useContext, useState, useEffect } from 'react';
+import { useContext, useState, useRef, useEffect } from 'react';
import { AppContext } from 'context/AppContext';
import { ProfileContext } from 'context/ProfileContext';
+import { AccountContext } from 'context/AccountContext';
import { useNavigate } from "react-router-dom";
+import { getUsername } from 'api/getUsername';
export function useIdentity() {
@@ -11,11 +13,21 @@ export function useIdentity() {
domain: '',
imageUrl: null,
image: null,
+
+ username: null,
+ usernameStatus: null,
+ password: null,
+ passwordStatus: null,
+ confirm: null,
+ confirmStatus: null,
+
});
const navigate = useNavigate();
const profile = useContext(ProfileContext);
+ const account = useContext(AccountContext);
const app = useContext(AppContext);
+ const debounce = useRef(null);
const updateState = (value) => {
setState((s) => ({ ...s, ...value }));
@@ -31,7 +43,56 @@ export function useIdentity() {
},
editProfile: () => {
navigate('/user/profile');
- }
+ },
+ setUsername: (value) => {
+ if (debounce.current) {
+ clearTimeout(debounce.current);
+ }
+ updateState({ username: value });
+ if (state.handle.toLowerCase() == value.toLowerCase() || value == null || value == '') {
+ updateState({ usernameStatus: null });
+ return;
+ }
+ debounce.current = setTimeout(async () => {
+ let available = await getUsername(value);
+ if (available) {
+ updateState({ usernameStatus: null });
+ }
+ else {
+ updateState({ usernameStatus: 'not available' });
+ }
+ }, 500);
+ },
+ setPassword: (value) => {
+ updateState({ password: value });
+ },
+ setConfirm: (value) => {
+ updateState({ confirm: value });
+ },
+ setLogin: async () => {
+ if (state.username == null || state.username == '') {
+ updateState({ usernameStatus: 'username required' });
+ throw 'username required';
+ }
+ else {
+ updateState({ usernameStatus: null });
+ }
+ if (state.password == null || state.password == '') {
+ updateState({ passwordStatus: 'password required' });
+ throw 'password required';
+ }
+ else {
+ updateState({ passwordStatus: null });
+ }
+ if (state.confirm != state.password) {
+ updateState({ confirmStatus: 'password mismatch' });
+ throw 'password mismatch';
+ }
+ else {
+ updateState({ confirmStatus: null });
+ }
+ await account.actions.setLogin(state.username, state.password);
+ },
};
useEffect(() => {
@@ -47,6 +108,7 @@ export function useIdentity() {
updateState({ image: identity.image });
updateState({ name: identity.name });
updateState({ handle: identity.handle });
+ updateState({ username: identity.handle });
updateState({ domain: identity.node });
}
}, [profile])
diff --git a/net/web/src/api/getUsername.js b/net/web/src/api/getUsername.js
new file mode 100644
index 00000000..e7f3b8db
--- /dev/null
+++ b/net/web/src/api/getUsername.js
@@ -0,0 +1,8 @@
+import { checkResponse, fetchWithTimeout } from './fetchUtil';
+
+export async function getUsername(name) {
+ let available = await fetchWithTimeout('/account/username?name=' + encodeURIComponent(name), { method: 'GET' })
+ checkResponse(available)
+ return await available.json()
+}
+
diff --git a/net/web/src/api/setAccountLogin.js b/net/web/src/api/setAccountLogin.js
new file mode 100644
index 00000000..cb108f74
--- /dev/null
+++ b/net/web/src/api/setAccountLogin.js
@@ -0,0 +1,10 @@
+import { checkResponse, fetchWithTimeout } from './fetchUtil';
+var base64 = require('base-64');
+
+export async function setAccountLogin(token, username, password) {
+ let headers = new Headers()
+ headers.append('Credentials', 'Basic ' + base64.encode(username + ":" + password));
+ let res = await fetchWithTimeout(`/account/login?agent=${token}`, { method: 'PUT', headers })
+ checkResponse(res);
+}
+
diff --git a/net/web/src/context/useAccountContext.hook.js b/net/web/src/context/useAccountContext.hook.js
index 558a6965..c62db36a 100644
--- a/net/web/src/context/useAccountContext.hook.js
+++ b/net/web/src/context/useAccountContext.hook.js
@@ -1,6 +1,7 @@
import { useEffect, useState, useRef } from 'react';
import { setAccountSearchable } from 'api/setAccountSearchable';
import { getAccountStatus } from 'api/getAccountStatus';
+import { setAccountLogin } from 'api/setAccountLogin';
export function useAccountContext() {
const [state, setState] = useState({
@@ -47,6 +48,9 @@ export function useAccountContext() {
setSearchable: async (flag) => {
await setAccountSearchable(access.current, flag);
},
+ setLogin: async (username, password) => {
+ await setAccountLogin(access.current, username, password);
+ },
}
return { state, actions }