cards #8
93 changed files with 2391 additions and 699 deletions
|
@ -11,7 +11,7 @@ android {
|
|||
|
||||
defaultConfig {
|
||||
applicationId = "com.faraphel.tasks_valider"
|
||||
minSdk = 24
|
||||
minSdk = 26
|
||||
targetSdk = 34
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
|
@ -52,8 +52,8 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation("androidx.core:core-ktx:1.13.0")
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
|
||||
implementation("androidx.core:core-ktx:1.13.1")
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.1")
|
||||
implementation("androidx.activity:activity-compose:1.9.0")
|
||||
implementation(platform("androidx.compose:compose-bom:2023.08.00"))
|
||||
implementation("androidx.compose.ui:ui")
|
||||
|
@ -66,6 +66,8 @@ dependencies {
|
|||
implementation("org.nanohttpd:nanohttpd:2.3.1")
|
||||
implementation("com.google.code.gson:gson:2.10.1")
|
||||
implementation("com.squareup.okhttp3:okhttp-android:5.0.0-alpha.14")
|
||||
implementation("com.journeyapps:zxing-android-embedded:4.3.0")
|
||||
implementation("com.google.zxing:core:3.5.3")
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||
|
@ -74,4 +76,5 @@ dependencies {
|
|||
debugImplementation("androidx.compose.ui:ui-tooling")
|
||||
debugImplementation("androidx.compose.ui:ui-test-manifest")
|
||||
ksp("androidx.room:room-compiler:2.6.1")
|
||||
implementation(kotlin("reflect"))
|
||||
}
|
||||
|
|
|
@ -5,12 +5,12 @@
|
|||
<!-- SDK -->
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="24"
|
||||
android:minSdkVersion="26"
|
||||
tools:ignore="GradleOverrides" />
|
||||
|
||||
<!-- Permissions -->
|
||||
|
||||
<!-- Internet -->
|
||||
<!-- Permissions: Internet -->
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
||||
|
@ -26,6 +26,10 @@
|
|||
android:usesPermissionFlags="neverForLocation"
|
||||
tools:targetApi="s" />
|
||||
|
||||
<!-- Permissions: Scan -->
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false"/>
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
|
||||
<!-- Applications -->
|
||||
|
||||
<!-- NOTE: usesCleartextTraffic is enabled because of the API system using simple HTTP -->
|
||||
|
@ -47,9 +51,12 @@
|
|||
android:theme="@style/Theme.Tasksvalider">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="com.google.zxing.client.android.SCAN"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
|
|
|
@ -6,24 +6,41 @@ import android.os.Bundle
|
|||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.faraphel.tasks_valider.connectivity.bwf.BwfManager
|
||||
import androidx.room.Room
|
||||
import com.faraphel.tasks_valider.connectivity.bwd.BwdManager
|
||||
import com.faraphel.tasks_valider.database.TaskDatabase
|
||||
import com.faraphel.tasks_valider.ui.screen.communication.CommunicationScreen
|
||||
import com.faraphel.tasks_valider.database.populateTaskDatabaseTest
|
||||
import com.faraphel.tasks_valider.ui.screen.communication.CommunicationModeSelectionScreen
|
||||
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
private var bwfManager: BwfManager? = null ///< the WiFi-Direct helper
|
||||
|
||||
companion object {
|
||||
private var bwdManager: BwdManager? = null ///< the WiFi-Direct helper
|
||||
private lateinit var database: TaskDatabase ///< the database manager
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// Reset the database | TODO(Faraphel): only for testing purpose
|
||||
this.deleteDatabase("local")
|
||||
|
||||
// Create the database
|
||||
this.database = Room.databaseBuilder(
|
||||
this,
|
||||
TaskDatabase::class.java,
|
||||
"local"
|
||||
).build()
|
||||
|
||||
// Populate the database with test data
|
||||
// TODO(Faraphel): remove test data
|
||||
Thread {
|
||||
populateTaskDatabaseTest(database)
|
||||
}.let { thread ->
|
||||
thread.start()
|
||||
thread.join()
|
||||
}
|
||||
|
||||
this.setContent {
|
||||
CommunicationScreen(this)
|
||||
CommunicationModeSelectionScreen(this, database)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,13 +49,13 @@ class MainActivity : ComponentActivity() {
|
|||
super.onResume()
|
||||
|
||||
// enable the WiFi-Direct events
|
||||
this.registerReceiver(this.bwfManager, BwfManager.ALL_INTENT_FILTER)
|
||||
this.registerReceiver(this.bwdManager, BwdManager.ALL_INTENT_FILTER)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
|
||||
// disable the WiFi-Direct events
|
||||
this.unregisterReceiver(this.bwfManager)
|
||||
this.unregisterReceiver(this.bwdManager)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package com.faraphel.tasks_valider.connectivity.bwf
|
||||
package com.faraphel.tasks_valider.connectivity.bwd
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
|
@ -10,7 +10,7 @@ import android.content.pm.PackageManager
|
|||
import android.net.wifi.p2p.*
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import com.faraphel.tasks_valider.connectivity.bwf.error.*
|
||||
import com.faraphel.tasks_valider.connectivity.bwd.error.*
|
||||
|
||||
|
||||
/**
|
||||
|
@ -22,7 +22,7 @@ import com.faraphel.tasks_valider.connectivity.bwf.error.*
|
|||
* @param manager The WiFi-Direct manager
|
||||
* @param channel The WiFi-Direct channel
|
||||
*/
|
||||
class BwfManager(
|
||||
class BwdManager(
|
||||
private var manager: WifiP2pManager,
|
||||
private var channel: WifiP2pManager.Channel,
|
||||
) : BroadcastReceiver() {
|
||||
|
@ -41,11 +41,11 @@ class BwfManager(
|
|||
* Create a new BwfManager from an activity.
|
||||
* @param activity The activity to create the manager from
|
||||
*/
|
||||
fun fromActivity(activity: Activity): BwfManager {
|
||||
fun fromActivity(activity: Activity): BwdManager {
|
||||
// check if the system support WiFi-Direct
|
||||
if (this.isSupported(activity)) {
|
||||
Log.e("wifi-p2p", "this device does not support the WiFi-Direct feature")
|
||||
throw BwfNotSupportedException()
|
||||
throw BwdNotSupportedException()
|
||||
}
|
||||
|
||||
// TODO(Faraphel): more check on permissions
|
||||
|
@ -62,11 +62,11 @@ class BwfManager(
|
|||
|
||||
// get the WiFi-Direct manager
|
||||
val manager = activity.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager?
|
||||
?: throw BwfPermissionException()
|
||||
?: throw BwdPermissionException()
|
||||
|
||||
// get the WiFi-Direct channel
|
||||
val channel = manager.initialize(activity, activity.mainLooper, null)
|
||||
return BwfManager(manager, channel)
|
||||
return BwdManager(manager, channel)
|
||||
|
||||
// NOTE(Faraphel): the broadcast receiver should be registered in the activity onResume
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ class BwfManager(
|
|||
fun connect(config: WifiP2pConfig, callback: () -> Unit = {}) =
|
||||
this.manager.connect(this.channel, config, object : WifiP2pManager.ActionListener {
|
||||
override fun onSuccess() { callback() }
|
||||
override fun onFailure(reason: Int) = throw BwfConnectException(reason)
|
||||
override fun onFailure(reason: Int) = throw BwdConnectException(reason)
|
||||
})
|
||||
|
||||
/**
|
||||
|
@ -105,7 +105,7 @@ class BwfManager(
|
|||
fun discoverPeers(callback: () -> Unit = {}) =
|
||||
this.manager.discoverPeers(this.channel, object : WifiP2pManager.ActionListener {
|
||||
override fun onSuccess() { callback() }
|
||||
override fun onFailure(reason: Int) = throw BwfDiscoverException(reason)
|
||||
override fun onFailure(reason: Int) = throw BwdDiscoverException(reason)
|
||||
})
|
||||
|
||||
/**
|
||||
|
@ -133,7 +133,7 @@ class BwfManager(
|
|||
fun createGroup(callback: () -> Unit = {}) =
|
||||
this.manager.createGroup(this.channel, object : WifiP2pManager.ActionListener {
|
||||
override fun onSuccess() { callback() }
|
||||
override fun onFailure(reason: Int) = throw BwfCreateGroupException(reason)
|
||||
override fun onFailure(reason: Int) = throw BwdCreateGroupException(reason)
|
||||
})
|
||||
|
||||
/**
|
||||
|
@ -143,7 +143,7 @@ class BwfManager(
|
|||
fun removeGroup(callback: () -> Unit = {}) =
|
||||
this.manager.removeGroup(this.channel, object : WifiP2pManager.ActionListener {
|
||||
override fun onSuccess() { callback() }
|
||||
override fun onFailure(reason: Int) = throw BwfRemoveGroupException(reason)
|
||||
override fun onFailure(reason: Int) = throw BwdRemoveGroupException(reason)
|
||||
})
|
||||
|
||||
/**
|
||||
|
@ -161,7 +161,7 @@ class BwfManager(
|
|||
this.requestGroupInfo { group ->
|
||||
// if a group exist, quit it
|
||||
if (group != null)
|
||||
this.removeGroup { this@BwfManager.createGroup(callback) }
|
||||
this.removeGroup { this@BwdManager.createGroup(callback) }
|
||||
else
|
||||
// create the group
|
||||
this.createGroup(callback)
|
|
@ -0,0 +1,5 @@
|
|||
package com.faraphel.tasks_valider.connectivity.bwd.error
|
||||
|
||||
class BwdConnectException(
|
||||
reason: Int
|
||||
) : BwdException("Cannot connect to the peer. Reason: $reason")
|
|
@ -0,0 +1,5 @@
|
|||
package com.faraphel.tasks_valider.connectivity.bwd.error
|
||||
|
||||
class BwdCreateGroupException (
|
||||
reason: Int
|
||||
) : BwdException("Could not create the group : $reason")
|
|
@ -0,0 +1,5 @@
|
|||
package com.faraphel.tasks_valider.connectivity.bwd.error
|
||||
|
||||
class BwdDiscoverException(
|
||||
reason: Int
|
||||
) : BwdException("Could not discover peers : $reason")
|
|
@ -1,9 +1,9 @@
|
|||
package com.faraphel.tasks_valider.connectivity.bwf.error
|
||||
package com.faraphel.tasks_valider.connectivity.bwd.error
|
||||
|
||||
|
||||
/**
|
||||
* Base Exception for everything concerning the WifiP2pHelper class
|
||||
*/
|
||||
open class BwfException(
|
||||
open class BwdException(
|
||||
override val message: String?
|
||||
) : Exception(message)
|
|
@ -0,0 +1,5 @@
|
|||
package com.faraphel.tasks_valider.connectivity.bwd.error
|
||||
|
||||
class BwdInvalidActionException(
|
||||
action: String
|
||||
) : BwdException("This WiFi-Direct action is not supported : $action")
|
|
@ -0,0 +1,4 @@
|
|||
package com.faraphel.tasks_valider.connectivity.bwd.error
|
||||
|
||||
class BwdNotSupportedException :
|
||||
BwdException("WiFi-Direct is not supported on this device.")
|
|
@ -0,0 +1,4 @@
|
|||
package com.faraphel.tasks_valider.connectivity.bwd.error
|
||||
|
||||
class BwdPermissionException :
|
||||
BwdException("WiFi-Direct requires permissions to work properly. Please grant the permissions.")
|
|
@ -0,0 +1,5 @@
|
|||
package com.faraphel.tasks_valider.connectivity.bwd.error
|
||||
|
||||
class BwdRemoveGroupException (
|
||||
reason: Int
|
||||
) : BwdException("Could not remove the group : $reason")
|
|
@ -1,5 +0,0 @@
|
|||
package com.faraphel.tasks_valider.connectivity.bwf.error
|
||||
|
||||
class BwfConnectException(
|
||||
reason: Int
|
||||
) : BwfException("Cannot connect to the peer. Reason: $reason")
|
|
@ -1,5 +0,0 @@
|
|||
package com.faraphel.tasks_valider.connectivity.bwf.error
|
||||
|
||||
class BwfCreateGroupException (
|
||||
reason: Int
|
||||
) : BwfException("Could not create the group : $reason")
|
|
@ -1,5 +0,0 @@
|
|||
package com.faraphel.tasks_valider.connectivity.bwf.error
|
||||
|
||||
class BwfDiscoverException(
|
||||
reason: Int
|
||||
) : BwfException("Could not discover peers : $reason")
|
|
@ -1,5 +0,0 @@
|
|||
package com.faraphel.tasks_valider.connectivity.bwf.error
|
||||
|
||||
class BwfInvalidActionException(
|
||||
action: String
|
||||
) : BwfException("This WiFi-Direct action is not supported : $action")
|
|
@ -1,4 +0,0 @@
|
|||
package com.faraphel.tasks_valider.connectivity.bwf.error
|
||||
|
||||
class BwfNotSupportedException :
|
||||
BwfException("WiFi-Direct is not supported on this device.")
|
|
@ -1,4 +0,0 @@
|
|||
package com.faraphel.tasks_valider.connectivity.bwf.error
|
||||
|
||||
class BwfPermissionException :
|
||||
BwfException("WiFi-Direct requires permissions to work properly. Please grant the permissions.")
|
|
@ -1,5 +0,0 @@
|
|||
package com.faraphel.tasks_valider.connectivity.bwf.error
|
||||
|
||||
class BwfRemoveGroupException (
|
||||
reason: Int
|
||||
) : BwfException("Could not remove the group : $reason")
|
|
@ -1,13 +1,11 @@
|
|||
package com.faraphel.tasks_valider.connectivity.task
|
||||
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import com.faraphel.tasks_valider.database.api.client.TaskEntityHttpClient
|
||||
import com.faraphel.tasks_valider.database.api.client.entities.*
|
||||
|
||||
|
||||
/**
|
||||
* A client to handle the room connection.
|
||||
* A client to handle the room connection and access the API
|
||||
* @param address the address of the server
|
||||
* @param port the port of the server
|
||||
* @param baseCookies list of cookies to use (optional)
|
||||
|
@ -17,84 +15,15 @@ class TaskClient(
|
|||
private val port: Int,
|
||||
private val baseCookies: List<okhttp3.Cookie> = listOf()
|
||||
) {
|
||||
private val baseUrl = "http://$address:$port"
|
||||
private val client = OkHttpClient().newBuilder().cookieJar(
|
||||
// TODO(Faraphel): should be moved into another object
|
||||
object : okhttp3.CookieJar {
|
||||
private val cookies = baseCookies.toMutableList() ///< list of cookies
|
||||
private val httpClient = TaskEntityHttpClient(address, port, baseCookies)
|
||||
|
||||
override fun loadForRequest(url: HttpUrl): List<okhttp3.Cookie> {
|
||||
return this.cookies
|
||||
}
|
||||
override fun saveFromResponse(url: HttpUrl, cookies: List<okhttp3.Cookie>) {
|
||||
this.cookies.addAll(cookies)
|
||||
}
|
||||
}
|
||||
).build()
|
||||
|
||||
// TODO(Faraphel): automatically convert content to the correct type ?
|
||||
|
||||
/**
|
||||
* Return a basic request to the server
|
||||
* @param endpoint the endpoint of the server
|
||||
*/
|
||||
private fun baseRequestBuilder(endpoint: String): okhttp3.Request.Builder =
|
||||
okhttp3.Request.Builder().url("$baseUrl/$endpoint")
|
||||
|
||||
/**
|
||||
* Run a HEAD request
|
||||
* @param endpoint the endpoint of the server
|
||||
*/
|
||||
fun head(endpoint: String): okhttp3.Request =
|
||||
this.baseRequestBuilder(endpoint).head().build()
|
||||
|
||||
/**
|
||||
* Run a GET request
|
||||
* @param endpoint the endpoint of the server
|
||||
*/
|
||||
fun get(endpoint: String): okhttp3.Response =
|
||||
this.client.newCall(
|
||||
this.baseRequestBuilder(endpoint)
|
||||
.get()
|
||||
.build()
|
||||
).execute()
|
||||
|
||||
/**
|
||||
* Run a POST request
|
||||
* @param endpoint the endpoint of the server
|
||||
* @param content the content of the request
|
||||
* @param type the type of the content
|
||||
*/
|
||||
fun post(endpoint: String, content: String, type: String = "text/plain"): okhttp3.Response =
|
||||
this.client.newCall(
|
||||
this.baseRequestBuilder(endpoint)
|
||||
.post(content.toRequestBody(type.toMediaType()))
|
||||
.build()
|
||||
).execute()
|
||||
|
||||
/**
|
||||
* Run a PATCH request
|
||||
* @param endpoint the endpoint of the server
|
||||
* @param content the content of the request
|
||||
* @param type the type of the content
|
||||
*/
|
||||
fun patch(endpoint: String, content: String, type: String = "text/plain"): okhttp3.Response =
|
||||
this.client.newCall(
|
||||
this.baseRequestBuilder(endpoint)
|
||||
.patch(content.toRequestBody(type.toMediaType()))
|
||||
.build()
|
||||
).execute()
|
||||
|
||||
/**
|
||||
* Run a DELETE request
|
||||
* @param endpoint the endpoint of the server
|
||||
* @param content the content of the request
|
||||
* @param type the type of the content
|
||||
*/
|
||||
fun delete(endpoint: String, content: String, type: String = "text/plain"): okhttp3.Response =
|
||||
this.client.newCall(
|
||||
this.baseRequestBuilder(endpoint)
|
||||
.delete(content.toRequestBody(type.toMediaType()))
|
||||
.build()
|
||||
).execute()
|
||||
val clientApi = ClassClientApi(httpClient)
|
||||
val personApi = PersonClientApi(httpClient)
|
||||
val sessionApi = SessionClientApi(httpClient)
|
||||
val subjectApi = SubjectClientApi(httpClient)
|
||||
val taskApi = TaskClientApi(httpClient)
|
||||
val validationApi = ValidationClientApi(httpClient)
|
||||
|
||||
val relationClassPersonApi = RelationClassPersonClientApi(httpClient)
|
||||
val relationPersonSessionSubjectApi = RelationPersonSessionSubjectClientApi(httpClient)
|
||||
}
|
|
@ -1,45 +1,51 @@
|
|||
package com.faraphel.tasks_valider.connectivity.task
|
||||
|
||||
import com.faraphel.tasks_valider.connectivity.task.api.TaskSessionManagerApi
|
||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskRole
|
||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskSession
|
||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskSessionManager
|
||||
import com.faraphel.tasks_valider.database.TaskDatabase
|
||||
import com.faraphel.tasks_valider.database.api.TaskDatabaseApi
|
||||
import com.faraphel.tasks_valider.database.api.server.DatabaseApi
|
||||
import com.faraphel.tasks_valider.database.entities.PersonEntity
|
||||
import com.faraphel.tasks_valider.database.entities.SessionEntity
|
||||
import fi.iki.elonen.NanoHTTPD
|
||||
|
||||
|
||||
/**
|
||||
* A server to handle the task API to allow clients to interact with the database.
|
||||
* @param port the port of the server
|
||||
* @param database the database to interact with
|
||||
* @param port the port of the server.
|
||||
* @param database the database to interact with.
|
||||
* @param session the current session.
|
||||
* @param adminPersonEntity the person that represent the host of the session.
|
||||
*/
|
||||
class TaskServer(
|
||||
private val port: Int,
|
||||
private val database: TaskDatabase
|
||||
private val database: TaskDatabase,
|
||||
private val session: SessionEntity,
|
||||
private val adminPersonEntity: PersonEntity,
|
||||
) : NanoHTTPD(port) {
|
||||
companion object {
|
||||
private val TASK_SESSION_ADMIN = TaskSession( ///< the admin default session
|
||||
role = TaskRole.ADMIN
|
||||
)
|
||||
private val sessionManager = TaskSessionManager(adminPersonEntity) ///< the session manager
|
||||
private val databaseApi = DatabaseApi(this.database, session) ///< the api of the database
|
||||
private val sessionManagerApi = TaskSessionManagerApi(this.sessionManager, this.database) ///< the api of the session manager
|
||||
|
||||
/**
|
||||
* Get the admin person entity
|
||||
* @return the admin person entity
|
||||
*/
|
||||
fun getAdminPersonEntity(): PersonEntity {
|
||||
return this.sessionManager.getAdminPersonEntity()
|
||||
}
|
||||
|
||||
private val sessionManager = TaskSessionManager() ///< the session manager
|
||||
private val adminSessionId = this.sessionManager.newSessionData(TASK_SESSION_ADMIN) ///< default admin session id
|
||||
|
||||
private val sessionManagerApi = TaskSessionManagerApi(this.sessionManager) ///< the api of the session manager
|
||||
private val databaseApi = TaskDatabaseApi(this.database) ///< the api of the database
|
||||
|
||||
/**
|
||||
* Return a new client that can be used by the admin
|
||||
* @return the client
|
||||
*/
|
||||
fun getClientAdmin(): TaskClient {
|
||||
fun getAdminClient(): TaskClient {
|
||||
// create the session cookie for the admin
|
||||
val cookieSession = okhttp3.Cookie.Builder()
|
||||
.domain("localhost")
|
||||
.name("sessionId")
|
||||
.value(adminSessionId)
|
||||
.name("sessionToken")
|
||||
.value(this.sessionManager.adminSessionToken)
|
||||
.build()
|
||||
|
||||
// create a new client
|
||||
return TaskClient(
|
||||
"localhost",
|
||||
|
@ -54,7 +60,8 @@ class TaskServer(
|
|||
*/
|
||||
override fun serve(httpSession: IHTTPSession): Response {
|
||||
// get the session data of the client
|
||||
val taskSession = this.sessionManager.getOrCreateSessionData(httpSession)
|
||||
val taskSessionData = this.sessionManager.getSessionData(httpSession)
|
||||
val taskSession = taskSessionData?.second
|
||||
|
||||
// parse the url
|
||||
val uri: String = httpSession.uri.trim('/')
|
||||
|
@ -69,11 +76,11 @@ class TaskServer(
|
|||
)
|
||||
|
||||
// get the response from the correct part of the application
|
||||
val response = when (requestType) {
|
||||
return when (requestType) {
|
||||
// session requests
|
||||
"sessions" -> this.sessionManagerApi.handleRequest(taskSession, httpSession, path)
|
||||
// entities requests
|
||||
"entities" -> return this.databaseApi.handleRequest(taskSession, httpSession, path)
|
||||
"entities" -> this.databaseApi.handleRequest(taskSession, httpSession, path)
|
||||
// invalid requests
|
||||
else ->
|
||||
newFixedLengthResponse(
|
||||
|
@ -82,9 +89,6 @@ class TaskServer(
|
|||
"Unknown request type"
|
||||
)
|
||||
}
|
||||
|
||||
// wrap additional information in the response
|
||||
return this.sessionManager.responseSetSessionData(response, httpSession.cookies)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package com.faraphel.tasks_valider.connectivity.task.api
|
||||
|
||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskPermission
|
||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskRole
|
||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskSession
|
||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskSessionManager
|
||||
import com.google.gson.Gson
|
||||
import com.faraphel.tasks_valider.database.TaskDatabase
|
||||
import com.faraphel.tasks_valider.utils.parser
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import fi.iki.elonen.NanoHTTPD
|
||||
|
||||
|
@ -12,9 +12,10 @@ import fi.iki.elonen.NanoHTTPD
|
|||
/**
|
||||
* the HTTP API for the session manager
|
||||
*/
|
||||
class TaskSessionManagerApi(private val sessionManager: TaskSessionManager) {
|
||||
private val jsonParser = Gson() ///< the json parser
|
||||
|
||||
class TaskSessionManagerApi(
|
||||
private val sessionManager: TaskSessionManager,
|
||||
private val database: TaskDatabase
|
||||
) {
|
||||
/**
|
||||
* Handle a HTTP Api request
|
||||
* @param taskSession the data of the client session
|
||||
|
@ -22,109 +23,118 @@ class TaskSessionManagerApi(private val sessionManager: TaskSessionManager) {
|
|||
* @param path the path of the request
|
||||
*/
|
||||
fun handleRequest(
|
||||
taskSession: TaskSession,
|
||||
taskSession: TaskSession?,
|
||||
httpSession: NanoHTTPD.IHTTPSession,
|
||||
path: MutableList<String>,
|
||||
): NanoHTTPD.Response {
|
||||
// get the target session id
|
||||
val targetSessionId = path.removeFirstOrNull()
|
||||
val action = path.removeFirstOrNull()
|
||||
|
||||
return if (targetSessionId == null) {
|
||||
// no specific session targeted
|
||||
this.handleRequestGeneric(taskSession, httpSession)
|
||||
} else {
|
||||
// a specific session is targeted
|
||||
this.handleRequestSpecific(taskSession, httpSession, targetSessionId)
|
||||
return when (action) {
|
||||
"self" -> this.handleRequestSelf(taskSession, httpSession, path)
|
||||
"all" -> this.handleRequestAll(taskSession, httpSession, path)
|
||||
else ->
|
||||
NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.BAD_REQUEST,
|
||||
"text/plain",
|
||||
"Unknown action"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a request with no specific session targeted
|
||||
* Handle an HTTP Api request about the user own session
|
||||
*/
|
||||
private fun handleRequestGeneric(
|
||||
taskSession: TaskSession,
|
||||
fun handleRequestSelf(
|
||||
taskSession: TaskSession?,
|
||||
httpSession: NanoHTTPD.IHTTPSession,
|
||||
path: MutableList<String>,
|
||||
): NanoHTTPD.Response {
|
||||
when (httpSession.method) {
|
||||
// get all the session data
|
||||
// get the session data
|
||||
NanoHTTPD.Method.GET -> {
|
||||
// check the permission of the session
|
||||
if (taskSession.role.permissions.contains(TaskPermission.READ))
|
||||
// check if the session is valid
|
||||
if (taskSession == null)
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.FORBIDDEN,
|
||||
NanoHTTPD.Response.Status.UNAUTHORIZED,
|
||||
"text/plain",
|
||||
"Forbidden"
|
||||
"No session"
|
||||
)
|
||||
|
||||
// return the session data
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.OK,
|
||||
"application/json",
|
||||
jsonParser.toJson(taskSession)
|
||||
parser.toJson(taskSession)
|
||||
)
|
||||
}
|
||||
// other action are limited
|
||||
else -> {
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.METHOD_NOT_ALLOWED,
|
||||
"text/plain",
|
||||
"Unknown method"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleRequestSpecific(
|
||||
taskSession: TaskSession,
|
||||
httpSession: NanoHTTPD.IHTTPSession,
|
||||
targetSessionId: String,
|
||||
): NanoHTTPD.Response {
|
||||
when (httpSession.method) {
|
||||
// change a specific client session data
|
||||
// connect the user to the session
|
||||
NanoHTTPD.Method.POST -> {
|
||||
// check the permission of the session
|
||||
if (taskSession.role.permissions.contains(TaskPermission.ADMIN))
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.FORBIDDEN,
|
||||
"text/plain",
|
||||
"You are not allowed to update a session"
|
||||
)
|
||||
|
||||
// parse the content of the request
|
||||
val targetSession = jsonParser.fromJson(
|
||||
// get the user identifiers
|
||||
val identifiers: Map<String, String> = parser.fromJson(
|
||||
httpSession.inputStream.bufferedReader().readText(),
|
||||
TaskSession::class.java
|
||||
object : TypeToken<Map<String, String>>() {}.type
|
||||
)
|
||||
|
||||
// update the session
|
||||
this.sessionManager.setSessionData(targetSessionId, targetSession)
|
||||
|
||||
// success message
|
||||
// check for the id
|
||||
if (!identifiers.contains("id"))
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.BAD_REQUEST,
|
||||
"text/plain",
|
||||
"Missing id"
|
||||
)
|
||||
|
||||
// get the id of the user (if invalid, return an error)
|
||||
val personId: Long = identifiers["id"]!!.toLongOrNull()
|
||||
?: return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.BAD_REQUEST,
|
||||
"text/plain",
|
||||
"Invalid id"
|
||||
)
|
||||
|
||||
// check for the password
|
||||
if (!identifiers.contains("password"))
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.BAD_REQUEST,
|
||||
"text/plain",
|
||||
"Missing password"
|
||||
)
|
||||
|
||||
// check if the identifiers are correct
|
||||
val person = this.database.personDao().getById(personId)
|
||||
?: return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.BAD_REQUEST,
|
||||
"text/plain",
|
||||
"No person with this id"
|
||||
)
|
||||
|
||||
// check if the password is correct
|
||||
if (!person.checkPassword(identifiers["password"]!!))
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.UNAUTHORIZED,
|
||||
"text/plain",
|
||||
"Invalid password"
|
||||
)
|
||||
|
||||
// create a new session for the userJHH
|
||||
val (sessionToken, session) = this.sessionManager.newSessionData(person)
|
||||
|
||||
// create the response
|
||||
val response = NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.OK,
|
||||
"text/plain",
|
||||
"Session updated"
|
||||
)
|
||||
}
|
||||
// delete the session
|
||||
NanoHTTPD.Method.DELETE -> {
|
||||
// check the permission of the session
|
||||
if (taskSession.role.permissions.contains(TaskPermission.ADMIN))
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.FORBIDDEN,
|
||||
"text/plain",
|
||||
"You are not allowed to delete a session"
|
||||
|
||||
// set the session token in the cookies
|
||||
this.sessionManager.responseSetSessionData(
|
||||
response,
|
||||
httpSession.cookies,
|
||||
sessionToken
|
||||
)
|
||||
|
||||
// delete the target session
|
||||
this.sessionManager.deleteSessionData(targetSessionId)
|
||||
|
||||
// success message
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.OK,
|
||||
"text/plain",
|
||||
"Session deleted"
|
||||
)
|
||||
// return the response
|
||||
return response
|
||||
}
|
||||
// ignore other methods
|
||||
else -> {
|
||||
|
@ -136,4 +146,86 @@ class TaskSessionManagerApi(private val sessionManager: TaskSessionManager) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an HTTP Api request about all the sessions
|
||||
*/
|
||||
private fun handleRequestAll(
|
||||
taskSession: TaskSession?,
|
||||
httpSession: NanoHTTPD.IHTTPSession,
|
||||
path: MutableList<String>,
|
||||
): NanoHTTPD.Response {
|
||||
// check if the session is valid
|
||||
if (taskSession == null)
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.UNAUTHORIZED,
|
||||
"text/plain",
|
||||
"No session"
|
||||
)
|
||||
|
||||
// check the permission of the session
|
||||
if (taskSession.person.role.permissions.contains(TaskPermission.ADMIN))
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.FORBIDDEN,
|
||||
"text/plain",
|
||||
"You are not allowed to update a session"
|
||||
)
|
||||
|
||||
when (httpSession.method) {
|
||||
// change a specific client session data
|
||||
NanoHTTPD.Method.POST -> {
|
||||
// parse the content of the request
|
||||
val targetSession = parser.fromJson(
|
||||
httpSession.inputStream.bufferedReader().readText(),
|
||||
TaskSession::class.java
|
||||
)
|
||||
|
||||
val targetSessionToken = path.removeFirstOrNull()
|
||||
?: return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.BAD_REQUEST,
|
||||
"text/plain",
|
||||
"Missing session token"
|
||||
)
|
||||
|
||||
// update the session
|
||||
this.sessionManager.updateSessionData(targetSessionToken, targetSession)
|
||||
|
||||
// success message
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.OK,
|
||||
"text/plain",
|
||||
"Session updated"
|
||||
)
|
||||
}
|
||||
|
||||
// delete the session
|
||||
NanoHTTPD.Method.DELETE -> {
|
||||
val targetSessionToken = path.removeFirstOrNull()
|
||||
?: return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.BAD_REQUEST,
|
||||
"text/plain",
|
||||
"Missing session token"
|
||||
)
|
||||
|
||||
// delete the target session
|
||||
this.sessionManager.deleteSessionData(targetSessionToken)
|
||||
|
||||
// success message
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.OK,
|
||||
"text/plain",
|
||||
"Session deleted"
|
||||
)
|
||||
}
|
||||
|
||||
// ignore other methods
|
||||
else -> {
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.METHOD_NOT_ALLOWED,
|
||||
"text/plain",
|
||||
"Invalid method"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
package com.faraphel.tasks_valider.connectivity.task.session
|
||||
|
||||
import com.faraphel.tasks_valider.database.entities.PersonEntity
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
||||
/**
|
||||
* store the data of a session in the task system
|
||||
* @param role the role accorded to the session
|
||||
*/
|
||||
@Serializable
|
||||
data class TaskSession(
|
||||
var role: TaskRole = TaskRole.STUDENT
|
||||
val person: PersonEntity,
|
||||
)
|
|
@ -1,7 +1,6 @@
|
|||
package com.faraphel.tasks_valider.connectivity.task.session
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.faraphel.tasks_valider.database.entities.PersonEntity
|
||||
import fi.iki.elonen.NanoHTTPD
|
||||
import java.util.*
|
||||
|
||||
|
@ -9,21 +8,37 @@ import java.util.*
|
|||
/**
|
||||
* The manager for the session system
|
||||
*/
|
||||
class TaskSessionManager {
|
||||
class TaskSessionManager(///< the admin person entity
|
||||
private val adminPersonEntity: PersonEntity
|
||||
) {
|
||||
private val sessions = mutableMapOf<String, TaskSession>() ///< sessions specific data
|
||||
|
||||
private val adminPersonData = this.newSessionData(this.adminPersonEntity) ///< the session of the admin
|
||||
val adminSessionToken = this.adminPersonData.first ///< the session token of the admin
|
||||
val adminSession = this.adminPersonData.second ///< the session of the admin
|
||||
|
||||
/**
|
||||
* Get the admin person entity
|
||||
* @return the admin person entity
|
||||
*/
|
||||
fun getAdminPersonEntity(): PersonEntity {
|
||||
return this.adminPersonEntity
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new session
|
||||
* @param session the data for the session (optional)
|
||||
* @param sessionId the session id to use (optional)
|
||||
* @return a new session identifier
|
||||
* @param person the person for the session (optional)
|
||||
* @return a new session identifier and the corresponding session
|
||||
*/
|
||||
fun newSessionData(
|
||||
session: TaskSession = TaskSession(),
|
||||
sessionId: String = UUID.randomUUID().toString()
|
||||
): String {
|
||||
this.sessions[sessionId] = session
|
||||
return sessionId
|
||||
fun newSessionData(person: PersonEntity): Pair<String, TaskSession> {
|
||||
// create a new session token that is a secret random string
|
||||
val sessionToken: String = UUID.randomUUID().toString()
|
||||
// create a new session
|
||||
val session = TaskSession(person)
|
||||
// store the session
|
||||
this.sessions[sessionToken] = session
|
||||
// return the session data
|
||||
return Pair(sessionToken, session)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -31,54 +46,60 @@ class TaskSessionManager {
|
|||
* @param httpSession the HTTP session
|
||||
* @return the session data
|
||||
*/
|
||||
fun getSessionData(httpSession: NanoHTTPD.IHTTPSession): TaskSession? {
|
||||
val sessionId = httpSession.cookies.read("sessionId") ?: return null
|
||||
val sessionData = this.getSessionData(sessionId)
|
||||
fun getSessionData(httpSession: NanoHTTPD.IHTTPSession): Pair<String, TaskSession>? {
|
||||
// get the session token from the cookies
|
||||
val sessionToken = httpSession.cookies.read("sessionToken") ?: return null
|
||||
// get the session data
|
||||
val sessionData = this.getSessionData(sessionToken)
|
||||
// return the session data
|
||||
return sessionData
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data from a session identifier
|
||||
* @param sessionId the identifier of the session
|
||||
* @param sessionToken the identifier of the session
|
||||
* @return the session data
|
||||
*/
|
||||
fun getSessionData(sessionId: String): TaskSession? {
|
||||
return this.sessions[sessionId]
|
||||
fun getSessionData(sessionToken: String): Pair<String, TaskSession>? {
|
||||
// get the session data
|
||||
val session = this.sessions[sessionToken]
|
||||
?: return null
|
||||
// return the session data
|
||||
return Pair(sessionToken, session)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the data of a session
|
||||
* @param sessionId the identifier of the session
|
||||
* @param session the session data
|
||||
* Update the session data
|
||||
* @param sessionToken the identifier of the session
|
||||
* @param session the new session data
|
||||
*/
|
||||
fun setSessionData(sessionId: String, session: TaskSession) {
|
||||
this.sessions[sessionId] = session
|
||||
fun updateSessionData(sessionToken: String, session: TaskSession) {
|
||||
// update the session
|
||||
this.sessions[sessionToken] = session
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a session
|
||||
* @param sessionId the identifier of the session
|
||||
* @param sessionToken the identifier of the session
|
||||
*/
|
||||
fun deleteSessionData(sessionId: String): TaskSession? {
|
||||
return this.sessions.remove(sessionId)
|
||||
fun deleteSessionData(sessionToken: String): Pair<String, TaskSession>? {
|
||||
// remove the session
|
||||
val session = this.sessions.remove(sessionToken) ?:
|
||||
return null
|
||||
// return the session data
|
||||
return Pair(sessionToken, session)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data from a http session. If it does not exist, create it.
|
||||
* @param httpSession the HTTP session
|
||||
*/
|
||||
fun getOrCreateSessionData(httpSession: NanoHTTPD.IHTTPSession): TaskSession {
|
||||
fun getOrCreateSessionData(httpSession: NanoHTTPD.IHTTPSession, person: PersonEntity): Pair<String, TaskSession> {
|
||||
// try to get the session directly
|
||||
var session = this.getSessionData(httpSession)
|
||||
|
||||
// if the session does not exist, create it
|
||||
if (session == null) {
|
||||
val sessionId = this.newSessionData()
|
||||
session = this.getSessionData(sessionId)!!
|
||||
}
|
||||
|
||||
// return the session
|
||||
return session
|
||||
val sessionData = this.getSessionData(httpSession)
|
||||
?: this.newSessionData(person)
|
||||
// return the session data
|
||||
return sessionData
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -88,10 +109,11 @@ class TaskSessionManager {
|
|||
*/
|
||||
fun responseSetSessionData(
|
||||
response: NanoHTTPD.Response,
|
||||
cookies: NanoHTTPD.CookieHandler
|
||||
cookies: NanoHTTPD.CookieHandler,
|
||||
sessionToken: String
|
||||
): NanoHTTPD.Response {
|
||||
// update the cookie of the user
|
||||
cookies.set(NanoHTTPD.Cookie("sessionId", this.newSessionData()))
|
||||
cookies.set(NanoHTTPD.Cookie("sessionToken", sessionToken))
|
||||
// load them in the response
|
||||
cookies.unloadQueue(response)
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ package com.faraphel.tasks_valider.database
|
|||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.TypeConverters
|
||||
import com.faraphel.tasks_valider.database.converters.InstantConverter
|
||||
import com.faraphel.tasks_valider.utils.converters.InstantConverter
|
||||
import com.faraphel.tasks_valider.database.dao.*
|
||||
import com.faraphel.tasks_valider.database.entities.*
|
||||
|
||||
|
@ -22,6 +22,7 @@ import com.faraphel.tasks_valider.database.entities.*
|
|||
ValidationEntity::class,
|
||||
|
||||
RelationClassPersonEntity::class,
|
||||
RelationPersonSessionSubjectEntity::class,
|
||||
],
|
||||
version = 1
|
||||
)
|
||||
|
@ -37,4 +38,5 @@ abstract class TaskDatabase: RoomDatabase() {
|
|||
abstract fun validationDao(): ValidationDao
|
||||
|
||||
abstract fun relationClassPersonDao(): RelationClassPersonDao
|
||||
abstract fun relationPersonSessionSubjectDao(): RelationPersonSessionSubjectDao
|
||||
}
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
package com.faraphel.tasks_valider.database.api.client
|
||||
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
|
||||
/**
|
||||
* An HTTP client to handle the room connection.
|
||||
* @param address the address of the server
|
||||
* @param port the port of the server
|
||||
* @param baseCookies list of cookies to use (optional)
|
||||
*/
|
||||
class TaskEntityHttpClient(
|
||||
private val address: String,
|
||||
private val port: Int,
|
||||
private val baseCookies: List<okhttp3.Cookie> = listOf()
|
||||
) {
|
||||
// the base url for the server
|
||||
private val baseUrl = "http://$address:$port"
|
||||
// the HTTP client
|
||||
private val client = OkHttpClient().newBuilder()
|
||||
.cookieJar(
|
||||
object : okhttp3.CookieJar {
|
||||
private val cookies = baseCookies.toMutableList() ///< list of cookies
|
||||
override fun loadForRequest(url: HttpUrl) = this.cookies
|
||||
override fun saveFromResponse(url: HttpUrl, cookies: List<okhttp3.Cookie>) { this.cookies.addAll(cookies) }
|
||||
}
|
||||
)
|
||||
.callTimeout(30.seconds)
|
||||
.build()
|
||||
|
||||
/**
|
||||
* Return a basic request to the server
|
||||
* @param endpoint the endpoint of the server
|
||||
*/
|
||||
private fun baseRequestBuilder(endpoint: String): okhttp3.Request.Builder =
|
||||
okhttp3.Request.Builder().url("$baseUrl/$endpoint")
|
||||
|
||||
/**
|
||||
* Run a HEAD request
|
||||
* @param endpoint the endpoint of the server
|
||||
*/
|
||||
fun head(endpoint: String): okhttp3.Request =
|
||||
this.baseRequestBuilder(endpoint).head().build()
|
||||
|
||||
/**
|
||||
* Run a GET request
|
||||
* @param endpoint the endpoint of the server
|
||||
*/
|
||||
fun get(endpoint: String): okhttp3.Response =
|
||||
this.client.newCall(
|
||||
this.baseRequestBuilder(endpoint)
|
||||
.get()
|
||||
.build()
|
||||
).execute()
|
||||
|
||||
/**
|
||||
* Run a POST request
|
||||
* @param endpoint the endpoint of the server
|
||||
* @param content the content of the request
|
||||
* @param type the type of the content
|
||||
*/
|
||||
fun post(endpoint: String, content: String, type: String = "text/plain"): okhttp3.Response =
|
||||
this.client.newCall(
|
||||
this.baseRequestBuilder(endpoint)
|
||||
.post(content.toRequestBody(type.toMediaType()))
|
||||
.build()
|
||||
).execute()
|
||||
|
||||
/**
|
||||
* Run a PATCH request
|
||||
* @param endpoint the endpoint of the server
|
||||
* @param content the content of the request
|
||||
* @param type the type of the content
|
||||
*/
|
||||
fun patch(endpoint: String, content: String, type: String = "text/plain"): okhttp3.Response =
|
||||
this.client.newCall(
|
||||
this.baseRequestBuilder(endpoint)
|
||||
.patch(content.toRequestBody(type.toMediaType()))
|
||||
.build()
|
||||
).execute()
|
||||
|
||||
/**
|
||||
* Run a DELETE request
|
||||
* @param endpoint the endpoint of the server
|
||||
* @param content the content of the request
|
||||
* @param type the type of the content
|
||||
*/
|
||||
fun delete(endpoint: String, content: String, type: String = "text/plain"): okhttp3.Response =
|
||||
this.client.newCall(
|
||||
this.baseRequestBuilder(endpoint)
|
||||
.delete(content.toRequestBody(type.toMediaType()))
|
||||
.build()
|
||||
).execute()
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package com.faraphel.tasks_valider.database.api.client.entities
|
||||
|
||||
import com.faraphel.tasks_valider.database.api.client.TaskEntityHttpClient
|
||||
import com.faraphel.tasks_valider.database.api.client.entities.base.BaseClientApi
|
||||
import com.faraphel.tasks_valider.database.entities.ClassEntity
|
||||
|
||||
|
||||
class ClassClientApi(
|
||||
client: TaskEntityHttpClient,
|
||||
) : BaseClientApi<ClassEntity>(
|
||||
client,
|
||||
ClassEntity::class
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
package com.faraphel.tasks_valider.database.api.client.entities
|
||||
|
||||
import com.faraphel.tasks_valider.database.api.client.TaskEntityHttpClient
|
||||
import com.faraphel.tasks_valider.database.api.client.entities.base.BaseClientApi
|
||||
import com.faraphel.tasks_valider.database.entities.PersonEntity
|
||||
|
||||
|
||||
class PersonClientApi(
|
||||
client: TaskEntityHttpClient,
|
||||
) : BaseClientApi<PersonEntity>(
|
||||
client,
|
||||
PersonEntity::class
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
package com.faraphel.tasks_valider.database.api.client.entities
|
||||
|
||||
import com.faraphel.tasks_valider.database.api.client.TaskEntityHttpClient
|
||||
import com.faraphel.tasks_valider.database.api.client.entities.base.BaseClientApi
|
||||
import com.faraphel.tasks_valider.database.entities.RelationClassPersonEntity
|
||||
|
||||
|
||||
class RelationClassPersonClientApi(
|
||||
client: TaskEntityHttpClient,
|
||||
) : BaseClientApi<RelationClassPersonEntity>(
|
||||
client,
|
||||
RelationClassPersonEntity::class
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
package com.faraphel.tasks_valider.database.api.client.entities
|
||||
|
||||
import com.faraphel.tasks_valider.database.api.client.TaskEntityHttpClient
|
||||
import com.faraphel.tasks_valider.database.api.client.entities.base.BaseClientApi
|
||||
import com.faraphel.tasks_valider.database.entities.RelationPersonSessionSubjectEntity
|
||||
|
||||
|
||||
class RelationPersonSessionSubjectClientApi(
|
||||
client: TaskEntityHttpClient,
|
||||
) : BaseClientApi<RelationPersonSessionSubjectEntity>(
|
||||
client,
|
||||
RelationPersonSessionSubjectEntity::class
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
package com.faraphel.tasks_valider.database.api.client.entities
|
||||
|
||||
import com.faraphel.tasks_valider.database.api.client.TaskEntityHttpClient
|
||||
import com.faraphel.tasks_valider.database.api.client.entities.base.BaseClientApi
|
||||
import com.faraphel.tasks_valider.database.entities.SessionEntity
|
||||
|
||||
|
||||
class SessionClientApi(
|
||||
client: TaskEntityHttpClient,
|
||||
) : BaseClientApi<SessionEntity>(
|
||||
client,
|
||||
SessionEntity::class
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
package com.faraphel.tasks_valider.database.api.client.entities
|
||||
|
||||
import com.faraphel.tasks_valider.database.api.client.TaskEntityHttpClient
|
||||
import com.faraphel.tasks_valider.database.api.client.entities.base.BaseClientApi
|
||||
import com.faraphel.tasks_valider.database.entities.SubjectEntity
|
||||
|
||||
|
||||
class SubjectClientApi(
|
||||
client: TaskEntityHttpClient,
|
||||
) : BaseClientApi<SubjectEntity>(
|
||||
client,
|
||||
SubjectEntity::class
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
package com.faraphel.tasks_valider.database.api.client.entities
|
||||
|
||||
import com.faraphel.tasks_valider.database.api.client.TaskEntityHttpClient
|
||||
import com.faraphel.tasks_valider.database.api.client.entities.base.BaseClientApi
|
||||
import com.faraphel.tasks_valider.database.entities.TaskEntity
|
||||
|
||||
|
||||
class TaskClientApi(
|
||||
client: TaskEntityHttpClient,
|
||||
) : BaseClientApi<TaskEntity>(
|
||||
client,
|
||||
TaskEntity::class
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
package com.faraphel.tasks_valider.database.api.client.entities
|
||||
|
||||
import com.faraphel.tasks_valider.database.api.client.TaskEntityHttpClient
|
||||
import com.faraphel.tasks_valider.database.api.client.entities.base.BaseClientApi
|
||||
import com.faraphel.tasks_valider.database.entities.ValidationEntity
|
||||
|
||||
|
||||
class ValidationClientApi(
|
||||
client: TaskEntityHttpClient,
|
||||
) : BaseClientApi<ValidationEntity>(
|
||||
client,
|
||||
ValidationEntity::class
|
||||
)
|
|
@ -0,0 +1,100 @@
|
|||
package com.faraphel.tasks_valider.database.api.client.entities.base;
|
||||
|
||||
import android.util.Log
|
||||
import com.faraphel.tasks_valider.database.api.client.TaskEntityHttpClient
|
||||
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
|
||||
import com.faraphel.tasks_valider.database.entities.error.HttpException
|
||||
import com.faraphel.tasks_valider.utils.parser
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.full.companionObject
|
||||
import kotlin.reflect.full.companionObjectInstance
|
||||
import kotlin.reflect.full.declaredMemberProperties
|
||||
|
||||
|
||||
abstract class BaseClientApi<Entity: BaseEntity>(
|
||||
private val client: TaskEntityHttpClient,
|
||||
private val entityType: KClass<Entity>,
|
||||
) {
|
||||
/**
|
||||
* return the API endpoint for this entity
|
||||
* @return the API endpoint for this entity
|
||||
*/
|
||||
private fun getEndpoint(): String {
|
||||
// get the property for the name of the table
|
||||
val propertyTableName = entityType.companionObject!!.declaredMemberProperties.first { member ->
|
||||
member.name == "TABLE_NAME"
|
||||
}
|
||||
// get the table name by calling the getter of the property
|
||||
val tableName = propertyTableName.getter.call(entityType.companionObjectInstance)
|
||||
// return the endpoint
|
||||
return "entities/${tableName}"
|
||||
}
|
||||
|
||||
/**
|
||||
* return all the entities for that table
|
||||
* @return all the entities for that table
|
||||
* @throws java.io.IOException reading error while parsing request
|
||||
* @throws HttpException error of the request
|
||||
*/
|
||||
fun getAll(): List<Entity> {
|
||||
// try to obtain the list of validations
|
||||
val response = client.get(this.getEndpoint())
|
||||
|
||||
// in case of error, notify it
|
||||
if (!response.isSuccessful)
|
||||
throw HttpException(response.code)
|
||||
|
||||
val data = response.body.string()
|
||||
|
||||
// parse the list of validations
|
||||
return parser.fromJson(
|
||||
data,
|
||||
TypeToken.getParameterized(ArrayList::class.java, entityType.java).type
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* create a new entity in the table
|
||||
* @return the id of the object in the database
|
||||
* @throws java.io.IOException reading error while parsing request
|
||||
* @throws HttpException error of the request
|
||||
*/
|
||||
fun save(entity: Entity): Long {
|
||||
// try to send the serialized entity as json
|
||||
val response = client.post(
|
||||
this.getEndpoint(),
|
||||
parser.toJson(entity),
|
||||
"application/json; charset=utf-8"
|
||||
)
|
||||
|
||||
// in case of error, notify it
|
||||
if (!response.isSuccessful)
|
||||
throw HttpException(response.code)
|
||||
|
||||
// return the id of the object
|
||||
return response.body.string().toLong()
|
||||
}
|
||||
|
||||
/**
|
||||
* delete an entity in the table
|
||||
* @return the number of object deleted in the database
|
||||
* @throws java.io.IOException reading error while parsing request
|
||||
* @throws HttpException error of the request
|
||||
*/
|
||||
fun delete(entity: Entity): Long {
|
||||
// try to delete the object
|
||||
val response = client.delete(
|
||||
this.getEndpoint(),
|
||||
parser.toJson(entity),
|
||||
"application/json; charset=utf-8"
|
||||
)
|
||||
|
||||
// in case of error, notify it
|
||||
if (!response.isSuccessful)
|
||||
throw HttpException(response.code)
|
||||
|
||||
// return the id of the object
|
||||
return response.body.string().toLong()
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
package com.faraphel.tasks_valider.database.api.entities
|
||||
|
||||
import com.faraphel.tasks_valider.database.api.entities.base.BaseJsonApi
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.entities.ClassEntity
|
||||
|
||||
class ClassApi(dao: BaseDao<ClassEntity>) : BaseJsonApi<ClassEntity>(dao)
|
|
@ -1,7 +0,0 @@
|
|||
package com.faraphel.tasks_valider.database.api.entities
|
||||
|
||||
import com.faraphel.tasks_valider.database.api.entities.base.BaseJsonApi
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.entities.PersonEntity
|
||||
|
||||
class PersonApi(dao: BaseDao<PersonEntity>) : BaseJsonApi<PersonEntity>(dao)
|
|
@ -1,7 +0,0 @@
|
|||
package com.faraphel.tasks_valider.database.api.entities
|
||||
|
||||
import com.faraphel.tasks_valider.database.api.entities.base.BaseJsonApi
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.entities.RelationClassPersonEntity
|
||||
|
||||
class RelationClassPersonApi(dao: BaseDao<RelationClassPersonEntity>) : BaseJsonApi<RelationClassPersonEntity>(dao)
|
|
@ -1,7 +0,0 @@
|
|||
package com.faraphel.tasks_valider.database.api.entities
|
||||
|
||||
import com.faraphel.tasks_valider.database.api.entities.base.BaseJsonApi
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.entities.SessionEntity
|
||||
|
||||
class SessionApi(dao: BaseDao<SessionEntity>) : BaseJsonApi<SessionEntity>(dao)
|
|
@ -1,7 +0,0 @@
|
|||
package com.faraphel.tasks_valider.database.api.entities
|
||||
|
||||
import com.faraphel.tasks_valider.database.api.entities.base.BaseJsonApi
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.entities.SubjectEntity
|
||||
|
||||
class SubjectApi(dao: BaseDao<SubjectEntity>) : BaseJsonApi<SubjectEntity>(dao)
|
|
@ -1,7 +0,0 @@
|
|||
package com.faraphel.tasks_valider.database.api.entities
|
||||
|
||||
import com.faraphel.tasks_valider.database.api.entities.base.BaseJsonApi
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.entities.TaskEntity
|
||||
|
||||
class TaskApi(dao: BaseDao<TaskEntity>) : BaseJsonApi<TaskEntity>(dao)
|
|
@ -1,7 +0,0 @@
|
|||
package com.faraphel.tasks_valider.database.api.entities
|
||||
|
||||
import com.faraphel.tasks_valider.database.api.entities.base.BaseJsonApi
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.entities.ValidationEntity
|
||||
|
||||
class ValidationApi(dao: BaseDao<ValidationEntity>) : BaseJsonApi<ValidationEntity>(dao)
|
|
@ -1,72 +0,0 @@
|
|||
package com.faraphel.tasks_valider.database.api.entities.base
|
||||
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import fi.iki.elonen.NanoHTTPD
|
||||
|
||||
/**
|
||||
* A base for the API to handle the database operations.
|
||||
* This is preconfigured to handle JSON data.
|
||||
* @param Entity the entity type to handle
|
||||
*/
|
||||
abstract class BaseJsonApi<Entity: BaseEntity>(private val dao: BaseDao<Entity>) : BaseApi {
|
||||
companion object {
|
||||
private val parser = Gson() ///< The JSON parser
|
||||
}
|
||||
|
||||
private val entityTypeToken: TypeToken<Entity> = object: TypeToken<Entity>() {} ///< the type of the managed entity
|
||||
|
||||
// Requests
|
||||
|
||||
override fun head(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
|
||||
val obj = parser.fromJson<Entity>(
|
||||
session.inputStream.bufferedReader().readText(),
|
||||
this.entityTypeToken.type
|
||||
)
|
||||
val exists = this.dao.exists(obj)
|
||||
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
if (exists) NanoHTTPD.Response.Status.OK else NanoHTTPD.Response.Status.NOT_FOUND,
|
||||
"text/plain",
|
||||
if (exists) "Exists" else "Not found"
|
||||
)
|
||||
}
|
||||
|
||||
override fun get(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.OK,
|
||||
"application/json",
|
||||
parser.toJson(this.dao.getAll())
|
||||
)
|
||||
}
|
||||
|
||||
override fun post(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
|
||||
val obj = parser.fromJson<Entity>(
|
||||
session.inputStream.bufferedReader().readText(),
|
||||
this.entityTypeToken.type
|
||||
)
|
||||
val id = this.dao.insert(obj)
|
||||
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.CREATED,
|
||||
"text/plain",
|
||||
id.toString()
|
||||
)
|
||||
}
|
||||
|
||||
override fun delete(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
|
||||
val obj = parser.fromJson<Entity>(
|
||||
session.inputStream.bufferedReader().readText(),
|
||||
this.entityTypeToken.type
|
||||
)
|
||||
val count = this.dao.delete(obj)
|
||||
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
if (count > 0) NanoHTTPD.Response.Status.OK else NanoHTTPD.Response.Status.NOT_FOUND,
|
||||
"text/plain",
|
||||
count.toString()
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,23 +1,28 @@
|
|||
package com.faraphel.tasks_valider.database.api
|
||||
package com.faraphel.tasks_valider.database.api.server
|
||||
|
||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskPermission
|
||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskSession
|
||||
import com.faraphel.tasks_valider.database.TaskDatabase
|
||||
import com.faraphel.tasks_valider.database.api.entities.*
|
||||
import com.faraphel.tasks_valider.database.api.entities.base.BaseApi
|
||||
import com.faraphel.tasks_valider.database.api.server.entities.base.BaseDatabaseApi
|
||||
import com.faraphel.tasks_valider.database.api.server.entities.*
|
||||
import com.faraphel.tasks_valider.database.entities.*
|
||||
import fi.iki.elonen.NanoHTTPD
|
||||
|
||||
class TaskDatabaseApi(private val database: TaskDatabase) {
|
||||
private val api: Map<String, BaseApi> = mapOf(
|
||||
ClassEntity.TABLE_NAME to ClassApi(this.database.classDao()),
|
||||
PersonEntity.TABLE_NAME to PersonApi(this.database.personDao()),
|
||||
SessionEntity.TABLE_NAME to SessionApi(this.database.sessionDao()),
|
||||
SubjectEntity.TABLE_NAME to SubjectApi(this.database.subjectDao()),
|
||||
TaskEntity.TABLE_NAME to TaskApi(this.database.taskDao()),
|
||||
ValidationEntity.TABLE_NAME to ValidationApi(this.database.validationDao()),
|
||||
|
||||
RelationClassPersonEntity.TABLE_NAME to RelationClassPersonApi(this.database.relationClassPersonDao()),
|
||||
class DatabaseApi(
|
||||
private val database: TaskDatabase,
|
||||
private val session: SessionEntity,
|
||||
) {
|
||||
private val api: Map<String, BaseDatabaseApi> = mapOf(
|
||||
ClassEntity.TABLE_NAME to ClassDatabaseApi(this.database.classDao(), session),
|
||||
PersonEntity.TABLE_NAME to PersonDatabaseApi(this.database.personDao(), session),
|
||||
SessionEntity.TABLE_NAME to SessionDatabaseApi(this.database.sessionDao(), session),
|
||||
SubjectEntity.TABLE_NAME to SubjectDatabaseApi(this.database.subjectDao(), session),
|
||||
TaskEntity.TABLE_NAME to TaskDatabaseApi(database.taskDao(), session),
|
||||
ValidationEntity.TABLE_NAME to ValidationDatabaseApi(this.database.validationDao(), session),
|
||||
|
||||
RelationClassPersonEntity.TABLE_NAME to RelationClassPersonDatabaseApi(this.database.relationClassPersonDao(), session),
|
||||
RelationPersonSessionSubjectEntity.TABLE_NAME to RelationPersonSessionSubjectDatabaseApi(this.database.relationPersonSessionSubjectDao(), session),
|
||||
)
|
||||
|
||||
/**
|
||||
|
@ -27,10 +32,18 @@ class TaskDatabaseApi(private val database: TaskDatabase) {
|
|||
* @param path the path of the request
|
||||
*/
|
||||
fun handleRequest(
|
||||
taskSession: TaskSession,
|
||||
taskSession: TaskSession?,
|
||||
httpSession: NanoHTTPD.IHTTPSession,
|
||||
path: MutableList<String>
|
||||
): NanoHTTPD.Response {
|
||||
// check if the user is authenticated
|
||||
if (taskSession == null)
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.UNAUTHORIZED,
|
||||
"text/plain",
|
||||
"Unauthorized"
|
||||
)
|
||||
|
||||
// get the entity name
|
||||
val entityName = path.removeFirstOrNull()
|
||||
?: return NanoHTTPD.newFixedLengthResponse(
|
||||
|
@ -53,7 +66,7 @@ class TaskDatabaseApi(private val database: TaskDatabase) {
|
|||
// TODO(Faraphel): should only be allowed to read data concerning the current class session
|
||||
NanoHTTPD.Method.HEAD -> {
|
||||
// check the permission of the session
|
||||
if (taskSession.role.permissions.contains(TaskPermission.READ))
|
||||
if (!taskSession.person.role.permissions.contains(TaskPermission.READ))
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.FORBIDDEN,
|
||||
"text/plain",
|
||||
|
@ -65,7 +78,7 @@ class TaskDatabaseApi(private val database: TaskDatabase) {
|
|||
// get the data from the database
|
||||
NanoHTTPD.Method.GET -> {
|
||||
// check the permission of the session
|
||||
if (taskSession.role.permissions.contains(TaskPermission.READ))
|
||||
if (!taskSession.person.role.permissions.contains(TaskPermission.READ))
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.FORBIDDEN,
|
||||
"text/plain",
|
||||
|
@ -77,7 +90,7 @@ class TaskDatabaseApi(private val database: TaskDatabase) {
|
|||
// insert the data into the database
|
||||
NanoHTTPD.Method.POST -> {
|
||||
// check the permission of the session
|
||||
if (taskSession.role.permissions.contains(TaskPermission.WRITE))
|
||||
if (!taskSession.person.role.permissions.contains(TaskPermission.WRITE))
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.FORBIDDEN,
|
||||
"text/plain",
|
||||
|
@ -89,7 +102,7 @@ class TaskDatabaseApi(private val database: TaskDatabase) {
|
|||
// delete the data from the database
|
||||
NanoHTTPD.Method.DELETE -> {
|
||||
// check the permission of the session
|
||||
if (taskSession.role.permissions.contains(TaskPermission.WRITE))
|
||||
if (!taskSession.person.role.permissions.contains(TaskPermission.WRITE))
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.FORBIDDEN,
|
||||
"text/plain",
|
|
@ -0,0 +1,16 @@
|
|||
package com.faraphel.tasks_valider.database.api.server.entities
|
||||
|
||||
import com.faraphel.tasks_valider.database.api.server.entities.base.BaseTaskDatabaseApi
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
|
||||
import com.faraphel.tasks_valider.database.entities.ClassEntity
|
||||
import com.faraphel.tasks_valider.database.entities.SessionEntity
|
||||
|
||||
|
||||
class ClassDatabaseApi(
|
||||
dao: BaseTaskDao<ClassEntity>,
|
||||
session: SessionEntity
|
||||
) : BaseTaskDatabaseApi<ClassEntity>(
|
||||
dao,
|
||||
session,
|
||||
ClassEntity::class.java
|
||||
)
|
|
@ -0,0 +1,16 @@
|
|||
package com.faraphel.tasks_valider.database.api.server.entities
|
||||
|
||||
import com.faraphel.tasks_valider.database.api.server.entities.base.BaseTaskDatabaseApi
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
|
||||
import com.faraphel.tasks_valider.database.entities.PersonEntity
|
||||
import com.faraphel.tasks_valider.database.entities.SessionEntity
|
||||
|
||||
|
||||
class PersonDatabaseApi(
|
||||
dao: BaseTaskDao<PersonEntity>,
|
||||
session: SessionEntity
|
||||
) : BaseTaskDatabaseApi<PersonEntity>(
|
||||
dao,
|
||||
session,
|
||||
PersonEntity::class.java
|
||||
)
|
|
@ -0,0 +1,16 @@
|
|||
package com.faraphel.tasks_valider.database.api.server.entities
|
||||
|
||||
import com.faraphel.tasks_valider.database.api.server.entities.base.BaseTaskDatabaseApi
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
|
||||
import com.faraphel.tasks_valider.database.entities.RelationClassPersonEntity
|
||||
import com.faraphel.tasks_valider.database.entities.SessionEntity
|
||||
|
||||
|
||||
class RelationClassPersonDatabaseApi(
|
||||
dao: BaseTaskDao<RelationClassPersonEntity>,
|
||||
session: SessionEntity
|
||||
) : BaseTaskDatabaseApi<RelationClassPersonEntity>(
|
||||
dao,
|
||||
session,
|
||||
RelationClassPersonEntity::class.java
|
||||
)
|
|
@ -0,0 +1,16 @@
|
|||
package com.faraphel.tasks_valider.database.api.server.entities
|
||||
|
||||
import com.faraphel.tasks_valider.database.api.server.entities.base.BaseTaskDatabaseApi
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
|
||||
import com.faraphel.tasks_valider.database.entities.RelationPersonSessionSubjectEntity
|
||||
import com.faraphel.tasks_valider.database.entities.SessionEntity
|
||||
|
||||
|
||||
class RelationPersonSessionSubjectDatabaseApi(
|
||||
dao: BaseTaskDao<RelationPersonSessionSubjectEntity>,
|
||||
session: SessionEntity
|
||||
) : BaseTaskDatabaseApi<RelationPersonSessionSubjectEntity>(
|
||||
dao,
|
||||
session,
|
||||
RelationPersonSessionSubjectEntity::class.java
|
||||
)
|
|
@ -0,0 +1,15 @@
|
|||
package com.faraphel.tasks_valider.database.api.server.entities
|
||||
|
||||
import com.faraphel.tasks_valider.database.api.server.entities.base.BaseTaskDatabaseApi
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
|
||||
import com.faraphel.tasks_valider.database.entities.SessionEntity
|
||||
|
||||
|
||||
class SessionDatabaseApi(
|
||||
dao: BaseTaskDao<SessionEntity>,
|
||||
session: SessionEntity
|
||||
) : BaseTaskDatabaseApi<SessionEntity>(
|
||||
dao,
|
||||
session,
|
||||
SessionEntity::class.java
|
||||
)
|
|
@ -0,0 +1,16 @@
|
|||
package com.faraphel.tasks_valider.database.api.server.entities
|
||||
|
||||
import com.faraphel.tasks_valider.database.api.server.entities.base.BaseTaskDatabaseApi
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
|
||||
import com.faraphel.tasks_valider.database.entities.SessionEntity
|
||||
import com.faraphel.tasks_valider.database.entities.SubjectEntity
|
||||
|
||||
|
||||
class SubjectDatabaseApi(
|
||||
dao: BaseTaskDao<SubjectEntity>,
|
||||
session: SessionEntity
|
||||
) : BaseTaskDatabaseApi<SubjectEntity>(
|
||||
dao,
|
||||
session,
|
||||
SubjectEntity::class.java
|
||||
)
|
|
@ -0,0 +1,16 @@
|
|||
package com.faraphel.tasks_valider.database.api.server.entities
|
||||
|
||||
import com.faraphel.tasks_valider.database.api.server.entities.base.BaseTaskDatabaseApi
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
|
||||
import com.faraphel.tasks_valider.database.entities.SessionEntity
|
||||
import com.faraphel.tasks_valider.database.entities.TaskEntity
|
||||
|
||||
|
||||
class TaskDatabaseApi(
|
||||
dao: BaseTaskDao<TaskEntity>,
|
||||
session: SessionEntity
|
||||
) : BaseTaskDatabaseApi<TaskEntity>(
|
||||
dao,
|
||||
session,
|
||||
TaskEntity::class.java
|
||||
)
|
|
@ -0,0 +1,16 @@
|
|||
package com.faraphel.tasks_valider.database.api.server.entities
|
||||
|
||||
import com.faraphel.tasks_valider.database.api.server.entities.base.BaseTaskDatabaseApi
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
|
||||
import com.faraphel.tasks_valider.database.entities.SessionEntity
|
||||
import com.faraphel.tasks_valider.database.entities.ValidationEntity
|
||||
|
||||
|
||||
class ValidationDatabaseApi(
|
||||
dao: BaseTaskDao<ValidationEntity>,
|
||||
session: SessionEntity
|
||||
) : BaseTaskDatabaseApi<ValidationEntity>(
|
||||
dao,
|
||||
session,
|
||||
ValidationEntity::class.java
|
||||
)
|
|
@ -1,32 +1,32 @@
|
|||
package com.faraphel.tasks_valider.database.api.entities.base
|
||||
package com.faraphel.tasks_valider.database.api.server.entities.base
|
||||
|
||||
import fi.iki.elonen.NanoHTTPD
|
||||
|
||||
/**
|
||||
* A base for the API to handle the database operations with an HTTP server.
|
||||
*/
|
||||
interface BaseApi {
|
||||
interface BaseDatabaseApi {
|
||||
/**
|
||||
* Handle the HEAD request
|
||||
* This is used to check if a data exists in the database
|
||||
*/
|
||||
fun head(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response
|
||||
fun head(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response
|
||||
|
||||
/**
|
||||
* Handle the GET request
|
||||
* This is used to get data from the database
|
||||
* This is used to get all the data of a table from the database
|
||||
*/
|
||||
fun get(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response
|
||||
fun get(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response
|
||||
|
||||
/**
|
||||
* Handle the POST request
|
||||
* This is used to insert data into the database
|
||||
*/
|
||||
fun post(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response
|
||||
fun post(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response
|
||||
|
||||
/**
|
||||
* Handle the PUT request
|
||||
* This is used to delete data from the database
|
||||
*/
|
||||
fun delete(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response
|
||||
fun delete(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
package com.faraphel.tasks_valider.database.api.server.entities.base
|
||||
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
|
||||
import com.faraphel.tasks_valider.database.entities.SessionEntity
|
||||
import com.faraphel.tasks_valider.utils.getBody
|
||||
import com.faraphel.tasks_valider.utils.parser
|
||||
import fi.iki.elonen.NanoHTTPD
|
||||
|
||||
|
||||
abstract class BaseTaskDatabaseApi<Entity> (
|
||||
private val dao: BaseTaskDao<Entity>,
|
||||
private val session: SessionEntity,
|
||||
private val entityType: Class<Entity>,
|
||||
) : BaseDatabaseApi {
|
||||
/**
|
||||
* Handle an HTTP HEAD request.
|
||||
* Indicate if an object exist in the database.
|
||||
* @param httpSession the current http session to handle.
|
||||
* @return a response indicating if the object does exist.
|
||||
*/
|
||||
override fun head(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
|
||||
// get the content of the request
|
||||
val data = httpSession.getBody()
|
||||
// parse the object
|
||||
val obj = parser.fromJson(data, entityType)
|
||||
// check if the object is in the object accessible from the session
|
||||
val exists = this.dao.getAllBySession(session.id).contains(obj)
|
||||
|
||||
// return the response
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
if (exists) NanoHTTPD.Response.Status.OK else NanoHTTPD.Response.Status.NOT_FOUND,
|
||||
"text/plain",
|
||||
if (exists) "Exists" else "Not found"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an HTTP GET request.
|
||||
* Indicate the content of a table in the database.
|
||||
* @param httpSession the current http session to handle.
|
||||
* @return a response indicating the content of all the objects.
|
||||
*/
|
||||
override fun get(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
|
||||
// return the content of all the objects in the database about this session
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.OK,
|
||||
"application/json",
|
||||
parser.toJson(this.dao.getAllBySession(session.id))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an HTTP POST request.
|
||||
* Create a new object in the database.
|
||||
* @param httpSession the current http session to handle.
|
||||
* @return the id of the newly created object
|
||||
*/
|
||||
override fun post(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
|
||||
// get the content of the request
|
||||
val data = httpSession.getBody()
|
||||
// parse the object
|
||||
val obj = parser.fromJson(data, entityType)
|
||||
// insert it into the database
|
||||
val id = this.dao.insert(obj)
|
||||
|
||||
// return the id of the object created
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.CREATED,
|
||||
"text/plain",
|
||||
id.toString()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an HTTP DELETE request.
|
||||
* Delete an item from the database.
|
||||
* @param httpSession the current http session to handle.
|
||||
* @return the number of object deleted.
|
||||
*/
|
||||
override fun delete(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
|
||||
// get the content of the request
|
||||
val data = httpSession.getBody()
|
||||
// parse the object
|
||||
val obj = parser.fromJson(data, entityType)
|
||||
// delete all the instance of this object in the database
|
||||
val count = this.dao.delete(obj)
|
||||
|
||||
// return the number of corresponding element deleted
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
if (count > 0) NanoHTTPD.Response.Status.OK else NanoHTTPD.Response.Status.NOT_FOUND,
|
||||
"text/plain",
|
||||
count.toString()
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
package com.faraphel.tasks_valider.database.converters
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.room.TypeConverter
|
||||
import java.time.Instant
|
||||
|
||||
class InstantConverter {
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@TypeConverter
|
||||
fun fromTimestamp(value: Long?): Instant? {
|
||||
return value?.let { Instant.ofEpochMilli(it) }
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@TypeConverter
|
||||
fun dateToTimestamp(instant: Instant?): Long? {
|
||||
return instant?.toEpochMilli()
|
||||
}
|
||||
}
|
|
@ -2,22 +2,32 @@ package com.faraphel.tasks_valider.database.dao
|
|||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseCronDao
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseSessionDao
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
|
||||
import com.faraphel.tasks_valider.database.entities.ClassEntity
|
||||
import com.faraphel.tasks_valider.database.entities.PersonEntity
|
||||
import com.faraphel.tasks_valider.database.entities.RelationClassPersonEntity
|
||||
import com.faraphel.tasks_valider.database.entities.SessionEntity
|
||||
|
||||
@Dao
|
||||
interface ClassDao : BaseDao<ClassEntity> {
|
||||
interface ClassDao : BaseTaskDao<ClassEntity> {
|
||||
@Query("SELECT * FROM ${ClassEntity.TABLE_NAME}")
|
||||
override fun getAll(): List<ClassEntity>
|
||||
|
||||
@Query(
|
||||
"SELECT * FROM ${ClassEntity.TABLE_NAME} " +
|
||||
"JOIN ${SessionEntity.TABLE_NAME} " +
|
||||
"ON ${SessionEntity.TABLE_NAME}.class_id = ${ClassEntity.TABLE_NAME}.id " +
|
||||
"WHERE ${SessionEntity.TABLE_NAME}.id = :sessionId"
|
||||
)
|
||||
override fun getAllBySession(sessionId: Long): List<ClassEntity>
|
||||
|
||||
/**
|
||||
* Get the object from its identifier
|
||||
*/
|
||||
@Query("SELECT * FROM ${ClassEntity.TABLE_NAME} WHERE id = :id")
|
||||
fun getById(id: Long): ClassEntity
|
||||
fun getById(id: Long): ClassEntity?
|
||||
|
||||
/**
|
||||
* Get all the sessions this class attended
|
||||
|
@ -31,7 +41,7 @@ interface ClassDao : BaseDao<ClassEntity> {
|
|||
* @param id the id of the class
|
||||
*/
|
||||
@Query(
|
||||
"SELECT * FROM ${PersonEntity.TABLE_NAME} " +
|
||||
"SELECT ${PersonEntity.TABLE_NAME}.* FROM ${PersonEntity.TABLE_NAME} " +
|
||||
"JOIN ${RelationClassPersonEntity.TABLE_NAME} " +
|
||||
"ON ${PersonEntity.TABLE_NAME}.id = ${RelationClassPersonEntity.TABLE_NAME}.student_id " +
|
||||
"WHERE ${RelationClassPersonEntity.TABLE_NAME}.class_id = :id"
|
||||
|
|
|
@ -2,11 +2,13 @@ package com.faraphel.tasks_valider.database.dao
|
|||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseCronDao
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseSessionDao
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
|
||||
import com.faraphel.tasks_valider.database.entities.*
|
||||
|
||||
@Dao
|
||||
interface PersonDao : BaseDao<PersonEntity> {
|
||||
interface PersonDao : BaseTaskDao<PersonEntity> {
|
||||
@Query("SELECT * FROM ${PersonEntity.TABLE_NAME}")
|
||||
override fun getAll(): List<PersonEntity>
|
||||
|
||||
|
@ -14,7 +16,20 @@ interface PersonDao : BaseDao<PersonEntity> {
|
|||
* Get the object from its identifier
|
||||
*/
|
||||
@Query("SELECT * FROM ${PersonEntity.TABLE_NAME} WHERE id = :id")
|
||||
fun getById(id: Long): PersonEntity
|
||||
fun getById(id: Long): PersonEntity?
|
||||
|
||||
@Query(
|
||||
"SELECT * FROM ${PersonEntity.TABLE_NAME} " +
|
||||
"WHERE id IN (" +
|
||||
"SELECT student_id FROM ${RelationClassPersonEntity.TABLE_NAME} " +
|
||||
"JOIN ${ClassEntity.TABLE_NAME} " +
|
||||
"ON ${RelationClassPersonEntity.TABLE_NAME}.class_id = ${ClassEntity.TABLE_NAME}.id " +
|
||||
"JOIN ${SessionEntity.TABLE_NAME} " +
|
||||
"ON ${SessionEntity.TABLE_NAME}.class_id = ${ClassEntity.TABLE_NAME}.id " +
|
||||
"WHERE ${SessionEntity.TABLE_NAME}.id = :sessionId" +
|
||||
")"
|
||||
)
|
||||
override fun getAllBySession(sessionId: Long): List<PersonEntity>
|
||||
|
||||
/**
|
||||
* Allow to get all the classes the person is attending as a student
|
||||
|
|
|
@ -2,22 +2,35 @@ package com.faraphel.tasks_valider.database.dao
|
|||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseCronDao
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseSessionDao
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
|
||||
import com.faraphel.tasks_valider.database.entities.ClassEntity
|
||||
import com.faraphel.tasks_valider.database.entities.RelationClassPersonEntity
|
||||
import com.faraphel.tasks_valider.database.entities.SessionEntity
|
||||
|
||||
@Dao
|
||||
interface RelationClassPersonDao : BaseDao<RelationClassPersonEntity> {
|
||||
interface RelationClassPersonDao : BaseTaskDao<RelationClassPersonEntity> {
|
||||
@Query("SELECT * FROM ${RelationClassPersonEntity.TABLE_NAME}")
|
||||
override fun getAll(): List<RelationClassPersonEntity>
|
||||
|
||||
@Query(
|
||||
"SELECT ${RelationClassPersonEntity.TABLE_NAME}.* FROM ${RelationClassPersonEntity.TABLE_NAME} " +
|
||||
"JOIN ${ClassEntity.TABLE_NAME} " +
|
||||
"ON ${RelationClassPersonEntity.TABLE_NAME}.class_id = ${ClassEntity.TABLE_NAME}.id " +
|
||||
"JOIN ${SessionEntity.TABLE_NAME} " +
|
||||
"ON ${SessionEntity.TABLE_NAME}.class_id = ${ClassEntity.TABLE_NAME}.id " +
|
||||
"WHERE ${SessionEntity.TABLE_NAME}.id = :sessionId"
|
||||
)
|
||||
override fun getAllBySession(sessionId: Long): List<RelationClassPersonEntity>
|
||||
|
||||
/**
|
||||
* Get the object from its identifiers
|
||||
*/
|
||||
@Query(
|
||||
"SELECT * FROM ${RelationClassPersonEntity.TABLE_NAME} " +
|
||||
"WHERE " +
|
||||
"class_id = :classId AND " +
|
||||
"student_id = :studentId"
|
||||
"WHERE class_id = :classId " +
|
||||
"AND student_id = :studentId"
|
||||
)
|
||||
fun getById(classId: Long, studentId: Long): RelationClassPersonEntity
|
||||
fun getById(classId: Long, studentId: Long): RelationClassPersonEntity?
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
package com.faraphel.tasks_valider.database.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseCronDao
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseSessionDao
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
|
||||
import com.faraphel.tasks_valider.database.entities.RelationPersonSessionSubjectEntity
|
||||
|
||||
|
||||
@Dao
|
||||
interface RelationPersonSessionSubjectDao : BaseTaskDao<RelationPersonSessionSubjectEntity> {
|
||||
@Query("SELECT * FROM ${RelationPersonSessionSubjectEntity.TABLE_NAME}")
|
||||
override fun getAll(): List<RelationPersonSessionSubjectEntity>
|
||||
|
||||
@Query(
|
||||
"SELECT * FROM ${RelationPersonSessionSubjectEntity.TABLE_NAME} " +
|
||||
"WHERE session_id = :sessionId"
|
||||
)
|
||||
override fun getAllBySession(sessionId: Long): List<RelationPersonSessionSubjectEntity>
|
||||
|
||||
/**
|
||||
* Get the object from its identifiers
|
||||
*/
|
||||
@Query(
|
||||
"SELECT * FROM ${RelationPersonSessionSubjectEntity.TABLE_NAME} " +
|
||||
"WHERE student_id = :studentId " +
|
||||
"AND session_id = :sessionId " +
|
||||
"AND subject_id = :subjectId"
|
||||
)
|
||||
fun getById(studentId: Long, sessionId: Long, subjectId: Long): RelationPersonSessionSubjectEntity?
|
||||
}
|
|
@ -2,17 +2,23 @@ package com.faraphel.tasks_valider.database.dao
|
|||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseCronDao
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseSessionDao
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
|
||||
import com.faraphel.tasks_valider.database.entities.ClassEntity
|
||||
import com.faraphel.tasks_valider.database.entities.SessionEntity
|
||||
|
||||
@Dao
|
||||
interface SessionDao : BaseDao<SessionEntity> {
|
||||
interface SessionDao : BaseTaskDao<SessionEntity> {
|
||||
@Query("SELECT * FROM ${SessionEntity.TABLE_NAME}")
|
||||
override fun getAll(): List<SessionEntity>
|
||||
|
||||
@Query("SELECT * FROM ${SessionEntity.TABLE_NAME} WHERE id = :sessionId")
|
||||
override fun getAllBySession(sessionId: Long): List<SessionEntity>
|
||||
|
||||
/**
|
||||
* Get the object from its identifier
|
||||
*/
|
||||
@Query("SELECT * FROM ${SessionEntity.TABLE_NAME} WHERE id = :id")
|
||||
fun getById(id: Long): SessionEntity
|
||||
fun getById(id: Long): SessionEntity?
|
||||
}
|
||||
|
|
|
@ -2,26 +2,38 @@ package com.faraphel.tasks_valider.database.dao
|
|||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.entities.SessionEntity
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseCronDao
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseSessionDao
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
|
||||
import com.faraphel.tasks_valider.database.entities.ClassEntity
|
||||
import com.faraphel.tasks_valider.database.entities.RelationPersonSessionSubjectEntity
|
||||
import com.faraphel.tasks_valider.database.entities.SubjectEntity
|
||||
import com.faraphel.tasks_valider.database.entities.TaskEntity
|
||||
|
||||
@Dao
|
||||
interface SubjectDao : BaseDao<SubjectEntity> {
|
||||
interface SubjectDao : BaseTaskDao<SubjectEntity> {
|
||||
@Query("SELECT * FROM ${SubjectEntity.TABLE_NAME}")
|
||||
override fun getAll(): List<SubjectEntity>
|
||||
|
||||
@Query(
|
||||
"SELECT * FROM ${SubjectEntity.TABLE_NAME} " +
|
||||
"WHERE id IN (" +
|
||||
"SELECT subject_id FROM ${RelationPersonSessionSubjectEntity.TABLE_NAME} " +
|
||||
"WHERE session_id = :sessionId" +
|
||||
")"
|
||||
)
|
||||
override fun getAllBySession(sessionId: Long): List<SubjectEntity>
|
||||
|
||||
/**
|
||||
* Get the object from its identifier
|
||||
*/
|
||||
@Query("SELECT * FROM ${SubjectEntity.TABLE_NAME} WHERE id = :id")
|
||||
fun getById(id: Long): SubjectEntity
|
||||
fun getById(id: Long): SubjectEntity?
|
||||
|
||||
/**
|
||||
* Get all the tasks available in a subject
|
||||
* @param id the id of the subject
|
||||
*/
|
||||
@Query("SELECT * FROM ${TaskEntity.TABLE_NAME} WHERE subject_id = :id")
|
||||
fun getSessions(id: Long): List<SessionEntity>
|
||||
fun getTasks(id: Long): List<TaskEntity>
|
||||
}
|
||||
|
|
|
@ -2,20 +2,32 @@ package com.faraphel.tasks_valider.database.dao
|
|||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseCronDao
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseSessionDao
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
|
||||
import com.faraphel.tasks_valider.database.entities.RelationPersonSessionSubjectEntity
|
||||
import com.faraphel.tasks_valider.database.entities.TaskEntity
|
||||
import com.faraphel.tasks_valider.database.entities.ValidationEntity
|
||||
|
||||
@Dao
|
||||
interface TaskDao : BaseDao<TaskEntity> {
|
||||
interface TaskDao : BaseTaskDao<TaskEntity> {
|
||||
@Query("SELECT * FROM ${TaskEntity.TABLE_NAME}")
|
||||
override fun getAll(): List<TaskEntity>
|
||||
|
||||
@Query(
|
||||
"SELECT * FROM ${TaskEntity.TABLE_NAME} " +
|
||||
"WHERE subject_id IN (" +
|
||||
"SELECT subject_id FROM ${RelationPersonSessionSubjectEntity.TABLE_NAME} " +
|
||||
"WHERE session_id = :sessionId" +
|
||||
")"
|
||||
)
|
||||
override fun getAllBySession(sessionId: Long): List<TaskEntity>
|
||||
|
||||
/**
|
||||
* Get the object from its identifier
|
||||
*/
|
||||
@Query("SELECT * FROM ${TaskEntity.TABLE_NAME} WHERE id = :id")
|
||||
fun getById(id: Long): TaskEntity
|
||||
fun getById(id: Long): TaskEntity?
|
||||
|
||||
/**
|
||||
* Get all the validations have been approved for this tasks
|
||||
|
|
|
@ -2,14 +2,29 @@ package com.faraphel.tasks_valider.database.dao
|
|||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseCronDao
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseSessionDao
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
|
||||
import com.faraphel.tasks_valider.database.entities.RelationPersonSessionSubjectEntity
|
||||
import com.faraphel.tasks_valider.database.entities.TaskEntity
|
||||
import com.faraphel.tasks_valider.database.entities.ValidationEntity
|
||||
|
||||
@Dao
|
||||
interface ValidationDao : BaseDao<ValidationEntity> {
|
||||
interface ValidationDao : BaseTaskDao<ValidationEntity> {
|
||||
@Query("SELECT * FROM ${ValidationEntity.TABLE_NAME}")
|
||||
override fun getAll(): List<ValidationEntity>
|
||||
|
||||
@Query(
|
||||
"SELECT * FROM ${ValidationEntity.TABLE_NAME} " +
|
||||
"JOIN ${TaskEntity.TABLE_NAME} " +
|
||||
"ON ${ValidationEntity.TABLE_NAME}.task_id = ${TaskEntity.TABLE_NAME}.id " +
|
||||
"WHERE ${TaskEntity.TABLE_NAME}.subject_id IN (" +
|
||||
"SELECT subject_id FROM ${RelationPersonSessionSubjectEntity.TABLE_NAME} " +
|
||||
"WHERE session_id = :sessionId" +
|
||||
")"
|
||||
)
|
||||
override fun getAllBySession(sessionId: Long): List<ValidationEntity>
|
||||
|
||||
/**
|
||||
* Get the object from its identifiers
|
||||
*/
|
||||
|
@ -20,5 +35,5 @@ interface ValidationDao : BaseDao<ValidationEntity> {
|
|||
"student_id = :studentId and " +
|
||||
"task_id = :taskId"
|
||||
)
|
||||
fun getById(teacherId: Long, studentId: Long, taskId: Long): ValidationEntity
|
||||
fun getById(teacherId: Long, studentId: Long, taskId: Long): ValidationEntity?
|
||||
}
|
||||
|
|
|
@ -3,17 +3,20 @@ package com.faraphel.tasks_valider.database.dao.base
|
|||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Update
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A base DAO to handle the database operations.
|
||||
* A base DAO to handle the database operations (CRON).
|
||||
* @param Entity the entity to handle
|
||||
*/
|
||||
interface BaseDao<Entity> {
|
||||
interface BaseCronDao<Entity> {
|
||||
/**
|
||||
* Check if the entity exists in the database.
|
||||
*/
|
||||
fun exists(entity: Entity): Boolean {
|
||||
return this.getAll().contains(entity)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the entities exists in the database.
|
||||
*/
|
||||
|
@ -21,12 +24,24 @@ interface BaseDao<Entity> {
|
|||
return this.getAll().containsAll(entities.toList())
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the entities into the database.
|
||||
*/
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(entity: Entity): Long
|
||||
|
||||
/**
|
||||
* Insert the entities into the database.
|
||||
*/
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(vararg entities: Entity): List<Long>
|
||||
|
||||
/**
|
||||
* Delete the entity from the database.
|
||||
*/
|
||||
@Delete
|
||||
fun delete(entity: Entity): Int
|
||||
|
||||
/**
|
||||
* Delete the entities from the database.
|
||||
*/
|
|
@ -0,0 +1,11 @@
|
|||
package com.faraphel.tasks_valider.database.dao.base
|
||||
|
||||
/**
|
||||
* The base for a DAO that is inside a session scope.
|
||||
*/
|
||||
interface BaseSessionDao<Entity> {
|
||||
/**
|
||||
* Return all the objects concerned by a session.
|
||||
*/
|
||||
fun getAllBySession(sessionId: Long): List<Entity>
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package com.faraphel.tasks_valider.database.dao.base
|
||||
|
||||
/**
|
||||
* The base for a DAO for the task project.
|
||||
*/
|
||||
interface BaseTaskDao<Entity>: BaseCronDao<Entity>, BaseSessionDao<Entity>
|
|
@ -3,17 +3,59 @@ package com.faraphel.tasks_valider.database.entities
|
|||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskRole
|
||||
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.security.MessageDigest
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
@Entity(tableName = PersonEntity.TABLE_NAME)
|
||||
data class PersonEntity (
|
||||
@ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||
@ColumnInfo("first_name") val firstName: String,
|
||||
@ColumnInfo("last_name") val lastName: String,
|
||||
@ColumnInfo("card_id") val cardId: UUID,
|
||||
@ColumnInfo("card_id") val cardId: String? = null,
|
||||
@ColumnInfo("password_hash") val passwordHash: String? = null,
|
||||
@ColumnInfo("role") val role: TaskRole = TaskRole.STUDENT,
|
||||
) : BaseEntity() {
|
||||
companion object {
|
||||
const val TABLE_NAME = "persons"
|
||||
|
||||
/**
|
||||
* Hash a password
|
||||
*/
|
||||
fun hashPassword(password: String): String {
|
||||
val digester = MessageDigest.getInstance("SHA-256")
|
||||
val hash = digester.digest(password.toByteArray())
|
||||
return hash.joinToString("") { byte -> "%02x".format(byte) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with string password
|
||||
*/
|
||||
constructor(
|
||||
firstName: String,
|
||||
lastName: String,
|
||||
cardId: String? = null,
|
||||
password: String? = null,
|
||||
role: TaskRole,
|
||||
) : this(
|
||||
firstName = firstName,
|
||||
lastName = lastName,
|
||||
cardId = cardId,
|
||||
passwordHash = password?.let { hashPassword(password) },
|
||||
role = role,
|
||||
)
|
||||
|
||||
/**
|
||||
* Return the full name of the person
|
||||
*/
|
||||
fun fullName(): String = "${this.firstName.capitalize(Locale.ROOT)} ${this.lastName.uppercase()}"
|
||||
|
||||
/**
|
||||
* Check if the password is correct
|
||||
*/
|
||||
fun checkPassword(password: String): Boolean = this.passwordHash == hashPassword(password)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
package com.faraphel.tasks_valider.database.entities
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
|
||||
|
||||
|
||||
/**
|
||||
* Represent the relation that associate a subject to a person for a specific session.
|
||||
*/
|
||||
@Entity(
|
||||
tableName = RelationPersonSessionSubjectEntity.TABLE_NAME,
|
||||
primaryKeys = [
|
||||
"student_id",
|
||||
"session_id",
|
||||
"subject_id"
|
||||
],
|
||||
foreignKeys = [
|
||||
ForeignKey(
|
||||
entity = PersonEntity::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["student_id"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
),
|
||||
ForeignKey(
|
||||
entity = SessionEntity::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["session_id"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
),
|
||||
ForeignKey(
|
||||
entity = SubjectEntity::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["subject_id"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
),
|
||||
]
|
||||
)
|
||||
data class RelationPersonSessionSubjectEntity (
|
||||
@ColumnInfo("student_id", index = true) val studentId: Long,
|
||||
@ColumnInfo("session_id", index = true) val sessionId: Long,
|
||||
@ColumnInfo("subject_id", index = true) val subjectId: Long,
|
||||
) : BaseEntity() {
|
||||
companion object {
|
||||
const val TABLE_NAME = "relation_person_session_subject"
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ data class TaskEntity (
|
|||
@ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||
@ColumnInfo("title") val title: String,
|
||||
@ColumnInfo("description") val description: String? = null,
|
||||
@ColumnInfo("order") val order: Int, ///< the order of the task in the list of the subject
|
||||
|
||||
@ColumnInfo("subject_id", index = true) val subjectId: Long,
|
||||
) : BaseEntity() {
|
||||
|
|
|
@ -35,14 +35,28 @@ import java.time.Instant
|
|||
]
|
||||
)
|
||||
data class ValidationEntity (
|
||||
@ColumnInfo("date") val date: Instant,
|
||||
|
||||
@ColumnInfo("teacher_id", index = true) val teacherId: Long,
|
||||
@ColumnInfo("student_id", index = true) val studentId: Long,
|
||||
@ColumnInfo("task_id", index = true) val taskId: Long,
|
||||
|
||||
@ColumnInfo("date") val date: Instant,
|
||||
) : BaseEntity() {
|
||||
companion object {
|
||||
const val TABLE_NAME = "validations"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new ValidationEntity. Automatically set the date to today.
|
||||
*/
|
||||
constructor(
|
||||
teacherId: Long,
|
||||
studentId: Long,
|
||||
taskId: Long
|
||||
):
|
||||
this(
|
||||
teacherId = teacherId,
|
||||
studentId = studentId,
|
||||
taskId = taskId,
|
||||
date = Instant.now()
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
package com.faraphel.tasks_valider.database.entities.base
|
||||
|
||||
open class BaseEntity
|
||||
open class BaseEntity {
|
||||
companion object {
|
||||
const val TABLE_NAME = "<Undefined>"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
package com.faraphel.tasks_valider.database.entities.error
|
||||
|
||||
|
||||
class HttpException(
|
||||
private val code: Int,
|
||||
) : Exception("Http Exception: $code")
|
133
app/src/main/java/com/faraphel/tasks_valider/database/test.kt
Normal file
133
app/src/main/java/com/faraphel/tasks_valider/database/test.kt
Normal file
|
@ -0,0 +1,133 @@
|
|||
package com.faraphel.tasks_valider.database
|
||||
|
||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskRole
|
||||
import com.faraphel.tasks_valider.database.entities.*
|
||||
|
||||
fun populateTaskDatabaseTest(database: TaskDatabase) {
|
||||
// classes
|
||||
val (
|
||||
classMiageId,
|
||||
classIsriId,
|
||||
) = database.classDao().insert(
|
||||
ClassEntity(name="MIAGE"),
|
||||
ClassEntity(name="ISRI")
|
||||
)
|
||||
|
||||
val classMiage = database.classDao().getById(classMiageId)!!
|
||||
val classIrsi = database.classDao().getById(classIsriId)!!
|
||||
|
||||
// persons
|
||||
val (
|
||||
personBillyId,
|
||||
personBobbyId,
|
||||
personBettyId,
|
||||
) = database.personDao().insert(
|
||||
PersonEntity(
|
||||
"Billy", "Bob",
|
||||
"0A1A7553-9DE5-103C-B23C-630998207116",
|
||||
"1234",
|
||||
TaskRole.STUDENT
|
||||
),
|
||||
PersonEntity(
|
||||
"Bobby", "Bob",
|
||||
null,
|
||||
"1234",
|
||||
TaskRole.STUDENT
|
||||
),
|
||||
PersonEntity(
|
||||
"Betty", "Bob",
|
||||
null,
|
||||
"1234",
|
||||
TaskRole.STUDENT
|
||||
)
|
||||
)
|
||||
|
||||
val personBilly = database.personDao().getById(personBillyId)!!
|
||||
val personBobby = database.personDao().getById(personBobbyId)!!
|
||||
val personBetty = database.personDao().getById(personBettyId)!!
|
||||
|
||||
// relations class <=> persons
|
||||
database.relationClassPersonDao().insert(
|
||||
RelationClassPersonEntity(personBobby.id, classMiage.id),
|
||||
RelationClassPersonEntity(personBilly.id, classMiage.id),
|
||||
RelationClassPersonEntity(personBetty.id, classIrsi.id),
|
||||
)
|
||||
|
||||
// subjects
|
||||
val (
|
||||
subjectAId,
|
||||
subjectBId
|
||||
) = database.subjectDao().insert(
|
||||
SubjectEntity(
|
||||
name="Type A"
|
||||
),
|
||||
SubjectEntity(
|
||||
name="Type B"
|
||||
)
|
||||
)
|
||||
|
||||
val subjectA = database.subjectDao().getById(subjectAId)!!
|
||||
val subjectB = database.subjectDao().getById(subjectBId)!!
|
||||
|
||||
// tasks
|
||||
val (
|
||||
taskA1Id,
|
||||
taskA2Id,
|
||||
taskA3Id,
|
||||
taskB1Id,
|
||||
taskB2Id,
|
||||
) = database.taskDao().insert(
|
||||
TaskEntity(
|
||||
title = "Commencer A",
|
||||
description = "Description 1",
|
||||
order = 1,
|
||||
subjectId = subjectA.id,
|
||||
),
|
||||
TaskEntity(
|
||||
title = "Continuer A",
|
||||
description = "Description 2",
|
||||
order = 2,
|
||||
subjectId = subjectA.id
|
||||
),
|
||||
TaskEntity(
|
||||
title = "Finir A",
|
||||
description = "Description 3",
|
||||
order = 3,
|
||||
subjectId = subjectA.id
|
||||
),
|
||||
TaskEntity(
|
||||
title = "Commencer B",
|
||||
description = "Description 1",
|
||||
order = 1,
|
||||
subjectId = subjectB.id,
|
||||
),
|
||||
TaskEntity(
|
||||
title = "Finir B",
|
||||
description = "Description 2",
|
||||
order = 2,
|
||||
subjectId = subjectB.id,
|
||||
)
|
||||
)
|
||||
|
||||
val taskA1 = database.taskDao().getById(taskA1Id)!!
|
||||
val taskA2 = database.taskDao().getById(taskA2Id)!!
|
||||
val taskA3 = database.taskDao().getById(taskA3Id)!!
|
||||
val taskB1 = database.taskDao().getById(taskB1Id)!!
|
||||
val taskB2 = database.taskDao().getById(taskB2Id)!!
|
||||
}
|
||||
|
||||
|
||||
fun populateSubjectSessionPersonTest(database: TaskDatabase, session: SessionEntity) {
|
||||
// get the list of available subjects
|
||||
val subjects = database.subjectDao().getAll()
|
||||
|
||||
// give a random subject to everyone in the session
|
||||
database.personDao().getAllBySession(session.id).forEach { person ->
|
||||
// get a random subject
|
||||
val subject = subjects.random()
|
||||
// insert a new subject for a person for a specific session
|
||||
database.relationPersonSessionSubjectDao().insert(
|
||||
RelationPersonSessionSubjectEntity(person.id, session.id, subject.id)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package com.faraphel.tasks_valider.ui.screen.authentification
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
|
||||
|
||||
/**
|
||||
* Authentification screen where the client can give his information
|
||||
*/
|
||||
@Composable
|
||||
fun AuthentificationClientScreen() {
|
||||
val cardId = remember { mutableStateOf("") }
|
||||
val password = remember { mutableStateOf("") }
|
||||
|
||||
Column {
|
||||
// card identifier
|
||||
TextField(
|
||||
value = cardId.value,
|
||||
onValueChange = { text -> cardId.value = text },
|
||||
)
|
||||
|
||||
// password
|
||||
TextField(
|
||||
value = password.value,
|
||||
visualTransformation = PasswordVisualTransformation(),
|
||||
onValueChange = { text -> password.value = text },
|
||||
)
|
||||
|
||||
// button
|
||||
Button(onClick = { /* do something */ }) {
|
||||
Text("Submit")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package com.faraphel.tasks_valider.ui.screen.authentification
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskRole
|
||||
import com.faraphel.tasks_valider.database.entities.PersonEntity
|
||||
|
||||
|
||||
/**
|
||||
* Authentification screen where the host can give his information
|
||||
*/
|
||||
@Composable
|
||||
fun AuthentificationServerScreen(personEntity: MutableState<PersonEntity?>) {
|
||||
val firstName = remember { mutableStateOf("") }
|
||||
val lastName = remember { mutableStateOf("") }
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
// title
|
||||
Text(
|
||||
text = "Your Profile",
|
||||
fontSize = 32.sp
|
||||
)
|
||||
|
||||
// separator
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// first name
|
||||
TextField(
|
||||
value = firstName.value,
|
||||
placeholder = { Text("first name") },
|
||||
onValueChange = { text -> firstName.value = text },
|
||||
)
|
||||
|
||||
// last name
|
||||
TextField(
|
||||
value = lastName.value,
|
||||
placeholder = { Text("last name") },
|
||||
onValueChange = { text -> lastName.value = text },
|
||||
)
|
||||
|
||||
// separator
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// confirm button
|
||||
Button(onClick = {
|
||||
// create the person entity with the given information
|
||||
personEntity.value = PersonEntity(
|
||||
firstName = firstName.value,
|
||||
lastName = lastName.value,
|
||||
role = TaskRole.ADMIN
|
||||
)
|
||||
}) {
|
||||
Text("Submit")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
package com.faraphel.tasks_valider.ui.screen.communication.internet.client
|
||||
package com.faraphel.tasks_valider.ui.screen.communication.internet
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.Button
|
||||
|
@ -16,15 +18,16 @@ import com.faraphel.tasks_valider.connectivity.task.TaskClient
|
|||
import com.faraphel.tasks_valider.ui.screen.communication.DEFAULT_SERVER_ADDRESS
|
||||
import com.faraphel.tasks_valider.ui.screen.communication.DEFAULT_SERVER_PORT
|
||||
import com.faraphel.tasks_valider.ui.screen.communication.RANGE_SERVER_PORT
|
||||
import com.faraphel.tasks_valider.ui.screen.task.TaskSessionScreen
|
||||
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@Composable
|
||||
fun CommunicationInternetClientScreen(activity: Activity) {
|
||||
val client = remember { mutableStateOf<TaskClient?>(null) }
|
||||
|
||||
if (client.value == null) CommunicationInternetClientContent(client)
|
||||
else TaskSessionScreen(activity, client.value!!)
|
||||
// TODO(Faraphel): fix and get a user
|
||||
// if (client.value == null) CommunicationInternetClientContent(client)
|
||||
// else TaskSessionScreen(activity, client.value!!, user)
|
||||
}
|
||||
|
||||
|
|
@ -1,33 +1,52 @@
|
|||
package com.faraphel.tasks_valider.ui.screen.communication.internet
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.faraphel.tasks_valider.ui.screen.communication.internet.client.CommunicationInternetClientScreen
|
||||
import com.faraphel.tasks_valider.ui.screen.communication.internet.server.CommunicationInternetServerScreen
|
||||
import com.faraphel.tasks_valider.database.TaskDatabase
|
||||
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@Composable
|
||||
fun CommunicationInternetScreen(activity: Activity) {
|
||||
fun CommunicationInternetSelectScreen(activity: Activity, database: TaskDatabase) {
|
||||
val controller = rememberNavController()
|
||||
|
||||
NavHost(navController = controller, startDestination = "mode") {
|
||||
composable("mode") { CommunicationInternetSelectContent(controller) }
|
||||
composable("client") { CommunicationInternetClientScreen(activity) }
|
||||
composable("server") { CommunicationInternetServerScreen(activity) }
|
||||
composable("server") { CommunicationInternetServerScreen(activity, database) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun CommunicationInternetSelectContent(controller: NavController) {
|
||||
Column {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
// title
|
||||
Text(
|
||||
text = "Role",
|
||||
fontSize = 32.sp
|
||||
)
|
||||
|
||||
// separator
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// client mode
|
||||
Button(onClick = { controller.navigate("client") }) { Text("Client") }
|
||||
// server mode
|
|
@ -0,0 +1,218 @@
|
|||
package com.faraphel.tasks_valider.ui.screen.communication.internet
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.faraphel.tasks_valider.connectivity.task.TaskClient
|
||||
import com.faraphel.tasks_valider.connectivity.task.TaskServer
|
||||
import com.faraphel.tasks_valider.database.TaskDatabase
|
||||
import com.faraphel.tasks_valider.database.entities.ClassEntity
|
||||
import com.faraphel.tasks_valider.database.entities.PersonEntity
|
||||
import com.faraphel.tasks_valider.database.entities.SessionEntity
|
||||
import com.faraphel.tasks_valider.database.populateSubjectSessionPersonTest
|
||||
import com.faraphel.tasks_valider.ui.screen.authentification.AuthentificationServerScreen
|
||||
import com.faraphel.tasks_valider.ui.screen.communication.DEFAULT_SERVER_PORT
|
||||
import com.faraphel.tasks_valider.ui.screen.communication.RANGE_SERVER_PORT
|
||||
import com.faraphel.tasks_valider.ui.screen.task.TaskSessionController
|
||||
import java.time.Instant
|
||||
|
||||
|
||||
/**
|
||||
* Screen for the host to configure the server
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@Composable
|
||||
fun CommunicationInternetServerScreen(
|
||||
activity: Activity,
|
||||
database: TaskDatabase,
|
||||
) {
|
||||
val controller = rememberNavController()
|
||||
|
||||
val adminPersonEntityRaw = remember { mutableStateOf<PersonEntity?>(null) }
|
||||
val adminPersonEntity = remember { mutableStateOf<PersonEntity?>(null) }
|
||||
val client = remember { mutableStateOf<TaskClient?>(null) }
|
||||
|
||||
NavHost(navController = controller, startDestination = "authentication") {
|
||||
composable("authentication") {
|
||||
// if the admin person is not created, prompt the user for the admin information
|
||||
if (adminPersonEntityRaw.value == null) AuthentificationServerScreen(adminPersonEntityRaw)
|
||||
else controller.navigate("configuration")
|
||||
}
|
||||
composable("configuration") {
|
||||
if (client.value == null)
|
||||
CommunicationInternetServerContent(
|
||||
database,
|
||||
adminPersonEntityRaw,
|
||||
adminPersonEntity,
|
||||
client,
|
||||
)
|
||||
else controller.navigate("session")
|
||||
}
|
||||
composable("session") {
|
||||
TaskSessionController(
|
||||
activity,
|
||||
client.value!!,
|
||||
adminPersonEntity.value!!
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@Composable
|
||||
fun CommunicationInternetServerContent(
|
||||
database: TaskDatabase,
|
||||
adminPersonEntityRaw: MutableState<PersonEntity?>,
|
||||
adminPersonEntity: MutableState<PersonEntity?>,
|
||||
client: MutableState<TaskClient?>
|
||||
) {
|
||||
val classes = remember { mutableStateOf<List<ClassEntity>?>(null) }
|
||||
val selectedClass = remember { mutableStateOf<ClassEntity?>(null) }
|
||||
val areClassesExpanded = remember { mutableStateOf(false) }
|
||||
|
||||
val serverPort = remember { mutableIntStateOf(DEFAULT_SERVER_PORT) }
|
||||
|
||||
LaunchedEffect(true) {
|
||||
// refresh the list of classes
|
||||
Thread { refreshClasses(database, classes) }.start()
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
// title
|
||||
Text(
|
||||
text = "New Session",
|
||||
fontSize = 32.sp
|
||||
)
|
||||
|
||||
// separator
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// classes
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
// description
|
||||
Text(text = "Class", fontSize = 12.sp)
|
||||
// separator
|
||||
Spacer(modifier = Modifier.width(width = 12.dp))
|
||||
// selector
|
||||
Button(onClick = { areClassesExpanded.value = !areClassesExpanded.value }) {
|
||||
// display the selected class, if selected
|
||||
if (selectedClass.value != null) Text(text = selectedClass.value!!.name)
|
||||
else Text(text = "<Not selected>")
|
||||
}
|
||||
|
||||
// class selector
|
||||
DropdownMenu(
|
||||
expanded = areClassesExpanded.value,
|
||||
onDismissRequest = { areClassesExpanded.value = false }
|
||||
) {
|
||||
// TODO(Faraphel): student lists should be loaded from the database or a file
|
||||
classes.value?.forEach { class_ ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(class_.name) },
|
||||
onClick = {
|
||||
selectedClass.value = class_
|
||||
areClassesExpanded.value = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// server port
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
// descriptor
|
||||
Text(text = "Port")
|
||||
// separator
|
||||
Spacer(modifier = Modifier.width(width = 12.dp))
|
||||
// input
|
||||
TextField(
|
||||
modifier = Modifier.width(80.dp),
|
||||
value = serverPort.intValue.toString(),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
singleLine = true,
|
||||
onValueChange = { text ->
|
||||
val port = text.toInt()
|
||||
if (port in RANGE_SERVER_PORT) {
|
||||
serverPort.intValue = port
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// check if a class is selected
|
||||
if (selectedClass.value != null)
|
||||
// button to create the server
|
||||
Button(onClick = {
|
||||
Thread { // a thread is used for networking
|
||||
|
||||
// Insert the admin in the database and get its id
|
||||
val adminPersonEntityId = database.personDao().insert(adminPersonEntityRaw.value!!)
|
||||
adminPersonEntity.value = database.personDao().getById(adminPersonEntityId)!!
|
||||
|
||||
// Create a new session
|
||||
// TODO(Faraphel): name
|
||||
val sessionId = database.sessionDao().insert(
|
||||
SessionEntity(
|
||||
name="NOM",
|
||||
start=Instant.now(),
|
||||
classId=selectedClass.value!!.id,
|
||||
)
|
||||
)
|
||||
val session = database.sessionDao().getById(sessionId)!!
|
||||
|
||||
// TODO(Faraphel): remove, this is a test function
|
||||
Thread {
|
||||
populateSubjectSessionPersonTest(database, session)
|
||||
}.let { thread ->
|
||||
thread.start()
|
||||
thread.join()
|
||||
}
|
||||
|
||||
// Create the server
|
||||
Log.i("room-server", "creating the server")
|
||||
val server = TaskServer(
|
||||
serverPort.intValue,
|
||||
database,
|
||||
session,
|
||||
adminPersonEntity.value!!,
|
||||
)
|
||||
server.start()
|
||||
|
||||
// Get the client from the server
|
||||
client.value = server.getAdminClient()
|
||||
}.start()
|
||||
}) {
|
||||
Text("Create")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Refresh the list of classes
|
||||
*/
|
||||
fun refreshClasses(database: TaskDatabase, classes: MutableState<List<ClassEntity>?>) {
|
||||
classes.value = database.classDao().getAll()
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
package com.faraphel.tasks_valider.ui.screen.communication.internet.server
|
||||
|
||||
import android.app.Activity
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.room.Room
|
||||
import com.faraphel.tasks_valider.connectivity.task.TaskClient
|
||||
import com.faraphel.tasks_valider.connectivity.task.TaskServer
|
||||
import com.faraphel.tasks_valider.database.TaskDatabase
|
||||
import com.faraphel.tasks_valider.ui.screen.communication.DEFAULT_SERVER_PORT
|
||||
import com.faraphel.tasks_valider.ui.screen.communication.RANGE_SERVER_PORT
|
||||
import com.faraphel.tasks_valider.ui.screen.task.TaskSessionScreen
|
||||
|
||||
|
||||
@Composable
|
||||
fun CommunicationInternetServerScreen(activity: Activity) {
|
||||
val client = remember { mutableStateOf<TaskClient?>(null) }
|
||||
|
||||
// if the server is not created, prompt the user for the server configuration
|
||||
if (client.value == null) CommunicationInternetServerContent(activity, client)
|
||||
// else, go to the base tasks screen
|
||||
else TaskSessionScreen(activity, client.value!!)
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun CommunicationInternetServerContent(activity: Activity, client: MutableState<TaskClient?>) {
|
||||
val expandedStudentList = remember { mutableStateOf(false) }
|
||||
val serverPort = remember { mutableIntStateOf(DEFAULT_SERVER_PORT) }
|
||||
|
||||
Column {
|
||||
// student list
|
||||
Button(onClick = { expandedStudentList.value = !expandedStudentList.value }) {
|
||||
Text(text = "Select Students List")
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = expandedStudentList.value,
|
||||
onDismissRequest = { expandedStudentList.value = false }
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text("ISRI") },
|
||||
onClick = {}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text("MIAGE") },
|
||||
onClick = {}
|
||||
)
|
||||
// TODO(Faraphel): student lists should be loaded from the database or a file
|
||||
}
|
||||
|
||||
// server port
|
||||
TextField(
|
||||
value = serverPort.intValue.toString(),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
onValueChange = { text ->
|
||||
val port = text.toInt()
|
||||
if (port in RANGE_SERVER_PORT) {
|
||||
serverPort.intValue = port
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
Button(onClick = {
|
||||
// Reset the database | TODO(Faraphel): only for testing purpose
|
||||
activity.deleteDatabase("local")
|
||||
|
||||
// Create the database
|
||||
val database = Room.databaseBuilder(
|
||||
activity,
|
||||
TaskDatabase::class.java,
|
||||
"local"
|
||||
).build()
|
||||
|
||||
// Create the server
|
||||
Log.i("room-server", "creating the server")
|
||||
Thread { // a thread is used for networking
|
||||
val server = TaskServer(serverPort.intValue, database)
|
||||
server.start()
|
||||
|
||||
// Get the client from the server
|
||||
client.value = server.getClientAdmin()
|
||||
}.start()
|
||||
}) {
|
||||
Text("Create")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,19 +1,27 @@
|
|||
package com.faraphel.tasks_valider.ui.screen.communication
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Build
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.faraphel.tasks_valider.connectivity.bwf.BwfManager
|
||||
import com.faraphel.tasks_valider.ui.screen.communication.internet.CommunicationInternetScreen
|
||||
import com.faraphel.tasks_valider.connectivity.bwd.BwdManager
|
||||
import com.faraphel.tasks_valider.database.TaskDatabase
|
||||
import com.faraphel.tasks_valider.ui.screen.communication.internet.CommunicationInternetSelectScreen
|
||||
import com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.CommunicationWifiP2pScreen
|
||||
|
||||
|
||||
|
@ -21,10 +29,13 @@ import com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.CommunicationW
|
|||
* CommunicationController is the main controller for the communication screen.
|
||||
* It is responsible for handling the navigation between the different communication methods.
|
||||
* It is also responsible for initializing the communication methods.
|
||||
*
|
||||
* @param activity: The activity that hosts the communication screen.
|
||||
* @param database: the database.
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@Composable
|
||||
fun CommunicationScreen(activity: Activity) {
|
||||
fun CommunicationModeSelectionScreen(activity: Activity, database: TaskDatabase) {
|
||||
val controller = rememberNavController()
|
||||
|
||||
NavHost(
|
||||
|
@ -35,11 +46,11 @@ fun CommunicationScreen(activity: Activity) {
|
|||
CommunicationSelectContent(controller, activity)
|
||||
}
|
||||
composable("internet") {
|
||||
CommunicationInternetScreen(activity)
|
||||
CommunicationInternetSelectScreen(activity, database)
|
||||
}
|
||||
composable("wifi-p2p") {
|
||||
val bwfManager = BwfManager.fromActivity(activity)
|
||||
CommunicationWifiP2pScreen(activity, bwfManager)
|
||||
val bwdManager = BwdManager.fromActivity(activity)
|
||||
CommunicationWifiP2pScreen(activity, bwdManager)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,9 +61,22 @@ fun CommunicationScreen(activity: Activity) {
|
|||
*/
|
||||
@Composable
|
||||
fun CommunicationSelectContent(controller: NavController, activity: Activity) {
|
||||
val isWifiP2pSupported = BwfManager.isSupported(activity)
|
||||
val isWifiP2pSupported = BwdManager.isSupported(activity)
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
// title
|
||||
Text(
|
||||
text = "Connection Type",
|
||||
fontSize = 32.sp
|
||||
)
|
||||
|
||||
// separator
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
Column {
|
||||
// internet communication mode
|
||||
Button(onClick = { controller.navigate("internet") }) {
|
||||
Text("Internet")
|
||||
|
@ -62,7 +86,7 @@ fun CommunicationSelectContent(controller: NavController, activity: Activity) {
|
|||
Button(
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
// if the WiFi-Direct is not supported, the button is grayed out
|
||||
containerColor = if (isWifiP2pSupported) Color.Unspecified else Color.Gray
|
||||
containerColor = if (isWifiP2pSupported) MaterialTheme.colorScheme.primary else Color.Gray
|
||||
),
|
||||
onClick = {
|
||||
// if the WiFi-Direct is supported, navigate to the WiFi-Direct screen
|
|
@ -8,12 +8,12 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import com.faraphel.tasks_valider.connectivity.bwf.BwfManager
|
||||
import com.faraphel.tasks_valider.connectivity.bwd.BwdManager
|
||||
import com.faraphel.tasks_valider.ui.widgets.connectivity.WifiP2pDeviceListWidget
|
||||
|
||||
|
||||
@Composable
|
||||
fun CommunicationWifiP2pClientScreen(activity: Activity, bwfManager: BwfManager) {
|
||||
fun CommunicationWifiP2pClientScreen(activity: Activity, bwdManager: BwdManager) {
|
||||
val selectedDevice = remember { mutableStateOf<WifiP2pDevice?>(null) }
|
||||
val isConnected = remember { mutableStateOf(false) }
|
||||
|
||||
|
@ -31,25 +31,25 @@ fun CommunicationWifiP2pClientScreen(activity: Activity, bwfManager: BwfManager)
|
|||
val config = WifiP2pConfig().apply {
|
||||
deviceAddress = selectedDevice.value!!.deviceAddress
|
||||
}
|
||||
bwfManager.connect(config) {
|
||||
bwdManager.connect(config) {
|
||||
isConnected.value = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// display the list of devices
|
||||
CommunicationWifiP2pClientContent(bwfManager, selectedDevice)
|
||||
CommunicationWifiP2pClientContent(bwdManager, selectedDevice)
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun CommunicationWifiP2pClientContent(
|
||||
bwfManager: BwfManager,
|
||||
bwdManager: BwdManager,
|
||||
selectedDevice: MutableState<WifiP2pDevice?>
|
||||
) {
|
||||
Column {
|
||||
WifiP2pDeviceListWidget(
|
||||
peers = bwfManager.statePeers.value,
|
||||
peers = bwdManager.statePeers.value,
|
||||
filter = { device: WifiP2pDevice -> device.isGroupOwner },
|
||||
selectedDevice,
|
||||
)
|
||||
|
|
|
@ -9,19 +9,19 @@ import androidx.navigation.NavController
|
|||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.faraphel.tasks_valider.connectivity.bwf.BwfManager
|
||||
import com.faraphel.tasks_valider.connectivity.bwd.BwdManager
|
||||
import com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.client.CommunicationWifiP2pClientScreen
|
||||
import com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.server.CommunicationWifiP2pServerScreen
|
||||
|
||||
|
||||
@Composable
|
||||
fun CommunicationWifiP2pScreen(activity: Activity, bwfManager: BwfManager) {
|
||||
fun CommunicationWifiP2pScreen(activity: Activity, bwdManager: BwdManager) {
|
||||
val controller = rememberNavController()
|
||||
|
||||
NavHost(navController = controller, startDestination = "mode") {
|
||||
composable("mode") { CommunicationWifiP2pSelectContent(controller) }
|
||||
composable("client") { CommunicationWifiP2pClientScreen(activity, bwfManager) }
|
||||
composable("server") { CommunicationWifiP2pServerScreen(activity, bwfManager) }
|
||||
composable("client") { CommunicationWifiP2pClientScreen(activity, bwdManager) }
|
||||
composable("server") { CommunicationWifiP2pServerScreen(activity, bwdManager) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,47 +1,33 @@
|
|||
package com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.server
|
||||
|
||||
import android.app.Activity
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.room.Room
|
||||
import com.faraphel.tasks_valider.connectivity.bwf.BwfManager
|
||||
import com.faraphel.tasks_valider.connectivity.bwd.BwdManager
|
||||
import com.faraphel.tasks_valider.connectivity.task.TaskClient
|
||||
import com.faraphel.tasks_valider.connectivity.task.TaskServer
|
||||
import com.faraphel.tasks_valider.database.TaskDatabase
|
||||
import com.faraphel.tasks_valider.ui.screen.communication.DEFAULT_SERVER_PORT
|
||||
import com.faraphel.tasks_valider.ui.screen.communication.RANGE_SERVER_PORT
|
||||
import com.faraphel.tasks_valider.ui.screen.task.TaskSessionScreen
|
||||
|
||||
|
||||
@Composable
|
||||
fun CommunicationWifiP2pServerScreen(activity: Activity, bwfManager: BwfManager) {
|
||||
fun CommunicationWifiP2pServerScreen(activity: Activity, bwdManager: BwdManager) {
|
||||
val client = remember { mutableStateOf<TaskClient?>(null) }
|
||||
|
||||
// TODO(Faraphel): fix and get a user
|
||||
// if the server is not created, prompt the user for the server configuration
|
||||
if (client.value == null) CommunicationWifiP2pServerContent(activity, bwfManager, client)
|
||||
// if (client.value == null) CommunicationWifiP2pServerContent(activity, bwfManager, client)
|
||||
// else, go to the base tasks screen
|
||||
else TaskSessionScreen(activity, client.value!!)
|
||||
// else TaskSessionScreen(activity, client.value!!)
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun CommunicationWifiP2pServerContent(
|
||||
activity: Activity,
|
||||
bwfManager: BwfManager,
|
||||
bwdManager: BwdManager,
|
||||
client: MutableState<TaskClient?>
|
||||
) {
|
||||
/*
|
||||
val expandedStudentList = remember { mutableStateOf(false) }
|
||||
val serverPort = remember { mutableIntStateOf(DEFAULT_SERVER_PORT) }
|
||||
|
||||
|
@ -90,17 +76,30 @@ fun CommunicationWifiP2pServerContent(
|
|||
"local"
|
||||
).build()
|
||||
|
||||
// Create the admin
|
||||
// TODO: the admin should be created from the card reader
|
||||
val adminPersonEntity = PersonEntity(
|
||||
"admin",
|
||||
"admin",
|
||||
"123456789",
|
||||
"admin",
|
||||
|
||||
)
|
||||
|
||||
// Insert the admin in the database
|
||||
database.personDao().insert(adminPersonEntity)
|
||||
|
||||
bwfManager.recreateGroup {
|
||||
// Create the server
|
||||
Log.i("room-server", "creating the server")
|
||||
val server = TaskServer(serverPort.intValue, database)
|
||||
val server = TaskServer(serverPort.intValue, database, adminPersonEntity)
|
||||
server.start()
|
||||
|
||||
// Get the client from the server
|
||||
client.value = server.getClientAdmin()
|
||||
client.value = server.getAdminClient()
|
||||
}
|
||||
}) {
|
||||
Text("Create")
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package com.faraphel.tasks_valider.ui.screen.scan.qr
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.content.pm.PackageManager
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.journeyapps.barcodescanner.BarcodeResult
|
||||
import com.journeyapps.barcodescanner.DecoratedBarcodeView
|
||||
import com.journeyapps.barcodescanner.DefaultDecoderFactory
|
||||
|
||||
/**
|
||||
* Screen to scan a Barcode / QR Code
|
||||
*/
|
||||
@Composable
|
||||
fun ScanBarcodeScreen(activity: Activity, barcode: MutableState<BarcodeResult?>) {
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
// check and prompt for the camera permission
|
||||
if (activity.checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED)
|
||||
// FIXME(Faraphel): seem to crash the application
|
||||
activity.requestPermissions(arrayOf(Manifest.permission.CAMERA), 1)
|
||||
|
||||
// AndroidView is used because "DecoratedBarcodeView" only support the legacy view system
|
||||
AndroidView(factory = {
|
||||
DecoratedBarcodeView(activity).apply {
|
||||
this.decoderFactory = DefaultDecoderFactory(listOf(BarcodeFormat.QR_CODE))
|
||||
this.initializeFromIntent(activity.intent)
|
||||
this.decodeContinuous { result -> barcode.value = result }
|
||||
this.resume()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
package com.faraphel.tasks_valider.ui.screen.task
|
||||
|
||||
import android.app.Activity
|
||||
import android.widget.Toast
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.navigation.NavController
|
||||
import com.faraphel.tasks_valider.connectivity.task.TaskClient
|
||||
import com.faraphel.tasks_valider.database.entities.PersonEntity
|
||||
import com.faraphel.tasks_valider.database.entities.ValidationEntity
|
||||
import com.faraphel.tasks_valider.ui.screen.scan.qr.ScanBarcodeScreen
|
||||
import com.journeyapps.barcodescanner.BarcodeResult
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
|
||||
|
||||
@Composable
|
||||
fun QuickValidationScreen(
|
||||
controller: NavController,
|
||||
activity: Activity,
|
||||
client: TaskClient,
|
||||
user: PersonEntity,
|
||||
) {
|
||||
val barcode = remember { mutableStateOf<BarcodeResult?>(null) }
|
||||
|
||||
// prompt for the qr code if not found
|
||||
if (barcode.value == null)
|
||||
return ScanBarcodeScreen(activity, barcode)
|
||||
|
||||
// show the content of the qr code
|
||||
val studentUrl = barcode.value!!.text.toHttpUrl()
|
||||
val cardId = studentUrl.pathSegments[0]
|
||||
|
||||
// when the barcode changed
|
||||
LaunchedEffect(cardId) {
|
||||
Thread {
|
||||
quickValidation(
|
||||
controller,
|
||||
activity,
|
||||
client,
|
||||
user,
|
||||
cardId,
|
||||
)
|
||||
}.start()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validate the latest task of a user from its student card
|
||||
*/
|
||||
fun quickValidation(
|
||||
controller: NavController,
|
||||
activity: Activity,
|
||||
client: TaskClient,
|
||||
user: PersonEntity,
|
||||
cardId: String,
|
||||
) {
|
||||
// action when an error occurred or everything worked
|
||||
fun finish() {
|
||||
activity.runOnUiThread {
|
||||
// go back to the main screen
|
||||
controller.navigateUp()
|
||||
}
|
||||
}
|
||||
|
||||
// requests all the persons
|
||||
val allPersons = client.personApi.getAll()
|
||||
// get the person with the matching card
|
||||
val person = allPersons.firstOrNull { person -> person.cardId == cardId }
|
||||
|
||||
if (person == null) {
|
||||
// tell to the user that this card is linked to nobody
|
||||
activity.runOnUiThread {
|
||||
Toast.makeText(activity, "No person found for that card.", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
return finish()
|
||||
}
|
||||
|
||||
// requests all the relation persons - subjects
|
||||
val allRelationsPersonSubject = client.relationPersonSessionSubjectApi.getAll()
|
||||
// get the corresponding relation
|
||||
val relationPersonSubject = allRelationsPersonSubject.first { relation -> relation.studentId == person.id }
|
||||
|
||||
// requests all the tasks
|
||||
val allTasks = client.taskApi.getAll()
|
||||
// get the corresponding tasks
|
||||
val tasks = allTasks
|
||||
.filter { task -> task.subjectId == relationPersonSubject.subjectId }
|
||||
.sortedBy { task -> task.order }
|
||||
|
||||
// requests all the validations
|
||||
val allValidations = client.validationApi.getAll()
|
||||
// get the corresponding relation
|
||||
val validations = allValidations.filter { validation -> validation.studentId == person.id }
|
||||
|
||||
// get the first task without any validation
|
||||
val task = tasks.firstOrNull { task ->
|
||||
// check in all the validations if the task is found
|
||||
val validation = validations.firstOrNull { validation -> validation.taskId == task.id }
|
||||
// keep the task if it has no validation
|
||||
validation == null
|
||||
}
|
||||
|
||||
if (task == null) {
|
||||
// tell to the user the action cannot be done
|
||||
activity.runOnUiThread {
|
||||
Toast.makeText(activity, "There are no tasks left.", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
return finish()
|
||||
}
|
||||
|
||||
// create a new validation on the server
|
||||
client.validationApi.save(
|
||||
ValidationEntity(
|
||||
teacherId=user.id,
|
||||
studentId=person.id,
|
||||
taskId=task.id,
|
||||
)
|
||||
)
|
||||
// confirm to the user the action was successful
|
||||
activity.runOnUiThread {
|
||||
Toast.makeText(activity, "Validated \"${task.title}\".", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
return finish()
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
package com.faraphel.tasks_valider.ui.screen.task
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.faraphel.tasks_valider.connectivity.task.TaskClient
|
||||
|
||||
|
||||
/**
|
||||
* This screen represent a session
|
||||
* @param activity the android activity
|
||||
* @param client an HTTP client that can communicate with the server
|
||||
*/
|
||||
@Composable
|
||||
fun TaskSessionScreen(activity: Activity, client: TaskClient) {
|
||||
Text("WIP : Session Screen")
|
||||
|
||||
/*
|
||||
val students = remember { mutableStateOf<List<TaskGroupEntity>?>(null) }
|
||||
|
||||
// title
|
||||
Text(text = "Task Group")
|
||||
|
||||
// if the groups are not yet defined, refresh the list
|
||||
if (groups.value == null) {
|
||||
Thread { refreshGroups(activity, client, groups) }.start()
|
||||
return
|
||||
}
|
||||
|
||||
// if the groups have already been defined, display them
|
||||
for (group in groups.value!!) {
|
||||
Text(text = group.toString())
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
fun refreshGroups(activity: Activity, client: TaskClient, groups: MutableState<List<TaskGroupEntity>?>) {
|
||||
// try to obtain the list of groups
|
||||
val response = client.get("entities/group")
|
||||
|
||||
// in case of error, notify it
|
||||
if (!response.isSuccessful) {
|
||||
Toast.makeText(activity, response.message, Toast.LENGTH_LONG).show()
|
||||
return
|
||||
}
|
||||
|
||||
// parse the list of groups
|
||||
groups.value = jsonParser.fromJson(
|
||||
response.body.toString(),
|
||||
object : TypeToken<List<TaskGroupEntity>>(){}
|
||||
)
|
||||
}
|
||||
*/
|
|
@ -0,0 +1,189 @@
|
|||
package com.faraphel.tasks_valider.ui.screen.task
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Activity.RESULT_OK
|
||||
import android.content.Intent
|
||||
import android.os.Environment
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.faraphel.tasks_valider.connectivity.task.TaskClient
|
||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskRole
|
||||
import com.faraphel.tasks_valider.database.entities.PersonEntity
|
||||
import com.faraphel.tasks_valider.utils.parser
|
||||
import java.io.File
|
||||
|
||||
|
||||
@Composable
|
||||
fun TaskSessionController(
|
||||
activity: Activity,
|
||||
client: TaskClient,
|
||||
user: PersonEntity,
|
||||
) {
|
||||
val controller = rememberNavController()
|
||||
|
||||
NavHost(
|
||||
navController = controller,
|
||||
startDestination = "main"
|
||||
) {
|
||||
composable("main") {
|
||||
TaskSessionScreen(controller, activity, client, user)
|
||||
}
|
||||
composable("quick_validation") {
|
||||
QuickValidationScreen(controller, activity, client, user)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This screen represent a session
|
||||
* @param activity the android activity
|
||||
* @param client an HTTP client that can communicate with the server
|
||||
*/
|
||||
@Composable
|
||||
fun TaskSessionScreen(
|
||||
controller: NavController,
|
||||
activity: Activity,
|
||||
client: TaskClient,
|
||||
user: PersonEntity,
|
||||
) {
|
||||
val students = remember { mutableStateOf<List<PersonEntity>?>(null) }
|
||||
val selectedStudent = remember { mutableStateOf<PersonEntity?>(null) }
|
||||
|
||||
// if the groups are not yet defined, refresh the list
|
||||
if (students.value == null)
|
||||
return LaunchedEffect(true) {
|
||||
Thread { refreshStudents(activity, client, students) }.start()
|
||||
}
|
||||
|
||||
if (selectedStudent.value != null)
|
||||
return TaskStudentScreen(
|
||||
activity,
|
||||
client,
|
||||
user,
|
||||
selectedStudent.value!!
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
// title
|
||||
Text(
|
||||
text = "Session",
|
||||
fontSize = 32.sp
|
||||
)
|
||||
|
||||
// separator
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// if the groups have already been defined, display them
|
||||
for (student in students.value!!) {
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp, horizontal = 16.dp),
|
||||
onClick = { selectedStudent.value = student },
|
||||
) {
|
||||
Text(text = student.fullName())
|
||||
}
|
||||
}
|
||||
|
||||
// separator
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
// buttons
|
||||
Row {
|
||||
// quick validation
|
||||
Button(onClick = { controller.navigate("quick_validation") }) {
|
||||
Text("Quick Validation")
|
||||
}
|
||||
Button(onClick = { Thread { exportToFile(activity, client) }.start() }) {
|
||||
Text("Export")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun refreshStudents(
|
||||
activity: Activity,
|
||||
client: TaskClient,
|
||||
students: MutableState<List<PersonEntity>?>
|
||||
) {
|
||||
try {
|
||||
// try to get all the persons in that session
|
||||
students.value = client.personApi.getAll()
|
||||
} catch (exception: Exception) {
|
||||
// in case of error, show a message
|
||||
return activity.runOnUiThread {
|
||||
Log.e("students", "$exception")
|
||||
Toast.makeText(activity, "Could not retrieve students.\n\n$exception", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun exportToFile(
|
||||
activity: Activity,
|
||||
client: TaskClient,
|
||||
) {
|
||||
// get all the values to export
|
||||
val allPersons = client.personApi.getAll()
|
||||
val allStudents = allPersons.filter { student -> student.role == TaskRole.STUDENT }
|
||||
val allRelationsStudentSessionSubject = client.relationPersonSessionSubjectApi.getAll()
|
||||
val allSubjects = client.subjectApi.getAll()
|
||||
val allTasks = client.taskApi.getAll()
|
||||
val allValidations = client.validationApi.getAll()
|
||||
|
||||
// for each student
|
||||
|
||||
val data = allStudents.map { student ->
|
||||
// fetch their subject
|
||||
val relationStudentSessionObject = allRelationsStudentSessionSubject.first { relation ->
|
||||
relation.studentId == student.id
|
||||
}
|
||||
val subject = allSubjects.first { subject -> subject.id == relationStudentSessionObject.subjectId }
|
||||
// get the tasks of this person
|
||||
val tasks = allTasks.filter { task -> task.subjectId == subject.id }
|
||||
|
||||
// for all the tasks
|
||||
val studentTasksData = tasks.map { task ->
|
||||
// if a validation match
|
||||
val isValidated = allValidations.any { validation -> validation.taskId == task.id }
|
||||
// return the pair of the task title and its validation
|
||||
mapOf(
|
||||
"name" to task.title,
|
||||
"validated" to isValidated,
|
||||
)
|
||||
}
|
||||
|
||||
// return the name of the student associated to his data
|
||||
mapOf(
|
||||
"name" to student.fullName(),
|
||||
"subject" to subject.name,
|
||||
"tasks" to studentTasksData,
|
||||
)
|
||||
}
|
||||
|
||||
val dataJson = parser.toJson(data)
|
||||
Log.i("export", dataJson)
|
||||
|
||||
// write all the data in a json file
|
||||
val file = File(activity.filesDir, "test.json")
|
||||
file.writeText(dataJson)
|
||||
|
||||
// TODO(Faraphel): prompt to open the file ?
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
package com.faraphel.tasks_valider.ui.screen.task
|
||||
|
||||
import android.app.Activity
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.faraphel.tasks_valider.connectivity.task.TaskClient
|
||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskPermission
|
||||
import com.faraphel.tasks_valider.database.entities.*
|
||||
import com.faraphel.tasks_valider.utils.dateTimeFormatter
|
||||
import com.faraphel.tasks_valider.utils.parser
|
||||
import com.google.gson.reflect.TypeToken
|
||||
|
||||
|
||||
/**
|
||||
* This screen represent a student
|
||||
* @param student the student object
|
||||
*/
|
||||
@Composable
|
||||
fun TaskStudentScreen(
|
||||
activity: Activity,
|
||||
client: TaskClient,
|
||||
user: PersonEntity,
|
||||
student: PersonEntity,
|
||||
) {
|
||||
val tasks = remember { mutableStateOf<List<TaskEntity>?>(null) }
|
||||
val validations = remember { mutableStateOf<List<ValidationEntity>?>(null) }
|
||||
|
||||
if (tasks.value == null || validations.value == null)
|
||||
Thread {
|
||||
refreshTasksValidations(
|
||||
activity,
|
||||
client,
|
||||
student,
|
||||
tasks,
|
||||
validations
|
||||
)
|
||||
}.start()
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
// title
|
||||
Text(text = "Student", fontSize = 32.sp)
|
||||
// student name - subtitle
|
||||
Text(text = student.fullName(), fontSize = 24.sp)
|
||||
|
||||
// separator
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// if both the list of tasks and validations are loaded
|
||||
if (!(tasks.value == null || validations.value == null)) {
|
||||
tasks.value?.forEach { task ->
|
||||
// get the validation
|
||||
val validation = validations.value!!.firstOrNull { validation -> validation.taskId == task.id }
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp, horizontal = 64.dp),
|
||||
) {
|
||||
Row {
|
||||
Column {
|
||||
// task title
|
||||
Text(text = task.title, fontWeight = FontWeight.Bold)
|
||||
// task description
|
||||
task.description?.let { description -> Text(description) }
|
||||
// if the task have been validated, show the date
|
||||
if (validation != null) Text(text = dateTimeFormatter.format(validation.date))
|
||||
}
|
||||
|
||||
// separator
|
||||
Spacer(modifier = Modifier.fillMaxWidth())
|
||||
|
||||
// the validation state
|
||||
Checkbox(
|
||||
checked = validation != null,
|
||||
enabled = user.role.permissions.contains(TaskPermission.WRITE),
|
||||
onCheckedChange = { state ->
|
||||
Thread {
|
||||
// TODO(Faraphel): simplify or put the UI refresh in the update function ?
|
||||
|
||||
// send a notification to the server about the validation
|
||||
updateValidation(
|
||||
client,
|
||||
state,
|
||||
validation ?: ValidationEntity(
|
||||
teacherId=user.id,
|
||||
studentId=student.id,
|
||||
taskId=task.id,
|
||||
)
|
||||
)
|
||||
// refresh the UI
|
||||
refreshTasksValidations(
|
||||
activity,
|
||||
client,
|
||||
student,
|
||||
tasks,
|
||||
validations
|
||||
)
|
||||
}.start()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun refreshTasksValidations(
|
||||
activity: Activity,
|
||||
client: TaskClient,
|
||||
student: PersonEntity,
|
||||
tasks: MutableState<List<TaskEntity>?>,
|
||||
validations: MutableState<List<ValidationEntity>?>,
|
||||
) {
|
||||
// try to obtain the list of subject
|
||||
val allRelationsPersonSessionSubject = client.relationPersonSessionSubjectApi.getAll()
|
||||
// get the subject that the student is using
|
||||
val relationPersonSessionSubject = allRelationsPersonSessionSubject.firstOrNull { relation ->
|
||||
relation.studentId == student.id
|
||||
}
|
||||
|
||||
if (relationPersonSessionSubject == null)
|
||||
// TODO(Faraphel): should be able to assign a subject ?
|
||||
return activity.runOnUiThread { Toast.makeText(activity, "No subject assigned", Toast.LENGTH_LONG).show() }
|
||||
|
||||
// try to obtain the list of tasks
|
||||
val allTasks = client.taskApi.getAll()
|
||||
|
||||
// get the tasks that are linked to this subject
|
||||
tasks.value = allTasks
|
||||
.filter { task -> task.subjectId == relationPersonSessionSubject.subjectId }
|
||||
.sortedBy { task -> task.order }
|
||||
|
||||
// try to obtain the list of validations
|
||||
val allValidations = client.validationApi.getAll()
|
||||
// filter only the interesting validations
|
||||
validations.value = allValidations.filter { validation ->
|
||||
validation.studentId == student.id &&
|
||||
validation.taskId in allTasks.map { task -> task.id }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun updateValidation(client: TaskClient, checked: Boolean, validation: ValidationEntity) {
|
||||
if (checked)
|
||||
// if the validation is not set, create it
|
||||
client.validationApi.save(validation)
|
||||
else
|
||||
// if the validation is set, delete it
|
||||
client.validationApi.delete(validation)
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package com.faraphel.tasks_valider.utils.converters
|
||||
|
||||
import androidx.room.TypeConverter
|
||||
import com.google.gson.*
|
||||
import java.lang.reflect.Type
|
||||
import java.time.Instant
|
||||
|
||||
|
||||
/**
|
||||
* Allow for automatically converting Instant values when using them in the database or in a json parser.
|
||||
*/
|
||||
class InstantConverter : JsonDeserializer<Instant>, JsonSerializer<Instant> {
|
||||
/**
|
||||
* Convert a long into an instant
|
||||
* @param value the number of milliseconds since the epoch of the time
|
||||
* @return the Instant object
|
||||
*/
|
||||
@TypeConverter
|
||||
fun deserialize(value: Long): Instant {
|
||||
return Instant.ofEpochMilli(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a long into an instant
|
||||
* @param instant the Instant object
|
||||
* @return the number of milliseconds since the epoch of the time
|
||||
*/
|
||||
@TypeConverter
|
||||
fun serialize(instant: Instant): Long {
|
||||
return instant.toEpochMilli()
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a long into an instant
|
||||
* @param json the json object
|
||||
* @return the Instant object
|
||||
*/
|
||||
override fun deserialize(json: JsonElement, type: Type, context: JsonDeserializationContext): Instant =
|
||||
this.deserialize(json.asLong)
|
||||
|
||||
/**
|
||||
* Convert a long into an instant
|
||||
* @param instant the instant object
|
||||
* @return the json object
|
||||
*/
|
||||
override fun serialize(instant: Instant, type: Type, context: JsonSerializationContext): JsonElement =
|
||||
JsonPrimitive(this.serialize(instant))
|
||||
}
|
11
app/src/main/java/com/faraphel/tasks_valider/utils/json.kt
Normal file
11
app/src/main/java/com/faraphel/tasks_valider/utils/json.kt
Normal file
|
@ -0,0 +1,11 @@
|
|||
package com.faraphel.tasks_valider.utils
|
||||
|
||||
import com.faraphel.tasks_valider.utils.converters.InstantConverter
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import java.time.Instant
|
||||
|
||||
|
||||
val parser: Gson = GsonBuilder()
|
||||
.registerTypeAdapter(Instant::class.java, InstantConverter())
|
||||
.create()
|
|
@ -0,0 +1,22 @@
|
|||
package com.faraphel.tasks_valider.utils
|
||||
|
||||
import fi.iki.elonen.NanoHTTPD
|
||||
import java.nio.charset.Charset
|
||||
|
||||
|
||||
/**
|
||||
* Return the body of a request as a string.
|
||||
* :param charset: the encoding of the body
|
||||
*/
|
||||
fun NanoHTTPD.IHTTPSession.getBody(
|
||||
charset: Charset = Charset.forName("UTF-8")
|
||||
): String {
|
||||
// get the length of the body
|
||||
val length = this.headers["content-length"]!!.toInt()
|
||||
// prepare a buffer for the body
|
||||
val buffer = ByteArray(length)
|
||||
// read the body into the buffer
|
||||
this.inputStream.read(buffer, 0, length)
|
||||
// convert that buffer into a string
|
||||
return buffer.toString(charset)
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package com.faraphel.tasks_valider.utils
|
||||
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
|
||||
var dateTimeFormatter: DateTimeFormatter =
|
||||
DateTimeFormatter.ofPattern("yyyy/MM/dd hh:mm:ss").withZone(ZoneId.systemDefault())
|
Loading…
Reference in a new issue