Skip to content

Commit

Permalink
Implement ApiClient for all endpoints
Browse files Browse the repository at this point in the history
Change delete endpoints to only take the id of the object to delete.
Change delete endpoints to use the HTTP DELETE method.

Add protocol objects to the frontend for talking to the backend.
Use kotlin coroutines instead of callbacks for the ApiClient.

Create tests that connect to http://localhost:3000 and tests with the
backend.
  • Loading branch information
mads256h committed Apr 4, 2024
1 parent 127d1b3 commit fbabbcd
Show file tree
Hide file tree
Showing 14 changed files with 307 additions and 64 deletions.
18 changes: 18 additions & 0 deletions .github/workflows/frontend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ on:
permissions:
contents: read

env:
CARGO_TERM_COLOR: always
DATABASE_URL: 'sqlite::memory:'

jobs:
build:

Expand All @@ -30,6 +34,20 @@ jobs:
java-version: '21'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
- uses: JarvusInnovations/background-action@v1
name: Bootstrap System Under Test (SUT)
with:
run: |
cargo install sqlx-cli --no-default-features --features sqlite
cargo sqlx db create
cargo sqlx migrate run
cargo build
cargo run &
wait-on: |
http://localhost:3000
working-directory: backend
- name: Build with Gradle
run: ./gradlew build
- name: Test (local)
Expand Down
6 changes: 3 additions & 3 deletions backend/src/handlers/tasks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{
data_model::{task::Task, time::Timespan},
extractors::auth::Authentication,
handlers::util::internal_error,
protocol::tasks::CreateTaskRequest,
protocol::tasks::{CreateTaskRequest, DeleteTaskRequest},
};

#[debug_handler]
Expand Down Expand Up @@ -77,7 +77,7 @@ pub async fn create_task(
pub async fn delete_task(
State(pool): State<SqlitePool>,
Authentication(account_id): Authentication,
Json(task): Json<Task>,
Json(delete_task_request): Json<DeleteTaskRequest>,
) -> Result<(), (StatusCode, String)> {
sqlx::query!(
r#"
Expand All @@ -89,7 +89,7 @@ pub async fn delete_task(
AND Devices.account_id == ?
)
"#,
task.id,
delete_task_request.id,
account_id
)
.execute(&pool)
Expand Down
8 changes: 4 additions & 4 deletions backend/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ mod protocol;
use std::error::Error;

use axum::{
routing::{get, post},
routing::{delete, get, post},
Router,
};
use dotenv::dotenv;
Expand All @@ -17,7 +17,7 @@ use handlers::{accounts::*, devices::*, tasks::*};

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
dotenv()?;
dotenv().ok();

let db_connection_string = std::env::var("DATABASE_URL")?;

Expand All @@ -40,10 +40,10 @@ fn app(pool: SqlitePool) -> Router {
Router::new()
.route("/tasks/all", get(get_tasks))
.route("/tasks/create", post(create_task))
.route("/tasks/delete", post(delete_task))
.route("/tasks/delete", delete(delete_task))
.route("/devices/all", get(get_devices))
.route("/devices/create", post(create_device))
.route("/devices/delete", post(delete_device))
.route("/devices/delete", delete(delete_device))
.route("/accounts/register", post(register_account))
.route("/accounts/login", post(login_to_account))
.with_state(pool)
Expand Down
5 changes: 5 additions & 0 deletions backend/src/protocol/devices.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@ use serde::{Deserialize, Serialize};
pub struct CreateDeviceRequest {
pub effect: f64,
}

#[derive(Deserialize, Serialize)]
pub struct DeleteDeviceRequest {
pub id: i64,
}
5 changes: 5 additions & 0 deletions backend/src/protocol/tasks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@ pub struct CreateTaskRequest {
pub duration: Milliseconds,
pub device_id: i64,
}

#[derive(Deserialize, Serialize)]
pub struct DeleteTaskRequest {
pub id: i64,
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,34 @@ import android.util.Log
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import dk.scheduling.schedulingfrontend.api.ApiClient
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import androidx.compose.runtime.rememberCoroutineScope
import dk.scheduling.schedulingfrontend.api.getApiClient
import kotlinx.coroutines.launch

@Composable
fun ApiButton() {
Button(onClick = {
val call = ApiClient.apiService.test()
val coroutineScope = rememberCoroutineScope()
val apiService = getApiClient("http://10.0.2.2:2222")

call.enqueue(
object : Callback<String> {
override fun onResponse(
call: Call<String>,
response: Response<String>,
) {
if (response.isSuccessful) {
// val post = response.body()
Log.i("testAPI", "we got a response")
// Handle the retrieved post data
} else {
Log.w("testAPI", "we did not get a successful response")
// Handle error
}
}
Button(onClick = {
coroutineScope.launch {
try {
val response = apiService.getTasks("NOT_VALID")

override fun onFailure(
call: Call<String>,
t: Throwable,
) {
// Handle failure
Log.e("testAPI", "could not get a response")
if (response.isSuccessful) {
// val post = response.body()
Log.i("testAPI", "we got a response")
// Handle the retrieved post data
} else {
Log.w("testAPI", "we did not get a successful response")
Log.w("testAPI", response.message())
// Handle error
}
},
)
} catch (e: Exception) {
Log.e("testApi", e.toString())
throw e
}
}
}) {
Text("Test API")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@ import com.google.gson.GsonBuilder
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.create
import java.security.SecureRandom
import java.security.cert.X509Certificate
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager

object RetrofitClient {
class RetrofitClient(val baseUrl: String) {
/**
* The base URL of the API
* 10.0.2.2 is a special alias to your host loopback interface (127.0.0.1 on your development machine)
* 2222 is the port where the server is running
*/
private const val BASE_URL = "http://10.0.2.2:2222/"
private val gson = GsonBuilder().setLenient().create()
private val okHttpClient: OkHttpClient by lazy {
val trustAllCertificates =
Expand Down Expand Up @@ -48,15 +48,13 @@ object RetrofitClient {
}
val retrofit: Retrofit by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.baseUrl(baseUrl)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
}
}

object ApiClient {
val apiService: ApiService by lazy {
RetrofitClient.retrofit.create(ApiService::class.java)
}
fun getApiClient(baseUrl: String): ApiService {
return RetrofitClient(baseUrl).retrofit.create<ApiService>()
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,70 @@
package dk.scheduling.schedulingfrontend.api
import retrofit2.Call
import dk.scheduling.schedulingfrontend.api.protocol.CreateDeviceRequest
import dk.scheduling.schedulingfrontend.api.protocol.CreateTaskRequest
import dk.scheduling.schedulingfrontend.api.protocol.DeleteDeviceRequest
import dk.scheduling.schedulingfrontend.api.protocol.DeleteTaskRequest
import dk.scheduling.schedulingfrontend.api.protocol.Device
import dk.scheduling.schedulingfrontend.api.protocol.RegisterOrLoginRequest
import dk.scheduling.schedulingfrontend.api.protocol.RegisterOrLoginResponse
import dk.scheduling.schedulingfrontend.api.protocol.Task
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.POST

interface ApiService {
@GET("test")
fun test(): Call<String>
/*
* Accounts
*/
@POST("accounts/register")
suspend fun registerAccount(
@Body registerOrLoginRequest: RegisterOrLoginRequest,
): Response<RegisterOrLoginResponse>

@POST("accounts/login")
suspend fun loginToAccount(
@Body registerOrLoginRequest: RegisterOrLoginRequest,
): Response<RegisterOrLoginResponse>

/*
* Devices
*/
@GET("devices/all")
suspend fun getDevices(
@Header("X-Auth-Token") authToken: String,
): Response<List<Device>>

@POST("devices/create")
suspend fun createDevice(
@Header("X-Auth-Token") authToken: String,
@Body createDeviceRequest: CreateDeviceRequest,
): Response<Device>

@DELETE("devices/delete")
suspend fun deleteDevice(
@Header("X-Auth-Token") authToken: String,
@Body deleteDeviceRequest: DeleteDeviceRequest,
): Response<String>

/*
* Tasks
*/
@GET("tasks/all")
suspend fun getTasks(
@Header("X-Auth-Token") authToken: String,
): Response<List<Task>>

@POST("tasks/create")
suspend fun createTask(
@Header("X-Auth-Token") authToken: String,
@Body createTaskRequest: CreateTaskRequest,
): Response<Task>

@DELETE("tasks/delete")
suspend fun deleteTask(
@Header("X-Auth-Token") authToken: String,
@Body deleteTaskRequest: DeleteTaskRequest,
): Response<String>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package dk.scheduling.schedulingfrontend.api.protocol

import java.util.UUID

data class RegisterOrLoginRequest(
val username: String,
val password: String,
)

data class RegisterOrLoginResponse(
val auth_token: UUID,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package dk.scheduling.schedulingfrontend.api.protocol

data class Device(
val id: Long,
val effect: Double,
val account_id: Long,
)

data class CreateDeviceRequest(
val effect: Double,
)

data class DeleteDeviceRequest(
val id: Long,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package dk.scheduling.schedulingfrontend.api.protocol

data class Task(
val id: Long,
val timespan: Timespan,
val duration: Long,
val device_id: Long,
)

data class CreateTaskRequest(
val timespan: Timespan,
val duration: Long,
val device_id: Long,
)

data class DeleteTaskRequest(
val id: Long,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package dk.scheduling.schedulingfrontend.api.protocol

// TODO: This should not be strings
data class Timespan(
val start: String,
val end: String,
)
Loading

0 comments on commit fbabbcd

Please sign in to comment.