add domain service, auth service

This commit is contained in:
dalbodeule 2024-06-05 23:15:29 +09:00
parent 3dd8fc69c1
commit d99606436d
No known key found for this signature in database
GPG Key ID: EFA860D069C9FA65
18 changed files with 299 additions and 112 deletions

View File

@ -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")

View File

@ -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=

View File

@ -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

View 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()
}
}

View File

@ -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") )
)
} }
} }

View File

@ -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)

View File

@ -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(

View File

@ -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')"
} }

View File

@ -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
} }

View File

@ -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.*

View 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')"
}

View 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?
}

View 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
)

View File

@ -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")

View File

@ -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")

View 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

View File

@ -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
} }
} }

View File

@ -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}")
}
} }
} }