Paul: Check-in [6286f96edf]

Online event coordination and survey application

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:feat: Add mail and sqlite3 support
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256:6286f96edf6d85eb739404d6e5bcad72fb381918d6f4d99550eb668067bf75d4
User & Date: milouse 2018-10-24 19:03:30
Context
2018-10-28
18:19
Add .rubocop.yml file check-in: 0d9a885156 user: milouse tags: trunk
2018-10-24
19:03
feat: Add mail and sqlite3 support check-in: 6286f96edf user: milouse tags: trunk
2018-10-23
11:50
feat: Continue work on survey creation check-in: d72c6fa209 user: milouse tags: trunk
Changes

Changes to .fossil-settings/ignore-glob.

1
2
3
4
5
6
7


.ruby-gemset
.bundle
tmp/logs/application.log
tmp/pids/sinatra.pid
node_modules
public/paul.js*
polls/*.yml









>
>
1
2
3
4
5
6
7
8
9
.ruby-gemset
.bundle
tmp/logs/application.log
tmp/pids/sinatra.pid
node_modules
public/paul.js*
polls/*.yml
config/config.yml
config/polls.db

Changes to Gemfile.

1
2
3
4

5
6
7
8

9
10
11
12
13
14
# frozen_string_literal: true

source 'https://rubygems.org'


gem 'rainbow', '~> 2.2'
gem 'rake', '~> 12.0'
gem 'sinatra', '~> 2.0'
gem 'slim', '~> 3.0'

gem 'thin', '~> 1.7'

group :development do
  gem 'rspec'
  gem 'rubocop'
end




>




>






1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# frozen_string_literal: true

source 'https://rubygems.org'

gem 'mail', '~> 2.7'
gem 'rainbow', '~> 2.2'
gem 'rake', '~> 12.0'
gem 'sinatra', '~> 2.0'
gem 'slim', '~> 3.0'
gem 'sqlite3', '~> 1.3'
gem 'thin', '~> 1.7'

group :development do
  gem 'rspec'
  gem 'rubocop'
end

Changes to Gemfile.lock.

2
3
4
5
6
7
8



9
10
11
12
13
14
15
..
43
44
45
46
47
48
49

50
51
52
53
54
55
56
57
58
59
60
61

62
63
64
65
66
67

68
69
70
71
  remote: https://rubygems.org/
  specs:
    ast (2.4.0)
    daemons (1.2.6)
    diff-lcs (1.3)
    eventmachine (1.2.7)
    jaro_winkler (1.5.1)



    mustermann (1.0.3)
    parallel (1.12.1)
    parser (2.5.1.2)
      ast (~> 2.4.0)
    powerpack (0.1.2)
    rack (2.0.5)
    rack-protection (2.0.4)
................................................................................
      mustermann (~> 1.0)
      rack (~> 2.0)
      rack-protection (= 2.0.4)
      tilt (~> 2.0)
    slim (3.0.9)
      temple (>= 0.7.6, < 0.9)
      tilt (>= 1.3.3, < 2.1)

    temple (0.8.0)
    thin (1.7.2)
      daemons (~> 1.0, >= 1.0.9)
      eventmachine (~> 1.0, >= 1.0.4)
      rack (>= 1, < 3)
    tilt (2.0.8)
    unicode-display_width (1.4.0)

PLATFORMS
  ruby

DEPENDENCIES

  rainbow (~> 2.2)
  rake (~> 12.0)
  rspec
  rubocop
  sinatra (~> 2.0)
  slim (~> 3.0)

  thin (~> 1.7)

BUNDLED WITH
   1.16.6







>
>
>







 







>












>






>




2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
..
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
  remote: https://rubygems.org/
  specs:
    ast (2.4.0)
    daemons (1.2.6)
    diff-lcs (1.3)
    eventmachine (1.2.7)
    jaro_winkler (1.5.1)
    mail (2.7.1)
      mini_mime (>= 0.1.1)
    mini_mime (1.0.1)
    mustermann (1.0.3)
    parallel (1.12.1)
    parser (2.5.1.2)
      ast (~> 2.4.0)
    powerpack (0.1.2)
    rack (2.0.5)
    rack-protection (2.0.4)
................................................................................
      mustermann (~> 1.0)
      rack (~> 2.0)
      rack-protection (= 2.0.4)
      tilt (~> 2.0)
    slim (3.0.9)
      temple (>= 0.7.6, < 0.9)
      tilt (>= 1.3.3, < 2.1)
    sqlite3 (1.3.13)
    temple (0.8.0)
    thin (1.7.2)
      daemons (~> 1.0, >= 1.0.9)
      eventmachine (~> 1.0, >= 1.0.4)
      rack (>= 1, < 3)
    tilt (2.0.8)
    unicode-display_width (1.4.0)

PLATFORMS
  ruby

DEPENDENCIES
  mail (~> 2.7)
  rainbow (~> 2.2)
  rake (~> 12.0)
  rspec
  rubocop
  sinatra (~> 2.0)
  slim (~> 3.0)
  sqlite3 (~> 1.3)
  thin (~> 1.7)

BUNDLED WITH
   1.16.6

Added config/config.yml.example.













>
>
>
>
>
>
1
2
3
4
5
6
---
mail:
  from: paul@TODO
  smtp:
    address: localhost
    port: 25

Added lib/notification.rb.







































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# frozen_string_literal: true

require 'mail'
require 'yaml'

# Generic notification class.
# For now, will just send emails
class Notification
  attr_reader :method

  def initialize
    @method = :sendmail
    @from = 'paul@todo.fr'
    conf = load_settings
    if conf.has_key? 'mail'
      if conf['mail'].has_key? 'smtp'
        load_smtp_settings(conf['mail']['smtp'])
      end
      @from = conf['mail']['from'] if conf['mail'].has_key? 'from'
    else
      Mail.defaults do
        delivery_method @method
      end
    end
  end

  def settings
    Mail::Configuration.instance.delivery_method.settings
  end

  def send(dest, subject_line, content)
    m = Mail.new do
      to dest
      subject subject_line
      body content
    end
    m.from = @from
    m.header['User-agent'] = 'Paul Survey System'
    m.header['X-Mailer'] = 'Ruby Mail'
    m.deliver!
  end

  private

  def load_settings
    conf = File.join 'config', 'config.yml'
    return YAML.load_file(conf).freeze if File.exist? conf
    {}
  end

  def load_smtp_settings(conf)
    @method = :smtp
    opts = {
      address: conf['address'],
      port: conf['port']
    }
    [:domain, :user_name, :password, :authentication,
     :ssl, :authentication, :enable_starttls,
     :enable_starttls_auto].each do |o|
      os = o.to_s
      opts[o] = conf[os] if conf.has_key? os
    end
    Mail.defaults do
      delivery_method :smtp, opts
    end
  end
end

Changes to lib/survey.rb.

59
60
61
62
63
64
65
66
67

68
69
70




71
72
73
74
75
76
77
78
79
80
81
82

83
84
      @data = YAML.load_file(@poll_path).freeze
    else
      raise NameError, 'Survey not found'
    end
  end

  def extract_data(opts)
    @data[:title] = opts['title'] if opts.has_key? 'title'
    @data[:author] = opts['author'] if opts.has_key? 'author'

    @data[:poll_type] = opts['poll_type'] || 'yesno'
    @data[:maybe] = opts['maybe'].to_s.to_bool if opts.has_key? 'maybe'
    return unless opts.has_key? 'proposals'




    @data[:proposals] ||= []
    opts['proposals'].each do |prop|
      if prop.is_a? String
        @data[:proposals] << prop
      elsif prop.has_key?('label')
        @data[:proposals] << prop['label']
      elsif prop.has_key?('date')
        loc_time = '00:00'
        loc_time = prop['time'] if prop['time'] && prop['time'] != ''
        @data[:proposals] << [prop['date'], loc_time].join(' ')
      end
    end

  end
end







|
|
>



>
>
>
>












>


59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
      @data = YAML.load_file(@poll_path).freeze
    else
      raise NameError, 'Survey not found'
    end
  end

  def extract_data(opts)
    @data[:title] = opts['title']
    @data[:author] = opts['author']
    @data[:email] = opts['email']
    @data[:poll_type] = opts['poll_type'] || 'yesno'
    @data[:maybe] = opts['maybe'].to_s.to_bool if opts.has_key? 'maybe'
    return unless opts.has_key? 'proposals'
    extract_proposals(opts)
  end

  def extract_proposals(opts)
    @data[:proposals] ||= []
    opts['proposals'].each do |prop|
      if prop.is_a? String
        @data[:proposals] << prop
      elsif prop.has_key?('label')
        @data[:proposals] << prop['label']
      elsif prop.has_key?('date')
        loc_time = '00:00'
        loc_time = prop['time'] if prop['time'] && prop['time'] != ''
        @data[:proposals] << [prop['date'], loc_time].join(' ')
      end
    end
    @data
  end
end

Added lib/tasks/db.rake.



























































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# frozen_string_literal: true

require 'sqlite3'

namespace :db do
  desc 'Init polls database'
  task :init do
    db = SQLite3::Database.new 'config/polls.db'
    db.execute <<-SQL
    create table polls (
      pid text(25),
      created_at text(23),
      updated_at text(23)
    );
    SQL
    db.execute <<-SQL
    create table authorizations (
      pid text(25),
      email text(255),
      name text(255),
      key text(50),
      created_at text(23),
      updated_at text(23),
      valid_until text(23)
    );
    SQL
    db.close
  end
end

Added lib/tasks/mail.rake.







































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# frozen_string_literal: true

require './lib/notification'

namespace :mail do
  desc 'Send a test e-mail'
  task :test, [:dest] do |_, args|
    dest = args[:dest] || ENV['dest'] || ''
    n = Notification.new
    text = <<-MAILBODY.gsub(/^ {6}/, '')
      This is a test email from paul, the survey libre application.

      Have a good day,

      -Paul
    MAILBODY
    n.send(dest, 'Test email from paul', text)
  end
end

Changes to package-lock.json.

2489
2490
2491
2492
2493
2494
2495
2496

2497
2498
2499
2500
2501
2502
2503
....
2904
2905
2906
2907
2908
2909
2910
2911

2912
2913
2914
2915
2916
2917
2918
....
2960
2961
2962
2963
2964
2965
2966

2967
2968
2969
2970
2971
2972
2973
....
3003
3004
3005
3006
3007
3008
3009
3010

3011
3012
3013
3014
3015

3016
3017
3018
3019
3020
3021
3022
          "bundled": true,
          "dev": true,
          "optional": true
        },
        "ansi-regex": {
          "version": "2.1.1",
          "bundled": true,
          "dev": true

        },
        "aproba": {
          "version": "1.2.0",
          "bundled": true,
          "dev": true,
          "optional": true
        },
................................................................................
          "requires": {
            "glob": "^7.0.5"
          }
        },
        "safe-buffer": {
          "version": "5.1.1",
          "bundled": true,
          "dev": true

        },
        "safer-buffer": {
          "version": "2.1.2",
          "bundled": true,
          "dev": true,
          "optional": true
        },
................................................................................
            "safe-buffer": "~5.1.0"
          }
        },
        "strip-ansi": {
          "version": "3.0.1",
          "bundled": true,
          "dev": true,

          "requires": {
            "ansi-regex": "^2.0.0"
          }
        },
        "strip-json-comments": {
          "version": "2.0.1",
          "bundled": true,
................................................................................
          "requires": {
            "string-width": "^1.0.2"
          }
        },
        "wrappy": {
          "version": "1.0.2",
          "bundled": true,
          "dev": true

        },
        "yallist": {
          "version": "3.0.2",
          "bundled": true,
          "dev": true

        }
      }
    },
    "function-bind": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",







|
>







 







|
>







 







>







 







|
>




|
>







2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
....
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
....
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
....
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
          "bundled": true,
          "dev": true,
          "optional": true
        },
        "ansi-regex": {
          "version": "2.1.1",
          "bundled": true,
          "dev": true,
          "optional": true
        },
        "aproba": {
          "version": "1.2.0",
          "bundled": true,
          "dev": true,
          "optional": true
        },
................................................................................
          "requires": {
            "glob": "^7.0.5"
          }
        },
        "safe-buffer": {
          "version": "5.1.1",
          "bundled": true,
          "dev": true,
          "optional": true
        },
        "safer-buffer": {
          "version": "2.1.2",
          "bundled": true,
          "dev": true,
          "optional": true
        },
................................................................................
            "safe-buffer": "~5.1.0"
          }
        },
        "strip-ansi": {
          "version": "3.0.1",
          "bundled": true,
          "dev": true,
          "optional": true,
          "requires": {
            "ansi-regex": "^2.0.0"
          }
        },
        "strip-json-comments": {
          "version": "2.0.1",
          "bundled": true,
................................................................................
          "requires": {
            "string-width": "^1.0.2"
          }
        },
        "wrappy": {
          "version": "1.0.2",
          "bundled": true,
          "dev": true,
          "optional": true
        },
        "yallist": {
          "version": "3.0.2",
          "bundled": true,
          "dev": true,
          "optional": true
        }
      }
    },
    "function-bind": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",

Changes to paul.rb.

1
2
3
4

5
6

7
8
9
10
11
12
13
..
45
46
47
48
49
50
51













52
53
54
55
56
57
58
# frozen_string_literal: true

require 'sinatra/base'
require 'logger'

require 'slim/smart'
require './lib/survey'


# To be included:
# Digest https://ruby-doc.org/stdlib-2.4.4/libdoc/digest/rdoc/Digest.html
# SecureRandom https://ruby-doc.org/stdlib-2.4.4/libdoc/securerandom/rdoc/SecureRandom.html

# Main Sinatra application
class Paul < Sinatra::Base
................................................................................
      "#{uri}/p/#{survey.id}"
    end
  end

  post '/create' do
    p = Survey.new(params)
    p.save













    redirect "/p/#{p.id}"
  end

  get '/p/:poll_id' do
    begin
      @poll = Survey.new(params[:poll_id])
    rescue NameError => e


<

>


>







 







>
>
>
>
>
>
>
>
>
>
>
>
>







1
2

3
4
5
6
7
8
9
10
11
12
13
14
..
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# frozen_string_literal: true


require 'logger'
require 'sinatra/base'
require 'slim/smart'
require './lib/survey'
require './lib/notification'

# To be included:
# Digest https://ruby-doc.org/stdlib-2.4.4/libdoc/digest/rdoc/Digest.html
# SecureRandom https://ruby-doc.org/stdlib-2.4.4/libdoc/securerandom/rdoc/SecureRandom.html

# Main Sinatra application
class Paul < Sinatra::Base
................................................................................
      "#{uri}/p/#{survey.id}"
    end
  end

  post '/create' do
    p = Survey.new(params)
    p.save
    n = Notification.new
    text = <<-MAILBODY.gsub(/^ {6}/, '')
      Hi #{p.data[:author]},

      Your survey has been successfully published. You can find it here:

      #{share_url(p)}

      Have a good day,

      -Paul
    MAILBODY
    n.send(p.data[:email], 'Your survey as been created', text)
    redirect "/p/#{p.id}"
  end

  get '/p/:poll_id' do
    begin
      @poll = Survey.new(params[:poll_id])
    rescue NameError => e

Changes to views/form.slim.

3
4
5
6
7
8
9



10
11
12
13
14
15
16
    div.form_group
      label> for='titlefield' Title
      input#titlefield type='text' name='title' required=true
    div.form_group
      label> for='authorfield' Display name
      input#authorfield type='text' name='author' required=true
    div.form_group



      label> for='formtypefield' This is a
      select#formtypefield name='poll_type' required=true
        option value='yesno' selected=(@poll_type == 'yesno') Yes/No survey
        option value='date' selected=(@poll_type == 'date') Date poll
    div.form_group
      label
        input type='checkbox' name='maybe' value='yes'







>
>
>







3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    div.form_group
      label> for='titlefield' Title
      input#titlefield type='text' name='title' required=true
    div.form_group
      label> for='authorfield' Display name
      input#authorfield type='text' name='author' required=true
    div.form_group
      label> for='emailfield' Contact email
      input#emailfield type='text' name='email' required=true
    div.form_group
      label> for='formtypefield' This is a
      select#formtypefield name='poll_type' required=true
        option value='yesno' selected=(@poll_type == 'yesno') Yes/No survey
        option value='date' selected=(@poll_type == 'date') Date poll
    div.form_group
      label
        input type='checkbox' name='maybe' value='yes'