Skip to content

Instantly share code, notes, and snippets.

@InukVT
Created May 4, 2019 16:56
Show Gist options
  • Select an option

  • Save InukVT/1f9c5fbef77609e01cb57353d7acfc4e to your computer and use it in GitHub Desktop.

Select an option

Save InukVT/1f9c5fbef77609e01cb57353d7acfc4e to your computer and use it in GitHub Desktop.
import Vapor
import Service
import Crypto
import Foundation
private var b2CacheKey = "__b2_authorization_token"
public struct B2Config: Service {
public let keyID: String
public let applicationID: String
public let bucketID: String
public init(keyID: String, applicationID: String, bucketID: String) {
self.applicationID = applicationID
self.keyID = keyID
self.bucketID = bucketID
}
}
public final class B2: ServiceType {
private let config: B2Config
private let client: Client
private let cache: KeyedCache
private init(config: B2Config, client: Client, cache: KeyedCache) {
self.config = config
self.client = client
self.cache = cache
}
public static func makeService(for worker: Container) throws -> B2 {
return B2(
config: try worker.make(),
client: try worker.client(),
cache: try worker.make()
)
}
public func upload(file: File) throws -> Future<Response> {
return authorize()
.flatMap(self.getUploadUrl)
.flatMap { uploadUrl in
let uploadAuthorizationToken = uploadUrl.authorizationToken
let fileName = file.filename
let sha1 = try SHA1.hash(file.data).hexEncodedString()
var b2Headers: [String: String] = ["Authorization": uploadAuthorizationToken]
b2Headers["X-Bz-File-Name"] = fileName
b2Headers["Content-Type"] = file.contentType?.description ?? "b2/X-auto"
b2Headers["Content-Length"] = "\(file.data.count + 40)"
b2Headers["X-Bz-Content-Sha1"] = sha1
let headers = HTTPHeaders(b2Headers.map { $0 })
let request = Request(using: self.client.container)
request.http.method = .POST
request.http.headers = headers
request.http.body = file.data.convertToHTTPBody()
request.http.url = URL(string: uploadUrl.uploadUrl)!
return self.client.send(request)
}
}
private func authorize() -> Future<B2Auth> {
return cache.get(b2CacheKey, as: B2Auth.self)
.flatMap { auth in
if let auth = auth {
return self.client.container.future(auth)
}
let keyID = self.config.keyID
let applicationID = self.config.applicationID
let auth = ((keyID + ":" + applicationID).data(using: .utf8) ?? Data()).base64EncodedString()
let request = Request(using: self.client.container)
request.http.method = .GET
request.http.headers.add(name: "Authorization", value: "Basic \(auth)")
request.http.url = URL(string: "https://api.backblazeb2.com/b2api/v2/b2_authorize_account")!
return self.client.send(request)
.flatMap { try $0.content.decode(B2Auth.self) }
.flatMap { self.cache.set(b2CacheKey, to: $0).transform(to: $0) }
}
}
private func getUploadUrl(auth: B2Auth) throws -> Future<B2UploadUrl> {
let request = Request(using: client.container)
request.http.method = .POST
request.http.headers.add(name: "Authorization", value: auth.authorizationToken)
try request.content.encode(json: B2UploadUrlRequest(bucketId: config.bucketID))
request.http.url = URL(string: auth.apiUrl + "/b2api/v2/b2_get_upload_url")!
return client.send(request)
.flatMap { try $0.content.decode(B2UploadUrl.self) }
}
}
private struct B2Auth: Codable {
let absoluteMinimumPartSize: Int
let accountId: String
let apiUrl: String
let authorizationToken: String
let downloadUrl: String
let recommendedPartSize: Int
}
private struct B2UploadUrl: Codable {
let bucketId: String
let uploadUrl: String
let authorizationToken: String
}
private struct B2UploadUrlRequest: Encodable {
let bucketId: String
}
public extension Container {
public func b2() throws -> B2 {
return try B2.makeService(for: self)
}
}
//import FluentSQLite
import Vapor
import S3
import ServiceExt
import B2
/// Called before your application initializes.
public func configure(_ config: inout Config, _ env: inout Environment, _ services: inout Services) throws {
/// Register providers first
//try services.register(FluentSQLiteProvider())
/// Register routes to the router
let router = EngineRouter.default()
try routes(router)
services.register(router, as: Router.self)
/// Register middleware
var middlewares = MiddlewareConfig() // Create _empty_ middleware config
/// middlewares.use(FileMiddleware.self) // Serves files from `Public/` directory
middlewares.use(ErrorMiddleware.self) // Catches errors and converts to HTTP response
services.register(middlewares)
Environment.dotenv()
if Environment.get("CLOUDPROVIDER") == "aws" {
guard let accessKey: String = Environment.get("AWSACCESSKEY"), let secretKey: String = Environment.get("AWSSECRETKEY") else {
throw Abort(.internalServerError, reason: "AWS environment variables missing")
}
print(accessKey)
let region = Region.euCentral1
try services.register(s3: S3Signer.Config(accessKey: accessKey,
secretKey: secretKey,
region: region),
defaultBucket: "bucket-inuk")
}
else if Environment.get("CLOUDPROVIDER") == "b2" {
guard let applicationKey: String = Environment.get("B2APPLICATIONKEY"), let keyID: String = Environment.get("B2KEYID") else {
throw Abort(.internalServerError, reason: "B2 environment variables missing")
}
let B2Creds = B2Config(keyID: keyID,
applicationID: applicationKey,
bucketID: "c57dae5326d7b3ac66660118")
services.register(B2Creds)
services.register(B2.self)
}
}
import Vapor
import S3
import B2
//rimport Foundation
/// Register your application's routes here.
public func routes(_ router: Router) throws {
// Basic "It works" example
router.get { req in
return "It works!"
}
let s3Routes = router.grouped("s3")
func postS3(_ req: Request) throws -> Future<Response> {
return try req.content.decode(Filer.self).flatMap { filer in
let mimeType = filer.file.contentType?.description ?? MediaType.plainText.description
let file = File.Upload(data: filer.file.data, destination: filer.file.filename, access: .publicRead, mime: mimeType)
return try req.makeS3Client().put(file: file, on: req).map(to: Response.self) {_ in
return req.redirect(to: "")
}
}
}
func deletes3(_ req: Request) throws -> Future<HTTPStatus> {
return try req.content.decode(Delete.self).flatMap { response in
return try req.makeS3Client().delete(file: response.file as LocationConvertible, on: req).transform(to: HTTPStatus.noContent)
}
}
s3Routes.post("post", use: postS3)
s3Routes.delete("delete", use: deletes3)
let b2Routes = router.grouped("b2")
func postB2(_ req: Request) throws -> Future<Response> {
return try req.content.decode(Filer.self).flatMap { filer in
let file = filer.file
return try req.make(B2.self).upload(file: file).map {_ in
return req.redirect(to: "")
}
}
}
b2Routes.post("post", use: postB2)
}
struct Filer: Content
{
let file: Core.File
}
struct Delete: Content {
let file: String
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment