Skip to content

Instantly share code, notes, and snippets.

@kevinah95
Created July 9, 2025 03:34
Show Gist options
  • Save kevinah95/d27dcfcedb2529bdb741fde09634666e to your computer and use it in GitHub Desktop.
Save kevinah95/d27dcfcedb2529bdb741fde09634666e to your computer and use it in GitHub Desktop.
package io.github.laChanchita
import com.google.auth.oauth2.GoogleCredentials
import com.google.cloud.firestore.Firestore
import com.google.cloud.firestore.FirestoreOptions
import com.google.firebase.FirebaseApp
import com.google.firebase.FirebaseOptions
import com.google.firebase.cloud.FirestoreClient
import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.DatabaseError
import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.FirebaseDatabase
import com.google.firebase.database.ValueEventListener
import io.github.laChanchita.model.Mejenga
import io.github.laChanchita.model.Mejenguero
import io.github.laChanchita.model.Venue
//import io.github.laChanchita.server.borrarMejanga
import io.github.laChanchita.server.cargarTodasLasMejengas
import io.github.laChanchita.server.cargarTodosLosMejengueros
import io.github.laChanchita.server.loadAllVenues
import io.ktor.http.HttpStatusCode
import io.ktor.serialization.kotlinx.json.json
import io.ktor.server.application.Application
import io.ktor.server.application.install
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
import io.ktor.server.request.receive
import io.ktor.server.response.respond
import io.ktor.server.response.respondText
import io.ktor.server.routing.delete
import io.ktor.server.routing.get
import io.ktor.server.routing.post
import io.ktor.server.routing.put
import io.ktor.server.routing.routing
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.serialization.json.Json
import java.io.File
import java.io.FileInputStream
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
val json = Json {
prettyPrint = true
encodeDefaults = true // ✅ ¡esto es esencial!
}
fun main() {
embeddedServer(Netty, port = SERVER_PORT, host = "0.0.0.0", module = Application::module)
.start(wait = true)
}
fun Application.module() {
lateinit var app: FirebaseApp
lateinit var defaultDatabase: FirebaseDatabase
lateinit var rootRef: DatabaseReference
lateinit var venuesRef: DatabaseReference
val venueListener = object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val venueList = mutableListOf<Venue>()
for (venueSnapshot in snapshot.children) {
// Each venueSnapshot represents a single venue object
val venue = venueSnapshot.getValue(Venue::class.java)
if (venue != null) {
// You might want to set the ID from the key if it's not part of your data
val venueWithId = venue.copy(id = venueSnapshot.key ?: "")
venueList.add(venueWithId)
}
}
// Now you have your list of Venue objects
println("Venues retrieved: $venueList")
// Update your UI, process data, etc.
}
override fun onCancelled(error: DatabaseError) {
// Handle error
println("Failed to read venues: ${error.message}")
}
}
install(ContentNegotiation){
json()
val refreshToken = FileInputStream("key/lacanchita-fc-firebase-adminsdk-fbsvc-fca28ea9ba.json")
val options = FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(refreshToken))
.setDatabaseUrl("https://lacanchita-fc-default-rtdb.firebaseio.com/")
.build()
app = FirebaseApp.initializeApp(options);
defaultDatabase = FirebaseDatabase.getInstance(app);
rootRef = defaultDatabase.reference
val dataRef: DatabaseReference = defaultDatabase.getReference("data")
val specificUserRef: DatabaseReference = defaultDatabase.getReference("data").child("venues")
FirestoreClient.getFirestore()
val db = FirestoreClient.getFirestore()
venuesRef = defaultDatabase.getReference("data").child("venues")
// venuesRef.addValueEventListener(venueListener)
println(dataRef);
}
fun fetchVenuesOnce(scope: CoroutineScope) {
scope.launch {
// try {
// val snapshot: DataSnapshot = venuesRef.get().await()
// val venueList = mutableListOf<Venue>()
// for (venueSnapshot in snapshot.children) {
// val venue = venueSnapshot.getValue(Venue::class.java)
// if (venue != null) {
// val venueWithId = venue.copy(id = venueSnapshot.key ?: "")
// venueList.add(venueWithId)
// }
// }
// println("Venues retrieved (coroutine): $venueList")
// // Process your data here
// } catch (e: Exception) {
// println("Failed to read venues (coroutine): ${e.message}")
// }
}
}
routing {
get("/") {
try {
val venueList = venuesRef.awaitSingleValue<Venue>()
call.respondText("Ktor: ${Greeting().greet()} ${venueList}}")
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, "Error fetching venues: ${e.message}")
}
}
//VENUES
get("/venues") {
val venues = loadAllVenues();
println(">> VENUES: $venues")
call.respond(venues)
}
//CANCHAS
get("/canchas") {
val venues = loadAllVenues();
val canchas = venues.flatMap { it.canchas ?: emptyList() }
canchas.forEach {
println("Cancha: ${it.name}")
}
println(">> CANCHAS: $canchas")
call.respond(canchas)
}
//MEJENGAS
post("/mejenga") {
val mejenga = call.receive<Mejenga>()
//guardarMejanga(mejenga)
println(">> MEJENGA RECIBIDA: $mejenga")
call.respondText("Mejenga recibida con éxito")
}
put("/mejenga") {
try {
val mejengaEditada = call.receive<Mejenga>()
val lista = cargarTodasLasMejengas().toMutableList()
val index = lista.indexOfFirst { it.id == mejengaEditada.id }
if (index != -1) {
// lista[index] = mejengaEditada
// archivo.writeText(json.encodeToString(lista))
//guardarMejanga(mejengaEditada)
call.respond(HttpStatusCode.OK, mapOf("message" to "Mejenga actualizada"))
} else {
call.respond(HttpStatusCode.NotFound, mapOf("error" to "Mejenga no encontrada"))
}
} catch (e: Exception) {
println(">> ERROR editando mejenga: ${e.message}")
call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Datos inválidos"))
}
}
get("/mejengas") {
val lista = cargarTodasLasMejengas()
call.respond(lista)
}
delete("/mejenga/{id}") {
val id = call.parameters["id"]
if (id != null) {
println("🗑️ Borrando mejenga con id: $id")
// Aquí podés eliminarla de una base de datos o lista temporal
// if (id != null && borrarMejanga(id)) {
// call.respond(HttpStatusCode.OK, "Mejenga $id borrada")
// } else {
// call.respond(HttpStatusCode.NotFound, "No se encontró")
// }
} else {
call.respond(HttpStatusCode.BadRequest, "Falta el ID")
}
}
//MEJENGUEROS
get("/mejengueros") {
val lista = cargarTodosLosMejengueros()
call.respond(lista)
}
get("/mejengueros/random/{cantidad}") {
val cantidad = call.parameters["cantidad"]?.toIntOrNull() ?: return@get call.respond(
HttpStatusCode.BadRequest,
mapOf("error" to "Cantidad inválida")
)
val archivoMejengueros = File("data/mejengueros.json")
if (!archivoMejengueros.exists()) {
return@get call.respond(HttpStatusCode.NotFound, mapOf("error" to "No hay mejengueros disponibles"))
}
val lista = json.decodeFromString<List<Mejenguero>>(archivoMejengueros.readText())
val seleccionados = lista.shuffled().take(cantidad)
call.respond(seleccionados)
}
}
}
suspend inline fun <reified T : Any> DatabaseReference.awaitSingleValue(): List<T> {
return suspendCancellableCoroutine { continuation ->
val listener = object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
try {
val values = snapshot.children.mapNotNull { child ->
child.getValue(T::class.java)?.let {
// A bit of reflection to create the object with id.
try {
val idProperty = it::class.java.getDeclaredField("id")
idProperty.isAccessible = true
idProperty.set(it, child.key)
} catch (e: NoSuchFieldException) {
// Ignore if the class doesn't have an 'id' field.
}
it
}
}
continuation.resume(values)
} catch (e: Exception) {
continuation.resumeWithException(e)
}
}
override fun onCancelled(error: DatabaseError) {
continuation.resumeWithException(error.toException())
}
}
addListenerForSingleValueEvent(listener)
continuation.invokeOnCancellation {
removeEventListener(listener)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment