GamendWeb.Uploads (gamend_web v1.0.1216)

Copy Markdown View Source

The two-step upload every icon and avatar endpoint shares.

Bytes never pass through the app server. The client asks for a ticket, PUTs the file straight to storage, then tells the server which key it wrote:

POST .../icon/upload_url   -> %{url: ..., key: ..., ...}
PUT  <ticket.url>          (client -> storage, direct)
POST .../icon              %{"key" => key}

Why there is no single POST /uploads

Authorization is a property of the target, not of uploading: only you may set your avatar, only a group admin may set that group's icon, only an admin may set a tournament's. A generic endpoint would have to take the target as a parameter and re-derive the same checks, so the route stays per-entity and only the mechanism is shared — that is what this module is.

confirm/5 is the load-bearing half, and on S3 it is the only half. A presigned PUT goes straight to the bucket, so none of the app-server checks run against it: the key (and therefore the extension) is server-chosen, but the bytes and their size are not, and ExAws does not sign the content type. Confirm is where an object becomes reachable from a row, so that is where it has to be proved: right prefix, within the size cap, and actually an image.

Summary

Functions

Validates a client-supplied key against prefix/owner_id/ and confirms the object exists, then calls fun with its public URL to persist.

The content type a client declared, as a string.

Issues a presigned upload ticket for prefix/owner_id/<random><ext>, after checking the declared content type is one the server accepts.

How long an upload ticket stays valid, in seconds.

Salt for the upload token. Public so the receiving controller verifies with the same one.

Functions

confirm(conn, prefix, owner_id, key, fun)

@spec confirm(Plug.Conn.t(), String.t(), String.t(), term(), (String.t() ->
                                                          Plug.Conn.t())) ::
  Plug.Conn.t()

Validates a client-supplied key against prefix/owner_id/ and confirms the object exists, then calls fun with its public URL to persist.

content_type(params)

@spec content_type(map()) :: String.t()

The content type a client declared, as a string.

ticket(conn, prefix, owner_id, stem, content_type)

@spec ticket(Plug.Conn.t(), String.t(), String.t(), String.t(), String.t()) ::
  Plug.Conn.t()

Issues a presigned upload ticket for prefix/owner_id/<random><ext>, after checking the declared content type is one the server accepts.

token_max_age()

How long an upload ticket stays valid, in seconds.

token_salt()

Salt for the upload token. Public so the receiving controller verifies with the same one.