Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cfb6c5863b | ||
|
|
521d16c9ce | ||
|
|
d58b0f5564 | ||
|
|
91e2f1ecc8 | ||
|
|
8f831c35d5 | ||
|
|
2c3cc3449b | ||
|
|
820fd56fbc | ||
|
|
41f7dabbf0 | ||
|
|
5041ebeeff |
@@ -35,26 +35,31 @@ describe('Portuguese Utils unit tests', () => {
|
||||
it('should translate all server responses to pt_br', () => {
|
||||
const keys: string[] = Object.keys(serverResponses);
|
||||
|
||||
expect(translateServerResponse(keys[0], 'pt_br')).toBe('Senha fraca: Senha deve possuir pelo menos 8 letras, 1 maiúscula, 1 caracter especial');
|
||||
expect(translateServerResponse(keys[1], 'pt_br')).toBe('Senha fraca: Senha deve possuir pelo menos 1 maiúscula, 1 caracter especial');
|
||||
expect(translateServerResponse(keys[2], 'pt_br')).toBe('Senha fraca: Senha deve possuir pelo menos 1 caracter especial');
|
||||
expect(translateServerResponse(keys[3], 'pt_br')).toBe('E-mail já cadastrado!');
|
||||
expect(translateServerResponse(keys[4], 'pt_br')).toBe('Proibido! Acesso negado');
|
||||
expect(translateServerResponse(keys[5], 'pt_br')).toBe('Se o endereço de e-mail informado estiver associado a uma conta, você receberá um link para resetar a senha em breve.');
|
||||
expect(translateServerResponse(keys[6], 'pt_br')).toBe('Erro Interno do Servidor!');
|
||||
expect(translateServerResponse(keys[7], 'pt_br')).toBe('Limite máximo de tentativas atingido. Por favor aguarde 30 minutos');
|
||||
expect(translateServerResponse(keys[8], 'pt_br')).toBe('Erro de rede ao tentar obter recursos.');
|
||||
expect(translateServerResponse(keys[9], 'pt_br')).toBe('Por favor, confirme seu e-mail antes de continuar');
|
||||
expect(translateServerResponse(keys[10], 'pt_br')).toBe('Por ravor, preencha todos os campos');
|
||||
expect(translateServerResponse(keys[11], 'pt_br')).toBe('Por favor, informe seu e-mail');
|
||||
expect(translateServerResponse(keys[12], 'pt_br')).toBe('Por favor, informe seu e-mail e senha!');
|
||||
expect(translateServerResponse(keys[13], 'pt_br')).toBe('Por favor, informe a nova senha');
|
||||
expect(translateServerResponse(keys[14], 'pt_br')).toBe('Por favor, digite pelo menos 3 letras');
|
||||
expect(translateServerResponse(keys[15], 'pt_br')).toBe('O tamanho máximo do texto é 2000');
|
||||
expect(translateServerResponse(keys[16], 'pt_br')).toBe('Erro desconhecido');
|
||||
expect(translateServerResponse(keys[17], 'pt_br')).toBe('Identificação incorreta ou faltando');
|
||||
expect(translateServerResponse(keys[18], 'pt_br')).toBe('Informação errada ou incompleta!');
|
||||
expect(translateServerResponse(keys[19], 'pt_br')).toBe('E-mail ou senha inválidos!');
|
||||
expect(translateServerResponse(keys[20], 'pt_br')).toBe('Nada para atualizar!');
|
||||
expect(translateServerResponse(keys[0], 'pt_br')).toBe('Senha fraca: Senha deve possuir pelo menos 8 letras');
|
||||
expect(translateServerResponse(keys[1], 'pt_br')).toBe('Senha fraca: Senha deve possuir pelo menos 8 letras, 1 caracter especial');
|
||||
expect(translateServerResponse(keys[2], 'pt_br')).toBe('Senha fraca: Senha deve possuir pelo menos 8 letras, 1 número, 1 caracter especial');
|
||||
expect(translateServerResponse(keys[3], 'pt_br')).toBe('Senha fraca: Senha deve possuir pelo menos 8 letras, 1 maiúscula, 1 número, 1 caracter especial');
|
||||
expect(translateServerResponse(keys[4], 'pt_br')).toBe('Senha fraca: Senha deve possuir pelo menos 8 letras, 1 maiúscula, 1 caracter especial');
|
||||
expect(translateServerResponse(keys[5], 'pt_br')).toBe('Senha fraca: Senha deve possuir pelo menos 1 maiúscula, 1 caracter especial');
|
||||
expect(translateServerResponse(keys[6], 'pt_br')).toBe('Senha fraca: Senha deve possuir pelo menos 1 caracter especial');
|
||||
expect(translateServerResponse(keys[7], 'pt_br')).toBe('Senha fraca: Senha deve possuir pelo menos 1 número');
|
||||
expect(translateServerResponse(keys[8], 'pt_br')).toBe('E-mail já cadastrado!');
|
||||
expect(translateServerResponse(keys[9], 'pt_br')).toBe('Proibido! Acesso negado');
|
||||
expect(translateServerResponse(keys[10], 'pt_br')).toBe('Se o endereço de e-mail informado estiver associado a uma conta, você receberá um link para resetar a senha em breve.');
|
||||
expect(translateServerResponse(keys[11], 'pt_br')).toBe('Erro Interno do Servidor!');
|
||||
expect(translateServerResponse(keys[12], 'pt_br')).toBe('Limite máximo de tentativas atingido. Por favor aguarde 30 minutos');
|
||||
expect(translateServerResponse(keys[13], 'pt_br')).toBe('Erro de rede ao tentar obter recursos.');
|
||||
expect(translateServerResponse(keys[14], 'pt_br')).toBe('Por favor, confirme seu e-mail antes de continuar');
|
||||
expect(translateServerResponse(keys[15], 'pt_br')).toBe('Por favor, preencha todos os campos');
|
||||
expect(translateServerResponse(keys[16], 'pt_br')).toBe('Por favor, informe seu e-mail');
|
||||
expect(translateServerResponse(keys[17], 'pt_br')).toBe('Por favor, informe seu e-mail e senha!');
|
||||
expect(translateServerResponse(keys[18], 'pt_br')).toBe('Por favor, informe a nova senha');
|
||||
expect(translateServerResponse(keys[19], 'pt_br')).toBe('Por favor, digite pelo menos 3 letras');
|
||||
expect(translateServerResponse(keys[20], 'pt_br')).toBe('O tamanho máximo do texto é 2000');
|
||||
expect(translateServerResponse(keys[21], 'pt_br')).toBe('Erro desconhecido');
|
||||
expect(translateServerResponse(keys[22], 'pt_br')).toBe('Identificação incorreta ou faltando');
|
||||
expect(translateServerResponse(keys[23], 'pt_br')).toBe('Informação errada ou incompleta!');
|
||||
expect(translateServerResponse(keys[24], 'pt_br')).toBe('E-mail ou senha inválidos!');
|
||||
expect(translateServerResponse(keys[25], 'pt_br')).toBe('Nada para atualizar!');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -56,26 +56,31 @@ describe('Russian Utils unit tests', () => {
|
||||
it('should translate all server responses to ru', () => {
|
||||
const keys: string[] = Object.keys(serverResponses);
|
||||
|
||||
expect(translateServerResponse(keys[0], 'ru')).toBe('Неправильный пароль: Пароль должен содержать не менее 8 символов, 1 заглавную букву, 1 специальный символ.');
|
||||
expect(translateServerResponse(keys[1], 'ru')).toBe('Неправильный пароль: Пароль должен содержать как минимум 1 заглавную букву и 1 специальный символ.');
|
||||
expect(translateServerResponse(keys[2], 'ru')).toBe('Неправильный пароль: Пароль должен содержать хотя бы 1 специальный символ.');
|
||||
expect(translateServerResponse(keys[3], 'ru')).toBe('Электронная почта уже используется!');
|
||||
expect(translateServerResponse(keys[4], 'ru')).toBe('Доступ запрещен!');
|
||||
expect(translateServerResponse(keys[5], 'ru')).toBe('Если введенный вами адрес электронной почты связан с учетной записью, вы вскоре получите ссылку для сброса пароля.');
|
||||
expect(translateServerResponse(keys[6], 'ru')).toBe('Внутренняя ошибка сервера!');
|
||||
expect(translateServerResponse(keys[7], 'ru')).toBe('Достигнут максимальный лимит попыток входа. Пожалуйста, подождите 30 минут');
|
||||
expect(translateServerResponse(keys[8], 'ru')).toBe('Ошибка сети при попытке получить ресурс.');
|
||||
expect(translateServerResponse(keys[9], 'ru')).toBe('Пожалуйста, подтвердите свой адрес электронной почты, прежде чем продолжить');
|
||||
expect(translateServerResponse(keys[10], 'ru')).toBe('Пожалуйста, заполните все поля');
|
||||
expect(translateServerResponse(keys[11], 'ru')).toBe('Пожалуйста, введите свой адрес электронной почты');
|
||||
expect(translateServerResponse(keys[12], 'ru')).toBe('Пожалуйста, введите свое имя пользователя и пароль!');
|
||||
expect(translateServerResponse(keys[13], 'ru')).toBe('Пожалуйста, введите новый пароль');
|
||||
expect(translateServerResponse(keys[14], 'ru')).toBe('Пожалуйста, введите не менее 3 символов');
|
||||
expect(translateServerResponse(keys[15], 'ru')).toBe('Максимальная длина текста — 2000 символов.');
|
||||
expect(translateServerResponse(keys[16], 'ru')).toBe('Неизвестная ошибка');
|
||||
expect(translateServerResponse(keys[17], 'ru')).toBe('Неправильная или отсутствующая идентификация');
|
||||
expect(translateServerResponse(keys[18], 'ru')).toBe('Неверная или отсутствующая информация!');
|
||||
expect(translateServerResponse(keys[19], 'ru')).toBe('Неправильный пользователь или пароль');
|
||||
expect(translateServerResponse(keys[20], 'ru')).toBe('Нечего обновлять!');
|
||||
expect(translateServerResponse(keys[0], 'ru')).toBe('Неправильный пароль: Пароль должен содержать не менее 8 символов.');
|
||||
expect(translateServerResponse(keys[1], 'ru')).toBe('Неправильный пароль: Пароль должен содержать не менее 8 символов, 1 специальный символ.');
|
||||
expect(translateServerResponse(keys[2], 'ru')).toBe('Неправильный пароль: Пароль должен содержать не менее 8 символов, 1 цифру, 1 специальный символ.');
|
||||
expect(translateServerResponse(keys[3], 'ru')).toBe('Неправильный пароль: Пароль должен содержать не менее 8 символов, 1 заглавную букву, 1 цифру, 1 специальный символ.');
|
||||
expect(translateServerResponse(keys[4], 'ru')).toBe('Неправильный пароль: Пароль должен содержать не менее 8 символов, 1 заглавную букву, 1 специальный символ.');
|
||||
expect(translateServerResponse(keys[5], 'ru')).toBe('Неправильный пароль: Пароль должен содержать как минимум 1 заглавную букву и 1 специальный символ.');
|
||||
expect(translateServerResponse(keys[6], 'ru')).toBe('Неправильный пароль: Пароль должен содержать хотя бы 1 специальный символ.');
|
||||
expect(translateServerResponse(keys[7], 'ru')).toBe('Неправильный пароль: Пароль должен содержать не менее 1 цифры.');
|
||||
expect(translateServerResponse(keys[8], 'ru')).toBe('Электронная почта уже используется!');
|
||||
expect(translateServerResponse(keys[9], 'ru')).toBe('Доступ запрещен!');
|
||||
expect(translateServerResponse(keys[10], 'ru')).toBe('Если введенный вами адрес электронной почты связан с учетной записью, вы вскоре получите ссылку для сброса пароля.');
|
||||
expect(translateServerResponse(keys[11], 'ru')).toBe('Внутренняя ошибка сервера!');
|
||||
expect(translateServerResponse(keys[12], 'ru')).toBe('Достигнут максимальный лимит попыток входа. Пожалуйста, подождите 30 минут');
|
||||
expect(translateServerResponse(keys[13], 'ru')).toBe('Ошибка сети при попытке получить ресурс.');
|
||||
expect(translateServerResponse(keys[14], 'ru')).toBe('Пожалуйста, подтвердите свой адрес электронной почты, прежде чем продолжить');
|
||||
expect(translateServerResponse(keys[15], 'ru')).toBe('Пожалуйста, заполните все поля');
|
||||
expect(translateServerResponse(keys[16], 'ru')).toBe('Пожалуйста, введите свой адрес электронной почты');
|
||||
expect(translateServerResponse(keys[17], 'ru')).toBe('Пожалуйста, введите свое имя пользователя и пароль!');
|
||||
expect(translateServerResponse(keys[18], 'ru')).toBe('Пожалуйста, введите новый пароль');
|
||||
expect(translateServerResponse(keys[19], 'ru')).toBe('Пожалуйста, введите не менее 3 символов');
|
||||
expect(translateServerResponse(keys[20], 'ru')).toBe('Максимальная длина текста — 2000 символов.');
|
||||
expect(translateServerResponse(keys[21], 'ru')).toBe('Неизвестная ошибка');
|
||||
expect(translateServerResponse(keys[22], 'ru')).toBe('Неправильная или отсутствующая идентификация');
|
||||
expect(translateServerResponse(keys[23], 'ru')).toBe('Неверная или отсутствующая информация!');
|
||||
expect(translateServerResponse(keys[24], 'ru')).toBe('Неправильный пользователь или пароль');
|
||||
expect(translateServerResponse(keys[25], 'ru')).toBe('Нечего обновлять!');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -35,26 +35,31 @@ describe('Spanish Utils unit tests', () => {
|
||||
it('should translate all server responses to es', () => {
|
||||
const keys: string[] = Object.keys(serverResponses);
|
||||
|
||||
expect(translateServerResponse(keys[0], 'es')).toBe('Contraseña inválida: La contraseña debe tener al menos 8 caracteres, 1 mayúscula y 1 carácter especial');
|
||||
expect(translateServerResponse(keys[1], 'es')).toBe('Contraseña inválida: La contraseña debe tener al menos 1 mayúscula y 1 carácter especial');
|
||||
expect(translateServerResponse(keys[2], 'es')).toBe('Contraseña inválida: La contraseña debe tener al menos 1 carácter especial');
|
||||
expect(translateServerResponse(keys[3], 'es')).toBe('¡El correo ya está registrado!');
|
||||
expect(translateServerResponse(keys[4], 'es')).toBe('¡Prohibido! Acceso denegado');
|
||||
expect(translateServerResponse(keys[5], 'es')).toBe('Si la dirección de correo electrónico ingresada está asociada a una cuenta, recibirá un enlace para restablecer su contraseña en breve.');
|
||||
expect(translateServerResponse(keys[6], 'es')).toBe('¡Error interno del servidor!');
|
||||
expect(translateServerResponse(keys[7], 'es')).toBe('Has alcanzado el límite máximo de intentos de inicio de sesión. Por favor, espera 30 minutos');
|
||||
expect(translateServerResponse(keys[8], 'es')).toBe('Error de red al intentar obtener el recurso.');
|
||||
expect(translateServerResponse(keys[9], 'es')).toBe('Por favor, confirme su correo electrónico antes de continuar');
|
||||
expect(translateServerResponse(keys[10], 'es')).toBe('Por favor, completa todos los campos');
|
||||
expect(translateServerResponse(keys[11], 'es')).toBe('Por favor, ingresa tu correo electronico');
|
||||
expect(translateServerResponse(keys[12], 'es')).toBe('¡Por favor, ingresa tu correo electronico y contraseña!');
|
||||
expect(translateServerResponse(keys[13], 'es')).toBe('Por favor, rellene la nueva contraseña');
|
||||
expect(translateServerResponse(keys[14], 'es')).toBe('¡Por favor, escriba al menos 3 caracteres!');
|
||||
expect(translateServerResponse(keys[15], 'es')).toBe('La longitud máxima del texto es 2000');
|
||||
expect(translateServerResponse(keys[16], 'es')).toBe('Error desconocido');
|
||||
expect(translateServerResponse(keys[17], 'es')).toBe('Identificación incorrecta o faltante');
|
||||
expect(translateServerResponse(keys[18], 'es')).toBe('¡Información incorrecta o incompleta!');
|
||||
expect(translateServerResponse(keys[19], 'es')).toBe('¡Usuario o contraseña incorrectos!');
|
||||
expect(translateServerResponse(keys[20], 'es')).toBe('¡Nada que actualizar!');
|
||||
expect(translateServerResponse(keys[0], 'es')).toBe('Contraseña inválida: La contraseña debe tener al menos 8 caracteres');
|
||||
expect(translateServerResponse(keys[1], 'es')).toBe('Contraseña inválida: La contraseña debe tener al menos 8 caracteres, 1 carácter especial');
|
||||
expect(translateServerResponse(keys[2], 'es')).toBe('Contraseña inválida: La contraseña debe tener al menos 8 caracteres, 1 número, 1 carácter especial');
|
||||
expect(translateServerResponse(keys[3], 'es')).toBe('Contraseña inválida: La contraseña debe tener al menos 8 caracteres, 1 mayúscula, 1 número, 1 carácter especial');
|
||||
expect(translateServerResponse(keys[4], 'es')).toBe('Contraseña inválida: La contraseña debe tener al menos 8 caracteres, 1 mayúscula y 1 carácter especial');
|
||||
expect(translateServerResponse(keys[5], 'es')).toBe('Contraseña inválida: La contraseña debe tener al menos 1 mayúscula y 1 carácter especial');
|
||||
expect(translateServerResponse(keys[6], 'es')).toBe('Contraseña inválida: La contraseña debe tener al menos 1 carácter especial');
|
||||
expect(translateServerResponse(keys[7], 'es')).toBe('Contraseña inválida: La contraseña debe tener al menos 1 número');
|
||||
expect(translateServerResponse(keys[8], 'es')).toBe('¡El correo ya está registrado!');
|
||||
expect(translateServerResponse(keys[9], 'es')).toBe('¡Prohibido! Acceso denegado');
|
||||
expect(translateServerResponse(keys[10], 'es')).toBe('Si la dirección de correo electrónico ingresada está asociada a una cuenta, recibirá un enlace para restablecer su contraseña en breve.');
|
||||
expect(translateServerResponse(keys[11], 'es')).toBe('¡Error interno del servidor!');
|
||||
expect(translateServerResponse(keys[12], 'es')).toBe('Has alcanzado el límite máximo de intentos de inicio de sesión. Por favor, espera 30 minutos');
|
||||
expect(translateServerResponse(keys[13], 'es')).toBe('Error de red al intentar obtener el recurso.');
|
||||
expect(translateServerResponse(keys[14], 'es')).toBe('Por favor, confirme su correo electrónico antes de continuar');
|
||||
expect(translateServerResponse(keys[15], 'es')).toBe('Por favor, completa todos los campos');
|
||||
expect(translateServerResponse(keys[16], 'es')).toBe('Por favor, ingresa tu correo electronico');
|
||||
expect(translateServerResponse(keys[17], 'es')).toBe('¡Por favor, ingresa tu correo electronico y contraseña!');
|
||||
expect(translateServerResponse(keys[18], 'es')).toBe('Por favor, rellene la nueva contraseña');
|
||||
expect(translateServerResponse(keys[19], 'es')).toBe('¡Por favor, escriba al menos 3 caracteres!');
|
||||
expect(translateServerResponse(keys[20], 'es')).toBe('La longitud máxima del texto es 2000');
|
||||
expect(translateServerResponse(keys[21], 'es')).toBe('Error desconocido');
|
||||
expect(translateServerResponse(keys[22], 'es')).toBe('Identificación incorrecta o faltante');
|
||||
expect(translateServerResponse(keys[23], 'es')).toBe('¡Información incorrecta o incompleta!');
|
||||
expect(translateServerResponse(keys[24], 'es')).toBe('¡Usuario o contraseña incorrectos!');
|
||||
expect(translateServerResponse(keys[25], 'es')).toBe('¡Nada que actualizar!');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -65,7 +65,13 @@ async function handleResponse(response: Response) {
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (contentType && contentType.includes('application/json')) {
|
||||
const data = await response.json();
|
||||
throw new Error(data.message);
|
||||
if ('message' in data && typeof data.message === 'string') {
|
||||
throw new Error(data.message);
|
||||
}
|
||||
if ('fields' in data && Array.isArray(data.fields)) {
|
||||
const firstError = data.fields[0].fieldMessage as string;
|
||||
throw new Error(firstError);
|
||||
}
|
||||
}
|
||||
handleError(response.status);
|
||||
}
|
||||
|
||||
@@ -64,6 +64,11 @@ export const timeAgoTranslations: Record<string, string> = {
|
||||
};
|
||||
|
||||
export const serverResponsesTranslations: Record<string, string> = {
|
||||
BAD_PASSWORD_8_pt_br: 'Senha fraca: Senha deve possuir pelo menos 8 letras',
|
||||
BAD_PASSWORD_7_pt_br: 'Senha fraca: Senha deve possuir pelo menos 8 letras, 1 caracter especial',
|
||||
BAD_PASSWORD_6_pt_br: 'Senha fraca: Senha deve possuir pelo menos 8 letras, 1 número, 1 caracter especial',
|
||||
BAD_PASSWORD_5_pt_br: 'Senha fraca: Senha deve possuir pelo menos 8 letras, 1 maiúscula, 1 número, 1 caracter especial',
|
||||
BAD_PASSWORD_4_pt_br: 'Senha fraca: Senha deve possuir pelo menos 1 número',
|
||||
BAD_PASSWORD_3_pt_br: 'Senha fraca: Senha deve possuir pelo menos 8 letras, 1 maiúscula, 1 caracter especial',
|
||||
BAD_PASSWORD_2_pt_br: 'Senha fraca: Senha deve possuir pelo menos 1 maiúscula, 1 caracter especial',
|
||||
BAD_PASSWORD_1_pt_br: 'Senha fraca: Senha deve possuir pelo menos 1 caracter especial',
|
||||
@@ -74,7 +79,7 @@ export const serverResponsesTranslations: Record<string, string> = {
|
||||
INVALID_CREDENTIALS_pt_br: 'E-mail ou senha inválidos!',
|
||||
MAX_LOGIN_ATTEMPT_pt_br: 'Limite máximo de tentativas atingido. Por favor aguarde 30 minutos',
|
||||
NETWORK_ERROR_pt_br: 'Erro de rede ao tentar obter recursos.',
|
||||
FILL_ALL_FIELDS_pt_br: 'Por ravor, preencha todos os campos',
|
||||
FILL_ALL_FIELDS_pt_br: 'Por favor, preencha todos os campos',
|
||||
FILL_NEW_PASSWORD_pt_br: 'Por favor, informe a nova senha',
|
||||
FILL_USER_pt_br: 'Por favor, informe seu e-mail',
|
||||
FILL_USER_AND_PASS_pt_br: 'Por favor, informe seu e-mail e senha!',
|
||||
@@ -86,6 +91,11 @@ export const serverResponsesTranslations: Record<string, string> = {
|
||||
WRONG_OR_MISSING_INFO_pt_br: 'Informação errada ou incompleta!',
|
||||
NOTHING_TO_UPDATE_pt_br: 'Nada para atualizar!',
|
||||
|
||||
BAD_PASSWORD_8_es: 'Contraseña inválida: La contraseña debe tener al menos 8 caracteres',
|
||||
BAD_PASSWORD_7_es: 'Contraseña inválida: La contraseña debe tener al menos 8 caracteres, 1 carácter especial',
|
||||
BAD_PASSWORD_6_es: 'Contraseña inválida: La contraseña debe tener al menos 8 caracteres, 1 número, 1 carácter especial',
|
||||
BAD_PASSWORD_5_es: 'Contraseña inválida: La contraseña debe tener al menos 8 caracteres, 1 mayúscula, 1 número, 1 carácter especial',
|
||||
BAD_PASSWORD_4_es: 'Contraseña inválida: La contraseña debe tener al menos 1 número',
|
||||
BAD_PASSWORD_3_es: 'Contraseña inválida: La contraseña debe tener al menos 8 caracteres, 1 mayúscula y 1 carácter especial',
|
||||
BAD_PASSWORD_2_es: 'Contraseña inválida: La contraseña debe tener al menos 1 mayúscula y 1 carácter especial',
|
||||
BAD_PASSWORD_1_es: 'Contraseña inválida: La contraseña debe tener al menos 1 carácter especial',
|
||||
@@ -108,6 +118,11 @@ export const serverResponsesTranslations: Record<string, string> = {
|
||||
WRONG_OR_MISSING_INFO_es: '¡Información incorrecta o incompleta!',
|
||||
NOTHING_TO_UPDATE_es: '¡Nada que actualizar!',
|
||||
|
||||
BAD_PASSWORD_8_ru: 'Неправильный пароль: Пароль должен содержать не менее 8 символов.',
|
||||
BAD_PASSWORD_7_ru: 'Неправильный пароль: Пароль должен содержать не менее 8 символов, 1 специальный символ.',
|
||||
BAD_PASSWORD_6_ru: 'Неправильный пароль: Пароль должен содержать не менее 8 символов, 1 цифру, 1 специальный символ.',
|
||||
BAD_PASSWORD_5_ru: 'Неправильный пароль: Пароль должен содержать не менее 8 символов, 1 заглавную букву, 1 цифру, 1 специальный символ.',
|
||||
BAD_PASSWORD_4_ru: 'Неправильный пароль: Пароль должен содержать не менее 1 цифры.',
|
||||
BAD_PASSWORD_3_ru: 'Неправильный пароль: Пароль должен содержать не менее 8 символов, 1 заглавную букву, 1 специальный символ.',
|
||||
BAD_PASSWORD_2_ru: 'Неправильный пароль: Пароль должен содержать как минимум 1 заглавную букву и 1 специальный символ.',
|
||||
BAD_PASSWORD_1_ru: 'Неправильный пароль: Пароль должен содержать хотя бы 1 специальный символ.',
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
export const serverResponses: Record<string, string> = {
|
||||
'Bad password: Password must have at least at least 8 characters': 'BAD_PASSWORD_8',
|
||||
'Bad password: Password must have at least at least 8 characters, 1 special character': 'BAD_PASSWORD_7',
|
||||
'Bad password: Password must have at least at least 8 characters, 1 number, 1 special character': 'BAD_PASSWORD_6',
|
||||
'Bad password: Password must have at least at least 8 characters, 1 uppercase, 1 number, 1 special character': 'BAD_PASSWORD_5',
|
||||
'Bad password: Password must have at least at least 8 characters, 1 uppercase, 1 special character': 'BAD_PASSWORD_3',
|
||||
'Bad password: Password must have at least 1 uppercase, 1 special character': 'BAD_PASSWORD_2',
|
||||
'Bad password: Password must have at least 1 special character': 'BAD_PASSWORD_1',
|
||||
'Bad password: Password must have at least 1 number': 'BAD_PASSWORD_4',
|
||||
'Email already exists!': 'EMAIL_EXISTS',
|
||||
'Forbidden! Access denied!': 'FORBIDDEN',
|
||||
'If the email address you entered is associated with an account, you will receive a password reset link shortly.': 'RECOVER_PASSWORD',
|
||||
|
||||
@@ -39,7 +39,7 @@ services:
|
||||
ports:
|
||||
- "8585:8585"
|
||||
- "5005:5005"
|
||||
image: maven:3.9.9-eclipse-temurin-25
|
||||
image: maven:3.9.12-eclipse-temurin-25
|
||||
entrypoint: './mvnw -ntp spring-boot:run -Dspring-boot.run.jvmArguments="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:5005" -Dmaven.plugin.validation=VERBOSE'
|
||||
working_dir: /app
|
||||
volumes:
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Email Changed</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #f4f6f8;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
background-color: #f4f6f8;
|
||||
padding: 24px 12px;
|
||||
}
|
||||
|
||||
.card {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: #ffffff;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 20px 24px;
|
||||
background: #111827;
|
||||
color: #ffffff;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 24px;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.change-box {
|
||||
margin: 16px 0;
|
||||
padding: 14px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
background: #f9fafb;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #4b5563;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: #111827;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 18px 24px 24px;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
.wrapper {
|
||||
padding: 16px 8px;
|
||||
}
|
||||
|
||||
.header,
|
||||
.content,
|
||||
.footer {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
<div class="card">
|
||||
<div class="header">Your email was changed</div>
|
||||
<div class="content">
|
||||
<p>Hello,</p>
|
||||
<p>We are confirming that the email address on your account was updated.</p>
|
||||
|
||||
<div class="change-box">
|
||||
<div><span class="label">Old email:</span> <span class="value">{{ EMAIL_FROM }}</span></div>
|
||||
<div><span class="label">New email:</span> <span class="value">{{ EMAIL_TO }}</span></div>
|
||||
</div>
|
||||
|
||||
<p>If you made this change, no further action is needed.</p>
|
||||
<p>If you did not make this change, please secure your account immediately and contact support at <a href="mailto:ricardompcampos@gmail.com">ricardompcampos@gmail.com</a>.</p>
|
||||
</div>
|
||||
<div class="footer">
|
||||
This is an automatic notification, please do not reply.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,97 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Password Changed</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #f4f6f8;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
background-color: #f4f6f8;
|
||||
padding: 24px 12px;
|
||||
}
|
||||
|
||||
.card {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: #ffffff;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 20px 24px;
|
||||
background: #111827;
|
||||
color: #ffffff;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 24px;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.notice {
|
||||
margin: 16px 0;
|
||||
padding: 14px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
background: #f9fafb;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 18px 24px 24px;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
.wrapper {
|
||||
padding: 16px 8px;
|
||||
}
|
||||
|
||||
.header,
|
||||
.content,
|
||||
.footer {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
<div class="card">
|
||||
<div class="header">Your password was changed</div>
|
||||
<div class="content">
|
||||
<p>Hello,</p>
|
||||
<p>This is a confirmation that your account password was successfully changed.</p>
|
||||
|
||||
<div class="notice">
|
||||
If you made this change, no further action is needed. If you did not make this change,
|
||||
secure your account immediately and contact support at <a href="mailto:ricardompcampos@gmail.com">ricardompcampos@gmail.com</a>.
|
||||
</div>
|
||||
|
||||
<p>Thank you.</p>
|
||||
</div>
|
||||
<div class="footer">This is an automatic message, please do not reply.</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,110 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Reset Your Password</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #f4f6f8;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
background-color: #f4f6f8;
|
||||
padding: 24px 12px;
|
||||
}
|
||||
|
||||
.card {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: #ffffff;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 20px 24px;
|
||||
background: #111827;
|
||||
color: #ffffff;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 24px;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.button-wrap {
|
||||
margin: 24px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: inline-block;
|
||||
padding: 12px 20px;
|
||||
background: #b91c1c;
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 700;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.fallback-link {
|
||||
margin-top: 8px;
|
||||
font-size: 13px;
|
||||
color: #4b5563;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 18px 24px 24px;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
.wrapper {
|
||||
padding: 16px 8px;
|
||||
}
|
||||
|
||||
.header,
|
||||
.content,
|
||||
.footer {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
<div class="card">
|
||||
<div class="header">Reset your password</div>
|
||||
<div class="content">
|
||||
<p>Hello,</p>
|
||||
<p>We received a request to reset your password. Click the button below to continue.</p>
|
||||
|
||||
<div class="button-wrap">
|
||||
<a class="button" href="{{ RESET_LINK }}" target="_blank" rel="noopener noreferrer">Reset Password</a>
|
||||
<div class="fallback-link">If the button does not work, open this link: {{ RESET_LINK }}</div>
|
||||
</div>
|
||||
|
||||
<p>If you did not request this, you can ignore this email.</p>
|
||||
</div>
|
||||
<div class="footer">This is an automatic message, please do not reply.</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,114 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Confirm Your Account</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #f4f6f8;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
background-color: #f4f6f8;
|
||||
padding: 24px 12px;
|
||||
}
|
||||
|
||||
.card {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: #ffffff;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 20px 24px;
|
||||
background: #111827;
|
||||
color: #ffffff;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 24px;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.button-wrap {
|
||||
margin: 24px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: inline-block;
|
||||
padding: 12px 20px;
|
||||
background: #0f766e;
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 700;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.fallback-link {
|
||||
margin-top: 8px;
|
||||
font-size: 13px;
|
||||
color: #4b5563;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 18px 24px 24px;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
.wrapper {
|
||||
padding: 16px 8px;
|
||||
}
|
||||
|
||||
.header,
|
||||
.content,
|
||||
.footer {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
<div class="card">
|
||||
<div class="header">Confirm your account</div>
|
||||
<div class="content">
|
||||
<p>Hello,</p>
|
||||
<p>Thank you for signing up. Please confirm your account by clicking the button below.</p>
|
||||
|
||||
<div class="button-wrap">
|
||||
<a class="button" href="{{ CONFIRMATION_LINK }}" target="_blank" rel="noopener noreferrer">
|
||||
Confirm Account
|
||||
</a>
|
||||
<div class="fallback-link">If the button does not work, open this link: {{ CONFIRMATION_LINK }}</div>
|
||||
</div>
|
||||
|
||||
<p>If you did not create this account, you can ignore this email.</p>
|
||||
</div>
|
||||
<div class="footer">
|
||||
This is an automatic message, please do not reply.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
+2
-2
@@ -11,7 +11,7 @@
|
||||
|
||||
<groupId>br.com.tasknoteapp</groupId>
|
||||
<artifactId>server</artifactId>
|
||||
<version>10</version>
|
||||
<version>13</version>
|
||||
<name>tasknote-api</name>
|
||||
<description>Java backend REST API to serve TaskNote frontend client</description>
|
||||
|
||||
@@ -173,7 +173,7 @@
|
||||
<configuration>
|
||||
<enableLazyInitialization>true</enableLazyInitialization>
|
||||
<enableDirtyTracking>true</enableDirtyTracking>
|
||||
<enableAssociationManagement>true</enableAssociationManagement>
|
||||
<enableAssociationManagement>false</enableAssociationManagement>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.springframework.security.config.annotation.authentication.configurati
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
@@ -59,6 +60,8 @@ public class SecurityConfig {
|
||||
.permitAll())
|
||||
.httpBasic(AbstractHttpConfigurer::disable)
|
||||
.formLogin(AbstractHttpConfigurer::disable)
|
||||
.sessionManagement(
|
||||
session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.exceptionHandling(
|
||||
exceptionHandling ->
|
||||
exceptionHandling.authenticationEntryPoint(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package br.com.tasknoteapp.server.controller;
|
||||
|
||||
import br.com.tasknoteapp.server.entity.NoteEntity;
|
||||
import br.com.tasknoteapp.server.exception.NoteNotFoundException;
|
||||
import br.com.tasknoteapp.server.request.NotePatchRequest;
|
||||
import br.com.tasknoteapp.server.request.NoteRequest;
|
||||
@@ -77,8 +76,8 @@ public class NoteController {
|
||||
*/
|
||||
@PostMapping
|
||||
public ResponseEntity<NoteResponse> postNotes(@RequestBody @Valid NoteRequest noteRequest) {
|
||||
NoteEntity createdNote = noteService.createNote(noteRequest);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(NoteResponse.fromEntity(createdNote));
|
||||
NoteResponse createdNote = noteService.createNote(noteRequest);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(createdNote);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+54
@@ -1,6 +1,13 @@
|
||||
package br.com.tasknoteapp.server.controller;
|
||||
|
||||
import br.com.tasknoteapp.server.exception.BaseBadRequestException;
|
||||
import br.com.tasknoteapp.server.exception.BaseNotFoundException;
|
||||
import br.com.tasknoteapp.server.exception.BaseServiceUnavailableException;
|
||||
import br.com.tasknoteapp.server.exception.EmailAlreadyExistsException;
|
||||
import br.com.tasknoteapp.server.exception.InvalidCredentialsException;
|
||||
import br.com.tasknoteapp.server.exception.UserForbiddenException;
|
||||
import br.com.tasknoteapp.server.response.ValidationExceptionResponse;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
@@ -15,4 +22,51 @@ public class RestExceptionController {
|
||||
MethodArgumentNotValidException ex) {
|
||||
return ResponseEntity.badRequest().body(new ValidationExceptionResponse(ex.getFieldErrors()));
|
||||
}
|
||||
|
||||
/* 400 - Bad Request */
|
||||
@ExceptionHandler(BaseBadRequestException.class)
|
||||
ResponseEntity<ValidationExceptionResponse> handleBadRequestException(
|
||||
BaseBadRequestException ex) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(new ValidationExceptionResponse(ex.getField(), ex.getReason()));
|
||||
}
|
||||
|
||||
/* 401 - Unauthorized */
|
||||
@ExceptionHandler(InvalidCredentialsException.class)
|
||||
ResponseEntity<ValidationExceptionResponse> handleInvalidCredentialsException(
|
||||
InvalidCredentialsException ex) {
|
||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
|
||||
.body(new ValidationExceptionResponse("access", ex.getReason()));
|
||||
}
|
||||
|
||||
/* 403 - Forbidden */
|
||||
@ExceptionHandler(UserForbiddenException.class)
|
||||
ResponseEntity<ValidationExceptionResponse> handleUserForbiddenException(
|
||||
UserForbiddenException ex) {
|
||||
return ResponseEntity.status(HttpStatus.FORBIDDEN)
|
||||
.body(new ValidationExceptionResponse("access", ex.getReason()));
|
||||
}
|
||||
|
||||
/* 404 - Not Found */
|
||||
@ExceptionHandler(BaseNotFoundException.class)
|
||||
ResponseEntity<ValidationExceptionResponse> handleNotFoundException(BaseNotFoundException ex) {
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND)
|
||||
.body(new ValidationExceptionResponse(ex.getField(), ex.getReason()));
|
||||
}
|
||||
|
||||
/* 409 - Conflict */
|
||||
@ExceptionHandler(EmailAlreadyExistsException.class)
|
||||
ResponseEntity<ValidationExceptionResponse> handleEmailAlreadyExistsException(
|
||||
EmailAlreadyExistsException ex) {
|
||||
return ResponseEntity.status(HttpStatus.CONFLICT)
|
||||
.body(new ValidationExceptionResponse("email", ex.getReason()));
|
||||
}
|
||||
|
||||
/* 503 - Service Unavailable */
|
||||
@ExceptionHandler(BaseServiceUnavailableException.class)
|
||||
ResponseEntity<ValidationExceptionResponse> handleServiceUnavailableException(
|
||||
BaseServiceUnavailableException ex) {
|
||||
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
|
||||
.body(new ValidationExceptionResponse(ex.getField(), ex.getReason()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@@ -30,9 +29,6 @@ public class NoteEntity {
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
private UserEntity user;
|
||||
|
||||
@OneToOne(mappedBy = "note", fetch = FetchType.LAZY)
|
||||
private NoteUrlEntity noteUrl;
|
||||
|
||||
@Column(name = "tag", nullable = true, length = 30)
|
||||
private String tag;
|
||||
|
||||
@@ -77,14 +73,6 @@ public class NoteEntity {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public NoteUrlEntity getNoteUrl() {
|
||||
return noteUrl;
|
||||
}
|
||||
|
||||
public void setNoteUrl(NoteUrlEntity noteUrl) {
|
||||
this.noteUrl = noteUrl;
|
||||
}
|
||||
|
||||
public String getTag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collection;
|
||||
@@ -41,9 +40,6 @@ public class UserEntity implements UserDetails {
|
||||
@Column(name = "name", length = 20)
|
||||
private String name;
|
||||
|
||||
@OneToMany(mappedBy = "user")
|
||||
private List<TaskEntity> tasks;
|
||||
|
||||
@Column(name = "email_confirmed_at", nullable = true)
|
||||
private LocalDateTime emailConfirmedAt;
|
||||
|
||||
@@ -147,14 +143,6 @@ public class UserEntity implements UserDetails {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public List<TaskEntity> getTasks() {
|
||||
return tasks;
|
||||
}
|
||||
|
||||
public void setTasks(List<TaskEntity> tasks) {
|
||||
this.tasks = tasks;
|
||||
}
|
||||
|
||||
public LocalDateTime getEmailConfirmedAt() {
|
||||
return emailConfirmedAt;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
package br.com.tasknoteapp.server.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
/** This exception represents an error when hashing. */
|
||||
@ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
|
||||
public class BadAlgorithmException extends ResponseStatusException {
|
||||
public class BadAlgorithmException extends BaseServiceUnavailableException {
|
||||
|
||||
public BadAlgorithmException(String error) {
|
||||
super(HttpStatus.SERVICE_UNAVAILABLE, error);
|
||||
super("algorithm", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
package br.com.tasknoteapp.server.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
/** This class represents a Bad Language exception. */
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public class BadLanguageException extends ResponseStatusException {
|
||||
public class BadLanguageException extends BaseBadRequestException {
|
||||
|
||||
public BadLanguageException() {
|
||||
super(HttpStatus.BAD_REQUEST, "Invalid language");
|
||||
super("lang", "Invalid language");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
package br.com.tasknoteapp.server.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
/** This class represents a Bad Password exception. */
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public class BadPasswordException extends ResponseStatusException {
|
||||
public class BadPasswordException extends BaseBadRequestException {
|
||||
|
||||
public BadPasswordException(String message) {
|
||||
super(HttpStatus.BAD_REQUEST, String.format("Bad password: %s", message));
|
||||
super("password", String.format("Bad password: %s", message));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
package br.com.tasknoteapp.server.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
/** This class represents a bad request when trying to convert to UUID. */
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public class BadUuidException extends ResponseStatusException {
|
||||
public class BadUuidException extends BaseBadRequestException {
|
||||
|
||||
public BadUuidException() {
|
||||
super(HttpStatus.BAD_REQUEST, "Bad user identification");
|
||||
super("uuid", "Bad user identification");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package br.com.tasknoteapp.server.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
/** This class represents a base exception for bad requests. */
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public class BaseBadRequestException extends ResponseStatusException {
|
||||
private final String field;
|
||||
|
||||
public BaseBadRequestException(String field, String message) {
|
||||
super(HttpStatus.BAD_REQUEST, message);
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
public String getField() {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package br.com.tasknoteapp.server.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
/** This exception represents a resource not found error. */
|
||||
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||
public class BaseNotFoundException extends ResponseStatusException {
|
||||
private final String field;
|
||||
|
||||
public BaseNotFoundException(String field, String message) {
|
||||
super(HttpStatus.NOT_FOUND, message);
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
public String getField() {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package br.com.tasknoteapp.server.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
/** This exception represents a service unavailable error. */
|
||||
@ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
|
||||
public class BaseServiceUnavailableException extends ResponseStatusException {
|
||||
|
||||
private final String field;
|
||||
|
||||
public BaseServiceUnavailableException(String field, String reason) {
|
||||
super(HttpStatus.SERVICE_UNAVAILABLE, reason);
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
public String getField() {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,9 @@
|
||||
package br.com.tasknoteapp.server.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
/** This exception represents an error when sending email messages. */
|
||||
@ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
|
||||
public class MailServiceException extends ResponseStatusException {
|
||||
public class MailServiceException extends BaseServiceUnavailableException {
|
||||
|
||||
public MailServiceException(String error) {
|
||||
super(HttpStatus.SERVICE_UNAVAILABLE, error);
|
||||
super("mail", error);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-7
@@ -1,14 +1,9 @@
|
||||
package br.com.tasknoteapp.server.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
/** This class represents a Max login limit exception. */
|
||||
@ResponseStatus(code = HttpStatus.BAD_REQUEST)
|
||||
public class MaxLoginLimitAttemptException extends ResponseStatusException {
|
||||
public class MaxLoginLimitAttemptException extends BaseBadRequestException {
|
||||
|
||||
public MaxLoginLimitAttemptException() {
|
||||
super(HttpStatus.BAD_REQUEST, "Max login attempt limit reached. Please wait 30 minutes");
|
||||
super("login", "Max login attempt limit reached. Please wait 30 minutes");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
package br.com.tasknoteapp.server.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
/** This class represents a Note Not Found request. */
|
||||
@ResponseStatus(code = HttpStatus.NOT_FOUND)
|
||||
public class NoteNotFoundException extends ResponseStatusException {
|
||||
public class NoteNotFoundException extends BaseNotFoundException {
|
||||
|
||||
public NoteNotFoundException() {
|
||||
super(HttpStatus.NOT_FOUND, "Task not found");
|
||||
super("note", "Note not found");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
package br.com.tasknoteapp.server.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
/** This class represents a Reset expired request. */
|
||||
@ResponseStatus(code = HttpStatus.BAD_REQUEST)
|
||||
public class ResetExpiredException extends ResponseStatusException {
|
||||
public class ResetExpiredException extends BaseBadRequestException {
|
||||
|
||||
public ResetExpiredException() {
|
||||
super(HttpStatus.NOT_FOUND, "Expired reset link.");
|
||||
super("reset", "Expired reset link.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
package br.com.tasknoteapp.server.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
/** This class represents a Task Not Found request. */
|
||||
@ResponseStatus(code = HttpStatus.NOT_FOUND)
|
||||
public class TaskNotFoundException extends ResponseStatusException {
|
||||
public class TaskNotFoundException extends BaseNotFoundException {
|
||||
|
||||
public TaskNotFoundException() {
|
||||
super(HttpStatus.NOT_FOUND, "Task not found");
|
||||
super("task", "Task not found");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
package br.com.tasknoteapp.server.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
/** This class represents a User Not Found request. */
|
||||
@ResponseStatus(code = HttpStatus.NOT_FOUND)
|
||||
public class UserNotFoundException extends ResponseStatusException {
|
||||
public class UserNotFoundException extends BaseNotFoundException {
|
||||
|
||||
public UserNotFoundException() {
|
||||
super(HttpStatus.NOT_FOUND, "User not found");
|
||||
super("user", "User not found");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ public interface NoteRepository extends JpaRepository<NoteEntity, Long> {
|
||||
|
||||
Optional<NoteEntity> findByShareToken(String shareToken);
|
||||
|
||||
Optional<NoteEntity> findByIdAndUser_id(Long id, Long userId);
|
||||
|
||||
@Query(
|
||||
"select n from NoteEntity n where (upper(n.title) like %?1% or upper(n.description) like"
|
||||
+ " %?1%) and n.user.id = ?2")
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
package br.com.tasknoteapp.server.repository;
|
||||
|
||||
import br.com.tasknoteapp.server.entity.NoteUrlEntity;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
/** This interface represents a note url repository, for database access. */
|
||||
public interface NoteUrlRepository extends JpaRepository<NoteUrlEntity, Long> {
|
||||
|
||||
Optional<NoteUrlEntity> findByNote_id(Long noteId);
|
||||
|
||||
List<NoteUrlEntity> findAllByNote_idIn(List<Long> noteIds);
|
||||
|
||||
void deleteByNote_id(Long noteId);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ public interface UserPwdLimitRepository extends JpaRepository<UserPwdLimitEntity
|
||||
|
||||
List<UserPwdLimitEntity> findAllByUser_id(Long userId, Sort sort);
|
||||
|
||||
List<UserPwdLimitEntity> findTop3ByUser_idOrderByWhenHappenedDesc(Long userId);
|
||||
|
||||
@Modifying
|
||||
@Query("delete UserPwdLimitEntity u where u.user.id = ?1")
|
||||
void deleteAllForUser(Long userId);
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package br.com.tasknoteapp.server.response;
|
||||
|
||||
import br.com.tasknoteapp.server.entity.NoteEntity;
|
||||
import br.com.tasknoteapp.server.entity.NoteUrlEntity;
|
||||
import br.com.tasknoteapp.server.util.TimeAgoUtil;
|
||||
import java.util.Objects;
|
||||
|
||||
/** This record represents a task and its urls object to be returned. */
|
||||
public record NoteResponse(
|
||||
@@ -16,9 +14,7 @@ public record NoteResponse(
|
||||
* @param entity The NoteEntity source data.
|
||||
* @return NoteResponse instance with all note data and urls, if any.
|
||||
*/
|
||||
public static NoteResponse fromEntity(NoteEntity entity) {
|
||||
NoteUrlEntity noteUrl = entity.getNoteUrl();
|
||||
String url = Objects.isNull(noteUrl) ? null : noteUrl.getUrl();
|
||||
public static NoteResponse fromEntity(NoteEntity entity, String url) {
|
||||
String timeAgoFmt = TimeAgoUtil.format(entity.getLastUpdate());
|
||||
|
||||
return new NoteResponse(
|
||||
|
||||
+5
@@ -25,6 +25,11 @@ public class ValidationExceptionResponse {
|
||||
this.errorMessage = String.format(MESSAGE_TEMPLATE, fields.size());
|
||||
}
|
||||
|
||||
public ValidationExceptionResponse(String field, String errorMessage) {
|
||||
this.fields = List.of(new FieldIssueResponse(field, errorMessage));
|
||||
this.errorMessage = String.format(MESSAGE_TEMPLATE, fields.size());
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
@@ -36,8 +36,6 @@ import java.util.UUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
@@ -513,8 +511,10 @@ public class AuthService {
|
||||
}
|
||||
|
||||
private void checkLoginAttemptLimit(Long userId) {
|
||||
Sort sort = Sort.by(Direction.DESC, "whenHappened");
|
||||
List<UserPwdLimitEntity> userPwdList = userPwdLimitRepository.findAllByUser_id(userId, sort);
|
||||
// Fetch only the 3 most recent failed attempts to avoid loading unbounded rows for
|
||||
// targeted/brute-forced accounts.
|
||||
List<UserPwdLimitEntity> userPwdList =
|
||||
userPwdLimitRepository.findTop3ByUser_idOrderByWhenHappenedDesc(userId);
|
||||
|
||||
logger.warn("login count attempt for user {}: {}", userId, userPwdList.size());
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import br.com.tasknoteapp.server.templates.MailgunTemplateResetPwd;
|
||||
import br.com.tasknoteapp.server.templates.MailgunTemplateResetPwdConfirm;
|
||||
import br.com.tasknoteapp.server.templates.MailgunTemplateSignUp;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.Base64;
|
||||
import java.util.Objects;
|
||||
import org.slf4j.Logger;
|
||||
@@ -53,7 +54,11 @@ public class MailgunEmailService {
|
||||
this.senderEmail = sender;
|
||||
this.targetEnv = targetEnv;
|
||||
this.restTemplate =
|
||||
templateBuilder.defaultHeader(HttpHeaders.AUTHORIZATION, basicAuth("api", apiKey)).build();
|
||||
templateBuilder
|
||||
.connectTimeout(Duration.ofSeconds(5))
|
||||
.readTimeout(Duration.ofSeconds(10))
|
||||
.defaultHeader(HttpHeaders.AUTHORIZATION, basicAuth("api", apiKey))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,6 @@ import br.com.tasknoteapp.server.entity.NoteEntity;
|
||||
import br.com.tasknoteapp.server.entity.NoteUrlEntity;
|
||||
import br.com.tasknoteapp.server.entity.UserEntity;
|
||||
import br.com.tasknoteapp.server.exception.NoteNotFoundException;
|
||||
import br.com.tasknoteapp.server.exception.TaskNotFoundException;
|
||||
import br.com.tasknoteapp.server.repository.NoteRepository;
|
||||
import br.com.tasknoteapp.server.repository.NoteUrlRepository;
|
||||
import br.com.tasknoteapp.server.request.NotePatchRequest;
|
||||
@@ -14,9 +13,11 @@ import br.com.tasknoteapp.server.util.AuthUtil;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -67,35 +68,39 @@ public class NoteService {
|
||||
List<NoteEntity> notes = noteRepository.findAllByUser_id(user.getId());
|
||||
logger.info(notes.size() + " notes found!");
|
||||
|
||||
return notes.stream().map(NoteResponse::fromEntity).toList();
|
||||
return getNotesUrl(notes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a note by its id.
|
||||
*
|
||||
* @param noteId The task id in the database.
|
||||
* @return {@link NoteResponse} with the found task or throw a {@link TaskNotFoundException}.
|
||||
* @param noteId The note id in the database.
|
||||
* @return {@link NoteResponse} with the found note or throw a {@link NoteNotFoundException}.
|
||||
*/
|
||||
public NoteResponse getNoteById(Long noteId) {
|
||||
UserEntity user = getCurrentUser();
|
||||
logger.info("Get note " + noteId + " to user " + user.getId());
|
||||
|
||||
Optional<NoteEntity> task = noteRepository.findById(noteId);
|
||||
if (task.isEmpty()) {
|
||||
Optional<NoteEntity> note = noteRepository.findById(noteId);
|
||||
if (note.isEmpty()) {
|
||||
throw new NoteNotFoundException();
|
||||
}
|
||||
|
||||
if (!note.get().getUser().getId().equals(user.getId())) {
|
||||
throw new NoteNotFoundException();
|
||||
}
|
||||
|
||||
logger.info("Note found! Id " + noteId);
|
||||
return NoteResponse.fromEntity(task.get());
|
||||
return NoteResponse.fromEntity(note.get(), getNoteUrl(noteId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a note for the user.
|
||||
*
|
||||
* @param noteRequest The note content.
|
||||
* @return {@link NoteEntity} created in the database
|
||||
* @return {@link NoteResponse} with created note data.
|
||||
*/
|
||||
public NoteEntity createNote(NoteRequest noteRequest) {
|
||||
public NoteResponse createNote(NoteRequest noteRequest) {
|
||||
UserEntity user = getCurrentUser();
|
||||
|
||||
logger.info("Creating note to user " + user.getId());
|
||||
@@ -110,13 +115,14 @@ public class NoteService {
|
||||
|
||||
logger.info("Note created! Id " + created.getId());
|
||||
|
||||
String savedUrl = null;
|
||||
if (!Objects.isNull(noteRequest.url()) && !noteRequest.url().isEmpty()) {
|
||||
NoteUrlEntity urlEntity = saveUrl(note, noteRequest.url());
|
||||
note.setNoteUrl(urlEntity);
|
||||
NoteUrlEntity urlEntity = saveUrl(created, noteRequest.url());
|
||||
savedUrl = urlEntity.getUrl();
|
||||
}
|
||||
|
||||
logger.info("Finished note creation!");
|
||||
return created;
|
||||
return NoteResponse.fromEntity(created, savedUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,7 +138,7 @@ public class NoteService {
|
||||
|
||||
logger.info("Patching task " + noteId + " to user " + user.getId());
|
||||
|
||||
Optional<NoteEntity> note = noteRepository.findById(noteId);
|
||||
Optional<NoteEntity> note = noteRepository.findByIdAndUser_id(noteId, user.getId());
|
||||
if (note.isEmpty()) {
|
||||
throw new NoteNotFoundException();
|
||||
}
|
||||
@@ -154,8 +160,7 @@ public class NoteService {
|
||||
logger.info("URL deleted from task " + noteId);
|
||||
|
||||
if (!Objects.isNull(patch.url()) && !patch.url().isBlank()) {
|
||||
NoteUrlEntity urlEntity = saveUrl(noteEntity, patch.url());
|
||||
noteEntity.setNoteUrl(urlEntity);
|
||||
saveUrl(noteEntity, patch.url());
|
||||
} else {
|
||||
logger.info("No urls to patch for task " + noteId);
|
||||
}
|
||||
@@ -165,7 +170,7 @@ public class NoteService {
|
||||
|
||||
logger.info("Note patched! Id " + patchedNote.getId());
|
||||
|
||||
return NoteResponse.fromEntity(patchedNote);
|
||||
return NoteResponse.fromEntity(patchedNote, getNoteUrl(patchedNote.getId()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -179,20 +184,15 @@ public class NoteService {
|
||||
|
||||
logger.info("Deleting note " + noteId + " to user " + user.getId());
|
||||
|
||||
Optional<NoteEntity> note = noteRepository.findById(noteId);
|
||||
Optional<NoteEntity> note = noteRepository.findByIdAndUser_id(noteId, user.getId());
|
||||
if (note.isEmpty()) {
|
||||
throw new NoteNotFoundException();
|
||||
}
|
||||
|
||||
NoteEntity noteEntity = note.get();
|
||||
|
||||
NoteUrlEntity noteUrl = noteEntity.getNoteUrl();
|
||||
if (!Objects.isNull(noteUrl)) {
|
||||
noteUrlRepository.delete(noteUrl);
|
||||
logger.info("URL Deleted from task " + noteId);
|
||||
} else {
|
||||
logger.info("No urls to delete for task " + noteId);
|
||||
}
|
||||
noteUrlRepository.deleteByNote_id(noteId);
|
||||
logger.info("URL deleted from task " + noteId);
|
||||
|
||||
noteRepository.delete(noteEntity);
|
||||
|
||||
@@ -213,7 +213,7 @@ public class NoteService {
|
||||
List<NoteEntity> notes =
|
||||
noteRepository.findAllBySearchTerm(searchTerm.toUpperCase(), user.getId());
|
||||
logger.info(notes.size() + " tasks found!");
|
||||
return notes.stream().map(NoteResponse::fromEntity).toList();
|
||||
return getNotesUrl(notes);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -222,16 +222,18 @@ public class NoteService {
|
||||
* @param noteId The note id from the database.
|
||||
* @return {@link NoteResponse} containing the updated note with share token.
|
||||
*/
|
||||
@Transactional
|
||||
public NoteResponse shareNote(Long noteId) {
|
||||
UserEntity user = getCurrentUser();
|
||||
logger.info("Sharing note " + noteId + " for user " + user.getId());
|
||||
|
||||
Optional<NoteEntity> noteOpt = noteRepository.findById(noteId);
|
||||
Optional<NoteEntity> noteOpt = noteRepository.findByIdAndUser_id(noteId, user.getId());
|
||||
if (noteOpt.isEmpty()) {
|
||||
throw new NoteNotFoundException();
|
||||
}
|
||||
|
||||
NoteEntity noteEntity = noteOpt.get();
|
||||
|
||||
if (!noteEntity.isShared()) {
|
||||
noteEntity.setShared(true);
|
||||
noteEntity.setShareToken(UUID.randomUUID().toString());
|
||||
@@ -239,7 +241,7 @@ public class NoteService {
|
||||
logger.info("Note " + noteId + " shared with token " + noteEntity.getShareToken());
|
||||
}
|
||||
|
||||
return NoteResponse.fromEntity(noteEntity);
|
||||
return NoteResponse.fromEntity(noteEntity, getNoteUrl(noteEntity.getId()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -252,7 +254,7 @@ public class NoteService {
|
||||
UserEntity user = getCurrentUser();
|
||||
logger.info("Unsharing note " + noteId + " for user " + user.getId());
|
||||
|
||||
Optional<NoteEntity> noteOpt = noteRepository.findById(noteId);
|
||||
Optional<NoteEntity> noteOpt = noteRepository.findByIdAndUser_id(noteId, user.getId());
|
||||
if (noteOpt.isEmpty()) {
|
||||
throw new NoteNotFoundException();
|
||||
}
|
||||
@@ -263,7 +265,7 @@ public class NoteService {
|
||||
noteRepository.save(noteEntity);
|
||||
logger.info("Note " + noteId + " unshared.");
|
||||
|
||||
return NoteResponse.fromEntity(noteEntity);
|
||||
return NoteResponse.fromEntity(noteEntity, getNoteUrl(noteEntity.getId()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -280,7 +282,7 @@ public class NoteService {
|
||||
throw new NoteNotFoundException();
|
||||
}
|
||||
|
||||
return NoteResponse.fromEntity(noteOpt.get());
|
||||
return NoteResponse.fromEntity(noteOpt.get(), getNoteUrl(noteOpt.get().getId()));
|
||||
}
|
||||
|
||||
private UserEntity getCurrentUser() {
|
||||
@@ -289,6 +291,22 @@ public class NoteService {
|
||||
return authService.findByEmail(email).orElseThrow();
|
||||
}
|
||||
|
||||
private String getNoteUrl(Long noteId) {
|
||||
return noteUrlRepository.findByNote_id(noteId).map(NoteUrlEntity::getUrl).orElse(null);
|
||||
}
|
||||
|
||||
private List<NoteResponse> getNotesUrl(List<NoteEntity> notes) {
|
||||
List<Long> noteIds = notes.stream().map(NoteEntity::getId).toList();
|
||||
if (noteIds.isEmpty()) {
|
||||
return notes.stream().map(n -> NoteResponse.fromEntity(n, null)).toList();
|
||||
}
|
||||
List<NoteUrlEntity> urls = noteUrlRepository.findAllByNote_idIn(noteIds);
|
||||
Map<Long, String> noteUrls =
|
||||
urls.stream().collect(Collectors.toMap(nu -> nu.getNote().getId(), NoteUrlEntity::getUrl));
|
||||
|
||||
return notes.stream().map(n -> NoteResponse.fromEntity(n, noteUrls.get(n.getId()))).toList();
|
||||
}
|
||||
|
||||
private NoteUrlEntity saveUrl(NoteEntity noteEntity, String url) {
|
||||
NoteUrlEntity noteUrl = new NoteUrlEntity();
|
||||
noteUrl.setUrl(url);
|
||||
|
||||
@@ -4,7 +4,6 @@ import br.com.tasknoteapp.server.entity.UserEntity;
|
||||
import br.com.tasknoteapp.server.repository.UserRepository;
|
||||
import br.com.tasknoteapp.server.service.UserService;
|
||||
import java.util.Optional;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -12,24 +11,21 @@ import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
class UserServiceImpl implements UserService {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final UserDetailsService cachedUserDetailsService;
|
||||
|
||||
public UserServiceImpl(UserRepository userRepository) {
|
||||
this.userRepository = userRepository;
|
||||
this.cachedUserDetailsService =
|
||||
email -> {
|
||||
Optional<UserEntity> user = userRepository.findByEmail(email);
|
||||
if (user.isEmpty()) {
|
||||
throw new RuntimeException("User not found: " + email);
|
||||
}
|
||||
return user.get();
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserDetailsService userDetailsService() {
|
||||
return new UserDetailsService() {
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String email) {
|
||||
Optional<UserEntity> user = userRepository.findByEmail(email);
|
||||
if (user.isEmpty()) {
|
||||
throw new RuntimeException("User not found: " + email);
|
||||
}
|
||||
|
||||
return user.get();
|
||||
}
|
||||
};
|
||||
return this.cachedUserDetailsService;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ import java.util.Optional;
|
||||
/** This class represents a template for the email change workflow. */
|
||||
public class MailgunTemplateEmailChanged implements MailgunTemplate {
|
||||
|
||||
private String templateName = "email changed";
|
||||
private String templateName = "email_changed";
|
||||
private String carbonCopy;
|
||||
private final Map<String, Object> props;
|
||||
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import java.util.Map;
|
||||
/** This class represents a template for the password reset workflow. */
|
||||
public class MailgunTemplateResetPwd implements MailgunTemplate {
|
||||
|
||||
private String templateName = "password reset";
|
||||
private String templateName = "password_reset";
|
||||
private final Map<String, Object> props;
|
||||
|
||||
public MailgunTemplateResetPwd() {
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import java.util.Map;
|
||||
/** This class represents a template for the password reset confirmation workflow. */
|
||||
public class MailgunTemplateResetPwdConfirm implements MailgunTemplate {
|
||||
|
||||
private String templateName = "password change confirmation";
|
||||
private String templateName = "password_change_confirmation";
|
||||
private final Map<String, Object> props;
|
||||
|
||||
public MailgunTemplateResetPwdConfirm() {
|
||||
|
||||
@@ -6,7 +6,7 @@ import java.util.Map;
|
||||
/** This class represents a template for the sign up workflow. */
|
||||
public class MailgunTemplateSignUp implements MailgunTemplate {
|
||||
|
||||
private String templateName = "sign up confirmation";
|
||||
private String templateName = "sign_up_confirmation";
|
||||
private final Map<String, Object> props;
|
||||
|
||||
public MailgunTemplateSignUp() {
|
||||
|
||||
@@ -36,7 +36,8 @@ spring:
|
||||
locations: classpath:db/migration
|
||||
jpa:
|
||||
database-platform: org.hibernate.dialect.PostgreSQLDialect
|
||||
open-in-view: false
|
||||
properties:
|
||||
hibernate:
|
||||
default_schema: tasknote
|
||||
show-sql: true
|
||||
show-sql: false
|
||||
|
||||
@@ -13,8 +13,8 @@ logging:
|
||||
|
||||
mailgun:
|
||||
api-key: ${MAILGUN_APIKEY:abc123456}
|
||||
domain: tasknoteapp.dev.br
|
||||
sender-email: no-reply@tasknoteapp.dev.br
|
||||
domain: tasknote.darkroasted.vps-kinghost.net
|
||||
sender-email: no-reply@tasknote.darkroasted.vps-kinghost.net
|
||||
|
||||
server:
|
||||
port: 8585
|
||||
@@ -36,7 +36,8 @@ spring:
|
||||
locations: classpath:db/migration
|
||||
jpa:
|
||||
database-platform: org.hibernate.dialect.PostgreSQLDialect
|
||||
open-in-view: false
|
||||
properties:
|
||||
hibernate:
|
||||
default_schema: tasknote
|
||||
show-sql: true
|
||||
show-sql: false
|
||||
|
||||
@@ -12,7 +12,6 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import br.com.tasknoteapp.server.entity.NoteEntity;
|
||||
import br.com.tasknoteapp.server.exception.NoteNotFoundException;
|
||||
import br.com.tasknoteapp.server.request.NotePatchRequest;
|
||||
import br.com.tasknoteapp.server.request.NoteRequest;
|
||||
@@ -199,10 +198,8 @@ class NoteControllerTest {
|
||||
void postNotes_happyPath_shouldSucceed() throws Exception {
|
||||
NoteRequest request = new NoteRequest("Title", "Description", null, null);
|
||||
|
||||
NoteEntity entity = new NoteEntity();
|
||||
entity.setId(1L);
|
||||
entity.setTitle(request.title());
|
||||
entity.setDescription(request.description());
|
||||
NoteResponse entity = new NoteResponse(1L, request.title(), request.description(),
|
||||
null, null, null, false, null);
|
||||
|
||||
when(noteService.createNote(request)).thenReturn(entity);
|
||||
|
||||
@@ -223,9 +220,9 @@ class NoteControllerTest {
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.content(payloadJson))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.id").value(entity.getId()))
|
||||
.andExpect(jsonPath("$.title").value(entity.getTitle()))
|
||||
.andExpect(jsonPath("$.description").value(entity.getDescription()))
|
||||
.andExpect(jsonPath("$.id").value(entity.id()))
|
||||
.andExpect(jsonPath("$.title").value(entity.title()))
|
||||
.andExpect(jsonPath("$.description").value(entity.description()))
|
||||
.andExpect(jsonPath("$.url", Matchers.nullValue()))
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@@ -36,8 +36,6 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
@@ -201,8 +199,8 @@ class AuthServiceTest {
|
||||
existing.setEmail(request.email());
|
||||
when(userRepository.findByEmail(request.email())).thenReturn(Optional.of(existing));
|
||||
|
||||
Sort sort = Sort.by(Direction.DESC, "whenHappened");
|
||||
when(userPwdLimitRepository.findAllByUser_id(existing.getId(), sort)).thenReturn(List.of());
|
||||
when(userPwdLimitRepository.findTop3ByUser_idOrderByWhenHappenedDesc(existing.getId()))
|
||||
.thenReturn(List.of());
|
||||
when(authenticationManager.authenticate(any())).thenReturn(null);
|
||||
when(jwtService.generateToken(existing)).thenReturn("a1b2c3");
|
||||
|
||||
@@ -241,8 +239,7 @@ class AuthServiceTest {
|
||||
limit1.setWhenHappened(LocalDateTime.now().minusMinutes(1));
|
||||
UserPwdLimitEntity limit2 = new UserPwdLimitEntity();
|
||||
UserPwdLimitEntity limit3 = new UserPwdLimitEntity();
|
||||
Sort sort = Sort.by(Direction.DESC, "whenHappened");
|
||||
when(userPwdLimitRepository.findAllByUser_id(existing.getId(), sort))
|
||||
when(userPwdLimitRepository.findTop3ByUser_idOrderByWhenHappenedDesc(existing.getId()))
|
||||
.thenReturn(List.of(limit1, limit2, limit3));
|
||||
|
||||
Assertions.assertThrows(
|
||||
@@ -261,8 +258,8 @@ class AuthServiceTest {
|
||||
existing.setId(919L);
|
||||
when(userRepository.findByEmail(request.email())).thenReturn(Optional.of(existing));
|
||||
|
||||
Sort sort = Sort.by(Direction.DESC, "whenHappened");
|
||||
when(userPwdLimitRepository.findAllByUser_id(existing.getId(), sort)).thenReturn(List.of());
|
||||
when(userPwdLimitRepository.findTop3ByUser_idOrderByWhenHappenedDesc(existing.getId()))
|
||||
.thenReturn(List.of());
|
||||
when(authenticationManager.authenticate(any())).thenThrow(new BadCredentialsException("Wrong"));
|
||||
|
||||
UserResponseWithToken token = authService.signInUser(request);
|
||||
|
||||
@@ -30,14 +30,15 @@ class MailgunEmailServiceTest {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
when(restTemplateBuilder.connectTimeout(any())).thenReturn(restTemplateBuilder);
|
||||
when(restTemplateBuilder.readTimeout(any())).thenReturn(restTemplateBuilder);
|
||||
when(restTemplateBuilder.defaultHeader(any(), any())).thenReturn(restTemplateBuilder);
|
||||
when(restTemplateBuilder.build()).thenReturn(restTemplate);
|
||||
|
||||
String apiKey = "abx123";
|
||||
String domain = "domain.com";
|
||||
String sender = "no-reply@domain.com";
|
||||
String target = "development";
|
||||
|
||||
when(restTemplateBuilder.defaultHeader(any(), any())).thenReturn(restTemplateBuilder);
|
||||
when(restTemplateBuilder.build()).thenReturn(restTemplate);
|
||||
|
||||
mailgunEmailService =
|
||||
new MailgunEmailService(apiKey, domain, sender, target, restTemplateBuilder);
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ package br.com.tasknoteapp.server.service;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.anyString;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -105,9 +105,9 @@ class NoteServiceTest {
|
||||
when(noteRepository.save(any(NoteEntity.class))).thenReturn(note);
|
||||
when(noteUrlRepository.save(any(NoteUrlEntity.class))).thenReturn(new NoteUrlEntity());
|
||||
|
||||
NoteEntity createdNote = noteService.createNote(noteRequest);
|
||||
NoteResponse createdNote = noteService.createNote(noteRequest);
|
||||
|
||||
assertEquals("Test Note", createdNote.getTitle());
|
||||
assertEquals("Test Note", createdNote.title());
|
||||
verify(noteRepository, times(1)).save(any(NoteEntity.class));
|
||||
verify(noteUrlRepository, times(1)).save(any(NoteUrlEntity.class));
|
||||
}
|
||||
@@ -116,13 +116,14 @@ class NoteServiceTest {
|
||||
void patchNote() {
|
||||
when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(user.getEmail()));
|
||||
when(authService.findByEmail(user.getEmail())).thenReturn(Optional.of(user));
|
||||
when(noteRepository.findById(note.getId())).thenReturn(Optional.of(note));
|
||||
when(noteRepository.findByIdAndUser_id(note.getId(), user.getId()))
|
||||
.thenReturn(Optional.of(note));
|
||||
when(noteRepository.save(any(NoteEntity.class))).thenReturn(note);
|
||||
|
||||
NoteResponse patchedNote = noteService.patchNote(note.getId(), notePatchRequest);
|
||||
|
||||
assertEquals("Updated Note", patchedNote.title());
|
||||
verify(noteRepository, times(1)).findById(note.getId());
|
||||
verify(noteRepository, times(1)).findByIdAndUser_id(note.getId(), user.getId());
|
||||
verify(noteRepository, times(1)).save(any(NoteEntity.class));
|
||||
}
|
||||
|
||||
@@ -130,7 +131,8 @@ class NoteServiceTest {
|
||||
void patchNote_notFound() {
|
||||
when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(user.getEmail()));
|
||||
when(authService.findByEmail(user.getEmail())).thenReturn(Optional.of(user));
|
||||
when(noteRepository.findById(note.getId())).thenReturn(Optional.empty());
|
||||
when(noteRepository.findByIdAndUser_id(note.getId(), user.getId()))
|
||||
.thenReturn(Optional.empty());
|
||||
Long noteId = note.getId();
|
||||
|
||||
assertThrows(
|
||||
@@ -141,11 +143,12 @@ class NoteServiceTest {
|
||||
void deleteNote() {
|
||||
when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(user.getEmail()));
|
||||
when(authService.findByEmail(user.getEmail())).thenReturn(Optional.of(user));
|
||||
when(noteRepository.findById(note.getId())).thenReturn(Optional.of(note));
|
||||
when(noteRepository.findByIdAndUser_id(note.getId(), user.getId()))
|
||||
.thenReturn(Optional.of(note));
|
||||
|
||||
noteService.deleteNote(note.getId());
|
||||
|
||||
verify(noteRepository, times(1)).findById(note.getId());
|
||||
verify(noteRepository, times(1)).findByIdAndUser_id(note.getId(), user.getId());
|
||||
verify(noteRepository, times(1)).delete(note);
|
||||
}
|
||||
|
||||
@@ -167,7 +170,8 @@ class NoteServiceTest {
|
||||
void shareNote() {
|
||||
when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(user.getEmail()));
|
||||
when(authService.findByEmail(user.getEmail())).thenReturn(Optional.of(user));
|
||||
when(noteRepository.findById(note.getId())).thenReturn(Optional.of(note));
|
||||
when(noteRepository.findByIdAndUser_id(note.getId(), user.getId()))
|
||||
.thenReturn(Optional.of(note));
|
||||
when(noteRepository.save(any(NoteEntity.class))).thenReturn(note);
|
||||
|
||||
NoteResponse response = noteService.shareNote(note.getId());
|
||||
@@ -180,7 +184,8 @@ class NoteServiceTest {
|
||||
void shareNote_notFound() {
|
||||
when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(user.getEmail()));
|
||||
when(authService.findByEmail(user.getEmail())).thenReturn(Optional.of(user));
|
||||
when(noteRepository.findById(note.getId())).thenReturn(Optional.empty());
|
||||
when(noteRepository.findByIdAndUser_id(note.getId(), user.getId()))
|
||||
.thenReturn(Optional.empty());
|
||||
Long noteId = note.getId();
|
||||
|
||||
assertThrows(NoteNotFoundException.class, () -> noteService.shareNote(noteId));
|
||||
@@ -192,7 +197,8 @@ class NoteServiceTest {
|
||||
note.setShareToken("some-token");
|
||||
when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(user.getEmail()));
|
||||
when(authService.findByEmail(user.getEmail())).thenReturn(Optional.of(user));
|
||||
when(noteRepository.findById(note.getId())).thenReturn(Optional.of(note));
|
||||
when(noteRepository.findByIdAndUser_id(note.getId(), user.getId()))
|
||||
.thenReturn(Optional.of(note));
|
||||
when(noteRepository.save(any(NoteEntity.class))).thenReturn(note);
|
||||
|
||||
NoteResponse response = noteService.unshareNote(note.getId());
|
||||
@@ -205,7 +211,8 @@ class NoteServiceTest {
|
||||
void unshareNote_notFound() {
|
||||
when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(user.getEmail()));
|
||||
when(authService.findByEmail(user.getEmail())).thenReturn(Optional.of(user));
|
||||
when(noteRepository.findById(note.getId())).thenReturn(Optional.empty());
|
||||
when(noteRepository.findByIdAndUser_id(note.getId(), user.getId()))
|
||||
.thenReturn(Optional.empty());
|
||||
Long noteId = note.getId();
|
||||
|
||||
assertThrows(NoteNotFoundException.class, () -> noteService.unshareNote(noteId));
|
||||
|
||||
@@ -45,7 +45,7 @@ class MailgunTemplateTest {
|
||||
MailgunTemplateEmailChanged emailChanged = new MailgunTemplateEmailChanged();
|
||||
|
||||
Assertions.assertNotNull(emailChanged.getName());
|
||||
Assertions.assertEquals("email changed", emailChanged.getName());
|
||||
Assertions.assertEquals("email_changed", emailChanged.getName());
|
||||
Assertions.assertNotNull(emailChanged.getVariables());
|
||||
Assertions.assertNotNull(emailChanged.getVariableValuesJson());
|
||||
Assertions.assertFalse(emailChanged.getVariableValuesJson().isBlank());
|
||||
|
||||
Reference in New Issue
Block a user