diff --git a/course-info.yaml b/course-info.yaml index 40e1a40..c3de9c7 100644 --- a/course-info.yaml +++ b/course-info.yaml @@ -24,4 +24,10 @@ additional_files: - name: docs/HOW_TO_SUBMIT.md - name: docs/PROJECT_IDEAS.md - name: docs/guides/numeric-literals-simplified.md + - name: participants/olena/project-1/src/main/java/com/wcc/platform/model/Member.java + - name: participants/olena/project-1/src/main/java/com/wcc/platform/model/MemberRepository.java + - name: src/main/java/com/wcc/bootcamp/java/Main.java + - name: participants/olena/project-1/src/main/java/com/wcc/platform/model/CsvMemberRepository.java + - name: participants/olena/project-1/src/test/java/com/wcc/platform/EmailValidatorTest.java + - name: participants/olena/project-1/CLAUDE.md yaml_version: 5 diff --git a/participants/olena/project-1/.gitignore b/participants/olena/project-1/.gitignore new file mode 100644 index 0000000..36170e1 --- /dev/null +++ b/participants/olena/project-1/.gitignore @@ -0,0 +1,17 @@ +# Gradle +.gradle/ +build/ +gradle/caches/ +gradle/daemon/ +gradle/native/ +gradle/wrapper/dists/ + +# IntelliJ (beyond what .idea/.gitignore covers) +*.iml +out/ + +# macOS +.DS_Store + +# Claude Code +CLAUDE.md diff --git a/participants/olena/project-1/README.md b/participants/olena/project-1/README.md index dca1cfb..eb24175 100644 --- a/participants/olena/project-1/README.md +++ b/participants/olena/project-1/README.md @@ -1,87 +1,92 @@ -# Member Directory Command Line Tool +# Member Directory — CLI Tool -A simple command-line application built in Java to manage WCC member profiles. +A command-line application built in Java to manage WCC (Women Coding Community) member profiles. -This project allows users to add, view, update, delete, and search member profiles directly from the terminal. It focuses on practising core Java concepts such as Collections, File I/O, Object-Oriented Programming, and basic CRUD operations. - -The goal of this tool is to provide a clean and structured way to manage community member information such as name, email, location, skills, and interests. +Members are stored in a CSV file and loaded automatically on startup. The app supports adding, viewing, updating, deleting, searching, and sorting members from an interactive terminal menu. ## Features -### Main features: - -Add new member profiles - -View all member profiles - -Update existing member information - -Delete member profiles - -**Search members by:** - -- Skills - -- Location - -Store member data in a text/CSV file - -Load member data from a file on application start - -Display formatted member lists in the console - -Email format validation - -Export members to CSV - -Import members from CSV +- Add new member profiles (name, email, location, skills) +- View all members in a formatted table +- Update existing member information (looked up by email) +- Delete members by email +- Search members by location +- Search members by skill +- Sort members by name (A–Z) +- Sort members by join date +- Email format validation with re-prompt on invalid input +- Persist data to `members.csv` on every change +- Load data from `members.csv` on startup -**Sort members by:** +## Project Structure -- Name -- Join date +``` +src/ +├── main/java/com/wcc/platform/ +│ ├── Main.java # Entry point +│ ├── cli/ +│ │ └── MemberCli.java # Interactive menu loop +│ ├── model/ +│ │ ├── Member.java # Member data model +│ │ ├── MemberRepository.java # Repository interface +│ │ └── CsvMemberRepository.java # CSV-backed implementation +│ └── validation/ +│ └── EmailValidator.java # Email regex validation +└── test/java/com/wcc/platform/ + └── EmailValidatorTest.java # Unit tests +``` ## Technologies -``` - Java 17 -Command Line Interface (CLI) - ``` + +- Java 17 +- Gradle 9 (Groovy DSL) +- JUnit Jupiter 5.10.2 ## How to Run -Ensure Java 17 is selected as the SDK +Make sure Java 17 is set as the SDK. -Build the project -``` +**Build:** +```bash ./gradlew build ``` -Run the project -``` +**Run:** +```bash ./gradlew run ``` +**Test:** +```bash +./gradlew test +``` -### Using the Application - -Once started, the application will display a menu with options such as: - -Add Member - -View Members - -Update Member +## Using the Application -Delete Member +Once started, the application displays an interactive menu: -Search Member +``` +===== Member Directory ===== +1. Add Member +2. View Members +3. Update Member +4. Delete Member +5. Search by Location +6. Search by Skill +7. Sort by Name +8. Sort by Join Date +0. Exit +``` -Export to CSV +Follow the on-screen prompts. Member data is automatically saved to `members.csv` after any change. -Import from CSV +## Data Storage -Sort Members +Members are saved in `members.csv` in the working directory with the format: -Exit +``` +name,email,location,skills,joinDate +Jane Doe,jane@example.com,London,Java|Python,2024-01-15 +``` -Follow the on-screen prompts to perform actions. \ No newline at end of file +Skills are stored as pipe-separated values within the CSV field. diff --git a/participants/olena/project-1/build.gradle b/participants/olena/project-1/build.gradle index 6948bdd..9c587e9 100644 --- a/participants/olena/project-1/build.gradle +++ b/participants/olena/project-1/build.gradle @@ -12,4 +12,26 @@ repositories { application { mainClass = 'com.wcc.platform.Main' } +tasks.named('run') { + standardInput = System.in +} +dependencies { + + testImplementation platform('org.junit:junit-bom:5.10.2') + testImplementation 'org.junit.jupiter:junit-jupiter' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + +} + +test { + useJUnitPlatform() + + outputs.upToDateWhen { false } + + testLogging { + events "passed", "failed", "skipped" + showStandardStreams = true + showExceptions = true + } +} diff --git a/participants/olena/project-1/members.csv b/participants/olena/project-1/members.csv new file mode 100644 index 0000000..b1012ab --- /dev/null +++ b/participants/olena/project-1/members.csv @@ -0,0 +1,5 @@ +name,email,location,skills,joinDate +Tom,tom@gmail.com,UK,reading,2026-03-12 +Ned,ned@gmail.com,UK,writing,2026-03-12 +Sam,sam@gmail.com,UK,puzzles,2026-03-12 +Tat,tat@gmail.com,France,n/a,2026-03-12 diff --git a/participants/olena/project-1/src/main/java/com/wcc/platform/Main.java b/participants/olena/project-1/src/main/java/com/wcc/platform/Main.java index e9f54e3..cc3ca08 100644 --- a/participants/olena/project-1/src/main/java/com/wcc/platform/Main.java +++ b/participants/olena/project-1/src/main/java/com/wcc/platform/Main.java @@ -1,9 +1,61 @@ package com.wcc.platform; -public class Main { - public static void main(String[] args) { - System.out.println("WCC Member Directory started..."); +import com.wcc.platform.cli.MemberCli; +import com.wcc.platform.model.Member; +import com.wcc.platform.model.MemberRepository; +import com.wcc.platform.model.CsvMemberRepository; +import com.wcc.platform.validation.EmailValidator; + +import java.io.IOException; +import java.time.LocalDate; +import java.util.List; +import java.util.Scanner; + + +public class Main { + private static final Scanner scanner = new Scanner(System.in); + + + + public static void main(String[] args) throws IOException { + MemberRepository repository = new CsvMemberRepository(); + repository.loadFromCsv("members.csv"); + MemberCli cli = new MemberCli(repository); + cli.start(); } +// repository.loadFromCsv("members.csv"); +// +// +// System.out.println("Enter name: "); +// String name = scanner.nextLine(); +// +// String email = EmailValidator.promptValidEmail(scanner); +// +// System.out.println("Enter location: "); +// String location = scanner.nextLine(); +// +// Member member = new Member( +// name, +// email, +// location, +// List.of(), +// LocalDate.now() +// ); +// repository.add(member); +// repository.saveToCsv("members.csv"); +// System.out.println("Member added."); +// viewMembers(); +// } +// public static void viewMembers() { +// +// System.out.printf("%-15s %-25s %-15s%n", "Name", "Email", "Location"); +// System.out.println("----------------------------------------------------------"); +// repository.findAll().forEach(member -> System.out.printf("%-15s %-25s %-15s%n", +// member.getName(), +// member.getEmail(), +// member.getLocation() +// )); +// } } diff --git a/participants/olena/project-1/src/main/java/com/wcc/platform/cli/MemberCli.java b/participants/olena/project-1/src/main/java/com/wcc/platform/cli/MemberCli.java new file mode 100644 index 0000000..429a3ec --- /dev/null +++ b/participants/olena/project-1/src/main/java/com/wcc/platform/cli/MemberCli.java @@ -0,0 +1,221 @@ +package com.wcc.platform.cli; + +import com.wcc.platform.model.Member; +import com.wcc.platform.model.MemberRepository; +import com.wcc.platform.validation.EmailValidator; + +import java.io.IOException; +import java.time.LocalDate; +import java.util.List; +import java.util.Scanner; + +public class MemberCli { + private final MemberRepository repository; + private final Scanner scanner; + + public MemberCli(MemberRepository repository) { + this.repository = repository; + this.scanner = new Scanner(System.in); + } + + public void start() throws IOException { + boolean running = true; + while(running) { + printMenu(); + String choice = scanner.nextLine(); + switch (choice) { + case "1": + addMember(); + break; + case "2": + viewMembers(); + break; + case "3": + updateMember(); + break; + case "4": + deleteMember(); + break; + + + case "5": + searchByLocation(); + break; + + case "6": + searchBySkill(); + break; + + case "7": + sortByName(); + break; + + case "8": + sortByJoinDate(); + break; + + + case "0": + running = false; + break; + + default: + System.out.println("Invalid option"); + } + } + } + + private void printMenu() { + System.out.println(); + System.out.println("===== Member Directory ====="); + System.out.println("1. Add Member"); + System.out.println("2. View Members"); + System.out.println("3. Update Member"); + System.out.println("4. Delete Member"); + System.out.println("5. Search by Location"); + System.out.println("6. Search by Skill"); + System.out.println("7. Sort by Name"); + System.out.println("8. Sort by Join Date"); + System.out.println("0. Exit"); + System.out.println("Choose option: "); + } + + private void addMember() throws IOException { + + System.out.println("Enter name:"); + String name = scanner.nextLine(); + + String email = EmailValidator.promptValidEmail(scanner); + + System.out.println("Enter location:"); + String location = scanner.nextLine(); + + System.out.println("Enter skills (comma separated):"); + String skillsInput = scanner.nextLine(); + + List skills = List.of(skillsInput.split(",")); + + Member member = new Member( + name, + email, + location, + skills, + LocalDate.now() + ); + + repository.add(member); + repository.saveToCsv("members.csv"); + + System.out.println("Member added."); + } + + private void viewMembers() { + + System.out.printf("%-15s %-25s %-15s %-20s%n", "Name", "Email", "Location", "Skills"); + System.out.println("---------------------------------------------------------------------------------"); + + repository.findAll().forEach(member -> + System.out.printf("%-15s %-25s %-15s %-20s%n", + member.getName(), + member.getEmail(), + member.getLocation(), + String.join("|", member.getSkills()) + ) + ); + } + + private void updateMember() throws IOException { + + System.out.println("Enter email of member to update:"); + String email = scanner.nextLine(); + + System.out.println("Enter new name:"); + String newName = scanner.nextLine(); + + System.out.println("Enter new email:"); + String newEmail = EmailValidator.promptValidEmail(scanner); + + System.out.println("Enter new location:"); + String newLocation = scanner.nextLine(); + + System.out.println("Enter new skills:"); + String newSkills = scanner.nextLine(); + + repository.updateMember(email, newName, newEmail, newLocation, newSkills); + + repository.saveToCsv("members.csv"); + } + + + private void deleteMember() throws IOException { + + System.out.println("Enter email to delete:"); + + String email = scanner.nextLine(); + + repository.deleteByEmail(email); + + repository.saveToCsv("members.csv"); + + System.out.println("Member deleted."); + } + + + private void searchByLocation() { + + System.out.println("Enter location:"); + String location = scanner.nextLine(); + + List results = repository.findByLocation(location); + + results.forEach(member -> + System.out.printf("%-15s %-25s %-15s%n", + member.getName(), + member.getEmail(), + member.getLocation() + ) + ); + } + + private void searchBySkill() { + + System.out.println("Enter skill:"); + String skill = scanner.nextLine(); + + List results = repository.findBySkill(skill); + + results.forEach(member -> + System.out.printf("%-15s %-25s %-15s%n", + member.getName(), + member.getEmail(), + member.getLocation() + ) + ); + } + + private void sortByName() { + + List results = repository.sortByName(); + + results.forEach(member -> + System.out.printf("%-15s %-25s %-15s%n", + member.getName(), + member.getEmail(), + member.getLocation() + ) + ); + } + + private void sortByJoinDate() { + + List results = repository.sortByJoinDate(); + + results.forEach(member -> + System.out.printf("%-15s %-25s %-15s%n", + member.getName(), + member.getEmail(), + member.getLocation() + ) + ); + } +} diff --git a/participants/olena/project-1/src/main/java/com/wcc/platform/model/CsvMemberRepository.java b/participants/olena/project-1/src/main/java/com/wcc/platform/model/CsvMemberRepository.java new file mode 100644 index 0000000..2b420f7 --- /dev/null +++ b/participants/olena/project-1/src/main/java/com/wcc/platform/model/CsvMemberRepository.java @@ -0,0 +1,144 @@ +package com.wcc.platform.model; + +import java.io.*; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + + +public class CsvMemberRepository implements MemberRepository { + + private final List members = new ArrayList<>(); + + public void add(Member member) { + boolean exists = members.stream().anyMatch(m->m.getEmail().equalsIgnoreCase(member.getEmail())); + + if(exists) { + System.out.println("Member already exists."); + return; + } + members.add(member); + } + + public List findAll() { + + return new ArrayList<>(members); + } + + public void deleteByEmail(String email) { + + members.removeIf(m -> m.getEmail().equalsIgnoreCase(email)); + } + + public void updateMember(String email, String newName, String newEmail, String newLocation, String newSkills) { + + for (int i = 0; i < members.size(); i++) { + + Member m = members.get(i); + + if (m.getEmail().equalsIgnoreCase(email)) { + + Member updatedMember = new Member( + newName, + newEmail, + newLocation, + m.getSkills(), + m.getJoinDate() + ); + + members.set(i, updatedMember); + + System.out.println("Member updated."); + + return; + } + } + + System.out.println("Member not found."); + } + + public List findByLocation(String location) { + + return members.stream() + .filter(m -> m.getLocation().equalsIgnoreCase(location)) + .toList(); + } + + public List findBySkill(String skill) { + + return members.stream() + .filter(m -> m.getSkills() + .stream() + .anyMatch(s -> s.equalsIgnoreCase(skill))) + .toList(); + } + + public List sortByName() { + + return members.stream() + .sorted((a, b) -> a.getName().compareToIgnoreCase(b.getName())) + .toList(); + } + + public List sortByJoinDate() { + + return members.stream() + .sorted((a, b) -> a.getJoinDate().compareTo(b.getJoinDate())) + .toList(); + } + + public void saveToCsv(String filePath) throws IOException { + + try (FileWriter writer = new FileWriter(filePath)) { + + writer.write("name,email,location,skills,joinDate\n"); + + for (Member member : members) { + + String skills = String.join("|", member.getSkills()); + + writer.write(String.format("%s,%s,%s,%s,%s%n", + member.getName(), + member.getEmail(), + member.getLocation(), + skills, + member.getJoinDate() + )); + } + } + } + + public void loadFromCsv(String filePath) throws IOException { + File file = new File(filePath); + if(!file.exists()){ + return; + } + + BufferedReader reader = new BufferedReader(new FileReader(filePath)); + + String line; + + reader.readLine(); + + while ((line = reader.readLine()) != null) { + String[] parts = line.split(","); + String name = parts[0].trim(); + String email = parts[1].trim(); + String location = parts[2].trim(); + String skills = parts[3].trim(); + List skillList = List.of(skills.split("\\|")); + LocalDate joinDate = LocalDate.parse(parts[4].trim()); + + Member member = new Member( + name, + email, + location, + skillList, + joinDate + ); + members.add(member); + } + reader.close(); + } +} + diff --git a/participants/olena/project-1/src/main/java/com/wcc/platform/model/Member.java b/participants/olena/project-1/src/main/java/com/wcc/platform/model/Member.java new file mode 100644 index 0000000..62ae63c --- /dev/null +++ b/participants/olena/project-1/src/main/java/com/wcc/platform/model/Member.java @@ -0,0 +1,56 @@ +package com.wcc.platform.model; + +import java.time.LocalDate; +import java.util.List; + +public class Member { + private final String name; + private final String email; + private final String location; + private final List skills; + private final LocalDate joinDate; + + + public Member( + String name, + String email, + String location, + List skills, + LocalDate joinDate + ) { + this.name = name; + this.email = email; + this.location = location; + this.skills = skills; + this.joinDate = joinDate; + } + + public String getName() { + return name; + } + + public String getEmail() { + return email; + } + + public String getLocation() { + return location; + } + + public List getSkills() { + return skills; + } + + public LocalDate getJoinDate() { + return joinDate; + } + + @Override + public String toString() { + return String.format("%s %s %s", name, email, location); + } +} + + + + diff --git a/participants/olena/project-1/src/main/java/com/wcc/platform/model/MemberRepository.java b/participants/olena/project-1/src/main/java/com/wcc/platform/model/MemberRepository.java new file mode 100644 index 0000000..a608cab --- /dev/null +++ b/participants/olena/project-1/src/main/java/com/wcc/platform/model/MemberRepository.java @@ -0,0 +1,27 @@ +package com.wcc.platform.model; + +import java.io.IOException; +import java.util.List; + +public interface MemberRepository { + + void add(Member member); + + List findAll(); + + void deleteByEmail(String email); + + void updateMember(String email, String newName, String newEmail, String newLocation, String newSkills); + + List findByLocation(String location); + + List findBySkill(String skill); + + List sortByName(); + + List sortByJoinDate(); + + void saveToCsv(String filePath) throws IOException; + + void loadFromCsv(String filePath) throws IOException; +} \ No newline at end of file diff --git a/participants/olena/project-1/src/main/java/com/wcc/platform/validation/EmailValidator.java b/participants/olena/project-1/src/main/java/com/wcc/platform/validation/EmailValidator.java new file mode 100644 index 0000000..0beb423 --- /dev/null +++ b/participants/olena/project-1/src/main/java/com/wcc/platform/validation/EmailValidator.java @@ -0,0 +1,27 @@ +package com.wcc.platform.validation; + +import java.util.Scanner; +import java.util.regex.Pattern; + +public class EmailValidator { + private static final Pattern EMAIL_PATTERN = + Pattern.compile("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$"); + + public static boolean isValid(String email) { + return EMAIL_PATTERN.matcher(email).matches(); + } + + public static String promptValidEmail(Scanner scanner) { + + while (true) { + System.out.println("Enter email: "); + String email = scanner.nextLine(); + + if (isValid(email)) { + return email; + } + + System.out.println("Invalid email format. Please enter a valid email."); + } + } +} \ No newline at end of file diff --git a/participants/olena/project-1/src/test/java/com/wcc/platform/EmailValidatorTest.java b/participants/olena/project-1/src/test/java/com/wcc/platform/EmailValidatorTest.java new file mode 100644 index 0000000..789b11e --- /dev/null +++ b/participants/olena/project-1/src/test/java/com/wcc/platform/EmailValidatorTest.java @@ -0,0 +1,34 @@ +package com.wcc.platform; + +import com.wcc.platform.validation.EmailValidator; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class EmailValidatorTest { + + @Test + void shouldValidateCorrectEmail() { + //Arrange + String email = "test@gmail.com"; + + //Act + boolean result = EmailValidator.isValid(email); + + //Assert + assertTrue(result); + + } + + @Test + void shouldRejectInvalidEmail() { + //Arrange + String email = "email.com"; + + //Act + boolean result = EmailValidator.isValid(email); + + //Assert + assertFalse(result, "Email is invalid"); + } +} \ No newline at end of file diff --git a/src/main/java/com/wcc/bootcamp/java/Task.java b/src/main/java/com/wcc/bootcamp/java/Main.java similarity index 62% rename from src/main/java/com/wcc/bootcamp/java/Task.java rename to src/main/java/com/wcc/bootcamp/java/Main.java index a065737..69783ab 100644 --- a/src/main/java/com/wcc/bootcamp/java/Task.java +++ b/src/main/java/com/wcc/bootcamp/java/Main.java @@ -1,7 +1,9 @@ +package com.wcc.bootcamp.java; + public class Main { public static void main(String[] args) { String greetings = "Hello World"; - System.out.println(greeting); + System.out.println(greetings); } }