Skip to content

Commit

Permalink
chore: partial refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
brunotot committed Oct 22, 2024
1 parent 10c3694 commit 116c51a
Show file tree
Hide file tree
Showing 66 changed files with 215 additions and 130 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { RouteMiddlewareFactory } from "@org/app-node-express/lib/@ts-rest"
import type { RequestHandler } from "express";
import type { Keycloak as KeycloakType, KeycloakConfig } from "keycloak-connect";

import { env } from "@org/app-node-express/env";
import { env } from "@org/app-node-express/server/env";
import { IocRegistry, inject } from "@org/app-node-express/ioc";
import { keycloakMemoryStore } from "@org/app-node-express/lib/keycloak";
import { RestError } from "@org/lib-api-client";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export class WithRouteSecured implements RouteSecuredMiddleware {
tokenData.preferred_username,
);

const hasAtLeastOneRole = userRoles.some(userRole => roles.includes(userRole));
const hasAtLeastOneRole = userRoles.some(userRole => roles.includes(userRole as Role));

if (!hasAtLeastOneRole) {
throw new RestError(403, "User does not have the required role");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import type { Role, User, Keycloak } from "@org/lib-api-client";
import type { Role, User, Keycloak, UserDto, UserForm } from "@org/lib-api-client";

import { inject } from "@org/app-node-express/ioc";
import { KeycloakDao } from "@org/app-node-express/lib/keycloak";
import { ROLE_LIST } from "@org/lib-api-client";

export interface AuthorizationRepository {
findAllUsers(): Promise<User[]>;
findUserByUsername(username: string): Promise<User | null>;
createUser(model: User): Promise<User>;
findAllUsers(): Promise<UserDto[]>;
findUserByUsername(username: string): Promise<UserDto | null>;
createUser(model: UserForm): Promise<UserDto>;
deleteUser(userId: string): Promise<void>;
updateUserPassword(userId: string, password: string): Promise<void>;
updateUserRoles(userId: string, roles: Role[]): Promise<void>;
}

Expand All @@ -17,14 +18,14 @@ export interface AuthorizationRepository {
*/
@inject("AuthorizationRepository")
export class UserRepository extends KeycloakDao implements AuthorizationRepository {
public async findUserByUsername(username: string): Promise<User | null> {
public async findUserByUsername(username: string): Promise<UserDto | null> {
const users = await this.get<Keycloak.UserRepresentation[]>(`/users?username=${username}`);
if (users.length === 0) return null;
const user = users.filter(user => user.username === username)[0];
return await this.userMapper(user);
}

public async findAllUsers(): Promise<User[]> {
public async findAllUsers(): Promise<UserDto[]> {
const users = await this.get<Keycloak.UserRepresentation[]>(`/users`);
const usersWithRoles = await Promise.all(users.map(user => this.userMapper(user)));
return usersWithRoles;
Expand All @@ -35,14 +36,31 @@ export class UserRepository extends KeycloakDao implements AuthorizationReposito
await this.post(`/users/${userId}/role-mappings/clients/${KeycloakDao.KC_CLIENT_ID}`, roles);
}

public async createUser({ roles, ...model }: User): Promise<User> {
public async updateUserPassword(userId: string, password: string): Promise<void> {
await this.put(`/users/${userId}/reset-password`, {
type: "password",
value: password,
temporary: false,
});
}

public async createUser({
hasCredentials,
roles,
password,
...model
}: UserForm): Promise<UserDto> {
const res = await this.post<Keycloak.UserRepresentation>(`/users`, {
...model,
enabled: true,
});

const out = (await this.findUserByUsername(res.username))!;
this.updateUserRoles(out.id!, roles);
let out = (await this.findUserByUsername(res.username))!;
await this.updateUserRoles(out.id!, roles);
if (hasCredentials) {
await this.updateUserPassword(out.id!, password!);
}
out = (await this.findUserByUsername(res.username))!;
return out;
}

Expand All @@ -66,10 +84,16 @@ export class UserRepository extends KeycloakDao implements AuthorizationReposito
return mapped;
}

private async userMapper(user: Keycloak.UserRepresentation): Promise<User> {
private async userMapper(user: User): Promise<UserDto> {
const roles = await this.findRolesByUserId(user.id!);

const hasCredentials = (
await this.get<Keycloak.CredentialRepresentation[]>(`/users/${user.id}/credentials`)
).some(cred => cred.type === "password");

return {
...user,
hasCredentials,
roles: roles
.filter(role => !!ROLE_LIST.find((r: string) => r === role.name))
.map(role => role.name as Role),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { AuthorizationRepository } from "../repository/UserRepository";
import type { TypedPaginationResponse, User } from "@org/lib-api-client";
import type { TypedPaginationResponse, UserDto, UserForm } from "@org/lib-api-client";
import type { TODO } from "@org/lib-commons";

import { autowired, inject } from "@org/app-node-express/ioc";
Expand All @@ -9,24 +9,24 @@ import { RestError } from "@org/lib-api-client";
export class UserService {
@autowired() private authorizationRepository: AuthorizationRepository;

async findAll(): Promise<User[]> {
async findAll(): Promise<UserDto[]> {
const users = await this.authorizationRepository.findAllUsers();
return users;
}

async findAllPaginated(paginationOptions: TODO): Promise<TypedPaginationResponse<User>> {
async findAllPaginated(paginationOptions: TODO): Promise<TypedPaginationResponse<UserDto>> {
// eslint-disable-next-line no-console
console.log(paginationOptions);
return null as TODO;
}

async findOneByUsername(username: string): Promise<User> {
async findOneByUsername(username: string): Promise<UserDto> {
const user = await this.authorizationRepository.findUserByUsername(username);
if (user === null) throw new RestError(404, "User not found");
return user;
}

async createUser(model: User): Promise<User> {
async createUser(model: UserForm): Promise<UserDto> {
const user = await this.authorizationRepository.createUser(model);
return user;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { env } from "@org/app-node-express/env";
import { env } from "@org/app-node-express/server/env";

import { KeycloakTokenManager } from "./KeycloakTokenManager";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { env } from "@org/app-node-express/env";
import { env } from "@org/app-node-express/server/env";
import session from "express-session";
import createMemoryStore from "memorystore";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { RequestHandler } from "express";

import { env } from "@org/app-node-express/env";
import { keycloakMemoryStore } from "@org/app-node-express/lib/keycloak";
import { env } from "@org/app-node-express/server/env";
import session from "express-session";

export function buildKeycloakSession(): RequestHandler {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { env } from "@org/app-node-express/env";
import { env } from "@org/app-node-express/server/env";
import { type Keycloak } from "@org/lib-api-client";

export class KeycloakTokenManager {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { env, testMode } from "@org/app-node-express/env";
import { env, testMode } from "@org/app-node-express/server/env";
import { type zod } from "@org/lib-commons";
import { MongoClient, type Db, type ClientSession } from "mongodb";

Expand Down
4 changes: 2 additions & 2 deletions packages/mern-sample-app/app-node-express/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import path from "path";

import { env } from "@org/app-node-express/env";
import { initServer } from "@org/app-node-express/initServer";
import { env } from "@org/app-node-express/server/env";
import { initServer } from "@org/app-node-express/server/initServer";

async function main() {
// Initialize Express server
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import type { RouteMiddlewareFactory } from "@org/app-node-express/lib/@ts-rest";
import type { middleware } from "@org/app-node-express/middleware/index";

import { env } from "@org/app-node-express/env";
import { env } from "@org/app-node-express/server/env";
import cors from "cors";

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import type { RouteMiddlewareFactory } from "@org/app-node-express/lib/@ts-rest";
import type { middleware } from "@org/app-node-express/middleware/index";

import { env } from "@org/app-node-express/env";
import { env } from "@org/app-node-express/server/env";

/**
* Provides a middleware factory to handle setting CORS credentials headers for allowed origins.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type { middleware } from "@org/app-node-express/middleware/index";
import fs from "fs";
import path from "path";

import { env } from "@org/app-node-express/env";
import { env } from "@org/app-node-express/server/env";
import express from "express";

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import type { MongoClient } from "@org/app-node-express/lib/mongodb";
import type { NoArgsClass } from "@org/lib-commons";
import type { IncomingMessage, Server, ServerResponse } from "http";

import { env } from "@org/app-node-express/env";
import { env } from "@org/app-node-express/server/env";
import { IocRegistry } from "@org/app-node-express/ioc";
import {
initializeExpressRoutes,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,11 @@ import type { NoArgsClass } from "@org/lib-commons";

import path from "path";

import { env } from "@org/app-node-express/env";
import { ExpressApp } from "@org/app-node-express/ExpressApp";
import { scanIocModules } from "@org/app-node-express/ioc";
import { log } from "@org/app-node-express/lib/winston";
import { middleware } from "@org/app-node-express/middleware";
import { env } from "@org/app-node-express/server/env";
import { ExpressApp } from "@org/app-node-express/server/ExpressApp";

export type InitServerConfig = Partial<{
mocks: Record<string, NoArgsClass>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { initServer } from "../../../dist/initServer";
import { initServer } from "../../../dist/server/initServer";
import { MongoDatabaseService } from "../../../dist/lib/mongodb/MongoDatabaseService";
import __mocks__ from "../__mocks__";
import { cleanup, setApp } from "./utils";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"types": [],
"esModuleInterop": true,
"moduleResolution": "Node",
"lib": ["ESNext.Decorators"],

"baseUrl": "./",
"paths": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { LayoutVariant } from "@org/app-vite-react/models";

import { TableChart, ViewSidebar } from "@mui/icons-material";
import { IconButton, Tooltip } from "@mui/material";
import { type LayoutVariant } from "@org/app-vite-react/app/layout";
import { useTranslation } from "@org/app-vite-react/lib/i18next";

export type InputLayoutToggleProps = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { Breakpoint } from "@mui/material";
import type * as RouteTypes from "@org/app-vite-react/route-typings";
import type * as RouteTypes from "@org/app-vite-react/server/route-typings";

import { ChevronRight, ExpandMore } from "@mui/icons-material";
import * as mui from "@mui/material";
import { useTranslation } from "@org/app-vite-react/lib/i18next";
import { reactServer } from "@org/app-vite-react/server";
import { reactServer } from "@org/app-vite-react/server/server";
import { sigUser } from "@org/app-vite-react/signals/sigUser";
import { Fragment } from "react";
import { useNavigate } from "react-router-dom";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Footer } from "@org/app-vite-react/app/components/Footer";
import { Header } from "@org/app-vite-react/app/components/Header";
import { Logo } from "@org/app-vite-react/app/components/Logo";
import { Sidebar } from "@org/app-vite-react/app/components/Sidebar";
import { type NavigationRoute } from "@org/app-vite-react/route-typings";
import { type NavigationRoute } from "@org/app-vite-react/server/route-typings";
import { sigLayoutVariant } from "@org/app-vite-react/signals/sigLayoutVariant";
import { sigLayoutWidth } from "@org/app-vite-react/signals/sigLayoutWidth";
import { sigSidebarOpen } from "@org/app-vite-react/signals/sigSidebarOpen";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type * as RouteTypes from "@org/app-vite-react/route-typings";
import type * as RouteTypes from "@org/app-vite-react/server/route-typings";

import { ExpandLess, ExpandMore } from "@mui/icons-material";
import { Collapse, Divider, List, ListItemButton, ListItemIcon, ListItemText } from "@mui/material";
import { isAnyRouteActive } from "@org/app-vite-react/app/layout/Layout";
import { useTranslation } from "@org/app-vite-react/lib/i18next";
import { reactServer } from "@org/app-vite-react/server";
import { reactServer } from "@org/app-vite-react/server/server";
import { sigUser } from "@org/app-vite-react/signals/sigUser";
import { useState } from "react";
import { Fragment } from "react/jsx-runtime";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { User } from "@org/lib-api-client";
import type { UserForm as UserFormModel } from "@org/lib-api-client";

import * as icons from "@mui/icons-material";
import { Add } from "@mui/icons-material";
Expand All @@ -12,15 +12,16 @@ export type UserCreateFormButtonProps = {
afterUpdate?: () => void;
};

const DEFAULT_FORM_STATE: User = {
const DEFAULT_FORM_STATE: UserFormModel = {
id: "",
username: "",
enabled: true,
roles: ["avr-user"],
hasCredentials: true,
};

export function UserCreateFormButton({ afterUpdate }: UserCreateFormButtonProps) {
const [user, setUser] = useState<User>(DEFAULT_FORM_STATE);
const [user, setUser] = useState<UserFormModel>(DEFAULT_FORM_STATE);

const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
import { TextField, Button, Box, Autocomplete, MenuItem, Chip } from "@mui/material";
import { type Role, ROLE_LIST, type User } from "@org/lib-api-client";
import {
TextField,
Button,
Box,
Autocomplete,
MenuItem,
Chip,
Checkbox,
FormControlLabel,
} from "@mui/material";
import { type Role, ROLE_LIST, type UserForm as UserFormModel } from "@org/lib-api-client";
import React from "react";

export type UserFormProps = {
value: User;
onChange: (newState: User) => void;
value: UserFormModel;
onChange: (newState: UserFormModel) => void;
onSubmit: (event: React.FormEvent<HTMLFormElement>) => void;
};

export function UserForm({ value, onChange, onSubmit }: UserFormProps) {
const mutate = (diff: Partial<User>) => {
const mutate = (diff: Partial<UserFormModel>) => {
onChange({
...value,
...diff,
Expand All @@ -29,6 +38,29 @@ export function UserForm({ value, onChange, onSubmit }: UserFormProps) {
onChange={e => mutate({ username: e.target.value })}
required
/>
{/* Write code for checkbox on has credentials */}

<FormControlLabel
control={
<Checkbox
checked={value.hasCredentials}
onChange={event => {
mutate({ hasCredentials: event.target.checked });
}}
/>
}
label="Set password"
/>

{value.hasCredentials && (
<TextField
label="Password"
type="password"
value={value.password}
onChange={e => mutate({ password: e.target.value })}
required
/>
)}
<TextField
label="First name"
value={value.firstName}
Expand Down
Loading

0 comments on commit 116c51a

Please sign in to comment.