mirror of
https://github.com/dalbodeule/sh0rt.kr-pdns.git
synced 2025-06-08 18:58:20 +00:00
add domain service, auth service
This commit is contained in:
parent
3dd8fc69c1
commit
d99606436d
@ -24,7 +24,7 @@ repositories {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
||||||
// implementation("org.springframework.boot:spring-boot-starter-security")
|
implementation("org.springframework.boot:spring-boot-starter-security")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-validation")
|
implementation("org.springframework.boot:spring-boot-starter-validation")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
|
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
|
||||||
|
1
inc.env
1
inc.env
@ -10,6 +10,7 @@ MARIADB_USER=pdns
|
|||||||
MARIADB_PASSWORD=
|
MARIADB_PASSWORD=
|
||||||
|
|
||||||
PDNS_API_KEY=
|
PDNS_API_KEY=
|
||||||
|
PDNS_API_URL=
|
||||||
PDNS_WEBSERVER_KEY=
|
PDNS_WEBSERVER_KEY=
|
||||||
PDNS_VERSION_STRING=
|
PDNS_VERSION_STRING=
|
||||||
MASTER_PDNS_IP=
|
MASTER_PDNS_IP=
|
||||||
|
@ -3,6 +3,8 @@ 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 org.springframework.security.core.context.SecurityContextHolder
|
||||||
|
import space.mori.dnsapi.db.User
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
import java.time.ZoneOffset
|
import java.time.ZoneOffset
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
42
src/main/kotlin/space/mori/dnsapi/SecurityConfig.kt
Normal file
42
src/main/kotlin/space/mori/dnsapi/SecurityConfig.kt
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package space.mori.dnsapi
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.context.annotation.Bean
|
||||||
|
import org.springframework.context.annotation.Configuration
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy
|
||||||
|
import org.springframework.security.web.SecurityFilterChain
|
||||||
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
|
||||||
|
import space.mori.dnsapi.filter.UserFilter
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
class SecurityConfig {
|
||||||
|
@Autowired
|
||||||
|
private val service: UserFilter? = null
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||||
|
return http
|
||||||
|
.csrf{ it.disable() }
|
||||||
|
.cors{ it.disable() }
|
||||||
|
.sessionManagement {
|
||||||
|
it.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||||
|
}
|
||||||
|
.authorizeHttpRequests {
|
||||||
|
it.requestMatchers("/zones/**").authenticated()
|
||||||
|
it.requestMatchers(
|
||||||
|
"/swagger-ui.html",
|
||||||
|
"/swagger-ui/**",
|
||||||
|
"/api-docs/**",
|
||||||
|
"/v3/api-docs/**",
|
||||||
|
"/v2/api-docs/**",
|
||||||
|
"/swagger-resources/**",
|
||||||
|
"/webjars/**"
|
||||||
|
).permitAll()
|
||||||
|
}
|
||||||
|
.addFilterBefore(service, UsernamePasswordAuthenticationFilter::class.java)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
@ -3,23 +3,28 @@ package space.mori.dnsapi
|
|||||||
import io.swagger.v3.oas.models.Components
|
import io.swagger.v3.oas.models.Components
|
||||||
import io.swagger.v3.oas.models.OpenAPI
|
import io.swagger.v3.oas.models.OpenAPI
|
||||||
import io.swagger.v3.oas.models.info.Info
|
import io.swagger.v3.oas.models.info.Info
|
||||||
|
import io.swagger.v3.oas.models.security.SecurityRequirement
|
||||||
|
import io.swagger.v3.oas.models.security.SecurityScheme
|
||||||
import org.springframework.context.annotation.Bean
|
import org.springframework.context.annotation.Bean
|
||||||
import org.springframework.context.annotation.Configuration
|
import org.springframework.context.annotation.Configuration
|
||||||
|
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
class SwaggerConfig {
|
class SwaggerConfig {
|
||||||
|
private val securitySchemeName = "api token"
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
fun openAPI(): OpenAPI {
|
fun openAPI(): OpenAPI {
|
||||||
return OpenAPI()
|
return OpenAPI()
|
||||||
.components(Components())
|
.info(Info().title("Cloudflare compatible PowerDNS API Server").version("v1.0.0"))
|
||||||
.info(apiInfo())
|
.addSecurityItem(SecurityRequirement().addList(securitySchemeName))
|
||||||
}
|
.components(Components()
|
||||||
|
.addSecuritySchemes(securitySchemeName,
|
||||||
private fun apiInfo(): Info {
|
SecurityScheme()
|
||||||
return Info()
|
.name(securitySchemeName)
|
||||||
.title("Spring Boot REST API Specifications")
|
.type(SecurityScheme.Type.HTTP)
|
||||||
.description("Specification")
|
.scheme("bearer")
|
||||||
.version("1.0.0")
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,8 +1,6 @@
|
|||||||
package space.mori.dnsapi.controller
|
package space.mori.dnsapi.controller
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation
|
import io.swagger.v3.oas.annotations.Operation
|
||||||
import io.swagger.v3.oas.annotations.Parameter
|
|
||||||
import io.swagger.v3.oas.annotations.media.ArraySchema
|
|
||||||
import io.swagger.v3.oas.annotations.media.Content
|
import io.swagger.v3.oas.annotations.media.Content
|
||||||
import io.swagger.v3.oas.annotations.media.Schema
|
import io.swagger.v3.oas.annotations.media.Schema
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse
|
import io.swagger.v3.oas.annotations.responses.ApiResponse
|
||||||
@ -10,61 +8,66 @@ 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.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.ApiResponseDTO
|
||||||
|
import space.mori.dnsapi.dto.DeleteResponseWithId
|
||||||
|
import space.mori.dnsapi.dto.DomainRequestDTO
|
||||||
import space.mori.dnsapi.dto.DomainResponseDTO
|
import space.mori.dnsapi.dto.DomainResponseDTO
|
||||||
|
import space.mori.dnsapi.filter.getCurrentUser
|
||||||
import space.mori.dnsapi.service.DomainService
|
import space.mori.dnsapi.service.DomainService
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/domain")
|
@RequestMapping("/zones")
|
||||||
class DomainController {
|
class DomainController(
|
||||||
@Autowired
|
@Autowired
|
||||||
private val domainService: DomainService? = null
|
private val domainService: DomainService
|
||||||
|
) {
|
||||||
@get:GetMapping
|
@GetMapping
|
||||||
@get:Operation(summary = "Get all domains", tags = ["domain"])
|
@Operation(summary = "Get all domains", tags = ["domain"])
|
||||||
@get:ApiResponse(responseCode = "200", description = "Returns all domains",
|
@ApiResponses(value = [
|
||||||
content = [Content(array = ArraySchema(schema = Schema(implementation = DomainResponseDTO::class)))])
|
ApiResponse(responseCode = "200", description = "Returns all domains", useReturnTypeSchema = true),
|
||||||
val allDomains: List<DomainResponseDTO?>
|
ApiResponse(responseCode = "404", description = "Returns not found",
|
||||||
get() = domainService!!.getAllDomains().map { it?.toDTO() }
|
content = [Content(schema = Schema(implementation = ApiResponseDTO::class))])
|
||||||
|
])
|
||||||
|
fun allDomains(): ApiResponseDTO<List<DomainResponseDTO?>> {
|
||||||
|
return ApiResponseDTO(result = domainService.getAllDomains().map { it.toDTO() })
|
||||||
|
}
|
||||||
|
|
||||||
@Operation(summary = "Get domain", tags = ["domain"])
|
@Operation(summary = "Get domain", tags = ["domain"])
|
||||||
@ApiResponses(value = [
|
@ApiResponses(value = [
|
||||||
ApiResponse(responseCode = "200", description = "Returns domain",
|
ApiResponse(responseCode = "200", description = "Returns domain", useReturnTypeSchema = true),
|
||||||
content = [Content(schema = Schema(implementation = DomainResponseDTO::class))]),
|
|
||||||
ApiResponse(responseCode = "404", description = "Returns not found",
|
ApiResponse(responseCode = "404", description = "Returns not found",
|
||||||
content = [Content(schema = Schema(implementation = Void::class))])
|
content = [Content(schema = Schema(implementation = ApiResponseDTO::class))])
|
||||||
])
|
])
|
||||||
@GetMapping("/{cfid}")
|
@GetMapping("/{cfid}")
|
||||||
fun getDomainByCfid(
|
fun getDomainByCfid(
|
||||||
@Parameter(description = "CFID", required = true)
|
|
||||||
@PathVariable cfid: String?
|
@PathVariable cfid: String?
|
||||||
): Optional<DomainResponseDTO> {
|
): ApiResponseDTO<DomainResponseDTO> {
|
||||||
return domainService!!.getDomainById(cfid!!).map { it.toDTO() }
|
return ApiResponseDTO(result = domainService.getDomainById(cfid!!).toDTO())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "Create domain", tags = ["domain"])
|
@Operation(summary = "Create domain", tags = ["domain"])
|
||||||
@ApiResponses(value = [
|
@ApiResponses(value = [
|
||||||
ApiResponse(responseCode = "200", description = "Created domain",
|
ApiResponse(responseCode = "200", description = "Created domain", useReturnTypeSchema = true),
|
||||||
content = [Content(schema = Schema(implementation = DomainResponseDTO::class))]),
|
|
||||||
ApiResponse(responseCode = "400", description = "Bad request",
|
ApiResponse(responseCode = "400", description = "Bad request",
|
||||||
content = [Content(schema = Schema(implementation = Void::class))])
|
content = [Content(schema = Schema(implementation = ApiResponseDTO::class))])
|
||||||
])
|
])
|
||||||
@PostMapping
|
@PostMapping
|
||||||
fun createDomain(@RequestBody domain: Domain?): DomainResponseDTO {
|
fun createDomain(@RequestBody domain: DomainRequestDTO): ApiResponseDTO<DomainResponseDTO> {
|
||||||
return domainService!!.createDomain(domain!!).toDTO()
|
return ApiResponseDTO(result = domainService.createDomain(domain).toDTO())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "Delete domain", tags = ["domain"])
|
@Operation(summary = "Delete domain", tags = ["domain"])
|
||||||
@ApiResponses(value = [
|
@ApiResponses(value = [
|
||||||
ApiResponse(responseCode = "200", description = "Deleted domain",
|
ApiResponse(responseCode = "200", description = "Deleted domain", useReturnTypeSchema = true),
|
||||||
content = [Content(schema = Schema(implementation = Void::class))]),
|
|
||||||
ApiResponse(responseCode = "400", description = "Bad request",
|
ApiResponse(responseCode = "400", description = "Bad request",
|
||||||
content = [Content(schema = Schema(implementation = Void::class))])
|
content = [Content(schema = Schema(implementation = ApiResponseDTO::class))])
|
||||||
])
|
])
|
||||||
@DeleteMapping("/{cfid}")
|
@DeleteMapping("/{domain_id}")
|
||||||
fun deleteDomain(@PathVariable cfid: String?) {
|
fun deleteDomain(@PathVariable domain_id: String?): ApiResponseDTO<DeleteResponseWithId> {
|
||||||
domainService!!.deleteDomain(cfid!!)
|
domainService.deleteDomain(domain_id!!)
|
||||||
|
|
||||||
|
return ApiResponseDTO(result=DeleteResponseWithId(domain_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Domain.toDTO() = DomainResponseDTO(id = cfid, name = name)
|
private fun Domain.toDTO() = DomainResponseDTO(id = cfid, name = name)
|
||||||
|
@ -1,21 +1,16 @@
|
|||||||
package space.mori.dnsapi.controller
|
package space.mori.dnsapi.controller
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation
|
import io.swagger.v3.oas.annotations.Operation
|
||||||
import io.swagger.v3.oas.annotations.media.ArraySchema
|
|
||||||
import io.swagger.v3.oas.annotations.media.Content
|
import io.swagger.v3.oas.annotations.media.Content
|
||||||
import io.swagger.v3.oas.annotations.media.Schema
|
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.db.Domain
|
import space.mori.dnsapi.dto.*
|
||||||
import space.mori.dnsapi.db.Record as DomainRecord
|
import space.mori.dnsapi.db.Record as DomainRecord
|
||||||
import space.mori.dnsapi.dto.RecordRequestDTO
|
|
||||||
import space.mori.dnsapi.dto.RecordResponseDTO
|
|
||||||
import space.mori.dnsapi.getISOFormat
|
import space.mori.dnsapi.getISOFormat
|
||||||
import space.mori.dnsapi.service.RecordService
|
import space.mori.dnsapi.service.RecordService
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/zones")
|
@RequestMapping("/zones")
|
||||||
@ -26,61 +21,57 @@ class RecordController(
|
|||||||
@GetMapping("{zone_id}/dns_records")
|
@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", useReturnTypeSchema = true),
|
||||||
content = [Content(array = ArraySchema(schema = Schema(implementation = RecordResponseDTO::class)))]),
|
|
||||||
ApiResponse(responseCode = "400", description = "Bad request",
|
ApiResponse(responseCode = "400", description = "Bad request",
|
||||||
content = [Content(schema = Schema(implementation = Void::class))]),
|
content = [Content(schema = Schema(implementation = ApiResponseDTO::class))]),
|
||||||
])
|
])
|
||||||
fun allRecords(@PathVariable zone_id: String): List<RecordResponseDTO> {
|
fun allRecords(@PathVariable zone_id: String): ApiResponseDTO<List<RecordResponseDTO>> {
|
||||||
return recordService.getRecordsByDomain(zone_id)?.map{ it } ?: listOf()
|
return ApiResponseDTO(result = recordService.getRecordsByDomain(zone_id)?.map{ it } ?: listOf())
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("{zone_id}/dns_records/{dns_record_id}")
|
@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", useReturnTypeSchema = true),
|
||||||
content = [Content(schema = Schema(implementation = RecordResponseDTO::class))]),
|
|
||||||
ApiResponse(responseCode = "400", description = "Bad request",
|
ApiResponse(responseCode = "400", description = "Bad request",
|
||||||
content = [Content(schema = Schema(implementation = Void::class))])
|
content = [Content(schema = Schema(implementation = ApiResponseDTO::class))]),
|
||||||
])
|
])
|
||||||
fun getRecordByCfid(@PathVariable zone_id: String, @PathVariable dns_record_id: String): RecordResponseDTO {
|
fun getRecordByCfid(@PathVariable zone_id: String, @PathVariable dns_record_id: String): ApiResponseDTO<RecordResponseDTO> {
|
||||||
return recordService.getRecord(zone_id, dns_record_id)
|
return ApiResponseDTO(result = recordService.getRecord(zone_id, dns_record_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("{zone_id}/dns_records")
|
@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", useReturnTypeSchema = true),
|
||||||
content = [Content(schema = Schema(implementation = RecordResponseDTO::class))]),
|
|
||||||
ApiResponse(responseCode = "400", description = "Bad request",
|
ApiResponse(responseCode = "400", description = "Bad request",
|
||||||
content = [Content(schema = Schema(implementation = Void::class))]),
|
content = [Content(schema = Schema(implementation = ApiResponseDTO::class))]),
|
||||||
])
|
])
|
||||||
fun createRecord(@PathVariable zone_id: String, @RequestBody record: RecordRequestDTO): RecordResponseDTO {
|
fun createRecord(@PathVariable zone_id: String, @RequestBody record: RecordRequestDTO): ApiResponseDTO<RecordResponseDTO> {
|
||||||
return recordService.createRecord(zone_id, record)
|
return ApiResponseDTO(result = recordService.createRecord(zone_id, record))
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("{zone_id}/dns_records/{dns_record_id}")
|
@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", useReturnTypeSchema = true),
|
||||||
content = [Content(schema = Schema(implementation = Void::class))]),
|
|
||||||
ApiResponse(responseCode = "400", description = "Bad request",
|
ApiResponse(responseCode = "400", description = "Bad request",
|
||||||
content = [Content(schema = Schema(implementation = Void::class))]),
|
content = [Content(schema = Schema(implementation = ApiResponseDTO::class))]),
|
||||||
])
|
])
|
||||||
fun deleteRecord(@PathVariable zone_id: String, @PathVariable dns_record_id: String) {
|
fun deleteRecord(@PathVariable zone_id: String, @PathVariable dns_record_id: String): ApiResponseDTO<DeleteResponseWithId> {
|
||||||
recordService.deleteRecord(zone_id, dns_record_id)
|
val record_id = recordService.deleteRecord(zone_id, dns_record_id)
|
||||||
|
return ApiResponseDTO(result = DeleteResponseWithId(record_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
@PatchMapping("{zone_id}/dns_records/{dns_record_id}")
|
@PatchMapping("{zone_id}/dns_records/{dns_record_id}")
|
||||||
@Operation(summary = "Update Record by ID", tags=["record"])
|
@Operation(summary = "Update Record by ID", tags=["record"])
|
||||||
@ApiResponses(value = [
|
@ApiResponses(value = [
|
||||||
ApiResponse(responseCode = "200", description = "Return Record",
|
ApiResponse(responseCode = "200", description = "Return Record", useReturnTypeSchema = true),
|
||||||
content = [Content(schema = Schema(implementation = RecordResponseDTO::class))]),
|
|
||||||
ApiResponse(responseCode = "400", description = "Bad request",
|
ApiResponse(responseCode = "400", description = "Bad request",
|
||||||
content = [Content(schema = Schema(implementation = Void::class))]),
|
content = [Content(schema = Schema(implementation = ApiResponseDTO::class))]),
|
||||||
])
|
])
|
||||||
fun updateRecord(@PathVariable zone_id: String, @PathVariable dns_record_id: String, @RequestBody record: RecordRequestDTO): RecordResponseDTO {
|
fun updateRecord(@PathVariable zone_id: String, @PathVariable dns_record_id: String, @RequestBody record: RecordRequestDTO): ApiResponseDTO<RecordResponseDTO> {
|
||||||
return recordService.updateRecord(zone_id, dns_record_id, record)
|
return ApiResponseDTO(result = recordService.updateRecord(zone_id, dns_record_id, record))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun DomainRecord.toDTO() = RecordResponseDTO(
|
private fun DomainRecord.toDTO() = RecordResponseDTO(
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package space.mori.dnsapi.db
|
package space.mori.dnsapi.db
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema
|
|
||||||
import jakarta.persistence.*
|
import jakarta.persistence.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@ -16,8 +15,14 @@ data class Domain(
|
|||||||
var name: String,
|
var name: String,
|
||||||
|
|
||||||
@Column(nullable = false, unique = true)
|
@Column(nullable = false, unique = true)
|
||||||
var cfid: String = UUID.randomUUID().toString().replace("-", "")
|
var cfid: String = UUID.randomUUID().toString().replace("-", ""),
|
||||||
) {
|
|
||||||
@OneToMany(mappedBy = "domain", cascade = [CascadeType.ALL], orphanRemoval = true)
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "user_id", nullable = false)
|
||||||
|
var user: User,
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "domain", cascade = [CascadeType.ALL], orphanRemoval = true, fetch = FetchType.LAZY)
|
||||||
var records: List<Record> = mutableListOf()
|
var records: List<Record> = mutableListOf()
|
||||||
|
) {
|
||||||
|
override fun toString(): String = "Domain(id=$cfid, name='$name')"
|
||||||
}
|
}
|
@ -10,5 +10,8 @@ interface DomainRepository : JpaRepository<Domain?, Long?> {
|
|||||||
fun findByCfid(cfid: String): Optional<Domain>
|
fun findByCfid(cfid: String): Optional<Domain>
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun deleteByCfid(cfid: String)
|
fun findAllByUser(user: User): List<Domain>
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
fun deleteByCfid(cfid: String): Int
|
||||||
}
|
}
|
@ -2,8 +2,6 @@ package space.mori.dnsapi.db
|
|||||||
|
|
||||||
import jakarta.transaction.Transactional
|
import jakarta.transaction.Transactional
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
import org.springframework.data.jpa.repository.Query
|
|
||||||
import org.springframework.data.repository.query.Param
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
22
src/main/kotlin/space/mori/dnsapi/db/User.kt
Normal file
22
src/main/kotlin/space/mori/dnsapi/db/User.kt
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package space.mori.dnsapi.db
|
||||||
|
|
||||||
|
import jakarta.persistence.*
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
data class User(
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy= GenerationType.IDENTITY)
|
||||||
|
val id: Long? = null,
|
||||||
|
|
||||||
|
@Column(nullable = false, unique = true, name = "api_key", length = 64)
|
||||||
|
val apiKey: String,
|
||||||
|
|
||||||
|
@Column(nullable = false, unique = false, length = 20)
|
||||||
|
val name: String,
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "user", cascade = [CascadeType.ALL], orphanRemoval = true, fetch = FetchType.EAGER)
|
||||||
|
val domains: List<Domain> = mutableListOf(),
|
||||||
|
) {
|
||||||
|
override fun toString(): String = "User(id=$id, name='$name')"
|
||||||
|
}
|
7
src/main/kotlin/space/mori/dnsapi/db/UserRepository.kt
Normal file
7
src/main/kotlin/space/mori/dnsapi/db/UserRepository.kt
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package space.mori.dnsapi.db
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
|
|
||||||
|
interface UserRepository: JpaRepository<User, Long> {
|
||||||
|
fun findByApiKey(apiKey: String): User?
|
||||||
|
}
|
17
src/main/kotlin/space/mori/dnsapi/dto/ApiResponseDTO.kt
Normal file
17
src/main/kotlin/space/mori/dnsapi/dto/ApiResponseDTO.kt
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package space.mori.dnsapi.dto
|
||||||
|
|
||||||
|
data class ApiResponseDTO<T>(
|
||||||
|
val success: Boolean = true,
|
||||||
|
val errors: List<ErrorOrMessage> = listOf(),
|
||||||
|
val messages: List<ErrorOrMessage> = listOf(),
|
||||||
|
val result: T? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
data class ErrorOrMessage(
|
||||||
|
val code: Int,
|
||||||
|
val message: String
|
||||||
|
)
|
||||||
|
|
||||||
|
data class DeleteResponseWithId(
|
||||||
|
val id: String
|
||||||
|
)
|
@ -7,7 +7,7 @@ data class RecordRequestDTO(
|
|||||||
@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.")
|
@Schema(description = "Host name", example = "www")
|
||||||
val name: String,
|
val name: String,
|
||||||
|
|
||||||
@Schema(description = "Record data", example = "192.0.2.1")
|
@Schema(description = "Record data", example = "192.0.2.1")
|
||||||
|
@ -11,7 +11,7 @@ data class RecordResponseDTO(
|
|||||||
@Schema(description = "Record type", example = "A")
|
@Schema(description = "Record type", example = "A")
|
||||||
var type: String,
|
var type: String,
|
||||||
|
|
||||||
@Schema(description = "Record name", example = "test.example.com")
|
@Schema(description = "Record name", example = "test")
|
||||||
var name: String,
|
var name: String,
|
||||||
|
|
||||||
@Schema(description = "Record content", example = "1.1.1.1")
|
@Schema(description = "Record content", example = "1.1.1.1")
|
||||||
|
38
src/main/kotlin/space/mori/dnsapi/filter/UserFilter.kt
Normal file
38
src/main/kotlin/space/mori/dnsapi/filter/UserFilter.kt
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package space.mori.dnsapi.filter
|
||||||
|
|
||||||
|
import jakarta.servlet.FilterChain
|
||||||
|
import jakarta.servlet.http.HttpServletRequest
|
||||||
|
import jakarta.servlet.http.HttpServletResponse
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.http.HttpHeaders
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder
|
||||||
|
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter
|
||||||
|
import space.mori.dnsapi.db.User
|
||||||
|
import space.mori.dnsapi.db.UserRepository
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class UserFilter(
|
||||||
|
@Autowired
|
||||||
|
private val userRepository: UserRepository
|
||||||
|
): OncePerRequestFilter() {
|
||||||
|
override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain) {
|
||||||
|
val apiKey = request.getHeader(HttpHeaders.AUTHORIZATION)
|
||||||
|
if(apiKey != null) {
|
||||||
|
val user = userRepository.findByApiKey(apiKey.replace("Bearer ", ""))
|
||||||
|
if(user != null) {
|
||||||
|
val authentication = UsernamePasswordAuthenticationToken(
|
||||||
|
user, null, emptyList()
|
||||||
|
)
|
||||||
|
authentication.details = WebAuthenticationDetailsSource().buildDetails(request)
|
||||||
|
SecurityContextHolder.getContext().authentication = authentication
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chain.doFilter(request, response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCurrentUser(): User =
|
||||||
|
SecurityContextHolder.getContext().authentication.principal as User
|
@ -2,29 +2,56 @@ 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 space.mori.dnsapi.PowerDNSApiClient
|
||||||
import space.mori.dnsapi.db.Domain
|
import space.mori.dnsapi.db.Domain
|
||||||
import space.mori.dnsapi.db.DomainRepository
|
import space.mori.dnsapi.db.DomainRepository
|
||||||
|
import space.mori.dnsapi.db.UserRepository
|
||||||
|
import space.mori.dnsapi.dto.DomainRequestDTO
|
||||||
|
import space.mori.dnsapi.filter.getCurrentUser
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class DomainService {
|
class DomainService(
|
||||||
@Autowired
|
@Autowired
|
||||||
private val domainRepository: DomainRepository? = null
|
private val userRepository: UserRepository,
|
||||||
|
@Autowired
|
||||||
|
private val domainRepository: DomainRepository,
|
||||||
|
@Autowired
|
||||||
|
private val powerDNSApiClient: PowerDNSApiClient
|
||||||
|
) {
|
||||||
|
fun getAllDomains(): List<Domain> {
|
||||||
|
val user = getCurrentUser()
|
||||||
|
val domain = domainRepository.findAllByUser(user)
|
||||||
|
if(domain.isEmpty()) throw RuntimeException("Unauthorized")
|
||||||
|
|
||||||
fun getAllDomains(): List<Domain?> {
|
return domain
|
||||||
return domainRepository!!.findAll()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDomainById(cfid: String): Optional<Domain> {
|
fun getDomainById(domain_id: String): Domain {
|
||||||
return domainRepository!!.findByCfid(cfid)
|
val domain = domainRepository.findByCfid(domain_id).orElseThrow {
|
||||||
|
RuntimeException("Failed to find domain in API: $domain_id")
|
||||||
|
}
|
||||||
|
val user = getCurrentUser()
|
||||||
|
if(domain.user.id != user.id)
|
||||||
|
throw RuntimeException("Unauthorized to create record in API: $domain_id")
|
||||||
|
|
||||||
|
return domain
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createDomain(domain: Domain): Domain {
|
fun createDomain(domain: DomainRequestDTO): Domain {
|
||||||
return domainRepository!!.save<Domain>(domain)
|
val user = getCurrentUser()
|
||||||
|
|
||||||
|
powerDNSApiClient.createDomain(domain.name)
|
||||||
|
val saved_domain = domainRepository.save(Domain(name=domain.name, user=user))
|
||||||
|
|
||||||
|
return saved_domain
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteDomain(cfid: String) {
|
fun deleteDomain(domain_id: String): String {
|
||||||
domainRepository!!.deleteByCfid(cfid)
|
val count = domainRepository.deleteByCfid(domain_id)
|
||||||
|
|
||||||
|
if(count > 0) throw RuntimeException("Domain with CFID $domain_id not found")
|
||||||
|
return domain_id
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,9 +7,11 @@ import space.mori.dnsapi.PowerDNSApiClient
|
|||||||
import space.mori.dnsapi.db.DomainRepository
|
import space.mori.dnsapi.db.DomainRepository
|
||||||
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.db.RecordRepository
|
||||||
|
import space.mori.dnsapi.db.UserRepository
|
||||||
import space.mori.dnsapi.dto.DomainRequestDTO
|
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.dto.RecordResponseDTO
|
||||||
|
import space.mori.dnsapi.filter.getCurrentUser
|
||||||
import space.mori.dnsapi.getISOFormat
|
import space.mori.dnsapi.getISOFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@ -21,18 +23,25 @@ class RecordService(
|
|||||||
@Autowired
|
@Autowired
|
||||||
private val domainRepository: DomainRepository,
|
private val domainRepository: DomainRepository,
|
||||||
@Autowired
|
@Autowired
|
||||||
private val recordRepository: RecordRepository
|
private val recordRepository: RecordRepository,
|
||||||
|
@Autowired
|
||||||
|
private val userRepository: UserRepository,
|
||||||
) {
|
) {
|
||||||
fun createRecord(domain_id: String, recordRequest: RecordRequestDTO): RecordResponseDTO {
|
fun createRecord(domain_id: String, recordRequest: RecordRequestDTO): RecordResponseDTO {
|
||||||
val domain = domainRepository.findByCfid(domain_id)
|
val domain = domainRepository.findByCfid(domain_id).orElseThrow {
|
||||||
if(domain.isEmpty) throw RuntimeException("Failed to find domain in API: $domain_id")
|
throw RuntimeException("Failed to find domain in API: $domain_id")
|
||||||
|
}
|
||||||
|
|
||||||
val response = powerDNSApiClient.createRecord(domain.get().name, recordRequest)
|
val user = getCurrentUser()
|
||||||
|
if(domain.user.id != user.id)
|
||||||
|
throw RuntimeException("Unauthorized to create record in API: $domain_id")
|
||||||
|
|
||||||
|
val response = powerDNSApiClient.createRecord(domain.name, recordRequest)
|
||||||
if (!response.statusCode.is2xxSuccessful) {
|
if (!response.statusCode.is2xxSuccessful) {
|
||||||
throw RuntimeException("Failed to create record in PowerDNS: ${response.body}")
|
throw RuntimeException("Failed to create record in PowerDNS: ${response.body}")
|
||||||
}
|
}
|
||||||
val record = DomainRecord(
|
val record = DomainRecord(
|
||||||
domain = domain.get(),
|
domain = domain,
|
||||||
name = recordRequest.name,
|
name = recordRequest.name,
|
||||||
type = recordRequest.type,
|
type = recordRequest.type,
|
||||||
content = recordRequest.content,
|
content = recordRequest.content,
|
||||||
@ -55,7 +64,7 @@ class RecordService(
|
|||||||
ttl = record.ttl,
|
ttl = record.ttl,
|
||||||
locked = false,
|
locked = false,
|
||||||
zoneId = record.cfid,
|
zoneId = record.cfid,
|
||||||
zoneName = domain.get().name,
|
zoneName = domain.name,
|
||||||
createdOn = record.createdOn.getISOFormat(),
|
createdOn = record.createdOn.getISOFormat(),
|
||||||
modifiedOn = record.modifiedOn.getISOFormat(),
|
modifiedOn = record.modifiedOn.getISOFormat(),
|
||||||
priority = record.prio,
|
priority = record.prio,
|
||||||
@ -64,10 +73,15 @@ class RecordService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getRecord(domain_id: String, record_id: String): RecordResponseDTO {
|
fun getRecord(domain_id: String, record_id: String): RecordResponseDTO {
|
||||||
val domain = domainRepository.findByCfid(domain_id)
|
val domain = domainRepository.findByCfid(domain_id).orElseThrow {
|
||||||
if(domain.isEmpty) throw RuntimeException("Failed to find domain in API: $domain_id")
|
RuntimeException("Failed to find domain in API: $domain_id")
|
||||||
|
}
|
||||||
|
|
||||||
val record = domain.get().records.find { it.cfid == record_id }
|
val user = getCurrentUser()
|
||||||
|
if(domain.user.id != user.id)
|
||||||
|
throw RuntimeException("Unauthorized to get record in API: $domain_id")
|
||||||
|
|
||||||
|
val record = domain.records.find { it.cfid == record_id }
|
||||||
if(record == null) throw RuntimeException("Failed to find record in API: $record_id")
|
if(record == null) throw RuntimeException("Failed to find record in API: $record_id")
|
||||||
|
|
||||||
return RecordResponseDTO(
|
return RecordResponseDTO(
|
||||||
@ -85,7 +99,14 @@ class RecordService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getRecordsByDomain(domain_id: String): List<RecordResponseDTO>? {
|
fun getRecordsByDomain(domain_id: String): List<RecordResponseDTO>? {
|
||||||
val domain = domainRepository.findByCfid(domain_id).orElseThrow { RuntimeException("Failed to find domain in API: $domain_id") }
|
val domain = domainRepository.findByCfid(domain_id).orElseThrow {
|
||||||
|
RuntimeException("Failed to find domain in API: $domain_id")
|
||||||
|
}
|
||||||
|
|
||||||
|
val user = getCurrentUser()
|
||||||
|
if(domain.user.id != user.id)
|
||||||
|
throw RuntimeException("Unauthorized to create record in API: $domain_id")
|
||||||
|
|
||||||
return domain?.records?.map { RecordResponseDTO(
|
return domain?.records?.map { RecordResponseDTO(
|
||||||
id = it.cfid,
|
id = it.cfid,
|
||||||
type = it.type,
|
type = it.type,
|
||||||
@ -102,10 +123,15 @@ class RecordService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun updateRecord(domainId: String, cfid: String, updatedRecord: RecordRequestDTO): RecordResponseDTO {
|
fun updateRecord(domain_id: String, cfid: String, updatedRecord: RecordRequestDTO): RecordResponseDTO {
|
||||||
// 도메인 조회
|
// 도메인 조회
|
||||||
val domain = domainRepository.findByCfid(domainId)
|
val domain = domainRepository.findByCfid(domain_id).orElseThrow {
|
||||||
.orElseThrow { RuntimeException("Domain not found") }
|
RuntimeException("Failed to find domain in API: $domain_id")
|
||||||
|
}
|
||||||
|
|
||||||
|
val user = getCurrentUser()
|
||||||
|
if(domain.user.id != user.id)
|
||||||
|
throw RuntimeException("Unauthorized to create record in API: $domain_id")
|
||||||
|
|
||||||
// 레코드 조회
|
// 레코드 조회
|
||||||
val record = recordRepository.findByDomainIdAndCfid(domain.id!!, cfid)
|
val record = recordRepository.findByDomainIdAndCfid(domain.id!!, cfid)
|
||||||
@ -144,18 +170,18 @@ class RecordService(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteRecord(domain_id: String, record_id: String) {
|
fun deleteRecord(domain_id: String, record_id: String): String {
|
||||||
val domain = domainRepository.findByCfid(domain_id).orElseThrow { RuntimeException("Failed to find domain in API: $domain_id") }
|
val domain = domainRepository.findByCfid(domain_id).orElseThrow {
|
||||||
|
RuntimeException("Failed to find domain in API: $domain_id")
|
||||||
|
}
|
||||||
|
|
||||||
|
val user = getCurrentUser()
|
||||||
|
if(domain.user.id != user.id)
|
||||||
|
throw RuntimeException("Unauthorized to create record in API: $domain_id")
|
||||||
|
|
||||||
val deletedCount = recordRepository.deleteByDomainIdAndCfid(domain.id!!, record_id)
|
val deletedCount = recordRepository.deleteByDomainIdAndCfid(domain.id!!, record_id)
|
||||||
|
|
||||||
if(deletedCount == 0) throw RuntimeException("Failed to find record in API: $record_id")
|
if(deletedCount == 0) throw RuntimeException("Failed to find record in API: $record_id")
|
||||||
}
|
else return 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}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user