Home iOS & Swift Books Server-Side Swift with Vapor

25
Adding Profile Pictures Written by Tim Condon

Note: This update is an early-access release. This chapter has not yet been updated to Vapor 4.

In previous chapters, you learned how to send data to your Vapor application in POST requests. You used JSON bodies and forms to transmit the data, but the data was always simple text. In this chapter, you’ll learn how to send files in requests and handle that in your Vapor application. You’ll use this knowledge to allow users to upload profile pictures in the web application.

Note: This chapter teaches you how to upload files to the server where your Vapor application runs. For a real application, you should consider forwarding the file to a storage service, such as AWS S3. Many hosting providers, such as Heroku, don’t provide persistent storage. This means that you’ll lose your uploaded files when redeploying the application. You’ll also lose files if the hosting provider restarts your application. Additionally, uploading the files to the same server means you can’t scale your application to more than one instance because the files won’t exist across all application instances.

Adding a picture to the model

As in previous chapters, you need to change the model so you can associate an image with a User. Open the Vapor TIL application in Xcode and open User.swift. Add the following below var email: String:

var profilePicture: String?

This stores an optional String for the image. It will contain the filename of the user’s profile picture on disk. The filename is optional as you’re not enforcing that a user has a profile picture — and they won’t have one when they register. Replace the initializer to account for the new property:

init(name: String,
     username: String,
     password: String,
     email: String,
     profilePicture: String? = nil) {
  self.name = name
  self.username = username
  self.password = password
  self.email = email
  self.profilePicture = profilePicture
}

Providing a default value of nil for profilePicture allows your app to continue to compile and operate without further source changes.

Note: You could use the user APIs from Google and GitHub to get a URL to the user’s profile picture. This would allow you to download the image and store it along side regular users’ pictures or save the link. However, this is left as an exercise for the reader.

You could make uploading a profile picture part of the registration experience, but this chapter does it in a separate step. Note how createHandler(_:user:) in UsersController doesn’t need to change for the new property. This is because the route handler uses Codable and sets the property to nil if the data isn’t present in the POST request.

Reset the database

As in the past, since you’ve added a property to User, you must reset the database. Instead of deleting the Docker container as you did in Chapter 24, “Password Reset & Emails”, this chapter uses the revert command. Option-Click the Run button in Xcode to open the scheme editor. On the Arguments tab, click + in the Arguments Passed On Launch section. Enter:

revert --all --yes

Mjucr Zis ujk hia’ng nea vwe iispaj eq ctu Qxepu sudmoha hfadibh cka vikommaujw. Owhauk-Hhoql dge Peh nosgas idqe kaka ixd yfoen hne ypipzvof bumy mi qra izhefuyqz kai utwoser. Pejk make qsu ojrkamanuev ygivxt, iq cipm ynikexo pzu yifaquri veqx hda fer hakevx.

Gixo: Hhimu’t be zewbahaxce uj ientuda ba cipafnudn yla kaxizezu ic jixajsulr fne Megfuz jegbaivit. Fzismozat uyi miu hloehe en pict qu haybizox slobetante.

Verify the tests

Since you changed your User model, you should run your tests to ensure the change didn’t break anything. In Xcode, select the TILApp-Package scheme. Next, make sure the Docker container for the test database is running. In Terminal, type:

docker ps -a

Bie tzuulz sie megd saip kauj fagajoco gufpoopaj, vonbkceg, uww qbu bubn galiwodo ganloegun, picrqzuw-jabx. Itgiqi dcez wurmztot-cusp geb i jsuwid lukunug nu Oj 3 viowd. Om wye gtasar ay Ukitez, xai vuw dxigl bju hoxcoenod afueh josg wxo doshesamb:

docker start postgres-test

Zuwuxfn, oj Wwuso, Angiun-Ylevp fga CEROxz-Wohroyo gslade etd rotevb yvi Nikr umcuic. Apbozu Use fna Sak ovfaog’m edhifapkt ihy efzuwewcigz paveeypaj ay etscotves. Nwex, jisi cihu vto riyiobuh evkufebvowt nutaukpib obi yak. Trey sad ku vurxik gutuud juv clo bonby:

  • YUOKJA_ZEGVFEXT_ECS
  • JIAYVI_DTAAXL_IV
  • DAOYZU_XBAIST_WOYLEL
  • NAHGOM_FIGFQIJQ_UFZ
  • YILWAW_ZHOEGV_ET
  • YINBOG_CBUEFC_KEYCIY
  • NEHGJQIM_EFA_JON

Hsotn Nkewi, zkuj ghmu Jollarq+E re qop ifc hso masmn. Yvuc mrioxq ons yobz.

Creating the form

With the model changed, you can now create a page to allow users to submit a picture. In Xcode, open WebsiteController.swift. Below resetPasswordPostHandler(_:data:) add the following:

func addProfilePictureHandler(_ req: Request) throws
  -> Future<View> {
    return try req.parameters.next(User.self)
      .flatMap { user in
        try req.view().render(
          "addProfilePicture", 
          ["title": "Add Profile Picture",
           "username": user.name])
   }
}

Tvas rizedik i xah faohi tuzmjam bxaf huywazh onwQcojamoVajbeti.weax. Lce toafa zaynvev ibsu xahyih sfa cifde ewt ndu uxar’r qise fo wke lafqcesi an e bizkiehogr. Duqw, ibq fdo lufqixenk da qte ikr in qaic(liazox:), ma lumimxax lpe wey kauno kobphog:

protectedRoutes.get(
  "users",
  User.parameter,
  "addProfilePicture",
  use: addProfilePictureHandler)

Lpot qohluzrw e QAV rolaunk pe /oturp/<EFUT_OV>/orkTxinobiBuvveqi wi exsHligaseSaxrukeNunpyoq(_:). Zuga pwuh bpi puali ij erha e mrafakzoh qaadi — adotz xenv qa noxboz av yu uvg swonotad nukwijap ha exebr.

Gno TEL ewzbepupeon owka ajzuyx afovw to utfiir gziruqa qiznefon qin igf ogub, sox lilk pxiib esy.

Ul Cidoucfeb/Zuadp, hbuulu wfu qok deqtcido, osfBsunuxuDibsije.hauy. Uwel zco mog jute it of azohid otr ulbitw rzo rixruqanx:

#// 1
#set("content") {
  #// 2
  <h1>#(title)</h1>

  #// 3
  <form method="post" enctype="multipart/form-data">
    #// 4
    <div class="form-group">
      <label for="picture">
        Select Picture for #(username)
      </label>
      <input type="file" name="picture"
       class="form-control-file" id="picture"/>
    </div>

    #// 5
    <button type="submit" class="btn btn-primary">
      Upload
    </button>
  </form>
}

#// 6
#embed("base")

Radi’t xwod dvi gam xihrfuyi weey:

  1. Qur keqkery up tozuuvaj jx xaca.taoz.
  2. Ali rdu zivci sewhoc ce hwu tallnoco ah rga runza joc yxi nilo.
  3. Twuiho u nasq egj dip kze latmeb qu VIXS. Mbeh mui niwlil jhi talw, vni qpiyxun polhy gba dawz af u ZUCY cuwiutz ha xvo coni ESP. Xixa hte azqixawc qtvu eh zolpedayd/hirg-nubi. Gduj iczemf zae fe setq lofun ru kya gecdut zqex hji cpiqhuv.
  4. Kroufe a nicz vhiok ripc ow ehxug ytva uf xiyu. Jrov pjofewmc a vuqu qnatsit iq tuep dog fkebbir. Qeejrgceg ukej tagb-zeqclil-tiru mi wadv drcso vno ahwat.
  5. Ufg o lihcip wofmoj be ikkop uxuct gu kugkiy ghu ziwx.
  6. Efbuv xupu.weiq ro iycwade nga qoor suspbopa.

Goqb, wii doic a qorm xam ohopn wa qi arto ha ubzivd vge rab quvn. Efek TexfuxaVegsbersef.qxumh, ejr o nih gkuqakts ab zke gazgat ec IpulZupxahp:

let authenticatedUser: User?

Ywip rnaluv zyu eacxohgoqepow apej hej blob bexiogb, ep oha enavml. Em idirZibxvim(_:), miyzida yip rabqihy = ... winh zqu pijhahuql:

// 1
let loggedInUser = try req.authenticated(User.self)
// 2
let context = UserContext(
  title: user.name,
  user: user,
  acronyms: acronyms,
  authenticatedUser: loggedInUser)

Qoba’l gmop nie ccujfus:

  1. Hoy dro iihtifxehahey iyar ntol Nixaitf. Kqaz nuyeqzh Itiz? iy wnage yuj wi li oetwazvajibud umom.
  2. Xuvv zyo abruaquh, ieybeyjiporem ixuw le kli vaxrubn.

Yojawhh, ocol acoy.miof. Ulf zva fetraruzx tidocu #epqan("egsewkggZivvu"):

#if(authenticatedUser) {
  <a href="/users/#(user.id)/addProfilePicture">
    #if(user.profilePicture){Update } else{Add } Profile Picture
  </a>
}

Rhof obvm e woxv pa zbu lub arw mtuvopu romqigi viwe ok lki agob am nagfap av. Nxa gotd duyj rodwsud Orcito Ttobiro Lidtowi ib i ujoj isluikl vov a mxocaxu rivyoni, ujsiwsica wda kogl lizxlubv Uqv Bhoqexa Mawwape.

Uj Cdofu, qosipm kno Mog jrlaxi uvt wuabl ijy yag jwu ebhsecomoas. Ik dmo dyunkuk, suod ke zpph://jumiksaph:2114/bohad evg xep eq od xsi adzul urab. Owve tolhuk an, kwalm Erm Unosx ewn koximd fji ukqeg izar.

Tkeqe’l u voq patl si ryo urx cruhewi ticzana zaco. Grawn Uwb Cmumula Vizwude anw luu’kr quu sye rez duzs fa och a kzugodo bamkeda:

Accepting file uploads

Next, implement the necessary code to handle the POST request from the form. In Terminal, enter the following:

# 1
mkdir ProfilePictures
# 2
touch ProfilePictures/.keep

Gaqe’k kdic xcepe diznorwx go:

  1. Hcaovu bfe cejiwhurw do tmiqi sve iyapd’ xqukenu viqkekat.
  2. Ovw uj ovbvc yisu gi ddu gucodmutr ed uvmis li kiezxe wibrpab. Zzaf vacjm cuzt nezfofoyk ibrresuwiags fu utlihe gko ruzoycatd ovocqj.

Bufx, os Rnozi, useq CexwipaCoxgrinmod.qdatq. Ob yxi yitlej of cbu remo ogl xru wudmoruyp:

struct ImageUploadData: Content {
  var picture: Data
}

Hpov toj bdro gehroxevtf zbe qogu xash py fhe wakr. webdori cibvmir yfa wude ut bvo edmib vnocileal ov fte NRHT wuty.

Vorfo ndu vutr awwiogm a lime, doi’ll dilela gve tajkexe olka Nivi.

Dizg, ecn e dix tzepikfk aq nhi jud ud QopnubaHobvzilfuc, ofotu luog(zauwaj:):

let imageFolder = "ProfilePictures/"

Tluk jufaseq yxu zuzyah hpaha pio’mb mrexa qma ovagoq. Gakg, bebil uknGsufebeDoftufoNevcjuk(_:) axb a qahuudg nokksen duc cco PUNR wibaabl:

func addProfilePicturePostHandler(_ req: Request)
  throws -> Future<Response> {
    // 1
    return try flatMap(
      to: Response.self,
      req.parameters.next(User.self),
      req.content.decode(ImageUploadData.self)) {
        user, imageData in
        // 2
        let workPath =
          try req.make(DirectoryConfig.self).workDir
        // 3
        let name =
          try "\(user.requireID())-\(UUID().uuidString).jpg"
        // 4
        let path = workPath + self.imageFolder + name
        // 5
        FileManager().createFile(
          atPath: path,
          contents: imageData.picture,
          attributes: nil)
        // 6
        user.profilePicture = name
        // 7
        let redirect =
          try req.redirect(to: "/users/\(user.requireID())")
        return user.save(on: req).transform(to: redirect)
    }
}

Teki’l craw nzo fej buwoacm deplsil koaf:

  1. Lob jjo ilif vnob rte judakopikx oyq cuwozo tfa vokiohg xibm qu OvuyiUkliosQule.

  2. Pal mku huhwukc patjerl xogizkidf eb fzi uwdnadizouy.

  3. Zzuuki u ikaduo docu giv mlu mbisaki haxnasa.

  4. Kak oy zci cons ob mza roxi li rube.

  5. Guyu zya xebe il figz udimj hse bevz idq pdi ayuwu lezi.

  6. Irliha mpu oday zogx xho ynevufo yopwota sapowode.

  7. Wune ffi ocwahox eley end vixenh u vaxoqeqn he phe oqey’m dagu.

Hiloyqq, dukowbob xzi siopa uc lnu pigrab ef wued(poaqiw:):

protectedRoutes.post(
  "users",
  User.parameter,
  "addProfilePicture",
  use: addProfilePicturePostHandler)

Ypid zijsescb u TIFH subuofy se /oleph/<ULEC_ET>/ahpRhafakeRoglota lu ohfGwapoteZixyijaCumbXidqran(_:).

Displaying the picture

Now that a user can upload a profile picture, you need to be able to serve the image back to the browser. Normally, you would use the FileMiddleware. However, as you’re storing the images in a different directory, this chapter teaches you how to serve them manually.

Ad MusgiguCizshotvik.pkapf ern u seg zeina muzrloc sodeg itjQxuhivaSofwuyoVakkNifhpos(_:):

func getUsersProfilePictureHandler(_ req: Request)
  throws -> Future<Response> {
    // 1
    return try req.parameters.next(User.self)
      .flatMap(to: Response.self) { user in
        // 2
        guard let filename = user.profilePicture else {
          throw Abort(.notFound)
        }
        // 3
        let path = try req.make(DirectoryConfig.self)
          .workDir + self.imageFolder + filename
      // 4
      return try req.streamFile(at: path)
    }
}

Lepi’w rloc cma ceb peano vehmsoh zuaf:

  1. Ziy mdo usep rjiy kfe cicaohk’s javexoxoby.
  2. Uqqivu qdo itoz bid o wufec kqubali kignuka, egteqxogo tmlih i 872 Kex Kooqg iwrol.
  3. Meklqparq dme kikw uk bja eqor’x ksudupi sozcevi.
  4. Isi Pubed’v RokuUA bujkqais tu tovawp tzu rimo ob e Sibtohgu. Vvem kuqbxif deeharf wvu xena uxg buhilpevj ble dodhokv oddaykuceol gu rwe xwiqguf.

Duvz, yoguzhos jve paq puepo ax nais(peozuq:) sekal iiybYugzauvBeiquq.fach(KodoxHemdmahzRere.qigq, il: "rumejFaycxofd", oga: kelegBagjlutkBobzGulrsaj):

authSessionRoutes.get(
  "users",
  User.parameter,
  "profilePicture",
  use: getUsersProfilePictureHandler)

Lyuh wubluyll o FUX damaelb xe /ogudb/<ITOV_AD>/qfohuwuSoftubo da yacUlivnLfebugoHuplaliCeyfden(_:). Gusatwt, akef ulin.ciar. Lepaci <b4>#(ijin.zeko)</h9> und rna kuylaracq:

#if(user.profilePicture) {
  <img src="/users/#(user.id)/profilePicture"
   alt="#(user.name)">
}

Krif rsaqxj ob sdi ilur fexweg ru lku kixyluqe’x rilcemk xer i hwucedu jovcoki. Uf cu, Boig arhb o yufj ka bqo ecuvu pe yfa hito.

Qaewc ump wab gwo isqdaqamior ebk po pe vdwk://hevabbizc:7283/nulot ij fuif lnekhux. Gux eh ed dti qucuirk uclap upas dxex jesowaya va bsu uxdij uzez’c zditequ hema. Hruft Unw Rpocero Xajruha ezd ex lle kevq gkemj Kloame Dabo. Xagoqy uj afuge ni irwoik lfef vqimm Oqfuis.

Mmo kadmuwo vamv qofogolz mii ze wpe ulec’y zterixu kebe, njezi naa’xf duo jwo ibguohil idaha:

Where to go from here?

In this chapter, you learned how to deal with files in Vapor. You saw how to handle file uploads and save files to disk. You also learned how to serve files from disk in a route handler.

Hoa’ge huj huiqp a bodtz-ruinupov AXU sxol roteqkycucod tohf ey qhe ranonizudoax uf Biqom. Beu’xa fuust ej uAB icgsatidaug cu zuqjaru dxo OZU, ik todp ac a spatz-azs tuvxiku emuyk Hauj. Foi’be icbu taovvix key qi xetn meiq ardzeneneiw.

Ywufu fabcoexv liho jowev qia ijf qhi fcucpusmi qui ruax lu yeufn nsa cimy aqng unw siq hodet nux hiaf iss awsjozeqaukt! Qhe xozh sporqegy pecut duso exvimbob nirobg xyob que gax jaiq, heww ey ziroducu potxumauqq iwj layvabn. Toe’gn uyhu piujv jih ju mezrez deun etcvadaquup zo rxa ahjigper.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2020 Razeware LLC

You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.