[API/Usos] Add syncing Courses and Terms.

This commit is contained in:
Kuba Szczodrzyński 2022-10-16 00:09:51 +02:00
parent 93ccdbdeb7
commit 8097e8d06d
No known key found for this signature in database
GPG Key ID: 70CB8A85BA1633CB
10 changed files with 205 additions and 24 deletions

View File

@ -203,6 +203,7 @@ const val ERROR_PODLASIE_API_DATA_MISSING = 632
const val ERROR_USOS_OAUTH_GOT_DIFFERENT_TOKEN = 702
const val ERROR_USOS_OAUTH_INCOMPLETE_RESPONSE = 703
const val ERROR_USOS_NO_STUDENT_PROGRAMMES = 704
const val ERROR_USOS_API_INCOMPLETE_RESPONSE = 705
const val ERROR_TEMPLATE_WEB_OTHER = 801

View File

@ -25,7 +25,7 @@ class DataUsos(
}
}
override fun generateUserCode() = "$schoolId:${studentNumber ?: studentId}"
override fun generateUserCode() = "$schoolId:${profile?.studentNumber ?: studentId}"
var schoolId: String?
get() { mSchoolId = mSchoolId ?: loginStore.getLoginData("schoolId", null); return mSchoolId }
@ -67,13 +67,8 @@ class DataUsos(
set(value) { loginStore.putLoginData("oauthTokenIsUser", value); mOauthTokenIsUser = value }
private var mOauthTokenIsUser: Boolean? = null
var studentId: String?
get() { mStudentId = mStudentId ?: profile?.getStudentData("studentId", null); return mStudentId }
var studentId: Int
get() { mStudentId = mStudentId ?: profile?.getStudentData("studentId", 0); return mStudentId ?: 0 }
set(value) { profile?.putStudentData("studentId", value) ?: return; mStudentId = value }
private var mStudentId: String? = null
var studentNumber: String?
get() { mStudentNumber = mStudentNumber ?: profile?.getStudentData("studentNumber", null); return mStudentNumber }
set(value) { profile?.putStudentData("studentNumber", value) ?: return; mStudentNumber = value }
private var mStudentNumber: String? = null
private var mStudentId: Int? = null
}

View File

@ -6,6 +6,7 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.usos
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.UsosData
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.firstlogin.UsosFirstLogin
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.login.UsosLogin
import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent
@ -58,9 +59,9 @@ class Usos(
d(TAG, "LoginMethod IDs: ${data.targetLoginMethodIds}")
d(TAG, "Endpoint IDs: ${data.targetEndpointIds}")
UsosLogin(data) {
/*UsosData(data) {
UsosData(data) {
completed()
}*/
}
}
}

View File

@ -4,15 +4,26 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.usos
import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.FEATURE_ALWAYS_NEEDED
import pl.szczodrzynski.edziennik.data.api.FEATURE_STUDENT_INFO
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_USOS_API
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_USOS
import pl.szczodrzynski.edziennik.data.api.FEATURE_TEAM_INFO
import pl.szczodrzynski.edziennik.data.api.models.Feature
const val ENDPOINT_USOS_API_USER = 7000
const val ENDPOINT_USOS_API_TERMS = 7010
const val ENDPOINT_USOS_API_COURSES = 7020
val UsosFeatures = listOf(
Feature(LOGIN_TYPE_USOS, FEATURE_STUDENT_INFO, listOf(
ENDPOINT_USOS_API_USER to LOGIN_METHOD_USOS_API,
), listOf(LOGIN_METHOD_USOS_API)),
Feature(LOGIN_TYPE_USOS, FEATURE_SCHOOL_INFO, listOf(
ENDPOINT_USOS_API_TERMS to LOGIN_METHOD_USOS_API,
), listOf(LOGIN_METHOD_USOS_API)),
Feature(LOGIN_TYPE_USOS, FEATURE_TEAM_INFO, listOf(
ENDPOINT_USOS_API_COURSES to LOGIN_METHOD_USOS_API,
), listOf(LOGIN_METHOD_USOS_API)),
)

View File

@ -6,7 +6,6 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.usos.data
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import im.wangchao.mhttp.AbsCallbackHandler
import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.body.MediaTypeUtils
@ -17,10 +16,7 @@ import pl.szczodrzynski.edziennik.data.api.ERROR_REQUEST_FAILURE
import pl.szczodrzynski.edziennik.data.api.SERVER_USER_AGENT
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.DataUsos
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.ext.currentTimeUnix
import pl.szczodrzynski.edziennik.ext.hmacSHA1
import pl.szczodrzynski.edziennik.ext.toQueryString
import pl.szczodrzynski.edziennik.ext.urlEncode
import pl.szczodrzynski.edziennik.ext.*
import pl.szczodrzynski.edziennik.utils.Utils.d
import java.net.HttpURLConnection.*
import java.util.UUID
@ -42,6 +38,9 @@ open class UsosApi(open val data: DataUsos, open val lastSync: Long?) {
val profile
get() = data.profile
protected fun JsonObject.getLangString(key: String) =
this.getJsonObject(key)?.getString("pl")
private fun valueToString(value: Any) = when (value) {
is String -> value
is Number -> value.toString()
@ -74,15 +73,22 @@ open class UsosApi(open val data: DataUsos, open val lastSync: Long?) {
fun <T> apiRequest(
tag: String,
service: String,
params: Map<String, Any>,
params: Map<String, Any>? = null,
fields: List<Any>? = null,
responseType: ResponseType,
onSuccess: (data: T, response: Response?) -> Unit,
) {
val url = "${data.instanceUrl}services/$service"
d(tag, "Request: Usos/Api - $url")
val formData = params.mapValues {
valueToString(it.value)
}
val formData = mutableMapOf<String, String>()
if (params != null)
formData.putAll(params.mapValues {
valueToString(it.value)
})
if (fields != null)
formData["fields"] = valueToString(fields)
val auth = mutableMapOf(
"realm" to url,
"oauth_consumer_key" to (data.oauthConsumerKey ?: ""),

View File

@ -7,7 +7,11 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.usos.data
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.edziennik.template.data.web.TemplateWebSample
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.DataUsos
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.ENDPOINT_USOS_API_COURSES
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.ENDPOINT_USOS_API_TERMS
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.ENDPOINT_USOS_API_USER
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api.UsosApiCourses
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api.UsosApiTerms
import pl.szczodrzynski.edziennik.utils.Utils.d
class UsosData(val data: DataUsos, val onSuccess: () -> Unit) {
@ -39,9 +43,17 @@ class UsosData(val data: DataUsos, val onSuccess: () -> Unit) {
private fun useEndpoint(endpointId: Int, lastSync: Long?, onSuccess: (endpointId: Int) -> Unit) {
d(TAG, "Using endpoint $endpointId. Last sync time = $lastSync")
when (endpointId) {
ENDPOINT_USOS_API_USER -> {
/*ENDPOINT_USOS_API_USER -> {
data.startProgress(R.string.edziennik_progress_endpoint_student_info)
// TemplateWebSample(data, lastSync, onSuccess)
}*/
ENDPOINT_USOS_API_TERMS -> {
data.startProgress(R.string.edziennik_progress_endpoint_school_info)
UsosApiTerms(data, lastSync, onSuccess)
}
ENDPOINT_USOS_API_COURSES -> {
data.startProgress(R.string.edziennik_progress_endpoint_teams)
UsosApiCourses(data, lastSync, onSuccess)
}
else -> onSuccess(endpointId)
}

View File

@ -0,0 +1,86 @@
/*
* Copyright (c) Kuba Szczodrzyński 2022-10-15.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.data.api.ERROR_USOS_API_INCOMPLETE_RESPONSE
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.DataUsos
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.ENDPOINT_USOS_API_COURSES
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.UsosApi
import pl.szczodrzynski.edziennik.data.db.entity.Team
import pl.szczodrzynski.edziennik.ext.*
class UsosApiCourses(
override val data: DataUsos,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit,
) : UsosApi(data, lastSync) {
companion object {
const val TAG = "UsosApiCourses"
}
init {
apiRequest<JsonObject>(
tag = TAG,
service = "courses/user",
fields = listOf(
// "terms" to listOf("id", "name", "start_date", "end_date"),
"course_editions" to listOf(
"course_id",
// "course_name",
// "term_id",
"user_groups" to listOf(
"course_unit_id",
"group_number",
"class_type",
"class_type_id",
// "lecturers",
),
),
),
responseType = ResponseType.OBJECT,
) { json, response ->
if (!processResponse(json)) {
data.error(TAG, ERROR_USOS_API_INCOMPLETE_RESPONSE, response)
return@apiRequest
}
data.setSyncNext(ENDPOINT_USOS_API_COURSES, 2 * DAY)
onSuccess(ENDPOINT_USOS_API_COURSES)
}
}
private fun processResponse(json: JsonObject): Boolean {
// val term = json.getJsonArray("terms")?.firstOrNull() ?: return false
val courseEditions = json.getJsonObject("course_editions")
?.entrySet()
?.flatMap { it.value.asJsonArray }
?.map { it.asJsonObject } ?: return false
var hasValidTeam = false
for (courseEdition in courseEditions) {
val courseId = courseEdition.getString("course_id") ?: continue
// val courseName = courseEdition.getLangString("course_name") ?: continue
val userGroups = courseEdition.getJsonArray("user_groups")?.asJsonObjectList() ?: continue
for (userGroup in userGroups) {
val courseUnitId = userGroup.getLong("course_unit_id") ?: continue
val groupNumber = userGroup.getInt("group_number") ?: continue
val classType = userGroup.getLangString("class_type") ?: continue
val classTypeId = userGroup.getString("class_type_id") ?: continue
data.teamList.put(courseUnitId, Team(
profileId,
courseUnitId,
"$classType $groupNumber ($courseId)",
2,
"${data.schoolId}:${courseId} $classTypeId$groupNumber",
-1,
))
hasValidTeam = true
}
}
return hasValidTeam
}
}

View File

@ -0,0 +1,65 @@
/*
* Copyright (c) Kuba Szczodrzyński 2022-10-15.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.data.api.ERROR_USOS_API_INCOMPLETE_RESPONSE
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.DataUsos
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.ENDPOINT_USOS_API_TERMS
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.UsosApi
import pl.szczodrzynski.edziennik.ext.*
import pl.szczodrzynski.edziennik.utils.models.Date
class UsosApiTerms(
override val data: DataUsos,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit,
) : UsosApi(data, lastSync) {
companion object {
const val TAG = "UsosApiTerms"
}
init {
apiRequest<JsonArray>(
tag = TAG,
service = "terms/search",
params = mapOf(
"query" to Date.getToday().year.toString(),
),
responseType = ResponseType.ARRAY,
) { json, response ->
if (!processResponse(json)) {
data.error(UsosApiCourses.TAG, ERROR_USOS_API_INCOMPLETE_RESPONSE, response)
return@apiRequest
}
data.setSyncNext(ENDPOINT_USOS_API_TERMS, 7 * DAY)
onSuccess(ENDPOINT_USOS_API_TERMS)
}
}
private fun processResponse(json: JsonArray): Boolean {
val dates = mutableSetOf<Date>()
for (term in json.asJsonObjectList()) {
if (!term.getBoolean("is_active", false))
continue
val startDate = term.getString("start_date")?.let { Date.fromY_m_d(it) }
val finishDate = term.getString("finish_date")?.let { Date.fromY_m_d(it) }
if (startDate != null)
dates += startDate
if (finishDate != null)
dates += finishDate
}
val datesSorted = dates.sorted()
if (datesSorted.size != 3)
return false
profile?.studentSchoolYearStart = datesSorted[0].year
profile?.dateSemester1Start = datesSorted[0]
profile?.dateSemester2Start = datesSorted[1]
profile?.dateYearEnd = datesSorted[2]
return true
}
}

View File

@ -68,9 +68,9 @@ class UsosFirstLogin(val data: DataUsos, val onSuccess: () -> Unit) {
accountName = null, // student account
studentData = JsonObject(
"studentId" to json.getInt("id"),
"studentNumber" to json.getInt("student_number"),
),
).also {
it.studentNumber = json.getInt("student_number", -1)
it.studentClassName = programmes.getJsonObject(0).getJsonObject("programme").getString("id")
}

View File

@ -233,6 +233,10 @@ open class Profile(
MainActivity.DRAWER_ITEM_GRADES,
MainActivity.DRAWER_ITEM_HOMEWORK
)
LOGIN_TYPE_USOS -> listOf(
MainActivity.DRAWER_ITEM_TIMETABLE,
MainActivity.DRAWER_ITEM_AGENDA
)
else -> listOf(
MainActivity.DRAWER_ITEM_TIMETABLE,
MainActivity.DRAWER_ITEM_AGENDA,