Skip to content

Commit dfb3294

Browse files
committed
!wip
1 parent 9b82880 commit dfb3294

File tree

26 files changed

+815
-193
lines changed

26 files changed

+815
-193
lines changed

Makefile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,6 @@ showmigrations: ## show all migrations for the messages project.
217217
superuser: ## Create an admin superuser with password "admin"
218218
@echo "$(BOLD)Creating a Django superuser$(RESET)"
219219
@$(MANAGE) createsuperuser --email [email protected] --password admin
220-
@$(MANAGE) createsuperuser --email [email protected] --password admin
221220
.PHONY: superuser
222221

223222
back-i18n-compile: ## compile the gettext files

src/backend/core/api/openapi.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3356,6 +3356,32 @@
33563356
}
33573357
}
33583358
}
3359+
},
3360+
"/api/v1.0/users/search/": {
3361+
"get": {
3362+
"operationId": "users_search_retrieve",
3363+
"description": "Search users by email, first name and last name.",
3364+
"tags": [
3365+
"users"
3366+
],
3367+
"security": [
3368+
{
3369+
"cookieAuth": []
3370+
}
3371+
],
3372+
"responses": {
3373+
"200": {
3374+
"content": {
3375+
"application/json": {
3376+
"schema": {
3377+
"$ref": "#/components/schemas/User"
3378+
}
3379+
}
3380+
},
3381+
"description": ""
3382+
}
3383+
}
3384+
}
33593385
}
33603386
},
33613387
"components": {

src/backend/core/api/viewsets/maildomain.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from core import models
1010
from core.api import permissions as core_permissions
1111
from core.api import serializers as core_serializers
12+
from core.identity.keycloak import reset_keycloak_user_password
1213

1314

1415
class MailDomainAdminViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
@@ -68,7 +69,9 @@ def get_queryset(self):
6869
def create(self, request, *args, **kwargs):
6970
maildomain_pk = self.kwargs.get("maildomain_pk")
7071
domain = get_object_or_404(models.MailDomain, pk=maildomain_pk)
72+
metadata = request.data.get("metadata", {})
7173

74+
type = metadata.get("type")
7275
local_part = request.data.get("local_part")
7376
alias_of_id = request.data.get("alias_of")
7477

@@ -121,8 +124,31 @@ def create(self, request, *args, **kwargs):
121124
domain=domain, local_part=local_part, alias_of=alias_of
122125
)
123126

127+
# --- Create user and mailbox access if type is personal ---
128+
if type == "personal":
129+
email = f"{local_part}@{domain.name}"
130+
first_name = metadata.get("first_name")
131+
last_name = metadata.get("last_name")
132+
user, created = models.User.objects.get_or_create(
133+
email=email,
134+
defaults={
135+
"full_name": f"{first_name} {last_name}",
136+
"short_name": first_name,
137+
"password": "?",
138+
}
139+
)
140+
models.MailboxAccess.objects.create(
141+
mailbox=mailbox,
142+
user=user,
143+
role=models.MailboxRoleChoices.ADMIN,
144+
)
145+
mailbox_password = reset_keycloak_user_password(email)
146+
124147
serializer = self.get_serializer(mailbox)
125148
headers = self.get_success_headers(serializer.data)
149+
payload = serializer.data
150+
if type == "personal":
151+
payload["one_time_password"] = mailbox_password
126152
return Response(
127-
serializer.data, status=status.HTTP_201_CREATED, headers=headers
153+
payload, status=status.HTTP_201_CREATED, headers=headers
128154
)

src/backend/core/api/viewsets/user.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
"""API ViewSet for User model."""
22

3+
from django.db.models import Q
34
import rest_framework as drf
45
from rest_framework import viewsets
6+
from rest_framework.decorators import action
7+
from core import models
58

69
from .. import permissions, serializers
710

@@ -27,3 +30,22 @@ def get_me(self, request):
2730
return drf.response.Response(
2831
self.serializer_class(request.user, context=context).data
2932
)
33+
34+
@action(detail=False, methods=["get"])
35+
def search(self, request, **kwargs):
36+
"""
37+
Search users by email, first name and last name.
38+
"""
39+
queryset = models.User.objects.filter(is_staff=False)
40+
41+
if query := request.query_params.get("q", ""):
42+
queryset = queryset.filter(
43+
Q(email__unaccent__icontains=query)
44+
| Q(full_name__unaccent__icontains=query)
45+
| Q(short_name__unaccent__icontains=query)
46+
)
47+
48+
queryset = queryset.order_by("full_name", "short_name", "email")
49+
50+
serializer = serializers.UserSerializer(queryset, many=True)
51+
return drf.response.Response(serializer.data)
69.4 KB
Binary file not shown.

src/frontend/src/features/api/gen/users/users.ts

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,3 +184,170 @@ export function useUsersMeRetrieve<
184184

185185
return query;
186186
}
187+
188+
/**
189+
* Search users by email, first name and last name.
190+
*/
191+
export type usersSearchRetrieveResponse200 = {
192+
data: User;
193+
status: 200;
194+
};
195+
196+
export type usersSearchRetrieveResponseComposite =
197+
usersSearchRetrieveResponse200;
198+
199+
export type usersSearchRetrieveResponse =
200+
usersSearchRetrieveResponseComposite & {
201+
headers: Headers;
202+
};
203+
204+
export const getUsersSearchRetrieveUrl = () => {
205+
return `/api/v1.0/users/search/`;
206+
};
207+
208+
export const usersSearchRetrieve = async (
209+
options?: RequestInit,
210+
): Promise<usersSearchRetrieveResponse> => {
211+
return fetchAPI<usersSearchRetrieveResponse>(getUsersSearchRetrieveUrl(), {
212+
...options,
213+
method: "GET",
214+
});
215+
};
216+
217+
export const getUsersSearchRetrieveQueryKey = () => {
218+
return [`/api/v1.0/users/search/`] as const;
219+
};
220+
221+
export const getUsersSearchRetrieveQueryOptions = <
222+
TData = Awaited<ReturnType<typeof usersSearchRetrieve>>,
223+
TError = unknown,
224+
>(options?: {
225+
query?: Partial<
226+
UseQueryOptions<
227+
Awaited<ReturnType<typeof usersSearchRetrieve>>,
228+
TError,
229+
TData
230+
>
231+
>;
232+
request?: SecondParameter<typeof fetchAPI>;
233+
}) => {
234+
const { query: queryOptions, request: requestOptions } = options ?? {};
235+
236+
const queryKey = queryOptions?.queryKey ?? getUsersSearchRetrieveQueryKey();
237+
238+
const queryFn: QueryFunction<
239+
Awaited<ReturnType<typeof usersSearchRetrieve>>
240+
> = ({ signal }) => usersSearchRetrieve({ signal, ...requestOptions });
241+
242+
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
243+
Awaited<ReturnType<typeof usersSearchRetrieve>>,
244+
TError,
245+
TData
246+
> & { queryKey: DataTag<QueryKey, TData, TError> };
247+
};
248+
249+
export type UsersSearchRetrieveQueryResult = NonNullable<
250+
Awaited<ReturnType<typeof usersSearchRetrieve>>
251+
>;
252+
export type UsersSearchRetrieveQueryError = unknown;
253+
254+
export function useUsersSearchRetrieve<
255+
TData = Awaited<ReturnType<typeof usersSearchRetrieve>>,
256+
TError = unknown,
257+
>(
258+
options: {
259+
query: Partial<
260+
UseQueryOptions<
261+
Awaited<ReturnType<typeof usersSearchRetrieve>>,
262+
TError,
263+
TData
264+
>
265+
> &
266+
Pick<
267+
DefinedInitialDataOptions<
268+
Awaited<ReturnType<typeof usersSearchRetrieve>>,
269+
TError,
270+
Awaited<ReturnType<typeof usersSearchRetrieve>>
271+
>,
272+
"initialData"
273+
>;
274+
request?: SecondParameter<typeof fetchAPI>;
275+
},
276+
queryClient?: QueryClient,
277+
): DefinedUseQueryResult<TData, TError> & {
278+
queryKey: DataTag<QueryKey, TData, TError>;
279+
};
280+
export function useUsersSearchRetrieve<
281+
TData = Awaited<ReturnType<typeof usersSearchRetrieve>>,
282+
TError = unknown,
283+
>(
284+
options?: {
285+
query?: Partial<
286+
UseQueryOptions<
287+
Awaited<ReturnType<typeof usersSearchRetrieve>>,
288+
TError,
289+
TData
290+
>
291+
> &
292+
Pick<
293+
UndefinedInitialDataOptions<
294+
Awaited<ReturnType<typeof usersSearchRetrieve>>,
295+
TError,
296+
Awaited<ReturnType<typeof usersSearchRetrieve>>
297+
>,
298+
"initialData"
299+
>;
300+
request?: SecondParameter<typeof fetchAPI>;
301+
},
302+
queryClient?: QueryClient,
303+
): UseQueryResult<TData, TError> & {
304+
queryKey: DataTag<QueryKey, TData, TError>;
305+
};
306+
export function useUsersSearchRetrieve<
307+
TData = Awaited<ReturnType<typeof usersSearchRetrieve>>,
308+
TError = unknown,
309+
>(
310+
options?: {
311+
query?: Partial<
312+
UseQueryOptions<
313+
Awaited<ReturnType<typeof usersSearchRetrieve>>,
314+
TError,
315+
TData
316+
>
317+
>;
318+
request?: SecondParameter<typeof fetchAPI>;
319+
},
320+
queryClient?: QueryClient,
321+
): UseQueryResult<TData, TError> & {
322+
queryKey: DataTag<QueryKey, TData, TError>;
323+
};
324+
325+
export function useUsersSearchRetrieve<
326+
TData = Awaited<ReturnType<typeof usersSearchRetrieve>>,
327+
TError = unknown,
328+
>(
329+
options?: {
330+
query?: Partial<
331+
UseQueryOptions<
332+
Awaited<ReturnType<typeof usersSearchRetrieve>>,
333+
TError,
334+
TData
335+
>
336+
>;
337+
request?: SecondParameter<typeof fetchAPI>;
338+
},
339+
queryClient?: QueryClient,
340+
): UseQueryResult<TData, TError> & {
341+
queryKey: DataTag<QueryKey, TData, TError>;
342+
} {
343+
const queryOptions = getUsersSearchRetrieveQueryOptions(options);
344+
345+
const query = useQuery(queryOptions, queryClient) as UseQueryResult<
346+
TData,
347+
TError
348+
> & { queryKey: DataTag<QueryKey, TData, TError> };
349+
350+
query.queryKey = queryOptions.queryKey;
351+
352+
return query;
353+
}

0 commit comments

Comments
 (0)