diff --git a/README.md b/README.md index 57c819c..6a1d5a6 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ credentials, invites, donations, etc.. * [x] View LDAP users as admin * [ ] List my donations * [ ] Invite new users from your account -* [ ] Sign up for a new account via invite +* [ ] Sign up for a new account via invitation * [ ] Sign up for a new account by donating upfront * [ ] Sign up for a new account via proving contributions (via cryptographic signature) * [ ] ... diff --git a/app/controllers/invitations_controller.rb b/app/controllers/invitations_controller.rb new file mode 100644 index 0000000..7809a07 --- /dev/null +++ b/app/controllers/invitations_controller.rb @@ -0,0 +1,33 @@ +class InvitationsController < ApplicationController + before_action :require_user_signed_in + + # GET /invitations + def index + @invitations = current_user.invitations + end + + # POST /invitations + def create + @invitation = Invitation.new(user: current_user) + + respond_to do |format| + if @invitation.save + format.html { redirect_to @invitation, notice: 'Invitation was successfully created.' } + format.json { render :show, status: :created, location: @invitation } + else + format.html { render :new } + format.json { render json: @invitation.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /invitations/1 + def destroy + @invitation = current_user.invitations.find(params[:id]) + @invitation.destroy + respond_to do |format| + format.html { redirect_to invitations_url } + format.json { head :no_content } + end + end +end diff --git a/app/helpers/invitations_helper.rb b/app/helpers/invitations_helper.rb new file mode 100644 index 0000000..1483b9e --- /dev/null +++ b/app/helpers/invitations_helper.rb @@ -0,0 +1,2 @@ +module InvitationsHelper +end diff --git a/app/models/invitation.rb b/app/models/invitation.rb new file mode 100644 index 0000000..74c3998 --- /dev/null +++ b/app/models/invitation.rb @@ -0,0 +1,14 @@ +class Invitation < ApplicationRecord + # Relations + belongs_to :user + + validates_presence_of :user + + before_create :generate_token + + private + + def generate_token + self.token = SecureRandom.hex(8) + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 4d60b98..bc28e90 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,4 +1,7 @@ class User < ApplicationRecord + # Relations + has_many :invitations, dependent: :destroy + # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable devise :ldap_authenticatable, diff --git a/app/views/invitations/index.html.erb b/app/views/invitations/index.html.erb new file mode 100644 index 0000000..4e081ec --- /dev/null +++ b/app/views/invitations/index.html.erb @@ -0,0 +1,23 @@ +
+

Invitations

+ + + + + + + + + + + + <% @invitations.each do |invitation| %> + + + + + + <% end %> + +
TokenCreated at
<%= invitation.token %><%= invitation.created_at %><%= link_to 'Delete', invitation, method: :delete, data: { confirm: 'Are you sure?' } %>
+
diff --git a/config/routes.rb b/config/routes.rb index 72a33b2..8b08a0f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,11 +1,13 @@ Rails.application.routes.draw do devise_for :users + get 'welcome', to: 'welcome#index' + get 'check_your_email', to: 'welcome#check_your_email' + get 'settings', to: 'settings#index' post 'settings_reset_password', to: 'settings#reset_password' - get 'welcome', to: 'welcome#index' - get 'check_your_email', to: 'welcome#check_your_email' + resources :invitations, only: ['index', 'create', 'destroy'] namespace :admin do root to: 'dashboard#index' diff --git a/db/migrate/20201130132533_create_invitations.rb b/db/migrate/20201130132533_create_invitations.rb new file mode 100644 index 0000000..75a410a --- /dev/null +++ b/db/migrate/20201130132533_create_invitations.rb @@ -0,0 +1,12 @@ +class CreateInvitations < ActiveRecord::Migration[6.0] + def change + create_table :invitations do |t| + t.string :token + t.integer :user_id + t.integer :invited_user_id + t.datetime :used_at + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index d2d374e..3d99780 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,16 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2020_11_09_090739) do +ActiveRecord::Schema.define(version: 2020_11_30_132533) do + + create_table "invitations", force: :cascade do |t| + t.string "token" + t.integer "user_id" + t.integer "invited_user_id" + t.datetime "used_at" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + end create_table "users", force: :cascade do |t| t.string "cn" diff --git a/spec/fixtures/invitations.yml b/spec/fixtures/invitations.yml new file mode 100644 index 0000000..e856184 --- /dev/null +++ b/spec/fixtures/invitations.yml @@ -0,0 +1,13 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + token: fedcba654321 + user_id: 1 + invited_user_id: nil + used_at: nil + +two: + token: abcdef123456 + user_id: 1 + invited_user_id: nil + used_at: nil diff --git a/spec/helpers/invitations_helper_spec.rb b/spec/helpers/invitations_helper_spec.rb new file mode 100644 index 0000000..4ca2783 --- /dev/null +++ b/spec/helpers/invitations_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the InvitationsHelper. For example: +# +# describe InvitationsHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe InvitationsHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/invitation_spec.rb b/spec/models/invitation_spec.rb new file mode 100644 index 0000000..7f7b953 --- /dev/null +++ b/spec/models/invitation_spec.rb @@ -0,0 +1,22 @@ +require 'rails_helper' + +RSpec.describe Invitation, type: :model do + let(:user) { build :user } + + describe "tokens" do + it "requires a user" do + expect { Invitation.create! }.to raise_error(ActiveRecord::RecordInvalid) + end + + it "generates a random token when created" do + invitation = Invitation.new(user: user) + invitation.save! + token = invitation.token + expect(token).to be_a(String) + expect(token.length).to eq(16) + + invitation_2 = Invitation.create(user: user) + expect(token).not_to eq(invitation_2.token) + end + end +end