diff --git a/README.md b/README.md
index 7db80e4..36761c7 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,13 @@
-# README
+# Ruby on Rails front end for OpenSMTPD.
+
+An ISC Licensed RoR app to manage your OpemSMTPD users.
+
+## OpenBSD Instructions.
+
+doas pkg_add
+
+## Database schema
+Is derived from https://github.com/OpenSMTPD/OpenSMTPD-extras/blob/master/extras/tables/table-mysql/table-mysql.5
This README would normally document whatever steps are necessary to get the
application up and running.
@@ -21,4 +30,14 @@ Things you may want to cover:
* Deployment instructions
-* ...
+
+
+
+## Licensed
+
+Copyright 2022 - 2023 Jez Caudle
+
+Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
diff --git a/app/controllers/credentials_controller.rb b/app/controllers/credentials_controller.rb
new file mode 100644
index 0000000..bf16207
--- /dev/null
+++ b/app/controllers/credentials_controller.rb
@@ -0,0 +1,70 @@
+class CredentialsController < ApplicationController
+ before_action :set_credential, only: %i[ show edit update destroy ]
+
+ # GET /credentials or /credentials.json
+ def index
+ @credentials = Credential.all
+ end
+
+ # GET /credentials/1 or /credentials/1.json
+ def show
+ end
+
+ # GET /credentials/new
+ def new
+ @credential = Credential.new
+ end
+
+ # GET /credentials/1/edit
+ def edit
+ end
+
+ # POST /credentials or /credentials.json
+ def create
+ @credential = Credential.new(credential_params)
+
+ respond_to do |format|
+ if @credential.save
+ format.html { redirect_to credential_url(@credential), notice: "Credential was successfully created." }
+ format.json { render :show, status: :created, location: @credential }
+ else
+ format.html { render :new, status: :unprocessable_entity }
+ format.json { render json: @credential.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ # PATCH/PUT /credentials/1 or /credentials/1.json
+ def update
+ respond_to do |format|
+ if @credential.update(credential_params)
+ format.html { redirect_to credential_url(@credential), notice: "Credential was successfully updated." }
+ format.json { render :show, status: :ok, location: @credential }
+ else
+ format.html { render :edit, status: :unprocessable_entity }
+ format.json { render json: @credential.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ # DELETE /credentials/1 or /credentials/1.json
+ def destroy
+ @credential.destroy
+
+ respond_to do |format|
+ format.html { redirect_to credentials_url, notice: "Credential was successfully destroyed." }
+ format.json { head :no_content }
+ end
+ end
+
+ private
+ # Use callbacks to share common setup or constraints between actions.
+ def set_credential
+ @credential = Credential.find(params[:id])
+ end
+
+ # Only allow a list of trusted parameters through.
+ def credential_params
+ params.require(:credential).permit(:email, :password)
+ end
+end
diff --git a/app/helpers/credentials_helper.rb b/app/helpers/credentials_helper.rb
new file mode 100644
index 0000000..c224b23
--- /dev/null
+++ b/app/helpers/credentials_helper.rb
@@ -0,0 +1,2 @@
+module CredentialsHelper
+end
diff --git a/app/models/credential.rb b/app/models/credential.rb
new file mode 100644
index 0000000..bed8801
--- /dev/null
+++ b/app/models/credential.rb
@@ -0,0 +1,2 @@
+class Credential < ApplicationRecord
+end
diff --git a/app/views/credentials/_credential.html.erb b/app/views/credentials/_credential.html.erb
new file mode 100644
index 0000000..171f678
--- /dev/null
+++ b/app/views/credentials/_credential.html.erb
@@ -0,0 +1,12 @@
+
+
+ Email:
+ <%= credential.email %>
+
+
+
+ Password:
+ <%= credential.password %>
+
+
+
diff --git a/app/views/credentials/_credential.json.jbuilder b/app/views/credentials/_credential.json.jbuilder
new file mode 100644
index 0000000..2d75c93
--- /dev/null
+++ b/app/views/credentials/_credential.json.jbuilder
@@ -0,0 +1,2 @@
+json.extract! credential, :id, :email, :password, :created_at, :updated_at
+json.url credential_url(credential, format: :json)
diff --git a/app/views/credentials/_form.html.erb b/app/views/credentials/_form.html.erb
new file mode 100644
index 0000000..5d71a5a
--- /dev/null
+++ b/app/views/credentials/_form.html.erb
@@ -0,0 +1,27 @@
+<%= form_with(model: credential) do |form| %>
+ <% if credential.errors.any? %>
+
+
<%= pluralize(credential.errors.count, "error") %> prohibited this credential from being saved:
+
+
+ <% credential.errors.each do |error| %>
+ - <%= error.full_message %>
+ <% end %>
+
+
+ <% end %>
+
+
+ <%= form.label :email, style: "display: block" %>
+ <%= form.text_field :email %>
+
+
+
+ <%= form.label :password, style: "display: block" %>
+ <%= form.text_field :password %>
+
+
+
+ <%= form.submit %>
+
+<% end %>
diff --git a/app/views/credentials/edit.html.erb b/app/views/credentials/edit.html.erb
new file mode 100644
index 0000000..2b8f560
--- /dev/null
+++ b/app/views/credentials/edit.html.erb
@@ -0,0 +1,10 @@
+Editing credential
+
+<%= render "form", credential: @credential %>
+
+
+
+
+ <%= link_to "Show this credential", @credential %> |
+ <%= link_to "Back to credentials", credentials_path %>
+
diff --git a/app/views/credentials/index.html.erb b/app/views/credentials/index.html.erb
new file mode 100644
index 0000000..c04af8f
--- /dev/null
+++ b/app/views/credentials/index.html.erb
@@ -0,0 +1,14 @@
+<%= notice %>
+
+Credentials
+
+
+ <% @credentials.each do |credential| %>
+ <%= render credential %>
+
+ <%= link_to "Show this credential", credential %>
+
+ <% end %>
+
+
+<%= link_to "New credential", new_credential_path %>
diff --git a/app/views/credentials/index.json.jbuilder b/app/views/credentials/index.json.jbuilder
new file mode 100644
index 0000000..05ef6c7
--- /dev/null
+++ b/app/views/credentials/index.json.jbuilder
@@ -0,0 +1 @@
+json.array! @credentials, partial: "credentials/credential", as: :credential
diff --git a/app/views/credentials/new.html.erb b/app/views/credentials/new.html.erb
new file mode 100644
index 0000000..ed24c5d
--- /dev/null
+++ b/app/views/credentials/new.html.erb
@@ -0,0 +1,9 @@
+New credential
+
+<%= render "form", credential: @credential %>
+
+
+
+
+ <%= link_to "Back to credentials", credentials_path %>
+
diff --git a/app/views/credentials/show.html.erb b/app/views/credentials/show.html.erb
new file mode 100644
index 0000000..0cc1ca9
--- /dev/null
+++ b/app/views/credentials/show.html.erb
@@ -0,0 +1,10 @@
+<%= notice %>
+
+<%= render @credential %>
+
+
+ <%= link_to "Edit this credential", edit_credential_path(@credential) %> |
+ <%= link_to "Back to credentials", credentials_path %>
+
+ <%= button_to "Destroy this credential", @credential, method: :delete %>
+
diff --git a/app/views/credentials/show.json.jbuilder b/app/views/credentials/show.json.jbuilder
new file mode 100644
index 0000000..e53ba57
--- /dev/null
+++ b/app/views/credentials/show.json.jbuilder
@@ -0,0 +1 @@
+json.partial! "credentials/credential", credential: @credential
diff --git a/config/routes.rb b/config/routes.rb
index e4b288e..8047b0c 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,4 +1,5 @@
Rails.application.routes.draw do
+ resources :credentials
resources :virtuals
resources :domains
devise_for :users
diff --git a/db/migrate/20230510184959_create_credentials.rb b/db/migrate/20230510184959_create_credentials.rb
new file mode 100644
index 0000000..3ae421d
--- /dev/null
+++ b/db/migrate/20230510184959_create_credentials.rb
@@ -0,0 +1,10 @@
+class CreateCredentials < ActiveRecord::Migration[7.0]
+ def change
+ create_table :credentials do |t|
+ t.string :email
+ t.string :password
+
+ t.timestamps
+ end
+ end
+end
diff --git a/test/controllers/credentials_controller_test.rb b/test/controllers/credentials_controller_test.rb
new file mode 100644
index 0000000..2c8d856
--- /dev/null
+++ b/test/controllers/credentials_controller_test.rb
@@ -0,0 +1,48 @@
+require "test_helper"
+
+class CredentialsControllerTest < ActionDispatch::IntegrationTest
+ setup do
+ @credential = credentials(:one)
+ end
+
+ test "should get index" do
+ get credentials_url
+ assert_response :success
+ end
+
+ test "should get new" do
+ get new_credential_url
+ assert_response :success
+ end
+
+ test "should create credential" do
+ assert_difference("Credential.count") do
+ post credentials_url, params: { credential: { email: @credential.email, password: @credential.password } }
+ end
+
+ assert_redirected_to credential_url(Credential.last)
+ end
+
+ test "should show credential" do
+ get credential_url(@credential)
+ assert_response :success
+ end
+
+ test "should get edit" do
+ get edit_credential_url(@credential)
+ assert_response :success
+ end
+
+ test "should update credential" do
+ patch credential_url(@credential), params: { credential: { email: @credential.email, password: @credential.password } }
+ assert_redirected_to credential_url(@credential)
+ end
+
+ test "should destroy credential" do
+ assert_difference("Credential.count", -1) do
+ delete credential_url(@credential)
+ end
+
+ assert_redirected_to credentials_url
+ end
+end
diff --git a/test/fixtures/credentials.yml b/test/fixtures/credentials.yml
new file mode 100644
index 0000000..62ef0d8
--- /dev/null
+++ b/test/fixtures/credentials.yml
@@ -0,0 +1,9 @@
+# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
+
+one:
+ email: MyString
+ password: MyString
+
+two:
+ email: MyString
+ password: MyString
diff --git a/test/fixtures/virtuals.yml b/test/fixtures/virtuals.yml
index 798393d..3509592 100644
--- a/test/fixtures/virtuals.yml
+++ b/test/fixtures/virtuals.yml
@@ -1,9 +1,9 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
- email: MyString
- destination: MyString
+ email: abuse@example.net
+ destination: bob@example.net
two:
- email: MyString
- destination: MyString
+ email: postmaster@example.net
+ destination: bob@example.net
diff --git a/test/models/.virtual_test.rb.kate-swp b/test/models/.virtual_test.rb.kate-swp
new file mode 100644
index 0000000..f2a3218
Binary files /dev/null and b/test/models/.virtual_test.rb.kate-swp differ
diff --git a/test/models/credential_test.rb b/test/models/credential_test.rb
new file mode 100644
index 0000000..3396384
--- /dev/null
+++ b/test/models/credential_test.rb
@@ -0,0 +1,7 @@
+require "test_helper"
+
+class CredentialTest < ActiveSupport::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+end
diff --git a/test/system/credentials_test.rb b/test/system/credentials_test.rb
new file mode 100644
index 0000000..a6c05d6
--- /dev/null
+++ b/test/system/credentials_test.rb
@@ -0,0 +1,43 @@
+require "application_system_test_case"
+
+class CredentialsTest < ApplicationSystemTestCase
+ setup do
+ @credential = credentials(:one)
+ end
+
+ test "visiting the index" do
+ visit credentials_url
+ assert_selector "h1", text: "Credentials"
+ end
+
+ test "should create credential" do
+ visit credentials_url
+ click_on "New credential"
+
+ fill_in "Email", with: @credential.email
+ fill_in "Password", with: @credential.password
+ click_on "Create Credential"
+
+ assert_text "Credential was successfully created"
+ click_on "Back"
+ end
+
+ test "should update Credential" do
+ visit credential_url(@credential)
+ click_on "Edit this credential", match: :first
+
+ fill_in "Email", with: @credential.email
+ fill_in "Password", with: @credential.password
+ click_on "Update Credential"
+
+ assert_text "Credential was successfully updated"
+ click_on "Back"
+ end
+
+ test "should destroy Credential" do
+ visit credential_url(@credential)
+ click_on "Destroy this credential", match: :first
+
+ assert_text "Credential was successfully destroyed"
+ end
+end