Skip to content

Commit e737cd2

Browse files
authored
Merge pull request #34 from Azm-Tech/feat/add-countries-lookup
Feat/sprint-6
2 parents d65c5c5 + cd302d1 commit e737cd2

117 files changed

Lines changed: 20737 additions & 283 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

backend/permissions.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,3 +187,7 @@ groups:
187187
CountryProfiles:
188188
description: Generate country profiles report
189189
roles: [cce-super-admin, cce-admin]
190+
Lookup:
191+
Manage:
192+
description: Manage lookup tables (nationalities, country phone codes)
193+
roles: [cce-super-admin]

backend/src/CCE.Api.Common/Auth/AuthEndpoints.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ public static IEndpointRouteBuilder MapAuthEndpoints(this IEndpointRouteBuilder
2929
body.OrganizationName,
3030
body.PhoneNumber,
3131
body.Password,
32-
body.ConfirmPassword), ct).ConfigureAwait(false);
32+
body.ConfirmPassword,
33+
body.CountryCodeId), ct).ConfigureAwait(false);
3334
return result.ToCreatedHttpResult();
3435
})
3536
.AllowAnonymous()

backend/src/CCE.Api.Common/Localization/Resources.yaml

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,8 @@ IDENTITY_USERNAME_EXISTS:
5151
en: "Username already taken"
5252

5353
IDENTITY_INVALID_CREDENTIALS:
54-
ar: "عذرًا، حدثت مشكلة أثناء تسجيل الدخول"
55-
en: "Sorry, a problem occurred during login"
56-
57-
IDENTITY_INVALID_TOKEN:
58-
ar: "رمز الوصول غير صالح"
59-
en: "Invalid access token"
60-
61-
IDENTITY_ACCOUNT_DEACTIVATED:
62-
ar: "عذرًا، حدثت مشكلة أثناء تسجيل الدخول"
63-
en: "Sorry, a problem occurred during login"
54+
ar: "البريد الإلكتروني أو كلمة المرور غير صحيحة"
55+
en: "Invalid email or password"
6456

6557
IDENTITY_NOT_AUTHENTICATED:
6658
ar: "المستخدم غير مصادق"
@@ -78,6 +70,14 @@ IDENTITY_STATE_REP_ASSIGNMENT_EXISTS:
7870
ar: "التعيين موجود بالفعل"
7971
en: "Assignment already exists"
8072

73+
IDENTITY_INVALID_TOKEN:
74+
ar: "رمز الوصول غير صالح"
75+
en: "Invalid access token"
76+
77+
IDENTITY_ACCOUNT_DEACTIVATED:
78+
ar: "الحساب غير نشط"
79+
en: "Account is deactivated"
80+
8181
CONTENT_RESOURCE_NOT_FOUND:
8282
ar: "المورد غير موجود"
8383
en: "Resource not found"
@@ -189,12 +189,12 @@ USER_NOT_FOUND:
189189
en: "Sorry, user not found"
190190

191191
EMAIL_EXISTS:
192-
ar: "عذرًا، حدثت مشكلة أثناء إنشاء الحساب"
193-
en: "Sorry, a problem occurred while creating the account"
192+
ar: "البريد الإلكتروني مستخدم بالفعل"
193+
en: "An account with this email already exists"
194194

195195
INVALID_CREDENTIALS:
196-
ar: "عذرًا، حدثت مشكلة أثناء تسجيل الدخول"
197-
en: "Sorry, a problem occurred during login"
196+
ar: "البريد الإلكتروني أو كلمة المرور غير صحيحة"
197+
en: "Invalid email or password"
198198

199199
NOT_AUTHENTICATED:
200200
ar: "المستخدم غير مصادق"
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
using CCE.Api.Common.Auth;
2+
using CCE.Api.Common.Extensions;
3+
using CCE.Application.Common.Interfaces;
4+
using CCE.Application.Content.Commands.UploadAsset;
5+
using CCE.Application.Content.Queries.GetAssetById;
6+
using CCE.Infrastructure;
7+
using MediatR;
8+
using Microsoft.AspNetCore.Builder;
9+
using Microsoft.AspNetCore.Http;
10+
using Microsoft.AspNetCore.Routing;
11+
using Microsoft.Extensions.Options;
12+
13+
namespace CCE.Api.External.Endpoints;
14+
15+
public static class AssetEndpoints
16+
{
17+
public static IEndpointRouteBuilder MapAssetEndpoints(this IEndpointRouteBuilder app)
18+
{
19+
var assets = app.MapGroup("/api/assets").WithTags("Assets").RequireAuthorization();
20+
21+
assets.MapPost("", async (
22+
IFormFile file,
23+
ICurrentUserAccessor currentUser,
24+
IMediator mediator,
25+
IOptions<CceInfrastructureOptions> infraOpts,
26+
CancellationToken ct) =>
27+
{
28+
if (currentUser.GetUserId() is null) return Results.Unauthorized();
29+
30+
if (file is null || file.Length == 0)
31+
return Results.BadRequest(new { error = "Upload requires a non-empty file." });
32+
33+
var allowed = infraOpts.Value.AllowedAssetMimeTypes;
34+
if (!allowed.Contains(file.ContentType, System.StringComparer.OrdinalIgnoreCase))
35+
return Results.StatusCode(StatusCodes.Status415UnsupportedMediaType);
36+
37+
await using var stream = file.OpenReadStream();
38+
var result = await mediator.Send(
39+
new UploadAssetCommand(stream, file.FileName, file.ContentType, file.Length),
40+
ct).ConfigureAwait(false);
41+
42+
return result.Success
43+
? Results.Created($"/api/assets/{result.Data!.Id}", result)
44+
: result.ToHttpResult();
45+
})
46+
.WithName("UploadAsset")
47+
.DisableAntiforgery()
48+
.WithMetadata(new RequestSizeLimitMetadataImpl(20L * 1024L * 1024L));
49+
50+
assets.MapGet("{id:guid}", async (
51+
System.Guid id,
52+
ICurrentUserAccessor currentUser,
53+
IMediator mediator,
54+
CancellationToken ct) =>
55+
{
56+
var userId = currentUser.GetUserId() ?? System.Guid.Empty;
57+
if (userId == System.Guid.Empty) return Results.Unauthorized();
58+
59+
var result = await mediator.Send(new GetAssetByIdQuery(id), ct).ConfigureAwait(false);
60+
61+
if (!result.Success || result.Data!.UploadedById != userId)
62+
return Results.NotFound();
63+
64+
return result.ToHttpResult();
65+
})
66+
.WithName("GetAssetById");
67+
68+
return app;
69+
}
70+
71+
private sealed class RequestSizeLimitMetadataImpl : Microsoft.AspNetCore.Http.Metadata.IRequestSizeLimitMetadata
72+
{
73+
public RequestSizeLimitMetadataImpl(long? max) { MaxRequestBodySize = max; }
74+
public long? MaxRequestBodySize { get; }
75+
}
76+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using CCE.Application.Lookups.Queries.GetCountryCodeById;
2+
using CCE.Application.Lookups.Queries.ListCountryCodes;
3+
using MediatR;
4+
using Microsoft.AspNetCore.Builder;
5+
using Microsoft.AspNetCore.Http;
6+
using Microsoft.AspNetCore.Routing;
7+
8+
namespace CCE.Api.External.Endpoints;
9+
10+
public static class CountryCodesPublicEndpoints
11+
{
12+
public static IEndpointRouteBuilder MapCountryCodesPublicEndpoints(this IEndpointRouteBuilder app)
13+
{
14+
var group = app.MapGroup("/api/country-codes").WithTags("CountryCodes");
15+
16+
group.MapGet("", async (
17+
string? search, bool? isActive,
18+
IMediator mediator, CancellationToken ct) =>
19+
{
20+
var query = new ListCountryCodesQuery(Search: search, IsActive: isActive);
21+
var result = await mediator.Send(query, ct).ConfigureAwait(false);
22+
return Results.Ok(result);
23+
})
24+
.AllowAnonymous()
25+
.WithName("ListPublicCountryCodes");
26+
27+
group.MapGet("/{id:guid}", async (
28+
System.Guid id,
29+
IMediator mediator, CancellationToken ct) =>
30+
{
31+
var result = await mediator.Send(new GetCountryCodeByIdQuery(id), ct).ConfigureAwait(false);
32+
return result.Success ? Results.Ok(result) : Results.NotFound(result);
33+
})
34+
.AllowAnonymous()
35+
.WithName("GetPublicCountryCodeById");
36+
37+
return app;
38+
}
39+
}

backend/src/CCE.Api.External/Endpoints/ProfileEndpoints.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ public static IEndpointRouteBuilder MapProfileEndpoints(this IEndpointRouteBuild
3434
body.OrganizationName,
3535
body.PhoneNumber,
3636
body.Password,
37-
body.ConfirmPassword), ct).ConfigureAwait(false);
37+
body.ConfirmPassword,
38+
body.CountryCodeId), ct).ConfigureAwait(false);
3839
return result.ToCreatedHttpResult();
3940
})
4041
.AllowAnonymous()
@@ -50,7 +51,8 @@ public static IEndpointRouteBuilder MapProfileEndpoints(this IEndpointRouteBuild
5051
if (userId == System.Guid.Empty) return Results.Unauthorized();
5152
var cmd = new SubmitExpertRequestCommand(
5253
userId, body.RequestedBioAr, body.RequestedBioEn,
53-
body.RequestedTags ?? System.Array.Empty<string>());
54+
body.RequestedTags ?? System.Array.Empty<string>(),
55+
body.CvAssetFileId);
5456
var result = await mediator.Send(cmd, ct).ConfigureAwait(false);
5557
return result.ToCreatedHttpResult();
5658
})
@@ -77,9 +79,11 @@ public static IEndpointRouteBuilder MapProfileEndpoints(this IEndpointRouteBuild
7779
var userId = currentUser.GetUserId() ?? System.Guid.Empty;
7880
if (userId == System.Guid.Empty) return Results.Unauthorized();
7981
var cmd = new UpdateMyProfileCommand(
80-
userId, body.LocalePreference, body.KnowledgeLevel,
82+
userId,
83+
body.FirstName, body.LastName, body.JobTitle, body.OrganizationName,
84+
body.LocalePreference, body.KnowledgeLevel,
8185
body.Interests ?? System.Array.Empty<string>(),
82-
body.AvatarUrl, body.CountryId);
86+
body.AvatarUrl, body.CountryId, body.CountryCodeId);
8387
var result = await mediator.Send(cmd, ct).ConfigureAwait(false);
8488
return result.ToHttpResult();
8589
})

backend/src/CCE.Api.External/Program.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
app.MapHub<NotificationsHub>("/hubs/notifications");
9696

9797
app.MapProfileEndpoints();
98+
app.MapAssetEndpoints();
9899
app.MapAuthEndpoints(CCE.Application.Identity.Auth.Common.LocalAuthApi.External);
99100
app.MapNotificationsEndpoints();
100101
app.MapNewsPublicEndpoints();
@@ -118,6 +119,7 @@
118119
app.MapPoliciesSettingsPublicEndpoints();
119120
app.MapMediaPublicEndpoints();
120121
app.MapVerificationEndpoints();
122+
app.MapCountryCodesPublicEndpoints();
121123

122124
app.MapGet("/health", async (IMediator mediator) =>
123125
{

backend/src/CCE.Api.External/appsettings.Development.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,5 +91,8 @@
9191
},
9292
"Otp": {
9393
"HmacSecret": "3ahs3DvW/rdx+InzjOCpqSUDSFuvyF59sPjziVdeIhE="
94+
},
95+
"Frontend": {
96+
"PasswordResetUrl": "http://localhost:4200"
9497
}
9598
}

backend/src/CCE.Api.External/appsettings.Production.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,5 +83,8 @@
8383
"BaseUrl": "https://cce-mocks.bonto.run",
8484
"TimeoutSeconds": 30
8585
}
86+
},
87+
"Frontend": {
88+
"PasswordResetUrl": "http://localhost:4200"
8689
}
8790
}

backend/src/CCE.Api.External/appsettings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,6 @@
7575
"EnableTracing": true
7676
},
7777
"Otp": {
78-
"HmacSecret": "replace-with-32-byte-base64-hmac-secret"
78+
"HmacSecret": "Bu7y2mktcNeV5dMdCmg8W2ZTRyPty9snQ7Q8QKrA2YY="
7979
}
8080
}

0 commit comments

Comments
 (0)