diff --git a/Demo/app/build.gradle b/Demo/app/build.gradle
index c88fc9a..4cb665e 100644
--- a/Demo/app/build.gradle
+++ b/Demo/app/build.gradle
@@ -3,6 +3,7 @@ plugins {
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
id 'androidx.navigation.safeargs.kotlin'
+ id 'com.google.dagger.hilt.android'
}
android {
@@ -37,11 +38,17 @@ android {
viewBinding true
dataBinding true
}
+
+ kapt {
+ correctErrorTypes true
+ }
}
dependencies {
def lifecycle_version = "2.6.1"
def nav_version = "2.5.3"
+ def retrofit_version = "2.9.0"
+ def hilt_version = "2.46.1"
implementation 'androidx.core:core-ktx:1.10.1'
implementation 'androidx.appcompat:appcompat:1.6.1'
@@ -54,6 +61,7 @@ dependencies {
implementation "androidx.preference:preference-ktx:1.2.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1"
implementation 'androidx.fragment:fragment-ktx:1.5.7'
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1"
// Navigation
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
@@ -65,6 +73,17 @@ dependencies {
// OkHttp3
implementation "com.squareup.okhttp3:okhttp:4.10.0"
+ // Retrofit2
+ implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
+ implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
+
+ // Hilt
+ implementation "com.google.dagger:hilt-android:$hilt_version"
+ kapt "com.google.dagger:hilt-compiler:$hilt_version"
+
+ // Glide
+ implementation "com.github.bumptech.glide:glide:4.13.2"
+
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
diff --git a/Demo/app/src/main/AndroidManifest.xml b/Demo/app/src/main/AndroidManifest.xml
index 3a1bbf8..7f07300 100644
--- a/Demo/app/src/main/AndroidManifest.xml
+++ b/Demo/app/src/main/AndroidManifest.xml
@@ -87,10 +87,33 @@
+ android:exported="true"
+ android:theme="@style/GitHubTheme">
+
+
+
+
+
+
+
+
+
+
+
+
+
+)
+
+sealed interface ProfileDetail {
+
+ val type: DetailType
+
+ data class ProfileName(
+ val avatar: String,
+ val username: String,
+ val name: String? = null,
+ override val type: DetailType = DetailType.NAME
+ ) : ProfileDetail
+
+ data class ProfileInfo(
+ @DrawableRes val icon: Int,
+ val title: String,
+ override val type: DetailType = DetailType.INFO
+ ) : ProfileDetail {
+ companion object {
+
+ fun from(userResponse: UserResponse) = buildList {
+ with(userResponse) {
+ bio?.let {
+ add(ProfileInfo(R.drawable.ic_info, it))
+ }
+ company?.let {
+ add(ProfileInfo(R.drawable.ic_chat, it))
+ }
+ blog?.let {
+ add(ProfileInfo(R.drawable.ic_link_logo, it))
+ }
+ email?.let {
+ add(ProfileInfo(R.drawable.ic_email, it))
+ }
+ add(
+ ProfileInfo(
+ R.drawable.ic_person_24,
+ ResourceHelper.resources.getString(R.string.follow_info, followers, following)
+ )
+ )
+ }
+ }
+ }
+ }
+}
+
+sealed interface ProfileInfo {
+ val type: ProfileType
+
+ data class ProfileCard(
+ val profileDetail: List,
+ override val type: ProfileType = ProfileType.DETAIL
+ ) : ProfileInfo {
+ companion object {
+
+ fun from(userResponse: UserResponse): ProfileCard {
+ val profileName = with(userResponse) {
+ ProfileDetail.ProfileName(avatarUrl, username, name)
+ }
+ val profileDetails: MutableList =
+ ProfileDetail.ProfileInfo.from(userResponse).toMutableList()
+ profileDetails.add(0, profileName)
+ return ProfileCard(profileDetails)
+ }
+ }
+ }
+
+ data class ProfileItem(
+ @DrawableRes val icon: Int,
+ @ColorInt val iconBackground: Int,
+ val title: String,
+ val count: Int? = null,
+ override val type: ProfileType = ProfileType.ITEM
+ ) : ProfileInfo {
+
+ companion object {
+ fun from(userResponse: UserResponse) = with(userResponse) {
+ with(ResourceHelper.resources) {
+ listOf(
+ ProfileItem(
+ R.drawable.ic_repo_24,
+ getColor(R.color.github_pull, null),
+ getString(R.string.repositories),
+ publicRepos
+ ), ProfileItem(
+ R.drawable.ic_git_pull_request,
+ getColor(R.color.github_gists, null),
+ getString(R.string.gists),
+ publicGists
+ ), ProfileItem(
+ R.drawable.ic_organization_24,
+ getColor(R.color.github_organization, null),
+ getString(R.string.organizations)
+ ), ProfileItem(
+ R.drawable.ic_star_24,
+ getColor(R.color.github_starred, null),
+ getString(R.string.starred)
+ )
+ )
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/githubclient/data/local/Release.kt b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/local/Release.kt
new file mode 100644
index 0000000..5efba7d
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/local/Release.kt
@@ -0,0 +1,19 @@
+package com.krunal.demo.githubclient.data.local
+
+import java.util.Date
+
+data class Release(
+ val repoFullName: String,
+ val releaseName: String,
+ val author: String,
+ val authorAvatar: String,
+ val releaseDate: Date,
+ val releaseAssets: List
+)
+
+data class ReleaseAsset(
+ val name: String,
+ val size: Int,
+ val contentType: String,
+ val downloadUrl: String,
+)
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/githubclient/data/local/Repo.kt b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/local/Repo.kt
new file mode 100644
index 0000000..8a1d95c
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/local/Repo.kt
@@ -0,0 +1,50 @@
+package com.krunal.demo.githubclient.data.local
+
+import androidx.annotation.ColorInt
+import androidx.annotation.DrawableRes
+import com.krunal.demo.R
+import com.krunal.demo.githubclient.data.remote.model.response.RepositoryResponse
+import com.krunal.demo.helpers.ResourceHelper
+
+data class RepoCard(
+ val avatar: String, val username: String, val repository: String, val repoFullName: String
+)
+
+data class RepoDetail(
+ val avatar: String,
+ val username: String,
+ val repository: String,
+ val repoFullName: String,
+ val description: String?,
+ val website: String?,
+ val starCount: Int,
+ val watchCount: Int?,
+ val forkCount: Int?,
+ val repoItems: List = emptyList()
+)
+
+data class RepoDetailItem(
+ @DrawableRes val icon: Int,
+ @ColorInt val iconBackground: Int,
+ val title: String,
+ val count: Int? = null
+) {
+ companion object {
+ fun from(repositoryResponse: RepositoryResponse) = with(repositoryResponse) {
+ with(ResourceHelper.resources) {
+ listOf(
+ RepoDetailItem(
+ R.drawable.ic_git_pull_request,
+ getColor(R.color.github_pull, null),
+ getString(R.string.issues),
+ ),
+ RepoDetailItem(
+ R.drawable.ic_git_pull_request,
+ getColor(R.color.github_pull, null),
+ getString(R.string.releases)
+ ),
+ )
+ }
+ }
+ }
+}
diff --git a/Demo/app/src/main/java/com/krunal/demo/githubclient/data/local/UpdateProfileDetail.kt b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/local/UpdateProfileDetail.kt
new file mode 100644
index 0000000..46a1e56
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/local/UpdateProfileDetail.kt
@@ -0,0 +1,82 @@
+package com.krunal.demo.githubclient.data.local
+
+import androidx.databinding.BaseObservable
+import androidx.databinding.Bindable
+import com.krunal.demo.BR
+import com.krunal.demo.githubclient.data.remote.model.response.UserResponse
+
+data class UpdateProfileDetail(
+ private var _avatar: String?,
+ private var _name: String?,
+ private var _email: String,
+ private var _bio: String?,
+ private var _website: String?,
+ private var _twitter: String?,
+ private var _company: String?
+) : BaseObservable() {
+
+ @get:Bindable
+ var avatar: String? = _avatar
+ set(value) {
+ _avatar = value
+ field = value
+ notifyPropertyChanged(BR.avatar)
+ }
+
+ @get:Bindable
+ var name: String? = _name
+ set(value) {
+ _name = value
+ field = value
+ notifyPropertyChanged(BR.name)
+ }
+
+ @get:Bindable
+ var email: String = _email
+ set(value) {
+ _email = value
+ field = value
+ notifyPropertyChanged(BR.email)
+ }
+
+ @get:Bindable
+ var bio: String? = _bio
+ set(value) {
+ _bio = value
+ field = value
+ notifyPropertyChanged(BR.bio)
+ }
+
+ @get:Bindable
+ var website: String? = _website
+ set(value) {
+ _website = value
+ field = value
+ notifyPropertyChanged(BR.website)
+ }
+
+ @get:Bindable
+ var twitter: String? = _twitter
+ set(value) {
+ _twitter = value
+ field = value
+ notifyPropertyChanged(BR.twitter)
+ }
+
+ @get:Bindable
+ var company: String? = _company
+ set(value) {
+ _company = value
+ field = value
+ notifyPropertyChanged(BR.company)
+ }
+
+ companion object {
+
+ fun from(userResponse: UserResponse): UpdateProfileDetail = with(userResponse) {
+ UpdateProfileDetail(
+ avatarUrl, name, email, bio, blog, twitterUsername, company
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/api/AuthorizationService.kt b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/api/AuthorizationService.kt
new file mode 100644
index 0000000..5cbfa87
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/api/AuthorizationService.kt
@@ -0,0 +1,15 @@
+package com.krunal.demo.githubclient.data.remote.api
+
+import com.krunal.demo.githubclient.data.remote.model.request.AuthorizationRequest
+import com.krunal.demo.githubclient.data.remote.model.response.AuthorizationResponse
+import retrofit2.Response
+import retrofit2.http.Body
+import retrofit2.http.Headers
+import retrofit2.http.POST
+
+interface AuthorizationService {
+
+ @Headers("Accept: application/json")
+ @POST("access_token")
+ suspend fun getAuthorizationToken(@Body body: AuthorizationRequest): Response
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/api/FileService.kt b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/api/FileService.kt
new file mode 100644
index 0000000..93eb319
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/api/FileService.kt
@@ -0,0 +1,28 @@
+package com.krunal.demo.githubclient.data.remote.api
+
+import com.krunal.demo.githubclient.data.remote.model.response.ImageUploadResponse
+import okhttp3.MultipartBody
+import okhttp3.ResponseBody
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Multipart
+import retrofit2.http.POST
+import retrofit2.http.Part
+import retrofit2.http.Query
+import retrofit2.http.Streaming
+import retrofit2.http.Url
+
+interface FileService {
+
+ @Streaming
+ @GET
+ suspend fun downloadFile(@Url url: String): ResponseBody
+
+ @Multipart
+ @POST("")
+ suspend fun uploadImageFile(
+ @Url url: String,
+ @Query("key") key: String,
+ @Part image: MultipartBody.Part
+ ): Response
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/api/IssueService.kt b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/api/IssueService.kt
new file mode 100644
index 0000000..09c24db
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/api/IssueService.kt
@@ -0,0 +1,22 @@
+package com.krunal.demo.githubclient.data.remote.api
+
+import com.krunal.demo.githubclient.data.remote.model.request.IssueRequest
+import com.krunal.demo.githubclient.data.remote.model.response.IssueResponse
+import retrofit2.Response
+import retrofit2.http.Body
+import retrofit2.http.GET
+import retrofit2.http.PATCH
+import retrofit2.http.POST
+import retrofit2.http.Path
+
+interface IssueService {
+
+ @POST("repos/{repo}/issues")
+ suspend fun createIssue(@Path("repo", encoded = true) repo: String, @Body issue: IssueRequest): Response
+
+ @PATCH("repos/{repo}/issues")
+ suspend fun updateIssue(@Path("repo", encoded = true) repo: String, @Body issue: IssueRequest): Response
+
+ @GET("repos/{repo}/issues/{issueId}")
+ suspend fun getIssue(@Path("repo", encoded = true) repo: String, @Path("issueId") issueId: Int): Response
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/api/NotificationService.kt b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/api/NotificationService.kt
new file mode 100644
index 0000000..2b774ac
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/api/NotificationService.kt
@@ -0,0 +1,13 @@
+package com.krunal.demo.githubclient.data.remote.api
+
+import com.krunal.demo.githubclient.data.remote.model.response.NotificationsResponseItem
+import com.krunal.demo.githubclient.data.remote.model.response.UserResponse
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Query
+
+interface NotificationService {
+
+ @GET("notifications")
+ suspend fun getNotifications(@Query("all") all: Boolean = true, @Query("per_page") pageSize: Int = 10): Response>
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/api/RepoService.kt b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/api/RepoService.kt
new file mode 100644
index 0000000..745e9c9
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/api/RepoService.kt
@@ -0,0 +1,27 @@
+package com.krunal.demo.githubclient.data.remote.api
+
+import com.krunal.demo.githubclient.data.local.Release
+import com.krunal.demo.githubclient.data.remote.model.response.ReleaseResponse
+import com.krunal.demo.githubclient.data.remote.model.response.RepositoryResponse
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.PUT
+import retrofit2.http.Path
+
+interface RepoService {
+
+ @GET("user/repos")
+ suspend fun getAuthorizedUserRepos(): Response>
+
+ @GET("users/{username}/repos")
+ suspend fun getRepos(@Path("username") username: String): Response>
+
+ @PUT("repos/{repo}/contents/{path}")
+ suspend fun createFile(@Path("repo", encoded = true) repo: String, @Path("path") filePath: String): Response>
+
+ @GET("repos/{repoName}")
+ suspend fun getRepo(@Path("repoName", encoded = true) repoName: String): Response
+
+ @GET("repos/{repoName}/releases/latest")
+ suspend fun getLatestRelease(@Path("repoName", encoded = true) repoName: String): Response
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/api/UserService.kt b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/api/UserService.kt
new file mode 100644
index 0000000..0741544
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/api/UserService.kt
@@ -0,0 +1,28 @@
+package com.krunal.demo.githubclient.data.remote.api
+
+import com.krunal.demo.githubclient.data.remote.model.request.LogoutRequest
+import com.krunal.demo.githubclient.data.remote.model.request.UpdateProfileRequest
+import com.krunal.demo.githubclient.data.remote.model.response.UserResponse
+import retrofit2.Response
+import retrofit2.http.Body
+import retrofit2.http.DELETE
+import retrofit2.http.GET
+import retrofit2.http.HTTP
+import retrofit2.http.PATCH
+import retrofit2.http.Path
+
+interface UserService {
+
+ @GET("user")
+ suspend fun getAuthorizedUser(): Response
+
+ @PATCH("user")
+ suspend fun updateUser(@Body updateProfileRequest: UpdateProfileRequest): Response
+
+ @HTTP(
+ method = "DELETE",
+ path = "applications/{clientId}/token",
+ hasBody = true
+ )
+ suspend fun logout(@Path("clientId") clientId: String, @Body body: LogoutRequest): Response
+}
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/model/request/AuthorizationRequest.kt b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/model/request/AuthorizationRequest.kt
new file mode 100644
index 0000000..567f8bc
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/model/request/AuthorizationRequest.kt
@@ -0,0 +1,9 @@
+package com.krunal.demo.githubclient.data.remote.model.request
+
+data class AuthorizationRequest(
+ val clientId: String,
+ val clientSecret: String,
+ val code: String,
+ val redirectUri: String? = null,
+ val repositoryId: String? = null
+)
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/model/request/IssueRequest.kt b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/model/request/IssueRequest.kt
new file mode 100644
index 0000000..0568b86
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/model/request/IssueRequest.kt
@@ -0,0 +1,9 @@
+package com.krunal.demo.githubclient.data.remote.model.request
+
+data class IssueRequest(
+ val title: String,
+ val body: String? = null,
+ val milestone: String? = null,
+ val labels: List = emptyList(),
+ val assignees: List = emptyList()
+)
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/model/request/LogoutRequest.kt b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/model/request/LogoutRequest.kt
new file mode 100644
index 0000000..1a331fc
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/model/request/LogoutRequest.kt
@@ -0,0 +1,5 @@
+package com.krunal.demo.githubclient.data.remote.model.request
+
+data class LogoutRequest(
+ val accessToken: String
+)
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/model/request/UpdateProfileRequest.kt b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/model/request/UpdateProfileRequest.kt
new file mode 100644
index 0000000..877589d
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/model/request/UpdateProfileRequest.kt
@@ -0,0 +1,12 @@
+package com.krunal.demo.githubclient.data.remote.model.request
+
+data class UpdateProfileRequest(
+ val name: String?,
+ val email: String?,
+ val blog: String?,
+ val twitterUsername: String?,
+ val company: String?,
+ val location: String?,
+ val hireable: Boolean?,
+ val bio: String?
+)
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/model/response/AuthorizationResponse.kt b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/model/response/AuthorizationResponse.kt
new file mode 100644
index 0000000..2278161
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/model/response/AuthorizationResponse.kt
@@ -0,0 +1,10 @@
+package com.krunal.demo.githubclient.data.remote.model.response
+
+data class AuthorizationResponse(
+ val accessToken: String,
+ val expiresIn: Int,
+ val refreshToken: String,
+ val refreshTokenExpiresIn: Int,
+ val scope: String,
+ val tokenType: String
+)
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/model/response/ErrorResponse.kt b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/model/response/ErrorResponse.kt
new file mode 100644
index 0000000..7ea1d45
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/model/response/ErrorResponse.kt
@@ -0,0 +1,12 @@
+package com.krunal.demo.githubclient.data.remote.model.response
+
+data class AuthorizationErrorResponse(
+ val error: String,
+ val errorDescription: String,
+ val errorUri: String
+)
+
+data class ApiError(
+ val documentationUrl: String,
+ val message: String
+)
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/model/response/ImageUploadResponse.kt b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/model/response/ImageUploadResponse.kt
new file mode 100644
index 0000000..0250afa
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/model/response/ImageUploadResponse.kt
@@ -0,0 +1,34 @@
+package com.krunal.demo.githubclient.data.remote.model.response
+
+data class ImageUploadResponse(
+ val `data`: Data, val status: Int, val success: Boolean
+)
+
+data class Medium(
+ val extension: String, val filename: String, val mime: String, val name: String, val url: String
+)
+
+data class Image(
+ val extension: String, val filename: String, val mime: String, val name: String, val url: String
+)
+
+data class Data(
+ val deleteUrl: String,
+ val displayUrl: String,
+ val expiration: String,
+ val height: String,
+ val id: String,
+ val image: Image,
+ val medium: Medium,
+ val size: String,
+ val thumb: Thumb,
+ val time: String,
+ val title: String,
+ val url: String,
+ val urlViewer: String,
+ val width: String
+)
+
+data class Thumb(
+ val extension: String, val filename: String, val mime: String, val name: String, val url: String
+)
\ No newline at end of file
diff --git a/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/model/response/IssueResponse.kt b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/model/response/IssueResponse.kt
new file mode 100644
index 0000000..94e25f5
--- /dev/null
+++ b/Demo/app/src/main/java/com/krunal/demo/githubclient/data/remote/model/response/IssueResponse.kt
@@ -0,0 +1,60 @@
+package com.krunal.demo.githubclient.data.remote.model.response
+
+data class IssueResponse(
+ val activeLockReason: String,
+ val assignee: UserResponse,
+ val assignees: List,
+ val authorAssociation: String,
+ val body: String,
+ val closedAt: Any?,
+ val closedBy: UserResponse,
+ val comments: Int,
+ val commentsUrl: String,
+ val createdAt: String,
+ val eventsUrl: String,
+ val htmlUrl: String,
+ val id: Int,
+ val labels: List