Skip to content
Merged
16 changes: 4 additions & 12 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,12 @@ plugins {

}

//val properties = Properties().apply {
// load(project.rootProject.file("local.properties").inputStream())
//}


val properties = Properties().apply {
val localPropertiesFile = project.rootProject.file("local.properties")
if (localPropertiesFile.exists()) {
load(localPropertiesFile.inputStream())
}
}

android {
namespace = "com.hsLink.hslink"
compileSdk = libs.versions.compileSdk.get().toInt()
Expand All @@ -35,12 +29,9 @@ android {
versionName = libs.versions.versionName.get()

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
buildConfigField(
"String",
"BASE_URL",
"\"${properties.getProperty("base.url", "https://default-url.com")}\""
)
//buildConfigField("String", "BASE_URL", properties["base.url"].toString())

buildConfigField("String", "BASE_URL", "\"${properties["base.url"]}\"")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add null safety for base.url property access.

Direct property access via properties["base.url"] can fail with a NullPointerException if the property is missing. This would result in cryptic build errors.

Apply this diff to add null safety with a clear error message:

-        buildConfigField("String", "BASE_URL", "\"${properties["base.url"]}\"")
+        buildConfigField("String", "BASE_URL", "\"${properties["base.url"] ?: error("base.url property is missing in local.properties")}\"")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
buildConfigField("String", "BASE_URL", "\"${properties["base.url"]}\"")
buildConfigField("String", "BASE_URL", "\"${properties["base.url"] ?: error("base.url property is missing in local.properties")}\"")
🤖 Prompt for AI Agents
In app/build.gradle.kts around line 31, the direct access properties["base.url"]
can be null and cause an NPE; change it to safely unwrap the property and fail
fast with a clear message (e.g. use requireNotNull(properties["base.url"]) {
"Missing required project property 'base.url'" } or check
properties.containsKey("base.url") and throw a GradleException) and then use the
resulting non-null value when calling buildConfigField (preserving the
surrounding quoted string expected by buildConfigField).


}

buildTypes {
Expand All @@ -50,6 +41,7 @@ android {
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
buildConfigField("String", "BASE_URL", "\"${properties["base.url"]}\"")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add null safety for base.url property access in release build.

Same issue as in defaultConfig - the property access lacks null safety.

Apply this diff:

-            buildConfigField("String", "BASE_URL", "\"${properties["base.url"]}\"")
+            buildConfigField("String", "BASE_URL", "\"${properties["base.url"] ?: error("base.url property is missing in local.properties")}\"")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
buildConfigField("String", "BASE_URL", "\"${properties["base.url"]}\"")
buildConfigField("String", "BASE_URL", "\"${properties["base.url"] ?: error("base.url property is missing in local.properties")}\"")
🤖 Prompt for AI Agents
In app/build.gradle.kts around line 42, the buildConfigField call uses
properties["base.url"] without null-safety; change it to use a safe cast and
fallback or fail fast — e.g., replace the direct access with
(properties["base.url"] as? String) ?: error("base.url property is required") so
the value is null-safe and provides a clear error if missing.

}
}
compileOptions {
Expand Down
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<application
android:name=".HsuConnectApplication"
android:allowBackup="true"
android:usesCleartextTraffic="true"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Security concern: Restrict cleartext traffic to development only.

Enabling usesCleartextTraffic="true" globally allows unencrypted HTTP traffic, which exposes user data to interception and man-in-the-middle attacks. This should not be shipped to production.

Recommended solution: Use a Network Security Configuration file to allow cleartext traffic only for specific development domains:

  1. Create res/xml/network_security_config.xml:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">localhost</domain>
        <domain includeSubdomains="true">10.0.2.2</domain>
        <!-- Add your dev server domains here -->
    </domain-config>
</network-security-config>
  1. Reference it in the manifest:
-        android:usesCleartextTraffic="true"
+        android:networkSecurityConfig="@xml/network_security_config"

Alternatively, use build-variant-specific manifests to enable this only in debug builds.

android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
Expand Down
22 changes: 9 additions & 13 deletions app/src/main/java/com/hsLink/hslink/core/network/BaseResponse.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,12 @@ import kotlinx.serialization.Serializable

@Serializable
data class BaseResponse<T>(
@SerialName("success")
val success: Boolean,
@SerialName("error")
val error: ErrorResponse? = null,
@SerialName("data")
val data: T? = null
) {
@Serializable
data class ErrorResponse(
@SerialName("message")
val message: String
)
}
@SerialName("isSuccess")
val isSuccess: Boolean,
@SerialName("code")
val code: String,
@SerialName("message")
val message: String,
@SerialName("result")
val result: T,
)
17 changes: 17 additions & 0 deletions app/src/main/java/com/hsLink/hslink/data/di/DataSourceModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.hsLink.hslink.data.di

import com.hsLink.hslink.data.remote.datasource.PostDataSource
import com.hsLink.hslink.data.remote.datasourceimpl.PostDataSourceImpl
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent

@Module
@InstallIn(SingletonComponent::class)
interface DataSourceModule {
@Binds
abstract fun bindePostRemoteDataSource(
postDataSourceImpl: PostDataSourceImpl,
): PostDataSource
}
Comment on lines +13 to +17
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix typo in method name.

The method name bindePostRemoteDataSource has a typo—it should be bindPostRemoteDataSource for consistency with Dagger naming conventions.

Apply this diff:

     @Binds
-    abstract fun bindePostRemoteDataSource(
+    abstract fun bindPostRemoteDataSource(
         postDataSourceImpl: PostDataSourceImpl,
     ): PostDataSource
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Binds
abstract fun bindePostRemoteDataSource(
postDataSourceImpl: PostDataSourceImpl,
): PostDataSource
}
@Binds
abstract fun bindPostRemoteDataSource(
postDataSourceImpl: PostDataSourceImpl,
): PostDataSource
}
🤖 Prompt for AI Agents
In app/src/main/java/com/hsLink/hslink/data/di/DataSourceModule.kt around lines
13 to 17, the Binds method name `bindePostRemoteDataSource` has a typo; rename
it to `bindPostRemoteDataSource` so it follows naming conventions and matches
other DI bindings, updating the method declaration only (keep parameter type and
return type unchanged).

19 changes: 14 additions & 5 deletions app/src/main/java/com/hsLink/hslink/data/di/NetworkModule.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.hsLink.hslink.data.di

import com.hsLink.hslink.BuildConfig
import com.hsLink.hslink.data.service.home.PostService
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import dagger.Module
import dagger.Provides
Expand All @@ -12,6 +13,7 @@ import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Converter
import retrofit2.Retrofit
import timber.log.Timber
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify usage of Timber import.

The Timber import is added but not visibly used in the provided code. If it's unused, consider removing it.


🏁 Script executed:

#!/bin/bash
# Description: Check if Timber is used in NetworkModule.kt

# Search for Timber usage in the file
rg -n "Timber\." app/src/main/java/com/hsLink/hslink/data/di/NetworkModule.kt

Length of output: 0



---

🏁 Script executed:

```shell
#!/bin/bash
# Check if file exists and view its content
if [ -f "app/src/main/java/com/hsLink/hslink/data/di/NetworkModule.kt" ]; then
  echo "=== File exists. Content: ==="
  cat "app/src/main/java/com/hsLink/hslink/data/di/NetworkModule.kt"
else
  echo "File not found at specified path"
fi

echo ""
echo "=== Searching for any Timber reference (including imports) ==="
rg -n "Timber" app/src/main/java/com/hsLink/hslink/data/di/NetworkModule.kt

Length of output: 2038


Remove unused Timber import.

The Timber import at line 16 is not used anywhere in the file. Remove it.

🤖 Prompt for AI Agents
In app/src/main/java/com/hsLink/hslink/data/di/NetworkModule.kt around line 16,
the import "timber.log.Timber" is unused; remove that import line to clean up
unused dependencies and avoid lint/compile warnings, leaving the rest of the
file unchanged.

import java.util.concurrent.TimeUnit
import javax.inject.Singleton

Expand Down Expand Up @@ -49,9 +51,16 @@ object NetworkModule {
fun providesRetrofit(
client: OkHttpClient,
converterFactory: Converter.Factory,
): Retrofit = Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.addConverterFactory(converterFactory)
.client(client)
.build()
): Retrofit {
return Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.addConverterFactory(converterFactory)
.client(client)
.build()
}

@Provides
@Singleton
fun providePostService(retrofit: Retrofit): PostService =
retrofit.create(PostService::class.java)
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.hsLink.hslink.data.di

import com.hsLink.hslink.data.repositoryimpl.DummyRepositoryImpl
import com.hsLink.hslink.data.repositoryimpl.PostRepositoryImpl
import com.hsLink.hslink.domain.DummyRepository
import com.hsLink.hslink.domain.repository.PostRepository
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
Expand All @@ -13,7 +15,12 @@ interface RepositoryModule {

@Binds
fun bindsDummyRepository(
dummyRepositoryImpl: DummyRepositoryImpl
dummyRepositoryImpl: DummyRepositoryImpl,
): DummyRepository

@Binds
fun bindsPostRepository(
postRepositoryImpl: PostRepositoryImpl,
): PostRepository

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.hsLink.hslink.data.dto.request

class PostRequestDto {
}
Comment on lines +3 to +4
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Empty class serves no purpose.

This class has no fields or methods. If it's a placeholder for future request bodies, consider removing it until actually needed (YAGNI principle). Alternatively, if requirements are known, add the necessary fields now.

Run the following script to verify if this class is referenced anywhere in the codebase:

If no usages are found, consider removing this file to keep the codebase clean.


🏁 Script executed:

#!/bin/bash
# Description: Check if PostRequestDto is used anywhere in the codebase

# Search for imports of PostRequestDto
rg -n "import.*PostRequestDto" -g "*.kt"

# Search for usages of PostRequestDto (excluding the definition itself)
rg -n "PostRequestDto" -g "*.kt" -C 2 | grep -v "class PostRequestDto"

Length of output: 264


Remove unused empty class PostRequestDto.

Verification confirms this class is not imported or used anywhere in the codebase. It serves no purpose and should be deleted to maintain code cleanliness and avoid future confusion.

🧰 Tools
🪛 detekt (1.23.8)

[warning] 3-4: The class or object PostRequestDto is empty.

(detekt.empty-blocks.EmptyClassBlock)

🤖 Prompt for AI Agents
In app/src/main/java/com/hsLink/hslink/data/dto/request/PostRequestDto.kt around
lines 3 to 4, remove the unused empty class PostRequestDto; delete the file
entirely (or remove the class declaration) and verify there are no imports or
references left in the project, then run a full build to ensure no compilation
errors.

Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.hsLink.hslink.data.dto.response

import com.hsLink.hslink.domain.model.PostPopularEntity
import com.hsLink.hslink.domain.model.PostPromotionEntity
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class PostResponseDto(
@SerialName("posts")
val posts: List<PostPopular>,
)

@Serializable
data class PostPopular(
@SerialName("id")
val id: Int,
@SerialName("title")
val title: String,
)

@Serializable
data class PostPromotionDto(
@SerialName("posts")
val posts: List<PostPromotion>,
)

@Serializable
data class PostPromotion(
@SerialName("id")
val id: Int,
@SerialName("title")
val title: String,
@SerialName("summary")
val summary: String,
@SerialName("author")
val author: String,
@SerialName("studentId")
val studentId: String,
@SerialName("authorStatus")
val authorStatus: String,

)


fun PostPopular.toEntity(): PostPopularEntity {
return PostPopularEntity(
id = this.id,
title = this.title,
)
}

fun PostPromotion.toEntity(): PostPromotionEntity {
return PostPromotionEntity(
id = this.id,
title = this.title,
summary = this.summary,
author = this.author,
studentId = this.studentId,
authorStatus = this.authorStatus,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.hsLink.hslink.data.remote.datasource

import com.hsLink.hslink.core.network.BaseResponse
import com.hsLink.hslink.data.dto.response.PostPromotionDto
import com.hsLink.hslink.data.dto.response.PostResponseDto

interface PostDataSource {
suspend fun getPopularPost() : BaseResponse<PostResponseDto>

suspend fun getPromotionPost() : BaseResponse<PostPromotionDto>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.hsLink.hslink.data.remote.datasourceimpl

import com.hsLink.hslink.core.network.BaseResponse
import com.hsLink.hslink.data.dto.response.PostPromotionDto
import com.hsLink.hslink.data.dto.response.PostResponseDto
import com.hsLink.hslink.data.remote.datasource.PostDataSource
import com.hsLink.hslink.data.service.home.PostService
import javax.inject.Inject

class PostDataSourceImpl @Inject constructor(
private val postService: PostService
): PostDataSource{
override suspend fun getPopularPost() : BaseResponse<PostResponseDto>{
return postService.getPopularPost()
}

override suspend fun getPromotionPost(): BaseResponse<PostPromotionDto> {
return postService.getPromotionPost()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.hsLink.hslink.data.repositoryimpl

import com.hsLink.hslink.data.dto.response.toEntity
import com.hsLink.hslink.data.remote.datasourceimpl.PostDataSourceImpl
import com.hsLink.hslink.domain.model.PostPopularEntity
import com.hsLink.hslink.domain.model.PostPromotionEntity
import com.hsLink.hslink.domain.repository.PostRepository
import javax.inject.Inject

class PostRepositoryImpl @Inject constructor(
private val postDataSourceImpl: PostDataSourceImpl,
) : PostRepository {
override suspend fun getPopularPost(): Result<List<PostPopularEntity>> =
runCatching {
val response = postDataSourceImpl.getPopularPost()
if (response.isSuccess) {
response.result.posts.map { it.toEntity() }
} else {
throw Exception(response.message)
}
}

override suspend fun getPromotionPost(): Result<List<PostPromotionEntity>> =
runCatching {
val respone = postDataSourceImpl.getPromotionPost()
if (respone.isSuccess) {
respone.result.posts.map { it.toEntity() }
} else {
throw Exception(respone.message)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.hsLink.hslink.data.service.home

import com.hsLink.hslink.core.network.BaseResponse
import com.hsLink.hslink.data.dto.response.PostPromotionDto
import com.hsLink.hslink.data.dto.response.PostResponseDto
import retrofit2.http.GET


interface PostService {
@GET("posts/popular")
suspend fun getPopularPost(): BaseResponse<PostResponseDto>

@GET("posts/promotion")
suspend fun getPromotionPost(): BaseResponse<PostPromotionDto>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.hsLink.hslink.domain.model

import kotlinx.serialization.SerialName

data class PostPopularEntity (
val id: Int,
val title: String,
)

data class PostPromotionEntity(
val id: Int,
val title: String,
val summary: String,
val author: String,
val studentId: String,
val authorStatus: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.hsLink.hslink.domain.repository

import com.hsLink.hslink.domain.model.PostPopularEntity
import com.hsLink.hslink.domain.model.PostPromotionEntity

interface PostRepository {
suspend fun getPopularPost(): Result<List<PostPopularEntity>>
suspend fun getPromotionPost(): Result<List<PostPromotionEntity>>
}
Loading