Initial app

This commit is contained in:
Edward Loveall 2021-05-01 17:02:08 -04:00
commit fcf3eb14d0
No known key found for this signature in database
GPG key ID: A7606DFEC2BA731F
84 changed files with 8470 additions and 0 deletions

1
.crystal-version Normal file
View file

@ -0,0 +1 @@
0.36.1

9
.editorconfig Normal file
View 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
View 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
View 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
View file

@ -0,0 +1,2 @@
nodejs 12.14.1
crystal 1.0.0

2
Procfile Normal file
View file

@ -0,0 +1,2 @@
web: bin/scribe
release: lucky db.migrate

3
Procfile.dev Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View file

@ -0,0 +1,3 @@
Lucky::HTMLPage.configure do |settings|
settings.render_component_comments = !Lucky::Env.production?
end

35
config/log.cr Normal file
View 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
View 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
View 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
View file

@ -0,0 +1,2 @@
host: 127.0.0.1
port: 5000

0
db/migrations/.keep Normal file
View file

25
package.json Normal file
View 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"
}
}

View file

0
public/favicon.ico Normal file
View file

4
public/robots.txt Normal file
View 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:

View 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
}

View 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
View 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
View 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
View 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
View 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
View file

0
spec/setup/.keep Normal file
View file

View file

@ -0,0 +1,3 @@
Spec.before_each do
AppDatabase.truncate
end

View 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 }

View file

@ -0,0 +1,3 @@
Spec.before_each do
Carbon::DevAdapter.reset
end

View file

@ -0,0 +1,2 @@
Db::Create.new(quiet: true).call
Db::Migrate.new(quiet: true).call

View 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
View 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
View file

View 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
View file

View file

View file

@ -0,0 +1,2 @@
class BaseFlow < LuckyFlow
end

View 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

View file

@ -0,0 +1,4 @@
abstract class BrowserAction < Lucky::Action
include Lucky::ProtectFromForgery
accepted_formats [:html, :json], default: :html
end

View 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

View file

@ -0,0 +1,5 @@
class Home::Index < BrowserAction
get "/" do
html Lucky::WelcomePage
end
end

0
src/actions/mixins/.keep Normal file
View file

23
src/app.cr Normal file
View 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
View file

@ -0,0 +1,2 @@
class AppDatabase < Avram::Database
end

24
src/app_server.cr Normal file
View 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
View file

View file

@ -0,0 +1,2 @@
abstract class BaseComponent < Lucky::BaseComponent
end

View 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

View 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

View 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

View 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
View 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
View file

0
src/css/mixins/.keep Normal file
View file

0
src/css/variables/.keep Normal file
View file

0
src/emails/.keep Normal file
View file

4
src/js/app.js Normal file
View file

@ -0,0 +1,4 @@
/* eslint no-console:0 */
require("@rails/ujs").start();
require("turbolinks").start();

5
src/models/base_model.cr Normal file
View 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
View file

0
src/operations/.keep Normal file
View file

View file

View 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
View 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
View file

0
src/queries/mixins/.keep Normal file
View file

1
src/scribe.cr Normal file
View file

@ -0,0 +1 @@
require "./start_server"

0
src/serializers/.keep Normal file
View file

View 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

View 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
View 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
View 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
View 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
View file

View 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

View 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
View 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();

7243
yarn.lock Normal file

File diff suppressed because it is too large Load diff