Skip to content

Commit

Permalink
Merge pull request #66 from dedis/http-service
Browse files Browse the repository at this point in the history
Add the backend connection to register new users
  • Loading branch information
jbsv authored Jan 25, 2024
2 parents 0e2931e + 3e27da7 commit b2e2bf9
Show file tree
Hide file tree
Showing 38 changed files with 495 additions and 71 deletions.
6 changes: 2 additions & 4 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,6 @@ dependencies {
implementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.13.4'
implementation 'com.google.mlkit:barcode-scanning:17.2.0'
implementation 'com.google.mlkit:text-recognition:16.0.0'
// ReactiveX
implementation 'io.reactivex.rxjava3:rxjava:3.1.8'
implementation 'io.reactivex.rxjava3:rxkotlin:3.0.1'

implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
Expand All @@ -212,7 +209,8 @@ dependencies {
implementation 'com.madgag.spongycastle:prov:1.58.0.0'

// HTTP Library
implementation 'com.google.android.gms:play-services-cronet:18.0.1'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-jackson:2.9.0'

debugImplementation 'junit:junit:4.13.2'
debugImplementation 'androidx.test.ext:junit-ktx:1.1.5'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package com.epfl.dedis.hbt.test.ui.page.register
import androidx.annotation.IdRes
import com.epfl.dedis.hbt.R

object ScanPassportFragmentPage {
object PassportScanFragmentPage {

@IdRes
fun scanPassportFragmentId() = R.id.scanPassportFragment
fun scanPassportFragmentId() = R.id.passportScanFragment
}
3 changes: 3 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
android:required="true" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera.any" />
<uses-permission android:name="android.permission.INTERNET" />

<application
android:name=".HBTApplication"
android:allowBackup="true"
Expand All @@ -18,6 +20,7 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Hbt"
android:networkSecurityConfig="@xml/network_security_config"
tools:targetApi="31">
<activity
android:name=".ui.MainActivity"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.epfl.dedis.hbt.data.document

import com.fasterxml.jackson.annotation.JsonProperty

data class Document(
@JsonProperty("doc_id") val id: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.epfl.dedis.hbt.data.document

import java.io.Serializable

data class Portrait(
val type: String,
val data: ByteArray
) : Serializable {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as Portrait

if (type != other.type) return false
return data.contentEquals(other.data)
}

override fun hashCode(): Int {
var result = type.hashCode()
result = 31 * result + data.contentHashCode()
return result
}

override fun toString(): String {
return "Portrait(type='$type', data=${data.contentToString()})"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package com.epfl.dedis.hbt.data.user

import android.content.SharedPreferences
import com.epfl.dedis.hbt.data.Result
import com.epfl.dedis.hbt.data.document.Portrait
import com.epfl.dedis.hbt.service.document.DocumentService
import com.epfl.dedis.hbt.service.json.JsonService
import com.epfl.dedis.hbt.service.json.JsonType.USER_DATA
import javax.inject.Inject
Expand All @@ -13,7 +15,8 @@ import javax.inject.Singleton
@Singleton
class UserDataSource @Inject constructor(
private val sharedPref: SharedPreferences,
private val jsonService: JsonService
private val jsonService: JsonService,
private val documentService: DocumentService
) {

private val usernamesKey: String = "users"
Expand Down Expand Up @@ -47,11 +50,22 @@ class UserDataSource @Inject constructor(
return username == usernamesKey || users.containsKey(username)
}

fun register(username: String, pincode: Int, passport: String, role: Role): Result<User> {
if (isRegistered(username)) return Result.Error(Exception("Already registered"))
suspend fun register(
username: String,
pincode: Int,
passport: String,
role: Role,
portrait: Portrait
): Result<User> {
//if (isRegistered(username)) return Result.Error(Exception("Already registered"))

// create user
val user = User(username, pincode, passport, role)
val result = documentService.create(user, portrait, false)
if (result is Result.Error) {
return result
}

users[username] = user

//create wallet
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.epfl.dedis.hbt.data.user

import com.epfl.dedis.hbt.data.Result
import com.epfl.dedis.hbt.data.document.Portrait
import javax.inject.Inject
import javax.inject.Singleton

Expand Down Expand Up @@ -36,9 +37,15 @@ class UserRepository @Inject constructor(private val dataSource: UserDataSource)
loggedInUser = null
}

fun register(username: String, pincode: String, passport: String, role: Role): Result<User> {
suspend fun register(
username: String,
pincode: String,
passport: String,
role: Role,
portrait: Portrait
): Result<User> {
val pin = pincode.toIntOrNull() ?: return Result.Error(NumberFormatException())
val result = dataSource.register(username, pin, passport, role)
val result = dataSource.register(username, pin, passport, role, portrait)

if (result is Result.Success) {
setLoggedInUser(result.data)
Expand Down
41 changes: 41 additions & 0 deletions android/app/src/main/java/com/epfl/dedis/hbt/di/HttpModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.epfl.dedis.hbt.di

import com.epfl.dedis.hbt.service.document.DocumentEndpoint
import com.epfl.dedis.hbt.service.http.ResultCallAdapterFactory
import com.fasterxml.jackson.databind.ObjectMapper
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import retrofit2.Retrofit
import retrofit2.converter.jackson.JacksonConverterFactory
import javax.inject.Qualifier
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object HttpModule {

@BaseURL
@Provides
@Singleton
fun provideBaseURL() = "http://10.0.2.2:3000"

@Provides
@Singleton
fun provideRetrofit(@BaseURL baseUrl: String, mapper: ObjectMapper): Retrofit =
Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(JacksonConverterFactory.create(mapper))
.addCallAdapterFactory(ResultCallAdapterFactory())
.build()

@Provides
@Singleton
fun provideDocumentService(retrofit: Retrofit): DocumentEndpoint =
retrofit.create(DocumentEndpoint::class.java)

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BaseURL
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ object JsonModule {

@Provides
@Singleton
fun provideObjectMapper() = ObjectMapper().registerKotlinModule()
fun provideObjectMapper(): ObjectMapper = ObjectMapper().registerKotlinModule()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.epfl.dedis.hbt.service.document

import com.epfl.dedis.hbt.data.Result
import com.epfl.dedis.hbt.data.document.Document
import okhttp3.MultipartBody
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.Part

interface DocumentEndpoint {

@Multipart
@POST("document")
suspend fun create(
@Part("name") name: String,
@Part("passport") passport: String,
@Part("role") role: Int,
@Part portrait: MultipartBody.Part,
@Part("registered") registered: Boolean,
): Result<Document>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.epfl.dedis.hbt.service.document

import com.epfl.dedis.hbt.data.Result
import com.epfl.dedis.hbt.data.document.Document
import com.epfl.dedis.hbt.data.document.Portrait
import com.epfl.dedis.hbt.data.user.User
import okhttp3.MediaType
import okhttp3.MultipartBody
import okhttp3.RequestBody
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class DocumentService @Inject constructor(private val endpoint: DocumentEndpoint) {

suspend fun create(user: User, portrait: Portrait, registered: Boolean): Result<Document> =
endpoint.create(
user.name,
user.passport,
user.role.ordinal,
MultipartBody.Part.createFormData("portrait",
"portrait.png",
RequestBody.create(MediaType.parse(portrait.type), portrait.data)),
registered
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.epfl.dedis.hbt.service.http

import com.epfl.dedis.hbt.data.Result
import okhttp3.Request
import okio.Timeout
import retrofit2.Call
import retrofit2.Callback
import retrofit2.HttpException
import retrofit2.Response
import java.io.IOException

class ResultCall<T : Any>(private val delegate: Call<T>) : Call<Result<T>> {
override fun enqueue(callback: Callback<Result<T>>) {
delegate.enqueue(
object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
if (response.isSuccessful) {
callback.onResponse(
this@ResultCall,
Response.success(
response.code(),
Result.Success(response.body()!!)
)
)
} else {
callback.onResponse(
this@ResultCall,
Response.success(
Result.Error(
HttpException(response)
)
)
)
}
}

override fun onFailure(call: Call<T>, t: Throwable) {
val errorMessage = when (t) {
is IOException -> "No internet connection"
is HttpException -> "Something went wrong!"
else -> t.localizedMessage
}
callback.onResponse(
this@ResultCall,
Response.success(Result.Error(RuntimeException(errorMessage, t)))
)
}
}
)
}

override fun isExecuted(): Boolean {
return delegate.isExecuted
}

override fun execute(): Response<Result<T>> {
return Response.success(Result.Success(delegate.execute().body()!!))
}

override fun cancel() {
delegate.cancel()
}

override fun isCanceled(): Boolean {
return delegate.isCanceled
}

override fun clone(): Call<Result<T>> {
return ResultCall(delegate.clone())
}

override fun request(): Request {
return delegate.request()
}

override fun timeout(): Timeout {
return delegate.timeout()
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.epfl.dedis.hbt.service.http

import com.epfl.dedis.hbt.data.Result
import retrofit2.Call
import retrofit2.CallAdapter
import retrofit2.Retrofit
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type

class ResultCallAdapterFactory : CallAdapter.Factory() {
override fun get(
returnType: Type,
annotations: Array<out Annotation>,
retrofit: Retrofit
): CallAdapter<*, *>? {
if (getRawType(returnType) != Call::class.java || returnType !is ParameterizedType) {
return null
}
val upperBound = getParameterUpperBound(0, returnType)

return if (upperBound is ParameterizedType && upperBound.rawType == Result::class.java) {
object : CallAdapter<Any, Call<Result<*>>> {
override fun responseType(): Type = getParameterUpperBound(0, upperBound)

override fun adapt(call: Call<Any>): Call<Result<*>> = ResultCall(call)
}
} else {
null
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.epfl.dedis.hbt.service.passport

import com.epfl.dedis.hbt.data.document.Portrait
import com.epfl.dedis.hbt.service.passport.mrz.MRZInfo
import org.jmrtd.lds.SODFile
import org.jmrtd.lds.icao.DG11File

data class Passport(
val mrzInfo: MRZInfo,
val sodFile: SODFile,
val portrait: Portrait,
val dg11File: DG11File?
)
Loading

0 comments on commit b2e2bf9

Please sign in to comment.