rhythia-api 239.0.0 → 241.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 +160 -98
- package/api/getProfile.ts +8 -0
- package/index.ts +8 -5
- package/package.json +2 -1
- package/types/database.ts +29 -0
- package/utils/countryList.ts +141 -0
package/api/editProfile.ts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
import { NextResponse } from "../utils/response";
|
|
2
|
-
import z from "zod";
|
|
3
|
-
import { Database } from "../types/database";
|
|
4
|
-
import { protectedApi, validUser } from "../utils/requestUtils";
|
|
1
|
+
import { NextResponse } from "../utils/response";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
import { Database } from "../types/database";
|
|
4
|
+
import { protectedApi, validUser } from "../utils/requestUtils";
|
|
5
5
|
import { supabase } from "../utils/supabase";
|
|
6
6
|
import { getUserBySession } from "../utils/getUserBySession";
|
|
7
|
+
import { COUNTRY_LIST } from "../utils/countryList";
|
|
7
8
|
import { User } from "@supabase/supabase-js";
|
|
8
9
|
import validator from "validator";
|
|
9
10
|
import removeZeroWidth from "zero-width";
|
|
10
11
|
|
|
11
12
|
const USERNAME_CHANGE_COOLDOWN_ERROR =
|
|
12
13
|
"Username can only be changed once every 6 months";
|
|
14
|
+
const FLAG_CHANGE_ERROR = "Flag can only be changed once";
|
|
15
|
+
const countryCodes = new Set(COUNTRY_LIST);
|
|
13
16
|
|
|
14
17
|
function getNextUsernameChangeAt(
|
|
15
18
|
lastChangedAt: string | null | undefined
|
|
@@ -42,110 +45,130 @@ function isUsernameChangeLocked(
|
|
|
42
45
|
export const Schema = {
|
|
43
46
|
input: z.strictObject({
|
|
44
47
|
session: z.string(),
|
|
45
|
-
data: z.object({
|
|
46
|
-
avatar_url: z.string().optional(),
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
data: z.object({
|
|
49
|
+
avatar_url: z.string().optional(),
|
|
50
|
+
flag: z.string().optional(),
|
|
51
|
+
profile_image: z.string().optional(),
|
|
52
|
+
username: z.string().optional(),
|
|
53
|
+
}),
|
|
50
54
|
}),
|
|
51
55
|
output: z.object({
|
|
52
56
|
error: z.string().optional(),
|
|
57
|
+
can_change_flag: z.boolean().optional(),
|
|
53
58
|
next_username_change_at: z.string().nullable().optional(),
|
|
54
59
|
}),
|
|
55
60
|
};
|
|
56
|
-
|
|
57
|
-
export async function POST(request: Request): Promise<NextResponse> {
|
|
58
|
-
return protectedApi({
|
|
59
|
-
request,
|
|
60
|
-
schema: Schema,
|
|
61
|
-
authorization: validUser,
|
|
62
|
-
activity: handler,
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export async function handler(
|
|
67
|
-
data: (typeof Schema)["input"]["_type"]
|
|
68
|
-
): Promise<NextResponse<(typeof Schema)["output"]["_type"]>> {
|
|
69
|
-
if (data.data.username !== undefined) {
|
|
70
|
-
if (data.data.username.length < 3) {
|
|
71
|
-
return NextResponse.json(
|
|
72
|
-
{
|
|
73
|
-
error: "Username must be at least 3 characters long",
|
|
74
|
-
},
|
|
75
|
-
{ status: 404 }
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (data.data.username.length > 20) {
|
|
80
|
-
return NextResponse.json(
|
|
81
|
-
{
|
|
82
|
-
error: "Username too long.",
|
|
83
|
-
},
|
|
84
|
-
{ status: 404 }
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (!/^[a-z0-9]+$/i.test(data.data.username)) {
|
|
89
|
-
return NextResponse.json(
|
|
90
|
-
{
|
|
91
|
-
error: "Username can only contain letters (a-z) and numbers (0-9)",
|
|
92
|
-
},
|
|
93
|
-
{ status: 404 }
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (validator.trim(data.data.username || "") !== (data.data.username || "")) {
|
|
99
|
-
return NextResponse.json(
|
|
100
|
-
{
|
|
101
|
-
error: "Username can't start or end with spaces.",
|
|
102
|
-
},
|
|
103
|
-
{ status: 404 }
|
|
104
|
-
);
|
|
61
|
+
|
|
62
|
+
export async function POST(request: Request): Promise<NextResponse> {
|
|
63
|
+
return protectedApi({
|
|
64
|
+
request,
|
|
65
|
+
schema: Schema,
|
|
66
|
+
authorization: validUser,
|
|
67
|
+
activity: handler,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function handler(
|
|
72
|
+
data: (typeof Schema)["input"]["_type"]
|
|
73
|
+
): Promise<NextResponse<(typeof Schema)["output"]["_type"]>> {
|
|
74
|
+
if (data.data.username !== undefined) {
|
|
75
|
+
if (data.data.username.length < 3) {
|
|
76
|
+
return NextResponse.json(
|
|
77
|
+
{
|
|
78
|
+
error: "Username must be at least 3 characters long",
|
|
79
|
+
},
|
|
80
|
+
{ status: 404 }
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (data.data.username.length > 20) {
|
|
85
|
+
return NextResponse.json(
|
|
86
|
+
{
|
|
87
|
+
error: "Username too long.",
|
|
88
|
+
},
|
|
89
|
+
{ status: 404 }
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!/^[a-z0-9]+$/i.test(data.data.username)) {
|
|
94
|
+
return NextResponse.json(
|
|
95
|
+
{
|
|
96
|
+
error: "Username can only contain letters (a-z) and numbers (0-9)",
|
|
97
|
+
},
|
|
98
|
+
{ status: 404 }
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (validator.trim(data.data.username || "") !== (data.data.username || "")) {
|
|
104
|
+
return NextResponse.json(
|
|
105
|
+
{
|
|
106
|
+
error: "Username can't start or end with spaces.",
|
|
107
|
+
},
|
|
108
|
+
{ status: 404 }
|
|
109
|
+
);
|
|
105
110
|
}
|
|
106
111
|
|
|
107
112
|
data.data.username = removeZeroWidth(data.data.username || "");
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
113
|
+
|
|
114
|
+
if (data.data.flag !== undefined) {
|
|
115
|
+
const normalizedFlag = validator.trim(data.data.flag).toUpperCase();
|
|
116
|
+
|
|
117
|
+
if (!countryCodes.has(normalizedFlag as (typeof COUNTRY_LIST)[number])) {
|
|
118
|
+
return NextResponse.json(
|
|
119
|
+
{
|
|
120
|
+
error: "Flag must be a valid country code.",
|
|
121
|
+
},
|
|
122
|
+
{ status: 404 }
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
data.data.flag = normalizedFlag;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const user = (await getUserBySession(data.session)) as User;
|
|
130
|
+
|
|
131
|
+
let userData: Database["public"]["Tables"]["profiles"]["Update"];
|
|
132
|
+
|
|
133
|
+
// Find user's entry
|
|
134
|
+
{
|
|
135
|
+
let { data: queryUserData, error } = await supabase
|
|
136
|
+
.from("profiles")
|
|
137
|
+
.select("*")
|
|
138
|
+
.eq("uid", user.id);
|
|
139
|
+
|
|
140
|
+
if (!queryUserData?.length) {
|
|
141
|
+
return NextResponse.json(
|
|
142
|
+
{
|
|
143
|
+
error: "User cannot be retrieved from session",
|
|
144
|
+
},
|
|
145
|
+
{ status: 404 }
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
userData = queryUserData[0];
|
|
149
|
+
}
|
|
150
|
+
|
|
131
151
|
if (
|
|
132
152
|
userData.ban == "excluded" ||
|
|
133
153
|
userData.ban == "restricted" ||
|
|
134
154
|
userData.ban == "silenced"
|
|
135
155
|
) {
|
|
136
|
-
return NextResponse.json(
|
|
137
|
-
{
|
|
138
|
-
error:
|
|
139
|
-
"Silenced, restricted or excluded players can't update their profile.",
|
|
140
|
-
},
|
|
141
|
-
{ status: 404 }
|
|
156
|
+
return NextResponse.json(
|
|
157
|
+
{
|
|
158
|
+
error:
|
|
159
|
+
"Silenced, restricted or excluded players can't update their profile.",
|
|
160
|
+
},
|
|
161
|
+
{ status: 404 }
|
|
142
162
|
);
|
|
143
163
|
}
|
|
144
164
|
|
|
145
165
|
const usernameHasChanged =
|
|
146
166
|
data.data.username !== undefined && data.data.username !== userData.username;
|
|
167
|
+
const flagHasChanged =
|
|
168
|
+
data.data.flag !== undefined && data.data.flag !== userData.flag;
|
|
147
169
|
|
|
148
170
|
let nextUsernameChangeAt: string | null = null;
|
|
171
|
+
let canChangeFlag = true;
|
|
149
172
|
|
|
150
173
|
if (usernameHasChanged) {
|
|
151
174
|
const { data: usernameHistory, error: usernameHistoryError } = await supabase
|
|
@@ -180,17 +203,46 @@ export async function handler(
|
|
|
180
203
|
}
|
|
181
204
|
}
|
|
182
205
|
|
|
206
|
+
if (flagHasChanged) {
|
|
207
|
+
const { data: flagHistory, error: flagHistoryError } = await supabase
|
|
208
|
+
.from("profileFlags")
|
|
209
|
+
.select("id")
|
|
210
|
+
.eq("profile_id", userData.id!)
|
|
211
|
+
.limit(1);
|
|
212
|
+
|
|
213
|
+
if (flagHistoryError) {
|
|
214
|
+
return NextResponse.json(
|
|
215
|
+
{
|
|
216
|
+
error: "Could not verify flag change availability.",
|
|
217
|
+
},
|
|
218
|
+
{ status: 500 }
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
canChangeFlag = !flagHistory?.length;
|
|
223
|
+
|
|
224
|
+
if (!canChangeFlag) {
|
|
225
|
+
return NextResponse.json(
|
|
226
|
+
{
|
|
227
|
+
error: FLAG_CHANGE_ERROR,
|
|
228
|
+
can_change_flag: false,
|
|
229
|
+
},
|
|
230
|
+
{ status: 404 }
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
183
235
|
const upsertPayload: Database["public"]["Tables"]["profiles"]["Update"] = {
|
|
184
236
|
id: userData.id,
|
|
185
237
|
computedUsername: data.data.username?.toLowerCase(),
|
|
186
238
|
...data.data,
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
const upsertResult = await supabase
|
|
190
|
-
.from("profiles")
|
|
191
|
-
.upsert(upsertPayload)
|
|
192
|
-
.select();
|
|
193
|
-
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const upsertResult = await supabase
|
|
242
|
+
.from("profiles")
|
|
243
|
+
.upsert(upsertPayload)
|
|
244
|
+
.select();
|
|
245
|
+
|
|
194
246
|
if (upsertResult.error) {
|
|
195
247
|
if (
|
|
196
248
|
usernameHasChanged &&
|
|
@@ -222,13 +274,23 @@ export async function handler(
|
|
|
222
274
|
);
|
|
223
275
|
}
|
|
224
276
|
|
|
277
|
+
if (flagHasChanged && upsertResult.error.message.includes(FLAG_CHANGE_ERROR)) {
|
|
278
|
+
return NextResponse.json(
|
|
279
|
+
{
|
|
280
|
+
error: FLAG_CHANGE_ERROR,
|
|
281
|
+
can_change_flag: false,
|
|
282
|
+
},
|
|
283
|
+
{ status: 404 }
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
225
287
|
return NextResponse.json(
|
|
226
288
|
{
|
|
227
289
|
error: "Can't update, username might be used by someone else!",
|
|
228
|
-
},
|
|
229
|
-
{ status: 404 }
|
|
230
|
-
);
|
|
231
|
-
}
|
|
290
|
+
},
|
|
291
|
+
{ status: 404 }
|
|
292
|
+
);
|
|
293
|
+
}
|
|
232
294
|
|
|
233
295
|
return NextResponse.json({});
|
|
234
296
|
}
|
package/api/getProfile.ts
CHANGED
|
@@ -53,6 +53,7 @@ export const Schema = {
|
|
|
53
53
|
activity_status: z.enum(["active", "inactive"]),
|
|
54
54
|
is_online: z.boolean(),
|
|
55
55
|
last_active_timestamp: z.number().nullable(),
|
|
56
|
+
can_change_flag: z.boolean(),
|
|
56
57
|
next_username_change_at: z.string().nullable(),
|
|
57
58
|
previous_usernames: z.array(
|
|
58
59
|
z.object({
|
|
@@ -153,6 +154,12 @@ export async function handler(
|
|
|
153
154
|
.select("username,changed_at")
|
|
154
155
|
.eq("profile_id", user.id)
|
|
155
156
|
.order("changed_at", { ascending: false });
|
|
157
|
+
|
|
158
|
+
const { data: flagHistoryData } = await supabase
|
|
159
|
+
.from("profileFlags")
|
|
160
|
+
.select("id")
|
|
161
|
+
.eq("profile_id", user.id)
|
|
162
|
+
.limit(1);
|
|
156
163
|
|
|
157
164
|
//last 30 minutes
|
|
158
165
|
if (activityData && activityData.last_activity) {
|
|
@@ -214,6 +221,7 @@ export async function handler(
|
|
|
214
221
|
activity_status: activityStatus,
|
|
215
222
|
is_online: isOnline,
|
|
216
223
|
last_active_timestamp: activityData?.last_activity ?? null,
|
|
224
|
+
can_change_flag: !flagHistoryData?.length,
|
|
217
225
|
next_username_change_at: getNextUsernameChangeAt(latestUsernameChangeAt),
|
|
218
226
|
previous_usernames: previousUsernames,
|
|
219
227
|
},
|
package/index.ts
CHANGED
|
@@ -304,14 +304,16 @@ export const editCollection = handleApi({url:"/api/editCollection",...EditCollec
|
|
|
304
304
|
export const Schema = {
|
|
305
305
|
input: z.strictObject({
|
|
306
306
|
session: z.string(),
|
|
307
|
-
data: z.object({
|
|
308
|
-
avatar_url: z.string().optional(),
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
307
|
+
data: z.object({
|
|
308
|
+
avatar_url: z.string().optional(),
|
|
309
|
+
flag: z.string().optional(),
|
|
310
|
+
profile_image: z.string().optional(),
|
|
311
|
+
username: z.string().optional(),
|
|
312
|
+
}),
|
|
312
313
|
}),
|
|
313
314
|
output: z.object({
|
|
314
315
|
error: z.string().optional(),
|
|
316
|
+
can_change_flag: z.boolean().optional(),
|
|
315
317
|
next_username_change_at: z.string().nullable().optional(),
|
|
316
318
|
}),
|
|
317
319
|
};*/
|
|
@@ -993,6 +995,7 @@ export const Schema = {
|
|
|
993
995
|
activity_status: z.enum(["active", "inactive"]),
|
|
994
996
|
is_online: z.boolean(),
|
|
995
997
|
last_active_timestamp: z.number().nullable(),
|
|
998
|
+
can_change_flag: z.boolean(),
|
|
996
999
|
next_username_change_at: z.string().nullable(),
|
|
997
1000
|
previous_usernames: z.array(
|
|
998
1001
|
z.object({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rhythia-api",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "241.0.0",
|
|
4
4
|
"main": "index.ts",
|
|
5
5
|
"author": "online-contributors-cunev",
|
|
6
6
|
"scripts": {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"test": "tsx ./scripts/test.ts",
|
|
11
11
|
"cache:clear-user-scores": "bun run scripts/clear-user-score-cache.ts",
|
|
12
12
|
"db:optimize-user-scores": "bun run scripts/optimize-user-scores-indexes.ts",
|
|
13
|
+
"db:create-profile-flags": "node scripts/create-profile-flags-table.ts",
|
|
13
14
|
"db:create-profile-reports": "node scripts/create-profile-reports-table.ts",
|
|
14
15
|
"query-pull": "bun run scripts/pull-queries.ts",
|
|
15
16
|
"query-push": "bun run scripts/deploy-queries.ts",
|
package/types/database.ts
CHANGED
|
@@ -558,6 +558,35 @@ export type Database = {
|
|
|
558
558
|
},
|
|
559
559
|
]
|
|
560
560
|
}
|
|
561
|
+
profileFlags: {
|
|
562
|
+
Row: {
|
|
563
|
+
changed_at: string
|
|
564
|
+
flag: string | null
|
|
565
|
+
id: number
|
|
566
|
+
profile_id: number
|
|
567
|
+
}
|
|
568
|
+
Insert: {
|
|
569
|
+
changed_at?: string
|
|
570
|
+
flag?: string | null
|
|
571
|
+
id?: number
|
|
572
|
+
profile_id: number
|
|
573
|
+
}
|
|
574
|
+
Update: {
|
|
575
|
+
changed_at?: string
|
|
576
|
+
flag?: string | null
|
|
577
|
+
id?: number
|
|
578
|
+
profile_id?: number
|
|
579
|
+
}
|
|
580
|
+
Relationships: [
|
|
581
|
+
{
|
|
582
|
+
foreignKeyName: "profileFlags_profile_id_fkey"
|
|
583
|
+
columns: ["profile_id"]
|
|
584
|
+
isOneToOne: false
|
|
585
|
+
referencedRelation: "profiles"
|
|
586
|
+
referencedColumns: ["id"]
|
|
587
|
+
},
|
|
588
|
+
]
|
|
589
|
+
}
|
|
561
590
|
profileUsernames: {
|
|
562
591
|
Row: {
|
|
563
592
|
changed_at: string
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
export const COUNTRY_LIST = [
|
|
2
|
+
"US",
|
|
3
|
+
"AE",
|
|
4
|
+
"AM",
|
|
5
|
+
"AR",
|
|
6
|
+
"AT",
|
|
7
|
+
"AU",
|
|
8
|
+
"AZ",
|
|
9
|
+
"BA",
|
|
10
|
+
"BB",
|
|
11
|
+
"BD",
|
|
12
|
+
"BE",
|
|
13
|
+
"BG",
|
|
14
|
+
"BH",
|
|
15
|
+
"BN",
|
|
16
|
+
"BO",
|
|
17
|
+
"BR",
|
|
18
|
+
"BT",
|
|
19
|
+
"BW",
|
|
20
|
+
"BY",
|
|
21
|
+
"CA",
|
|
22
|
+
"CH",
|
|
23
|
+
"CI",
|
|
24
|
+
"CL",
|
|
25
|
+
"CN",
|
|
26
|
+
"CO",
|
|
27
|
+
"CR",
|
|
28
|
+
"CU",
|
|
29
|
+
"CW",
|
|
30
|
+
"CY",
|
|
31
|
+
"CZ",
|
|
32
|
+
"DE",
|
|
33
|
+
"DJ",
|
|
34
|
+
"DK",
|
|
35
|
+
"DO",
|
|
36
|
+
"DZ",
|
|
37
|
+
"EC",
|
|
38
|
+
"EE",
|
|
39
|
+
"EG",
|
|
40
|
+
"ES",
|
|
41
|
+
"ET",
|
|
42
|
+
"FI",
|
|
43
|
+
"FJ",
|
|
44
|
+
"FO",
|
|
45
|
+
"FR",
|
|
46
|
+
"GA",
|
|
47
|
+
"GB",
|
|
48
|
+
"GE",
|
|
49
|
+
"GH",
|
|
50
|
+
"GR",
|
|
51
|
+
"GT",
|
|
52
|
+
"GU",
|
|
53
|
+
"HK",
|
|
54
|
+
"HN",
|
|
55
|
+
"HR",
|
|
56
|
+
"HU",
|
|
57
|
+
"ID",
|
|
58
|
+
"IE",
|
|
59
|
+
"IL",
|
|
60
|
+
"IM",
|
|
61
|
+
"IN",
|
|
62
|
+
"IQ",
|
|
63
|
+
"IR",
|
|
64
|
+
"IS",
|
|
65
|
+
"IT",
|
|
66
|
+
"JE",
|
|
67
|
+
"JM",
|
|
68
|
+
"JO",
|
|
69
|
+
"JP",
|
|
70
|
+
"KE",
|
|
71
|
+
"KG",
|
|
72
|
+
"KH",
|
|
73
|
+
"KR",
|
|
74
|
+
"KW",
|
|
75
|
+
"LI",
|
|
76
|
+
"LK",
|
|
77
|
+
"LT",
|
|
78
|
+
"LU",
|
|
79
|
+
"LV",
|
|
80
|
+
"MA",
|
|
81
|
+
"MC",
|
|
82
|
+
"MD",
|
|
83
|
+
"MG",
|
|
84
|
+
"MK",
|
|
85
|
+
"MM",
|
|
86
|
+
"MN",
|
|
87
|
+
"MT",
|
|
88
|
+
"MU",
|
|
89
|
+
"MV",
|
|
90
|
+
"MX",
|
|
91
|
+
"MY",
|
|
92
|
+
"NA",
|
|
93
|
+
"NC",
|
|
94
|
+
"NG",
|
|
95
|
+
"NL",
|
|
96
|
+
"NO",
|
|
97
|
+
"NP",
|
|
98
|
+
"NZ",
|
|
99
|
+
"OM",
|
|
100
|
+
"PA",
|
|
101
|
+
"PE",
|
|
102
|
+
"PG",
|
|
103
|
+
"PH",
|
|
104
|
+
"PK",
|
|
105
|
+
"PL",
|
|
106
|
+
"PR",
|
|
107
|
+
"PS",
|
|
108
|
+
"PT",
|
|
109
|
+
"PY",
|
|
110
|
+
"QA",
|
|
111
|
+
"RE",
|
|
112
|
+
"RO",
|
|
113
|
+
"RU",
|
|
114
|
+
"SA",
|
|
115
|
+
"SD",
|
|
116
|
+
"SE",
|
|
117
|
+
"SG",
|
|
118
|
+
"SI",
|
|
119
|
+
"SK",
|
|
120
|
+
"SL",
|
|
121
|
+
"SN",
|
|
122
|
+
"SR",
|
|
123
|
+
"SV",
|
|
124
|
+
"SY",
|
|
125
|
+
"TG",
|
|
126
|
+
"TH",
|
|
127
|
+
"TN",
|
|
128
|
+
"TR",
|
|
129
|
+
"TT",
|
|
130
|
+
"TW",
|
|
131
|
+
"TZ",
|
|
132
|
+
"UA",
|
|
133
|
+
"UY",
|
|
134
|
+
"UZ",
|
|
135
|
+
"KZ",
|
|
136
|
+
"VE",
|
|
137
|
+
"VN",
|
|
138
|
+
"XK",
|
|
139
|
+
"ZA",
|
|
140
|
+
"ZW",
|
|
141
|
+
] as const;
|