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:
commit
83b9879145
36 changed files with 354 additions and 173 deletions
|
@ -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"
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
defmodule Legendary.Admin.Telemetry do
|
||||
@moduledoc """
|
||||
Collect metrics from the admin app.
|
||||
"""
|
||||
|
||||
use Supervisor
|
||||
import Telemetry.Metrics
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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" />)},
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
{
|
||||
"presets": [
|
||||
"@babel/preset-env"
|
||||
]
|
||||
"presets": ["@babel/preset-env"]
|
||||
}
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
node_modules
|
||||
css/phoenix.css
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 };
|
||||
|
|
19
apps/app/assets/package-lock.json
generated
19
apps/app/assets/package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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")(),
|
||||
],
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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: [],
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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"
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
defmodule AppWeb.Telemetry do
|
||||
@moduledoc """
|
||||
Collect metrics on your app.
|
||||
"""
|
||||
|
||||
use Supervisor
|
||||
import Telemetry.Metrics
|
||||
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
defmodule Legendary.Content.CommentAdmin do
|
||||
@moduledoc """
|
||||
Custom admin logic for blog post comments.
|
||||
"""
|
||||
|
||||
def index(_) do
|
||||
[
|
||||
id: nil,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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"},
|
||||
]
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
13
lefthook.yml
Normal 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}
|
2
mix.lock
2
mix.lock
|
@ -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"},
|
||||
|
|
Loading…
Reference in a new issue