add powerdns client codes.

This commit is contained in:
dalbodeule 2024-06-05 20:50:43 +09:00
parent 07aa50cd3a
commit 3dd8fc69c1
No known key found for this signature in database
GPG Key ID: EFA860D069C9FA65
15 changed files with 372 additions and 176 deletions

View File

@ -31,6 +31,8 @@ dependencies {
implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2") implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2")
implementation("com.google.code.gson:gson:2.11.0")
implementation("io.github.cdimascio:dotenv-kotlin:6.4.1") implementation("io.github.cdimascio:dotenv-kotlin:6.4.1")
runtimeOnly("org.mariadb.jdbc:mariadb-java-client") runtimeOnly("org.mariadb.jdbc:mariadb-java-client")

7
docker_build.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
# 현재 날짜와 시간을 YYMMDD(AM/PM)HHMM 형식으로 설정
current_time=$(date +'%y%m%d%p%H%M')
# Docker 이미지 빌드 명령 실행
docker build --no-cache -t dalbodeule/dnsapi:$current_time -t dalbodeule/dnsapi:latest .

View File

@ -3,6 +3,10 @@ package space.mori.dnsapi
import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication import org.springframework.boot.runApplication
import io.github.cdimascio.dotenv.dotenv import io.github.cdimascio.dotenv.dotenv
import java.time.OffsetDateTime
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import java.util.*
@SpringBootApplication @SpringBootApplication
class DnsapiApplication class DnsapiApplication
@ -13,7 +17,9 @@ fun main(args: Array<String>) {
"DB_PORT" to dotenv["DB_PORT"], "DB_PORT" to dotenv["DB_PORT"],
"DB_NAME" to dotenv["DB_NAME"], "DB_NAME" to dotenv["DB_NAME"],
"DB_USER" to dotenv["DB_USER"], "DB_USER" to dotenv["DB_USER"],
"DB_PASSWORD" to dotenv["DB_PASSWORD"] "DB_PASSWORD" to dotenv["DB_PASSWORD"],
"PDNS_API_KEY" to dotenv["PDNS_API_KEY"],
"PDNS_API_URL" to dotenv["PDNS_API_URL"],
) )
runApplication<DnsapiApplication>(*args) { runApplication<DnsapiApplication>(*args) {
@ -23,4 +29,9 @@ fun main(args: Array<String>) {
val dotenv = dotenv { val dotenv = dotenv {
ignoreIfMissing = true ignoreIfMissing = true
}
fun Date.getISOFormat(): String {
val offsetDateTime = OffsetDateTime.ofInstant(this.toInstant(), ZoneOffset.UTC)
return offsetDateTime.format(DateTimeFormatter.ISO_DATE_TIME)
} }

View File

@ -0,0 +1,82 @@
package space.mori.dnsapi
import com.google.gson.Gson
import org.springframework.beans.factory.annotation.Value
import org.springframework.http.*
import org.springframework.stereotype.Service
import org.springframework.web.client.RestTemplate
import space.mori.dnsapi.dto.RecordRequestDTO
@Service
class PowerDNSApiClient {
@Value("\${pdns.api.url}")
private lateinit var apiUrl: String
@Value("\${pdns.api.key}")
private lateinit var apiKey: String
private val restTemplate = RestTemplate()
private val gson = Gson()
private fun createHeaders(): HttpHeaders {
val headers = HttpHeaders()
headers.set("X-API-Key", apiKey)
headers.contentType = MediaType.APPLICATION_JSON
return headers
}
fun createDomain(name: String): ResponseEntity<String> {
val url = "$apiUrl/servers/localhost/zones"
val headers = createHeaders()
val domainRequest = DomainRequest("$name.", "Master", arrayOf(), arrayOf())
val body = gson.toJson(domainRequest)
val entity = HttpEntity(body, headers)
return restTemplate.exchange(url, HttpMethod.POST, entity, String::class.java)
}
fun createRecord(domainName: String, recordRequest: RecordRequestDTO): ResponseEntity<String> {
val url = "$apiUrl/servers/localhost/zones/$domainName."
val headers = createHeaders()
val record = RecordRequest(
name = "${recordRequest.name}.$domainName.",
type = recordRequest.type,
ttl = recordRequest.ttl,
changetype = "REPLACE",
records = arrayOf(RecordContent(recordRequest.content, false))
)
val body = gson.toJson(RecordRequestWrapper(arrayOf(record)))
val entity = HttpEntity(body, headers)
return restTemplate.exchange(url, HttpMethod.PATCH, entity, String::class.java)
}
fun deleteDomain(name: String): ResponseEntity<String> {
val url = "$apiUrl/servers/localhost/zones/$name."
val headers = createHeaders()
val entity = HttpEntity<String>(headers)
return restTemplate.exchange(url, HttpMethod.DELETE, entity, String::class.java)
}
private data class DomainRequest(
val name: String,
val kind: String,
val masters: Array<String>,
val nameservers: Array<String>
)
private data class RecordRequestWrapper(
val rrsets: Array<RecordRequest>
)
private data class RecordRequest(
val name: String,
val type: String,
val ttl: Int,
val changetype: String,
val records: Array<RecordContent>
)
private data class RecordContent(
val content: String,
val disabled: Boolean
)
}

View File

@ -8,7 +8,6 @@ import io.swagger.v3.oas.annotations.media.Schema
import io.swagger.v3.oas.annotations.responses.ApiResponse import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.responses.ApiResponses import io.swagger.v3.oas.annotations.responses.ApiResponses
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.ErrorResponse
import org.springframework.web.bind.annotation.* import org.springframework.web.bind.annotation.*
import space.mori.dnsapi.db.Domain import space.mori.dnsapi.db.Domain
import space.mori.dnsapi.dto.DomainResponseDTO import space.mori.dnsapi.dto.DomainResponseDTO
@ -68,5 +67,5 @@ class DomainController {
domainService!!.deleteDomain(cfid!!) domainService!!.deleteDomain(cfid!!)
} }
private fun Domain.toDTO() = DomainResponseDTO(cfid = cfid!!, domainName = domainName!!) private fun Domain.toDTO() = DomainResponseDTO(id = cfid, name = name)
} }

View File

@ -7,20 +7,23 @@ import io.swagger.v3.oas.annotations.media.Schema
import io.swagger.v3.oas.annotations.responses.ApiResponse import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.responses.ApiResponses import io.swagger.v3.oas.annotations.responses.ApiResponses
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.jpa.domain.AbstractPersistable_.id
import org.springframework.web.bind.annotation.* import org.springframework.web.bind.annotation.*
import space.mori.dnsapi.service.RecordService import space.mori.dnsapi.db.Domain
import space.mori.dnsapi.db.Record import space.mori.dnsapi.db.Record as DomainRecord
import space.mori.dnsapi.dto.RecordRequestDTO import space.mori.dnsapi.dto.RecordRequestDTO
import space.mori.dnsapi.dto.RecordResponseDTO import space.mori.dnsapi.dto.RecordResponseDTO
import space.mori.dnsapi.getISOFormat
import space.mori.dnsapi.service.RecordService
import java.util.* import java.util.*
@RestController @RestController
@RequestMapping("/record") @RequestMapping("/zones")
class RecordController { class RecordController(
@Autowired @Autowired
private val recordService: RecordService? = null private val recordService: RecordService,
) {
@GetMapping @GetMapping("{zone_id}/dns_records")
@Operation(summary = "Get all records", tags=["record"]) @Operation(summary = "Get all records", tags=["record"])
@ApiResponses(value = [ @ApiResponses(value = [
ApiResponse(responseCode = "200", description = "Return All Records", ApiResponse(responseCode = "200", description = "Return All Records",
@ -28,11 +31,11 @@ class RecordController {
ApiResponse(responseCode = "400", description = "Bad request", ApiResponse(responseCode = "400", description = "Bad request",
content = [Content(schema = Schema(implementation = Void::class))]), content = [Content(schema = Schema(implementation = Void::class))]),
]) ])
fun allRecords(@PathVariable cfid: String?): List<RecordResponseDTO?> { fun allRecords(@PathVariable zone_id: String): List<RecordResponseDTO> {
return recordService!!.getAllRecords(cfid!!).map{ it?.toDTO() } return recordService.getRecordsByDomain(zone_id)?.map{ it } ?: listOf()
} }
@GetMapping("/{cfid}") @GetMapping("{zone_id}/dns_records/{dns_record_id}")
@Operation(summary = "Get Record by ID", tags=["record"]) @Operation(summary = "Get Record by ID", tags=["record"])
@ApiResponses(value = [ @ApiResponses(value = [
ApiResponse(responseCode = "200", description = "Return Record", ApiResponse(responseCode = "200", description = "Return Record",
@ -40,11 +43,11 @@ class RecordController {
ApiResponse(responseCode = "400", description = "Bad request", ApiResponse(responseCode = "400", description = "Bad request",
content = [Content(schema = Schema(implementation = Void::class))]) content = [Content(schema = Schema(implementation = Void::class))])
]) ])
fun getRecordByCfid(@PathVariable cfid: String?): Optional<RecordResponseDTO> { fun getRecordByCfid(@PathVariable zone_id: String, @PathVariable dns_record_id: String): RecordResponseDTO {
return recordService!!.getRecordById(cfid!!).map { it.toDTO() } return recordService.getRecord(zone_id, dns_record_id)
} }
@PostMapping @PostMapping("{zone_id}/dns_records")
@Operation(summary = "Add Record by ID", tags=["record"]) @Operation(summary = "Add Record by ID", tags=["record"])
@ApiResponses(value = [ @ApiResponses(value = [
ApiResponse(responseCode = "200", description = "Return Record", ApiResponse(responseCode = "200", description = "Return Record",
@ -52,11 +55,11 @@ class RecordController {
ApiResponse(responseCode = "400", description = "Bad request", ApiResponse(responseCode = "400", description = "Bad request",
content = [Content(schema = Schema(implementation = Void::class))]), content = [Content(schema = Schema(implementation = Void::class))]),
]) ])
fun createRecord(@RequestBody record: RecordRequestDTO): RecordResponseDTO { fun createRecord(@PathVariable zone_id: String, @RequestBody record: RecordRequestDTO): RecordResponseDTO {
return recordService!!.createRecord(record).toDTO() return recordService.createRecord(zone_id, record)
} }
@DeleteMapping("/{cfid}") @DeleteMapping("{zone_id}/dns_records/{dns_record_id}")
@Operation(summary = "Remove Record by ID", tags=["record"]) @Operation(summary = "Remove Record by ID", tags=["record"])
@ApiResponses(value = [ @ApiResponses(value = [
ApiResponse(responseCode = "200", description = "Return Record", ApiResponse(responseCode = "200", description = "Return Record",
@ -64,19 +67,33 @@ class RecordController {
ApiResponse(responseCode = "400", description = "Bad request", ApiResponse(responseCode = "400", description = "Bad request",
content = [Content(schema = Schema(implementation = Void::class))]), content = [Content(schema = Schema(implementation = Void::class))]),
]) ])
fun deleteRecord(@PathVariable cfid: String?) { fun deleteRecord(@PathVariable zone_id: String, @PathVariable dns_record_id: String) {
recordService!!.deleteRecord(cfid!!) recordService.deleteRecord(zone_id, dns_record_id)
} }
private fun Record.toDTO() = RecordResponseDTO( @PatchMapping("{zone_id}/dns_records/{dns_record_id}")
cfid = cfid!!, @Operation(summary = "Update Record by ID", tags=["record"])
name = name!!, @ApiResponses(value = [
type = type!!, ApiResponse(responseCode = "200", description = "Return Record",
content = content!!, content = [Content(schema = Schema(implementation = RecordResponseDTO::class))]),
prio = prio!!, ApiResponse(responseCode = "400", description = "Bad request",
ttl = ttl!!, content = [Content(schema = Schema(implementation = Void::class))]),
changeDate = changeDate!!, ])
auth = auth, fun updateRecord(@PathVariable zone_id: String, @PathVariable dns_record_id: String, @RequestBody record: RecordRequestDTO): RecordResponseDTO {
disabled = disabled return recordService.updateRecord(zone_id, dns_record_id, record)
}
private fun DomainRecord.toDTO() = RecordResponseDTO(
id = cfid,
type = type,
name = name,
content = content,
zoneId = domain.cfid,
zoneName = domain.name,
priority = prio,
ttl = ttl,
createdOn = createdOn.getISOFormat(),
modifiedOn = modifiedOn.getISOFormat(),
comment = comment
) )
} }

View File

@ -7,37 +7,17 @@ import java.util.*
@Entity @Entity
@Table(name = "domains") @Table(name = "domains")
class Domain { data class Domain(
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private val id: Long? = null var id: Long? = null,
@Column(nullable = false, length = 255) @Column(nullable = false, unique = true)
val domainName: String? = null var name: String,
@Column(nullable = true, length = 128) @Column(nullable = false, unique = true)
val master: String? = null var cfid: String = UUID.randomUUID().toString().replace("-", "")
) {
@Column(nullable = true, name = "last_check") @OneToMany(mappedBy = "domain", cascade = [CascadeType.ALL], orphanRemoval = true)
val lastCheck: Int? = null var records: List<Record> = mutableListOf()
@Column(nullable = false, length = 6)
val type: String? = null
@Column(nullable = true, name = "notified_serial")
val notifiedSerial: Int? = null
@Column(nullable = false, length = 128)
val account: String? = null
@Column(unique = true, nullable = false, length = 32)
var cfid: String? = null
@OneToMany(mappedBy = "domain", cascade = [CascadeType.ALL], fetch = FetchType.LAZY)
private val records: Set<Record>? = null
@PrePersist
protected fun onCreate() {
this.cfid = UUID.randomUUID().toString().replace("-", "")
} // Getters and setters
} }

View File

@ -6,68 +6,28 @@ import java.util.*
@Entity @Entity
@Table(name = "records") @Table(name = "records")
class Record { data class Record(
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private val id: Long? = null var id: Long? = null,
@ManyToOne @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "domain_id", nullable = false) @JoinColumn(name = "domain_id", nullable = false)
var domain: Domain? = null var domain: Domain,
@Column(nullable = false, length = 255) var name: String,
var name: String? = null var type: String,
var content: String,
var ttl: Int,
var prio: Int,
var disabled: Boolean,
var auth: Boolean,
@Column(nullable = false, length = 10) var createdOn: Date,
var type: String? = null var modifiedOn: Date,
@Column(nullable = false, length = 64000) var comment: String,
var content: String? = null
@Column(nullable = true) @Column(nullable = false, unique = true)
var ttl: Int? var cfid: String = UUID.randomUUID().toString().replace("-", "")
)
@Column(nullable = true)
var prio: Int?
@Column(nullable = true)
var changeDate: Int?
var disabled: Boolean = false
var auth: Boolean = true
@Column(unique = true, nullable = false, length = 32)
var cfid: String? = null
@Column(nullable = true, length = 64)
var comment: String? = null
@PrePersist
private fun onCreate() {
this.cfid = UUID.randomUUID().toString().replace("-", "")
} // Getters and setters
constructor(
name: String,
type: String,
content: String,
changeDate: Int?,
disabled: Boolean,
domain: Domain,
comment: String?,
auth: Boolean = true,
ttl: Int? = 300,
prio: Int? = 0,
) {
this.name = name
this.type = type
this.content = content
this.ttl = ttl
this.prio = prio
this.changeDate = changeDate
this.disabled = disabled
this.auth = auth
this.domain = domain
this.comment = comment
}
}

View File

@ -12,8 +12,11 @@ interface RecordRepository : JpaRepository<Record?, Long?> {
fun findByCfid(cfid: String): Optional<Record> fun findByCfid(cfid: String): Optional<Record>
@Transactional @Transactional
fun deleteByCfid(cfid: String) fun findByDomainIdAndCfid(domainId: Long, cfid: String): Optional<Record>
@Query("SELECT r FROM Record r WHERE r.domain.cfid = :domainCfid") @Transactional
fun findByDomainCfid(@Param("domainCfid") cfid: String): List<Record> fun deleteByCfid(cfid: String): Int
@Transactional
fun deleteByDomainIdAndCfid(domain_id: Long, cfid: String): Int
} }

View File

@ -5,5 +5,5 @@ import io.swagger.v3.oas.annotations.media.Schema
@Schema(description = "Request DTO for Domain") @Schema(description = "Request DTO for Domain")
data class DomainRequestDTO( data class DomainRequestDTO(
@Schema(description = "Domain name(TLD)", example = "example.com") @Schema(description = "Domain name(TLD)", example = "example.com")
val domainName: String val name: String
) )

View File

@ -5,8 +5,8 @@ import io.swagger.v3.oas.annotations.media.Schema
@Schema(description = "Response DTO for Domain") @Schema(description = "Response DTO for Domain")
data class DomainResponseDTO( data class DomainResponseDTO(
@Schema(description = "Domain CFID", example = "123e4567e89b12d3a456426655440000") @Schema(description = "Domain CFID", example = "123e4567e89b12d3a456426655440000")
val cfid: String, val id: String,
@Schema(description = "Domain name(TLD)", example = "example.com") @Schema(description = "Domain name(TLD)", example = "example.com")
val domainName: String val name: String
) )

View File

@ -4,20 +4,23 @@ import io.swagger.v3.oas.annotations.media.Schema
@Schema(description = "Request DTO for Record") @Schema(description = "Request DTO for Record")
data class RecordRequestDTO( data class RecordRequestDTO(
@Schema(description = "Host name", example = "www")
val host: String,
@Schema(description = "Record type", example = "A") @Schema(description = "Record type", example = "A")
val type: String, val type: String,
@Schema(description = "Host name", example = "www.example.com.")
val name: String,
@Schema(description = "Record data", example = "192.0.2.1") @Schema(description = "Record data", example = "192.0.2.1")
val data: String, val content: String,
@Schema(description = "TTL (Time to Live)", example = "3600") @Schema(description = "TTL (Time to Live)", example = "3600")
val ttl: Int, val ttl: Int = 300,
@Schema(description = "Domain CFID", example = "123e4567e89b12d3a456426655440000") @Schema(description = "Priority", example = "0")
val cfid: String, val priority: Int? = null,
@Schema(description = "Proxied: cloudflare api compatibility", example = "false")
val proxied: Boolean = false,
@Schema(description = "comment", example="") @Schema(description = "comment", example="")
val comment: String val comment: String

View File

@ -5,30 +5,45 @@ import java.util.*
@Schema(description = "Response DTO for Record") @Schema(description = "Response DTO for Record")
data class RecordResponseDTO( data class RecordResponseDTO(
@Schema(description = "Record CFID", example = "123e4567e89b12d3a456426655440001") @Schema(description = "Record ID", example = "123e4567e89b12d3a456426655440001")
val cfid: String, val id: String,
@Schema(description = "Host name", example = "www.domain.tld")
val name: String,
@Schema(description = "Record type", example = "A") @Schema(description = "Record type", example = "A")
val type: String, var type: String,
@Schema(description = "Record data", example = "192.0.2.1") @Schema(description = "Record name", example = "test.example.com")
val content: String, var name: String,
@Schema(description = "TTL (Time to Live)", example = "3600") @Schema(description = "Record content", example = "1.1.1.1")
val ttl: Int, var content: String,
@Schema(description = "TTL per second", example = "300s") @Schema(description = "Zone(TLD) ID", example = "123e4567e89b12d3a456426655440001")
val prio: Int, val zoneId: String,
@Schema(description = "Changed date with Unix Timestamp") @Schema(description = "Zone name(TLD)", example = "example.com")
val changeDate: Int, val zoneName: String,
@Schema(description = "is disabled?", example = "false") @Schema(description = "Record creation time", example = "2014-01-01T05:20:00.12345Z")
val disabled: Boolean, val createdOn: String,
@Schema(description = "is authed", example = "true") @Schema(description = "Record modification time", example = "2014-01-01T05:20:00.12345Z")
val auth: Boolean, val modifiedOn: String,
@Schema(description = "Record priority", example = "0")
val priority: Int? = 0,
@Schema(description = "is proxyable: must false", example = "false")
val proxiable: Boolean = false,
@Schema(description = "is proxied: must false", example = "false")
val proxied: Boolean = false,
@Schema(description = "Record TTL", example = "300")
val ttl: Int = 300,
@Schema(description = "Record is locked: must false", example = "false")
val locked: Boolean = false,
@Schema(description = "Record comments", example = "")
val comment: String? = null,
) )

View File

@ -2,45 +2,160 @@ package space.mori.dnsapi.service
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import space.mori.dnsapi.PowerDNSApiClient
import space.mori.dnsapi.db.DomainRepository import space.mori.dnsapi.db.DomainRepository
import space.mori.dnsapi.db.RecordRepository
import space.mori.dnsapi.db.Record as DomainRecord import space.mori.dnsapi.db.Record as DomainRecord
import space.mori.dnsapi.db.RecordRepository
import space.mori.dnsapi.dto.DomainRequestDTO
import space.mori.dnsapi.dto.RecordRequestDTO import space.mori.dnsapi.dto.RecordRequestDTO
import space.mori.dnsapi.dto.RecordResponseDTO
import space.mori.dnsapi.getISOFormat
import java.util.* import java.util.*
@Service @Service
class RecordService { class RecordService(
@Autowired @Autowired
private lateinit var domainRepository: DomainRepository private val powerDNSApiClient: PowerDNSApiClient,
@Autowired @Autowired
private val recordRepository: RecordRepository? = null private val domainRepository: DomainRepository,
@Autowired
private val recordRepository: RecordRepository
) {
fun createRecord(domain_id: String, recordRequest: RecordRequestDTO): RecordResponseDTO {
val domain = domainRepository.findByCfid(domain_id)
if(domain.isEmpty) throw RuntimeException("Failed to find domain in API: $domain_id")
fun getAllRecords(cfid: String): List<DomainRecord?> { val response = powerDNSApiClient.createRecord(domain.get().name, recordRequest)
return recordRepository!!.findByDomainCfid(cfid) if (!response.statusCode.is2xxSuccessful) {
} throw RuntimeException("Failed to create record in PowerDNS: ${response.body}")
}
fun getRecordById(cfid: String): Optional<DomainRecord> { val record = DomainRecord(
return recordRepository!!.findByCfid(cfid) domain = domain.get(),
} name = recordRequest.name,
type = recordRequest.type,
fun createRecord(record: RecordRequestDTO): DomainRecord { content = recordRequest.content,
val domain = domainRepository.findByCfid(record.cfid) ttl = recordRequest.ttl,
.orElseThrow { IllegalArgumentException("Invalid domain CFID") } prio = recordRequest.priority ?: 0,
val r = DomainRecord( disabled = false,
name = record.host, auth = true,
type = record.type, createdOn = Date(),
content = record.data, modifiedOn = Date(),
ttl = record.ttl, comment = recordRequest.comment,
domain = domain, )
comment = record.comment,
changeDate = java.util.Date().time.toInt(), return RecordResponseDTO(
disabled = false id = record.cfid,
type = record.type,
name = record.name,
content = record.content,
proxiable = false,
proxied = false,
ttl = record.ttl,
locked = false,
zoneId = record.cfid,
zoneName = domain.get().name,
createdOn = record.createdOn.getISOFormat(),
modifiedOn = record.modifiedOn.getISOFormat(),
priority = record.prio,
comment = record.comment
) )
return recordRepository!!.save(r)
} }
fun deleteRecord(cfid: String) { fun getRecord(domain_id: String, record_id: String): RecordResponseDTO {
recordRepository!!.deleteByCfid(cfid) val domain = domainRepository.findByCfid(domain_id)
if(domain.isEmpty) throw RuntimeException("Failed to find domain in API: $domain_id")
val record = domain.get().records.find { it.cfid == record_id }
if(record == null) throw RuntimeException("Failed to find record in API: $record_id")
return RecordResponseDTO(
id = record.cfid,
type = record.type,
name = record.name,
content = record.content,
ttl = record.ttl,
zoneId = record.domain.cfid,
zoneName = record.domain.name,
createdOn = record.createdOn.getISOFormat(),
modifiedOn = record.modifiedOn.getISOFormat(),
comment = record.comment,
)
}
fun getRecordsByDomain(domain_id: String): List<RecordResponseDTO>? {
val domain = domainRepository.findByCfid(domain_id).orElseThrow { RuntimeException("Failed to find domain in API: $domain_id") }
return domain?.records?.map { RecordResponseDTO(
id = it.cfid,
type = it.type,
name = it.name,
content = it.content,
zoneId = it.domain.cfid,
zoneName = it.domain.name,
priority = it.prio,
ttl = it.ttl,
createdOn = it.createdOn.getISOFormat(),
modifiedOn = it.modifiedOn.getISOFormat(),
comment = it.comment,
)}
}
@Transactional
fun updateRecord(domainId: String, cfid: String, updatedRecord: RecordRequestDTO): RecordResponseDTO {
// 도메인 조회
val domain = domainRepository.findByCfid(domainId)
.orElseThrow { RuntimeException("Domain not found") }
// 레코드 조회
val record = recordRepository.findByDomainIdAndCfid(domain.id!!, cfid)
.orElseThrow { RuntimeException("Record not found") }
// 레코드 업데이트
record.name = updatedRecord.name
record.type = updatedRecord.type
record.content = updatedRecord.content
record.ttl = updatedRecord.ttl
record.prio = updatedRecord.priority ?: 0
record.comment = updatedRecord.comment
record.modifiedOn = Date()
val response = powerDNSApiClient.createRecord(domain!!.name, updatedRecord)
if (!response.statusCode.is2xxSuccessful) {
throw RuntimeException("Failed to update record in PowerDNS: ${response.body}")
}
// 저장
val savedRecord = recordRepository.save(record)
return RecordResponseDTO(
id = savedRecord.cfid,
type = savedRecord.type,
name = savedRecord.name,
content = savedRecord.content,
proxiable = true,
proxied = false,
ttl = savedRecord.ttl,
locked = false,
zoneId = domain.cfid,
zoneName = domain.name,
createdOn = savedRecord.createdOn.getISOFormat(),
modifiedOn = savedRecord.modifiedOn.getISOFormat(),
priority = savedRecord.prio
)
}
fun deleteRecord(domain_id: String, record_id: String) {
val domain = domainRepository.findByCfid(domain_id).orElseThrow { RuntimeException("Failed to find domain in API: $domain_id") }
val deletedCount = recordRepository.deleteByDomainIdAndCfid(domain.id!!, record_id)
if(deletedCount == 0) throw RuntimeException("Failed to find record in API: $record_id")
}
fun deleteDomain(name: String) {
val response = powerDNSApiClient.deleteDomain(name)
if (!response.statusCode.is2xxSuccessful) {
throw RuntimeException("Failed to delete domain in PowerDNS: ${response.body}")
}
} }
} }

View File

@ -11,4 +11,6 @@ springdoc.swagger-ui.enabled=false
springdoc.api-docs.path=/api-docs springdoc.api-docs.path=/api-docs
springdoc.default-consumes-media-type= application/json springdoc.default-consumes-media-type= application/json
springdoc.default-produces-media-type= application/json springdoc.default-produces-media-type= application/json
springdoc.version= '@project.version@' springdoc.version= '@project.version@'
pdns.api.key=${PDNS_API_KEY}
pdns.api.url=${PDNS_API_URL}