Merge branch 'upgrade-credo' into 'master'

feat: Add linters to CI/CD config

See merge request mythic-insight/legendary!92
This commit is contained in:
Robert Prehn 2021-07-20 22:51:27 +00:00
commit 83b9879145
36 changed files with 354 additions and 173 deletions

View file

@ -38,6 +38,36 @@ test_1.12.1:
<<: *test_template
image: "elixir:1.12.1-alpine"
credo:
stage: test
image: "elixir:1.12.1-alpine"
cache:
key: "test_1.12.1"
paths:
- _build/
- deps/
script:
- mix local.hex --force
- mix local.rebar --force
- mix deps.get
- mix credo --all
stylelint:
stage: test
image: "node:16"
script:
- cd apps/app/assets/
- npm install
- npx stylelint **/*.css
prettier:
stage: test
image: "node:16"
script:
- cd apps/app/assets/
- npm install
- npx prettier --check **/*.js
build_image_for_commit:
stage: test
image: "docker:20.10"

View file

@ -1,4 +1,9 @@
defmodule Legendary.Admin.Routes do
@moduledoc """
Routes from the admin app. Use like:
use Legendary.Admin.Routes
"""
defmacro __using__(_opts \\ []) do
quote do
use Kaffy.Routes, scope: "/admin", pipe_through: [:require_admin]

View file

@ -1,4 +1,8 @@
defmodule Legendary.Admin.Telemetry do
@moduledoc """
Collect metrics from the admin app.
"""
use Supervisor
import Telemetry.Metrics

View file

@ -1,4 +1,8 @@
defmodule Legendary.Admin.Kaffy.Config do
@moduledoc """
Pull in the resource list for the admin from the application config.
"""
def create_resources(_conn) do
config = Application.get_env(:admin, Legendary.Admin)

View file

@ -1,4 +1,9 @@
defmodule Legendary.Admin.Kaffy.EditorExtension do
@moduledoc """
Bring in additional CSS and JS for the admin interface e.g. the
markdown editor library.
"""
def stylesheets(_conn) do
[
{:safe, ~s(<link rel="stylesheet" href="/css/content-editor.css" />)},

View file

@ -1,5 +1,3 @@
{
"presets": [
"@babel/preset-env"
]
"presets": ["@babel/preset-env"]
}

View file

@ -1 +1,2 @@
node_modules
css/phoenix.css

View file

@ -1,11 +1,7 @@
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";
@import "@fortawesome/fontawesome-free/css/all.css";
@import "blog";
@import "code";
@ -14,27 +10,28 @@ body {
}
input[type="checkbox"]::after {
content: "";
color: currentColor;
display: inline-flex;
justify-content: center;
width: 100%;
height: 100%;
display: inline-flex;
color: currentColor;
align-items: center;
justify-content: center;
content: "";
}
input[type="checkbox"]:checked::after {
content: "✓";
}
.hidden-options-toggle:checked+.hidden {
.hidden-options-toggle:checked + .hidden {
display: block;
}
.password-revealer {
@apply absolute;
height: 45px;
width: 45px;
right: 0;
bottom: 0;
width: 45px;
height: 45px;
}

View file

@ -57,7 +57,7 @@
.hljs {
@apply mt-6 mb-12 border-l-4 border-indigo-900 font-mono;
--background-color: theme("colors.indigo.100");
--background-color: theme("colors.indigo.100"); /* stylelint-disable-line custom-property-empty-line-before */
--attribute-color: theme("colors.orange.700");
--section-color: theme("colors.teal.700");
--string-color: theme("colors.green.700");

View file

@ -1,7 +1,7 @@
// We need to import the CSS so that webpack will load it.
// The MiniCssExtractPlugin is used to separate it out into
// its own CSS file.
import "../css/app.css"
import "../css/app.css";
// webpack automatically bundles all modules in your
// entry points. Those entry points can be configured
@ -12,48 +12,46 @@ import "../css/app.css"
// import {Socket} from "phoenix"
// import socket from "./socket"
//
import "phoenix_html"
import "alpinejs"
import "./live"
import { ready } from "./utils"
import "phoenix_html";
import "alpinejs";
import "./live";
import { ready } from "./utils";
function togglePasswordFieldVisibility()
{
const passwordFields = document.querySelectorAll('[name="user[password]"]')
var bloop;
function togglePasswordFieldVisibility() {
const passwordFields = document.querySelectorAll('[name="user[password]"]');
passwordFields.forEach((el) => {
if (el.type == 'password')
{
el.type = 'text'
if (el.type == "password") {
el.type = "text";
} else {
el.type = "password";
}
else
{
el.type = 'password'
}
})
});
}
const toggleSidebar = (event) => {
document.querySelectorAll('.sidebar').forEach((el) => {
el.classList.toggle('visible')
})
}
document.querySelectorAll(".sidebar").forEach((el) => {
el.classList.toggle("visible");
});
};
ready(() => {
(document.getElementById('nav-toggle') ||{}).onclick = function(){
(document.getElementById("nav-toggle") || {}).onclick = function () {
document.getElementById("nav-content").classList.toggle("hidden");
}
};
document.querySelectorAll('.js-passwordRevealer').forEach((el) => {
el.addEventListener('click', togglePasswordFieldVisibility)
})
document.querySelectorAll(".js-passwordRevealer").forEach((el) => {
el.addEventListener("click", togglePasswordFieldVisibility);
});
document.querySelectorAll('.js-SidebarOpener').forEach((el) => {
el.addEventListener('click', toggleSidebar)
})
document.querySelectorAll(".js-SidebarOpener").forEach((el) => {
el.addEventListener("click", toggleSidebar);
});
document.querySelectorAll('.js-flash-closer').forEach((el) => {
el.addEventListener('click', () => {
el.closest('.js-flash').remove()
})
})
})
document.querySelectorAll(".js-flash-closer").forEach((el) => {
el.addEventListener("click", () => {
el.closest(".js-flash").remove();
});
});
});

View file

@ -1,34 +1,33 @@
import { ready } from "./utils"
import SimpleMDE from "simplemde"
import "simplemde/dist/simplemde.min.css"
import "../css/content-editor-overrides.css"
import { ready } from "./utils";
import SimpleMDE from "simplemde";
import "simplemde/dist/simplemde.min.css";
import "../css/content-editor-overrides.css";
const requestPreview = (plainText, previewContainer) => {
let request = new XMLHttpRequest()
const postForm = previewContainer.closest('form')
let formData = new FormData(postForm)
let request = new XMLHttpRequest();
const postForm = previewContainer.closest("form");
let formData = new FormData(postForm);
formData.set('post[content]', plainText)
formData.set("post[content]", plainText);
request.addEventListener('load', function(event) {
previewContainer.innerHTML = event.target.responseText
})
request.addEventListener("load", function (event) {
previewContainer.innerHTML = event.target.responseText;
});
request.open('POST', '/pages/posts/preview', true)
request.open("POST", "/pages/posts/preview", true);
request.send(formData)
}
request.send(formData);
};
ready(() => {
document.querySelectorAll('[data-simplemde]').forEach(el => {
document.querySelectorAll("[data-simplemde]").forEach((el) => {
new SimpleMDE({
element: el,
previewRender: (plainText, previewContainer) => {
requestPreview(plainText, previewContainer)
requestPreview(plainText, previewContainer);
return previewContainer.innerHTML
return previewContainer.innerHTML;
},
})
})
})
});
});
});

View file

@ -1,18 +1,25 @@
import {Socket} from "phoenix"
import LiveSocket from "phoenix_live_view"
import topbar from "topbar"
import { Socket } from "phoenix";
import LiveSocket from "phoenix_live_view";
import topbar from "topbar";
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let csrfToken = document
.querySelector("meta[name='csrf-token']")
.getAttribute("content");
let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}})
let liveSocket = new LiveSocket("/live", Socket, {
params: { _csrf_token: csrfToken },
});
// Show progress bar on live navigation and form submits
topbar.config({barColors: {0: "#3B82F6"}, shadowColor: "rgba(0, 0, 0, .3)"})
window.addEventListener("phx:page-loading-start", info => topbar.show())
window.addEventListener("phx:page-loading-stop", info => topbar.hide())
topbar.config({
barColors: { 0: "#3B82F6" },
shadowColor: "rgba(0, 0, 0, .3)",
});
window.addEventListener("phx:page-loading-start", (info) => topbar.show());
window.addEventListener("phx:page-loading-stop", (info) => topbar.hide());
// Connect if there are any LiveViews on the page
liveSocket.connect()
liveSocket.connect();
// Expose liveSocket on window for web console debug logs and latency simulation:
// >> liveSocket.enableDebug()
@ -20,4 +27,4 @@ liveSocket.connect()
// The latency simulator is enabled for the duration of the browser session.
// Call disableLatencySim() to disable:
// >> liveSocket.disableLatencySim()
window.liveSocket = liveSocket
window.liveSocket = liveSocket;

View file

@ -6,9 +6,9 @@
//
// Pass the token on params as below. Or remove it
// from the params if you are not using authentication.
import {Socket} from "phoenix"
import { Socket } from "phoenix";
let socket = new Socket("/socket", {params: {token: window.userToken}})
let socket = new Socket("/socket", { params: { token: window.userToken } });
// When you connect, you'll often need to authenticate the client.
// For example, imagine you have an authentication plug, `MyAuth`,
@ -52,12 +52,17 @@ let socket = new Socket("/socket", {params: {token: window.userToken}})
// end
//
// Finally, connect to the socket:
socket.connect()
socket.connect();
// Now that you are connected, you can join channels with a topic:
let channel = socket.channel("topic:subtopic", {})
channel.join()
.receive("ok", resp => { console.log("Joined successfully", resp) })
.receive("error", resp => { console.log("Unable to join", resp) })
let channel = socket.channel("topic:subtopic", {});
channel
.join()
.receive("ok", (resp) => {
console.log("Joined successfully", resp);
})
.receive("error", (resp) => {
console.log("Unable to join", resp);
});
export default socket
export default socket;

View file

@ -1,10 +1,9 @@
const ready = (fn) => {
if (document.readyState != 'loading') {
fn()
if (document.readyState != "loading") {
fn();
} else {
document.addEventListener('DOMContentLoaded', fn)
document.addEventListener("DOMContentLoaded", fn);
}
}
};
export { ready }
export { ready };

View file

@ -34,6 +34,7 @@
"postcss-css-variables": "^0.17.0",
"postcss-import": "^12.0.1",
"postcss-loader": "^6.1.0",
"prettier": "2.3.2",
"sass": "^1.35.1",
"sass-loader": "^8.0.2",
"style-loader": "^1.2.1",
@ -9749,6 +9750,18 @@
"node": ">=0.10.0"
}
},
"node_modules/prettier": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz",
"integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/pretty-hrtime": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz",
@ -20384,6 +20397,12 @@
"dev": true,
"optional": true
},
"prettier": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz",
"integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==",
"dev": true
},
"pretty-hrtime": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz",

View file

@ -36,6 +36,7 @@
"postcss-css-variables": "^0.17.0",
"postcss-import": "^12.0.1",
"postcss-loader": "^6.1.0",
"prettier": "2.3.2",
"sass": "^1.35.1",
"sass-loader": "^8.0.2",
"style-loader": "^1.2.1",

View file

@ -1,13 +1,11 @@
module.exports = {
plugins: [
require('postcss-import')({
plugins: [
require('stylelint')(),
]
require("postcss-import")({
plugins: [require("stylelint")()],
}),
require('tailwindcss'),
require('autoprefixer'),
require('csswring')(),
require('postcss-color-function')()
require("tailwindcss"),
require("autoprefixer"),
require("csswring")(),
require("postcss-color-function")(),
],
}
};

View file

@ -1,7 +1,7 @@
const yargsParser = require('yargs-parser');
const yargsParser = require("yargs-parser");
const cliArgs = yargsParser(process.argv);
const mode = process.env.NODE_ENV || cliArgs.mode || 'development';
const mode = process.env.NODE_ENV || cliArgs.mode || "development";
module.exports = {
future: {
@ -9,21 +9,21 @@ module.exports = {
purgeLayersByDefault: true,
},
purge: {
enabled: mode == 'production',
layers: ['base', 'components', 'utilities'],
enabled: mode == "production",
layers: ["base", "components", "utilities"],
content: [
'../../../**/views/*.ex',
'../../../**/*.html.eex',
'../../../**/*.html.leex',
'../../../**/*.html.heex',
'./js/**/*.js'
]
"../../../**/views/*.ex",
"../../../**/*.html.eex",
"../../../**/*.html.leex",
"../../../**/*.html.heex",
"./js/**/*.js",
],
},
theme: {
extend: {},
},
variants: {
backgroundColor: ['responsive', 'hover', 'focus', 'checked'],
backgroundColor: ["responsive", "hover", "focus", "checked"],
},
plugins: [],
}
};

View file

@ -1,31 +1,31 @@
const path = require('path');
const glob = require('glob');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const path = require("path");
const glob = require("glob");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const nodeModulesPath = path.resolve(__dirname, 'node_modules')
const nodeModulesPath = path.resolve(__dirname, "node_modules");
module.exports = (env, options) => {
const devMode = options.mode !== 'production';
const devMode = options.mode !== "production";
return {
optimization: {
minimizer: [
new TerserPlugin({ cache: true, parallel: true, sourceMap: devMode }),
new CssMinimizerPlugin(),
]
],
},
mode: options.mode,
devtool: devMode ? 'source-map' : undefined,
devtool: devMode ? "source-map" : undefined,
entry: {
'app': glob.sync('./vendor/**/*.js').concat(['./js/app.js']),
'content-editor': ['./js/content-editor.js'],
app: glob.sync("./vendor/**/*.js").concat(["./js/app.js"]),
"content-editor": ["./js/content-editor.js"],
},
output: {
filename: 'js/[name].js',
path: path.resolve(__dirname, '../priv/static/')
filename: "js/[name].js",
path: path.resolve(__dirname, "../priv/static/"),
},
module: {
rules: [
@ -33,9 +33,9 @@ module.exports = (env, options) => {
{
test: /\.(jpg|jpeg|gif|png)$/,
use: [
'file-loader',
"file-loader",
{
loader: 'image-webpack-loader',
loader: "image-webpack-loader",
options: {
disable: devMode,
},
@ -44,61 +44,108 @@ module.exports = (env, options) => {
},
{
test: /\.(woff2?|ttf|eot|svg)(\?[a-z0-9\=\.]+)?$/,
loader: 'file-loader',
loader: "file-loader",
options: {
publicPath: '/fonts',
publicPath: "/fonts",
outputPath: (url, resourcePath, context) => {
return `/fonts/${url}`;
},
}
},
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
loader: "babel-loader",
},
},
{
test: /\.css$/,
use: [
{loader: MiniCssExtractPlugin.loader},
{loader: 'css-loader', options: {sourceMap: true}},
{loader: 'postcss-loader', options: {sourceMap: true}},
{ loader: MiniCssExtractPlugin.loader },
{ loader: "css-loader", options: { sourceMap: true } },
{ loader: "postcss-loader", options: { sourceMap: true } },
],
},
]
],
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].css',
chunkFilename: '[id].css',
filename: "css/[name].css",
chunkFilename: "[id].css",
}),
new CopyWebpackPlugin({
patterns: [
{
from: path.resolve(__dirname, "static"),
to: path.resolve(__dirname, "../priv/static"),
},
],
}),
new CopyWebpackPlugin({patterns: [
{
from: path.resolve(__dirname, 'static'),
to: path.resolve(__dirname, '../priv/static'),
},
]}),
],
resolve: {
alias: {
"../webfonts/fa-brands-400.eot": path.resolve(__dirname, "node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.eot"),
"../webfonts/fa-brands-400.woff2": path.resolve(__dirname, "node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff2"),
"../webfonts/fa-brands-400.woff": path.resolve(__dirname, "node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff"),
"../webfonts/fa-brands-400.ttf": path.resolve(__dirname, "node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.ttf"),
"../webfonts/fa-brands-400.svg": path.resolve(__dirname, "node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.svg"),
"../webfonts/fa-regular-400.eot": path.resolve(__dirname, "node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.eot"),
"../webfonts/fa-regular-400.woff2": path.resolve(__dirname, "node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.woff2"),
"../webfonts/fa-regular-400.woff": path.resolve(__dirname, "node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.woff"),
"../webfonts/fa-regular-400.ttf": path.resolve(__dirname, "node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.ttf"),
"../webfonts/fa-regular-400.svg": path.resolve(__dirname, "node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.svg"),
"../webfonts/fa-solid-900.eot": path.resolve(__dirname, "node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.eot"),
"../webfonts/fa-solid-900.woff2": path.resolve(__dirname, "node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff2"),
"../webfonts/fa-solid-900.woff": path.resolve(__dirname, "node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff"),
"../webfonts/fa-solid-900.ttf": path.resolve(__dirname, "node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.ttf"),
"../webfonts/fa-solid-900.svg": path.resolve(__dirname, "node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.svg"),
}
"../webfonts/fa-brands-400.eot": path.resolve(
__dirname,
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.eot"
),
"../webfonts/fa-brands-400.woff2": path.resolve(
__dirname,
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff2"
),
"../webfonts/fa-brands-400.woff": path.resolve(
__dirname,
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff"
),
"../webfonts/fa-brands-400.ttf": path.resolve(
__dirname,
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.ttf"
),
"../webfonts/fa-brands-400.svg": path.resolve(
__dirname,
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.svg"
),
"../webfonts/fa-regular-400.eot": path.resolve(
__dirname,
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.eot"
),
"../webfonts/fa-regular-400.woff2": path.resolve(
__dirname,
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.woff2"
),
"../webfonts/fa-regular-400.woff": path.resolve(
__dirname,
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.woff"
),
"../webfonts/fa-regular-400.ttf": path.resolve(
__dirname,
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.ttf"
),
"../webfonts/fa-regular-400.svg": path.resolve(
__dirname,
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.svg"
),
"../webfonts/fa-solid-900.eot": path.resolve(
__dirname,
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.eot"
),
"../webfonts/fa-solid-900.woff2": path.resolve(
__dirname,
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff2"
),
"../webfonts/fa-solid-900.woff": path.resolve(
__dirname,
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff"
),
"../webfonts/fa-solid-900.ttf": path.resolve(
__dirname,
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.ttf"
),
"../webfonts/fa-solid-900.svg": path.resolve(
__dirname,
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.svg"
),
},
},
}
};
};

View file

@ -1,4 +1,8 @@
defmodule AppWeb.Telemetry do
@moduledoc """
Collect metrics on your app.
"""
use Supervisor
import Telemetry.Metrics

View file

@ -1,4 +1,8 @@
defmodule Legendary.Content.CommentAdmin do
@moduledoc """
Custom admin logic for blog post comments.
"""
def index(_) do
[
id: nil,

View file

@ -1,4 +1,8 @@
defmodule Legendary.Content.MarkupField do
@moduledoc """
Custom field type definition for markdown fields. Currently uses simplemde
to provide a markdown editing GUI.
"""
use Ecto.Type
def type, do: :string

View file

@ -1,4 +1,8 @@
defmodule Legendary.Content.PostAdmin do
@moduledoc """
Custom admin logic for content posts and pages.
"""
import Ecto.Query, only: [from: 2]
def singular_name(_) do

View file

@ -87,11 +87,12 @@ defmodule Legendary.Content.PostsController do
end
# The static page we're looking for is missing, so this is just a 404
# credo:disable-for-next-line
raise Phoenix.Router.NoRouteError.exception(conn: conn, router: router)
_ ->
# We aren't missing the static page, we're missing a partial. This is probably
# a developer error, so bubble it up
raise e
reraise e, System.stacktrace
end
end
end

View file

@ -1,4 +1,8 @@
defmodule Legendary.Content.Routes do
@moduledoc """
Routes for the content engine, including blog posts, feeds, and pages.
"""
defmacro __using__(_opts \\ []) do
quote do
pipeline :feed do

View file

@ -52,7 +52,7 @@ defmodule Legendary.Content.PostsTest do
test "delete_posts/1", %{public_post: post} do
assert Enum.count(Posts.list_posts()) == 1
assert {:ok, _} = Posts.delete_posts(post)
assert Enum.count(Posts.list_posts()) == 0
assert Enum.empty?(Posts.list_posts())
end
test "change_posts/1", %{public_post: post} do

View file

@ -1,4 +1,9 @@
defmodule Legendary.Auth.MnesiaClusterSupervisor do
@moduledoc """
Manages the cache in Mnesia for Pow. This allows users to remain logged in
even if their traffic is hitting different nodes in the cluster.
"""
use Supervisor
def start_link(init_arg) do

View file

@ -1,7 +1,12 @@
defmodule Legendary.Auth.Roles do
@moduledoc """
Functions for working with roles on users, such as testing whether a user has
a role.
"""
def has_role?(userlike, role) when is_atom(role), do: has_role?(userlike, Atom.to_string(role))
def has_role?(nil, _), do: false
def has_role?(user = %Legendary.Auth.User{}, role) do
def has_role?(%Legendary.Auth.User{} = user, role) do
Enum.any?(user.roles || [], & &1 == role)
end
end

View file

@ -40,7 +40,7 @@ defmodule Legendary.Auth.User do
|> pow_extension_changeset(attrs)
end
def reset_password_changeset(user = %Legendary.Auth.User{}, params) do
def reset_password_changeset(%Legendary.Auth.User{} = user, params) do
user
|> new_password_changeset(params, @pow_config)
|> Changeset.validate_required([:password])

View file

@ -1,9 +1,15 @@
defmodule Legendary.Auth.UserAdmin do
@moduledoc """
Custom admin login for user records.
"""
import Ecto.Query, only: [from: 2]
alias Legendary.Auth.User
alias Legendary.Core.Repo
def custom_links(_schema) do
# We add the funwithflags admin URL under this custom admin because kaffy
# doesn't have global custom links that work in this way and user is the
# closest fit.
[
%{name: "Feature Flags", url: "/admin/feature-flags", order: 2, location: :top, icon: "flag"},
]

View file

@ -1,6 +1,10 @@
defmodule Legendary.AuthWeb.Helpers do
def current_user(socket = %Phoenix.LiveView.Socket{assigns: %{current_user: user}}), do: user
def current_user(socket = %Phoenix.LiveView.Socket{assigns: %{__assigns__: %{current_user: user}}}), do: user
@moduledoc """
Utility functions for working with users and roles.
"""
def current_user(%Phoenix.LiveView.Socket{assigns: %{current_user: user}}), do: user
def current_user(%Phoenix.LiveView.Socket{assigns: %{__assigns__: %{current_user: user}}}), do: user
def current_user(%Phoenix.LiveView.Socket{}), do: nil
def current_user(conn), do: Pow.Plug.current_user(conn)

View file

@ -7,7 +7,7 @@ defmodule Legendary.Core.MapUtils do
Map.merge(base, override, &deep_value/3)
end
defp deep_value(_key, base = %{}, override = %{}) do
defp deep_value(_key, %{} = base, %{} = override) do
deep_merge(base, override)
end

View file

@ -1,4 +1,10 @@
defmodule Legendary.Core.Routes do
@moduledoc """
Router module that brings in core framework routes, such as the feature flag
admin interface. Can be included like:
use Legendary.Core.Routes
"""
defmacro __using__(_opts \\ []) do
quote do
scope path: "/admin/feature-flags" do

View file

@ -1,4 +1,8 @@
defmodule Mix.Legendary do
@moduledoc """
Parent module for all Legendary framework mix tasks. Provides some helpers
used by tasks and generators.
"""
alias Mix.Phoenix.{Schema}
@doc false

13
lefthook.yml Normal file
View file

@ -0,0 +1,13 @@
pre-commit:
parallel: true
commands:
credo:
runner: mix credo diff master
stylelint:
root: "apps/app/assets/"
glob: "*.{css}"
runner: npx stylelint {staged_files}
prettier:
root: "apps/app/assets/"
glob: "*.{js}"
runner: npx prettier --check {staged_files}

View file

@ -11,7 +11,7 @@
"cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"},
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.1", "ebd1a1d7aff97f27c66654e78ece187abdc646992714164380d8a041eda16754", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a6efd3366130eab84ca372cbd4a7d3c3a97bdfcfb4911233b035d117063f0af"},
"cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"},
"credo": {:hex, :credo, "1.4.0", "92339d4cbadd1e88b5ee43d427b639b68a11071b6f73854e33638e30a0ea11f5", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1fd3b70dce216574ce3c18bdf510b57e7c4c85c2ec9cad4bff854abaf7e58658"},
"credo": {:hex, :credo, "1.5.6", "e04cc0fdc236fefbb578e0c04bd01a471081616e741d386909e527ac146016c6", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "4b52a3e558bd64e30de62a648518a5ea2b6e3e5d2b164ef5296244753fc7eb17"},
"crontab": {:hex, :crontab, "1.1.10", "dc9bb1f4299138d47bce38341f5dcbee0aa6c205e864fba7bc847f3b5cb48241", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "1347d889d1a0eda997990876b4894359e34bfbbd688acbb0ba28a2795ca40685"},
"db_connection": {:hex, :db_connection, "2.2.2", "3bbca41b199e1598245b716248964926303b5d4609ff065125ce98bcd368939e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "642af240d8a8affb93b4ba5a6fcd2bbcbdc327e1a524b825d383711536f8070c"},
"decimal": {:hex, :decimal, "1.9.0", "83e8daf59631d632b171faabafb4a9f4242c514b0a06ba3df493951c08f64d07", [:mix], [], "hexpm", "b1f2343568eed6928f3e751cf2dffde95bfaa19dd95d09e8a9ea92ccfd6f7d85"},