diff --git a/Sources/CrudRouter/CrudController.swift b/Sources/CrudRouter/CrudController.swift index 30a74cc..e059e86 100644 --- a/Sources/CrudRouter/CrudController.swift +++ b/Sources/CrudRouter/CrudController.swift @@ -69,7 +69,11 @@ public struct CrudController: CrudControllerProtocol wh self.path = path self.router = router } +} + +// MARK: ParentsController methods +extension CrudController { /// Returns a parent controller, which retrieves models that are parents of ModelType /// /// - Parameter relation: Keypath from origin model to a Parent relation, which goes from origin model to @@ -86,18 +90,6 @@ public struct CrudController: CrudControllerProtocol wh return CrudParentController(relation: relation, basePath: baseIdPath, path: path) } - public func crudRouterCollection( - at path: PathComponentsRepresentable..., - forChildren relation: KeyPath> - ) -> CrudChildrenController where - ChildType: Model & Content, - ModelType.Database == ChildType.Database, - ChildType.ID: Parameter { - let baseIdPath = self.path.appending(ModelType.ID.parameter) - - return CrudChildrenController(childrenRelation: relation, basePath: baseIdPath, path: path) - } - public func crudRegister( at path: PathComponentsRepresentable..., forParent relation: KeyPath>, @@ -112,6 +104,22 @@ public struct CrudController: CrudControllerProtocol wh try controller.boot(router: self.router) } +} + + +// MARK: ChildController methods +extension CrudController { + public func crudRouterCollection( + at path: PathComponentsRepresentable..., + forChildren relation: KeyPath> + ) -> CrudChildrenController where + ChildType: Model & Content, + ModelType.Database == ChildType.Database, + ChildType.ID: Parameter { + let baseIdPath = self.path.appending(ModelType.ID.parameter) + + return CrudChildrenController(childrenRelation: relation, basePath: baseIdPath, path: path) + } public func crudRegister( at path: PathComponentsRepresentable..., @@ -127,21 +135,64 @@ public struct CrudController: CrudControllerProtocol wh try controller.boot(router: self.router) } +} + +// MARK: SiblingController methods +public extension CrudController { + public func crudRouterCollection( + forSiblings relation: KeyPath>, + at path: [PathComponentsRepresentable] + ) -> CrudSiblingsController where + ChildType: Content, + ModelType.Database == ThroughType.Database, + ChildType.ID: Parameter, + ThroughType: ModifiablePivot, + ThroughType.Database: JoinSupporting, + ThroughType.Database == ChildType.Database { + let baseIdPath = self.path.appending(ModelType.ID.parameter) + + return CrudSiblingsController(siblingRelation: relation, basePath: baseIdPath, path: path) + } + + public func crudRegister( + at path: PathComponentsRepresentable..., + forSiblings relation: KeyPath>, + relationConfiguration: ((CrudSiblingsController) throws -> Void)?=nil + ) throws where + ChildType: Content, + ModelType.Database == ThroughType.Database, + ChildType.ID: Parameter, + ThroughType: ModifiablePivot, + ThroughType.Database: JoinSupporting, + ThroughType.Database == ChildType.Database, + ThroughType.Left == ModelType, + ThroughType.Right == ChildType { + let baseIdPath = self.path.appending(ModelType.ID.parameter) + + let controller = CrudSiblingsController(siblingRelation: relation, basePath: baseIdPath, path: path) + + try controller.boot(router: self.router) + } + + public func crudRegister( + at path: PathComponentsRepresentable..., + forSiblings relation: KeyPath>, + relationConfiguration: ((CrudSiblingsController) throws -> Void)?=nil + ) throws where + ChildType: Content, + ModelType.Database == ThroughType.Database, + ChildType.ID: Parameter, + ThroughType: ModifiablePivot, + ThroughType.Database: JoinSupporting, + ThroughType.Database == ChildType.Database, + ThroughType.Right == ModelType, + ThroughType.Left == ChildType { + let baseIdPath = self.path.appending(ModelType.ID.parameter) - // public func crudRouterCollection( - // forSiblings relation: KeyPath>, - // at path: [PathComponentsRepresentable] - // ) -> CrudSiblingsController where - // ChildType: Content, - // ModelType.Database == ThroughType.Database, - // ChildType.ID: Parameter, - // ThroughType: ModifiablePivot, - // ThroughType.Database: JoinSupporting, - // ThroughType.Database == ChildType.Database { - // let baseIdPath = self.path.appending(ModelType.ID.parameter) - // - // return CrudSiblingsController(siblingRelation: relation, basePath: baseIdPath, path: path) - // } + let controller = CrudSiblingsController(siblingRelation: relation, basePath: baseIdPath, path: path) + + try controller.boot(router: self.router) + } } extension CrudController: RouteCollection { diff --git a/Sources/CrudRouter/CrudSiblingsController.swift b/Sources/CrudRouter/CrudSiblingsController.swift index 626c222..8d8466b 100644 --- a/Sources/CrudRouter/CrudSiblingsController.swift +++ b/Sources/CrudRouter/CrudSiblingsController.swift @@ -1,227 +1,218 @@ -//import Vapor -//import Fluent -// -//public protocol CrudSiblingsControllerProtocol { -// associatedtype ParentType: Model & Content where ParentType.ID: Parameter -// associatedtype ChildType: Model & Content where ChildType.ID: Parameter, ChildType.Database == ParentType.Database -// associatedtype ThroughType: ModifiablePivot where -// ThroughType.Database: JoinSupporting, -// ChildType.Database == ThroughType.Database -// -// var siblings: KeyPath> { get } -// -// init(siblingRelation: KeyPath>, basePath: [PathComponentsRepresentable], path: [PathComponentsRepresentable]) -// -// func index(_ req: Request) throws -> Future -// func indexAll(_ req: Request) throws -> Future<[ChildType]> -//// func create(_ req: Request) throws -> Future -// func update(_ req: Request) throws -> Future -//// func delete(_ req: Request) throws -> Future -//} -// -//public extension CrudSiblingsControllerProtocol { -// func index(_ req: Request) throws -> Future { -// let parentId: ParentType.ID = try getId(from: req) -// let childId: ChildType.ID = try getId(from: req) -// -// return ParentType.find(parentId, on: req).unwrap(or: Abort(.notFound)).flatMap { parent -> Future in -// -// return try parent[keyPath: self.siblings] -// .query(on: req) -// .filter(\ChildType.fluentID == childId) -// .first() -// .unwrap(or: Abort(.notFound)) -// } -// } -// -// func indexAll(_ req: Request) throws -> Future<[ChildType]> { -// let parentId: ParentType.ID = try getId(from: req) -// -// return ParentType.find(parentId, on: req).unwrap(or: Abort(.notFound)).flatMap { parent -> Future<[ChildType]> in -// let siblingsRelation = parent[keyPath: self.siblings] -// return try siblingsRelation -// .query(on: req) -// .all() -// } -// } -// -// func update(_ req: Request) throws -> Future { -// let parentId: ParentType.ID = try getId(from: req) -// let childId: ChildType.ID = try getId(from: req) -// -// return ParentType -// .find(parentId, on: req) -// .unwrap(or: Abort(.notFound)) -// .flatMap { parent -> Future in -// return try parent[keyPath: self.siblings] -// .query(on: req) -// .filter(\ChildType.fluentID == childId) -// .first() -// .unwrap(or: Abort(.notFound)) -// }.flatMap { oldChild in -// return try req.content.decode(ChildType.self).flatMap { newChild in -// var temp = newChild -// temp.fluentID = oldChild.fluentID -// return temp.update(on: req) -// } -// } -// } -//} -// -//public extension CrudSiblingsControllerProtocol where ThroughType.Left == ParentType, -//ThroughType.Right == ChildType { -// func create(_ req: Request) throws -> Future { -// let parentId: ParentType.ID = try getId(from: req) -// -// return ParentType.find(parentId, on: req).unwrap(or: Abort(.notFound)).flatMap { parent -> Future in -// -// return try req.content.decode(ChildType.self).flatMap { child in -// let relation = parent[keyPath: self.siblings] -// return relation.attach(child, on: req).transform(to: child) -// } -// } -// } -// -// func delete(_ req: Request) throws -> Future { -// let parentId: ParentType.ID = try getId(from: req) -// let childId: ChildType.ID = try getId(from: req) -// -// return ParentType -// .find(parentId, on: req) -// .unwrap(or: Abort(.notFound)) -// .flatMap { parent -> Future in -// let siblingsRelation = parent[keyPath: self.siblings] -// return try siblingsRelation -// .query(on: req) -// .filter(\ChildType.fluentID == childId) -// .first() -// .unwrap(or: Abort(.notFound)) -// .delete(on: req) -// .transform(to: HTTPStatus.ok) -// } -// } -//} -// -//public extension CrudSiblingsControllerProtocol where ThroughType.Right == ParentType, -//ThroughType.Left == ChildType { -// func create(_ req: Request) throws -> Future { -// let parentId: ParentType.ID = try getId(from: req) -// -// return ParentType.find(parentId, on: req).unwrap(or: Abort(.notFound)).flatMap { parent -> Future in -// -// return try req.content.decode(ChildType.self).flatMap { child in -// let relation = parent[keyPath: self.siblings] -// return relation.attach(child, on: req).transform(to: child) -// } -// } -// } -// -// func delete(_ req: Request) throws -> Future { -// let parentId: ParentType.ID = try getId(from: req) -// let childId: ChildType.ID = try getId(from: req) -// -// return ParentType -// .find(parentId, on: req) -// .unwrap(or: Abort(.notFound)) -// .flatMap { parent -> Future in -// let siblingsRelation = parent[keyPath: self.siblings] -// return try siblingsRelation -// .query(on: req) -// .filter(\ChildType.fluentID == childId) -// .first() -// .unwrap(or: Abort(.notFound)) -// .delete(on: req) -// .transform(to: HTTPStatus.ok) -// } -// } -//} -// -//fileprivate extension CrudSiblingsControllerProtocol { -// func getId(from req: Request) throws -> T { -// guard let id = try req.parameters.next(T.self) as? T else { fatalError() } -// -// return id -// } -//} -// -//public struct CrudSiblingsController: CrudSiblingsControllerProtocol where -// ChildT.ID: Parameter, -// ParentT.ID: Parameter, -// ChildT.Database == ParentT.Database, -// ThroughT.Database: JoinSupporting, -// ThroughT.Database == ChildT.Database { -// -// public typealias ThroughType = ThroughT -// public typealias ParentType = ParentT -// public typealias ChildType = ChildT -// -// public var siblings: KeyPath> -// let basePath: [PathComponentsRepresentable] -// let path: [PathComponentsRepresentable] -// -// public init( -// siblingRelation: KeyPath>, -// basePath: [PathComponentsRepresentable], -// path: [PathComponentsRepresentable] -// ) { -// self.siblings = siblingRelation -// self.basePath = basePath -// self.path = path -// } -//} -// -//extension CrudSiblingsController: RouteCollection {} -// -//public extension CrudSiblingsController where ThroughType.Right == ParentType, -//ThroughType.Left == ChildType { -// public func boot(router: Router) throws { -// let parentString -// = self.path.count == 0 -// ? [String(describing: ParentType.self).snakeCased()! as PathComponentsRepresentable] -// : self.path -// -// let parentPath = self.basePath.appending(parentString) -// let parentIdPath = self.basePath.appending(parentString).appending(ParentType.ID.parameter) -// -// router.get(parentIdPath, use: self.index) -// router.get(parentPath, use: self.indexAll) -// router.post(parentPath, use: self.create) -// router.put(parentIdPath, use: self.update) -// router.delete(parentIdPath, use: self.delete) -// } -//} -// -//public extension CrudSiblingsController where ThroughType.Left == ParentType, -//ThroughType.Right == ChildType { -// public func boot(router: Router) throws { -// let parentString -// = self.path.count == 0 -// ? [String(describing: ParentType.self).snakeCased()! as PathComponentsRepresentable] -// : self.path -// -// let parentPath = self.basePath.appending(parentString) -// let parentIdPath = self.basePath.appending(parentString).appending(ParentType.ID.parameter) -// -// router.get(parentIdPath, use: self.index) -// router.get(parentPath, use: self.indexAll) -// router.post(parentPath, use: self.create) -// router.put(parentIdPath, use: self.update) -// router.delete(parentIdPath, use: self.delete) -// } -//} -// -//public extension CrudSiblingsController { -// public func boot(router: Router) throws { -// let parentString -// = self.path.count == 0 -// ? [String(describing: ParentType.self).snakeCased()! as PathComponentsRepresentable] -// : self.path -// -// let parentPath = self.basePath.appending(parentString) -// let parentIdPath = self.basePath.appending(parentString).appending(ParentType.ID.parameter) -// -// router.get(parentIdPath, use: self.index) -// router.get(parentPath, use: self.indexAll) -// router.put(parentIdPath, use: self.update) -// } -//} +import Vapor +import Fluent + +public protocol CrudSiblingsControllerProtocol { + associatedtype ParentType: Model & Content where ParentType.ID: Parameter + associatedtype ChildType: Model & Content where ChildType.ID: Parameter, ChildType.Database == ParentType.Database + associatedtype ThroughType: ModifiablePivot where + ThroughType.Database: JoinSupporting, + ChildType.Database == ThroughType.Database + + var siblings: KeyPath> { get } + + init(siblingRelation: KeyPath>, basePath: [PathComponentsRepresentable], path: [PathComponentsRepresentable]) + + func index(_ req: Request) throws -> Future + func indexAll(_ req: Request) throws -> Future<[ChildType]> + func update(_ req: Request) throws -> Future +} + +public extension CrudSiblingsControllerProtocol { + func index(_ req: Request) throws -> Future { + let parentId: ParentType.ID = try getId(from: req) + let childId: ChildType.ID = try getId(from: req) + + return ParentType.find(parentId, on: req).unwrap(or: Abort(.notFound)).flatMap { parent -> Future in + + return try parent[keyPath: self.siblings] + .query(on: req) + .filter(\ChildType.fluentID == childId) + .first() + .unwrap(or: Abort(.notFound)) + } + } + + func indexAll(_ req: Request) throws -> Future<[ChildType]> { + let parentId: ParentType.ID = try getId(from: req) + + return ParentType.find(parentId, on: req).unwrap(or: Abort(.notFound)).flatMap { parent -> Future<[ChildType]> in + let siblingsRelation = parent[keyPath: self.siblings] + return try siblingsRelation + .query(on: req) + .all() + } + } + + func update(_ req: Request) throws -> Future { + let parentId: ParentType.ID = try getId(from: req) + let childId: ChildType.ID = try getId(from: req) + + return ParentType + .find(parentId, on: req) + .unwrap(or: Abort(.notFound)) + .flatMap { parent -> Future in + return try parent[keyPath: self.siblings] + .query(on: req) + .filter(\ChildType.fluentID == childId) + .first() + .unwrap(or: Abort(.notFound)) + }.flatMap { oldChild in + return try req.content.decode(ChildType.self).flatMap { newChild in + var temp = newChild + temp.fluentID = oldChild.fluentID + return temp.update(on: req) + } + } + } +} + +public extension CrudSiblingsControllerProtocol where ThroughType.Left == ParentType, +ThroughType.Right == ChildType { + func create(_ req: Request) throws -> Future { + let parentId: ParentType.ID = try getId(from: req) + + return ParentType.find(parentId, on: req).unwrap(or: Abort(.notFound)).flatMap { parent -> Future in + + return try req.content.decode(ChildType.self).flatMap { child in + let relation = parent[keyPath: self.siblings] + return relation.attach(child, on: req).transform(to: child) + } + } + } + + func delete(_ req: Request) throws -> Future { + let parentId: ParentType.ID = try getId(from: req) + let childId: ChildType.ID = try getId(from: req) + + return ParentType + .find(parentId, on: req) + .unwrap(or: Abort(.notFound)) + .flatMap { parent -> Future in + let siblingsRelation = parent[keyPath: self.siblings] + return try siblingsRelation + .query(on: req) + .filter(\ChildType.fluentID == childId) + .first() + .unwrap(or: Abort(.notFound)) + .flatMap { siblingsRelation.detach($0, on: req).transform(to: $0) } + .delete(on: req) + .transform(to: HTTPStatus.ok) + } + } +} + +public extension CrudSiblingsControllerProtocol where ThroughType.Right == ParentType, +ThroughType.Left == ChildType { + func create(_ req: Request) throws -> Future { + let parentId: ParentType.ID = try getId(from: req) + + return ParentType.find(parentId, on: req).unwrap(or: Abort(.notFound)).flatMap { parent -> Future in + + return try req.content.decode(ChildType.self).flatMap { child in + return child.create(on: req) + }.flatMap { child in + let relation = parent[keyPath: self.siblings] + return relation.attach(child, on: req).transform(to: child) + } + } + } + + func delete(_ req: Request) throws -> Future { + let parentId: ParentType.ID = try getId(from: req) + let childId: ChildType.ID = try getId(from: req) + + return ParentType + .find(parentId, on: req) + .unwrap(or: Abort(.notFound)) + .flatMap { parent -> Future in + let siblingsRelation = parent[keyPath: self.siblings] + return try siblingsRelation + .query(on: req) + .filter(\ChildType.fluentID == childId) + .first() + .unwrap(or: Abort(.notFound)) + .delete(on: req) + .transform(to: HTTPStatus.ok) + } + } +} + +fileprivate extension CrudSiblingsControllerProtocol { + func getId(from req: Request) throws -> T { + guard let id = try req.parameters.next(T.self) as? T else { fatalError() } + + return id + } +} + +public struct CrudSiblingsController: CrudSiblingsControllerProtocol where + ChildT.ID: Parameter, + ParentT.ID: Parameter, + ChildT.Database == ParentT.Database, + ThroughT.Database: JoinSupporting, +ThroughT.Database == ChildT.Database { + + public typealias ThroughType = ThroughT + public typealias ParentType = ParentT + public typealias ChildType = ChildT + + public var siblings: KeyPath> + let basePath: [PathComponentsRepresentable] + let path: [PathComponentsRepresentable] + + public init( + siblingRelation: KeyPath>, + basePath: [PathComponentsRepresentable], + path: [PathComponentsRepresentable] + ) { + let path + = path.count == 0 + ? [String(describing: ChildType.self).snakeCased()! as PathComponentsRepresentable] + : path + + self.siblings = siblingRelation + self.basePath = basePath + self.path = path + } +} + +extension CrudSiblingsController: RouteCollection {} + +public extension CrudSiblingsController where ThroughType.Right == ParentType, +ThroughType.Left == ChildType { + public func boot(router: Router) throws { + let parentPath = self.basePath.appending(self.path) + let parentIdPath = self.basePath.appending(self.path).appending(ParentType.ID.parameter) + + router.get(parentIdPath, use: self.index) + router.get(parentPath, use: self.indexAll) + router.post(parentPath, use: self.create) + router.put(parentIdPath, use: self.update) + router.delete(parentIdPath, use: self.delete) + } +} + +public extension CrudSiblingsController where ThroughType.Left == ParentType, +ThroughType.Right == ChildType { + public func boot(router: Router) throws { + let parentPath = self.basePath.appending(self.path) + let parentIdPath = self.basePath.appending(self.path).appending(ParentType.ID.parameter) + + router.get(parentIdPath, use: self.index) + router.get(parentPath, use: self.indexAll) + router.post(parentPath, use: self.create) + router.put(parentIdPath, use: self.update) + router.delete(parentIdPath, use: self.delete) + } +} + +public extension CrudSiblingsController { + public func boot(router: Router) throws { + let parentPath = self.basePath.appending(self.path) + let parentIdPath = self.basePath.appending(self.path).appending(ParentType.ID.parameter) + + router.get(parentIdPath, use: self.index) + router.get(parentPath, use: self.indexAll) + router.put(parentIdPath, use: self.update) + } +}