diff --git a/src/Managing.Api/Controllers/UserController.cs b/src/Managing.Api/Controllers/UserController.cs index acbfcd0..0a8ca81 100644 --- a/src/Managing.Api/Controllers/UserController.cs +++ b/src/Managing.Api/Controllers/UserController.cs @@ -74,5 +74,18 @@ public class UserController : BaseController var updatedUser = await _userService.UpdateAgentName(user, agentName); return Ok(updatedUser); } + + /// + /// Updates the avatar URL for the current user. + /// + /// The new avatar URL to set. + /// The updated user with the new avatar URL. + [HttpPut("avatar")] + public async Task> UpdateAvatarUrl([FromBody] string avatarUrl) + { + var user = await GetUser(); + var updatedUser = await _userService.UpdateAvatarUrl(user, avatarUrl); + return Ok(updatedUser); + } } \ No newline at end of file diff --git a/src/Managing.Application.Abstractions/Services/IUserService.cs b/src/Managing.Application.Abstractions/Services/IUserService.cs index be8e8ad..ca09541 100644 --- a/src/Managing.Application.Abstractions/Services/IUserService.cs +++ b/src/Managing.Application.Abstractions/Services/IUserService.cs @@ -7,5 +7,6 @@ public interface IUserService Task Authenticate(string name, string address, string message, string signature); Task GetUserByAddressAsync(string address); Task UpdateAgentName(User user, string agentName); + Task UpdateAvatarUrl(User user, string avatarUrl); User GetUser(string name); } diff --git a/src/Managing.Application/Users/UserService.cs b/src/Managing.Application/Users/UserService.cs index 6314e42..ac1ea6b 100644 --- a/src/Managing.Application/Users/UserService.cs +++ b/src/Managing.Application/Users/UserService.cs @@ -1,4 +1,5 @@ -using Managing.Application.Abstractions.Repositories; +using System.Text.RegularExpressions; +using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Services; using Managing.Common; using Managing.Domain.Accounts; @@ -134,10 +135,33 @@ public class UserService : IUserService var existingUser = await _userRepository.GetUserByAgentNameAsync(agentName); if (existingUser != null) { - throw new Exception("Agent name already used"); + throw new Exception($"Agent name already used by {existingUser.Name}"); + } + else + { + user.AgentName = agentName; + await _userRepository.UpdateUser(user); + return user; + } + } + + public async Task UpdateAvatarUrl(User user, string avatarUrl) + { + // Validate URL format and image extension + if (!Uri.TryCreate(avatarUrl, UriKind.Absolute, out Uri? uriResult) || + (uriResult.Scheme != Uri.UriSchemeHttp && uriResult.Scheme != Uri.UriSchemeHttps)) + { + throw new Exception("Invalid URL format"); } - user.AgentName = agentName; + // Check for valid image extension + string pattern = @"\.(jpeg|jpg|png)$"; + if (!Regex.IsMatch(avatarUrl, pattern, RegexOptions.IgnoreCase)) + { + throw new Exception("URL must point to a JPEG or PNG image"); + } + + user.AvatarUrl = avatarUrl; await _userRepository.UpdateUser(user); return user; } diff --git a/src/Managing.Domain/Users/User.cs b/src/Managing.Domain/Users/User.cs index 9d01d24..62e7416 100644 --- a/src/Managing.Domain/Users/User.cs +++ b/src/Managing.Domain/Users/User.cs @@ -7,4 +7,5 @@ public class User public string Name { get; set; } public List Accounts { get; set; } public string AgentName { get; set; } + public string AvatarUrl { get; set; } } diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/UserDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/UserDto.cs index c0a81c3..74a2221 100644 --- a/src/Managing.Infrastructure.Database/MongoDb/Collections/UserDto.cs +++ b/src/Managing.Infrastructure.Database/MongoDb/Collections/UserDto.cs @@ -8,4 +8,5 @@ public class UserDto : Document { public string Name { get; set; } public string AgentName { get; set; } + public string AvatarUrl { get; set; } } \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs b/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs index dee8027..f2d6f15 100644 --- a/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs +++ b/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs @@ -532,7 +532,8 @@ public static class MongoMappers return new User { Name = user.Name, - AgentName = user.AgentName + AgentName = user.AgentName, + AvatarUrl = user.AvatarUrl, }; } @@ -541,7 +542,8 @@ public static class MongoMappers return new UserDto { Name = user.Name, - AgentName = user.AgentName + AgentName = user.AgentName, + AvatarUrl = user.AvatarUrl, }; } diff --git a/src/Managing.Infrastructure.Database/UserRepository.cs b/src/Managing.Infrastructure.Database/UserRepository.cs index 6e17c47..cf64404 100644 --- a/src/Managing.Infrastructure.Database/UserRepository.cs +++ b/src/Managing.Infrastructure.Database/UserRepository.cs @@ -38,6 +38,7 @@ public class UserRepository : IUserRepository { var dto = await _userRepository.FindOneAsync(u => u.Name == user.Name); dto.AgentName = user.AgentName; + dto.AvatarUrl = user.AvatarUrl; _userRepository.Update(dto); } catch (Exception e) diff --git a/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts b/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts index 2bf7694..d96f32a 100644 --- a/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts +++ b/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts @@ -459,8 +459,6 @@ export const closeGmxPositionImpl = async ( return position.marketInfo.indexToken.symbol === ticker && position.isLong === (direction === TradeDirection.Long); }); - console.log("positionsInfo", positionsInfo); - if (!positionKey) { throw new Error(`No open ${direction} position found for ${ticker}`); } diff --git a/src/Managing.Web3Proxy/test/plugins/close-position.test.ts b/src/Managing.Web3Proxy/test/plugins/close-position.test.ts index fb98bc9..ad6cfcd 100644 --- a/src/Managing.Web3Proxy/test/plugins/close-position.test.ts +++ b/src/Managing.Web3Proxy/test/plugins/close-position.test.ts @@ -4,15 +4,15 @@ import {closeGmxPositionImpl, getClientForAddress} from '../../src/plugins/custo import {TradeDirection} from '../../src/generated/ManagingApiTypes' test('GMX Position Closing', async (t) => { - await t.test('should close a long position for BTC', async () => { - const sdk = await getClientForAddress('0x932167388dD9aad41149b3cA23eBD489E2E2DD78') + await t.test('should close a long position for BTC', async () => { + const sdk = await getClientForAddress('0xbBA4eaA534cbD0EcAed5E2fD6036Aec2E7eE309f') - const result = await closeGmxPositionImpl( - sdk, - 'BNB', - TradeDirection.Long - ) - console.log('Position closing result:', result) - assert.ok(result, 'Position closing result should be defined') - }) + const result = await closeGmxPositionImpl( + sdk, + 'BNB', + TradeDirection.Long + ) + console.log('Position closing result:', result) + assert.ok(result, 'Position closing result should be defined') + }) }) diff --git a/src/Managing.WebApp/src/components/organism/Backtest/backtestRowDetails.tsx b/src/Managing.WebApp/src/components/organism/Backtest/backtestRowDetails.tsx index a8132bc..dbd328c 100644 --- a/src/Managing.WebApp/src/components/organism/Backtest/backtestRowDetails.tsx +++ b/src/Managing.WebApp/src/components/organism/Backtest/backtestRowDetails.tsx @@ -1,6 +1,6 @@ -import { TradeChart, CardPositionItem } from '..' -import { Backtest, MoneyManagement } from '../../../generated/ManagingApi' -import { CardPosition, CardText } from '../../mollecules' +import {CardPositionItem, TradeChart} from '..' +import {Backtest} from '../../../generated/ManagingApi' +import {CardPosition, CardText} from '../../mollecules' interface IBacktestRowDetailsProps { backtest: Backtest; @@ -21,7 +21,7 @@ const BacktestRowDetails: React.FC = ({ strategiesValues, signals, statistics, - moneyManagement + config } = backtest; return ( @@ -71,8 +71,8 @@ const BacktestRowDetails: React.FC = ({ (null as any); } + + user_UpdateAvatarUrl(avatarUrl: string): Promise { + let url_ = this.baseUrl + "/User/avatar"; + url_ = url_.replace(/[?&]$/, ""); + + const content_ = JSON.stringify(avatarUrl); + + let options_: RequestInit = { + body: content_, + method: "PUT", + headers: { + "Content-Type": "application/json", + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processUser_UpdateAvatarUrl(_response); + }); + } + + protected processUser_UpdateAvatarUrl(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as User; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } } export class WorkflowClient extends AuthorizedApiBase { @@ -2519,6 +2558,7 @@ export interface User { name?: string | null; accounts?: Account[] | null; agentName?: string | null; + avatarUrl?: string | null; } export interface Balance { diff --git a/src/Managing.WebApp/src/pages/settingsPage/UserInfoSettings.tsx b/src/Managing.WebApp/src/pages/settingsPage/UserInfoSettings.tsx index 871a8b1..cdb900f 100644 --- a/src/Managing.WebApp/src/pages/settingsPage/UserInfoSettings.tsx +++ b/src/Managing.WebApp/src/pages/settingsPage/UserInfoSettings.tsx @@ -10,8 +10,13 @@ type UpdateAgentNameForm = { agentName: string } +type UpdateAvatarForm = { + avatarUrl: string +} + function UserInfoSettings() { const [showUpdateModal, setShowUpdateModal] = useState(false) + const [showAvatarModal, setShowAvatarModal] = useState(false) const queryClient = useQueryClient() const { apiUrl } = useApiUrlStore() const api = new UserClient({}, apiUrl) @@ -22,12 +27,18 @@ function UserInfoSettings() { }) const { - register, - handleSubmit, - formState: { errors }, + register: registerAgentName, + handleSubmit: handleSubmitAgentName, + formState: { errors: agentNameErrors }, } = useForm() - const onSubmit = async (data: UpdateAgentNameForm) => { + const { + register: registerAvatar, + handleSubmit: handleSubmitAvatar, + formState: { errors: avatarErrors }, + } = useForm() + + const onSubmitAgentName = async (data: UpdateAgentNameForm) => { const toast = new Toast('Updating agent name') try { await api.user_UpdateAgentName(data.agentName) @@ -40,6 +51,19 @@ function UserInfoSettings() { } } + const onSubmitAvatar = async (data: UpdateAvatarForm) => { + const toast = new Toast('Updating avatar') + try { + await api.user_UpdateAvatarUrl(data.avatarUrl) + queryClient.invalidateQueries({ queryKey: ['user'] }) + setShowAvatarModal(false) + toast.update('success', 'Avatar updated successfully') + } catch (error) { + console.error('Error updating avatar:', error) + toast.update('error', 'Failed to update avatar') + } + } + return ( @@ -61,13 +85,36 @@ function UserInfoSettings() { Update Agent Name + + + Avatar: + + {user?.avatarUrl ? ( + + ) : ( + + {user?.name?.[0]?.toUpperCase() || '?'} + + )} + setShowAvatarModal(true)} + > + Update Avatar + + + setShowUpdateModal(false)} - onSubmit={handleSubmit(onSubmit)} + onSubmit={handleSubmitAgentName(onSubmitAgentName)} titleHeader="Update Agent Name" > @@ -77,13 +124,50 @@ function UserInfoSettings() { - {errors.agentName && ( + {agentNameErrors.agentName && ( - {errors.agentName.message} + {agentNameErrors.agentName.message} + + + )} + + + + Update + + + + + setShowAvatarModal(false)} + onSubmit={handleSubmitAvatar(onSubmitAvatar)} + titleHeader="Update Avatar" + > + + + Avatar URL (JPEG or PNG) + + + {avatarErrors.avatarUrl && ( + + + {avatarErrors.avatarUrl.message} )}