Skip to content

Commit ab2f4b7

Browse files
committed
Minor changes
1 parent f170ed5 commit ab2f4b7

File tree

24 files changed

+298
-40
lines changed

24 files changed

+298
-40
lines changed

apps/codebattle/assets/js/__tests__/UserSettings.test.jsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ jest.mock("react-bootstrap/Alert", () => ({
2525
) : null,
2626
}));
2727

28+
jest.mock("../i18n", () => ({
29+
__esModule: true,
30+
getSupportedLocale: jest.fn((locale) => (["en", "ru"].includes(locale) ? locale : "en")),
31+
default: {
32+
t: jest.fn((key) => key),
33+
changeLanguage: jest.fn(() => Promise.resolve()),
34+
},
35+
}));
36+
2837
const reducer = combineReducers(reducers);
2938

3039
const preloadedState = {
@@ -131,7 +140,7 @@ describe("UserSettings test cases", () => {
131140
test("successfull locale change", async () => {
132141
const settingUpdaterSpy = global.fetch.mockResolvedValueOnce({
133142
ok: true,
134-
json: async () => ({}),
143+
json: async () => ({ locale: "ru" }),
135144
});
136145
const { getByLabelText, getByTestId, findByText, user } = setup(
137146
<Provider store={store}>
@@ -151,6 +160,9 @@ describe("UserSettings test cases", () => {
151160
locale: "ru",
152161
});
153162
});
163+
164+
const i18n = jest.requireMock("../i18n").default;
165+
expect(i18n.changeLanguage).toHaveBeenCalledWith("ru");
154166
});
155167

156168
test("failed user settings update", async () => {

apps/codebattle/assets/js/i18n/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ import i18next from "i18next";
66
import en from "../../../priv/gettext/en/LC_MESSAGES/default.po";
77
import ru from "../../../priv/gettext/ru/LC_MESSAGES/default.po";
88

9-
const lng = Gon?.getAsset?.("locale") || "en";
9+
const supportedLocales = ["en", "ru"];
10+
const normalizeLocale = (locale) => (supportedLocales.includes(locale) ? locale : "en");
11+
const lng = normalizeLocale(Gon?.getAsset?.("locale"));
1012

1113
export const getLocale = () => lng;
14+
export const getSupportedLocale = normalizeLocale;
1215

1316
i18next.init({
1417
nsSeparator: false,

apps/codebattle/assets/js/widgets/pages/event/ParticipantDashboard.jsx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React, { useEffect } from "react";
22

33
import NiceModal, { unregister } from "@ebay/nice-modal-react";
44
import cn from "classnames";
5+
import Gon from "gon";
56
import upperCase from "lodash/upperCase";
67
import { useSelector } from "react-redux";
78

@@ -13,6 +14,53 @@ import EventStageConfirmationModal from "./EventStageConfirmationModal";
1314
import NotPassedIcon from "./NotPassedIcon";
1415
import PassedIcon from "./PassedIcon";
1516

17+
const externalPlatformLoginUrl = Gon.getAsset("external_platform_login_url");
18+
const externalPlatformProfileUrlTemplate = Gon.getAsset("external_platform_profile_url_template");
19+
20+
const getExternalPlatformProfileUrl = (login) => {
21+
if (!login || !externalPlatformProfileUrlTemplate) {
22+
return null;
23+
}
24+
25+
return externalPlatformProfileUrlTemplate.replace("USER_LOGIN", login);
26+
};
27+
28+
const renderExternalPlatformLink = (user) => {
29+
if (user.externalPlatformId) {
30+
const profileUrl = getExternalPlatformProfileUrl(user.externalOauthLogin);
31+
32+
if (!profileUrl) {
33+
return <span className="cb-custom-event-profile-data ms-2">{user.externalPlatformId}</span>;
34+
}
35+
36+
return (
37+
<a
38+
className="cb-custom-event-profile-data ms-2"
39+
href={profileUrl}
40+
target="_blank"
41+
rel="noreferrer"
42+
>
43+
{i18n.t("View profile")}
44+
</a>
45+
);
46+
}
47+
48+
if (!externalPlatformLoginUrl) {
49+
return <span className="cb-custom-event-profile-data ms-2">-</span>;
50+
}
51+
52+
return (
53+
<a
54+
className="cb-custom-event-profile-data ms-2"
55+
href={externalPlatformLoginUrl}
56+
target="_blank"
57+
rel="noreferrer"
58+
>
59+
{i18n.t("Link profile")}
60+
</a>
61+
);
62+
};
63+
1664
function ParticipantDashboard() {
1765
useEffect(() => {
1866
NiceModal.register(ModalCodes.eventStageModal, EventStageConfirmationModal);
@@ -66,6 +114,10 @@ function ParticipantDashboard() {
66114
{i18n.t("Category")}
67115
<span className="cb-custom-event-profile-data ms-2">{user.category}</span>
68116
</div>
117+
<div className="d-flex text-white justify-content-between cb-custom-event-profile my-1 mx-1 w-100">
118+
{i18n.t("External platform")}
119+
{renderExternalPlatformLink(user)}
120+
</div>
69121
</div>
70122
</div>
71123
</div>

apps/codebattle/assets/js/widgets/pages/settings/UserSettings.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import noop from "lodash/noop";
88
import Alert from "react-bootstrap/Alert";
99
import { useDispatch, useSelector } from "react-redux";
1010

11-
import i18n from "../../../i18n";
11+
import i18n, { getSupportedLocale } from "../../../i18n";
1212
import { userSettingsSelector } from "../../selectors";
1313
import { actions } from "../../slices";
1414

@@ -111,6 +111,7 @@ function UserSettings() {
111111
try {
112112
const data = await updateSettings(values);
113113

114+
await i18n.changeLanguage(getSupportedLocale(data.locale));
114115
dispatch(actions.updateUserSettings(camelizeKeys(data)));
115116
setNotification(notifications.success);
116117
} catch (error) {

apps/codebattle/lib/codebattle/auth/external.ex

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ defmodule Codebattle.Auth.External do
33
Module that handles External OAuth
44
"""
55

6+
@http_timeout_ms 7_000
7+
68
def client_id do
79
Application.get_env(:codebattle, :oauth)[:external_client_id]
810
end
@@ -27,15 +29,21 @@ defmodule Codebattle.Auth.External do
2729

2830
opts =
2931
Keyword.merge(
30-
Application.get_env(:codebattle, :auth_req_options, []),
32+
request_opts(),
3133
body: body,
3234
headers: headers
3335
)
3436

35-
external_auth_url()
36-
|> Req.post!(opts)
37-
|> Map.get(:body)
38-
|> check_authenticated()
37+
case Req.post(external_auth_url(), opts) do
38+
{:ok, %{status: status, body: response_body}} when status in 200..299 ->
39+
check_authenticated(response_body)
40+
41+
{:ok, %{status: status, body: response_body}} ->
42+
{:error, {:external_auth_failed, status, response_body}}
43+
44+
{:error, reason} ->
45+
{:error, reason}
46+
end
3947
end
4048

4149
defp external_auth_url do
@@ -51,17 +59,37 @@ defmodule Codebattle.Auth.External do
5159
defp get_user_details(access_token) do
5260
opts =
5361
Keyword.put(
54-
Application.get_env(:codebattle, :auth_req_options, []),
62+
request_opts(),
5563
:headers,
5664
authorization: "OAuth #{access_token}"
5765
)
5866

67+
case Req.get(external_user_info_url(), opts) do
68+
{:ok, %{status: status, body: response_body}} when status in 200..299 ->
69+
response_body
70+
|> Map.take(["default_avatar_id", "id", "is_avatar_empty", "login"])
71+
|> Runner.AtomizedMap.atomize()
72+
|> then(&{:ok, &1})
73+
74+
{:ok, %{status: status, body: response_body}} ->
75+
{:error, {:external_user_info_failed, status, response_body}}
76+
77+
{:error, reason} ->
78+
{:error, reason}
79+
end
80+
end
81+
82+
defp external_user_info_url do
5983
:codebattle
6084
|> Application.get_env(:oauth)
6185
|> Keyword.get(:external_user_info_url)
62-
|> Req.get!(opts)
63-
|> Map.get(:body)
64-
|> Map.take(["default_avatar_id", "id", "is_avatar_empty", "login"])
65-
|> Runner.AtomizedMap.atomize()
86+
end
87+
88+
defp request_opts do
89+
Keyword.merge(
90+
Application.get_env(:codebattle, :auth_req_options, []),
91+
connect_options: [timeout: @http_timeout_ms],
92+
receive_timeout: @http_timeout_ms
93+
)
6694
end
6795
end

apps/codebattle/lib/codebattle/auth/external_user.ex

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ defmodule Codebattle.Auth.User.ExternalUser do
88

99
@spec find_or_create(map()) :: {:ok, User.t()} | {:error, term()}
1010
def find_or_create(profile) do
11+
external_platform_id = Codebattle.ExternalPlatform.get_user_id_by_login(profile.login)
12+
1113
User
1214
|> Repo.get_by(external_oauth_id: profile.id)
1315
|> case do
@@ -17,6 +19,7 @@ defmodule Codebattle.Auth.User.ExternalUser do
1719
params = %{
1820
external_oauth_id: profile.id,
1921
external_oauth_login: profile.login,
22+
external_platform_id: external_platform_id,
2023
name: name,
2124
subscription_type: :free,
2225
lang: Application.get_env(:codebattle, :default_lang_slug),
@@ -30,7 +33,8 @@ defmodule Codebattle.Auth.User.ExternalUser do
3033
user ->
3134
params = %{
3235
avatar_url: external_avatar_url(profile),
33-
external_oauth_login: profile.login
36+
external_oauth_login: profile.login,
37+
external_platform_id: external_platform_id
3438
}
3539

3640
user
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
defmodule Codebattle.ExternalPlatform do
2+
@moduledoc false
3+
4+
require Logger
5+
6+
@http_timeout_ms 7_000
7+
@lookup_path "/v1/users/id"
8+
9+
def get_user_id_by_login(login) when is_binary(login) do
10+
login = String.trim(login)
11+
12+
if login == "" do
13+
nil
14+
else
15+
do_get_user_id_by_login(login)
16+
end
17+
end
18+
19+
def get_user_id_by_login(_), do: nil
20+
21+
defp do_get_user_id_by_login(login) do
22+
opts =
23+
Keyword.put(
24+
request_opts(),
25+
:params,
26+
login: login
27+
)
28+
29+
case Req.get(external_platform_service_url() <> @lookup_path, opts) do
30+
{:ok, %{status: 200, body: %{"id" => id}}} when is_binary(id) and id != "" ->
31+
id
32+
33+
{:ok, %{status: 200, body: body}} ->
34+
Logger.warning("External platform lookup returned invalid body=#{inspect(body)}")
35+
nil
36+
37+
{:ok, %{status: status}} when status in [400, 404] ->
38+
nil
39+
40+
{:ok, %{status: status, body: body}} ->
41+
Logger.warning("External platform lookup failed status=#{status} body=#{inspect(body)}")
42+
nil
43+
44+
{:error, reason} ->
45+
Logger.warning("External platform lookup failed reason=#{inspect(reason)}")
46+
nil
47+
end
48+
end
49+
50+
defp external_platform_service_url do
51+
Application.get_env(:codebattle, :external_platform_service_url)
52+
end
53+
54+
defp request_opts do
55+
Keyword.merge(
56+
Application.get_env(:codebattle, :auth_req_options, []),
57+
connect_options: [timeout: @http_timeout_ms],
58+
receive_timeout: @http_timeout_ms
59+
)
60+
end
61+
end

apps/codebattle/lib/codebattle/user.ex

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ defmodule Codebattle.User do
2727
:db_type,
2828
:editor_mode,
2929
:editor_theme,
30+
:external_oauth_login,
31+
:external_platform_id,
3032
:games_played,
3133
:github_id,
3234
:github_name,
@@ -65,6 +67,7 @@ defmodule Codebattle.User do
6567
field(:email, :string)
6668
field(:external_oauth_id, :string)
6769
field(:external_oauth_login, :string)
70+
field(:external_platform_id, :string)
6871
field(:firebase_uid, :string)
6972
field(:github_id, :integer)
7073
field(:github_name, :string)
@@ -107,6 +110,7 @@ defmodule Codebattle.User do
107110
:email,
108111
:external_oauth_id,
109112
:external_oauth_login,
113+
:external_platform_id,
110114
:firebase_uid,
111115
:github_id,
112116
:github_name,
@@ -138,7 +142,15 @@ defmodule Codebattle.User do
138142

139143
def token_changeset(user, params \\ %{}) do
140144
user
141-
|> cast(params, [:external_oauth_id, :external_oauth_login, :category, :name, :clan, :subscription_type])
145+
|> cast(params, [
146+
:external_oauth_id,
147+
:external_oauth_login,
148+
:external_platform_id,
149+
:category,
150+
:name,
151+
:clan,
152+
:subscription_type
153+
])
142154
|> cast_embed(:sound_settings)
143155
|> unique_constraint(:name)
144156
|> validate_length(:name, min: 2, max: 39)

apps/codebattle/lib/codebattle_web/controllers/auth_controller.ex

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,10 @@ defmodule CodebattleWeb.AuthController do
7373

7474
"external" ->
7575
redirect_uri = Routes.auth_url(conn, :callback, provider_name)
76-
profile = External.external_auth(code, redirect_uri)
77-
Codebattle.Auth.User.ExternalUser.find_or_create(profile)
76+
77+
with {:ok, profile} <- External.external_auth(code, redirect_uri) do
78+
Codebattle.Auth.User.ExternalUser.find_or_create(profile)
79+
end
7880
end
7981

8082
case case_result do

apps/codebattle/lib/codebattle_web/controllers/public_event_controller.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ defmodule CodebattleWeb.PublicEventController do
2424
|> assign(:ticker_text, event.ticker_text)
2525
|> assign(:show_header, true)
2626
|> put_gon(
27+
external_platform_login_url: Application.get_env(:codebattle, :external_platform_login_url),
28+
external_platform_profile_url_template: Application.get_env(:codebattle, :external_platform_profile_url_template),
2729
event: %{
2830
event: event,
2931
user_event: user_event

0 commit comments

Comments
 (0)