Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Macros #91

Merged
merged 55 commits into from
Jun 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
8de70a2
Add example executable
Dec 17, 2023
9b4a649
WIP
Dec 29, 2023
fd49656
Cleanup
Jan 1, 2024
ceae407
WIP
joshuawright11 May 18, 2024
810f2f6
working alchemyx query
joshuawright11 May 31, 2024
eb51f3c
fix up logging
joshuawright11 Jun 1, 2024
aadee49
add ResourceMigration
joshuawright11 Jun 2, 2024
cc8d460
add database type
joshuawright11 Jun 2, 2024
8fe65fa
add resource postgres support
joshuawright11 Jun 4, 2024
87eb6b3
cleanup
joshuawright11 Jun 4, 2024
ce87dfe
PapyrusRouter
joshuawright11 Jun 4, 2024
b5b4271
cleanup
joshuawright11 Jun 4, 2024
6a5a505
auth and access control
joshuawright11 Jun 5, 2024
c4d72be
plugins and such
joshuawright11 Jun 12, 2024
e9e6030
JobMacro
joshuawright11 Jun 12, 2024
4a2e39b
ApplicationMacro
joshuawright11 Jun 12, 2024
40fe3e8
Basic
joshuawright11 Jun 12, 2024
96bf5c1
auto
joshuawright11 Jun 12, 2024
1daaefa
wrap jobs
joshuawright11 Jun 12, 2024
d676f99
parameter parsing
joshuawright11 Jun 13, 2024
b934fc4
jobs
joshuawright11 Jun 13, 2024
55bf0aa
controllers
joshuawright11 Jun 13, 2024
0df3fb0
validations
joshuawright11 Jun 14, 2024
f1e6e44
add options
joshuawright11 Jun 14, 2024
cbd26ed
cleanup
joshuawright11 Jun 14, 2024
9da01b8
update
joshuawright11 Jun 14, 2024
636bd5b
clean route
joshuawright11 Jun 15, 2024
3b8c8be
cleanup
joshuawright11 Jun 15, 2024
7b3bf2a
cleanup
joshuawright11 Jun 15, 2024
908066c
Add model macro
joshuawright11 Jun 15, 2024
4892649
ModelMacro first stab
joshuawright11 Jun 17, 2024
f4b50b9
Codable support
joshuawright11 Jun 17, 2024
f228835
model macro working
joshuawright11 Jun 18, 2024
910c404
add encoding of relationships
joshuawright11 Jun 19, 2024
631567d
bit of cleanup
joshuawright11 Jun 19, 2024
d3a3cf5
hasmany
joshuawright11 Jun 19, 2024
e3523cf
relationship macros
joshuawright11 Jun 20, 2024
a3c2e42
cleanup
joshuawright11 Jun 20, 2024
d399425
cleanup
joshuawright11 Jun 20, 2024
cd3f1d1
workflows
joshuawright11 Jun 20, 2024
4a1b42b
workflows again
joshuawright11 Jun 20, 2024
ef0345c
direct link
joshuawright11 Jun 20, 2024
9c1bbd3
fix some tests
joshuawright11 Jun 21, 2024
54800ca
tests pass
joshuawright11 Jun 22, 2024
8c64551
no main
joshuawright11 Jun 22, 2024
7ac145c
logging
joshuawright11 Jun 22, 2024
b40c29a
now with regex
joshuawright11 Jun 22, 2024
6deead3
update
joshuawright11 Jun 22, 2024
96e27eb
fix relationship
joshuawright11 Jun 23, 2024
dc48f67
cleanup relation tests
joshuawright11 Jun 23, 2024
c7f72a4
fix type inference and default id value
joshuawright11 Jun 23, 2024
03042cb
bad
joshuawright11 Jun 23, 2024
5ca1f94
cleanup
joshuawright11 Jun 23, 2024
b1ec1aa
update
joshuawright11 Jun 23, 2024
05be476
cleanup macros
joshuawright11 Jun 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,20 @@ on:

jobs:
test-macos:
runs-on: macos-13
runs-on: macos-14
env:
DEVELOPER_DIR: /Applications/Xcode_14.3.app/Contents/Developer
DEVELOPER_DIR: /Applications/Xcode_15.4.app/Contents/Developer
steps:
- uses: actions/checkout@v3
- name: Build
run: swift build -v
- name: Run tests
run: swift test -v
test-linux:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
strategy:
matrix:
swift: [5.8]
swift: ["5.10"]
container: swift:${{ matrix.swift }}
steps:
- uses: actions/checkout@v3
Expand Down
128 changes: 128 additions & 0 deletions Alchemy/AlchemyMacros.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
@attached(extension, conformances: Application, Controller, names: named(route))
public macro Application() = #externalMacro(module: "AlchemyPlugin", type: "ApplicationMacro")

@attached(extension, conformances: Controller, names: named(route))
public macro Controller() = #externalMacro(module: "AlchemyPlugin", type: "ControllerMacro")

@attached(peer, names: prefixed(`$`))
public macro Job() = #externalMacro(module: "AlchemyPlugin", type: "JobMacro")

// MARK: Rune - Model

@attached(memberAttribute)
@attached(member, names: named(storage), named(fieldLookup))
@attached(extension, conformances: Model, Codable, names: named(init), named(fields), named(encode))
public macro Model() = #externalMacro(module: "AlchemyPlugin", type: "ModelMacro")

@attached(accessor)
public macro ID() = #externalMacro(module: "AlchemyPlugin", type: "IDMacro")

// MARK: Rune - Relationships

@attached(accessor)
@attached(peer, names: prefixed(`$`))
public macro HasMany(from: String? = nil, to: String? = nil) = #externalMacro(module: "AlchemyPlugin", type: "RelationshipMacro")

@attached(accessor)
@attached(peer, names: prefixed(`$`))
public macro HasManyThrough(
_ through: String,
from: String? = nil,
to: String? = nil,
throughFrom: String? = nil,
throughTo: String? = nil
) = #externalMacro(module: "AlchemyPlugin", type: "RelationshipMacro")

@attached(accessor)
@attached(peer, names: prefixed(`$`))
public macro HasOne(from: String? = nil, to: String? = nil) = #externalMacro(module: "AlchemyPlugin", type: "RelationshipMacro")

@attached(accessor)
@attached(peer, names: prefixed(`$`))
public macro HasOneThrough(
_ through: String,
from: String? = nil,
to: String? = nil,
throughFrom: String? = nil,
throughTo: String? = nil
) = #externalMacro(module: "AlchemyPlugin", type: "RelationshipMacro")

@attached(accessor)
@attached(peer, names: prefixed(`$`))
public macro BelongsTo(from: String? = nil, to: String? = nil) = #externalMacro(module: "AlchemyPlugin", type: "RelationshipMacro")

@attached(accessor)
@attached(peer, names: prefixed(`$`))
public macro BelongsToThrough(
_ through: String,
from: String? = nil,
to: String? = nil,
throughFrom: String? = nil,
throughTo: String? = nil
) = #externalMacro(module: "AlchemyPlugin", type: "RelationshipMacro")

@attached(accessor)
@attached(peer, names: prefixed(`$`))
public macro BelongsToMany(
_ pivot: String? = nil,
from: String? = nil,
to: String? = nil,
pivotFrom: String? = nil,
pivotTo: String? = nil
) = #externalMacro(module: "AlchemyPlugin", type: "RelationshipMacro")

// MARK: Route Methods

@attached(peer, names: prefixed(`$`)) public macro HTTP(_ method: String, _ path: String, options: RouteOptions = []) = #externalMacro(module: "AlchemyPlugin", type: "HTTPMethodMacro")
@attached(peer, names: prefixed(`$`)) public macro DELETE(_ path: String, options: RouteOptions = []) = #externalMacro(module: "AlchemyPlugin", type: "HTTPMethodMacro")
@attached(peer, names: prefixed(`$`)) public macro GET(_ path: String, options: RouteOptions = []) = #externalMacro(module: "AlchemyPlugin", type: "HTTPMethodMacro")
@attached(peer, names: prefixed(`$`)) public macro PATCH(_ path: String, options: RouteOptions = []) = #externalMacro(module: "AlchemyPlugin", type: "HTTPMethodMacro")
@attached(peer, names: prefixed(`$`)) public macro POST(_ path: String, options: RouteOptions = []) = #externalMacro(module: "AlchemyPlugin", type: "HTTPMethodMacro")
@attached(peer, names: prefixed(`$`)) public macro PUT(_ path: String, options: RouteOptions = []) = #externalMacro(module: "AlchemyPlugin", type: "HTTPMethodMacro")
@attached(peer, names: prefixed(`$`)) public macro OPTIONS(_ path: String, options: RouteOptions = []) = #externalMacro(module: "AlchemyPlugin", type: "HTTPMethodMacro")
@attached(peer, names: prefixed(`$`)) public macro HEAD(_ path: String, options: RouteOptions = []) = #externalMacro(module: "AlchemyPlugin", type: "HTTPMethodMacro")
@attached(peer, names: prefixed(`$`)) public macro TRACE(_ path: String, options: RouteOptions = []) = #externalMacro(module: "AlchemyPlugin", type: "HTTPMethodMacro")
@attached(peer, names: prefixed(`$`)) public macro CONNECT(_ path: String, options: RouteOptions = []) = #externalMacro(module: "AlchemyPlugin", type: "HTTPMethodMacro")

// MARK: Route Parameters

@propertyWrapper public struct Path<L: LosslessStringConvertible> {
public var wrappedValue: L

public init(wrappedValue: L) {
self.wrappedValue = wrappedValue
}
}

@propertyWrapper public struct Header<L: LosslessStringConvertible> {
public var wrappedValue: L

public init(wrappedValue: L) {
self.wrappedValue = wrappedValue
}
}

@propertyWrapper public struct URLQuery<L: LosslessStringConvertible> {
public var wrappedValue: L

public init(wrappedValue: L) {
self.wrappedValue = wrappedValue
}
}

@propertyWrapper public struct Field<C: Codable> {
public var wrappedValue: C

public init(wrappedValue: C) {
self.wrappedValue = wrappedValue
}
}

@propertyWrapper public struct Body<C: Codable> {
public var wrappedValue: C

public init(wrappedValue: C) {
self.wrappedValue = wrappedValue
}
}

119 changes: 119 additions & 0 deletions Alchemy/AlchemyX/Application+AlchemyX.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import AlchemyX

extension Application {
@discardableResult
public func useAlchemyX(db: Database = DB) -> Self {
// 1. users table

db.migrations.append(UserMigration())
db.migrations.append(TokenMigration())

// 2. users endpoint

return use(AuthController())
}
}

private struct AuthController: Controller, AuthAPI {
func route(_ router: Router) {
registerHandlers(on: router)
}

func signUp(email: String, password: String) async throws -> AuthResponse {
let password = try await Hash.make(password)
let user = try await User(email: email, password: password).insertReturn()
let token = try await Token(userId: user.id).insertReturn()
return .init(token: token.value, user: user.dto)
}

func signIn(email: String, password: String) async throws -> AuthResponse {
guard let user = try await User.firstWhere("email" == email) else {
throw HTTPError(.notFound)
}

guard try await Hash.verify(password, hash: user.password) else {
throw HTTPError(.unauthorized)
}

let token = try await Token(userId: user.id).insertReturn()
return .init(token: token.value, user: user.dto)
}

func signOut() async throws {
try await token.delete()
}

func getUser() async throws -> AlchemyX.User {
try user.dto
}

func updateUser(email: String?, phone: String?, password: String?) async throws -> AlchemyX.User {
var user = try user
if let email { user.email = email }
if let phone { user.phone = phone }
if let password { user.password = try await Hash.make(password) }
return try await user.save().dto
}
}

extension Controller {
var req: Request { .current }
fileprivate var user: User { get throws { try req.get() } }
fileprivate var token: Token { get throws { try req.get() } }
}

@Model
struct Token: TokenAuthable {
var id: UUID
var value: String = UUID().uuidString
let userId: UUID

@BelongsTo var user: User
}

@Model
struct User {
var id: UUID
var email: String
var password: String
var phone: String?

@HasMany var tokens: [Token]

var dto: AlchemyX.User {
AlchemyX.User(
id: id,
email: email,
phone: phone
)
}
}

struct TokenMigration: Migration {
func up(db: Database) async throws {
try await db.createTable("tokens") {
$0.uuid("id").primary()
$0.string("value").notNull()
$0.uuid("user_id").references("id", on: "users").notNull()
}
}

func down(db: Database) async throws {
try await db.dropTable("tokens")
}
}

struct UserMigration: Migration {
func up(db: Database) async throws {
try await db.createTable("users") {
$0.uuid("id").primary()
$0.string("email").notNull()
$0.string("password").notNull()
$0.string("phone")
}
}

func down(db: Database) async throws {
try await db.dropTable("users")
}
}
Loading
Loading