sticky threads

This commit is contained in:
Ben Harris 2018-02-23 15:04:04 -05:00
parent 21f499426b
commit d456e7b161
23 changed files with 216 additions and 61 deletions

View File

@ -1641,11 +1641,6 @@
"resolve-dir": "0.1.1"
}
},
"font-awesome": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz",
"integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM="
},
"for-in": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",

View File

@ -8,7 +8,6 @@
},
"dependencies": {
"bootstrap": "^3.3.7",
"font-awesome": "^4.7.0",
"jquery": "^3.3.1",
"phoenix": "file:../deps/phoenix",
"phoenix_html": "file:../deps/phoenix_html"

View File

@ -15,12 +15,15 @@ use Mix.Config
# which you typically run after static files are built.
config :forum, ForumWeb.Endpoint,
load_from_system_env: true,
url: [host: "forum.tilde.team", port: 4002],
# url: [host: "forum.tilde.team", port: 4002],
http: [port: {:system, "PORT"}],
cache_static_manifest: "priv/static/cache_manifest.json"
# Do not print debug messages in production
config :logger, level: :info
config :phoenix, :serve_endpoints, true
# ## SSL Support
#
# To get SSL working, you will need to add the `https` key

1
domains Normal file
View File

@ -0,0 +1 @@
forum.tilde.team

View File

@ -39,6 +39,15 @@ defmodule Forum do
Repo.get_by!(User, name: name)
end
@doc """
Gets user id by name
"""
def get_user_id_by_name(name) do
from(u in User, where: u.name == ^name, select: [u.id])
|> Repo.one()
|> hd
end
@doc """
Creates a user.

View File

@ -5,6 +5,7 @@ defmodule Forum.Thread do
schema "threads" do
field(:name, :string)
field(:sticky, :boolean)
has_many(:posts, Forum.Post)
belongs_to(:user, Forum.User)
@ -15,7 +16,7 @@ defmodule Forum.Thread do
@doc false
def changeset(%Thread{} = thread, attrs) do
thread
|> cast(attrs, [:name, :user_id])
|> cast(attrs, [:name, :user_id, :sticky])
|> validate_required([:name, :user_id])
|> foreign_key_constraint(:user_id)
end

View File

@ -22,6 +22,9 @@ defmodule Forum.User do
|> unique_constraint(:name)
end
@doc """
Gets contents of tagline.txt for user
"""
def tagline(user) do
resp = get("~#{user.name}/tagline.txt")
@ -36,4 +39,18 @@ defmodule Forum.User do
"something went wrong..."
end
end
@doc """
Gets tilde url for user
"""
def profile_url(user) do
"https://tilde.team/~#{user.name}/"
end
@doc """
Gets path to avatar for user
"""
def pfp(user) do
profile_url(user) <> "avatar.png"
end
end

View File

@ -1,31 +1,43 @@
defmodule Forum.Util do
@moduledoc """
General utilities for the Forum
"""
import Plug.Conn
alias Forum.Repo
alias Forum.User
@doc """
Converts Markdown to HTML
"""
def md(text) do
text
|> Earmark.as_html!()
|> Phoenix.HTML.raw()
end
def get_session_user(conn) do
Repo.get_by(User, name: current_username(conn))
end
@doc """
Gets the logged-in username for a conn
"""
def current_username(conn) do
get_session(conn, :current_user)
end
@doc """
Can current user sudo?
"""
def is_sudoer?(conn) do
get_session(conn, :sudoer)
end
@doc """
"""
def can_edit?(conn, user) do
user.name == current_username(conn) or is_sudoer?(conn)
end
@doc """
"""
def pp_time(time) do
time
|> Timex.local()
@ -33,17 +45,19 @@ defmodule Forum.Util do
|> String.downcase()
end
def pfp(user) do
profile_url(user) <> "avatar.png"
end
def profile_url(user) do
"https://tilde.team/~#{user.name}/"
end
@doc """
"""
def paginate(conn, kerosene) do
if kerosene.total_pages > 1 do
Kerosene.HTML.paginate(conn, kerosene, next_label: ">>", previous_label: "<<")
Kerosene.HTML.paginate(
conn,
kerosene,
next_label: ">>",
previous_label: "<<",
first_label: "first",
last_label: "last"
)
end
end
end

View File

@ -43,6 +43,7 @@ defmodule ForumWeb do
import ForumWeb.ErrorHelpers
import ForumWeb.Gettext
alias Forum.User
alias Forum.Util
end
end

View File

@ -4,20 +4,16 @@ defmodule ForumWeb.PostController do
def create(conn, %{"post" => post_params}) do
case Forum.create_post(post_params) do
{:ok, post} ->
post = Forum.Repo.preload(post, :thread)
conn
|> put_flash(:info, "reply created successfully.")
|> redirect(to: thread_path(conn, :show, post.thread.id))
|> redirect(to: thread_path(conn, :show, post.thread_id))
{:error, %Ecto.Changeset{} = changeset} ->
user = Util.get_session_user(conn)
render(
conn,
"new.html",
changeset: changeset,
user_id: user.id,
user_id: Forum.get_user_id_by_name(Util.current_username(conn)),
thread_id: post_params["thread_id"]
)
end

View File

@ -9,20 +9,39 @@ defmodule ForumWeb.ThreadController do
alias Forum.User
def index(conn, params) do
stickies =
Thread
|> order_by(desc: :updated_at)
|> where(sticky: true)
|> preload(:user)
|> Repo.all()
{threads, kerosene} =
Thread
|> order_by(desc: :id)
|> where(sticky: false)
|> preload(:user)
|> Repo.paginate(params)
tagline = Tesla.get("https://tilde.team/~ben/api/").body
render(conn, "index.html", threads: threads, kerosene: kerosene, tagline: tagline)
render(
conn,
"index.html",
stickies: stickies,
threads: threads,
kerosene: kerosene,
tagline: tagline
)
end
def new(conn, _params) do
changeset = Forum.change_thread(%Thread{})
user = Util.get_session_user(conn)
render(conn, "new.html", changeset: changeset, user_id: user.id)
changeset =
Forum.change_thread(%Thread{
user_id: conn |> Util.current_username() |> Forum.get_user_id_by_name()
})
render(conn, "new.html", changeset: changeset)
end
def create(conn, %{"thread" => thread_params}) do

View File

@ -3,7 +3,7 @@
<div class="panel-body">
<div class="pull-right">
<a href="<%= user_path(@conn, :show, @post.user.name) %>">
<img src="<%= Util.pfp(@post.user) %>" alt="<%= @post.user.name %> avatar" width="96px" height="96px">
<img src="<%= User.pfp(@post.user) %>" alt="<%= @post.user.name %> avatar" width="96px" height="96px">
</a>
</div>

View File

@ -6,16 +6,22 @@
<% IO.inspect @changeset %>
<% end %>
<%= if assigns[:user_id] do
hidden_input f, :user_id, value: assigns[:user_id]
end %>
<%= hidden_input f, :user_id %>
<div class="form-group">
<%= text_input f, :name, placeholder: "my cool new thread", class: "form-control" %>
<%= label f, :name, "thread title" %>
<%= text_input f, :name, placeholder: "my cool new thread", class: "form-control", autofocus: true %>
<%= error_tag f, :name %>
</div>
<%= if Util.is_sudoer?(@conn) do %>
<div class="form-group">
<%= label f, :sticky, "sticky thread?" %>
<%= checkbox f, :sticky %>
</div>
<% end %>
<div class="form-group">
<%= submit "create", class: "btn btn-primary" %>
<%= submit "save", class: "btn btn-primary" %>
</div>
<% end %>

View File

@ -1,23 +1,19 @@
<h1>tilde~forum</h1>
<small><em><%= @tagline %></em></small>
<%= if Enum.any?(@stickies) do %>
<hr>
<h4>sticky threads</h4>
<% end %>
<%= for sticky <- @stickies do %>
<%= render "thread.html", conn: @conn, thread: sticky %>
<% end %>
<hr>
<%= for thread <- @threads do %>
<div class="panel panel-default">
<div class="panel-body">
<h4><a href="<%= thread_path(@conn, :show, thread) %>"><%= thread.name %></a></h4>
</div>
<div class="panel-footer">
<%= if Util.can_edit?(@conn, thread.user) do %>
<div class="pull-right">
<%= link "edit", to: thread_path(@conn, :edit, thread), class: "btn btn-info btn-xs" %>
<%= link "delete", to: thread_path(@conn, :delete, thread), method: :delete, data: [confirm: "are you sure?"], class: "btn btn-danger btn-xs" %>
</div>
<% end %>
by: <a href="<%= user_path(@conn, :show, thread.user.name) %>"><em>~<%= thread.user.name %></em></a> // <small>created <%= thread.inserted_at |> Util.pp_time %></small>
</div>
</div>
<%= render "thread.html", conn: @conn, thread: thread %>
<% end %>

View File

@ -1,4 +1,6 @@
<h2>start new thread</h2>
<div class="page-header">
<h1>start new thread</h1>
</div>
<%= render "form.html", Map.put(assigns, :action, thread_path(@conn, :create)) %>

View File

@ -1,6 +1,6 @@
<%= if Util.can_edit?(@conn, @thread.user) do %>
<div class="pull-right">
<span><%= link "edit thread title", to: thread_path(@conn, :edit, @thread), class: "btn btn-info btn-xs" %></span>
<span><%= link "edit thread", to: thread_path(@conn, :edit, @thread), class: "btn btn-info btn-xs" %></span>
</div>
<% end %>

View File

@ -0,0 +1,15 @@
<div class="panel panel-default">
<div class="panel-body">
<h4><a href="<%= thread_path(@conn, :show, @thread) %>"><%= @thread.name %></a></h4>
</div>
<div class="panel-footer">
<%= if Util.can_edit?(@conn, @thread.user) do %>
<div class="pull-right">
<%= link "edit", to: thread_path(@conn, :edit, @thread), class: "btn btn-info btn-xs" %>
<%= link "delete", to: thread_path(@conn, :delete, @thread), method: :delete, data: [confirm: "are you sure?"], class: "btn btn-danger btn-xs" %>
</div>
<% end %>
by: <a href="<%= user_path(@conn, :show, @thread.user.name) %>"><em>~<%= @thread.user.name %></em></a> // <small>created <%= @thread.inserted_at |> Util.pp_time %></small>
</div>
</div>

View File

@ -1,9 +1,9 @@
<div class="page-header clearfix">
<div class="pull-right">
<img src="<%= Util.pfp(@user) %>" alt="<%= @user.name %> avatar" width="128px" height="128px">
<img src="<%= @user |> User.pfp %>" alt="<%= @user.name %> avatar" width="128px" height="128px">
</div>
<h1>
<a href="<%= Util.profile_url(@user) %>">~<%= @user.name %></a>
<a href="<%= @user |> User.profile_url %>">~<%= @user.name %></a>
</h1>
<%= if @user.sudoer do %>
<span class="label label-info">sudoer</span>

View File

@ -4,7 +4,7 @@ defmodule Forum.Mixfile do
def project do
[
app: :forum,
version: "0.0.1",
version: "0.0.#{committed_at()}",
elixir: "~> 1.4",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: [:phoenix, :gettext] ++ Mix.compilers(),
@ -14,6 +14,11 @@ defmodule Forum.Mixfile do
]
end
@doc "unix timestamp of last commit"
def committed_at() do
System.cmd("git", ~w[log -1 --date=short --pretty=format:%ct]) |> elem(0)
end
# Configuration for the OTP application.
#
# Type `mix help compile.app` for more information.
@ -34,6 +39,7 @@ defmodule Forum.Mixfile do
defp deps do
[
{:cowboy, "~> 1.0"},
{:distillery, "~> 1.5", runtime: false},
{:earmark, ">= 1.2.4"},
{:gettext, "~> 0.11"},
{:kerosene, "~> 0.7.0"},

View File

@ -6,6 +6,7 @@
"cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm"},
"db_connection": {:hex, :db_connection, "1.1.3", "89b30ca1ef0a3b469b1c779579590688561d586694a3ce8792985d4d7e575a61", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
"decimal": {:hex, :decimal, "1.4.1", "ad9e501edf7322f122f7fc151cce7c2a0c9ada96f2b0155b8a09a795c2029770", [:mix], [], "hexpm"},
"distillery": {:hex, :distillery, "1.5.2", "eec18b2d37b55b0bcb670cf2bcf64228ed38ce8b046bb30a9b636a6f5a4c0080", [:mix], [], "hexpm"},
"earmark": {:hex, :earmark, "1.2.4", "99b637c62a4d65a20a9fb674b8cffb8baa771c04605a80c911c4418c69b75439", [:mix], [], "hexpm"},
"ecto": {:hex, :ecto, "2.2.8", "a4463c0928b970f2cee722cd29aaac154e866a15882c5737e0038bbfcf03ec2c", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
"file_system": {:hex, :file_system, "0.2.4", "f0bdda195c0e46e987333e986452ec523aed21d784189144f647c43eaf307064", [:mix], [], "hexpm"},

View File

@ -3,10 +3,10 @@ defmodule Forum.Repo.Migrations.CreateThreads do
def change do
create table(:threads) do
add :name, :string
add(:name, :string)
add(:sticky, :boolean, default: false)
timestamps()
end
end
end

View File

@ -9,3 +9,24 @@
#
# We recommend using the bang functions (`insert!`, `update!`
# and so on) as they will fail if something goes wrong.
Forum.Repo.insert!(%Forum.User{name: "ben", sudoer: true, bio: "dev/sysadmin"})
Forum.Repo.insert!(%Forum.Thread{name: "forum faq", sticky: true, user_id: 1})
for i <- 1..100 do
Forum.Repo.insert!(%Forum.Post{
content: "welcome! lots of posts #{i}",
thread_id: 1,
user_id: 1
})
end
Forum.Repo.insert!(%Forum.Thread{name: "what are you working on?", user_id: 1})
Forum.Repo.insert!(%Forum.Post{
content:
"learning elixir by making this site! check it out on [github](https://github.com/tilde-team/forum/tree/elixir)",
thread_id: 2,
user_id: 1
})

53
rel/config.exs Normal file
View File

@ -0,0 +1,53 @@
# Import all plugins from `rel/plugins`
# They can then be used by adding `plugin MyPlugin` to
# either an environment, or release definition, where
# `MyPlugin` is the name of the plugin module.
Path.join(["rel", "plugins", "*.exs"])
|> Path.wildcard()
|> Enum.map(&Code.eval_file(&1))
use Mix.Releases.Config,
# This sets the default release built by `mix release`
default_release: :default,
# This sets the default environment used by `mix release`
default_environment: Mix.env()
# For a full list of config options for both releases
# and environments, visit https://hexdocs.pm/distillery/configuration.html
# You may define one or more environments in this file,
# an environment's settings will override those of a release
# when building in that environment, this combination of release
# and environment configuration is called a profile
environment :dev do
# If you are running Phoenix, you should make sure that
# server: true is set and the code reloader is disabled,
# even in dev mode.
# It is recommended that you build with MIX_ENV=prod and pass
# the --env flag to Distillery explicitly if you want to use
# dev mode.
set dev_mode: true
set include_erts: false
set cookie: :"syId2a?^I4@,}pMpVTb&YJvp~mfgl3oW3MN(3qVVz[m>u3tz^@$4kv*41XmY.6,0"
end
environment :prod do
set include_erts: true
set include_src: false
set cookie: :"`SKJ$KS/7GL&d.O||qanMD=R&:MZx:Fwd8Fq]qB0pT3Nqy!nG]</(2Xoz!z]sL>7"
end
# You may define one or more releases in this file.
# If you have not set a default release, or selected one
# when running `mix release`, the first release in the file
# will be used by default
release :forum do
set version: current_version(:forum)
set applications: [
:runtime_tools
]
end