Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/check_lesson_25_java_pr.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Check Lesson 25 Java Pull Request

on:
pull_request:
branches: [ "main" ]
paths:
- "lesson_25/db/**"

jobs:
build:

runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write

steps:
- uses: actions/checkout@v4

- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'

- name: Build Lesson 26 with Java
working-directory: ./lesson_25/db
run: ./gradlew check
25 changes: 24 additions & 1 deletion lesson_25/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,27 @@ Please review the following resources before lecture:

## Homework

- TODO(anthonydmays): Add more information.
- [ ] Complete [Loading the Library, Part II](#loading-the-library-part-ii) assignment.


### Loading The Library, Part II

Instead of loading our library data from JSON or CSV files as we did in [lesson_10](/lesson_10/), we now want to load data from a proper database. A new implementation of the `LibraryDbDataLoader` data loader has been provided to accomplish this task and is now the [default data loader][library-app] for the app.

To build familiarity in working with databases, you are charged with the following tasks:

* Write a `.sql` script file that queries the following data. Use a unique name for your file and store it in the [queries][queries-dir] directory of the resources folder.
* A `SELECT` query that returns the counts of media items by type.
* A `SELECT` query that returns the sum of total pages checked out by guests.
* A `SELECT` query that shows all 5 guests and any corresponding records in the `checked_out_items` table.
* Add a new table called `library_users` to the [SQLite database][sqlite-db] that stores a user's id (UUID formatted string), email, first name, last name, and a password (bcrypt encoded string). Add a model and repository that loads the users into the LibraryDataModel (see `LibraryGuestModel` and `LibraryGuestRepository` as examples). Populate the database with a few users.

As before, you can run the app from the console using the following command:

```bash
./gradlew run --console=plain
```

[queries-dir]: ./db/db_app/src/main/resources/queries/
[sqlite-db]: ./db/db_app/src/main/resources/sqlite/
[library-app]: ./db/db_app/src/main/java/com/codedifferently/lesson25/cli/LibraryApp.java#L26
23 changes: 23 additions & 0 deletions lesson_25/createdb/createdb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import os
import pandas as pd
import numpy as np
import sqlite3

# Step 1: Load the CSV file into a pandas DataFrame
media_items_df = pd.read_csv('/workspaces/code-society-25-2/lesson_12/io/io_app/src/main/resources/csv/media_items.csv')
guests_df = pd.read_csv('/workspaces/code-society-25-2/lesson_12/io/io_app/src/main/resources/csv/guests.csv')
checked_out_items_df = pd.read_csv('/workspaces/code-society-25-2/lesson_12/io/io_app/src/main/resources/csv/checked_out_items.csv')
checked_out_items_df['due_date'] = pd.to_datetime(checked_out_items_df['due_date']).values.astype(np.int64)

# Step 2: Create a connection to the SQLite database
# Note: This will create the database file if it doesn't exist already
os.makedirs('/workspaces/code-society-25-2/lesson_25/db/db_app/src/main/resources/sqlite/', exist_ok=True)
conn = sqlite3.connect('../db/db_app/src/main/resources/sqlite/data.db')
Comment on lines +7 to +15
Copy link

Copilot AI Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard-coded absolute paths make the script non-portable. Consider using relative paths or environment variables to make the script work across different development environments.

Suggested change
media_items_df = pd.read_csv('/workspaces/code-society-25-2/lesson_12/io/io_app/src/main/resources/csv/media_items.csv')
guests_df = pd.read_csv('/workspaces/code-society-25-2/lesson_12/io/io_app/src/main/resources/csv/guests.csv')
checked_out_items_df = pd.read_csv('/workspaces/code-society-25-2/lesson_12/io/io_app/src/main/resources/csv/checked_out_items.csv')
checked_out_items_df['due_date'] = pd.to_datetime(checked_out_items_df['due_date']).values.astype(np.int64)
# Step 2: Create a connection to the SQLite database
# Note: This will create the database file if it doesn't exist already
os.makedirs('/workspaces/code-society-25-2/lesson_25/db/db_app/src/main/resources/sqlite/', exist_ok=True)
conn = sqlite3.connect('../db/db_app/src/main/resources/sqlite/data.db')
base_csv_dir = os.path.join(os.path.dirname(__file__), '../../12/io/io_app/src/main/resources/csv')
media_items_df = pd.read_csv(os.path.join(base_csv_dir, 'media_items.csv'))
guests_df = pd.read_csv(os.path.join(base_csv_dir, 'guests.csv'))
checked_out_items_df = pd.read_csv(os.path.join(base_csv_dir, 'checked_out_items.csv'))
checked_out_items_df['due_date'] = pd.to_datetime(checked_out_items_df['due_date']).values.astype(np.int64)
# Step 2: Create a connection to the SQLite database
# Note: This will create the database file if it doesn't exist already
db_dir = os.path.join(os.path.dirname(__file__), '../db/db_app/src/main/resources/sqlite')
os.makedirs(db_dir, exist_ok=True)
conn = sqlite3.connect(os.path.join(db_dir, 'data.db'))

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +15
Copy link

Copilot AI Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard-coded absolute path for the SQLite directory makes the script environment-specific. Use relative paths or path resolution based on the script's location.

Suggested change
os.makedirs('/workspaces/code-society-25-2/lesson_25/db/db_app/src/main/resources/sqlite/', exist_ok=True)
conn = sqlite3.connect('../db/db_app/src/main/resources/sqlite/data.db')
# Resolve the SQLite directory relative to this script's location
script_dir = os.path.dirname(os.path.abspath(__file__))
sqlite_dir = os.path.join(script_dir, '../db/db_app/src/main/resources/sqlite/')
os.makedirs(sqlite_dir, exist_ok=True)
db_path = os.path.join(sqlite_dir, 'data.db')
conn = sqlite3.connect(db_path)

Copilot uses AI. Check for mistakes.

# Step 3: Write the DataFrame to the SQLite database
media_items_df.to_sql('media_items', conn, if_exists='replace', index=False)
guests_df.to_sql('guests', conn, if_exists='replace', index=False)
checked_out_items_df.to_sql('checked_out_items', conn, if_exists='replace', index=False)

# Don't forget to close the connection
conn.close()
1 change: 1 addition & 0 deletions lesson_25/createdb/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pandas
9 changes: 9 additions & 0 deletions lesson_25/db/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# Linux start script should use lf
/gradlew text eol=lf

# These are Windows script files and should use crlf
*.bat text eol=crlf

5 changes: 5 additions & 0 deletions lesson_25/db/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Ignore Gradle project-specific cache directory
.gradle

# Ignore Gradle build output directory
build
79 changes: 79 additions & 0 deletions lesson_25/db/db_app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
plugins {
// Apply the application plugin to add support for building a CLI application in Java.
application
eclipse
id("com.diffplug.spotless") version "6.25.0"
id("org.springframework.boot") version "3.4.0"
id("com.adarshr.test-logger") version "4.0.0"
}

apply(plugin = "io.spring.dependency-management")

repositories {
// Use Maven Central for resolving dependencies.
mavenCentral()
}

dependencies {
// Use JUnit Jupiter for testing.
testImplementation("com.codedifferently.instructional:instructional-lib")
testImplementation("org.junit.jupiter:junit-jupiter:5.11.3")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.assertj:assertj-core:3.26.3")
testImplementation("at.favre.lib:bcrypt:0.10.2")
testCompileOnly("org.projectlombok:lombok:1.18.38")
testAnnotationProcessor("org.projectlombok:lombok:1.18.38")

// This dependency is used by the application.
implementation("com.codedifferently.instructional:instructional-lib")
implementation("com.google.guava:guava:33.3.1-jre")
implementation("com.google.code.gson:gson:2.11.0")
implementation("commons-cli:commons-cli:1.6.0")
implementation("org.springframework.boot:spring-boot-starter")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("com.fasterxml.jackson.core:jackson-databind:2.13.0")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.3")
implementation("com.opencsv:opencsv:5.9")
implementation("org.apache.commons:commons-csv:1.10.0")
implementation("org.xerial:sqlite-jdbc:3.36.0")
implementation("org.hibernate.orm:hibernate-community-dialects:6.2.7.Final")
compileOnly("org.projectlombok:lombok:1.18.38")
annotationProcessor("org.projectlombok:lombok:1.18.38")
}

application {
// Define the main class for the application.
mainClass.set("com.codedifferently.lesson25.Lesson25")
}

tasks.named<JavaExec>("run") {
standardInput = System.`in`
}

tasks.named<Test>("test") {
// Use JUnit Platform for unit tests.
useJUnitPlatform()
}


configure<com.diffplug.gradle.spotless.SpotlessExtension> {

format("misc", {
// define the files to apply `misc` to
target("*.gradle", ".gitattributes", ".gitignore")

// define the steps to apply to those files
trimTrailingWhitespace()
indentWithTabs() // or spaces. Takes an integer argument if you don't like 4
endWithNewline()
})

java {
// don't need to set target, it is inferred from java

// apply a specific flavor of google-java-format
googleJavaFormat()
// fix formatting of type annotations
formatAnnotations()
}
}
2 changes: 2 additions & 0 deletions lesson_25/db/db_app/lombok.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# This file is generated by the 'io.freefair.lombok' Gradle plugin
config.stopBubbling = true
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.codedifferently.lesson25;

import com.codedifferently.lesson25.cli.LibraryApp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Configuration;

@Configuration
@SpringBootApplication(scanBasePackages = "com.codedifferently")
public class Lesson25 implements CommandLineRunner {

@Autowired private LibraryApp libraryApp;

public static void main(String[] args) {
var application = new SpringApplication(Lesson25.class);
application.run(args);
}

@Override
public void run(String... args) throws Exception {
// Don't run as an app if we're running as a JUnit test.
if (isJUnitTest()) {
return;
}

libraryApp.run(args);
}

private static boolean isJUnitTest() {
for (StackTraceElement element : Thread.currentThread().getStackTrace()) {
if (element.getClassName().startsWith("org.junit.")) {
return true;
}
}
return false;
}
}
Loading