Lab5 finish
This commit is contained in:
parent
053970541a
commit
7356ccb00b
@ -16,17 +16,10 @@ import retrofit2.http.Path
|
|||||||
import retrofit2.http.Query
|
import retrofit2.http.Query
|
||||||
import ru.ulstu.`is`.pmu.api.model.UserRemote
|
import ru.ulstu.`is`.pmu.api.model.UserRemote
|
||||||
import ru.ulstu.`is`.pmu.api.model.PetRemote
|
import ru.ulstu.`is`.pmu.api.model.PetRemote
|
||||||
import ru.ulstu.`is`.pmu.api.report.ReportRemote
|
|
||||||
|
|
||||||
|
|
||||||
interface MyServerService {
|
interface MyServerService {
|
||||||
|
|
||||||
@GET("report")
|
|
||||||
suspend fun getReportInfo(
|
|
||||||
@Query("fromDate") fromDate: String,
|
|
||||||
@Query("toDate") toDate: String
|
|
||||||
): List<ReportRemote>
|
|
||||||
|
|
||||||
@GET("users")
|
@GET("users")
|
||||||
suspend fun getUsers(): List<UserRemote>
|
suspend fun getUsers(): List<UserRemote>
|
||||||
|
|
||||||
|
@ -9,7 +9,6 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
import ru.ulstu.`is`.pmu.api.MyServerService
|
import ru.ulstu.`is`.pmu.api.MyServerService
|
||||||
import ru.ulstu.`is`.pmu.api.model.toPet
|
import ru.ulstu.`is`.pmu.api.model.toPet
|
||||||
import ru.ulstu.`is`.pmu.api.model.toPetRemote
|
import ru.ulstu.`is`.pmu.api.model.toPetRemote
|
||||||
import ru.ulstu.`is`.pmu.api.report.ReportRemote
|
|
||||||
import ru.ulstu.`is`.pmu.api.user.RestUserRepository
|
import ru.ulstu.`is`.pmu.api.user.RestUserRepository
|
||||||
import ru.ulstu.`is`.pmu.common.AppContainer
|
import ru.ulstu.`is`.pmu.common.AppContainer
|
||||||
import ru.ulstu.`is`.pmu.common.PetRepository
|
import ru.ulstu.`is`.pmu.common.PetRepository
|
||||||
@ -83,9 +82,5 @@ class RestPetRepository(
|
|||||||
override suspend fun deletePet(pet: Pet) {
|
override suspend fun deletePet(pet: Pet) {
|
||||||
service.deletePet(pet.uid).toPet()
|
service.deletePet(pet.uid).toPet()
|
||||||
}
|
}
|
||||||
suspend fun getReport(fromDate: String, toDate: String):List<ReportRemote>
|
|
||||||
{
|
|
||||||
return service.getReportInfo(fromDate,toDate)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
@ -1,12 +0,0 @@
|
|||||||
package ru.ulstu.`is`.pmu.api.report
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class ReportRemote(
|
|
||||||
val reportId: Int = 0,
|
|
||||||
val petid: Int = 0,
|
|
||||||
val petname: String = "",
|
|
||||||
val birthday: String = "",
|
|
||||||
val userid: Int = 0,
|
|
||||||
val login: String = ""
|
|
||||||
)
|
|
@ -10,7 +10,6 @@ import ru.ulstu.`is`.pmu.ui.pet.edit.PetEditViewModel
|
|||||||
import ru.ulstu.`is`.pmu.ui.pet.edit.UserDropDownViewModel
|
import ru.ulstu.`is`.pmu.ui.pet.edit.UserDropDownViewModel
|
||||||
import ru.ulstu.`is`.pmu.ui.pet.list.PetListViewModel
|
import ru.ulstu.`is`.pmu.ui.pet.list.PetListViewModel
|
||||||
import ru.ulstu.`is`.pmu.ui.pet.list.UserListViewModel
|
import ru.ulstu.`is`.pmu.ui.pet.list.UserListViewModel
|
||||||
import ru.ulstu.`is`.pmu.ui.pet.report.ReportViewModel
|
|
||||||
|
|
||||||
object AppViewModelProvider {
|
object AppViewModelProvider {
|
||||||
val Factory = viewModelFactory {
|
val Factory = viewModelFactory {
|
||||||
@ -30,11 +29,6 @@ object AppViewModelProvider {
|
|||||||
UserDropDownViewModel(petApplication().container.userRestRepository
|
UserDropDownViewModel(petApplication().container.userRestRepository
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
initializer {
|
|
||||||
ReportViewModel(
|
|
||||||
petApplication().container.petRestRepository,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +33,6 @@ import ru.ulstu.`is`.pmu.ui.pet.edit.PetEdit
|
|||||||
import ru.ulstu.`is`.pmu.ui.pet.list.PetList
|
import ru.ulstu.`is`.pmu.ui.pet.list.PetList
|
||||||
import ru.ulstu.`is`.pmu.ui.pet.list.PetUserList
|
import ru.ulstu.`is`.pmu.ui.pet.list.PetUserList
|
||||||
import ru.ulstu.`is`.pmu.ui.pet.list.UserList
|
import ru.ulstu.`is`.pmu.ui.pet.list.UserList
|
||||||
import ru.ulstu.`is`.pmu.ui.pet.report.ReportPage
|
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@ -104,7 +103,6 @@ fun Navhost(
|
|||||||
startDestination = Screen.PetList.route,
|
startDestination = Screen.PetList.route,
|
||||||
modifier.padding(innerPadding)
|
modifier.padding(innerPadding)
|
||||||
) {
|
) {
|
||||||
composable(Screen.Report.route) { ReportPage(navController = navController) }
|
|
||||||
composable(Screen.PetList.route) { PetList(navController) }
|
composable(Screen.PetList.route) { PetList(navController) }
|
||||||
composable(Screen.UserList.route) { UserList(navController) }
|
composable(Screen.UserList.route) { UserList(navController) }
|
||||||
composable(
|
composable(
|
||||||
|
@ -18,9 +18,6 @@ enum class Screen(
|
|||||||
PetList(
|
PetList(
|
||||||
"pet-list", R.string.list_pet, Icons.Filled.Home
|
"pet-list", R.string.list_pet, Icons.Filled.Home
|
||||||
),
|
),
|
||||||
Report(
|
|
||||||
"report", R.string.report, Icons.Filled.Info
|
|
||||||
),
|
|
||||||
PetUserList(
|
PetUserList(
|
||||||
"pet-user-list/{id}", R.string.list_pet, showInBottomBar = false
|
"pet-user-list/{id}", R.string.list_pet, showInBottomBar = false
|
||||||
),
|
),
|
||||||
@ -35,7 +32,6 @@ enum class Screen(
|
|||||||
val bottomBarItems = listOf(
|
val bottomBarItems = listOf(
|
||||||
UserList,
|
UserList,
|
||||||
PetList,
|
PetList,
|
||||||
Report
|
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getItem(route: String): Screen? {
|
fun getItem(route: String): Screen? {
|
||||||
|
@ -1,197 +0,0 @@
|
|||||||
package ru.ulstu.`is`.pmu.ui.pet.report
|
|
||||||
|
|
||||||
import android.content.ContentResolver
|
|
||||||
import android.content.ContentValues
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.graphics.Paint
|
|
||||||
import android.graphics.pdf.PdfDocument
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Environment
|
|
||||||
import android.provider.MediaStore
|
|
||||||
import android.widget.CalendarView
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.border
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.RowScope
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.wrapContentSize
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.ButtonDefaults
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import androidx.navigation.NavController
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import ru.ulstu.`is`.pmu.R
|
|
||||||
import ru.ulstu.`is`.pmu.api.report.ReportRemote
|
|
||||||
import ru.ulstu.`is`.pmu.common.AppViewModelProvider
|
|
||||||
import java.io.IOException
|
|
||||||
import java.io.OutputStream
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Calendar
|
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun ReportPage (navController: NavController?, viewModel: ReportViewModel = viewModel(factory = AppViewModelProvider.Factory))
|
|
||||||
{
|
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
val reportResultPageState = viewModel.reportResultPageUiState
|
|
||||||
|
|
||||||
// Здесь вы можете определить startDate и endDate
|
|
||||||
val startDate = viewModel.reportPageUiState.reportDetails.startDate
|
|
||||||
val endDate = viewModel.reportPageUiState.reportDetails.endDate
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.verticalScroll(rememberScrollState()),
|
|
||||||
verticalArrangement = Arrangement.Center,
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
|
||||||
)
|
|
||||||
{
|
|
||||||
Text(
|
|
||||||
text = stringResource(id = R.string.startDate),
|
|
||||||
style = MaterialTheme.typography.headlineLarge
|
|
||||||
)
|
|
||||||
|
|
||||||
CustomCalendarView(selectedDate = startDate) { selectedDate ->
|
|
||||||
viewModel.onUpdate(viewModel.reportPageUiState.reportDetails.copy(startDate = selectedDate))
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = stringResource(id = R.string.endDate),
|
|
||||||
style = MaterialTheme.typography.headlineLarge
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
CustomCalendarView(selectedDate = endDate) { selectedDate ->
|
|
||||||
viewModel.onUpdate(viewModel.reportPageUiState.reportDetails.copy(endDate = selectedDate))
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
Button(
|
|
||||||
onClick = {coroutineScope.launch { viewModel.getReport()} },
|
|
||||||
enabled = viewModel.reportPageUiState.isEntryValid,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(all = 10.dp)
|
|
||||||
.border(4.dp, MaterialTheme.colorScheme.onPrimary, shape = RoundedCornerShape(10.dp)),
|
|
||||||
shape = RoundedCornerShape(10.dp),
|
|
||||||
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary)
|
|
||||||
) {
|
|
||||||
Text("Сформировать отчет")
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.height(32.dp))
|
|
||||||
Text(
|
|
||||||
text = "Результат",
|
|
||||||
style = MaterialTheme.typography.headlineLarge
|
|
||||||
)
|
|
||||||
TableScreen(reportData = reportResultPageState.resReport)
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@Composable
|
|
||||||
fun CustomCalendarView(
|
|
||||||
selectedDate: Date,
|
|
||||||
onDateSelected: (Date) -> Unit
|
|
||||||
) {
|
|
||||||
AndroidView(
|
|
||||||
|
|
||||||
modifier = Modifier.wrapContentSize(),
|
|
||||||
factory = { context ->
|
|
||||||
CalendarView(context).apply {
|
|
||||||
// Настройка CalendarView
|
|
||||||
val calendar = Calendar.getInstance()
|
|
||||||
calendar.time = selectedDate
|
|
||||||
|
|
||||||
// Установка начальной выбранной даты
|
|
||||||
date = calendar.timeInMillis
|
|
||||||
|
|
||||||
setOnDateChangeListener { _, year, month, dayOfMonth ->
|
|
||||||
calendar.set(Calendar.YEAR, year)
|
|
||||||
calendar.set(Calendar.MONTH, month)
|
|
||||||
calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth)
|
|
||||||
val newSelectedDate = calendar.time
|
|
||||||
|
|
||||||
// Установка новой выбранной даты
|
|
||||||
date = calendar.timeInMillis
|
|
||||||
|
|
||||||
// Вызов колбэка для передачи выбранной даты в родительский компонент
|
|
||||||
onDateSelected(newSelectedDate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun RowScope.TableCell(
|
|
||||||
text: String,
|
|
||||||
weight: Float
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = text,
|
|
||||||
Modifier
|
|
||||||
.border(1.dp, Color.Black)
|
|
||||||
.weight(weight)
|
|
||||||
.padding(8.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun TableScreen(reportData: List<ReportRemote>) {
|
|
||||||
Column(
|
|
||||||
Modifier
|
|
||||||
.padding(16.dp)) {
|
|
||||||
|
|
||||||
Row(Modifier.background(Color.White)) {
|
|
||||||
TableCell(text = "№", weight = 1f)
|
|
||||||
TableCell(text = "Id питомца", weight = 1f)
|
|
||||||
TableCell(text = "Имя", weight = 1f)
|
|
||||||
TableCell(text = "Д/р", weight = 1f)
|
|
||||||
TableCell(text = "Id хозяина", weight = 1f)
|
|
||||||
TableCell(text = "Логин", weight = 1f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Here are all the lines of your table.
|
|
||||||
reportData.forEach {
|
|
||||||
val (reportId, petid, petname, birthday, userid, login) = it
|
|
||||||
Row(Modifier.fillMaxWidth()) {
|
|
||||||
TableCell(text = reportId.toString(), weight = 1f)
|
|
||||||
TableCell(text = petid.toString(), weight = 1f)
|
|
||||||
TableCell(text = petname, weight = 1f)
|
|
||||||
TableCell(text = birthday, weight = 1f)
|
|
||||||
TableCell(text = userid.toString(), weight = 1f)
|
|
||||||
TableCell(text = login, weight = 1f)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
|||||||
package ru.ulstu.`is`.pmu.ui.pet.report
|
|
||||||
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import ru.ulstu.`is`.pmu.api.pet.RestPetRepository
|
|
||||||
import ru.ulstu.`is`.pmu.api.report.ReportRemote
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Calendar
|
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
class ReportViewModel(private val restPetRepository: RestPetRepository): ViewModel() {
|
|
||||||
var reportPageUiState by mutableStateOf(ReportPageUiState())
|
|
||||||
private set
|
|
||||||
|
|
||||||
var reportResultPageUiState by mutableStateOf(ReportResultPageUiState())
|
|
||||||
private set
|
|
||||||
|
|
||||||
fun onUpdate(reportDetails: ReportDetails) {
|
|
||||||
reportPageUiState = ReportPageUiState(reportDetails = reportDetails, isEntryValid = validateInput(reportDetails))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun validateInput(uiState: ReportDetails = reportPageUiState.reportDetails): Boolean {
|
|
||||||
return with(uiState) {
|
|
||||||
startDate != Date(0) && endDate != Date(0) && startDate < endDate
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getReport() {
|
|
||||||
|
|
||||||
val startDateQueryParam = SimpleDateFormat("dd.MM.yyyy").format(reportPageUiState.reportDetails.startDate)
|
|
||||||
val endDateQueryParam = SimpleDateFormat("dd.MM.yyyy").format(reportPageUiState.reportDetails.endDate)
|
|
||||||
|
|
||||||
// Вызов репозитория с отформатированными датами
|
|
||||||
val res = restPetRepository.getReport(startDateQueryParam, endDateQueryParam)
|
|
||||||
reportResultPageUiState = ReportResultPageUiState(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class ReportDetails(
|
|
||||||
val startDate: Date = Calendar.getInstance().time,
|
|
||||||
val endDate: Date = Calendar.getInstance().time
|
|
||||||
)
|
|
||||||
|
|
||||||
data class ReportPageUiState(
|
|
||||||
val reportDetails: ReportDetails = ReportDetails(),
|
|
||||||
val isEntryValid: Boolean = false
|
|
||||||
)
|
|
||||||
|
|
||||||
data class ReportResultPageUiState(
|
|
||||||
var resReport: List<ReportRemote> = emptyList()
|
|
||||||
)
|
|
@ -2,7 +2,7 @@
|
|||||||
"name": "fake-db",
|
"name": "fake-db",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "json-server --watch data.json --middlewares ./router.js --host 0.0.0.0 -p 8079"
|
"start": "json-server --watch data.json --host 0.0.0.0 -p 8079"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"pdfkit": "^0.14.0"
|
"pdfkit": "^0.14.0"
|
||||||
|
@ -1,76 +0,0 @@
|
|||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
module.exports = (req, res, next) => {
|
|
||||||
const data = JSON.parse(fs.readFileSync(path.join(__dirname, 'data.json'), 'utf8'));
|
|
||||||
|
|
||||||
if (req.url.startsWith('/search') && req.method === 'GET') {
|
|
||||||
try {
|
|
||||||
const searchText = req.query.name;
|
|
||||||
const searched = data.pets.filter(
|
|
||||||
(pet) =>
|
|
||||||
pet.name.toLowerCase().includes(searchText.toLowerCase())
|
|
||||||
);
|
|
||||||
return res.json(searched);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading data:', error);
|
|
||||||
res.status(500).json({ message: 'Internal Server Error' });
|
|
||||||
}
|
|
||||||
} else if (req.url.startsWith('/report') && req.method === 'GET') {
|
|
||||||
try {
|
|
||||||
// Добавляем фильтрацию по дате рождения
|
|
||||||
const fromDate = req.query.fromDate;
|
|
||||||
const toDate = req.query.toDate;
|
|
||||||
const filteredData = filterByBirthday(data.pets, fromDate, toDate);
|
|
||||||
|
|
||||||
// Возвращаем отфильтрованные данные в формате отчета
|
|
||||||
const reportData = generateReport(filteredData);
|
|
||||||
res.json(reportData);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading data:', error);
|
|
||||||
return res.status(500).json({ message: 'Internal Server Error' });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Функция generateReport для создания отчета с id
|
|
||||||
function generateReport(pets) {
|
|
||||||
return pets.map((pet, index) => ({
|
|
||||||
reportId: index + 1, // Уникальный id отчета (можете использовать другую логику)
|
|
||||||
petid: pet.id,
|
|
||||||
petname: pet.name,
|
|
||||||
birthday: pet.birthday,
|
|
||||||
userid: pet.userId,
|
|
||||||
login: getUserLogin(pet.userId)
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Функция для фильтрации по дате рождения
|
|
||||||
function filterByBirthday(pets, fromDate, toDate) {
|
|
||||||
if (!fromDate && !toDate) {
|
|
||||||
return pets;
|
|
||||||
}
|
|
||||||
|
|
||||||
const filteredPets = pets.filter((pet) => {
|
|
||||||
const petBirthday = parseDate(pet.birthday);
|
|
||||||
return (!fromDate || petBirthday >= parseDate(fromDate)) &&
|
|
||||||
(!toDate || petBirthday <= parseDate(toDate));
|
|
||||||
});
|
|
||||||
|
|
||||||
return filteredPets;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Функция для парсинга даты из строки в формате "dd.MM.yyyy"
|
|
||||||
function parseDate(dateString) {
|
|
||||||
const [day, month, year] = dateString.split('.').map(Number);
|
|
||||||
return new Date(year, month - 1, day);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Функция для получения логина пользователя по userId
|
|
||||||
function getUserLogin(userId) {
|
|
||||||
const data = JSON.parse(fs.readFileSync(path.join(__dirname, 'data.json'), 'utf8'));
|
|
||||||
const user = data.users.find((user) => user.id === userId);
|
|
||||||
return user ? user.login : 'Unknown';
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user