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