Contenido del curso

Completando el Inbox Service en Kotlin

Resumen

Construir un Inbox Service en Kotlin implica algo más que registrar usuarios y enviar correos: necesitas funciones que listen, marquen, muevan y eliminen emails con seguridad. Aquí cerramos la implementación del servicio paso a paso, validando dueños, manejando excepciones y probando el flujo completo en consola.

Cómo listar emails por carpeta y por usuario

La función listByFolder recibe el usuario activo y la carpeta que quieres consultar, y devuelve una lista filtrada de correos. Internamente usamos emailsRepository.findByOwner(user.id) y aplicamos un filtro lambda donde cada elemento (it) coincida con el folder solicitado.

Esto te da un patrón reutilizable: primero obtienes todo lo que pertenece al dueño, luego refinas por criterio. Sencillo, pero potente.

¿Qué hace findByOwner en un repositorio in memory? Devuelve todos los emails asociados al user.id que pasas como parámetro. Sobre esa colección puedes encadenar filtros como filter { it.folder == folder } para acotar el resultado.

Cómo marcar un correo como leído sin duplicar registros

La función markAsRead recibe el user y un emailId tipo UUID. El primer paso es buscar el correo con emailRepository.findById(emailId), y como ese resultado puede ser nulo, usamos el operador Elvis (?:) para lanzar una excepción personalizada.

Aquí aparece un detalle importante: necesitamos crear EmailNotFoundException, que recibe el emailId.toString() y lo incluye en el mensaje. La sumamos al conjunto de excepciones del proyecto junto con las que ya teníamos.

Después de encontrar el email, validamos que su owner coincida con el user.id activo. Si no coincide, también lanzamos EmailNotFoundException. Esta doble validación evita que un usuario manipule correos ajenos.

Una vez validado, cambiamos isRead = true y volvemos a guardar con emailRepository.save(mail). Y aquí viene lo interesante: como estamos trabajando con una lista en memoria, guardar un email con el mismo id no reemplaza al anterior, lo duplica.

Por qué necesitas removeIf antes de guardar

Las listas en Kotlin aceptan elementos repetidos y no protegen contra duplicados por id. Para resolverlo, dentro del save del repositorio agregamos un removeIf { it.id == email.id } antes de insertar el nuevo registro. Así garantizamos una única instancia por correo.

Cómo mover un correo entre carpetas en Kotlin

La función moveToFolder reutiliza la misma lógica de búsqueda y validación de dueño que markAsRead. Recibe user, emailId (UUID) y el folder destino.

Los pasos son:

  • Buscar el email con findById y aplicar Elvis para lanzar excepción si no existe.
  • Validar que el owner del correo sea el mismo user.id.
  • Asignar el nuevo valor a la propiedad folder del email.
  • Guardar con emailsRepository.save(mail).

No inventamos nada nuevo: replicamos un patrón que ya funciona y solo cambiamos qué propiedad modificamos antes de persistir.

Cómo eliminar un correo y probar el flujo completo

deleteMail sigue la misma estructura: recibe user y emailId, busca el correo, valida pertenencia y luego invoca emailRepository.delete(emailId). Tres pasos limpios.

Con todas las funciones listas, montamos una función main para simular la ejecución. Instanciamos las dependencias en este orden:

  • userRepository con la implementación InMemoryUserRepository.
  • emailRepository con InMemoryEmailRepository.
  • inboxService recibiendo ambos repositorios por constructor.

Luego imprimimos un encabezado tipo Inbox Simulator y construimos el flujo de registro pidiendo usuario y clave con readLine()?.trim().orEmpty(). Llamamos a inboxService.register(username, password) y pasamos al login.

Qué hacer cuando el registro no persiste el usuario

Al probar el flujo con el usuario juandroiddev y la clave 1234, el login devolvió user not found. El error tiene una causa concreta: dentro de register solo verificábamos si el usuario existía, pero nunca lo guardábamos en el repositorio.

La corrección es crear el objeto User(username = username, password = password) y persistirlo con el repositorio. Con esa línea agregada, el login devuelve correctamente el usuario con su UUID, username y password.

¿Por qué se recomienda hashing para contraseñas? Porque guardar la clave en texto plano expone al usuario si alguien accede al almacenamiento o si se imprime accidentalmente en un log. El hashing la transforma en un valor irreversible y seguro.

Cómo manejar excepciones de autenticación en el login

Dentro del bloque try, llamamos a inboxService.login(usuarioLogin, passwordLogin) y asignamos el resultado a currentUser. Si la contraseña no coincide o el usuario no existe, el servicio lanza InboxAuthenticationException.

En el catch, imprimimos el tipo de excepción y usamos return para terminar la ejecución, en lugar de devolver null. Recuerda que en Kotlin la última expresión de un try/catch, when o if es la que se asigna a la variable.

Probando con la clave incorrecta 56789, la consola muestra: InboxAuthenticationException: password or username incorrect. El primer segmento del Inbox Service funciona como esperábamos.

¿Qué función agregarías tú primero a este menú por consola? Cuéntanos en los comentarios.