Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remaining animation crud #3

Merged
merged 6 commits into from
Dec 30, 2023
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
2 changes: 1 addition & 1 deletion .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
distribution: 'corretto'
- name: Build with Gradle
uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0
with:
Expand Down
6 changes: 6 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,15 @@ To get the project up and running:
git clone git@github.com:chris-schmitz/matrix-animator-api-2.git
cd matrix-animator-api-2

# To list all of the gradle tasks, including the ones custom to this project:
./gradlew tasks --all

# To launch a local instance of the project (note this will launch detached):
./gradlew dockerComposeUp

# To rebuild the jar and relaunch an already running list of services:
./gradlew dockerComposeReload

# To stop the local instance of the project:
./gradlew dockerComposeDown

Expand Down
8 changes: 7 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,20 @@ tasks {
}
}
val dockerComposeDown by creating(Task::class) {
description = "Pulls a running detached docker servies down."
description = "Pulls a running detached docker services down."
doLast {
project.exec {
commandLine("sh", "-c", "docker-compose down")
}
}
}
val dockerComposeReload by creating(Task::class) {
description = "Reload the docker services if they're already running. Includes jar rebuild."
dependsOn(dockerComposeDown)
dependsOn(dockerComposeUp)
}
}
tasks.getByName("dockerComposeUp").mustRunAfter(tasks.getByName("dockerComposeDown"))

// !--------Build Utilities---------! //
// * I tried to pull these out to a class, but when I did intellij was _not happy_ about it.
Expand Down
33 changes: 27 additions & 6 deletions matrix-animator-api.http
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
### Test
GET http://localhost:8080/rest/animations/test

### save an animation
POST http://localhost:8080/rest/animations
Content-Type: application/json
Expand All @@ -10,6 +7,30 @@ Content-Type: application/json
"frames": []
}

#### Get all animations
#GET http://localhost:8080/rest/animations/
#
### Get an animation by id
< {%
request.variables.set("id", "35")
%}
GET http://localhost:8080/rest/animations/{{id}}

### Get all animations
GET http://localhost:8080/rest/animations

### Update existing animation
PUT http://localhost:8080/rest/animations
Content-Type: application/json

{
"title": "Updated animation",
"frames": [],
"id": 34,
"height": 1,
"width": 2,
"speed": 3
}

### Delete a specific animation
< {%
request.variables.set("id", "38")
%}
DELETE http://localhost:8080/rest/animations/{{id}}
Original file line number Diff line number Diff line change
@@ -1,24 +1,45 @@
package com.lightinspiration.matrixanimatorapi.controllers

import com.lightinspiration.matrixanimatorapi.domain.Animation
import com.lightinspiration.matrixanimatorapi.domain.AnimationMeta
import com.lightinspiration.matrixanimatorapi.services.AnimationService
import org.springframework.http.HttpStatus.NOT_FOUND
import org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY
import org.springframework.web.bind.annotation.*
import org.springframework.web.server.ResponseStatusException

@RestController
@RequestMapping("/rest/animations")
class AnimationController(
private val animationService: AnimationService,
) {

// TODO: rip out after adding a legit get call
// ! don't forget to pull it out of the http client.
@GetMapping("/test")
fun test(): String {
return "worked again!! :O :O :O :nice:"
@GetMapping("/{id}")
fun getAnimation(@PathVariable("id") id: Int): Animation {
return animationService.getAnimation(id) ?: throw ResponseStatusException(NOT_FOUND)
}

@GetMapping
fun getAnimationList(): List<AnimationMeta> {
return animationService.getAnimationList()
}

@PostMapping
fun saveAnimation(@RequestBody animation: Animation) {
animationService.saveAnimation(animation)
}

@PutMapping
fun updateAnimation(@RequestBody animation: Animation) {
return if (animation.id != null)
animationService.updateAnimation(animation.id, animation)
else
throw ResponseStatusException(UNPROCESSABLE_ENTITY, "We can't update an animation without it's ID.")

}

@DeleteMapping("/{id}")
fun deleteAnimation(@PathVariable("id") id: Int) {
animationService.deleteAnimation(id)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.lightinspiration.matrixanimatorapi.domain

import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.databind.ObjectMapper

data class Animation(
Expand All @@ -14,4 +15,11 @@ data class Animation(
fun toJson(): String {
return ObjectMapper().writeValueAsString(this)
}

@JsonIgnore
fun getMeta(): AnimationMeta {
return AnimationMeta(this.id, this.title)
}
}

data class AnimationMeta(val id: Int? = null, val title: String)
Original file line number Diff line number Diff line change
@@ -1,32 +1,104 @@
package com.lightinspiration.matrixanimatorapi.repositories

import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.ObjectMapper
import com.lightinspiration.matrixanimatorapi.domain.Animation
import com.lightinspiration.matrixanimatorapi.domain.Frame
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
import org.springframework.jdbc.support.GeneratedKeyHolder
import org.springframework.stereotype.Repository
import java.sql.ResultSet

@Repository
class AnimationRepository(
private val template: NamedParameterJdbcTemplate,
private val objectMapper: ObjectMapper
) {
fun save(animation: Animation) {

fun getAnimation(id: Int): Animation? {
return template.query(
"""
$ANIMATION_SELECT_QUERY
WHERE id = :id
""",
MapSqlParameterSource().addValue("id", id),
animationRowMapper
)
.firstOrNull()
}

fun getAnimations(): List<Animation> {
return template.query(ANIMATION_SELECT_QUERY, animationRowMapper)
}

fun saveAnimation(animation: Animation): Int {
val keyholder = GeneratedKeyHolder()
template.update(
"""
INSERT INTO matrix_animator.animations
(title, frames, user_id, height, width, speed)
VALUES
(:title, :frames::jsonb, :userId, :height, :width, :speed)
""",
mapAnimationToParameters(animation),
keyholder
)
return keyholder.keys?.get("id") as Int
}

fun updateAnimation(id: Int, animation: Animation) {
template.update(
"""
INSERT INTO matrix_animator.animations
(title, frames, user_id, height, width, speed)
VALUES
(:title, :frames::jsonb, :userId, :height, :width, :speed)
UPDATE matrix_animator.animations
SET
(title, user_id, height, width, speed, frames)
=
(:title, :userId, :height, :width, :speed, :frames::jsonb)
WHERE id = :id
""",
MapSqlParameterSource()
.addValue("title", animation.title)
.addValue("frames", objectMapper.writeValueAsString(animation.frames))
.addValue("userId", animation.userId)
.addValue("height", animation.height)
.addValue("width", animation.width)
.addValue("speed", animation.speed)
mapAnimationToParameters(animation).apply { addValue("id", id) }
)
}

fun deleteAnimation(id: Int): Int {
return template.update(
"""
DELETE FROM matrix_animator.animations
WHERE id = :id
""",
MapSqlParameterSource().addValue("id", id)
)
}

private fun mapAnimationToParameters(animation: Animation) =
MapSqlParameterSource()
.addValue("title", animation.title)
.addValue("frames", objectMapper.writeValueAsString(animation.frames))
.addValue("userId", animation.userId)
.addValue("height", animation.height)
.addValue("width", animation.width)
.addValue("speed", animation.speed)

private val animationRowMapper: (ResultSet, Int) -> Animation =
{ resultSet, _ ->
Animation(
resultSet.getString("title"),
resultSet.getInt("user_id"),
resultSet.getInt("height"),
resultSet.getInt("width"),
resultSet.getInt("speed"),
objectMapper.readValue(resultSet.getString("frames"), object : TypeReference<List<Frame>>() {}),
resultSet.getInt("id")
)
}

companion object {
private const val ANIMATION_SELECT_QUERY = """
SELECT
id, title, frames, user_id, height, width, speed
FROM
matrix_animator.animations
"""
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.lightinspiration.matrixanimatorapi.services

import com.lightinspiration.matrixanimatorapi.domain.Animation
import com.lightinspiration.matrixanimatorapi.domain.AnimationMeta
import com.lightinspiration.matrixanimatorapi.repositories.AnimationRepository
import org.springframework.stereotype.Service

Expand All @@ -9,7 +10,25 @@ class AnimationService(
val animationRepository: AnimationRepository
) {
fun saveAnimation(animation: Animation) {
animationRepository.save(animation)
animationRepository.saveAnimation(animation)
}

fun getAnimation(id: Int): Animation? {
return animationRepository.getAnimation(id)
}

fun getAnimationList(): List<AnimationMeta> {
return animationRepository
.getAnimations()
.map { it.getMeta() }
}

fun updateAnimation(id: Int, animation: Animation) {
animationRepository.updateAnimation(id, animation)
}

fun deleteAnimation(id: Int) {
animationRepository.deleteAnimation(id)
}

}
Original file line number Diff line number Diff line change
@@ -1,20 +1,12 @@
package com.lightinspiration.matrixanimatorapi.configuration

import liquibase.Contexts
import liquibase.LabelExpression
import liquibase.Liquibase
import liquibase.Scope
import liquibase.database.DatabaseFactory
import liquibase.database.jvm.JdbcConnection
import liquibase.resource.DirectoryResourceAccessor
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.boot.jdbc.DataSourceBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Profile
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
import org.testcontainers.containers.PostgreSQLContainer
import java.io.File
import javax.sql.DataSource

@Configuration("test-datasource-configuration")
Expand Down Expand Up @@ -62,36 +54,6 @@ class TestDataSourceConfiguration {
}
.build()
}

// * keeping this around for now. I have a feeling there's a chance this may be needed once the
// * db creds for prod and test are different, but really that _should_ be solve-able via test properties.
fun buildSchema(dataSource: DataSource) {
Scope.child(
emptyMap()
) { ->
// TODO: the path should prob be abstracted and passed in as a property
val changeLog = File("src/main/resources/db/changelog/db.changelog-master.yaml")
val liquibase = Liquibase(
changeLog.name,
DirectoryResourceAccessor(changeLog.parentFile),
DatabaseFactory.getInstance()
.findCorrectDatabaseImplementation(JdbcConnection(dataSource.connection))
)
liquibase.update(Contexts(), LabelExpression())
// * From what I'm reading this _should_ be the non-deprecated approach to starting and running liquibase,
// * but it doesn't work and I'm tired of fiddling with it :|
//val database =
// DatabaseFactory.getInstance().findCorrectDatabaseImplementation(JdbcConnection(dataSource.connection))
//CommandScope(UpdateCommandStep.COMMAND_NAME.first())
// .addArgumentValue(DbUrlConnectionCommandStep.DATABASE_ARG, database)
// .addArgumentValue(
// UpdateCommandStep.CHANGELOG_FILE_ARG,
// "src/test/resources/db/changelog/db.changelog-master.yaml"
// )
// .execute()
}
}

}
}

Expand Down
Loading