rhythia-api 238.0.0 → 239.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/api/editProfile.ts +136 -36
- package/api/getProfile.ts +51 -18
- package/index.ts +17 -9
- package/package.json +1 -1
- package/types/database.ts +29 -0
package/api/editProfile.ts
CHANGED
|
@@ -2,25 +2,57 @@ import { NextResponse } from "../utils/response";
|
|
|
2
2
|
import z from "zod";
|
|
3
3
|
import { Database } from "../types/database";
|
|
4
4
|
import { protectedApi, validUser } from "../utils/requestUtils";
|
|
5
|
-
import { supabase } from "../utils/supabase";
|
|
6
|
-
import { getUserBySession } from "../utils/getUserBySession";
|
|
7
|
-
import { User } from "@supabase/supabase-js";
|
|
8
|
-
import validator from "validator";
|
|
9
|
-
import removeZeroWidth from "zero-width";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
5
|
+
import { supabase } from "../utils/supabase";
|
|
6
|
+
import { getUserBySession } from "../utils/getUserBySession";
|
|
7
|
+
import { User } from "@supabase/supabase-js";
|
|
8
|
+
import validator from "validator";
|
|
9
|
+
import removeZeroWidth from "zero-width";
|
|
10
|
+
|
|
11
|
+
const USERNAME_CHANGE_COOLDOWN_ERROR =
|
|
12
|
+
"Username can only be changed once every 6 months";
|
|
13
|
+
|
|
14
|
+
function getNextUsernameChangeAt(
|
|
15
|
+
lastChangedAt: string | null | undefined
|
|
16
|
+
): string | null {
|
|
17
|
+
if (!lastChangedAt) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const nextAllowedAt = new Date(lastChangedAt);
|
|
22
|
+
nextAllowedAt.setUTCMonth(nextAllowedAt.getUTCMonth() + 6);
|
|
23
|
+
return nextAllowedAt.toISOString();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function formatUsernameChangeDate(date: string): string {
|
|
27
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
28
|
+
year: "numeric",
|
|
29
|
+
month: "short",
|
|
30
|
+
day: "numeric",
|
|
31
|
+
}).format(new Date(date));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isUsernameChangeLocked(
|
|
35
|
+
lastChangedAt: string | null | undefined,
|
|
36
|
+
now = Date.now()
|
|
37
|
+
): boolean {
|
|
38
|
+
const nextAllowedAt = getNextUsernameChangeAt(lastChangedAt);
|
|
39
|
+
return nextAllowedAt !== null && new Date(nextAllowedAt).getTime() > now;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const Schema = {
|
|
43
|
+
input: z.strictObject({
|
|
44
|
+
session: z.string(),
|
|
14
45
|
data: z.object({
|
|
15
46
|
avatar_url: z.string().optional(),
|
|
16
47
|
profile_image: z.string().optional(),
|
|
17
48
|
username: z.string().optional(),
|
|
18
49
|
}),
|
|
19
|
-
}),
|
|
20
|
-
output: z.object({
|
|
21
|
-
error: z.string().optional(),
|
|
22
|
-
|
|
23
|
-
}
|
|
50
|
+
}),
|
|
51
|
+
output: z.object({
|
|
52
|
+
error: z.string().optional(),
|
|
53
|
+
next_username_change_at: z.string().nullable().optional(),
|
|
54
|
+
}),
|
|
55
|
+
};
|
|
24
56
|
|
|
25
57
|
export async function POST(request: Request): Promise<NextResponse> {
|
|
26
58
|
return protectedApi({
|
|
@@ -70,9 +102,9 @@ export async function handler(
|
|
|
70
102
|
},
|
|
71
103
|
{ status: 404 }
|
|
72
104
|
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
data.data.username = removeZeroWidth(data.data.username || "");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
data.data.username = removeZeroWidth(data.data.username || "");
|
|
76
108
|
|
|
77
109
|
const user = (await getUserBySession(data.session)) as User;
|
|
78
110
|
|
|
@@ -96,24 +128,62 @@ export async function handler(
|
|
|
96
128
|
userData = queryUserData[0];
|
|
97
129
|
}
|
|
98
130
|
|
|
99
|
-
if (
|
|
100
|
-
userData.ban == "excluded" ||
|
|
101
|
-
userData.ban == "restricted" ||
|
|
102
|
-
userData.ban == "silenced"
|
|
103
|
-
) {
|
|
131
|
+
if (
|
|
132
|
+
userData.ban == "excluded" ||
|
|
133
|
+
userData.ban == "restricted" ||
|
|
134
|
+
userData.ban == "silenced"
|
|
135
|
+
) {
|
|
104
136
|
return NextResponse.json(
|
|
105
137
|
{
|
|
106
138
|
error:
|
|
107
139
|
"Silenced, restricted or excluded players can't update their profile.",
|
|
108
140
|
},
|
|
109
141
|
{ status: 404 }
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const usernameHasChanged =
|
|
146
|
+
data.data.username !== undefined && data.data.username !== userData.username;
|
|
147
|
+
|
|
148
|
+
let nextUsernameChangeAt: string | null = null;
|
|
149
|
+
|
|
150
|
+
if (usernameHasChanged) {
|
|
151
|
+
const { data: usernameHistory, error: usernameHistoryError } = await supabase
|
|
152
|
+
.from("profileUsernames")
|
|
153
|
+
.select("changed_at")
|
|
154
|
+
.eq("profile_id", userData.id!)
|
|
155
|
+
.order("changed_at", { ascending: false })
|
|
156
|
+
.limit(1);
|
|
157
|
+
|
|
158
|
+
if (usernameHistoryError) {
|
|
159
|
+
return NextResponse.json(
|
|
160
|
+
{
|
|
161
|
+
error: "Could not verify username change availability.",
|
|
162
|
+
},
|
|
163
|
+
{ status: 500 }
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const lastChangedAt = usernameHistory?.[0]?.changed_at ?? null;
|
|
168
|
+
nextUsernameChangeAt = getNextUsernameChangeAt(lastChangedAt);
|
|
169
|
+
|
|
170
|
+
if (isUsernameChangeLocked(lastChangedAt)) {
|
|
171
|
+
return NextResponse.json(
|
|
172
|
+
{
|
|
173
|
+
error: `${USERNAME_CHANGE_COOLDOWN_ERROR}. Next change available ${formatUsernameChangeDate(
|
|
174
|
+
nextUsernameChangeAt!
|
|
175
|
+
)}.`,
|
|
176
|
+
next_username_change_at: nextUsernameChangeAt,
|
|
177
|
+
},
|
|
178
|
+
{ status: 404 }
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const upsertPayload: Database["public"]["Tables"]["profiles"]["Update"] = {
|
|
184
|
+
id: userData.id,
|
|
185
|
+
computedUsername: data.data.username?.toLowerCase(),
|
|
186
|
+
...data.data,
|
|
117
187
|
};
|
|
118
188
|
|
|
119
189
|
const upsertResult = await supabase
|
|
@@ -121,14 +191,44 @@ export async function handler(
|
|
|
121
191
|
.upsert(upsertPayload)
|
|
122
192
|
.select();
|
|
123
193
|
|
|
124
|
-
if (upsertResult.error) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
194
|
+
if (upsertResult.error) {
|
|
195
|
+
if (
|
|
196
|
+
usernameHasChanged &&
|
|
197
|
+
upsertResult.error.message.includes(USERNAME_CHANGE_COOLDOWN_ERROR)
|
|
198
|
+
) {
|
|
199
|
+
if (!nextUsernameChangeAt) {
|
|
200
|
+
const { data: usernameHistory } = await supabase
|
|
201
|
+
.from("profileUsernames")
|
|
202
|
+
.select("changed_at")
|
|
203
|
+
.eq("profile_id", userData.id!)
|
|
204
|
+
.order("changed_at", { ascending: false })
|
|
205
|
+
.limit(1);
|
|
206
|
+
|
|
207
|
+
nextUsernameChangeAt = getNextUsernameChangeAt(
|
|
208
|
+
usernameHistory?.[0]?.changed_at ?? null
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return NextResponse.json(
|
|
213
|
+
{
|
|
214
|
+
error: `${USERNAME_CHANGE_COOLDOWN_ERROR}. Next change available ${
|
|
215
|
+
nextUsernameChangeAt
|
|
216
|
+
? formatUsernameChangeDate(nextUsernameChangeAt)
|
|
217
|
+
: "later"
|
|
218
|
+
}.`,
|
|
219
|
+
next_username_change_at: nextUsernameChangeAt,
|
|
220
|
+
},
|
|
221
|
+
{ status: 404 }
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return NextResponse.json(
|
|
226
|
+
{
|
|
227
|
+
error: "Can't update, username might be used by someone else!",
|
|
128
228
|
},
|
|
129
229
|
{ status: 404 }
|
|
130
230
|
);
|
|
131
231
|
}
|
|
132
|
-
|
|
133
|
-
return NextResponse.json({});
|
|
134
|
-
}
|
|
232
|
+
|
|
233
|
+
return NextResponse.json({});
|
|
234
|
+
}
|
package/api/getProfile.ts
CHANGED
|
@@ -5,14 +5,26 @@ import { Database } from "../types/database";
|
|
|
5
5
|
import { protectedApi } from "../utils/requestUtils";
|
|
6
6
|
import { supabase } from "../utils/supabase";
|
|
7
7
|
import { getUserBySession } from "../utils/getUserBySession";
|
|
8
|
-
import { User } from "@supabase/supabase-js";
|
|
9
|
-
import {
|
|
10
|
-
getActivityStatusForUserId,
|
|
11
|
-
getScoreActivityCutoffIso,
|
|
12
|
-
} from "../utils/activityStatus";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
import { User } from "@supabase/supabase-js";
|
|
9
|
+
import {
|
|
10
|
+
getActivityStatusForUserId,
|
|
11
|
+
getScoreActivityCutoffIso,
|
|
12
|
+
} from "../utils/activityStatus";
|
|
13
|
+
|
|
14
|
+
function getNextUsernameChangeAt(
|
|
15
|
+
lastChangedAt: string | null | undefined
|
|
16
|
+
): string | null {
|
|
17
|
+
if (!lastChangedAt) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const nextAllowedAt = new Date(lastChangedAt);
|
|
22
|
+
nextAllowedAt.setUTCMonth(nextAllowedAt.getUTCMonth() + 6);
|
|
23
|
+
return nextAllowedAt.toISOString();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const Schema = {
|
|
27
|
+
input: z.strictObject({
|
|
16
28
|
session: z.string(),
|
|
17
29
|
id: z.number().nullable().optional(),
|
|
18
30
|
}),
|
|
@@ -41,6 +53,13 @@ export const Schema = {
|
|
|
41
53
|
activity_status: z.enum(["active", "inactive"]),
|
|
42
54
|
is_online: z.boolean(),
|
|
43
55
|
last_active_timestamp: z.number().nullable(),
|
|
56
|
+
next_username_change_at: z.string().nullable(),
|
|
57
|
+
previous_usernames: z.array(
|
|
58
|
+
z.object({
|
|
59
|
+
username: z.string(),
|
|
60
|
+
changed_at: z.string(),
|
|
61
|
+
})
|
|
62
|
+
),
|
|
44
63
|
clans: z
|
|
45
64
|
.object({
|
|
46
65
|
id: z.number(),
|
|
@@ -128,6 +147,12 @@ export async function handler(
|
|
|
128
147
|
.select("last_activity")
|
|
129
148
|
.eq("uid", user.uid || "")
|
|
130
149
|
.single();
|
|
150
|
+
|
|
151
|
+
const { data: usernameHistoryData } = await supabase
|
|
152
|
+
.from("profileUsernames")
|
|
153
|
+
.select("username,changed_at")
|
|
154
|
+
.eq("profile_id", user.id)
|
|
155
|
+
.order("changed_at", { ascending: false });
|
|
131
156
|
|
|
132
157
|
//last 30 minutes
|
|
133
158
|
if (activityData && activityData.last_activity) {
|
|
@@ -165,24 +190,32 @@ export async function handler(
|
|
|
165
190
|
: null;
|
|
166
191
|
}
|
|
167
192
|
|
|
168
|
-
if (user.verificationDeadline < Date.now()) {
|
|
169
|
-
await supabase
|
|
170
|
-
.from("profiles")
|
|
171
|
-
.upsert({
|
|
193
|
+
if (user.verificationDeadline < Date.now()) {
|
|
194
|
+
await supabase
|
|
195
|
+
.from("profiles")
|
|
196
|
+
.upsert({
|
|
172
197
|
id: user.id,
|
|
173
198
|
verified: false,
|
|
174
|
-
})
|
|
175
|
-
.select();
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
user
|
|
199
|
+
})
|
|
200
|
+
.select();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const previousUsernames = (usernameHistoryData || []).filter((entry) => {
|
|
204
|
+
return entry.username.toLowerCase() !== (user.username || "").toLowerCase();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const latestUsernameChangeAt = usernameHistoryData?.[0]?.changed_at ?? null;
|
|
208
|
+
|
|
209
|
+
return NextResponse.json({
|
|
210
|
+
user: {
|
|
180
211
|
...user,
|
|
181
212
|
position,
|
|
182
213
|
country_position: countryPosition,
|
|
183
214
|
activity_status: activityStatus,
|
|
184
215
|
is_online: isOnline,
|
|
185
216
|
last_active_timestamp: activityData?.last_activity ?? null,
|
|
217
|
+
next_username_change_at: getNextUsernameChangeAt(latestUsernameChangeAt),
|
|
218
|
+
previous_usernames: previousUsernames,
|
|
186
219
|
},
|
|
187
220
|
});
|
|
188
221
|
}
|
package/index.ts
CHANGED
|
@@ -301,18 +301,19 @@ export const editCollection = handleApi({url:"/api/editCollection",...EditCollec
|
|
|
301
301
|
// ./api/editProfile.ts API
|
|
302
302
|
|
|
303
303
|
/*
|
|
304
|
-
export const Schema = {
|
|
305
|
-
input: z.strictObject({
|
|
306
|
-
session: z.string(),
|
|
304
|
+
export const Schema = {
|
|
305
|
+
input: z.strictObject({
|
|
306
|
+
session: z.string(),
|
|
307
307
|
data: z.object({
|
|
308
308
|
avatar_url: z.string().optional(),
|
|
309
309
|
profile_image: z.string().optional(),
|
|
310
310
|
username: z.string().optional(),
|
|
311
311
|
}),
|
|
312
|
-
}),
|
|
313
|
-
output: z.object({
|
|
314
|
-
error: z.string().optional(),
|
|
315
|
-
|
|
312
|
+
}),
|
|
313
|
+
output: z.object({
|
|
314
|
+
error: z.string().optional(),
|
|
315
|
+
next_username_change_at: z.string().nullable().optional(),
|
|
316
|
+
}),
|
|
316
317
|
};*/
|
|
317
318
|
import { Schema as EditProfile } from "./api/editProfile"
|
|
318
319
|
export { Schema as SchemaEditProfile } from "./api/editProfile"
|
|
@@ -962,8 +963,8 @@ export const getPassToken = handleApi({url:"/api/getPassToken",...GetPassToken})
|
|
|
962
963
|
// ./api/getProfile.ts API
|
|
963
964
|
|
|
964
965
|
/*
|
|
965
|
-
export const Schema = {
|
|
966
|
-
input: z.strictObject({
|
|
966
|
+
export const Schema = {
|
|
967
|
+
input: z.strictObject({
|
|
967
968
|
session: z.string(),
|
|
968
969
|
id: z.number().nullable().optional(),
|
|
969
970
|
}),
|
|
@@ -992,6 +993,13 @@ export const Schema = {
|
|
|
992
993
|
activity_status: z.enum(["active", "inactive"]),
|
|
993
994
|
is_online: z.boolean(),
|
|
994
995
|
last_active_timestamp: z.number().nullable(),
|
|
996
|
+
next_username_change_at: z.string().nullable(),
|
|
997
|
+
previous_usernames: z.array(
|
|
998
|
+
z.object({
|
|
999
|
+
username: z.string(),
|
|
1000
|
+
changed_at: z.string(),
|
|
1001
|
+
})
|
|
1002
|
+
),
|
|
995
1003
|
clans: z
|
|
996
1004
|
.object({
|
|
997
1005
|
id: z.number(),
|
package/package.json
CHANGED
package/types/database.ts
CHANGED
|
@@ -558,6 +558,35 @@ export type Database = {
|
|
|
558
558
|
},
|
|
559
559
|
]
|
|
560
560
|
}
|
|
561
|
+
profileUsernames: {
|
|
562
|
+
Row: {
|
|
563
|
+
changed_at: string
|
|
564
|
+
id: number
|
|
565
|
+
profile_id: number
|
|
566
|
+
username: string
|
|
567
|
+
}
|
|
568
|
+
Insert: {
|
|
569
|
+
changed_at?: string
|
|
570
|
+
id?: number
|
|
571
|
+
profile_id: number
|
|
572
|
+
username: string
|
|
573
|
+
}
|
|
574
|
+
Update: {
|
|
575
|
+
changed_at?: string
|
|
576
|
+
id?: number
|
|
577
|
+
profile_id?: number
|
|
578
|
+
username?: string
|
|
579
|
+
}
|
|
580
|
+
Relationships: [
|
|
581
|
+
{
|
|
582
|
+
foreignKeyName: "profileUsernames_profile_id_fkey"
|
|
583
|
+
columns: ["profile_id"]
|
|
584
|
+
isOneToOne: false
|
|
585
|
+
referencedRelation: "profiles"
|
|
586
|
+
referencedColumns: ["id"]
|
|
587
|
+
},
|
|
588
|
+
]
|
|
589
|
+
}
|
|
561
590
|
profiles: {
|
|
562
591
|
Row: {
|
|
563
592
|
about_me: string | null
|