added UserController, UserDTO, CreateUser migration, User model
This commit is contained in:
1 parent
6357258f45
commit
56d1988b17
13 files changed
+297
-159
No files matched your search
@@ -10,3 +10,4 @@ db.sqlite
|
||||
.env.*
|
||||
!.env.example
|
||||
default.profraw
|
||||
.vscode
|
||||
Whitespace-only changes.
@@ -1,37 +0,0 @@
|
||||
import Fluent
|
||||
import Vapor
|
||||
|
||||
struct TodoController: RouteCollection {
|
||||
func boot(routes: any RoutesBuilder) throws {
|
||||
let todos = routes.grouped("todos")
|
||||
|
||||
todos.get(use: self.index)
|
||||
todos.post(use: self.create)
|
||||
todos.group(":todoID") { todo in
|
||||
todo.delete(use: self.delete)
|
||||
}
|
||||
}
|
||||
|
||||
@Sendable
|
||||
func index(req: Request) async throws -> [TodoDTO] {
|
||||
try await Todo.query(on: req.db).all().map { $0.toDTO() }
|
||||
}
|
||||
|
||||
@Sendable
|
||||
func create(req: Request) async throws -> TodoDTO {
|
||||
let todo = try req.content.decode(TodoDTO.self).toModel()
|
||||
|
||||
try await todo.save(on: req.db)
|
||||
return todo.toDTO()
|
||||
}
|
||||
|
||||
@Sendable
|
||||
func delete(req: Request) async throws -> HTTPStatus {
|
||||
guard let todo = try await Todo.find(req.parameters.get("todoID"), on: req.db) else {
|
||||
throw Abort(.notFound)
|
||||
}
|
||||
|
||||
try await todo.delete(on: req.db)
|
||||
return .noContent
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import Fluent
|
||||
import Vapor
|
||||
|
||||
struct UserController: RouteCollection {
|
||||
func boot(routes: any RoutesBuilder) throws {
|
||||
let users = routes.grouped("users")
|
||||
|
||||
users.get(use: self.index)
|
||||
users.post(use: self.create)
|
||||
users.group(":userID") { user in
|
||||
user.get(use: show)
|
||||
user.put(use: update)
|
||||
user.delete(use: self.delete)
|
||||
}
|
||||
}
|
||||
|
||||
@Sendable
|
||||
func index(req: Request) async throws -> [UserDTO] {
|
||||
try await User.query(on: req.db).all().map { $0.toDTO() }
|
||||
}
|
||||
|
||||
@Sendable
|
||||
func create(req: Request) async throws -> UserDTO {
|
||||
let user = try req.content.decode(UserDTO.self).toModel()
|
||||
|
||||
try await user.save(on: req.db)
|
||||
return user.toDTO()
|
||||
}
|
||||
|
||||
@Sendable
|
||||
func show(req: Request) async throws -> UserDTO {
|
||||
guard let user = try await User.find(req.parameters.get("userID"), on: req.db) else {
|
||||
throw Abort(.notFound)
|
||||
}
|
||||
return user.toDTO()
|
||||
}
|
||||
|
||||
@Sendable
|
||||
func update(req: Request) async throws -> UserDTO {
|
||||
guard let user = try await User.find(req.parameters.get("userID"), on: req.db) else {
|
||||
throw Abort(.notFound)
|
||||
}
|
||||
let updatedUser = try req.content.decode(User.self)
|
||||
user.username = updatedUser.username
|
||||
user.email = updatedUser.email
|
||||
user.password = updatedUser.password
|
||||
user.group = updatedUser.group
|
||||
user.isSuperuser = updatedUser.isSuperuser
|
||||
try await user.save(on: req.db)
|
||||
return user.toDTO()
|
||||
}
|
||||
|
||||
@Sendable
|
||||
func delete(req: Request) async throws -> HTTPStatus {
|
||||
guard let user = try await User.find(req.parameters.get("userID"), on: req.db) else {
|
||||
throw Abort(.notFound)
|
||||
}
|
||||
|
||||
try await user.delete(on: req.db)
|
||||
return .noContent
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import Fluent
|
||||
import Vapor
|
||||
|
||||
struct TodoDTO: Content {
|
||||
var id: UUID?
|
||||
var title: String?
|
||||
|
||||
func toModel() -> Todo {
|
||||
let model = Todo()
|
||||
|
||||
model.id = self.id
|
||||
if let title = self.title {
|
||||
model.title = title
|
||||
}
|
||||
return model
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import Fluent
|
||||
import Vapor
|
||||
|
||||
struct UserDTO: Content {
|
||||
var id: UUID?
|
||||
var username: String?
|
||||
var email: String?
|
||||
var password: String?
|
||||
var group: String?
|
||||
var isSuperuser: Bool?
|
||||
|
||||
func toModel() -> User {
|
||||
let model = User()
|
||||
|
||||
model.id = self.id
|
||||
if let username = self.username {
|
||||
model.username = username
|
||||
}
|
||||
if let email = self.email {
|
||||
model.email = email
|
||||
}
|
||||
if let password = self.password {
|
||||
model.password = password
|
||||
}
|
||||
if let group = self.group {
|
||||
model.group = group
|
||||
} else {
|
||||
model.group = "users"
|
||||
}
|
||||
if let isSuperuser = self.isSuperuser {
|
||||
model.isSuperuser = isSuperuser
|
||||
} else {
|
||||
model.isSuperuser = false
|
||||
}
|
||||
return model
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import Fluent
|
||||
|
||||
struct CreateTodo: AsyncMigration {
|
||||
func prepare(on database: any Database) async throws {
|
||||
try await database.schema("todos")
|
||||
.id()
|
||||
.field("title", .string, .required)
|
||||
.create()
|
||||
}
|
||||
|
||||
func revert(on database: any Database) async throws {
|
||||
try await database.schema("todos").delete()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import Fluent
|
||||
|
||||
struct CreateUser: AsyncMigration {
|
||||
func prepare(on database: any Database) async throws {
|
||||
try await database.schema("users")
|
||||
.id()
|
||||
.field("username", .string, .required)
|
||||
.field("email", .string, .required)
|
||||
.field("password", .string, .required)
|
||||
.field("group", .string, .required)
|
||||
.field("is_superuser", .bool, .required)
|
||||
.create()
|
||||
}
|
||||
|
||||
func revert(on database: any Database) async throws {
|
||||
try await database.schema("users").delete()
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import Fluent
|
||||
import struct Foundation.UUID
|
||||
|
||||
/// Property wrappers interact poorly with `Sendable` checking, causing a warning for the `@ID` property
|
||||
/// It is recommended you write your model with sendability checking on and then suppress the warning
|
||||
/// afterwards with `@unchecked Sendable`.
|
||||
final class Todo: Model, @unchecked Sendable {
|
||||
static let schema = "todos"
|
||||
|
||||
@ID(key: .id)
|
||||
var id: UUID?
|
||||
|
||||
@Field(key: "title")
|
||||
var title: String
|
||||
|
||||
init() { }
|
||||
|
||||
init(id: UUID? = nil, title: String) {
|
||||
self.id = id
|
||||
self.title = title
|
||||
}
|
||||
|
||||
func toDTO() -> TodoDTO {
|
||||
.init(
|
||||
id: self.id,
|
||||
title: self.$title.value
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import Fluent
|
||||
|
||||
import struct Foundation.UUID
|
||||
|
||||
final class User: Model, @unchecked Sendable {
|
||||
static let schema = "users"
|
||||
|
||||
@ID(key: .id)
|
||||
var id: UUID?
|
||||
|
||||
@Field(key: "username")
|
||||
var username: String
|
||||
|
||||
@Field(key: "email")
|
||||
var email: String
|
||||
|
||||
@Field(key: "password")
|
||||
var password: String
|
||||
|
||||
@Field(key: "group")
|
||||
var group: String
|
||||
|
||||
@Field(key: "is_superuser")
|
||||
var isSuperuser: Bool
|
||||
|
||||
init() {}
|
||||
|
||||
init(
|
||||
id: UUID? = nil, username: String, email: String, password: String, group: String,
|
||||
isSuperuser: Bool
|
||||
) {
|
||||
self.id = id
|
||||
self.username = username
|
||||
self.email = email
|
||||
self.password = password
|
||||
self.group = group
|
||||
self.isSuperuser = isSuperuser
|
||||
}
|
||||
|
||||
func toDTO() -> UserDTO {
|
||||
.init(
|
||||
id: self.id,
|
||||
username: self.username,
|
||||
email: self.email,
|
||||
group: self.group,
|
||||
isSuperuser: self.isSuperuser
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import NIOSSL
|
||||
import Fluent
|
||||
import FluentSQLiteDriver
|
||||
import NIOSSL
|
||||
import Vapor
|
||||
|
||||
// configures your application
|
||||
@@ -10,8 +10,8 @@ public func configure(_ app: Application) async throws {
|
||||
|
||||
app.databases.use(DatabaseConfigurationFactory.sqlite(.file("db.sqlite")), as: .sqlite)
|
||||
|
||||
app.migrations.add(CreateTodo())
|
||||
app.http.server.configuration.address = .hostname("localhost", port: 9090)
|
||||
app.migrations.add(CreateUser())
|
||||
app.http.server.configuration.address = .hostname("localhost", port: 43243)
|
||||
|
||||
// register routes
|
||||
try routes(app)
|
||||
|
||||
@@ -2,13 +2,5 @@ import Fluent
|
||||
import Vapor
|
||||
|
||||
func routes(_ app: Application) throws {
|
||||
app.get { req async in
|
||||
"It works!"
|
||||
}
|
||||
|
||||
app.get("hello") { req async -> String in
|
||||
"Hello, world!"
|
||||
}
|
||||
|
||||
try app.register(collection: TodoController())
|
||||
try app.register(collection: UserController())
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
@testable import auth_service
|
||||
import VaporTesting
|
||||
import Testing
|
||||
import Fluent
|
||||
import Testing
|
||||
import VaporTesting
|
||||
|
||||
@testable import auth_service
|
||||
|
||||
@Suite("App Tests with DB", .serialized)
|
||||
struct auth_serviceTests {
|
||||
private func withApp(_ test: (Application) async throws -> ()) async throws {
|
||||
private func withApp(_ test: (Application) async throws -> Void) async throws {
|
||||
let app = try await Application.make(.testing)
|
||||
do {
|
||||
try await configure(app)
|
||||
@@ -19,66 +20,141 @@ struct auth_serviceTests {
|
||||
}
|
||||
try await app.asyncShutdown()
|
||||
}
|
||||
|
||||
@Test("Test Hello World Route")
|
||||
func helloWorld() async throws {
|
||||
|
||||
@Test("Getting all the Users")
|
||||
func getAllUsers() async throws {
|
||||
try await withApp { app in
|
||||
try await app.testing().test(.GET, "hello", afterResponse: { res async in
|
||||
#expect(res.status == .ok)
|
||||
#expect(res.body.string == "Hello, world!")
|
||||
})
|
||||
let sampleUsers = [
|
||||
User(
|
||||
username: "user1", email: "user1@example.com", password: "user1password",
|
||||
group: "users", isSuperuser: true),
|
||||
User(
|
||||
username: "user2", email: "user2@example.com", password: "user2password",
|
||||
group: "users", isSuperuser: false),
|
||||
]
|
||||
try await sampleUsers.create(on: app.db)
|
||||
|
||||
try await app.testing().test(
|
||||
.GET, "users",
|
||||
afterResponse: { res async throws in
|
||||
#expect(res.status == .ok)
|
||||
#expect(
|
||||
try res.content.decode([UserDTO].self).sorted(by: {
|
||||
($0.username ?? "") < ($1.username ?? "")
|
||||
})
|
||||
== sampleUsers.map { $0.toDTO() }.sorted(by: {
|
||||
($0.username ?? "") < ($1.username ?? "")
|
||||
})
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@Test("Getting all the Todos")
|
||||
func getAllTodos() async throws {
|
||||
|
||||
@Test("Creating a User")
|
||||
func createUser() async throws {
|
||||
let newDTO = UserDTO(
|
||||
id: nil, username: "test", email: "test@example.com", password: "testpassword")
|
||||
|
||||
try await withApp { app in
|
||||
let sampleTodos = [Todo(title: "sample1"), Todo(title: "sample2")]
|
||||
try await sampleTodos.create(on: app.db)
|
||||
|
||||
try await app.testing().test(.GET, "todos", afterResponse: { res async throws in
|
||||
#expect(res.status == .ok)
|
||||
#expect(try
|
||||
res.content.decode([TodoDTO].self).sorted(by: { ($0.title ?? "") < ($1.title ?? "") }) ==
|
||||
sampleTodos.map { $0.toDTO() }.sorted(by: { ($0.title ?? "") < ($1.title ?? "") })
|
||||
)
|
||||
})
|
||||
try await app.testing().test(
|
||||
.POST, "users",
|
||||
beforeRequest: { req in
|
||||
try req.content.encode(newDTO)
|
||||
},
|
||||
afterResponse: { res async throws in
|
||||
#expect(res.status == .ok)
|
||||
let models = try await User.query(on: app.db).all()
|
||||
#expect(models.map({ $0.toDTO().username }) == [newDTO.username])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@Test("Creating a Todo")
|
||||
func createTodo() async throws {
|
||||
let newDTO = TodoDTO(id: nil, title: "test")
|
||||
|
||||
|
||||
@Test("Deleting a User")
|
||||
func deleteUser() async throws {
|
||||
let testUsers = [
|
||||
User(
|
||||
username: "user1", email: "user1@example.com", password: "user1password",
|
||||
group: "users", isSuperuser: true),
|
||||
User(
|
||||
username: "user2", email: "user2@example.com", password: "user2password",
|
||||
group: "users", isSuperuser: false),
|
||||
]
|
||||
|
||||
try await withApp { app in
|
||||
try await app.testing().test(.POST, "todos", beforeRequest: { req in
|
||||
try req.content.encode(newDTO)
|
||||
}, afterResponse: { res async throws in
|
||||
#expect(res.status == .ok)
|
||||
let models = try await Todo.query(on: app.db).all()
|
||||
#expect(models.map({ $0.toDTO().title }) == [newDTO.title])
|
||||
})
|
||||
try await testUsers.create(on: app.db)
|
||||
|
||||
try await app.testing().test(
|
||||
.DELETE, "users/\(testUsers[0].requireID())",
|
||||
afterResponse: { res async throws in
|
||||
#expect(res.status == .noContent)
|
||||
let model = try await User.find(testUsers[0].id, on: app.db)
|
||||
#expect(model == nil)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@Test("Deleting a Todo")
|
||||
func deleteTodo() async throws {
|
||||
let testTodos = [Todo(title: "test1"), Todo(title: "test2")]
|
||||
|
||||
|
||||
@Test("Getting a User by ID")
|
||||
func showUser() async throws {
|
||||
let testUsers = [
|
||||
User(
|
||||
username: "user1", email: "user1@example.com", password: "user1password",
|
||||
group: "users", isSuperuser: true),
|
||||
User(
|
||||
username: "user2", email: "user2@example.com", password: "user2password",
|
||||
group: "users", isSuperuser: false),
|
||||
]
|
||||
try await withApp { app in
|
||||
try await testTodos.create(on: app.db)
|
||||
|
||||
try await app.testing().test(.DELETE, "todos/\(testTodos[0].requireID())", afterResponse: { res async throws in
|
||||
#expect(res.status == .noContent)
|
||||
let model = try await Todo.find(testTodos[0].id, on: app.db)
|
||||
#expect(model == nil)
|
||||
})
|
||||
try await testUsers.create(on: app.db)
|
||||
|
||||
try await app.testing().test(
|
||||
.GET, "users/\(testUsers[0].requireID())",
|
||||
afterResponse: { res async throws in
|
||||
#expect(res.status == .ok)
|
||||
let user = try res.content.decode(UserDTO.self)
|
||||
#expect(user.id == testUsers[0].id)
|
||||
#expect(user.username == testUsers[0].username)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@Test("Updating a User")
|
||||
func updateUser() async throws {
|
||||
let testUsersDTOs = [
|
||||
UserDTO(
|
||||
username: "user1", email: "user1@example.com", password: "user1password",
|
||||
group: "users", isSuperuser: true),
|
||||
UserDTO(
|
||||
username: "user2", email: "user2@example.com", password: "user2password",
|
||||
group: "users", isSuperuser: false),
|
||||
]
|
||||
let testUsers = [
|
||||
User(
|
||||
username: "user1", email: "user1@example.com", password: "user1password",
|
||||
group: "users", isSuperuser: true),
|
||||
User(
|
||||
username: "user2", email: "user2@example.com", password: "user2password",
|
||||
group: "users", isSuperuser: false),
|
||||
]
|
||||
try await withApp { app in
|
||||
try await testUsers.create(on: app.db)
|
||||
|
||||
try await app.testing().test(
|
||||
.PUT, "users/\(testUsers[0].requireID())",
|
||||
beforeRequest: { req in
|
||||
try req.content.encode(testUsersDTOs[0])
|
||||
},
|
||||
afterResponse: { res async throws in
|
||||
#expect(res.status == .ok)
|
||||
let user = try res.content.decode(UserDTO.self)
|
||||
#expect(user.id == testUsers[0].id)
|
||||
#expect(user.username == testUsers[0].username)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TodoDTO: Equatable {
|
||||
extension UserDTO: Equatable {
|
||||
public static func == (lhs: Self, rhs: Self) -> Bool {
|
||||
lhs.id == rhs.id && lhs.title == rhs.title
|
||||
lhs.id == rhs.id && lhs.username == rhs.username
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user