Compare commits

17 Commits

Author SHA1 Message Date
darwincereska
f5f40eb79c reset: wiped and rewriting in go 2025-12-20 03:03:41 -05:00
darwincereska
3f4349c9d2 feat: first release 2025-12-19 10:14:05 -05:00
darwincereska
194fd7357c feat: fixed some route serialization 2025-12-18 19:48:50 -05:00
darwincereska
9a644b689a feat: added health and api routes 2025-12-18 09:48:54 -05:00
darwincereska
9bc861f1d1 feat: added block routes 2025-12-18 09:42:28 -05:00
darwincereska
89e45128b6 feat: added mining routes 2025-12-18 09:40:05 -05:00
darwincereska
3c097af03d feat: added transaction routes 2025-12-18 09:37:43 -05:00
darwincereska
35a73c340c feat: added wallet route 2025-12-18 09:34:36 -05:00
darwincereska
1c8fe77a43 feat: added rest of services 2025-12-18 09:22:54 -05:00
darwincereska
6d360df21d feat: added mining service 2025-12-17 10:32:53 -05:00
darwincereska
a6021f9523 feat: added transaction service 2025-12-17 10:26:56 -05:00
darwincereska
abc8ee7553 feat: added wallet service 2025-12-17 10:25:50 -05:00
darwincereska
f46459b687 feat: added hash utils 2025-12-17 10:04:59 -05:00
darwincereska
f818b38b3d feat: added crypto utils 2025-12-17 10:02:51 -05:00
darwincereska
87d094e2f1 feat: added server config 2025-12-17 09:47:09 -05:00
darwincereska
5b70c6de89 feat: added database config 2025-12-17 09:40:34 -05:00
darwincereska
d957f2034d feat: added exceptions 2025-12-17 09:29:42 -05:00
43 changed files with 0 additions and 571 deletions

View File

@@ -1,67 +0,0 @@
# Multi-stage build for Java 24
FROM openjdk:24-jdk-slim AS builder
# Install required packages
RUN apt-get update && apt-get install -y \
curl \
unzip \
&& rm -rf /var/lib/apt/lists/*
# Set working directory
WORKDIR /app
# Copy gradle wrapper and build files
COPY gradle/ gradle/
COPY gradlew .
COPY gradle.properties .
COPY settings.gradle.kts .
COPY build.gradle.kts .
# Make gradlew executable
RUN chmod +x gradlew
# Download dependencies (for better caching)
RUN ./gradlew dependencies --no-daemon
# Copy source code
COPY src/ src/
# Build the application
RUN ./gradlew shadowJar --no-daemon
# Runtime stage
FROM openjdk:24-jdk-slim
# Install required runtime packages
RUN apt-get update && apt-get install -y \
curl \
&& rm -rf /var/lib/apt/lists/*
# Create app user
RUN groupadd -r ccoin && useradd -r -g ccoin ccoin
# Set working directory
WORKDIR /app
# Copy the built JAR from builder stage
COPY --from=builder /app/build/libs/*.jar app.jar
# Change ownership to app user
RUN chown -R ccoin:ccoin /app
# Switch to app user
USER ccoin
# Expose port
EXPOSE 8080
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
# JVM options for production
ENV JAVA_OPTS="-Xmx512m -Xms256m -XX:+UseG1GC -XX:+UseStringDeduplication"
# Run the application
CMD ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

View File

View File

@@ -1,121 +0,0 @@
val ktor_version: String by project
val kotlin_version: String by project
val logback_version: String by project
val exposed_version: String by project
val postgresql_version: String by project
val hikari_version: String by project
val flyway_version: String by project
val bouncycastle_version: String by project
plugins {
kotlin("jvm") version "2.2.21"
kotlin("plugin.serialization") version "2.2.21"
id("io.ktor.plugin") version "3.3.3"
id("com.gradleup.shadow") version "9.3.0"
id("org.flywaydb.flyway") version "11.19.0"
application
}
group = "org.ccoin"
version = "1.0.0"
application {
mainClass.set("org.ccoin.ServerKt")
val isDevelopment: Boolean = project.ext.has("development")
applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment")
}
repositories {
mavenCentral()
}
dependencies {
// Ktor server
implementation("io.ktor:ktor-server-core-jvm:$ktor_version")
implementation("io.ktor:ktor-server-netty-jvm:$ktor_version")
implementation("io.ktor:ktor-server-content-negotiation-jvm:$ktor_version")
implementation("io.ktor:ktor-serialization-kotlinx-json-jvm:$ktor_version")
implementation("io.ktor:ktor-server-cors-jvm:$ktor_version")
implementation("io.ktor:ktor-server-call-logging-jvm:$ktor_version")
implementation("io.ktor:ktor-server-status-pages-jvm:$ktor_version")
implementation("io.ktor:ktor-server-compression-jvm:$ktor_version")
implementation("io.ktor:ktor-server-default-headers-jvm:$ktor_version")
implementation("io.ktor:ktor-server-host-common-jvm:$ktor_version")
implementation("io.ktor:ktor-server-config-yaml:$ktor_version")
// Database
implementation("org.jetbrains.exposed:exposed-core:$exposed_version")
implementation("org.jetbrains.exposed:exposed-dao:$exposed_version")
implementation("org.jetbrains.exposed:exposed-jdbc:$exposed_version")
implementation("org.jetbrains.exposed:exposed-java-time:$exposed_version")
implementation("org.postgresql:postgresql:$postgresql_version")
implementation("com.zaxxer:HikariCP:$hikari_version")
implementation("org.flywaydb:flyway-core:$flyway_version")
implementation("org.flywaydb:flyway-database-postgresql:$flyway_version")
// Logging
implementation("ch.qos.logback:logback-classic:$logback_version")
implementation("io.github.oshai:kotlin-logging-jvm:7.0.0")
// Crypto utilities
implementation("org.bouncycastle:bcprov-jdk18on:$bouncycastle_version")
implementation("org.bouncycastle:bcpkix-jdk18on:$bouncycastle_version")
// JSON
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.1")
// Coroutines
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
// Testing
testImplementation("io.ktor:ktor-server-test-host-jvm:$ktor_version")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version")
testImplementation("io.mockk:mockk:1.13.13")
testImplementation("org.junit.jupiter:junit-jupiter:5.11.3")
testImplementation("org.testcontainers:testcontainers:1.20.3")
testImplementation("org.testcontainers:postgresql:1.20.3")
testImplementation("org.testcontainers:junit-jupiter:1.20.3")
}
tasks.withType<Test> {
useJUnitPlatform()
}
// Fat JAR configuration for Docker
tasks.jar {
enabled = false
}
tasks.shadowJar {
archiveClassifier.set("")
manifest {
attributes["Main-Class"] = "org.ccoin.ServerKt"
}
mergeServiceFiles()
}
// Flyway configuration
flyway {
url = "jdbc:postgresql://localhost:5432/ccoin"
user = "ccoin"
password = "ccoin"
locations = arrayOf("classpath:db/migration")
baselineOnMigrate = true
validateOnMigrate = true
}
// Docker build task
tasks.register<Exec>("buildDocker") {
dependsOn("shadowJar")
commandLine("docker", "build", "-t", "ccoin-server:latest", ".")
}
// Development task
tasks.register("dev") {
dependsOn("run")
doFirst {
project.ext.set("development", true)
}
}

View File

@@ -1,82 +0,0 @@
services:
# Postgres Database
postgres:
image: postgres:17-alpine
container_name: ccoin-postgres
restart: unless-stopped
environment:
POSTGRES_DB: ccoin
POSTGRES_USER: ccoin
POSTGRES_PASSWORD: ccoin
POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C"
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./docker/postgres/init:/docker-entrypoint-initdb.d
networks:
- ccoin-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ccoin -d ccoin"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
# CCoin server
ccoin-server:
build:
context: .
dockerfile: Dockerfile
container_name: ccoin-server
restart: unless-stopped
ports:
- "8080:8080"
depends_on:
postgres:
condition: service_healthy
environment:
# Database configuration
DATABASE_URL: jdbc:postgresql://postgres:5432/ccoin
DATABASE_USER: ccoin
DATABASE_PASSWORD: ccoin
DATABASE_POOL_SIZE: 20
# Server configuration
SERVER_HOST: 0.0.0.0
SERVER_PORT: 8080
# Mining configuration
MINING_DIFFICULTY: 4
MINING_REWARD: 50.0
BLOCK_TIME_TARGET: 600000
# Security
JWT_SECRET: your-super-secret-jwt-key-change-this-in-production
# Logging
LOG_LEVEL: INFO
# Development
DEVELOPMENT_MODE: false
volumes:
- ./logs:/app/logs
networks:
- ccoin-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
volumes:
postgres_data:
driver: local
networks:
ccoin-network:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16

View File

@@ -1,24 +0,0 @@
# Ktor
ktor_version=3.3.3
kotlin_version=2.2.21
# Database
exposed_version=0.56.0
postgresql_version=42.7.4
hikari_version=6.0.0
flyway_version=11.19.0
# Crypto
bouncycastle_version=1.78.1
# Logging
logback_version=1.5.12
# Gradle
kotlin.code.style=official
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
org.gradle.caching=true
org.gradle.parallel=true
# Application
ccoin.version=1.0.0

View File

@@ -1,5 +0,0 @@
package org.ccoin
fun main() {
println("CCoin Server Started")
}

View File

@@ -1,69 +0,0 @@
package org.ccoin.database
import org.jetbrains.exposed.sql.Table
import org.jetbrains.exposed.sql.javatime.timestamp
import java.math.BigDecimal
object Tables {
object Wallets : Table("wallets") {
val address = varchar("address", 64) // Format random_word:random_6_digits
val balance = decimal("balance", 20, 8).default(BigDecimal.ZERO)
val label = varchar("label", 255).nullable()
val createdAt = long("created_at")
val lastActivity = long("last_activity").nullable()
override val primaryKey = PrimaryKey(address)
init {
index(false, createdAt)
index(false, lastActivity)
}
}
object Transactions : Table("transactions") {
val hash = varchar("hash", 64)
val fromAddress = varchar("from_address", 64).nullable()
val toAddress = varchar("to_address", 64)
val amount = decimal("amount", 20, 8)
val fee = decimal("fee", 20, 8).default(BigDecimal.ZERO)
val memo = text("memo").nullable()
val blockHash = varchar("block_hash", 64).nullable()
val timestamp = long("timestamp")
val status = varchar("status", 20).default("pending")
val confirmations = integer("confirmations").default(0)
override val primaryKey = PrimaryKey(hash)
init {
index(false, fromAddress)
index(false, toAddress)
index(false, blockHash)
index(false, timestamp)
index(false, status)
}
}
object Blocks : Table("blocks") {
val hash = varchar("hash", 64)
val previousHash = varchar("previous_hash", 64).nullable()
val merkleRoot = varchar("merkle_root", 64)
val timestamp = long("timestamp")
val difficulty = integer("difficulty")
val nonce = long("nonce")
val minerAddress = varchar("miner_address", 64)
val reward = decimal("reward", 20, 8)
val height = integer("height").autoIncrement()
val transactionCount = integer("transaction_count").default(0)
val confirmations = integer("confirmations").default(0)
override val primaryKey = PrimaryKey(hash)
init {
index(false, height)
index(false, minerAddress)
index(false, timestamp)
index(false, previousHash)
}
}
}

View File

@@ -1,80 +0,0 @@
package org.ccoin.models
import kotlinx.serialization.Serializable
@Serializable
data class ApiResponse<T>(
val success: Boolean,
val data: T? = null,
val error: String? = null,
val timestamp: Long = System.currentTimeMillis()
)
@Serializable
data class ErrorResponse(
val error: String,
val code: String? = null,
val details: Map<String, String>? = null,
val timestamp: Long = System.currentTimeMillis()
)
@Serializable
data class SuccessResponse(
val message: String,
val timestamp: Long = System.currentTimeMillis()
)
@Serializable
data class HealthResponse(
val status: String,
val version: String,
val uptime: Long,
val database: DatabaseHealth,
val blockchain: BlockchainHealth
)
@Serializable
data class DatabaseHealth(
val connected: Boolean,
val responseTime: Long,
val activeConnections: Int,
val maxConnections: Int
)
@Serializable
data class BlockchainHealth(
val latestBlock: Int,
val pendingTransactions: Int,
val networkHashRate: Double,
val averageBlockTime: Long
)
@Serializable
data class PaginationRequest(
val page: Int = 1,
val pageSize: Int = 50,
val sortBy: String? = null,
val sortOrder: SortOrder = SortOrder.DESC
)
@Serializable
data class PaginatedResponse<T>(
val data: List<T>,
val pagination: PaginationInfo
)
@Serializable
data class PaginationInfo(
val currentPage: Int,
val pageSize: Int,
val totalItems: Int,
val totalPages: Int,
val hasNext: Boolean,
val hasPrevious: Boolean
)
@Serializable
enum class SortOrder {
ASC,
DESC
}

View File

@@ -1,53 +0,0 @@
package org.ccoin.models
import kotlinx.serialization.Serializable
@Serializable
data class StartMiningRequest(
val minerAddress: String, // Format: random_word:random_6_digits
val difficulty: Int? = null
)
@Serializable
data class SubmitMiningRequest(
val minerAddress: String,
val nonce: Long,
val hash: String,
val previousHash: String,
val timestamp: Long
)
@Serializable
data class BlockResponse(
val hash: String,
val previousHash: String?,
val merkleRoot: String,
val timestamp: Long,
val difficulty: Int,
val nonce: Long,
val minerAddress: String
val reward: Double,
val height: Int,
val transactionCount: Int,
val confirmations: Int = 0
)
@Serializable
data class MiningJobResponse(
val jobId: String,
val target: String,
val difficulty: Int,
val previousHash: String,
val height: Int,
val timestamp: Long,
val expiresAt: Long
)
@Serializable
data class MiningStatsResponse(
val minerAddress: String,
val totalBlocksMined: Int,
val totalRewardEarned: Double,
val lastBlockMined: Long?,
val currentDifficulty: Int
)

View File

@@ -1,42 +0,0 @@
package org.ccoin.models
import kotlinx.serialization.Serializable
@Serializable
data class SendTransactionRequest(
val fromAddress: String, // Format random_word:random_6_digits
val toAddress: String, // Format random_word:random_6_digits
val amount: Double,
val fee: Double = 0.0,
val memo: String? = null
)
@Serializable
data class TransactionResponse(
val hash: String,
val fromAddress: String?,
val toAddress: String,
val amount: Double,
val fee: Double,
val memo: String?,
val blockHash: String?,
val timestamp: Long,
val status: TransactionStatus,
val confirmations: Int = 0
)
@Serializable
data class TransactionHistoryResponse(
val transactions: List<TransactionResponse>,
val totalCount: Int,
val page: Int,
val pageSize: Int
)
@Serializable
enum class TransactionStatus {
PENDING,
CONFIRMED,
FAILED,
CANCELLED
}

View File

@@ -1,28 +0,0 @@
package org.ccoin.models
import kotlinx.serialization.Serializable
@Serializable
data class CreateWalletRequest(
val label: String? = null
)
@Serializable
data class WalletResponse(
val address: String, // Format random_word:random_6_digits (e.g. "phoenix:123456")
val balance: Double,
val label: String?,
val createdAt: Long,
val lastActivity: Long?
)
@Serializable
data class BalanceResponse(
val address: String,
val balance: Double
)
@Serializable
data class UpdateWalletRequest(
val label: String?
)