.crystal-version CHANGED
@@ -1 +1 @@
1
- 1.1.0
1
+ 1.2.1
.github/workflows/ci.yml CHANGED
@@ -1,75 +1,81 @@
1
1
  name: my_app CI
2
2
 
3
3
  on:
4
4
  push:
5
5
  branches: "*"
6
6
  pull_request:
7
7
  branches: "*"
8
8
 
9
9
  jobs:
10
10
  check-format:
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ crystal_version:
15
+ - 1.2.1
16
+ experimental:
17
+ - false
11
18
  runs-on: ubuntu-latest
12
- container:
13
- image: crystallang/crystal:1.1.0
19
+ continue-on-error: ${{ matrix.experimental }}
14
20
  steps:
15
21
  - uses: actions/checkout@v2
22
+ - name: Install Crystal
23
+ uses: crystal-lang/install-crystal@v1
24
+ with:
25
+ crystal: ${{ matrix.crystal_version }}
16
26
  - name: Format
17
27
  run: crystal tool format --check
18
28
 
19
29
  specs:
30
+ strategy:
31
+ fail-fast: false
32
+ matrix:
33
+ crystal_version:
34
+ - 1.2.1
35
+ experimental:
36
+ - false
20
37
  runs-on: ubuntu-latest
21
- container:
22
- image: crystallang/crystal:1.1.0
23
38
  env:
24
39
  LUCKY_ENV: test
25
40
  DB_HOST: postgres
26
-
41
+ continue-on-error: ${{ matrix.experimental }}
27
42
  services:
28
43
  postgres:
29
44
  image: postgres:12-alpine
30
45
  env:
31
46
  POSTGRES_PASSWORD: postgres
32
47
  ports:
33
48
  - 5432:5432
34
49
  # Set health checks to wait until postgres has started
35
50
  options: >-
36
51
  --health-cmd pg_isready
37
52
  --health-interval 10s
38
53
  --health-timeout 5s
39
54
  --health-retries 5
40
55
 
41
56
  steps:
42
57
  - uses: actions/checkout@v2
43
-
44
- - name: Install PostgreSQL client
45
- run: |
46
- apt-get update
47
- apt-get -yqq install libpq-dev postgresql-client
48
- - name: Install browser
58
+ - name: Install Crystal
49
- run: apt-get -yqq install chromium-browser
50
-
51
- - uses: actions/setup-node@v1
59
+ uses: crystal-lang/install-crystal@v1
52
60
  with:
53
- node-version: '12.x'
54
- - name: "Install yarn"
61
+ crystal: ${{ matrix.crystal_version }}
55
- run: npm install -g yarn
56
62
 
57
63
  - name: Get yarn cache directory path
58
64
  id: yarn-cache-dir-path
59
65
  run: echo "::set-output name=dir::$(yarn cache dir)"
60
66
 
61
67
  - name: Set up Yarn cache
62
68
  uses: actions/cache@v2
63
69
  with:
64
70
  path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
65
71
  key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
66
72
  restore-keys: |
67
73
  ${{ runner.os }}-yarn-
68
74
 
69
75
  - name: Set up Node cache
70
76
  uses: actions/cache@v2
71
77
  id: node-cache # use this to check for `cache-hit` (`steps.node-cache.outputs.cache-hit != 'true'`)
72
78
  with:
73
79
  path: '**/node_modules'
74
80
  key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
75
81
  restore-keys: |
config/application.cr CHANGED
@@ -0,0 +1,24 @@
1
+ # This file may be used for custom Application configurations.
2
+ # It will be loaded before other config files.
3
+ #
4
+ # Read more on configuration:
5
+ # https://luckyframework.org/guides/getting-started/configuration#configuring-your-own-code
6
+
7
+ # Use this code as an example:
8
+ #
9
+ # ```
10
+ # module Application
11
+ # Habitat.create do
12
+ # setting support_email : String
13
+ # setting lock_with_basic_auth : Bool
14
+ # end
15
+ # end
16
+ #
17
+ # Application.configure do |settings|
18
+ # settings.support_email = "support@myapp.io"
19
+ # settings.lock_with_basic_auth = LuckEnv.staging?
20
+ # end
21
+ #
22
+ # # In your application, call
23
+ # # `Application.settings.support_email` anywhere you need it.
24
+ # ```
config/authentic.cr CHANGED
@@ -1,10 +1,11 @@
1
1
  require "./server"
2
2
 
3
3
  Authentic.configure do |settings|
4
4
  settings.secret_key = Lucky::Server.settings.secret_key_base
5
5
 
6
6
  unless LuckyEnv.production?
7
+ # This value can be between 4 and 31
7
8
  fastest_encryption_possible = 4
8
9
  settings.encryption_cost = fastest_encryption_possible
9
10
  end
10
11
  end
config/server.cr CHANGED
@@ -29,28 +29,38 @@
29
29
  # if LuckyEnv.production?
30
30
  # settings.asset_host = "https://mycdnhost.com"
31
31
  # else
32
32
  # settings.asset_host = ""
33
33
  # end
34
34
  # end
35
35
  settings.asset_host = "" # Lucky will serve assets
36
36
  end
37
37
 
38
38
  Lucky::ForceSSLHandler.configure do |settings|
39
39
  # To force SSL in production, uncomment the lines below.
40
40
  # This will cause http requests to be redirected to https:
41
41
  #
42
42
  # settings.enabled = LuckyEnv.production?
43
43
  # settings.strict_transport_security = {max_age: 1.year, include_subdomains: true}
44
44
  #
45
45
  # Or, leave it disabled:
46
46
  settings.enabled = false
47
47
  end
48
48
 
49
+ # Set a uniuqe ID for each HTTP request.
50
+ Lucky::RequestIdHandler.configure do |settings|
51
+ # To enable the request ID, uncomment the lines below.
52
+ # You can set your own custom String, or use a random UUID.
53
+ #
54
+ # settings.set_request_id = ->(context : HTTP::Server::Context) {
55
+ # UUID.random.to_s
56
+ # }
57
+ end
58
+
49
59
  private def secret_key_from_env
50
60
  ENV["SECRET_KEY_BASE"]? || raise_missing_secret_key_in_production
51
61
  end
52
62
 
53
63
  private def raise_missing_secret_key_in_production
54
64
  puts "Please set the SECRET_KEY_BASE environment variable. You can generate a secret key with 'lucky gen.secret_key'".colorize.red
55
65
  exit(1)
56
66
  end
package.json CHANGED
@@ -1,25 +1,25 @@
1
1
  {
2
2
  "license": "UNLICENSED",
3
3
  "private": true,
4
4
  "dependencies": {
5
5
  "@rails/ujs": "^6.0.0",
6
6
  "modern-normalize": "^1.1.0",
7
7
  "turbolinks": "^5.2.0"
8
8
  },
9
9
  "scripts": {
10
10
  "heroku-postbuild": "yarn prod",
11
- "dev": "mix",
11
+ "dev": "yarn run mix",
12
- "watch": "mix watch",
12
+ "watch": "yarn run mix watch",
13
- "prod": "mix --production"
13
+ "prod": "yarn run mix --production"
14
14
  },
15
15
  "devDependencies": {
16
16
  "@babel/compat-data": "^7.9.0",
17
17
  "browser-sync": "^2.26.7",
18
18
  "compression-webpack-plugin": "^7.0.0",
19
19
  "laravel-mix": "^6.0.0",
20
20
  "postcss": "^8.1.0",
21
21
  "resolve-url-loader": "^3.1.1",
22
22
  "sass": "^1.26.10",
23
23
  "sass-loader": "^10.0.2"
24
24
  }
25
25
  }
public/mix-manifest.json CHANGED
@@ -0,0 +1,4 @@
1
+ {
2
+ "/css/app.css": "/css/app.css",
3
+ "/js/app.js": "/js/app.js"
4
+ }
shard.yml CHANGED
@@ -1,38 +1,38 @@
1
1
  name: my_app
2
2
  version: 0.1.0
3
3
 
4
4
  authors:
5
5
  - your-name-here <your-email-here>
6
6
 
7
7
  targets:
8
8
  my_app:
9
9
  main: src/my_app.cr
10
10
 
11
- crystal: 1.1.0
11
+ crystal: 1.2.1
12
12
 
13
13
  dependencies:
14
14
  lucky:
15
15
  github: luckyframework/lucky
16
- version: ~> 0.28.0
16
+ version: ~> 0.29.0
17
17
  authentic:
18
18
  github: luckyframework/authentic
19
- version: ~> 0.8.0
19
+ version: ~> 0.8.1
20
20
  carbon:
21
21
  github: luckyframework/carbon
22
22
  version: ~> 0.2.0
23
23
  carbon_sendgrid_adapter:
24
24
  github: luckyframework/carbon_sendgrid_adapter
25
- version: ~> 0.1.0
25
+ version: ~> 0.2.0
26
26
  lucky_env:
27
27
  github: luckyframework/lucky_env
28
- version: ~> 0.1.3
28
+ version: ~> 0.1.4
29
29
  lucky_task:
30
30
  github: luckyframework/lucky_task
31
- version: ~> 0.1.0
31
+ version: ~> 0.1.1
32
32
  jwt:
33
33
  github: crystal-community/jwt
34
- version: ~> 1.5.0
34
+ version: ~> 1.6.0
35
35
  development_dependencies:
36
36
  lucky_flow:
37
37
  github: luckyframework/lucky_flow
38
38
  version: ~> 0.7.3
spec/flows/authentication_spec.cr CHANGED
@@ -1,23 +1,23 @@
1
1
  require "../spec_helper"
2
2
 
3
- describe "Authentication flow" do
3
+ describe "Authentication flow", tags: "flow" do
4
4
  it "works" do
5
5
  flow = AuthenticationFlow.new("test@example.com")
6
6
 
7
7
  flow.sign_up "password"
8
8
  flow.should_be_signed_in
9
9
  flow.sign_out
10
10
  flow.sign_in "wrong-password"
11
11
  flow.should_have_password_error
12
12
  flow.sign_in "password"
13
13
  flow.should_be_signed_in
14
14
  end
15
15
 
16
16
  # This is to show you how to sign in as a user during tests.
17
17
  # Use the `visit` method's `as` option in your tests to sign in as that user.
18
18
  #
19
19
  # Feel free to delete this once you have other tests using the 'as' option.
20
20
  it "allows sign in through backdoor when testing" do
21
21
  user = UserFactory.create
22
22
  flow = BaseFlow.new
23
23
 
spec/flows/reset_password_spec.cr CHANGED
@@ -1,18 +1,18 @@
1
1
  require "../spec_helper"
2
2
 
3
- describe "Reset password flow" do
3
+ describe "Reset password flow", tags: "flow" do
4
4
  it "works" do
5
5
  user = UserFactory.create
6
6
  flow = ResetPasswordFlow.new(user)
7
7
 
8
8
  flow.request_password_reset
9
9
  flow.should_have_sent_reset_email
10
10
  flow.reset_password "new-password"
11
11
  flow.should_be_signed_in
12
12
  flow.sign_out
13
13
  flow.sign_in "wrong-password"
14
14
  flow.should_have_password_error
15
15
  flow.sign_in "new-password"
16
16
  flow.should_be_signed_in
17
17
  end
18
18
  end
src/actions/api_action.cr CHANGED
@@ -1,17 +1,17 @@
1
1
  # Include modules and add methods that are for all API requests
2
2
  abstract class ApiAction < Lucky::Action
3
3
  # APIs typically do not need to send cookie/session data.
4
4
  # Remove this line if you want to send cookies in the response header.
5
5
  disable_cookies
6
6
  accepted_formats [:json]
7
7
 
8
8
  include Api::Auth::Helpers
9
9
 
10
10
  # By default all actions require sign in.
11
11
  # Add 'include Api::Auth::SkipRequireAuthToken' to your actions to allow all requests.
12
12
  include Api::Auth::RequireAuthToken
13
13
 
14
- # By default all actions are required to use underscores.
14
+ # By default all actions are required to use underscores to separate words.
15
15
  # Add 'include Lucky::SkipRouteStyleCheck' to your actions if you wish to ignore this check for specific routes.
16
16
  include Lucky::EnforceUnderscoredRoute
17
17
  end
src/actions/browser_action.cr CHANGED
@@ -1,22 +1,25 @@
1
1
  abstract class BrowserAction < Lucky::Action
2
2
  include Lucky::ProtectFromForgery
3
+
4
+ # By default all actions are required to use underscores.
5
+ # Add `include Lucky::SkipRouteStyleCheck` to your actions if you wish to ignore this check for specific routes.
3
6
  include Lucky::EnforceUnderscoredRoute
4
7
 
5
8
  # This module disables Google FLoC by setting the
6
9
  # [Permissions-Policy](https://github.com/WICG/floc) HTTP header to `interest-cohort=()`.
7
10
  #
8
11
  # This header is a part of Google's Federated Learning of Cohorts (FLoC) which is used
9
12
  # to track browsing history instead of using 3rd-party cookies.
10
13
  #
11
14
  # Remove this include if you want to use the FLoC tracking.
12
15
  include Lucky::SecureHeaders::DisableFLoC
13
16
 
14
17
  accepted_formats [:html, :json], default: :html
15
18
 
16
19
  # This module provides current_user, sign_in, and sign_out methods
17
20
  include Authentic::ActionHelpers(User)
18
21
 
19
22
  # When testing you can skip normal sign in by using `visit` with the `as` param
20
23
  #
21
24
  # flow.visit Me::Show, as: UserFactory.create
22
25
  include Auth::TestBackdoor
src/actions/errors/show.cr CHANGED
@@ -33,31 +33,31 @@
33
33
  def render(error : Lucky::RenderableError)
34
34
  if html?
35
35
  error_html DEFAULT_MESSAGE, status: error.renderable_status
36
36
  else
37
37
  error_json error.renderable_message, status: error.renderable_status
38
38
  end
39
39
  end
40
40
 
41
41
  # If none of the 'render' methods return a response for the raised Exception,
42
42
  # Lucky will use this method.
43
43
  def default_render(error : Exception) : Lucky::Response
44
44
  if html?
45
45
  error_html DEFAULT_MESSAGE, status: 500
46
46
  else
47
47
  error_json DEFAULT_MESSAGE, status: 500
48
48
  end
49
49
  end
50
50
 
51
51
  private def error_html(message : String, status : Int)
52
52
  context.response.status_code = status
53
- html Errors::ShowPage, message: message, status: status
53
+ html_with_status Errors::ShowPage, status, message: message, status_code: status
54
54
  end
55
55
 
56
56
  private def error_json(message : String, status : Int, details = nil, param = nil)
57
57
  json ErrorSerializer.new(message: message, details: details, param: param), status: status
58
58
  end
59
59
 
60
60
  private def report(error : Exception) : Nil
61
61
  # Send to Rollbar, send an email, etc.
62
62
  end
63
63
  end
src/app.cr CHANGED
@@ -1,26 +1,26 @@
1
1
  require "./shards"
2
2
 
3
+ # Load the asset manifest
3
- # Load the asset manifest in public/mix-manifest.json
4
+ Lucky::AssetHelpers.load_manifest "public/mix-manifest.json"
4
- Lucky::AssetHelpers.load_manifest
5
5
 
6
+ require "../config/server"
7
+ require "../config/**"
6
8
  require "./app_database"
7
9
  require "./models/base_model"
8
10
  require "./models/mixins/**"
9
11
  require "./models/**"
10
12
  require "./queries/mixins/**"
11
13
  require "./queries/**"
12
14
  require "./operations/mixins/**"
13
15
  require "./operations/**"
14
16
  require "./serializers/base_serializer"
15
17
  require "./serializers/**"
16
18
  require "./emails/base_email"
17
19
  require "./emails/**"
18
20
  require "./actions/mixins/**"
19
21
  require "./actions/**"
20
22
  require "./components/base_component"
21
23
  require "./components/**"
22
24
  require "./pages/**"
23
- require "../config/server"
24
- require "../config/**"
25
25
  require "../db/migrations/**"
26
26
  require "./app_server"
src/app_server.cr CHANGED
@@ -1,27 +1,26 @@
1
1
  class AppServer < Lucky::BaseAppServer
2
2
  # Learn about middleware with HTTP::Handlers:
3
3
  # https://luckyframework.org/guides/http-and-routing/http-handlers
4
4
  def middleware : Array(HTTP::Handler)
5
5
  [
6
+ Lucky::RequestIdHandler.new,
6
7
  Lucky::ForceSSLHandler.new,
7
8
  Lucky::HttpMethodOverrideHandler.new,
8
9
  Lucky::LogHandler.new,
9
10
  Lucky::ErrorHandler.new(action: Errors::Show),
10
11
  Lucky::RemoteIpHandler.new,
11
12
  Lucky::RouteHandler.new,
12
13
  Lucky::StaticCompressionHandler.new("./public", file_ext: "gz", content_encoding: "gzip"),
13
14
  Lucky::StaticFileHandler.new("./public", fallthrough: false, directory_listing: false),
14
15
  Lucky::RouteNotFoundHandler.new,
15
16
  ] of HTTP::Handler
16
17
  end
17
18
 
18
19
  def protocol
19
20
  "http"
20
21
  end
21
22
 
22
23
  def listen
23
- # Learn about bind_tcp: https://tinyurl.com/bind-tcp-docs
24
- server.bind_tcp(host, port, reuse_port: false)
24
+ server.listen(host, port, reuse_port: false)
25
- server.listen
26
25
  end
27
26
  end
src/operations/mixins/password_validations.cr CHANGED
@@ -1,11 +1,12 @@
1
1
  module PasswordValidations
2
2
  macro included
3
3
  before_save run_password_validations
4
4
  end
5
5
 
6
6
  private def run_password_validations
7
7
  validate_required password, password_confirmation
8
8
  validate_confirmation_of password, with: password_confirmation
9
+ # 72 is a limitation of BCrypt
9
- validate_size_of password, min: 6
10
+ validate_size_of password, min: 6, max: 72
10
11
  end
11
12
  end
src/pages/errors/show_page.cr CHANGED
@@ -1,40 +1,40 @@
1
1
  class Errors::ShowPage
2
2
  include Lucky::HTMLPage
3
3
 
4
4
  needs message : String
5
- needs status : Int32
5
+ needs status_code : Int32
6
6
 
7
7
  def render
8
8
  html_doctype
9
9
  html lang: "en" do
10
10
  head do
11
11
  utf8_charset
12
12
  title "Something went wrong"
13
13
  load_lato_font
14
14
  normalize_styles
15
15
  error_page_styles
16
16
  end
17
17
 
18
18
  body do
19
19
  div class: "container" do
20
- h2 status, class: "status-code"
20
+ h2 status_code, class: "status-code"
21
21
  h1 message, class: "message"
22
22
 
23
23
  ul class: "helpful-links" do
24
24
  li do
25
25
  a "Try heading back to home", href: "/", class: "helpful-link"
26
26
  end
27
27
  end
28
28
  end
29
29
  end
30
30
  end
31
31
  end
32
32
 
33
33
  def load_lato_font
34
34
  css_link "https://fonts.googleapis.com/css?family=Lato"
35
35
  end
36
36
 
37
37
  def normalize_styles
38
38
  style <<-CSS
39
39
  /*! 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}
40
40
  CSS