Initial app
This commit is contained in:
commit
fcf3eb14d0
84 changed files with 8470 additions and 0 deletions
1
.crystal-version
Normal file
1
.crystal-version
Normal file
|
@ -0,0 +1 @@
|
||||||
|
0.36.1
|
9
.editorconfig
Normal file
9
.editorconfig
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*.cr]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
trim_trailing_whitespace = true
|
110
.github/workflows/ci.yml
vendored
Normal file
110
.github/workflows/ci.yml
vendored
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
name: scribe CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: "*"
|
||||||
|
pull_request:
|
||||||
|
branches: "*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-format:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: crystallang/crystal:1.0.0
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Format
|
||||||
|
run: crystal tool format --check
|
||||||
|
|
||||||
|
specs:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: crystallang/crystal:1.0.0
|
||||||
|
env:
|
||||||
|
LUCKY_ENV: test
|
||||||
|
DB_HOST: postgres
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:12-alpine
|
||||||
|
env:
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
# Set health checks to wait until postgres has started
|
||||||
|
options: >-
|
||||||
|
--health-cmd pg_isready
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install PostgreSQL client
|
||||||
|
run: |
|
||||||
|
apt-get update
|
||||||
|
apt-get -yqq install libpq-dev postgresql-client
|
||||||
|
- name: Install browser
|
||||||
|
run: apt-get -yqq install chromium-browser
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: '12.x'
|
||||||
|
- name: "Install yarn"
|
||||||
|
run: npm install -g yarn
|
||||||
|
|
||||||
|
- name: Get yarn cache directory path
|
||||||
|
id: yarn-cache-dir-path
|
||||||
|
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||||
|
|
||||||
|
- name: Set up Yarn cache
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||||
|
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-yarn-
|
||||||
|
|
||||||
|
- name: Set up Node cache
|
||||||
|
uses: actions/cache@v2
|
||||||
|
id: node-cache # use this to check for `cache-hit` (`steps.node-cache.outputs.cache-hit != 'true'`)
|
||||||
|
with:
|
||||||
|
path: '**/node_modules'
|
||||||
|
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-node-
|
||||||
|
- name: Set up Crystal cache
|
||||||
|
uses: actions/cache@v2
|
||||||
|
id: crystal-cache
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cache/crystal
|
||||||
|
lib
|
||||||
|
lucky_tasks
|
||||||
|
key: ${{ runner.os }}-crystal-${{ hashFiles('**/shard.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-crystal-
|
||||||
|
|
||||||
|
- name: Install shards
|
||||||
|
if: steps.crystal-cache.outputs.cache-hit != 'true'
|
||||||
|
run: shards check || shards install
|
||||||
|
|
||||||
|
- name: Install yarn packages
|
||||||
|
if: steps.node-cache.outputs.cache-hit != 'true'
|
||||||
|
run: yarn install --frozen-lockfile --no-progress
|
||||||
|
- name: Compiling assets
|
||||||
|
run: yarn prod
|
||||||
|
|
||||||
|
- name: Build lucky_tasks
|
||||||
|
if: steps.crystal-cache.outputs.cache-hit != 'true'
|
||||||
|
run: crystal build tasks.cr -o ./lucky_tasks
|
||||||
|
|
||||||
|
- name: Prepare database
|
||||||
|
run: |
|
||||||
|
./lucky_tasks db.create
|
||||||
|
./lucky_tasks db.migrate
|
||||||
|
./lucky_tasks db.seed.required_data
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: crystal spec
|
15
.gitignore
vendored
Normal file
15
.gitignore
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
/docs/
|
||||||
|
/lib/
|
||||||
|
/bin/
|
||||||
|
/.shards/
|
||||||
|
*.dwarf
|
||||||
|
start_server
|
||||||
|
*.dwarf
|
||||||
|
*.local.cr
|
||||||
|
.env
|
||||||
|
/tmp
|
||||||
|
/public/js
|
||||||
|
/public/css
|
||||||
|
/public/mix-manifest.json
|
||||||
|
/node_modules
|
||||||
|
yarn-error.log
|
2
.tool-versions
Normal file
2
.tool-versions
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
nodejs 12.14.1
|
||||||
|
crystal 1.0.0
|
2
Procfile
Normal file
2
Procfile
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
web: bin/scribe
|
||||||
|
release: lucky db.migrate
|
3
Procfile.dev
Normal file
3
Procfile.dev
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
system_check: script/system_check && sleep 100000
|
||||||
|
web: lucky watch --reload-browser
|
||||||
|
assets: yarn watch
|
14
README.md
Normal file
14
README.md
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
# scribe
|
||||||
|
|
||||||
|
This is a project written using [Lucky](https://luckyframework.org). Enjoy!
|
||||||
|
|
||||||
|
### Setting up the project
|
||||||
|
|
||||||
|
1. [Install required dependencies](https://luckyframework.org/guides/getting-started/installing#install-required-dependencies)
|
||||||
|
1. Update database settings in `config/database.cr`
|
||||||
|
1. Run `script/setup`
|
||||||
|
1. Run `lucky dev` to start the app
|
||||||
|
|
||||||
|
### Learning Lucky
|
||||||
|
|
||||||
|
Lucky uses the [Crystal](https://crystal-lang.org) programming language. You can learn about Lucky from the [Lucky Guides](https://luckyframework.org/guides/getting-started/why-lucky).
|
26
bs-config.js
Normal file
26
bs-config.js
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
| Browser-sync config file
|
||||||
|
|
|
||||||
|
| For up-to-date information about the options:
|
||||||
|
| http://www.browsersync.io/docs/options/
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
snippetOptions: {
|
||||||
|
rule: {
|
||||||
|
match: /<\/head>/i,
|
||||||
|
fn: function (snippet, match) {
|
||||||
|
return snippet + match;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
files: ["public/css/**/*.css", "public/js/**/*.js"],
|
||||||
|
watchEvents: ["change"],
|
||||||
|
open: false,
|
||||||
|
browser: "default",
|
||||||
|
ghostMode: false,
|
||||||
|
ui: false,
|
||||||
|
online: false,
|
||||||
|
logConnections: false
|
||||||
|
};
|
10
config/authentic.cr
Normal file
10
config/authentic.cr
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
require "./server"
|
||||||
|
|
||||||
|
Authentic.configure do |settings|
|
||||||
|
settings.secret_key = Lucky::Server.settings.secret_key_base
|
||||||
|
|
||||||
|
unless Lucky::Env.production?
|
||||||
|
fastest_encryption_possible = 4
|
||||||
|
settings.encryption_cost = fastest_encryption_possible
|
||||||
|
end
|
||||||
|
end
|
4
config/colors.cr
Normal file
4
config/colors.cr
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# This enables the color output when in development or test
|
||||||
|
# Check out the Colorize docs for more information
|
||||||
|
# https://crystal-lang.org/api/Colorize.html
|
||||||
|
Colorize.enabled = Lucky::Env.development? || Lucky::Env.test?
|
14
config/cookies.cr
Normal file
14
config/cookies.cr
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
require "./server"
|
||||||
|
|
||||||
|
Lucky::Session.configure do |settings|
|
||||||
|
settings.key = "_scribe_session"
|
||||||
|
end
|
||||||
|
|
||||||
|
Lucky::CookieJar.configure do |settings|
|
||||||
|
settings.on_set = ->(cookie : HTTP::Cookie) {
|
||||||
|
cookie.secure(Lucky::ForceSSLHandler.settings.enabled)
|
||||||
|
cookie.http_only(true)
|
||||||
|
cookie.samesite(:lax)
|
||||||
|
cookie.path("/")
|
||||||
|
}
|
||||||
|
end
|
20
config/database.cr
Normal file
20
config/database.cr
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
database_name = "scribe_#{Lucky::Env.name}"
|
||||||
|
|
||||||
|
AppDatabase.configure do |settings|
|
||||||
|
if Lucky::Env.production?
|
||||||
|
settings.credentials = Avram::Credentials.parse(ENV["DATABASE_URL"])
|
||||||
|
else
|
||||||
|
settings.credentials = Avram::Credentials.parse?(ENV["DATABASE_URL"]?) || Avram::Credentials.new(
|
||||||
|
database: database_name,
|
||||||
|
hostname: ENV["DB_HOST"]? || "localhost",
|
||||||
|
port: ENV["DB_PORT"]?.try(&.to_i) || 5432,
|
||||||
|
username: ENV["DB_USERNAME"]? || "postgres",
|
||||||
|
password: ENV["DB_PASSWORD"]? || "postgres"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Avram.configure do |settings|
|
||||||
|
settings.database_to_migrate = AppDatabase
|
||||||
|
settings.lazy_load_enabled = Lucky::Env.production?
|
||||||
|
end
|
20
config/env.cr
Normal file
20
config/env.cr
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
module Lucky::Env
|
||||||
|
extend self
|
||||||
|
|
||||||
|
{% for env in [:development, :test, :production] %}
|
||||||
|
def {{ env.id }}?
|
||||||
|
name == {{ env.id.stringify }}
|
||||||
|
end
|
||||||
|
{% end %}
|
||||||
|
|
||||||
|
def name
|
||||||
|
ENV["LUCKY_ENV"]? || "development"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns true if a task is being run through the `lucky` cli
|
||||||
|
#
|
||||||
|
# Use this method to only run (or avoid running) code when a task is executed.
|
||||||
|
def task?
|
||||||
|
ENV["LUCKY_TASK"]? == "true"
|
||||||
|
end
|
||||||
|
end
|
3
config/error_handler.cr
Normal file
3
config/error_handler.cr
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
Lucky::ErrorHandler.configure do |settings|
|
||||||
|
settings.show_debug_output = !Lucky::Env.production?
|
||||||
|
end
|
3
config/html_page.cr
Normal file
3
config/html_page.cr
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
Lucky::HTMLPage.configure do |settings|
|
||||||
|
settings.render_component_comments = !Lucky::Env.production?
|
||||||
|
end
|
35
config/log.cr
Normal file
35
config/log.cr
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
require "file_utils"
|
||||||
|
|
||||||
|
if Lucky::Env.test?
|
||||||
|
# Logs to `tmp/test.log`
|
||||||
|
FileUtils.mkdir_p("tmp")
|
||||||
|
|
||||||
|
backend = Log::IOBackend.new(File.new("tmp/test.log", mode: "w"))
|
||||||
|
backend.formatter = Lucky::PrettyLogFormatter.proc
|
||||||
|
Log.dexter.configure(:debug, backend)
|
||||||
|
elsif Lucky::Env.production?
|
||||||
|
backend = Log::IOBackend.new
|
||||||
|
backend.formatter = Dexter::JSONLogFormatter.proc
|
||||||
|
Log.dexter.configure(:info, backend)
|
||||||
|
else
|
||||||
|
backend = Log::IOBackend.new
|
||||||
|
backend.formatter = Lucky::PrettyLogFormatter.proc
|
||||||
|
Log.dexter.configure(:debug, backend)
|
||||||
|
DB::Log.level = :info
|
||||||
|
end
|
||||||
|
|
||||||
|
# If you want to log every pipe that runs, set the log level to ':info'
|
||||||
|
Lucky::ContinuedPipeLog.dexter.configure(:none)
|
||||||
|
|
||||||
|
# Set the log to ':info' to log all queries
|
||||||
|
Avram::QueryLog.dexter.configure(:none)
|
||||||
|
|
||||||
|
# Skip logging static assets requests in development
|
||||||
|
Lucky::LogHandler.configure do |settings|
|
||||||
|
if Lucky::Env.development?
|
||||||
|
settings.skip_if = ->(context : HTTP::Server::Context) {
|
||||||
|
context.request.method.downcase == "get" &&
|
||||||
|
context.request.resource.starts_with?(/\/css\/|\/js\/|\/assets\/|\/favicon\.ico/)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
9
config/route_helper.cr
Normal file
9
config/route_helper.cr
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
Lucky::RouteHelper.configure do |settings|
|
||||||
|
if Lucky::Env.production?
|
||||||
|
# Example: https://my_app.com
|
||||||
|
settings.base_uri = ENV.fetch("APP_DOMAIN")
|
||||||
|
else
|
||||||
|
# Set domain to the default host/port in development/test
|
||||||
|
settings.base_uri = "http://localhost:#{Lucky::ServerSettings.port}"
|
||||||
|
end
|
||||||
|
end
|
36
config/server.cr
Normal file
36
config/server.cr
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
Lucky::Server.configure do |settings|
|
||||||
|
if Lucky::Env.production?
|
||||||
|
settings.secret_key_base = secret_key_from_env
|
||||||
|
settings.host = "0.0.0.0"
|
||||||
|
settings.port = ENV["PORT"].to_i
|
||||||
|
settings.gzip_enabled = true
|
||||||
|
# To add additional extensions do something like this:
|
||||||
|
# settings.gzip_content_types << "content/type"
|
||||||
|
# https://github.com/luckyframework/lucky/blob/master/src/lucky/server.cr
|
||||||
|
else
|
||||||
|
settings.secret_key_base = "QRpx5Ugh3/S5QCJZNScwNI7HcBdi8jBdtU7iDX5RRcE="
|
||||||
|
settings.host = Lucky::ServerSettings.host
|
||||||
|
settings.port = Lucky::ServerSettings.port
|
||||||
|
end
|
||||||
|
settings.asset_host = "" # Lucky will serve assets
|
||||||
|
end
|
||||||
|
|
||||||
|
Lucky::ForceSSLHandler.configure do |settings|
|
||||||
|
# To force SSL in production, uncomment the lines below.
|
||||||
|
# This will cause http requests to be redirected to https:
|
||||||
|
#
|
||||||
|
# settings.enabled = Lucky::Env.production?
|
||||||
|
# settings.strict_transport_security = {max_age: 1.year, include_subdomains: true}
|
||||||
|
#
|
||||||
|
# Or, leave it disabled:
|
||||||
|
settings.enabled = false
|
||||||
|
end
|
||||||
|
|
||||||
|
private def secret_key_from_env
|
||||||
|
ENV["SECRET_KEY_BASE"]? || raise_missing_secret_key_in_production
|
||||||
|
end
|
||||||
|
|
||||||
|
private def raise_missing_secret_key_in_production
|
||||||
|
puts "Please set the SECRET_KEY_BASE environment variable. You can generate a secret key with 'lucky gen.secret_key'".colorize.red
|
||||||
|
exit(1)
|
||||||
|
end
|
2
config/watch.yml
Normal file
2
config/watch.yml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 5000
|
0
db/migrations/.keep
Normal file
0
db/migrations/.keep
Normal file
25
package.json
Normal file
25
package.json
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"license": "UNLICENSED",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@rails/ujs": "^6.0.0",
|
||||||
|
"normalize-scss": "^7.0.1",
|
||||||
|
"turbolinks": "^5.2.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"heroku-postbuild": "yarn prod",
|
||||||
|
"dev": "mix",
|
||||||
|
"watch": "mix watch",
|
||||||
|
"prod": "mix --production"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/compat-data": "^7.9.0",
|
||||||
|
"browser-sync": "^2.26.7",
|
||||||
|
"compression-webpack-plugin": "^7.0.0",
|
||||||
|
"laravel-mix": "^6.0.0",
|
||||||
|
"postcss": "^8.1.0",
|
||||||
|
"resolve-url-loader": "^3.1.1",
|
||||||
|
"sass": "^1.26.10",
|
||||||
|
"sass-loader": "^10.0.2"
|
||||||
|
}
|
||||||
|
}
|
0
public/assets/images/.keep
Normal file
0
public/assets/images/.keep
Normal file
0
public/favicon.ico
Normal file
0
public/favicon.ico
Normal file
4
public/robots.txt
Normal file
4
public/robots.txt
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# Learn more about robots.txt: https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
# 'Disallow' with an empty value allows all paths to be crawled
|
||||||
|
Disallow:
|
67
script/helpers/function_helpers
Normal file
67
script/helpers/function_helpers
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# This file contains a set of functions used as helpers
|
||||||
|
# for various tasks. Read the examples for each one for
|
||||||
|
# more information. Feel free to put any additional helper
|
||||||
|
# functions you may need for your app
|
||||||
|
|
||||||
|
|
||||||
|
# Returns true if the command $1 is not found
|
||||||
|
# example:
|
||||||
|
# if command_not_found "yarn"; then
|
||||||
|
# echo "no yarn"
|
||||||
|
# fi
|
||||||
|
command_not_found() {
|
||||||
|
! command -v $1 > /dev/null
|
||||||
|
return $?
|
||||||
|
}
|
||||||
|
|
||||||
|
# Returns true if the command $1 is not running
|
||||||
|
# You must supply the full command to check as an argument
|
||||||
|
# example:
|
||||||
|
# if command_not_running "redis-cli ping"; then
|
||||||
|
# print_error "Redis is not running"
|
||||||
|
# fi
|
||||||
|
command_not_running() {
|
||||||
|
$1
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
true
|
||||||
|
else
|
||||||
|
false
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Returns true if the OS is macOS
|
||||||
|
# example:
|
||||||
|
# if is_mac; then
|
||||||
|
# echo "do mac stuff"
|
||||||
|
# fi
|
||||||
|
is_mac() {
|
||||||
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
true
|
||||||
|
else
|
||||||
|
false
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Returns true if the OS is linux based
|
||||||
|
# example:
|
||||||
|
# if is_linux; then
|
||||||
|
# echo "do linux stuff"
|
||||||
|
# fi
|
||||||
|
is_linux() {
|
||||||
|
if [[ "$OSTYPE" == "linux"* ]]; then
|
||||||
|
true
|
||||||
|
else
|
||||||
|
false
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prints error and exit.
|
||||||
|
# example:
|
||||||
|
# print_error "Redis is not running. Run it with some_command"
|
||||||
|
print_error() {
|
||||||
|
printf "${BOLD_RED_COLOR}There is a problem with your system setup:\n\n"
|
||||||
|
printf "${BOLD_RED_COLOR}$1 \n\n" | indent
|
||||||
|
exit 1
|
||||||
|
}
|
32
script/helpers/text_helpers
Normal file
32
script/helpers/text_helpers
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# This file contains a set of functions used to format text,
|
||||||
|
# and make printing text a little easier. Feel free to put
|
||||||
|
# any additional functions you need for formatting your shell
|
||||||
|
# output text.
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
BOLD_RED_COLOR="\e[1m\e[31m"
|
||||||
|
|
||||||
|
# Indents the text 2 spaces
|
||||||
|
# example:
|
||||||
|
# printf "Hello" | indent
|
||||||
|
indent() {
|
||||||
|
while read LINE; do
|
||||||
|
echo " $LINE" || true
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prints out an arrow to your custom notice
|
||||||
|
# example:
|
||||||
|
# notice "Installing new magic"
|
||||||
|
notice() {
|
||||||
|
printf "\n▸ $1\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prints out a check mark and Done.
|
||||||
|
# example:
|
||||||
|
# print_done
|
||||||
|
print_done() {
|
||||||
|
printf "✔ Done\n" | indent
|
||||||
|
}
|
45
script/setup
Executable file
45
script/setup
Executable file
|
@ -0,0 +1,45 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Exit if any subcommand fails
|
||||||
|
set -e
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
source script/helpers/text_helpers
|
||||||
|
|
||||||
|
|
||||||
|
notice "Running System Check"
|
||||||
|
./script/system_check
|
||||||
|
print_done
|
||||||
|
|
||||||
|
notice "Installing node dependencies"
|
||||||
|
yarn install --no-progress | indent
|
||||||
|
|
||||||
|
notice "Compiling assets"
|
||||||
|
yarn dev | indent
|
||||||
|
|
||||||
|
print_done
|
||||||
|
|
||||||
|
notice "Installing shards"
|
||||||
|
shards install | indent
|
||||||
|
|
||||||
|
if [ ! -f ".env" ]; then
|
||||||
|
notice "No .env found. Creating one."
|
||||||
|
touch .env
|
||||||
|
print_done
|
||||||
|
fi
|
||||||
|
|
||||||
|
notice "Creating the database"
|
||||||
|
lucky db.create | indent
|
||||||
|
|
||||||
|
notice "Verifying postgres connection"
|
||||||
|
lucky db.verify_connection | indent
|
||||||
|
|
||||||
|
notice "Migrating the database"
|
||||||
|
lucky db.migrate | indent
|
||||||
|
|
||||||
|
notice "Seeding the database with required and sample records"
|
||||||
|
lucky db.seed.required_data | indent
|
||||||
|
lucky db.seed.sample_data | indent
|
||||||
|
|
||||||
|
print_done
|
||||||
|
notice "Run 'lucky dev' to start the app"
|
42
script/system_check
Executable file
42
script/system_check
Executable file
|
@ -0,0 +1,42 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
source script/helpers/text_helpers
|
||||||
|
source script/helpers/function_helpers
|
||||||
|
|
||||||
|
# Use this script to check the system for required tools and process that your app needs.
|
||||||
|
# A few helper functions are provided to make writing bash a little easier. See the
|
||||||
|
# script/helpers/function_helpers file for more examples.
|
||||||
|
#
|
||||||
|
# A few examples you might use here:
|
||||||
|
# * 'lucky db.verify_connection' to test postgres can be connected
|
||||||
|
# * Checking that elasticsearch, redis, or postgres is installed and/or booted
|
||||||
|
# * Note: Booting additional processes for things like mail, background jobs, etc...
|
||||||
|
# should go in your Procfile.dev.
|
||||||
|
|
||||||
|
if command_not_found "yarn"; then
|
||||||
|
print_error "Yarn is not installed\n See https://yarnpkg.com/lang/en/docs/install/ for install instructions."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Only if this isn't CI
|
||||||
|
if [ -z "$CI" ]; then
|
||||||
|
lucky ensure_process_runner_installed
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command_not_found "createdb"; then
|
||||||
|
MSG="Please install the postgres CLI tools, then try again."
|
||||||
|
if is_mac; then
|
||||||
|
MSG="$MSG\nIf you're using Postgres.app, see https://postgresapp.com/documentation/cli-tools.html."
|
||||||
|
fi
|
||||||
|
MSG="$MSG\nSee https://www.postgresql.org/docs/current/tutorial-install.html for install instructions."
|
||||||
|
|
||||||
|
print_error "$MSG"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
## CUSTOM PRE-BOOT CHECKS ##
|
||||||
|
# example:
|
||||||
|
# if command_not_running "redis-cli ping"; then
|
||||||
|
# print_error "Redis is not running."
|
||||||
|
# fi
|
||||||
|
|
||||||
|
|
90
shard.lock
Normal file
90
shard.lock
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
version: 2.0
|
||||||
|
shards:
|
||||||
|
authentic:
|
||||||
|
git: https://github.com/luckyframework/authentic.git
|
||||||
|
version: 0.7.3
|
||||||
|
|
||||||
|
avram:
|
||||||
|
git: https://github.com/luckyframework/avram.git
|
||||||
|
version: 0.20.0
|
||||||
|
|
||||||
|
carbon:
|
||||||
|
git: https://github.com/luckyframework/carbon.git
|
||||||
|
version: 0.1.4
|
||||||
|
|
||||||
|
cry:
|
||||||
|
git: https://github.com/luckyframework/cry.git
|
||||||
|
version: 0.4.3
|
||||||
|
|
||||||
|
crystar:
|
||||||
|
git: https://github.com/naqvis/crystar.git
|
||||||
|
version: 0.2.0
|
||||||
|
|
||||||
|
db:
|
||||||
|
git: https://github.com/crystal-lang/crystal-db.git
|
||||||
|
version: 0.10.1
|
||||||
|
|
||||||
|
dexter:
|
||||||
|
git: https://github.com/luckyframework/dexter.git
|
||||||
|
version: 0.3.3
|
||||||
|
|
||||||
|
dotenv:
|
||||||
|
git: https://github.com/gdotdesign/cr-dotenv.git
|
||||||
|
version: 1.0.0
|
||||||
|
|
||||||
|
exception_page:
|
||||||
|
git: https://github.com/crystal-loot/exception_page.git
|
||||||
|
version: 0.1.5
|
||||||
|
|
||||||
|
future:
|
||||||
|
git: https://github.com/crystal-community/future.cr.git
|
||||||
|
version: 1.0.0+git.commit.9fe168418c6884cb3552c13b004763eb4815ceb9
|
||||||
|
|
||||||
|
habitat:
|
||||||
|
git: https://github.com/luckyframework/habitat.git
|
||||||
|
version: 0.4.7
|
||||||
|
|
||||||
|
lucky:
|
||||||
|
git: https://github.com/luckyframework/lucky.git
|
||||||
|
version: 0.27.2
|
||||||
|
|
||||||
|
lucky_flow:
|
||||||
|
git: https://github.com/luckyframework/lucky_flow.git
|
||||||
|
version: 0.7.3
|
||||||
|
|
||||||
|
lucky_router:
|
||||||
|
git: https://github.com/luckyframework/lucky_router.git
|
||||||
|
version: 0.4.2
|
||||||
|
|
||||||
|
lucky_task:
|
||||||
|
git: https://github.com/luckyframework/lucky_task.git
|
||||||
|
version: 0.1.0
|
||||||
|
|
||||||
|
pg:
|
||||||
|
git: https://github.com/will/crystal-pg.git
|
||||||
|
version: 0.23.2
|
||||||
|
|
||||||
|
pulsar:
|
||||||
|
git: https://github.com/luckyframework/pulsar.git
|
||||||
|
version: 0.2.2
|
||||||
|
|
||||||
|
selenium:
|
||||||
|
git: https://github.com/matthewmcgarvey/selenium.cr.git
|
||||||
|
version: 0.9.1
|
||||||
|
|
||||||
|
shell-table:
|
||||||
|
git: https://github.com/luckyframework/shell-table.cr.git
|
||||||
|
version: 0.9.2+git.commit.ad72379f241ba28698ca2873d9b43785324a7932
|
||||||
|
|
||||||
|
teeplate:
|
||||||
|
git: https://github.com/luckyframework/teeplate.git
|
||||||
|
version: 0.8.3
|
||||||
|
|
||||||
|
webdrivers:
|
||||||
|
git: https://github.com/matthewmcgarvey/webdrivers.cr.git
|
||||||
|
version: 0.4.0
|
||||||
|
|
||||||
|
wordsmith:
|
||||||
|
git: https://github.com/luckyframework/wordsmith.git
|
||||||
|
version: 0.2.2
|
||||||
|
|
32
shard.yml
Normal file
32
shard.yml
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
name: scribe
|
||||||
|
version: 0.1.0
|
||||||
|
|
||||||
|
authors:
|
||||||
|
- Edward Loveall <edward@edwardloveall.com>
|
||||||
|
|
||||||
|
targets:
|
||||||
|
scribe:
|
||||||
|
main: src/scribe.cr
|
||||||
|
|
||||||
|
crystal: 1.0.0
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
lucky:
|
||||||
|
github: luckyframework/lucky
|
||||||
|
version: ~> 0.27.0
|
||||||
|
authentic:
|
||||||
|
github: luckyframework/authentic
|
||||||
|
version: ~> 0.7.3
|
||||||
|
carbon:
|
||||||
|
github: luckyframework/carbon
|
||||||
|
version: ~> 0.1.4
|
||||||
|
dotenv:
|
||||||
|
github: gdotdesign/cr-dotenv
|
||||||
|
version: ~> 1.0.0
|
||||||
|
lucky_task:
|
||||||
|
github: luckyframework/lucky_task
|
||||||
|
version: ~> 0.1.0
|
||||||
|
development_dependencies:
|
||||||
|
lucky_flow:
|
||||||
|
github: luckyframework/lucky_flow
|
||||||
|
version: ~> 0.7.3
|
0
spec/flows/.keep
Normal file
0
spec/flows/.keep
Normal file
0
spec/setup/.keep
Normal file
0
spec/setup/.keep
Normal file
3
spec/setup/clean_database.cr
Normal file
3
spec/setup/clean_database.cr
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
Spec.before_each do
|
||||||
|
AppDatabase.truncate
|
||||||
|
end
|
5
spec/setup/configure_lucky_flow.cr
Normal file
5
spec/setup/configure_lucky_flow.cr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
LuckyFlow.configure do |settings|
|
||||||
|
settings.stop_retrying_after = 200.milliseconds
|
||||||
|
settings.base_uri = Lucky::RouteHelper.settings.base_uri
|
||||||
|
end
|
||||||
|
Spec.before_each { LuckyFlow::Server::INSTANCE.reset }
|
3
spec/setup/reset_emails.cr
Normal file
3
spec/setup/reset_emails.cr
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
Spec.before_each do
|
||||||
|
Carbon::DevAdapter.reset
|
||||||
|
end
|
2
spec/setup/setup_database.cr
Normal file
2
spec/setup/setup_database.cr
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Db::Create.new(quiet: true).call
|
||||||
|
Db::Migrate.new(quiet: true).call
|
10
spec/setup/start_app_server.cr
Normal file
10
spec/setup/start_app_server.cr
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
app_server = AppServer.new
|
||||||
|
|
||||||
|
spawn do
|
||||||
|
app_server.listen
|
||||||
|
end
|
||||||
|
|
||||||
|
Spec.after_suite do
|
||||||
|
LuckyFlow.shutdown
|
||||||
|
app_server.close
|
||||||
|
end
|
17
spec/spec_helper.cr
Normal file
17
spec/spec_helper.cr
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
ENV["LUCKY_ENV"] = "test"
|
||||||
|
ENV["DEV_PORT"] = "5001"
|
||||||
|
require "spec"
|
||||||
|
require "lucky_flow"
|
||||||
|
require "../src/app"
|
||||||
|
require "./support/flows/base_flow"
|
||||||
|
require "./support/**"
|
||||||
|
require "../db/migrations/**"
|
||||||
|
require "./setup/**"
|
||||||
|
|
||||||
|
include Carbon::Expectations
|
||||||
|
include Lucky::RequestExpectations
|
||||||
|
include LuckyFlow::Expectations
|
||||||
|
|
||||||
|
Avram::Migrator::Runner.new.ensure_migrated!
|
||||||
|
Avram::SchemaEnforcer.ensure_correct_column_mappings!
|
||||||
|
Habitat.raise_if_missing_settings!
|
0
spec/support/.keep
Normal file
0
spec/support/.keep
Normal file
6
spec/support/api_client.cr
Normal file
6
spec/support/api_client.cr
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
class ApiClient < Lucky::BaseHTTPClient
|
||||||
|
def initialize
|
||||||
|
super
|
||||||
|
headers("Content-Type": "application/json")
|
||||||
|
end
|
||||||
|
end
|
0
spec/support/boxes/.keep
Normal file
0
spec/support/boxes/.keep
Normal file
0
spec/support/factories/.keep
Normal file
0
spec/support/factories/.keep
Normal file
2
spec/support/flows/base_flow.cr
Normal file
2
spec/support/flows/base_flow.cr
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
class BaseFlow < LuckyFlow
|
||||||
|
end
|
5
src/actions/api_action.cr
Normal file
5
src/actions/api_action.cr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
abstract class ApiAction < Lucky::Action
|
||||||
|
# Remove this line if you want to send cookies in the response header.
|
||||||
|
disable_cookies
|
||||||
|
accepted_formats [:json]
|
||||||
|
end
|
4
src/actions/browser_action.cr
Normal file
4
src/actions/browser_action.cr
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
abstract class BrowserAction < Lucky::Action
|
||||||
|
include Lucky::ProtectFromForgery
|
||||||
|
accepted_formats [:html, :json], default: :html
|
||||||
|
end
|
61
src/actions/errors/show.cr
Normal file
61
src/actions/errors/show.cr
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
# https://luckyframework.org/guides/http-and-routing/error-handling
|
||||||
|
class Errors::Show < Lucky::ErrorAction
|
||||||
|
DEFAULT_MESSAGE = "Something went wrong."
|
||||||
|
default_format :html
|
||||||
|
dont_report [Lucky::RouteNotFoundError, Avram::RecordNotFoundError]
|
||||||
|
|
||||||
|
def render(error : Lucky::RouteNotFoundError | Avram::RecordNotFoundError)
|
||||||
|
if html?
|
||||||
|
error_html "Sorry, we couldn't find that page.", status: 404
|
||||||
|
else
|
||||||
|
error_json "Not found", status: 404
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# When the request is JSON and an InvalidOperationError is raised, show a
|
||||||
|
# helpful error with the param that is invalid, and what was wrong with it.
|
||||||
|
def render(error : Avram::InvalidOperationError)
|
||||||
|
if html?
|
||||||
|
error_html DEFAULT_MESSAGE, status: 500
|
||||||
|
else
|
||||||
|
error_json \
|
||||||
|
message: error.renderable_message,
|
||||||
|
details: error.renderable_details,
|
||||||
|
param: error.invalid_attribute_name,
|
||||||
|
status: 400
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Always keep this below other 'render' methods or it may override your
|
||||||
|
# custom 'render' methods.
|
||||||
|
def render(error : Lucky::RenderableError)
|
||||||
|
if html?
|
||||||
|
error_html DEFAULT_MESSAGE, status: error.renderable_status
|
||||||
|
else
|
||||||
|
error_json error.renderable_message, status: error.renderable_status
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# If none of the 'render' methods return a response for the raised Exception,
|
||||||
|
# Lucky will use this method.
|
||||||
|
def default_render(error : Exception) : Lucky::Response
|
||||||
|
if html?
|
||||||
|
error_html DEFAULT_MESSAGE, status: 500
|
||||||
|
else
|
||||||
|
error_json DEFAULT_MESSAGE, status: 500
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private def error_html(message : String, status : Int)
|
||||||
|
context.response.status_code = status
|
||||||
|
html Errors::ShowPage, message: message, status: status
|
||||||
|
end
|
||||||
|
|
||||||
|
private def error_json(message : String, status : Int, details = nil, param = nil)
|
||||||
|
json ErrorSerializer.new(message: message, details: details, param: param), status: status
|
||||||
|
end
|
||||||
|
|
||||||
|
private def report(error : Exception) : Nil
|
||||||
|
# Send to Rollbar, send an email, etc.
|
||||||
|
end
|
||||||
|
end
|
5
src/actions/home/index.cr
Normal file
5
src/actions/home/index.cr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
class Home::Index < BrowserAction
|
||||||
|
get "/" do
|
||||||
|
html Lucky::WelcomePage
|
||||||
|
end
|
||||||
|
end
|
0
src/actions/mixins/.keep
Normal file
0
src/actions/mixins/.keep
Normal file
23
src/app.cr
Normal file
23
src/app.cr
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
require "./shards"
|
||||||
|
|
||||||
|
Lucky::AssetHelpers.load_manifest
|
||||||
|
|
||||||
|
require "./app_database"
|
||||||
|
require "./models/base_model"
|
||||||
|
require "./models/mixins/**"
|
||||||
|
require "./models/**"
|
||||||
|
require "./queries/mixins/**"
|
||||||
|
require "./queries/**"
|
||||||
|
require "./operations/mixins/**"
|
||||||
|
require "./operations/**"
|
||||||
|
require "./serializers/base_serializer"
|
||||||
|
require "./serializers/**"
|
||||||
|
require "./actions/mixins/**"
|
||||||
|
require "./actions/**"
|
||||||
|
require "./components/base_component"
|
||||||
|
require "./components/**"
|
||||||
|
require "./pages/**"
|
||||||
|
require "../config/env"
|
||||||
|
require "../config/**"
|
||||||
|
require "../db/migrations/**"
|
||||||
|
require "./app_server"
|
2
src/app_database.cr
Normal file
2
src/app_database.cr
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
class AppDatabase < Avram::Database
|
||||||
|
end
|
24
src/app_server.cr
Normal file
24
src/app_server.cr
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
class AppServer < Lucky::BaseAppServer
|
||||||
|
def middleware : Array(HTTP::Handler)
|
||||||
|
[
|
||||||
|
Lucky::ForceSSLHandler.new,
|
||||||
|
Lucky::HttpMethodOverrideHandler.new,
|
||||||
|
Lucky::LogHandler.new,
|
||||||
|
Lucky::ErrorHandler.new(action: Errors::Show),
|
||||||
|
Lucky::RemoteIpHandler.new,
|
||||||
|
Lucky::RouteHandler.new,
|
||||||
|
Lucky::StaticCompressionHandler.new("./public", file_ext: "gz", content_encoding: "gzip"),
|
||||||
|
Lucky::StaticFileHandler.new("./public", fallthrough: false, directory_listing: false),
|
||||||
|
Lucky::RouteNotFoundHandler.new,
|
||||||
|
] of HTTP::Handler
|
||||||
|
end
|
||||||
|
|
||||||
|
def protocol
|
||||||
|
"http"
|
||||||
|
end
|
||||||
|
|
||||||
|
def listen
|
||||||
|
server.bind_tcp(host, port, reuse_port: false)
|
||||||
|
server.listen
|
||||||
|
end
|
||||||
|
end
|
0
src/components/.keep
Normal file
0
src/components/.keep
Normal file
2
src/components/base_component.cr
Normal file
2
src/components/base_component.cr
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
abstract class BaseComponent < Lucky::BaseComponent
|
||||||
|
end
|
21
src/components/shared/field.cr
Normal file
21
src/components/shared/field.cr
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# https://luckyframework.org/guides/frontend/html-forms#shared-components
|
||||||
|
|
||||||
|
class Shared::Field(T) < BaseComponent
|
||||||
|
include Lucky::CatchUnpermittedAttribute
|
||||||
|
|
||||||
|
needs attribute : Avram::PermittedAttribute(T)
|
||||||
|
needs label_text : String?
|
||||||
|
|
||||||
|
def render
|
||||||
|
label_for attribute, label_text
|
||||||
|
tag_defaults field: attribute do |tag_builder|
|
||||||
|
yield tag_builder
|
||||||
|
end
|
||||||
|
|
||||||
|
mount Shared::FieldErrors, attribute
|
||||||
|
end
|
||||||
|
|
||||||
|
def render
|
||||||
|
render &.text_input
|
||||||
|
end
|
||||||
|
end
|
15
src/components/shared/field_errors.cr
Normal file
15
src/components/shared/field_errors.cr
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
class Shared::FieldErrors(T) < BaseComponent
|
||||||
|
needs attribute : Avram::PermittedAttribute(T)
|
||||||
|
|
||||||
|
def render
|
||||||
|
unless attribute.valid?
|
||||||
|
div class: "error" do
|
||||||
|
text "#{label_text} #{attribute.errors.first}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def label_text : String
|
||||||
|
Wordsmith::Inflector.humanize(attribute.name.to_s)
|
||||||
|
end
|
||||||
|
end
|
11
src/components/shared/flash_messages.cr
Normal file
11
src/components/shared/flash_messages.cr
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
class Shared::FlashMessages < BaseComponent
|
||||||
|
needs flash : Lucky::FlashStore
|
||||||
|
|
||||||
|
def render
|
||||||
|
flash.each do |flash_type, flash_message|
|
||||||
|
div class: "flash-#{flash_type}", flow_id: "flash" do
|
||||||
|
text flash_message
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
16
src/components/shared/layout_head.cr
Normal file
16
src/components/shared/layout_head.cr
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
class Shared::LayoutHead < BaseComponent
|
||||||
|
needs page_title : String
|
||||||
|
needs context : HTTP::Server::Context
|
||||||
|
|
||||||
|
def render
|
||||||
|
head do
|
||||||
|
utf8_charset
|
||||||
|
title "My App - #{@page_title}"
|
||||||
|
css_link asset("css/app.css"), data_turbolinks_track: "reload"
|
||||||
|
js_link asset("js/app.js"), defer: "true", data_turbolinks_track: "reload"
|
||||||
|
meta name: "turbolinks-cache-control", content: "no-cache"
|
||||||
|
csrf_meta_tags
|
||||||
|
responsive_meta_tag
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
66
src/css/app.scss
Normal file
66
src/css/app.scss
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
// Lucky generates 3 folders to help you organize your CSS:
|
||||||
|
//
|
||||||
|
// - src/css/variables # Files for colors, spacing, etc.
|
||||||
|
// - src/css/mixins # Put your mixin functions in files here
|
||||||
|
// - src/css/components # CSS for your components
|
||||||
|
//
|
||||||
|
// Remember to import your new CSS files or they won't be loaded:
|
||||||
|
//
|
||||||
|
// @import "./variables/colors" # Imports the file in src/css/variables/_colors.scss
|
||||||
|
//
|
||||||
|
// Note: importing with `~` tells webpack to look in the installed npm packages
|
||||||
|
// https://stackoverflow.com/questions/39535760/what-does-a-tilde-in-a-css-url-do
|
||||||
|
|
||||||
|
@import "~normalize-scss/sass/normalize/import-now";
|
||||||
|
// Add your own components and import them like this:
|
||||||
|
//
|
||||||
|
// @import "components/my_new_component";
|
||||||
|
|
||||||
|
// Default Lucky styles.
|
||||||
|
// Delete these when you're ready to bring in your own CSS.
|
||||||
|
body {
|
||||||
|
font-family: system-ui, BlinkMacSystemFont, -apple-system, Segoe UI, Roboto,
|
||||||
|
Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 800px;
|
||||||
|
padding: 20px 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label,
|
||||||
|
input {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type="color"],
|
||||||
|
[type="date"],
|
||||||
|
[type="datetime"],
|
||||||
|
[type="datetime-local"],
|
||||||
|
[type="email"],
|
||||||
|
[type="month"],
|
||||||
|
[type="number"],
|
||||||
|
[type="password"],
|
||||||
|
[type="search"],
|
||||||
|
[type="tel"],
|
||||||
|
[type="text"],
|
||||||
|
[type="time"],
|
||||||
|
[type="url"],
|
||||||
|
[type="week"],
|
||||||
|
input:not([type]),
|
||||||
|
textarea {
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 1px solid #bbb;
|
||||||
|
margin: 7px 0 14px 0;
|
||||||
|
max-width: 400px;
|
||||||
|
padding: 8px 6px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type="submit"] {
|
||||||
|
font-weight: 900;
|
||||||
|
margin: 9px 0;
|
||||||
|
padding: 6px 9px;
|
||||||
|
}
|
0
src/css/components/.keep
Normal file
0
src/css/components/.keep
Normal file
0
src/css/mixins/.keep
Normal file
0
src/css/mixins/.keep
Normal file
0
src/css/variables/.keep
Normal file
0
src/css/variables/.keep
Normal file
0
src/emails/.keep
Normal file
0
src/emails/.keep
Normal file
4
src/js/app.js
Normal file
4
src/js/app.js
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
/* eslint no-console:0 */
|
||||||
|
|
||||||
|
require("@rails/ujs").start();
|
||||||
|
require("turbolinks").start();
|
5
src/models/base_model.cr
Normal file
5
src/models/base_model.cr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
abstract class BaseModel < Avram::Model
|
||||||
|
def self.database : Avram::Database.class
|
||||||
|
AppDatabase
|
||||||
|
end
|
||||||
|
end
|
0
src/models/mixins/.keep
Normal file
0
src/models/mixins/.keep
Normal file
0
src/operations/.keep
Normal file
0
src/operations/.keep
Normal file
0
src/operations/mixins/.keep
Normal file
0
src/operations/mixins/.keep
Normal file
93
src/pages/errors/show_page.cr
Normal file
93
src/pages/errors/show_page.cr
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
class Errors::ShowPage
|
||||||
|
include Lucky::HTMLPage
|
||||||
|
|
||||||
|
needs message : String
|
||||||
|
needs status : Int32
|
||||||
|
|
||||||
|
def render
|
||||||
|
html_doctype
|
||||||
|
html lang: "en" do
|
||||||
|
head do
|
||||||
|
utf8_charset
|
||||||
|
title "Something went wrong"
|
||||||
|
load_lato_font
|
||||||
|
normalize_styles
|
||||||
|
error_page_styles
|
||||||
|
end
|
||||||
|
|
||||||
|
body do
|
||||||
|
div class: "container" do
|
||||||
|
h2 status, class: "status-code"
|
||||||
|
h1 message, class: "message"
|
||||||
|
|
||||||
|
ul class: "helpful-links" do
|
||||||
|
li do
|
||||||
|
a "Try heading back to home", href: "/", class: "helpful-link"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_lato_font
|
||||||
|
css_link "https://fonts.googleapis.com/css?family=Lato"
|
||||||
|
end
|
||||||
|
|
||||||
|
def normalize_styles
|
||||||
|
style <<-CSS
|
||||||
|
/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}
|
||||||
|
CSS
|
||||||
|
end
|
||||||
|
|
||||||
|
def error_page_styles
|
||||||
|
style <<-CSS
|
||||||
|
body {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
color: #000;
|
||||||
|
font-family: 'Lato', sans-serif;
|
||||||
|
padding-top: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.helpful-links {
|
||||||
|
list-style-type: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.helpful-link {
|
||||||
|
color: #15A38B;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-code {
|
||||||
|
opacity: 0.4;
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
font-size: 34px;
|
||||||
|
line-height: 56px;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 450px;
|
||||||
|
padding: 55px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 500px) {
|
||||||
|
.status-code {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
font-size: 26px;
|
||||||
|
line-height: 40px;
|
||||||
|
margin: 20px 0 35px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CSS
|
||||||
|
end
|
||||||
|
end
|
23
src/pages/main_layout.cr
Normal file
23
src/pages/main_layout.cr
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
abstract class MainLayout
|
||||||
|
include Lucky::HTMLPage
|
||||||
|
|
||||||
|
abstract def content
|
||||||
|
abstract def page_title
|
||||||
|
|
||||||
|
def page_title
|
||||||
|
"Welcome"
|
||||||
|
end
|
||||||
|
|
||||||
|
def render
|
||||||
|
html_doctype
|
||||||
|
|
||||||
|
html lang: "en" do
|
||||||
|
mount Shared::LayoutHead, page_title: page_title, context: context
|
||||||
|
|
||||||
|
body do
|
||||||
|
mount Shared::FlashMessages, context.flash
|
||||||
|
content
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
0
src/queries/.keep
Normal file
0
src/queries/.keep
Normal file
0
src/queries/mixins/.keep
Normal file
0
src/queries/mixins/.keep
Normal file
1
src/scribe.cr
Normal file
1
src/scribe.cr
Normal file
|
@ -0,0 +1 @@
|
||||||
|
require "./start_server"
|
0
src/serializers/.keep
Normal file
0
src/serializers/.keep
Normal file
7
src/serializers/base_serializer.cr
Normal file
7
src/serializers/base_serializer.cr
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
abstract class BaseSerializer < Lucky::Serializer
|
||||||
|
def self.for_collection(collection : Enumerable, *args, **named_args)
|
||||||
|
collection.map do |object|
|
||||||
|
new(object, *args, **named_args)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
14
src/serializers/error_serializer.cr
Normal file
14
src/serializers/error_serializer.cr
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
# This is the default error serializer generated by Lucky.
|
||||||
|
# Feel free to customize it in any way you like.
|
||||||
|
class ErrorSerializer < BaseSerializer
|
||||||
|
def initialize(
|
||||||
|
@message : String,
|
||||||
|
@details : String? = nil,
|
||||||
|
@param : String? = nil # so you can track which param (if any) caused the problem
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def render
|
||||||
|
{message: @message, param: @param, details: @details}
|
||||||
|
end
|
||||||
|
end
|
9
src/shards.cr
Normal file
9
src/shards.cr
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# Load .env file before any other config or app code
|
||||||
|
require "dotenv"
|
||||||
|
Dotenv.load?
|
||||||
|
|
||||||
|
# Require your shards here
|
||||||
|
require "avram"
|
||||||
|
require "lucky"
|
||||||
|
require "carbon"
|
||||||
|
require "authentic"
|
16
src/start_server.cr
Normal file
16
src/start_server.cr
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
require "./app"
|
||||||
|
|
||||||
|
Habitat.raise_if_missing_settings!
|
||||||
|
|
||||||
|
if Lucky::Env.development?
|
||||||
|
Avram::Migrator::Runner.new.ensure_migrated!
|
||||||
|
Avram::SchemaEnforcer.ensure_correct_column_mappings!
|
||||||
|
end
|
||||||
|
|
||||||
|
app_server = AppServer.new
|
||||||
|
|
||||||
|
Signal::INT.trap do
|
||||||
|
app_server.close
|
||||||
|
end
|
||||||
|
|
||||||
|
app_server.listen
|
11
tasks.cr
Normal file
11
tasks.cr
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
# https://luckyframework.org/guides/command-line-tasks/custom-tasks
|
||||||
|
|
||||||
|
ENV["LUCKY_TASK"] = "true"
|
||||||
|
|
||||||
|
require "./src/app"
|
||||||
|
require "lucky_task"
|
||||||
|
require "./tasks/**"
|
||||||
|
require "./db/migrations/**"
|
||||||
|
require "lucky/tasks/**"
|
||||||
|
|
||||||
|
LuckyTask::Runner.run
|
0
tasks/.keep
Normal file
0
tasks/.keep
Normal file
14
tasks/db/seed/required_data.cr
Normal file
14
tasks/db/seed/required_data.cr
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
require "../../../spec/support/factories/**"
|
||||||
|
|
||||||
|
class Db::Seed::RequiredData < LuckyTask::Task
|
||||||
|
summary "Add database records required for the app to work"
|
||||||
|
|
||||||
|
def call
|
||||||
|
# Avram::Factory:
|
||||||
|
# UserFactory.create &.email("me@example.com")
|
||||||
|
|
||||||
|
# SaveOperation:
|
||||||
|
# SaveUser.create!(email: "me@example.com", name: "Jane")
|
||||||
|
puts "Done adding required data"
|
||||||
|
end
|
||||||
|
end
|
14
tasks/db/seed/sample_data.cr
Normal file
14
tasks/db/seed/sample_data.cr
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
require "../../../spec/support/factories/**"
|
||||||
|
|
||||||
|
class Db::Seed::SampleData < LuckyTask::Task
|
||||||
|
summary "Add sample database records helpful for development"
|
||||||
|
|
||||||
|
def call
|
||||||
|
# Avram::Factory:
|
||||||
|
# UserFactory.create &.email("me@example.com")
|
||||||
|
|
||||||
|
# SaveOperation:
|
||||||
|
# SaveUser.create!(email: "me@example.com", name: "Jane")
|
||||||
|
puts "Done adding sample data"
|
||||||
|
end
|
||||||
|
end
|
38
webpack.mix.js
Normal file
38
webpack.mix.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
// Docs: https://github.com/JeffreyWay/laravel-mix/tree/master/docs#readme
|
||||||
|
|
||||||
|
let mix = require("laravel-mix");
|
||||||
|
let plugins = [];
|
||||||
|
|
||||||
|
let WebpackNotifierPlugin = require("webpack-notifier");
|
||||||
|
let webpackNotifier = new WebpackNotifierPlugin({
|
||||||
|
alwaysNotify: false,
|
||||||
|
skipFirstNotification: true,
|
||||||
|
});
|
||||||
|
plugins.push(webpackNotifier);
|
||||||
|
|
||||||
|
if (mix.inProduction()) {
|
||||||
|
let CompressionWepackPlugin = require("compression-webpack-plugin");
|
||||||
|
let gzipCompression = new CompressionWepackPlugin({
|
||||||
|
compressionOptions: { level: 9 },
|
||||||
|
test: /\.js$|\.css$|\.html$|\.svg$/,
|
||||||
|
});
|
||||||
|
plugins.push(gzipCompression);
|
||||||
|
}
|
||||||
|
|
||||||
|
mix
|
||||||
|
.setPublicPath("public")
|
||||||
|
.js("src/js/app.js", "js")
|
||||||
|
.sass("src/css/app.scss", "css")
|
||||||
|
.options({
|
||||||
|
imgLoaderOptions: { enabled: false },
|
||||||
|
clearConsole: false,
|
||||||
|
})
|
||||||
|
.version(["public/assets"])
|
||||||
|
.webpackConfig({
|
||||||
|
stats: "errors-only",
|
||||||
|
plugins: plugins,
|
||||||
|
watchOptions: {
|
||||||
|
ignored: /node_modules/,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.disableNotifications();
|
Loading…
Reference in a new issue