ToDoApp - adding users, managing tasks, getting tasks The 2019 Stack Overflow Developer Survey...

What are the motivations for publishing new editions of an existing textbook, beyond new discoveries in a field?

Manuscript was "unsubmitted" because the manuscript was deposited in Arxiv Preprints

Dual Citizen. Exited the US on Italian passport recently

Why do UK politicians seemingly ignore opinion polls on Brexit?

If a poisoned arrow's piercing damage is reduced to 0, do you still get poisoned?

Pristine Bit Checking

Extreme, unacceptable situation and I can't attend work tomorrow morning

JSON.serialize: is it possible to suppress null values of a map?

Access elements in std::string where positon of string is greater than its size

Are USB sockets on wall outlets live all the time, even when the switch is off?

"What time...?" or "At what time...?" - what is more grammatically correct?

Lethal sonic weapons

What is the use of option -o in the useradd command?

How to deal with fear of taking dependencies

On the insanity of kings as an argument against monarchy

Is there a name of the flying bionic bird?

Should I use my personal or workplace e-mail when registering to external websites for work purpose?

Spanish for "widget"

Which Sci-Fi work first showed weapon of galactic-scale mass destruction?

How to change the limits of integration

Why is it "Tumoren" and not "Tumore"?

Why don't Unix/Linux systems traverse through directories until they find the required version of a linked library?

Deadlock Graph and Interpretation, solution to avoid

"To split hairs" vs "To be pedantic"



ToDoApp - adding users, managing tasks, getting tasks



The 2019 Stack Overflow Developer Survey Results Are InSolution for swapping ArrayList data members with anotherUse of static factory methods for vectors and matrices libraryController to list usersQuerying Facebook for details of a user's OAuth tokenImplementation of stackToDoApp with persistence (using JSON)Android app to let users complete tasks for cashTask-List: Adding, updating and deleting tasks with CSS animationsFollow-up 2: Copy File, remove spaces in specific linesEncrypted safebox





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty{ margin-bottom:0;
}







2












$begingroup$


I've implemented a console ToDo application. I would like to hear some opinions about it, what should I change etc.



About project:



It is intended to manage everyday tasks. Application contains:




  • adding users to database

  • adding tasks to database

  • getting list of user's tasks


It's my second project in which I use mockito, lombok, junit, slf4j.
I tried to use MVC pattern. I've written unit tests for my controller, too.



Let's start from package MODEL (there is nothing interesting, but I want to add every class).
User class (model)



package model;

import lombok.*;

@RequiredArgsConstructor
@Getter
@Setter
@EqualsAndHashCode

public class User {

private final String name;
private final String password;
private int ID;
}


Task class (model)



package model;

import lombok.*;

@Getter
@Setter
@AllArgsConstructor
@ToString
@NoArgsConstructor
@EqualsAndHashCode
public class Task {
private String taskName;
}


Here is my VIEW package:



ToDoView class(view)



package view;

import controller.ToDoEngine;
import model.Task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.SQLException;
import java.util.InputMismatchException;
import java.util.List;
import java.util.Scanner;


public class ToDoView {

private static final int LOGIN = 1;
private static final int REGISTER = 2;
private Logger logger;
private ToDoEngine toDoEngine;
private Scanner input;
private int option;

public ToDoView(ToDoEngine toDoEngine) {
this.toDoEngine = toDoEngine;
logger = LoggerFactory.getLogger(ToDoView.class);
input = new Scanner(System.in);
}

public void executeMainMenu() {
logger.info("--MAIN MENU--");
logger.info("1. Sign in");
logger.info("2. Sign up");
boolean isInvalid = true;
while (isInvalid) {
try {
option = input.nextInt();
if (option == 1 || option == 2) {
isInvalid = false;
switch (option) {
case LOGIN:
executeLoginCase();
break;
case REGISTER:
executeRegistrationCase();
break;
}
}
} catch (InputMismatchException e) {
logger.error("Try again");
input.next();
} catch (SQLException e) {
e.printStackTrace();
}
}
}

private void executeLoginCase() throws SQLException {
String username, password;
boolean isInvalid = true;
do {
logger.info("Put your username and password");
logger.info("User: ");
username = input.next();
logger.info("Password: ");
password = input.next();
if (toDoEngine.signIn(username, password)) {
logger.info("You've logged in");
executeUserCase();
isInvalid = false;
} else
logger.info("Bad login or password, try again");
}
while (isInvalid);
}

private void executeRegistrationCase() throws SQLException {
String username, password;
logger.info("Put your username and password");
logger.info("User: ");
username = input.next();
logger.info("Password: ");
password = input.next();
if (toDoEngine.createUser(username, password))
logger.info("User created successfully, now you can sign in!");
}

private void executeUserCase() throws SQLException {
do {
logger.info("1. Add task");
logger.info("2. Remove task");
logger.info("3. Get all tasks");
logger.info("4. Quit");
option = input.nextInt();
executeUserOptions(option);
}
while (option != 4);
}

private void executeUserOptions(int option) throws SQLException {
switch (option) {
case 1:
logger.info("Input task");
String taskName = input.next();
toDoEngine.addTask(taskName);
break;
case 2:
logger.info("Input task");
taskName = input.next();
toDoEngine.deleteTask(taskName);
break;
case 3:
List<Task> tasks = toDoEngine.getTasks();
for (Task task : tasks)
logger.info(String.valueOf(task));
break;
}
}
}


Controller package



ToDoEngine class (controller)



package controller;

import model.Task;
import model.User;
import repository.TaskActions;
import repository.UserActions;

import java.sql.SQLException;
import java.util.List;

public class ToDoEngine {

private TaskActions taskActions;
private UserActions userActions;
private User connectedUser;

public ToDoEngine(UserActions userStorage, TaskActions taskStorage) {
this.taskActions = taskStorage;
this.userActions = userStorage;
}

public boolean signIn(String username, String password) throws SQLException {
connectedUser = new User(username, password);
if (!userActions.signIn(connectedUser)) {
return false;
}
connectedUser.setID(retrieveConnectedUserID(connectedUser));
return true;
}

private int retrieveConnectedUserID(User connectedUser) throws SQLException {
return userActions.retrieveUserID(connectedUser);
}

public boolean createUser(String username, String password) throws SQLException {
return userActions.createUser(new User(username, password));
}

public void addTask(String taskName) throws SQLException {
taskActions.addTask(new Task(taskName), connectedUser);
}

public void deleteTask(String taskName) throws SQLException {
taskActions.deleteTask(new Task(taskName), connectedUser);
}

public List<Task> getTasks() throws SQLException {
return taskActions.getTasks(connectedUser);
}
}


Repository package.
I created 2 interfaces - UserActions and TaskActions. That's beacuse I wanted to have everything organized. (I'm not gonna paste interfaces. Just classes that implement them)



TaskRepository class (repository)



package repository;

import model.Task;
import model.User;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.TimeZone;

public class TaskRepository implements TaskActions {

private Connection connection;

public TaskRepository() throws SQLException {
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/todoapp?autoReconnect=true&serverTimezone=" + TimeZone.getDefault().getID(), "root", "password");
}

public boolean addTask(Task task, User user) throws SQLException {
if (task == null)
return false;
PreparedStatement preparedStatement = connection.prepareStatement("INSERT into tasks (task,idUser) values (?,?) ");
preparedStatement.setString(1, task.getTaskName());
preparedStatement.setInt(2, user.getID());
preparedStatement.executeUpdate();

return true;
}

public boolean deleteTask(Task task, User user) throws SQLException {
if (!doesTaskExists(task))
return false;
PreparedStatement preparedStatement = connection.prepareStatement("DELETE from tasks WHERE idUser=? AND task=?");
preparedStatement.setInt(1, user.getID());
preparedStatement.setString(2, task.getTaskName());
preparedStatement.executeUpdate();

return true;
}

public List<Task> getTasks(User connectedUser) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM tasks where idUser=?");
preparedStatement.setInt(1, connectedUser.getID());
ResultSet result = preparedStatement.executeQuery();
List<Task> tasks = new ArrayList<Task>();
while (result.next()) {
Task task = new Task();
task.setTaskName(result.getString("task"));

tasks.add(task);
}
return tasks;
}



public boolean doesTaskExists(Task task) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement("SELECT task FROM tasks WHERE task=?");
preparedStatement.setString(1, task.getTaskName());
ResultSet result = preparedStatement.executeQuery();
return result.next();
}
}


UserRepository (repository)



package repository;

import model.User;

import java.sql.*;
import java.util.TimeZone;

public class UserRepository implements UserActions {

private Connection connection;

public UserRepository() throws SQLException {
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/todoapp?autoReconnect=true&serverTimezone=" + TimeZone.getDefault().getID(), "root", "password");
}

public boolean createUser(User user) throws SQLException {
if (doesUserWithThatUsernameExist(user))
return false;
PreparedStatement preparedStatement = connection.prepareStatement("INSERT into user (username,password) values (?,?)");
preparedStatement.setString(1, user.getName());
preparedStatement.setString(2, user.getPassword());
preparedStatement.executeUpdate();

return true;
}

public boolean signIn(User user) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement("SELECT COUNT(username) FROM user WHERE username=? AND password=? LIMIT 0,1");
preparedStatement.setString(1, user.getName());
preparedStatement.setString(2, user.getPassword());
ResultSet result = preparedStatement.executeQuery();
int count = 0;
if (result.next())
count = result.getInt(1);

return count >= 1;
}

public int retrieveUserID(User user) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement("SELECT id FROM user WHERE username=? AND password=?");
preparedStatement.setString(1, user.getName());
preparedStatement.setString(2, user.getPassword());
ResultSet result = preparedStatement.executeQuery();
if (result.next())
user.setID(result.getInt("id"));

return user.getID();
}

private boolean doesUserWithThatUsernameExist(User user) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement("SELECT username FROM user WHERE username=?");
preparedStatement.setString(1, user.getName().toUpperCase());
ResultSet result = preparedStatement.executeQuery();

return result.next();
}


}


Main class:



import controller.ToDoEngine;
import repository.TaskRepository;
import repository.UserRepository;
import view.ToDoView;

import java.sql.SQLException;

public class Main {

public static void main(String[] args) throws SQLException {
UserRepository userRepository = new UserRepository();
TaskRepository taskRepository = new TaskRepository();
ToDoEngine toDoEngine = new ToDoEngine(userRepository,taskRepository);
ToDoView toDoView = new ToDoView(toDoEngine);
toDoView.executeMainMenu();
}
}


Here are my unit tests:



package controller;

import model.Task;
import model.User;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import repository.TaskActions;
import repository.UserActions;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class ToDoEngineTest {

@Mock
TaskActions taskActionsMock;
@Mock
UserActions userActionsMock;
private ToDoEngine toDoEngine;

@Before
public void setup() {
MockitoAnnotations.initMocks(this);
toDoEngine = new ToDoEngine(userActionsMock, taskActionsMock);
}

@Test
public void createUser() throws SQLException {
User user = new User("admin", "123");
toDoEngine.createUser("admin", "123");
verify(userActionsMock).createUser(user);
}

@Test
public void getTasks() throws SQLException {
List<Task> tasks = new ArrayList<>();
User user = new User("admin", "123");
Task task = new Task("wash");
tasks.add(task);
when(taskActionsMock.getTasks((User) any())).thenReturn(tasks);

List<Task> actual = toDoEngine.getTasks();
List<Task> expected = taskActionsMock.getTasks(user);

assertEquals(expected, actual);

}

@Test
public void signIn() {
// TODO: 2018-09-06
}

@Test
public void addTask() throws SQLException {
toDoEngine.signIn("admin", "123");
Task taskName = new Task("wash");
User user = new User("admin", "123");
verify(userActionsMock).signIn(user);
toDoEngine.addTask("wash");
verify(taskActionsMock).addTask(taskName, user);
}

@Test
public void deleteTask() throws SQLException {
toDoEngine.signIn("admin", "123");
Task taskName = new Task("wash");
User user = new User("admin", "123");
verify(userActionsMock).signIn(user);
toDoEngine.deleteTask("wash");
verify(taskActionsMock).deleteTask(taskName, user);
}
}









share|improve this question











$endgroup$




bumped to the homepage by Community 1 hour ago


This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.















  • $begingroup$
    Welcome to Code Review! Does it currently work as intended?
    $endgroup$
    – Mast
    Sep 7 '18 at 13:02






  • 1




    $begingroup$
    @Mast hey, yes. Everything works fine!
    $endgroup$
    – pipilam
    Sep 7 '18 at 13:16










  • $begingroup$
    Hello @pipilam, could you explain your choice of using a Logger instead of System.out or anything else to print messages to the user ?
    $endgroup$
    – gervais.b
    Sep 11 '18 at 6:40










  • $begingroup$
    @gervais.b I've read that using Loggeris more efficient and it is also good practice to use that instead of System.out (thinking about future projects)
    $endgroup$
    – pipilam
    Sep 11 '18 at 8:10










  • $begingroup$
    It is indeed a good practice to use a logger to log messages. But logging is for technical purpose (debugging, tracing) but not to display something to your users.
    $endgroup$
    – gervais.b
    Sep 11 '18 at 12:31


















2












$begingroup$


I've implemented a console ToDo application. I would like to hear some opinions about it, what should I change etc.



About project:



It is intended to manage everyday tasks. Application contains:




  • adding users to database

  • adding tasks to database

  • getting list of user's tasks


It's my second project in which I use mockito, lombok, junit, slf4j.
I tried to use MVC pattern. I've written unit tests for my controller, too.



Let's start from package MODEL (there is nothing interesting, but I want to add every class).
User class (model)



package model;

import lombok.*;

@RequiredArgsConstructor
@Getter
@Setter
@EqualsAndHashCode

public class User {

private final String name;
private final String password;
private int ID;
}


Task class (model)



package model;

import lombok.*;

@Getter
@Setter
@AllArgsConstructor
@ToString
@NoArgsConstructor
@EqualsAndHashCode
public class Task {
private String taskName;
}


Here is my VIEW package:



ToDoView class(view)



package view;

import controller.ToDoEngine;
import model.Task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.SQLException;
import java.util.InputMismatchException;
import java.util.List;
import java.util.Scanner;


public class ToDoView {

private static final int LOGIN = 1;
private static final int REGISTER = 2;
private Logger logger;
private ToDoEngine toDoEngine;
private Scanner input;
private int option;

public ToDoView(ToDoEngine toDoEngine) {
this.toDoEngine = toDoEngine;
logger = LoggerFactory.getLogger(ToDoView.class);
input = new Scanner(System.in);
}

public void executeMainMenu() {
logger.info("--MAIN MENU--");
logger.info("1. Sign in");
logger.info("2. Sign up");
boolean isInvalid = true;
while (isInvalid) {
try {
option = input.nextInt();
if (option == 1 || option == 2) {
isInvalid = false;
switch (option) {
case LOGIN:
executeLoginCase();
break;
case REGISTER:
executeRegistrationCase();
break;
}
}
} catch (InputMismatchException e) {
logger.error("Try again");
input.next();
} catch (SQLException e) {
e.printStackTrace();
}
}
}

private void executeLoginCase() throws SQLException {
String username, password;
boolean isInvalid = true;
do {
logger.info("Put your username and password");
logger.info("User: ");
username = input.next();
logger.info("Password: ");
password = input.next();
if (toDoEngine.signIn(username, password)) {
logger.info("You've logged in");
executeUserCase();
isInvalid = false;
} else
logger.info("Bad login or password, try again");
}
while (isInvalid);
}

private void executeRegistrationCase() throws SQLException {
String username, password;
logger.info("Put your username and password");
logger.info("User: ");
username = input.next();
logger.info("Password: ");
password = input.next();
if (toDoEngine.createUser(username, password))
logger.info("User created successfully, now you can sign in!");
}

private void executeUserCase() throws SQLException {
do {
logger.info("1. Add task");
logger.info("2. Remove task");
logger.info("3. Get all tasks");
logger.info("4. Quit");
option = input.nextInt();
executeUserOptions(option);
}
while (option != 4);
}

private void executeUserOptions(int option) throws SQLException {
switch (option) {
case 1:
logger.info("Input task");
String taskName = input.next();
toDoEngine.addTask(taskName);
break;
case 2:
logger.info("Input task");
taskName = input.next();
toDoEngine.deleteTask(taskName);
break;
case 3:
List<Task> tasks = toDoEngine.getTasks();
for (Task task : tasks)
logger.info(String.valueOf(task));
break;
}
}
}


Controller package



ToDoEngine class (controller)



package controller;

import model.Task;
import model.User;
import repository.TaskActions;
import repository.UserActions;

import java.sql.SQLException;
import java.util.List;

public class ToDoEngine {

private TaskActions taskActions;
private UserActions userActions;
private User connectedUser;

public ToDoEngine(UserActions userStorage, TaskActions taskStorage) {
this.taskActions = taskStorage;
this.userActions = userStorage;
}

public boolean signIn(String username, String password) throws SQLException {
connectedUser = new User(username, password);
if (!userActions.signIn(connectedUser)) {
return false;
}
connectedUser.setID(retrieveConnectedUserID(connectedUser));
return true;
}

private int retrieveConnectedUserID(User connectedUser) throws SQLException {
return userActions.retrieveUserID(connectedUser);
}

public boolean createUser(String username, String password) throws SQLException {
return userActions.createUser(new User(username, password));
}

public void addTask(String taskName) throws SQLException {
taskActions.addTask(new Task(taskName), connectedUser);
}

public void deleteTask(String taskName) throws SQLException {
taskActions.deleteTask(new Task(taskName), connectedUser);
}

public List<Task> getTasks() throws SQLException {
return taskActions.getTasks(connectedUser);
}
}


Repository package.
I created 2 interfaces - UserActions and TaskActions. That's beacuse I wanted to have everything organized. (I'm not gonna paste interfaces. Just classes that implement them)



TaskRepository class (repository)



package repository;

import model.Task;
import model.User;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.TimeZone;

public class TaskRepository implements TaskActions {

private Connection connection;

public TaskRepository() throws SQLException {
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/todoapp?autoReconnect=true&serverTimezone=" + TimeZone.getDefault().getID(), "root", "password");
}

public boolean addTask(Task task, User user) throws SQLException {
if (task == null)
return false;
PreparedStatement preparedStatement = connection.prepareStatement("INSERT into tasks (task,idUser) values (?,?) ");
preparedStatement.setString(1, task.getTaskName());
preparedStatement.setInt(2, user.getID());
preparedStatement.executeUpdate();

return true;
}

public boolean deleteTask(Task task, User user) throws SQLException {
if (!doesTaskExists(task))
return false;
PreparedStatement preparedStatement = connection.prepareStatement("DELETE from tasks WHERE idUser=? AND task=?");
preparedStatement.setInt(1, user.getID());
preparedStatement.setString(2, task.getTaskName());
preparedStatement.executeUpdate();

return true;
}

public List<Task> getTasks(User connectedUser) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM tasks where idUser=?");
preparedStatement.setInt(1, connectedUser.getID());
ResultSet result = preparedStatement.executeQuery();
List<Task> tasks = new ArrayList<Task>();
while (result.next()) {
Task task = new Task();
task.setTaskName(result.getString("task"));

tasks.add(task);
}
return tasks;
}



public boolean doesTaskExists(Task task) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement("SELECT task FROM tasks WHERE task=?");
preparedStatement.setString(1, task.getTaskName());
ResultSet result = preparedStatement.executeQuery();
return result.next();
}
}


UserRepository (repository)



package repository;

import model.User;

import java.sql.*;
import java.util.TimeZone;

public class UserRepository implements UserActions {

private Connection connection;

public UserRepository() throws SQLException {
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/todoapp?autoReconnect=true&serverTimezone=" + TimeZone.getDefault().getID(), "root", "password");
}

public boolean createUser(User user) throws SQLException {
if (doesUserWithThatUsernameExist(user))
return false;
PreparedStatement preparedStatement = connection.prepareStatement("INSERT into user (username,password) values (?,?)");
preparedStatement.setString(1, user.getName());
preparedStatement.setString(2, user.getPassword());
preparedStatement.executeUpdate();

return true;
}

public boolean signIn(User user) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement("SELECT COUNT(username) FROM user WHERE username=? AND password=? LIMIT 0,1");
preparedStatement.setString(1, user.getName());
preparedStatement.setString(2, user.getPassword());
ResultSet result = preparedStatement.executeQuery();
int count = 0;
if (result.next())
count = result.getInt(1);

return count >= 1;
}

public int retrieveUserID(User user) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement("SELECT id FROM user WHERE username=? AND password=?");
preparedStatement.setString(1, user.getName());
preparedStatement.setString(2, user.getPassword());
ResultSet result = preparedStatement.executeQuery();
if (result.next())
user.setID(result.getInt("id"));

return user.getID();
}

private boolean doesUserWithThatUsernameExist(User user) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement("SELECT username FROM user WHERE username=?");
preparedStatement.setString(1, user.getName().toUpperCase());
ResultSet result = preparedStatement.executeQuery();

return result.next();
}


}


Main class:



import controller.ToDoEngine;
import repository.TaskRepository;
import repository.UserRepository;
import view.ToDoView;

import java.sql.SQLException;

public class Main {

public static void main(String[] args) throws SQLException {
UserRepository userRepository = new UserRepository();
TaskRepository taskRepository = new TaskRepository();
ToDoEngine toDoEngine = new ToDoEngine(userRepository,taskRepository);
ToDoView toDoView = new ToDoView(toDoEngine);
toDoView.executeMainMenu();
}
}


Here are my unit tests:



package controller;

import model.Task;
import model.User;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import repository.TaskActions;
import repository.UserActions;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class ToDoEngineTest {

@Mock
TaskActions taskActionsMock;
@Mock
UserActions userActionsMock;
private ToDoEngine toDoEngine;

@Before
public void setup() {
MockitoAnnotations.initMocks(this);
toDoEngine = new ToDoEngine(userActionsMock, taskActionsMock);
}

@Test
public void createUser() throws SQLException {
User user = new User("admin", "123");
toDoEngine.createUser("admin", "123");
verify(userActionsMock).createUser(user);
}

@Test
public void getTasks() throws SQLException {
List<Task> tasks = new ArrayList<>();
User user = new User("admin", "123");
Task task = new Task("wash");
tasks.add(task);
when(taskActionsMock.getTasks((User) any())).thenReturn(tasks);

List<Task> actual = toDoEngine.getTasks();
List<Task> expected = taskActionsMock.getTasks(user);

assertEquals(expected, actual);

}

@Test
public void signIn() {
// TODO: 2018-09-06
}

@Test
public void addTask() throws SQLException {
toDoEngine.signIn("admin", "123");
Task taskName = new Task("wash");
User user = new User("admin", "123");
verify(userActionsMock).signIn(user);
toDoEngine.addTask("wash");
verify(taskActionsMock).addTask(taskName, user);
}

@Test
public void deleteTask() throws SQLException {
toDoEngine.signIn("admin", "123");
Task taskName = new Task("wash");
User user = new User("admin", "123");
verify(userActionsMock).signIn(user);
toDoEngine.deleteTask("wash");
verify(taskActionsMock).deleteTask(taskName, user);
}
}









share|improve this question











$endgroup$




bumped to the homepage by Community 1 hour ago


This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.















  • $begingroup$
    Welcome to Code Review! Does it currently work as intended?
    $endgroup$
    – Mast
    Sep 7 '18 at 13:02






  • 1




    $begingroup$
    @Mast hey, yes. Everything works fine!
    $endgroup$
    – pipilam
    Sep 7 '18 at 13:16










  • $begingroup$
    Hello @pipilam, could you explain your choice of using a Logger instead of System.out or anything else to print messages to the user ?
    $endgroup$
    – gervais.b
    Sep 11 '18 at 6:40










  • $begingroup$
    @gervais.b I've read that using Loggeris more efficient and it is also good practice to use that instead of System.out (thinking about future projects)
    $endgroup$
    – pipilam
    Sep 11 '18 at 8:10










  • $begingroup$
    It is indeed a good practice to use a logger to log messages. But logging is for technical purpose (debugging, tracing) but not to display something to your users.
    $endgroup$
    – gervais.b
    Sep 11 '18 at 12:31














2












2








2





$begingroup$


I've implemented a console ToDo application. I would like to hear some opinions about it, what should I change etc.



About project:



It is intended to manage everyday tasks. Application contains:




  • adding users to database

  • adding tasks to database

  • getting list of user's tasks


It's my second project in which I use mockito, lombok, junit, slf4j.
I tried to use MVC pattern. I've written unit tests for my controller, too.



Let's start from package MODEL (there is nothing interesting, but I want to add every class).
User class (model)



package model;

import lombok.*;

@RequiredArgsConstructor
@Getter
@Setter
@EqualsAndHashCode

public class User {

private final String name;
private final String password;
private int ID;
}


Task class (model)



package model;

import lombok.*;

@Getter
@Setter
@AllArgsConstructor
@ToString
@NoArgsConstructor
@EqualsAndHashCode
public class Task {
private String taskName;
}


Here is my VIEW package:



ToDoView class(view)



package view;

import controller.ToDoEngine;
import model.Task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.SQLException;
import java.util.InputMismatchException;
import java.util.List;
import java.util.Scanner;


public class ToDoView {

private static final int LOGIN = 1;
private static final int REGISTER = 2;
private Logger logger;
private ToDoEngine toDoEngine;
private Scanner input;
private int option;

public ToDoView(ToDoEngine toDoEngine) {
this.toDoEngine = toDoEngine;
logger = LoggerFactory.getLogger(ToDoView.class);
input = new Scanner(System.in);
}

public void executeMainMenu() {
logger.info("--MAIN MENU--");
logger.info("1. Sign in");
logger.info("2. Sign up");
boolean isInvalid = true;
while (isInvalid) {
try {
option = input.nextInt();
if (option == 1 || option == 2) {
isInvalid = false;
switch (option) {
case LOGIN:
executeLoginCase();
break;
case REGISTER:
executeRegistrationCase();
break;
}
}
} catch (InputMismatchException e) {
logger.error("Try again");
input.next();
} catch (SQLException e) {
e.printStackTrace();
}
}
}

private void executeLoginCase() throws SQLException {
String username, password;
boolean isInvalid = true;
do {
logger.info("Put your username and password");
logger.info("User: ");
username = input.next();
logger.info("Password: ");
password = input.next();
if (toDoEngine.signIn(username, password)) {
logger.info("You've logged in");
executeUserCase();
isInvalid = false;
} else
logger.info("Bad login or password, try again");
}
while (isInvalid);
}

private void executeRegistrationCase() throws SQLException {
String username, password;
logger.info("Put your username and password");
logger.info("User: ");
username = input.next();
logger.info("Password: ");
password = input.next();
if (toDoEngine.createUser(username, password))
logger.info("User created successfully, now you can sign in!");
}

private void executeUserCase() throws SQLException {
do {
logger.info("1. Add task");
logger.info("2. Remove task");
logger.info("3. Get all tasks");
logger.info("4. Quit");
option = input.nextInt();
executeUserOptions(option);
}
while (option != 4);
}

private void executeUserOptions(int option) throws SQLException {
switch (option) {
case 1:
logger.info("Input task");
String taskName = input.next();
toDoEngine.addTask(taskName);
break;
case 2:
logger.info("Input task");
taskName = input.next();
toDoEngine.deleteTask(taskName);
break;
case 3:
List<Task> tasks = toDoEngine.getTasks();
for (Task task : tasks)
logger.info(String.valueOf(task));
break;
}
}
}


Controller package



ToDoEngine class (controller)



package controller;

import model.Task;
import model.User;
import repository.TaskActions;
import repository.UserActions;

import java.sql.SQLException;
import java.util.List;

public class ToDoEngine {

private TaskActions taskActions;
private UserActions userActions;
private User connectedUser;

public ToDoEngine(UserActions userStorage, TaskActions taskStorage) {
this.taskActions = taskStorage;
this.userActions = userStorage;
}

public boolean signIn(String username, String password) throws SQLException {
connectedUser = new User(username, password);
if (!userActions.signIn(connectedUser)) {
return false;
}
connectedUser.setID(retrieveConnectedUserID(connectedUser));
return true;
}

private int retrieveConnectedUserID(User connectedUser) throws SQLException {
return userActions.retrieveUserID(connectedUser);
}

public boolean createUser(String username, String password) throws SQLException {
return userActions.createUser(new User(username, password));
}

public void addTask(String taskName) throws SQLException {
taskActions.addTask(new Task(taskName), connectedUser);
}

public void deleteTask(String taskName) throws SQLException {
taskActions.deleteTask(new Task(taskName), connectedUser);
}

public List<Task> getTasks() throws SQLException {
return taskActions.getTasks(connectedUser);
}
}


Repository package.
I created 2 interfaces - UserActions and TaskActions. That's beacuse I wanted to have everything organized. (I'm not gonna paste interfaces. Just classes that implement them)



TaskRepository class (repository)



package repository;

import model.Task;
import model.User;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.TimeZone;

public class TaskRepository implements TaskActions {

private Connection connection;

public TaskRepository() throws SQLException {
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/todoapp?autoReconnect=true&serverTimezone=" + TimeZone.getDefault().getID(), "root", "password");
}

public boolean addTask(Task task, User user) throws SQLException {
if (task == null)
return false;
PreparedStatement preparedStatement = connection.prepareStatement("INSERT into tasks (task,idUser) values (?,?) ");
preparedStatement.setString(1, task.getTaskName());
preparedStatement.setInt(2, user.getID());
preparedStatement.executeUpdate();

return true;
}

public boolean deleteTask(Task task, User user) throws SQLException {
if (!doesTaskExists(task))
return false;
PreparedStatement preparedStatement = connection.prepareStatement("DELETE from tasks WHERE idUser=? AND task=?");
preparedStatement.setInt(1, user.getID());
preparedStatement.setString(2, task.getTaskName());
preparedStatement.executeUpdate();

return true;
}

public List<Task> getTasks(User connectedUser) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM tasks where idUser=?");
preparedStatement.setInt(1, connectedUser.getID());
ResultSet result = preparedStatement.executeQuery();
List<Task> tasks = new ArrayList<Task>();
while (result.next()) {
Task task = new Task();
task.setTaskName(result.getString("task"));

tasks.add(task);
}
return tasks;
}



public boolean doesTaskExists(Task task) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement("SELECT task FROM tasks WHERE task=?");
preparedStatement.setString(1, task.getTaskName());
ResultSet result = preparedStatement.executeQuery();
return result.next();
}
}


UserRepository (repository)



package repository;

import model.User;

import java.sql.*;
import java.util.TimeZone;

public class UserRepository implements UserActions {

private Connection connection;

public UserRepository() throws SQLException {
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/todoapp?autoReconnect=true&serverTimezone=" + TimeZone.getDefault().getID(), "root", "password");
}

public boolean createUser(User user) throws SQLException {
if (doesUserWithThatUsernameExist(user))
return false;
PreparedStatement preparedStatement = connection.prepareStatement("INSERT into user (username,password) values (?,?)");
preparedStatement.setString(1, user.getName());
preparedStatement.setString(2, user.getPassword());
preparedStatement.executeUpdate();

return true;
}

public boolean signIn(User user) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement("SELECT COUNT(username) FROM user WHERE username=? AND password=? LIMIT 0,1");
preparedStatement.setString(1, user.getName());
preparedStatement.setString(2, user.getPassword());
ResultSet result = preparedStatement.executeQuery();
int count = 0;
if (result.next())
count = result.getInt(1);

return count >= 1;
}

public int retrieveUserID(User user) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement("SELECT id FROM user WHERE username=? AND password=?");
preparedStatement.setString(1, user.getName());
preparedStatement.setString(2, user.getPassword());
ResultSet result = preparedStatement.executeQuery();
if (result.next())
user.setID(result.getInt("id"));

return user.getID();
}

private boolean doesUserWithThatUsernameExist(User user) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement("SELECT username FROM user WHERE username=?");
preparedStatement.setString(1, user.getName().toUpperCase());
ResultSet result = preparedStatement.executeQuery();

return result.next();
}


}


Main class:



import controller.ToDoEngine;
import repository.TaskRepository;
import repository.UserRepository;
import view.ToDoView;

import java.sql.SQLException;

public class Main {

public static void main(String[] args) throws SQLException {
UserRepository userRepository = new UserRepository();
TaskRepository taskRepository = new TaskRepository();
ToDoEngine toDoEngine = new ToDoEngine(userRepository,taskRepository);
ToDoView toDoView = new ToDoView(toDoEngine);
toDoView.executeMainMenu();
}
}


Here are my unit tests:



package controller;

import model.Task;
import model.User;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import repository.TaskActions;
import repository.UserActions;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class ToDoEngineTest {

@Mock
TaskActions taskActionsMock;
@Mock
UserActions userActionsMock;
private ToDoEngine toDoEngine;

@Before
public void setup() {
MockitoAnnotations.initMocks(this);
toDoEngine = new ToDoEngine(userActionsMock, taskActionsMock);
}

@Test
public void createUser() throws SQLException {
User user = new User("admin", "123");
toDoEngine.createUser("admin", "123");
verify(userActionsMock).createUser(user);
}

@Test
public void getTasks() throws SQLException {
List<Task> tasks = new ArrayList<>();
User user = new User("admin", "123");
Task task = new Task("wash");
tasks.add(task);
when(taskActionsMock.getTasks((User) any())).thenReturn(tasks);

List<Task> actual = toDoEngine.getTasks();
List<Task> expected = taskActionsMock.getTasks(user);

assertEquals(expected, actual);

}

@Test
public void signIn() {
// TODO: 2018-09-06
}

@Test
public void addTask() throws SQLException {
toDoEngine.signIn("admin", "123");
Task taskName = new Task("wash");
User user = new User("admin", "123");
verify(userActionsMock).signIn(user);
toDoEngine.addTask("wash");
verify(taskActionsMock).addTask(taskName, user);
}

@Test
public void deleteTask() throws SQLException {
toDoEngine.signIn("admin", "123");
Task taskName = new Task("wash");
User user = new User("admin", "123");
verify(userActionsMock).signIn(user);
toDoEngine.deleteTask("wash");
verify(taskActionsMock).deleteTask(taskName, user);
}
}









share|improve this question











$endgroup$




I've implemented a console ToDo application. I would like to hear some opinions about it, what should I change etc.



About project:



It is intended to manage everyday tasks. Application contains:




  • adding users to database

  • adding tasks to database

  • getting list of user's tasks


It's my second project in which I use mockito, lombok, junit, slf4j.
I tried to use MVC pattern. I've written unit tests for my controller, too.



Let's start from package MODEL (there is nothing interesting, but I want to add every class).
User class (model)



package model;

import lombok.*;

@RequiredArgsConstructor
@Getter
@Setter
@EqualsAndHashCode

public class User {

private final String name;
private final String password;
private int ID;
}


Task class (model)



package model;

import lombok.*;

@Getter
@Setter
@AllArgsConstructor
@ToString
@NoArgsConstructor
@EqualsAndHashCode
public class Task {
private String taskName;
}


Here is my VIEW package:



ToDoView class(view)



package view;

import controller.ToDoEngine;
import model.Task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.SQLException;
import java.util.InputMismatchException;
import java.util.List;
import java.util.Scanner;


public class ToDoView {

private static final int LOGIN = 1;
private static final int REGISTER = 2;
private Logger logger;
private ToDoEngine toDoEngine;
private Scanner input;
private int option;

public ToDoView(ToDoEngine toDoEngine) {
this.toDoEngine = toDoEngine;
logger = LoggerFactory.getLogger(ToDoView.class);
input = new Scanner(System.in);
}

public void executeMainMenu() {
logger.info("--MAIN MENU--");
logger.info("1. Sign in");
logger.info("2. Sign up");
boolean isInvalid = true;
while (isInvalid) {
try {
option = input.nextInt();
if (option == 1 || option == 2) {
isInvalid = false;
switch (option) {
case LOGIN:
executeLoginCase();
break;
case REGISTER:
executeRegistrationCase();
break;
}
}
} catch (InputMismatchException e) {
logger.error("Try again");
input.next();
} catch (SQLException e) {
e.printStackTrace();
}
}
}

private void executeLoginCase() throws SQLException {
String username, password;
boolean isInvalid = true;
do {
logger.info("Put your username and password");
logger.info("User: ");
username = input.next();
logger.info("Password: ");
password = input.next();
if (toDoEngine.signIn(username, password)) {
logger.info("You've logged in");
executeUserCase();
isInvalid = false;
} else
logger.info("Bad login or password, try again");
}
while (isInvalid);
}

private void executeRegistrationCase() throws SQLException {
String username, password;
logger.info("Put your username and password");
logger.info("User: ");
username = input.next();
logger.info("Password: ");
password = input.next();
if (toDoEngine.createUser(username, password))
logger.info("User created successfully, now you can sign in!");
}

private void executeUserCase() throws SQLException {
do {
logger.info("1. Add task");
logger.info("2. Remove task");
logger.info("3. Get all tasks");
logger.info("4. Quit");
option = input.nextInt();
executeUserOptions(option);
}
while (option != 4);
}

private void executeUserOptions(int option) throws SQLException {
switch (option) {
case 1:
logger.info("Input task");
String taskName = input.next();
toDoEngine.addTask(taskName);
break;
case 2:
logger.info("Input task");
taskName = input.next();
toDoEngine.deleteTask(taskName);
break;
case 3:
List<Task> tasks = toDoEngine.getTasks();
for (Task task : tasks)
logger.info(String.valueOf(task));
break;
}
}
}


Controller package



ToDoEngine class (controller)



package controller;

import model.Task;
import model.User;
import repository.TaskActions;
import repository.UserActions;

import java.sql.SQLException;
import java.util.List;

public class ToDoEngine {

private TaskActions taskActions;
private UserActions userActions;
private User connectedUser;

public ToDoEngine(UserActions userStorage, TaskActions taskStorage) {
this.taskActions = taskStorage;
this.userActions = userStorage;
}

public boolean signIn(String username, String password) throws SQLException {
connectedUser = new User(username, password);
if (!userActions.signIn(connectedUser)) {
return false;
}
connectedUser.setID(retrieveConnectedUserID(connectedUser));
return true;
}

private int retrieveConnectedUserID(User connectedUser) throws SQLException {
return userActions.retrieveUserID(connectedUser);
}

public boolean createUser(String username, String password) throws SQLException {
return userActions.createUser(new User(username, password));
}

public void addTask(String taskName) throws SQLException {
taskActions.addTask(new Task(taskName), connectedUser);
}

public void deleteTask(String taskName) throws SQLException {
taskActions.deleteTask(new Task(taskName), connectedUser);
}

public List<Task> getTasks() throws SQLException {
return taskActions.getTasks(connectedUser);
}
}


Repository package.
I created 2 interfaces - UserActions and TaskActions. That's beacuse I wanted to have everything organized. (I'm not gonna paste interfaces. Just classes that implement them)



TaskRepository class (repository)



package repository;

import model.Task;
import model.User;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.TimeZone;

public class TaskRepository implements TaskActions {

private Connection connection;

public TaskRepository() throws SQLException {
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/todoapp?autoReconnect=true&serverTimezone=" + TimeZone.getDefault().getID(), "root", "password");
}

public boolean addTask(Task task, User user) throws SQLException {
if (task == null)
return false;
PreparedStatement preparedStatement = connection.prepareStatement("INSERT into tasks (task,idUser) values (?,?) ");
preparedStatement.setString(1, task.getTaskName());
preparedStatement.setInt(2, user.getID());
preparedStatement.executeUpdate();

return true;
}

public boolean deleteTask(Task task, User user) throws SQLException {
if (!doesTaskExists(task))
return false;
PreparedStatement preparedStatement = connection.prepareStatement("DELETE from tasks WHERE idUser=? AND task=?");
preparedStatement.setInt(1, user.getID());
preparedStatement.setString(2, task.getTaskName());
preparedStatement.executeUpdate();

return true;
}

public List<Task> getTasks(User connectedUser) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM tasks where idUser=?");
preparedStatement.setInt(1, connectedUser.getID());
ResultSet result = preparedStatement.executeQuery();
List<Task> tasks = new ArrayList<Task>();
while (result.next()) {
Task task = new Task();
task.setTaskName(result.getString("task"));

tasks.add(task);
}
return tasks;
}



public boolean doesTaskExists(Task task) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement("SELECT task FROM tasks WHERE task=?");
preparedStatement.setString(1, task.getTaskName());
ResultSet result = preparedStatement.executeQuery();
return result.next();
}
}


UserRepository (repository)



package repository;

import model.User;

import java.sql.*;
import java.util.TimeZone;

public class UserRepository implements UserActions {

private Connection connection;

public UserRepository() throws SQLException {
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/todoapp?autoReconnect=true&serverTimezone=" + TimeZone.getDefault().getID(), "root", "password");
}

public boolean createUser(User user) throws SQLException {
if (doesUserWithThatUsernameExist(user))
return false;
PreparedStatement preparedStatement = connection.prepareStatement("INSERT into user (username,password) values (?,?)");
preparedStatement.setString(1, user.getName());
preparedStatement.setString(2, user.getPassword());
preparedStatement.executeUpdate();

return true;
}

public boolean signIn(User user) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement("SELECT COUNT(username) FROM user WHERE username=? AND password=? LIMIT 0,1");
preparedStatement.setString(1, user.getName());
preparedStatement.setString(2, user.getPassword());
ResultSet result = preparedStatement.executeQuery();
int count = 0;
if (result.next())
count = result.getInt(1);

return count >= 1;
}

public int retrieveUserID(User user) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement("SELECT id FROM user WHERE username=? AND password=?");
preparedStatement.setString(1, user.getName());
preparedStatement.setString(2, user.getPassword());
ResultSet result = preparedStatement.executeQuery();
if (result.next())
user.setID(result.getInt("id"));

return user.getID();
}

private boolean doesUserWithThatUsernameExist(User user) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement("SELECT username FROM user WHERE username=?");
preparedStatement.setString(1, user.getName().toUpperCase());
ResultSet result = preparedStatement.executeQuery();

return result.next();
}


}


Main class:



import controller.ToDoEngine;
import repository.TaskRepository;
import repository.UserRepository;
import view.ToDoView;

import java.sql.SQLException;

public class Main {

public static void main(String[] args) throws SQLException {
UserRepository userRepository = new UserRepository();
TaskRepository taskRepository = new TaskRepository();
ToDoEngine toDoEngine = new ToDoEngine(userRepository,taskRepository);
ToDoView toDoView = new ToDoView(toDoEngine);
toDoView.executeMainMenu();
}
}


Here are my unit tests:



package controller;

import model.Task;
import model.User;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import repository.TaskActions;
import repository.UserActions;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class ToDoEngineTest {

@Mock
TaskActions taskActionsMock;
@Mock
UserActions userActionsMock;
private ToDoEngine toDoEngine;

@Before
public void setup() {
MockitoAnnotations.initMocks(this);
toDoEngine = new ToDoEngine(userActionsMock, taskActionsMock);
}

@Test
public void createUser() throws SQLException {
User user = new User("admin", "123");
toDoEngine.createUser("admin", "123");
verify(userActionsMock).createUser(user);
}

@Test
public void getTasks() throws SQLException {
List<Task> tasks = new ArrayList<>();
User user = new User("admin", "123");
Task task = new Task("wash");
tasks.add(task);
when(taskActionsMock.getTasks((User) any())).thenReturn(tasks);

List<Task> actual = toDoEngine.getTasks();
List<Task> expected = taskActionsMock.getTasks(user);

assertEquals(expected, actual);

}

@Test
public void signIn() {
// TODO: 2018-09-06
}

@Test
public void addTask() throws SQLException {
toDoEngine.signIn("admin", "123");
Task taskName = new Task("wash");
User user = new User("admin", "123");
verify(userActionsMock).signIn(user);
toDoEngine.addTask("wash");
verify(taskActionsMock).addTask(taskName, user);
}

@Test
public void deleteTask() throws SQLException {
toDoEngine.signIn("admin", "123");
Task taskName = new Task("wash");
User user = new User("admin", "123");
verify(userActionsMock).signIn(user);
toDoEngine.deleteTask("wash");
verify(taskActionsMock).deleteTask(taskName, user);
}
}






java sql mvc to-do-list lombok






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Sep 7 '18 at 14:52









200_success

131k17157422




131k17157422










asked Sep 7 '18 at 12:02









pipilampipilam

111




111





bumped to the homepage by Community 1 hour ago


This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.







bumped to the homepage by Community 1 hour ago


This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.














  • $begingroup$
    Welcome to Code Review! Does it currently work as intended?
    $endgroup$
    – Mast
    Sep 7 '18 at 13:02






  • 1




    $begingroup$
    @Mast hey, yes. Everything works fine!
    $endgroup$
    – pipilam
    Sep 7 '18 at 13:16










  • $begingroup$
    Hello @pipilam, could you explain your choice of using a Logger instead of System.out or anything else to print messages to the user ?
    $endgroup$
    – gervais.b
    Sep 11 '18 at 6:40










  • $begingroup$
    @gervais.b I've read that using Loggeris more efficient and it is also good practice to use that instead of System.out (thinking about future projects)
    $endgroup$
    – pipilam
    Sep 11 '18 at 8:10










  • $begingroup$
    It is indeed a good practice to use a logger to log messages. But logging is for technical purpose (debugging, tracing) but not to display something to your users.
    $endgroup$
    – gervais.b
    Sep 11 '18 at 12:31


















  • $begingroup$
    Welcome to Code Review! Does it currently work as intended?
    $endgroup$
    – Mast
    Sep 7 '18 at 13:02






  • 1




    $begingroup$
    @Mast hey, yes. Everything works fine!
    $endgroup$
    – pipilam
    Sep 7 '18 at 13:16










  • $begingroup$
    Hello @pipilam, could you explain your choice of using a Logger instead of System.out or anything else to print messages to the user ?
    $endgroup$
    – gervais.b
    Sep 11 '18 at 6:40










  • $begingroup$
    @gervais.b I've read that using Loggeris more efficient and it is also good practice to use that instead of System.out (thinking about future projects)
    $endgroup$
    – pipilam
    Sep 11 '18 at 8:10










  • $begingroup$
    It is indeed a good practice to use a logger to log messages. But logging is for technical purpose (debugging, tracing) but not to display something to your users.
    $endgroup$
    – gervais.b
    Sep 11 '18 at 12:31
















$begingroup$
Welcome to Code Review! Does it currently work as intended?
$endgroup$
– Mast
Sep 7 '18 at 13:02




$begingroup$
Welcome to Code Review! Does it currently work as intended?
$endgroup$
– Mast
Sep 7 '18 at 13:02




1




1




$begingroup$
@Mast hey, yes. Everything works fine!
$endgroup$
– pipilam
Sep 7 '18 at 13:16




$begingroup$
@Mast hey, yes. Everything works fine!
$endgroup$
– pipilam
Sep 7 '18 at 13:16












$begingroup$
Hello @pipilam, could you explain your choice of using a Logger instead of System.out or anything else to print messages to the user ?
$endgroup$
– gervais.b
Sep 11 '18 at 6:40




$begingroup$
Hello @pipilam, could you explain your choice of using a Logger instead of System.out or anything else to print messages to the user ?
$endgroup$
– gervais.b
Sep 11 '18 at 6:40












$begingroup$
@gervais.b I've read that using Loggeris more efficient and it is also good practice to use that instead of System.out (thinking about future projects)
$endgroup$
– pipilam
Sep 11 '18 at 8:10




$begingroup$
@gervais.b I've read that using Loggeris more efficient and it is also good practice to use that instead of System.out (thinking about future projects)
$endgroup$
– pipilam
Sep 11 '18 at 8:10












$begingroup$
It is indeed a good practice to use a logger to log messages. But logging is for technical purpose (debugging, tracing) but not to display something to your users.
$endgroup$
– gervais.b
Sep 11 '18 at 12:31




$begingroup$
It is indeed a good practice to use a logger to log messages. But logging is for technical purpose (debugging, tracing) but not to display something to your users.
$endgroup$
– gervais.b
Sep 11 '18 at 12:31










1 Answer
1






active

oldest

votes


















0












$begingroup$

Globally, it works. I have to admit that I am a bit skeptic on your usage of
a logger. Because a logger is dedicated to log, some can be asynchronous or
use a buffer or are just disabled. Why don't you use the System.out ?



You have some tests and is a good thing. Sadly, you are only testing the
ToDoEngine that most of the time delegate to your actions and your actions
are mocks. The most complex part of your application (the one with the biggest
number of conditions and branches) is the ToDoView. But this one is not
tested and this is difficult to do because this class receive inputs and write
to the console/logger.



A solution would be to create one abstraction for the "output" and another for
the "input". But this still looks strange to me.





After looking at a simple description of the MVC pattern, I have the feeling that
your view is also your controller:





  • Model: Represents problem data and operations to be performed on it.

  • View : Presents information from the Model to the user via the display.

  • Controller: Interprets inputs from the user and adjust the Model or View
    accordingly.


-- https://twitter.com/coreload/status/980212512137166848/photo/1




Let's try to move the Scanner into the controller. This one will still change
what is displayed to the user via the view:



class ToDoController {
ToDoView view;
Scanner in;

void start() {
do {
view.displayMenu();
action = in.nextInt();
switch (action) {
case REGISTER:
executeRegistrationCase();
case LOGIN:
executeLoginCase();
break;
default:
view.showError("Invalid option");
break;
}
} while ( action!=QUIT );
}

}


Because the ToDoView is now a dumb component you can live without testing it.
The complexity is now in your controller that Interprets inputs from the user
and adjust the .. View accordingly.



Another improvement that you can do is to create controllers for each use cases.
So that your ToDoController act more as a front controller and drive the
whole application without knowing all the details. If we go deeper is this idea,
you can end up by having one controller/action for each menu entry and each of
them will change a shared model that is the state of your application.



[1] Smart and Dumb components are mainly used in frontend development but the concept is not new.



[2] Front controller is one of the J2EE patterns, "request dispatcher" is also used for the same purpose. They are dedicated to Web applications but can be reused in other kind of systems. http://www.oracle.com/technetwork/java/frontcontroller-135648.html






share|improve this answer









$endgroup$













  • $begingroup$
    You mean something like this:? gist.github.com/must1/ab36f8fe3e7dcba078099b059015a379
    $endgroup$
    – pipilam
    Sep 13 '18 at 19:20










  • $begingroup$
    Hey. I created new controller. I updated gist. Take a look if you can. Thanks.
    $endgroup$
    – pipilam
    Sep 13 '18 at 19:58










  • $begingroup$
    Well my idea was more to launch another controller like a subsystem. I'm updating your project with what I have in mind on the little free time I have. Until it is available I dropped the important parts on this gist gist.github.com/gervaisb/b26ebfa8bdbd39e70b7d1e9d8226f90c (And will update the post one completed)
    $endgroup$
    – gervais.b
    Sep 14 '18 at 7:55










  • $begingroup$
    Well, it looks like another project, not mine. That was not what I was about. I wanted to hear what I should change, not how I should write it. I want to work on what I've written. Does the view class looks fine? Just printing messages. What about ToDoController?
    $endgroup$
    – pipilam
    Sep 14 '18 at 8:25












  • $begingroup$
    I've updated gist. It should be fine right now. Check it out.
    $endgroup$
    – pipilam
    Sep 14 '18 at 15:16












Your Answer





StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");

StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");

StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});

function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});


}
});














draft saved

draft discarded


















StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f203299%2ftodoapp-adding-users-managing-tasks-getting-tasks%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown

























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes









0












$begingroup$

Globally, it works. I have to admit that I am a bit skeptic on your usage of
a logger. Because a logger is dedicated to log, some can be asynchronous or
use a buffer or are just disabled. Why don't you use the System.out ?



You have some tests and is a good thing. Sadly, you are only testing the
ToDoEngine that most of the time delegate to your actions and your actions
are mocks. The most complex part of your application (the one with the biggest
number of conditions and branches) is the ToDoView. But this one is not
tested and this is difficult to do because this class receive inputs and write
to the console/logger.



A solution would be to create one abstraction for the "output" and another for
the "input". But this still looks strange to me.





After looking at a simple description of the MVC pattern, I have the feeling that
your view is also your controller:





  • Model: Represents problem data and operations to be performed on it.

  • View : Presents information from the Model to the user via the display.

  • Controller: Interprets inputs from the user and adjust the Model or View
    accordingly.


-- https://twitter.com/coreload/status/980212512137166848/photo/1




Let's try to move the Scanner into the controller. This one will still change
what is displayed to the user via the view:



class ToDoController {
ToDoView view;
Scanner in;

void start() {
do {
view.displayMenu();
action = in.nextInt();
switch (action) {
case REGISTER:
executeRegistrationCase();
case LOGIN:
executeLoginCase();
break;
default:
view.showError("Invalid option");
break;
}
} while ( action!=QUIT );
}

}


Because the ToDoView is now a dumb component you can live without testing it.
The complexity is now in your controller that Interprets inputs from the user
and adjust the .. View accordingly.



Another improvement that you can do is to create controllers for each use cases.
So that your ToDoController act more as a front controller and drive the
whole application without knowing all the details. If we go deeper is this idea,
you can end up by having one controller/action for each menu entry and each of
them will change a shared model that is the state of your application.



[1] Smart and Dumb components are mainly used in frontend development but the concept is not new.



[2] Front controller is one of the J2EE patterns, "request dispatcher" is also used for the same purpose. They are dedicated to Web applications but can be reused in other kind of systems. http://www.oracle.com/technetwork/java/frontcontroller-135648.html






share|improve this answer









$endgroup$













  • $begingroup$
    You mean something like this:? gist.github.com/must1/ab36f8fe3e7dcba078099b059015a379
    $endgroup$
    – pipilam
    Sep 13 '18 at 19:20










  • $begingroup$
    Hey. I created new controller. I updated gist. Take a look if you can. Thanks.
    $endgroup$
    – pipilam
    Sep 13 '18 at 19:58










  • $begingroup$
    Well my idea was more to launch another controller like a subsystem. I'm updating your project with what I have in mind on the little free time I have. Until it is available I dropped the important parts on this gist gist.github.com/gervaisb/b26ebfa8bdbd39e70b7d1e9d8226f90c (And will update the post one completed)
    $endgroup$
    – gervais.b
    Sep 14 '18 at 7:55










  • $begingroup$
    Well, it looks like another project, not mine. That was not what I was about. I wanted to hear what I should change, not how I should write it. I want to work on what I've written. Does the view class looks fine? Just printing messages. What about ToDoController?
    $endgroup$
    – pipilam
    Sep 14 '18 at 8:25












  • $begingroup$
    I've updated gist. It should be fine right now. Check it out.
    $endgroup$
    – pipilam
    Sep 14 '18 at 15:16
















0












$begingroup$

Globally, it works. I have to admit that I am a bit skeptic on your usage of
a logger. Because a logger is dedicated to log, some can be asynchronous or
use a buffer or are just disabled. Why don't you use the System.out ?



You have some tests and is a good thing. Sadly, you are only testing the
ToDoEngine that most of the time delegate to your actions and your actions
are mocks. The most complex part of your application (the one with the biggest
number of conditions and branches) is the ToDoView. But this one is not
tested and this is difficult to do because this class receive inputs and write
to the console/logger.



A solution would be to create one abstraction for the "output" and another for
the "input". But this still looks strange to me.





After looking at a simple description of the MVC pattern, I have the feeling that
your view is also your controller:





  • Model: Represents problem data and operations to be performed on it.

  • View : Presents information from the Model to the user via the display.

  • Controller: Interprets inputs from the user and adjust the Model or View
    accordingly.


-- https://twitter.com/coreload/status/980212512137166848/photo/1




Let's try to move the Scanner into the controller. This one will still change
what is displayed to the user via the view:



class ToDoController {
ToDoView view;
Scanner in;

void start() {
do {
view.displayMenu();
action = in.nextInt();
switch (action) {
case REGISTER:
executeRegistrationCase();
case LOGIN:
executeLoginCase();
break;
default:
view.showError("Invalid option");
break;
}
} while ( action!=QUIT );
}

}


Because the ToDoView is now a dumb component you can live without testing it.
The complexity is now in your controller that Interprets inputs from the user
and adjust the .. View accordingly.



Another improvement that you can do is to create controllers for each use cases.
So that your ToDoController act more as a front controller and drive the
whole application without knowing all the details. If we go deeper is this idea,
you can end up by having one controller/action for each menu entry and each of
them will change a shared model that is the state of your application.



[1] Smart and Dumb components are mainly used in frontend development but the concept is not new.



[2] Front controller is one of the J2EE patterns, "request dispatcher" is also used for the same purpose. They are dedicated to Web applications but can be reused in other kind of systems. http://www.oracle.com/technetwork/java/frontcontroller-135648.html






share|improve this answer









$endgroup$













  • $begingroup$
    You mean something like this:? gist.github.com/must1/ab36f8fe3e7dcba078099b059015a379
    $endgroup$
    – pipilam
    Sep 13 '18 at 19:20










  • $begingroup$
    Hey. I created new controller. I updated gist. Take a look if you can. Thanks.
    $endgroup$
    – pipilam
    Sep 13 '18 at 19:58










  • $begingroup$
    Well my idea was more to launch another controller like a subsystem. I'm updating your project with what I have in mind on the little free time I have. Until it is available I dropped the important parts on this gist gist.github.com/gervaisb/b26ebfa8bdbd39e70b7d1e9d8226f90c (And will update the post one completed)
    $endgroup$
    – gervais.b
    Sep 14 '18 at 7:55










  • $begingroup$
    Well, it looks like another project, not mine. That was not what I was about. I wanted to hear what I should change, not how I should write it. I want to work on what I've written. Does the view class looks fine? Just printing messages. What about ToDoController?
    $endgroup$
    – pipilam
    Sep 14 '18 at 8:25












  • $begingroup$
    I've updated gist. It should be fine right now. Check it out.
    $endgroup$
    – pipilam
    Sep 14 '18 at 15:16














0












0








0





$begingroup$

Globally, it works. I have to admit that I am a bit skeptic on your usage of
a logger. Because a logger is dedicated to log, some can be asynchronous or
use a buffer or are just disabled. Why don't you use the System.out ?



You have some tests and is a good thing. Sadly, you are only testing the
ToDoEngine that most of the time delegate to your actions and your actions
are mocks. The most complex part of your application (the one with the biggest
number of conditions and branches) is the ToDoView. But this one is not
tested and this is difficult to do because this class receive inputs and write
to the console/logger.



A solution would be to create one abstraction for the "output" and another for
the "input". But this still looks strange to me.





After looking at a simple description of the MVC pattern, I have the feeling that
your view is also your controller:





  • Model: Represents problem data and operations to be performed on it.

  • View : Presents information from the Model to the user via the display.

  • Controller: Interprets inputs from the user and adjust the Model or View
    accordingly.


-- https://twitter.com/coreload/status/980212512137166848/photo/1




Let's try to move the Scanner into the controller. This one will still change
what is displayed to the user via the view:



class ToDoController {
ToDoView view;
Scanner in;

void start() {
do {
view.displayMenu();
action = in.nextInt();
switch (action) {
case REGISTER:
executeRegistrationCase();
case LOGIN:
executeLoginCase();
break;
default:
view.showError("Invalid option");
break;
}
} while ( action!=QUIT );
}

}


Because the ToDoView is now a dumb component you can live without testing it.
The complexity is now in your controller that Interprets inputs from the user
and adjust the .. View accordingly.



Another improvement that you can do is to create controllers for each use cases.
So that your ToDoController act more as a front controller and drive the
whole application without knowing all the details. If we go deeper is this idea,
you can end up by having one controller/action for each menu entry and each of
them will change a shared model that is the state of your application.



[1] Smart and Dumb components are mainly used in frontend development but the concept is not new.



[2] Front controller is one of the J2EE patterns, "request dispatcher" is also used for the same purpose. They are dedicated to Web applications but can be reused in other kind of systems. http://www.oracle.com/technetwork/java/frontcontroller-135648.html






share|improve this answer









$endgroup$



Globally, it works. I have to admit that I am a bit skeptic on your usage of
a logger. Because a logger is dedicated to log, some can be asynchronous or
use a buffer or are just disabled. Why don't you use the System.out ?



You have some tests and is a good thing. Sadly, you are only testing the
ToDoEngine that most of the time delegate to your actions and your actions
are mocks. The most complex part of your application (the one with the biggest
number of conditions and branches) is the ToDoView. But this one is not
tested and this is difficult to do because this class receive inputs and write
to the console/logger.



A solution would be to create one abstraction for the "output" and another for
the "input". But this still looks strange to me.





After looking at a simple description of the MVC pattern, I have the feeling that
your view is also your controller:





  • Model: Represents problem data and operations to be performed on it.

  • View : Presents information from the Model to the user via the display.

  • Controller: Interprets inputs from the user and adjust the Model or View
    accordingly.


-- https://twitter.com/coreload/status/980212512137166848/photo/1




Let's try to move the Scanner into the controller. This one will still change
what is displayed to the user via the view:



class ToDoController {
ToDoView view;
Scanner in;

void start() {
do {
view.displayMenu();
action = in.nextInt();
switch (action) {
case REGISTER:
executeRegistrationCase();
case LOGIN:
executeLoginCase();
break;
default:
view.showError("Invalid option");
break;
}
} while ( action!=QUIT );
}

}


Because the ToDoView is now a dumb component you can live without testing it.
The complexity is now in your controller that Interprets inputs from the user
and adjust the .. View accordingly.



Another improvement that you can do is to create controllers for each use cases.
So that your ToDoController act more as a front controller and drive the
whole application without knowing all the details. If we go deeper is this idea,
you can end up by having one controller/action for each menu entry and each of
them will change a shared model that is the state of your application.



[1] Smart and Dumb components are mainly used in frontend development but the concept is not new.



[2] Front controller is one of the J2EE patterns, "request dispatcher" is also used for the same purpose. They are dedicated to Web applications but can be reused in other kind of systems. http://www.oracle.com/technetwork/java/frontcontroller-135648.html







share|improve this answer












share|improve this answer



share|improve this answer










answered Sep 11 '18 at 20:46









gervais.bgervais.b

1,190510




1,190510












  • $begingroup$
    You mean something like this:? gist.github.com/must1/ab36f8fe3e7dcba078099b059015a379
    $endgroup$
    – pipilam
    Sep 13 '18 at 19:20










  • $begingroup$
    Hey. I created new controller. I updated gist. Take a look if you can. Thanks.
    $endgroup$
    – pipilam
    Sep 13 '18 at 19:58










  • $begingroup$
    Well my idea was more to launch another controller like a subsystem. I'm updating your project with what I have in mind on the little free time I have. Until it is available I dropped the important parts on this gist gist.github.com/gervaisb/b26ebfa8bdbd39e70b7d1e9d8226f90c (And will update the post one completed)
    $endgroup$
    – gervais.b
    Sep 14 '18 at 7:55










  • $begingroup$
    Well, it looks like another project, not mine. That was not what I was about. I wanted to hear what I should change, not how I should write it. I want to work on what I've written. Does the view class looks fine? Just printing messages. What about ToDoController?
    $endgroup$
    – pipilam
    Sep 14 '18 at 8:25












  • $begingroup$
    I've updated gist. It should be fine right now. Check it out.
    $endgroup$
    – pipilam
    Sep 14 '18 at 15:16


















  • $begingroup$
    You mean something like this:? gist.github.com/must1/ab36f8fe3e7dcba078099b059015a379
    $endgroup$
    – pipilam
    Sep 13 '18 at 19:20










  • $begingroup$
    Hey. I created new controller. I updated gist. Take a look if you can. Thanks.
    $endgroup$
    – pipilam
    Sep 13 '18 at 19:58










  • $begingroup$
    Well my idea was more to launch another controller like a subsystem. I'm updating your project with what I have in mind on the little free time I have. Until it is available I dropped the important parts on this gist gist.github.com/gervaisb/b26ebfa8bdbd39e70b7d1e9d8226f90c (And will update the post one completed)
    $endgroup$
    – gervais.b
    Sep 14 '18 at 7:55










  • $begingroup$
    Well, it looks like another project, not mine. That was not what I was about. I wanted to hear what I should change, not how I should write it. I want to work on what I've written. Does the view class looks fine? Just printing messages. What about ToDoController?
    $endgroup$
    – pipilam
    Sep 14 '18 at 8:25












  • $begingroup$
    I've updated gist. It should be fine right now. Check it out.
    $endgroup$
    – pipilam
    Sep 14 '18 at 15:16
















$begingroup$
You mean something like this:? gist.github.com/must1/ab36f8fe3e7dcba078099b059015a379
$endgroup$
– pipilam
Sep 13 '18 at 19:20




$begingroup$
You mean something like this:? gist.github.com/must1/ab36f8fe3e7dcba078099b059015a379
$endgroup$
– pipilam
Sep 13 '18 at 19:20












$begingroup$
Hey. I created new controller. I updated gist. Take a look if you can. Thanks.
$endgroup$
– pipilam
Sep 13 '18 at 19:58




$begingroup$
Hey. I created new controller. I updated gist. Take a look if you can. Thanks.
$endgroup$
– pipilam
Sep 13 '18 at 19:58












$begingroup$
Well my idea was more to launch another controller like a subsystem. I'm updating your project with what I have in mind on the little free time I have. Until it is available I dropped the important parts on this gist gist.github.com/gervaisb/b26ebfa8bdbd39e70b7d1e9d8226f90c (And will update the post one completed)
$endgroup$
– gervais.b
Sep 14 '18 at 7:55




$begingroup$
Well my idea was more to launch another controller like a subsystem. I'm updating your project with what I have in mind on the little free time I have. Until it is available I dropped the important parts on this gist gist.github.com/gervaisb/b26ebfa8bdbd39e70b7d1e9d8226f90c (And will update the post one completed)
$endgroup$
– gervais.b
Sep 14 '18 at 7:55












$begingroup$
Well, it looks like another project, not mine. That was not what I was about. I wanted to hear what I should change, not how I should write it. I want to work on what I've written. Does the view class looks fine? Just printing messages. What about ToDoController?
$endgroup$
– pipilam
Sep 14 '18 at 8:25






$begingroup$
Well, it looks like another project, not mine. That was not what I was about. I wanted to hear what I should change, not how I should write it. I want to work on what I've written. Does the view class looks fine? Just printing messages. What about ToDoController?
$endgroup$
– pipilam
Sep 14 '18 at 8:25














$begingroup$
I've updated gist. It should be fine right now. Check it out.
$endgroup$
– pipilam
Sep 14 '18 at 15:16




$begingroup$
I've updated gist. It should be fine right now. Check it out.
$endgroup$
– pipilam
Sep 14 '18 at 15:16


















draft saved

draft discarded




















































Thanks for contributing an answer to Code Review Stack Exchange!


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


Use MathJax to format equations. MathJax reference.


To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f203299%2ftodoapp-adding-users-managing-tasks-getting-tasks%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

is 'sed' thread safeWhat should someone know about using Python scripts in the shell?Nexenta bash script uses...

How do i solve the “ No module named 'mlxtend' ” issue on Jupyter?

Pilgersdorf Inhaltsverzeichnis Geografie | Geschichte | Bevölkerungsentwicklung | Politik | Kultur...