-
Notifications
You must be signed in to change notification settings - Fork 34
Expand file tree
/
Copy pathUserApiEndpoints.cs
More file actions
142 lines (123 loc) · 7.55 KB
/
Copy pathUserApiEndpoints.cs
File metadata and controls
142 lines (123 loc) · 7.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
using AttribDoc.Attributes;
using Bunkum.Core;
using Bunkum.Core.Endpoints;
using Bunkum.Core.RateLimit;
using Bunkum.Core.Storage;
using Bunkum.Protocols.Http;
using Refresh.Common.Constants;
using Refresh.Core.Authentication.Permission;
using Refresh.Core.Configuration;
using Refresh.Core.RateLimits.Relations;
using Refresh.Core.RateLimits.Users;
using Refresh.Core.Services;
using Refresh.Core.Types.Data;
using Refresh.Database;
using Refresh.Database.Models.Authentication;
using Refresh.Database.Models.Pins;
using Refresh.Database.Models.Users;
using Refresh.Interfaces.APIv3.Documentation.Attributes;
using Refresh.Interfaces.APIv3.Documentation.Descriptions;
using Refresh.Interfaces.APIv3.Endpoints.ApiTypes;
using Refresh.Interfaces.APIv3.Endpoints.ApiTypes.Errors;
using Refresh.Interfaces.APIv3.Endpoints.DataTypes.Request;
using Refresh.Interfaces.APIv3.Endpoints.DataTypes.Response.Users;
using Refresh.Interfaces.APIv3.Extensions;
namespace Refresh.Interfaces.APIv3.Endpoints;
public class UserApiEndpoints : EndpointGroup
{
[ApiV3Endpoint("users/{idType}/{id}"), Authentication(false)]
[DocSummary("Tries to find a user by name or UUID")]
[DocError(typeof(ApiNotFoundError), "The user cannot be found")]
[RateLimitSettings(SingleUserEndpointLimits.TimeoutDuration, SingleUserEndpointLimits.ApiRequestAmount,
SingleUserEndpointLimits.BlockDuration, SingleUserEndpointLimits.ApiRequestBucket)]
public ApiResponse<ApiGameUserResponse> GetUser(RequestContext context, GameDatabaseContext database,
[DocSummary(SharedParamDescriptions.UserIdParam)] string id,
[DocSummary(SharedParamDescriptions.UserIdTypeParam)] string idType, DataContext dataContext)
{
GameUser? user = database.GetUserByIdAndType(idType, id);
if(user == null) return ApiNotFoundError.UserMissingError;
return ApiGameUserResponse.FromOld(user, dataContext);
}
// TODO: Also allow specifying user by username
[ApiV3Endpoint("users/{idType}/{id}/heart", HttpMethods.Post)]
[DocSummary("Hearts a user by their name or UUID")]
[DocError(typeof(ApiNotFoundError), ApiNotFoundError.UserMissingErrorWhen)]
[RateLimitSettings(CommonRelationEndpointLimits.TimeoutDuration, CommonRelationEndpointLimits.RequestAmount,
CommonRelationEndpointLimits.BlockDuration, CommonRelationEndpointLimits.RequestBucket)]
public ApiOkResponse HeartUser(RequestContext context, GameDatabaseContext database,
[DocSummary(SharedParamDescriptions.UserIdParam)] string id,
[DocSummary(SharedParamDescriptions.UserIdTypeParam)] string idType, DataContext dataContext, GameUser user, GameServerConfig config)
{
if (user.IsWriteBlocked(config))
return ApiAuthenticationError.ReadOnlyError;
GameUser? target = database.GetUserByIdAndType(idType, id);
if(target == null) return ApiNotFoundError.UserMissingError;
bool success = database.FavouriteUser(target, user);
dataContext.Cache.UpdateUserHeartedStatusByUser(user, target, true, database);
// Only give pin if the user was hearted without having already been hearted.
// Won't protect against spam, but this way the pin objective is more accurately implemented.
if (success)
database.IncrementUserPinProgress((long)ServerPins.HeartPlayerOnWebsite, 1, user, false, TokenPlatform.Website);
return new ApiOkResponse();
}
[ApiV3Endpoint("users/{idType}/{id}/unheart", HttpMethods.Post)]
[DocSummary("Unhearts a user by their name or UUID")]
[DocError(typeof(ApiNotFoundError), ApiNotFoundError.UserMissingErrorWhen)]
[RateLimitSettings(CommonRelationEndpointLimits.TimeoutDuration, CommonRelationEndpointLimits.RequestAmount,
CommonRelationEndpointLimits.BlockDuration, CommonRelationEndpointLimits.RequestBucket)]
public ApiOkResponse UnheartUser(RequestContext context, GameDatabaseContext database,
[DocSummary(SharedParamDescriptions.UserIdParam)] string id,
[DocSummary(SharedParamDescriptions.UserIdTypeParam)] string idType, DataContext dataContext, GameUser user, GameServerConfig config)
{
if (user.IsWriteBlocked(config))
return ApiAuthenticationError.ReadOnlyError;
GameUser? target = database.GetUserByIdAndType(idType, id);
if(target == null) return ApiNotFoundError.UserMissingError;
database.UnfavouriteUser(target, user);
dataContext.Cache.UpdateUserHeartedStatusByUser(user, target, false, database);
return new ApiOkResponse();
}
[ApiV3Endpoint("users/me"), MinimumRole(GameUserRole.Restricted)]
[DocSummary("Returns your own user, provided you are authenticated")]
[DocError(typeof(ApiAuthenticationError), "You are not authenticated")]
[RateLimitSettings(120, 35, 80, "me-api")]
public ApiResponse<ApiExtendedGameUserResponse> GetMyUser(RequestContext context, GameUser? user,
GameDatabaseContext database, IDataStore dataStore, DataContext dataContext)
{
if (user == null) return ApiAuthenticationError.NotAuthenticated;
return ApiExtendedGameUserResponse.FromOld(user, dataContext);
}
[ApiV3Endpoint("users/me/previousUsernames"), MinimumRole(GameUserRole.Moderator)]
[DocSummary("Gets all previous usernames which you have used.")]
[DocUsesPageData]
[RateLimitSettings(120, 35, 80, "me-api")] // TODO remove when we clean up rate-limit stats
public ApiListResponse<ApiExtendedPreviousUsernameResponse> GetMyPreviousUsernames(RequestContext context,
GameDatabaseContext database, IDataStore dataStore, DataContext dataContext, GameUser user)
{
(int skip, int count) = context.GetPageData();
DatabaseList<PreviousUsername> previousNames = database.GetPreviousUsernameRecordsByUser(user, skip, count);
return DatabaseListExtensions.FromOldList<ApiExtendedPreviousUsernameResponse, PreviousUsername>(previousNames, dataContext);
}
[ApiV3Endpoint("users/me", HttpMethods.Patch)]
[DocSummary("Updates your profile with the given data")]
[RateLimitSettings(UserModificationEndpointLimits.TimeoutDuration, UserModificationEndpointLimits.ApiRequestAmount,
UserModificationEndpointLimits.BlockDuration, UserModificationEndpointLimits.ApiRequestBucket)]
public ApiResponse<ApiExtendedGameUserResponse> UpdateUser(RequestContext context, GameDatabaseContext database,
GameUser user, ApiUpdateUserRequest body, IDataStore dataStore, DataContext dataContext, IntegrationConfig integrationConfig,
SmtpService smtpService)
{
(body.IconHash, ApiError? mainIconError) = body.IconHash.ValidateIcon(dataContext);
if (mainIconError != null) return mainIconError;
(body.VitaIconHash, ApiError? vitaIconError) = body.VitaIconHash.ValidateIcon(dataContext);
if (vitaIconError != null) return vitaIconError;
(body.BetaIconHash, ApiError? betaIconError) = body.BetaIconHash.ValidateIcon(dataContext);
if (betaIconError != null) return betaIconError;
if (body.EmailAddress != null && !smtpService.CheckEmailDomainValidity(body.EmailAddress))
return ApiValidationError.EmailDoesNotActuallyExistError;
// Trim description
if (body.Description != null && body.Description.Length > UgcLimits.DescriptionLimit)
body.Description = body.Description[..UgcLimits.DescriptionLimit];
database.UpdateUserData(user, body);
return ApiExtendedGameUserResponse.FromOld(user, dataContext);
}
}