feat: Initial commit
This commit is contained in:
49
Sources/App/Controllers/ApiController.swift
Normal file
49
Sources/App/Controllers/ApiController.swift
Normal file
@@ -0,0 +1,49 @@
|
||||
import Fluent
|
||||
import Foundation
|
||||
import Vapor
|
||||
|
||||
struct ApiController: RouteCollection {
|
||||
|
||||
func boot(routes: any RoutesBuilder) throws {
|
||||
let users = routes.grouped("api", "users")
|
||||
users.get(use: usersIndex(req:))
|
||||
users.post(use: createUser(req:))
|
||||
|
||||
let proCon = routes.grouped("api", "procons")
|
||||
proCon.get(use: prosAndConsIndex(req:))
|
||||
proCon.post(use: createProCon(req:))
|
||||
}
|
||||
|
||||
@Sendable
|
||||
func usersIndex(req: Request) async throws -> [User] {
|
||||
try await User.query(on: req.db).all()
|
||||
}
|
||||
|
||||
@Sendable
|
||||
func prosAndConsIndex(req: Request) async throws -> [ProCon] {
|
||||
try await ProCon.query(on: req.db).all()
|
||||
}
|
||||
|
||||
@Sendable
|
||||
func createUser(req: Request) async throws -> User {
|
||||
let user = try req.content.decode(User.self)
|
||||
try await user.save(on: req.db)
|
||||
return user
|
||||
}
|
||||
|
||||
@Sendable
|
||||
func createProCon(req: Request) async throws -> ProCon {
|
||||
let proconData = try req.content.decode(ProConDTO.self)
|
||||
let proCon = ProCon(type: proconData.type, description: proconData.description, userId: proconData.userId)
|
||||
try await proCon.create(on: req.db)
|
||||
return proCon
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct ProConDTO: Content {
|
||||
let id: UUID?
|
||||
let type: ProCon.ProConType
|
||||
let description: String
|
||||
let userId: User.IDValue
|
||||
}
|
||||
19
Sources/App/Migrations/CreateProCon.swift
Normal file
19
Sources/App/Migrations/CreateProCon.swift
Normal file
@@ -0,0 +1,19 @@
|
||||
import Fluent
|
||||
import Vapor
|
||||
|
||||
struct CreateProCon: AsyncMigration {
|
||||
|
||||
func prepare(on database: Database) async throws {
|
||||
try await database.schema("procon")
|
||||
.id()
|
||||
.field("description", .string, .required)
|
||||
.field("type", .string, .required)
|
||||
.field("userId", .uuid, .required, .references("user", "id"))
|
||||
.create()
|
||||
}
|
||||
|
||||
func revert(on database: Database) async throws {
|
||||
try await database.schema("procon").delete()
|
||||
}
|
||||
|
||||
}
|
||||
16
Sources/App/Migrations/CreateUser.swift
Normal file
16
Sources/App/Migrations/CreateUser.swift
Normal file
@@ -0,0 +1,16 @@
|
||||
import Fluent
|
||||
import Vapor
|
||||
|
||||
struct CreateUser: AsyncMigration {
|
||||
func prepare(on database: Database) async throws {
|
||||
try await database.schema("user")
|
||||
.id()
|
||||
.field("displayName", .string, .required)
|
||||
.create()
|
||||
}
|
||||
|
||||
func revert(on database: Database) async throws {
|
||||
try await database.schema("user").delete()
|
||||
}
|
||||
|
||||
}
|
||||
1039
Sources/App/Models/BadWords.swift
Normal file
1039
Sources/App/Models/BadWords.swift
Normal file
File diff suppressed because it is too large
Load Diff
40
Sources/App/Models/ProCon.swift
Normal file
40
Sources/App/Models/ProCon.swift
Normal file
@@ -0,0 +1,40 @@
|
||||
import Fluent
|
||||
import Foundation
|
||||
import Vapor
|
||||
|
||||
final class ProCon: Model {
|
||||
static let schema = "procon"
|
||||
|
||||
@ID(key: .id)
|
||||
var id: UUID?
|
||||
|
||||
@Parent(key: "userId")
|
||||
var user: User
|
||||
|
||||
@Field(key: "description")
|
||||
var description: String
|
||||
|
||||
@Field(key: "type")
|
||||
var type: ProConType
|
||||
|
||||
init() {}
|
||||
|
||||
init(
|
||||
id: UUID? = nil,
|
||||
type: ProConType,
|
||||
description: String,
|
||||
userId: User.IDValue
|
||||
) {
|
||||
self.id = id
|
||||
self.type = type
|
||||
self.description = description
|
||||
$user.id = userId
|
||||
}
|
||||
|
||||
enum ProConType: String, Codable, Equatable, Sendable {
|
||||
case pro, con
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ProCon: Content {}
|
||||
25
Sources/App/Models/User.swift
Normal file
25
Sources/App/Models/User.swift
Normal file
@@ -0,0 +1,25 @@
|
||||
import Fluent
|
||||
import Foundation
|
||||
import Vapor
|
||||
|
||||
final class User: Model {
|
||||
static let schema = "user"
|
||||
|
||||
@ID(key: .id)
|
||||
var id: UUID?
|
||||
|
||||
@Field(key: "displayName")
|
||||
var displayName: String
|
||||
|
||||
@Children(for: \.$user)
|
||||
var prosAndCons: [ProCon]
|
||||
|
||||
init() {}
|
||||
|
||||
init(id: UUID? = nil, displayName: String) {
|
||||
self.id = id
|
||||
self.displayName = displayName
|
||||
}
|
||||
}
|
||||
|
||||
extension User: Content {}
|
||||
@@ -1,9 +1,22 @@
|
||||
import Fluent
|
||||
import FluentSQLiteDriver
|
||||
import Leaf
|
||||
import Vapor
|
||||
|
||||
// configures your application
|
||||
public func configure(_ app: Application) async throws {
|
||||
// uncomment to serve files from /Public folder
|
||||
// app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))
|
||||
// register routes
|
||||
try routes(app)
|
||||
// uncomment to serve files from /Public folder
|
||||
app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))
|
||||
app.middleware.use(app.sessions.middleware)
|
||||
// app.databases.use(.sqlite(.file("db.sqlite")), as: .sqlite)
|
||||
app.databases.use(.sqlite(.memory), as: .sqlite)
|
||||
app.migrations.add(CreateProCon())
|
||||
app.migrations.add(CreateUser())
|
||||
app.views.use(.leaf)
|
||||
|
||||
// register routes
|
||||
try routes(app)
|
||||
try app.register(collection: ApiController())
|
||||
|
||||
try await app.autoMigrate()
|
||||
}
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import Vapor
|
||||
import Logging
|
||||
import NIOCore
|
||||
import NIOPosix
|
||||
import Vapor
|
||||
|
||||
@main
|
||||
enum Entrypoint {
|
||||
static func main() async throws {
|
||||
var env = try Environment.detect()
|
||||
try LoggingSystem.bootstrap(from: &env)
|
||||
|
||||
let app = try await Application.make(env)
|
||||
static func main() async throws {
|
||||
var env = try Environment.detect()
|
||||
try LoggingSystem.bootstrap(from: &env)
|
||||
|
||||
// This attempts to install NIO as the Swift Concurrency global executor.
|
||||
// You can enable it if you'd like to reduce the amount of context switching between NIO and Swift Concurrency.
|
||||
// Note: this has caused issues with some libraries that use `.wait()` and cleanly shutting down.
|
||||
// If enabled, you should be careful about calling async functions before this point as it can cause assertion failures.
|
||||
// let executorTakeoverSuccess = NIOSingletons.unsafeTryInstallSingletonPosixEventLoopGroupAsConcurrencyGlobalExecutor()
|
||||
// app.logger.debug("Tried to install SwiftNIO's EventLoopGroup as Swift's global concurrency executor", metadata: ["success": .stringConvertible(executorTakeoverSuccess)])
|
||||
|
||||
do {
|
||||
try await configure(app)
|
||||
} catch {
|
||||
app.logger.report(error: error)
|
||||
try? await app.asyncShutdown()
|
||||
throw error
|
||||
}
|
||||
try await app.execute()
|
||||
try await app.asyncShutdown()
|
||||
let app = try await Application.make(env)
|
||||
|
||||
// This attempts to install NIO as the Swift Concurrency global executor.
|
||||
// You can enable it if you'd like to reduce the amount of context switching between NIO and Swift Concurrency.
|
||||
// Note: this has caused issues with some libraries that use `.wait()` and cleanly shutting down.
|
||||
// If enabled, you should be careful about calling async functions before this point as it can cause assertion failures.
|
||||
// let executorTakeoverSuccess = NIOSingletons.unsafeTryInstallSingletonPosixEventLoopGroupAsConcurrencyGlobalExecutor()
|
||||
// app.logger.debug("Tried to install SwiftNIO's EventLoopGroup as Swift's global concurrency executor", metadata: ["success": .stringConvertible(executorTakeoverSuccess)])
|
||||
|
||||
do {
|
||||
try await configure(app)
|
||||
} catch {
|
||||
app.logger.report(error: error)
|
||||
try? await app.asyncShutdown()
|
||||
throw error
|
||||
}
|
||||
try await app.execute()
|
||||
try await app.asyncShutdown()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,93 @@
|
||||
import Fluent
|
||||
import Foundation
|
||||
import Vapor
|
||||
|
||||
func routes(_ app: Application) throws {
|
||||
app.get { req async in
|
||||
"It works!"
|
||||
app.get { req in
|
||||
let output = try await req.view.render("login")
|
||||
return output
|
||||
}
|
||||
|
||||
app.get("loggedIn") { req in
|
||||
guard let userIdString = req.session.data["userId"],
|
||||
let displayName = req.session.data["displayName"],
|
||||
let userId = UUID(uuidString: userIdString)
|
||||
else {
|
||||
return try await req.view.render("/")
|
||||
}
|
||||
guard let user = try await User.query(on: req.db)
|
||||
.filter(\.$id == userId)
|
||||
.with(\.$prosAndCons)
|
||||
.first()
|
||||
else {
|
||||
throw Abort(.unauthorized)
|
||||
}
|
||||
// let prosAndCons = try await user.$prosAndCons.get(on: req.db)
|
||||
return try await req.view.render(
|
||||
"loggedIn",
|
||||
LoggedInContext(name: displayName, prosAndCons: user.prosAndCons)
|
||||
)
|
||||
}
|
||||
|
||||
app.get("submitProOrCon") { req in
|
||||
let params = try req.query.decode(SubmitProOrCon.self)
|
||||
guard let userIdString = req.session.data["userId"],
|
||||
let userId = UUID(uuidString: userIdString)
|
||||
else {
|
||||
throw Abort(.unauthorized)
|
||||
}
|
||||
let proOrCon = ProCon(type: params.type, description: params.description, userId: userId)
|
||||
_ = try await req.db.transaction {
|
||||
proOrCon.save(on: $0)
|
||||
}
|
||||
.get()
|
||||
|
||||
return req.redirect(to: "loggedIn")
|
||||
}
|
||||
|
||||
app.get("login") { req in
|
||||
let params = try req.query.decode(LoginParams.self)
|
||||
req.logger.info("params: \(params)")
|
||||
|
||||
do {
|
||||
try checkForBadWords(in: params.displayName)
|
||||
} catch {
|
||||
throw Abort(.unauthorized, reason: "Stop using such naughty language.")
|
||||
}
|
||||
|
||||
app.get("hello") { req async -> String in
|
||||
"Hello, world!"
|
||||
}
|
||||
let user = User(displayName: params.displayName)
|
||||
_ = try await req.db.transaction {
|
||||
user.save(on: $0)
|
||||
}.get()
|
||||
|
||||
let userId = user.id?.uuidString ?? "nil"
|
||||
req.session.data["userId"] = userId
|
||||
req.session.data["displayName"] = user.displayName
|
||||
|
||||
// return try await req.view.render("loggedIn", ["name": user.displayName])
|
||||
return req.redirect(to: "loggedIn")
|
||||
}
|
||||
}
|
||||
|
||||
struct DisplayNameError: Error {}
|
||||
|
||||
struct LoginParams: Content {
|
||||
let displayName: String
|
||||
}
|
||||
|
||||
struct SubmitProOrCon: Content {
|
||||
let type: ProCon.ProConType
|
||||
let description: String
|
||||
}
|
||||
|
||||
struct LoggedInContext: Encodable {
|
||||
let name: String
|
||||
let pros: [ProCon]
|
||||
let cons: [ProCon]
|
||||
|
||||
init(name: String, prosAndCons: [ProCon]) {
|
||||
self.name = name
|
||||
self.cons = prosAndCons.filter { $0.type == .con }
|
||||
self.pros = prosAndCons.filter { $0.type == .pro }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user