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

Implement ApiClient for all endpoints #18

Merged
merged 1 commit into from
Apr 5, 2024
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
16 changes: 15 additions & 1 deletion .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://ci.db?mode=rwc

jobs:
build:

Expand All @@ -30,8 +34,17 @@ jobs:
java-version: '21'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
- uses: Swatinem/rust-cache@v2
- name: Run backend for testing
run: |
cargo install sqlx-cli --no-default-features --features sqlite
cargo sqlx db create
cargo sqlx migrate run
cargo build
cargo run &
working-directory: backend
- name: Build with Gradle
run: ./gradlew build
run: ./gradlew build -x test
mads256h marked this conversation as resolved.
Show resolved Hide resolved
- name: Test (local)
run: ./gradlew test
- name: Test (emulator)
Expand All @@ -45,6 +58,7 @@ jobs:
with:
working-directory: "./frontend"
show-skipped: "true"
if: ${{ always() }}
- name: Lint
run: |
./gradlew lint
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,
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this the device' id?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes it matches the name in the device struct

}
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,
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this the tasks' id?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes it matches the name in the task struct

}
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
Loading