Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
elixir 1.16.0-otp-26
erlang 26.2.1
nodejs 20.10.0
34 changes: 30 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,37 @@
# - https://pkgs.org/ - resource for finding needed packages
# - Ex: hexpm/elixir:1.13.3-erlang-24.2-debian-bullseye-20210902-slim
#
ARG ELIXIR_VERSION=1.15.7
ARG OTP_VERSION=26.2
ARG ELIXIR_VERSION=1.16.0
ARG OTP_VERSION=26.2.1
ARG DEBIAN_VERSION=buster-20231009-slim

ARG RUST_IMAGE="rust:slim-buster"
ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}"

FROM ${RUST_IMAGE} as rust

FROM ${BUILDER_IMAGE} as builder

# install build dependencies
RUN apt-get update -y && apt-get install -y build-essential git \
RUN apt-get update -y && apt-get install -y build-essential git
# && apt-get clean && rm -f /var/lib/apt/lists/*_*

# rust shenanigans
ENV RUSTUP_HOME=/usr/local/rustup \
CARGO_HOME=/usr/local/cargo \
PATH=/usr/local/cargo/bin:$PATH
COPY --from=rust /usr/local/cargo /usr/local/cargo
COPY --from=rust /usr/local/rustup /usr/local/rustup

# rustler/python3 dependencies
RUN apt-get update -y && apt-get install -y expat libxml2-dev pkg-config \
libfontconfig1-dev libasound2-dev libssl-dev cmake libfreetype6-dev \
libexpat1-dev libxcb-composite0-dev libharfbuzz-dev
# && apt-get clean && rm -f /var/lib/apt/lists/*_*

# install python3 for rustler
RUN apt-get update -y && apt-get install -y libssl-dev libffi-dev python3-dev \
&& apt-get clean && rm -f /var/lib/apt/lists/*_*

# prepare build dir
Expand Down Expand Up @@ -65,7 +85,13 @@ RUN mix release
# the compiled release and other runtime necessities
FROM ${RUNNER_IMAGE}

RUN apt-get update -y && apt-get install -y libstdc++6 openssl libncurses5 locales \
RUN apt-get update -y && apt-get install -y libstdc++6 openssl libncurses5 locales
# && apt-get clean && rm -f /var/lib/apt/lists/*_*

# rustler stuff needed in runner image
RUN apt-get update -y && apt-get install -y expat libxml2-dev pkg-config \
libfontconfig1-dev libasound2-dev libssl-dev cmake libfreetype6-dev \
libexpat1-dev libxcb-composite0-dev libharfbuzz-dev fonts-firacode \
&& apt-get clean && rm -f /var/lib/apt/lists/*_*

# Set the locale
Expand Down
8 changes: 8 additions & 0 deletions config/dev.secret.example.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Config

config :ctrlv, Ctrlv.Integrations.AWS.Client,
access_key_id: "",
secret_access_key: "",
region: ""

config :ctrlv, Ctrlv.Integrations.AWS.S3, bucket: ""
33 changes: 7 additions & 26 deletions config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -68,31 +68,12 @@ if config_env() == :prod do
],
secret_key_base: secret_key_base

# ## Using releases
#
# If you are doing OTP releases, you need to instruct Phoenix
# to start each relevant endpoint:
#
# config :ctrlv, CtrlvWeb.Endpoint, server: true
#
# Then you can assemble a release by calling `mix release`.
# See `mix help release` for more information.
## AWS

# ## Configuring the mailer
#
# In production you need to configure the mailer to use a different adapter.
# Also, you may need to configure the Swoosh API client of your choice if you
# are not using SMTP. Here is an example of the configuration:
#
# config :ctrlv, Ctrlv.Mailer,
# adapter: Swoosh.Adapters.Mailgun,
# api_key: System.get_env("MAILGUN_API_KEY"),
# domain: System.get_env("MAILGUN_DOMAIN")
#
# For this example you need include a HTTP client required by Swoosh API client.
# Swoosh supports Hackney and Finch out of the box:
#
# config :swoosh, :api_client, Swoosh.ApiClient.Hackney
#
# See https://hexdocs.pm/swoosh/Swoosh.html#module-installation for details.
config :ctrlv, Ctrlv.Integrations.AWS.Client,
access_key_id: System.fetch_env!("AWS_ACCESS_KEY_ID"),
secret_access_key: System.fetch_env!("AWS_SECRET_ACCESS_KEY"),
region: System.get_env("AWS_REGION", "us-east-1")

config :ctrlv, Ctrlv.Integrations.AWS.S3, bucket: System.fetch_env!("AWS_BUCKET")
end
1 change: 1 addition & 0 deletions fly.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ kill_timeout = 5
http_checks = []
internal_port = 4000
protocol = "tcp"
min_machines_running = 1

[services.concurrency]
hard_limit = 25
Expand Down
3 changes: 2 additions & 1 deletion lib/ctrlv/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ defmodule Ctrlv.Application do
# Start the PubSub system
{Phoenix.PubSub, name: Ctrlv.PubSub},
# Start the Endpoint (http/https)
CtrlvWeb.Endpoint
CtrlvWeb.Endpoint,
{Finch, name: Ctrlv.Finch}
# Start a worker by calling: Ctrlv.Worker.start_link(arg)
# {Ctrlv.Worker, arg}
| schedules()
Expand Down
58 changes: 58 additions & 0 deletions lib/ctrlv/code_image.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
defmodule Ctrlv.CodeImage do
@moduledoc """
Images from code. These are used for `og:image` tags.
"""

alias Ctrlv.Integrations.AWS.S3
alias Ctrlv.Pastes.Paste

@doc """
Generate the image from a `Paste` struct.
"""
@spec from_paste(Paste.t()) :: binary()
def from_paste(%Paste{content: code, language: language}) do
generate(code, to_string(language))
end

@doc """
Generate the image from code string.
"""
@spec generate(String.t(), String.t()) :: binary()
def generate(code, language) do
Silicon.Native.format_png(
code,
%Silicon.Options.Format{
lang: language,
theme: "TwoDark",
image_options: %Silicon.Options.Image{
font: [
{"Fira Code", 26.0},
{"Hack", 26.0}
],
line_pad: 2,
line_number: true,
window_controls: true,
line_offset: 1
}
}
)
end

@doc """
Upload the code image to S3.
"""
@spec upload(String.t(), binary()) :: {:ok, path :: String.t()} | {:error, term()}
def upload(filename, image) do
"ctrlv-images"
|> Path.join(filename)
|> S3.upload_object("image/png", image)
end

@doc """
Get the URL for an image by it's path.
"""
@spec url(String.t()) :: String.t()
def url(path) do
S3.get_object_url(path)
end
end
21 changes: 21 additions & 0 deletions lib/ctrlv/integrations/aws/client.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
defmodule Ctrlv.Integrations.AWS.Client do
@moduledoc """
AWS Client integration.
"""

@doc """
Create a new AWS client.
"""
def new do
config = config!()

AWS.Client.create(
config.access_key_id,
config.secret_access_key,
config.region
)
|> AWS.Client.put_http_client({AWS.HTTPClient.Finch, finch_name: Ctrlv.Finch})
end

defp config!, do: Application.fetch_env!(:ctrlv, __MODULE__) |> Map.new()
end
49 changes: 49 additions & 0 deletions lib/ctrlv/integrations/aws/s3.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
defmodule Ctrlv.Integrations.AWS.S3 do
@moduledoc """
AWS S3 API integrations.
"""

alias Ctrlv.Integrations.AWS.Client

require Logger

@doc """
Upload an object to S3.
"""
def upload_object(filename, content_type, content) do
client = Client.new()
bucket = config!(:bucket)

body = %{
"Body" => content,
"ContentMD5" => :crypto.hash(:md5, content) |> Base.encode64(),
"ContentType" => content_type
}

case AWS.S3.put_object(client, bucket, filename, body, _options = []) do
{:ok, _result, _resp} ->
{:ok, filename}

{:error, {:unexpected_response, resp}} ->
Logger.error("Bad upload: #{inspect(resp)}")
{:error, resp}

{:error, reason} ->
Logger.error("Error uploading: #{inspect(reason)}")
{:error, reason}
end
end

@doc """
Get the URL of an S3 object from the path (and config).
"""
def get_object_url(path) do
bucket = config!(:bucket)
region = Application.fetch_env!(:ctrlv, Client) |> Keyword.fetch!(:region)
"https://#{bucket}.s3.#{region}.amazonaws.com/#{path}"
end

defp config!(key) do
Application.fetch_env!(:ctrlv, __MODULE__) |> Keyword.fetch!(key)
end
end
11 changes: 9 additions & 2 deletions lib/ctrlv/pastes.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ defmodule Ctrlv.Pastes do

alias Ecto.Changeset

alias Ctrlv.CodeImage
alias Ctrlv.Pastes.Paste
alias Ctrlv.Repo

Expand Down Expand Up @@ -90,13 +91,19 @@ defmodule Ctrlv.Pastes do

"""
def create_paste(%Changeset{} = changeset) do
Repo.insert(changeset)
Repo.transact(fn ->
with {:ok, paste} <- Repo.insert(changeset),
image <- CodeImage.from_paste(paste),
{:ok, path} <- CodeImage.upload(paste.puid <> ".png", image) do
update_paste(paste, %{image_path: path})
end
end)
end

def create_paste(attrs) do
%Paste{}
|> Paste.changeset(attrs)
|> Repo.insert()
|> create_paste()
end

@doc """
Expand Down
Loading