Add bots scaffold

This commit is contained in:
Jesse C. Fisher 2023-11-28 12:04:44 +00:00 committed by TheNeedful-DO
parent e920a8feb9
commit 11077b1dc4
31 changed files with 665 additions and 1 deletions

View File

@ -83,3 +83,4 @@ gem 'importmap-rails'
gem 'turbo-rails'
gem 'stimulus-rails'
gem 'will_paginate'
gem 'telegram-bot-ruby', '~> 1.0'

View File

@ -163,6 +163,25 @@ GEM
dotenv-rails (2.8.1)
dotenv (= 2.8.1)
railties (>= 3.2)
dry-core (1.0.0)
concurrent-ruby (~> 1.0)
zeitwerk (~> 2.6)
dry-inflector (1.0.0)
dry-logic (1.5.0)
concurrent-ruby (~> 1.0)
dry-core (~> 1.0, < 2)
zeitwerk (~> 2.6)
dry-struct (1.6.0)
dry-core (~> 1.0, < 2)
dry-types (>= 1.7, < 2)
ice_nine (~> 0.11)
zeitwerk (~> 2.6)
dry-types (1.7.1)
concurrent-ruby (~> 1.0)
dry-core (~> 1.0)
dry-inflector (~> 1.0)
dry-logic (~> 1.4)
zeitwerk (~> 2.6)
ed25519 (1.3.0)
em-websocket (0.5.3)
eventmachine (>= 0.12.9)
@ -178,6 +197,13 @@ GEM
railties (>= 3.0.0)
faker (3.2.1)
i18n (>= 1.8.11, < 2)
faraday (2.7.12)
base64
faraday-net_http (>= 2.0, < 3.1)
ruby2_keywords (>= 0.0.4)
faraday-multipart (1.0.4)
multipart-post (~> 2)
faraday-net_http (3.0.2)
ffi (1.16.3)
formatador (1.1.0)
friendly_id (5.4.2)
@ -232,6 +258,7 @@ GEM
hub (1.12.4)
i18n (1.14.1)
concurrent-ruby (~> 1.0)
ice_nine (0.11.2)
importmap-rails (1.2.1)
actionpack (>= 6.0.0)
railties (>= 6.0.0)
@ -271,6 +298,7 @@ GEM
mini_portile2 (2.8.5)
minitest (5.20.0)
multi_json (1.15.0)
multipart-post (2.3.0)
nenv (0.3.0)
net-imap (0.4.2)
date
@ -410,6 +438,7 @@ GEM
rubocop-ast (1.29.0)
parser (>= 3.2.1.0)
ruby-progressbar (1.13.0)
ruby2_keywords (0.0.5)
ruby_parser (3.20.3)
sexp_processor (~> 4.16)
rubyzip (2.3.2)
@ -458,6 +487,11 @@ GEM
stimulus-rails (1.3.0)
railties (>= 6.0.0)
stringio (3.0.8)
telegram-bot-ruby (1.0.0)
dry-struct (~> 1.6)
faraday (~> 2.0)
faraday-multipart (~> 1.0)
zeitwerk (~> 2.6)
temple (0.10.3)
thor (1.3.0)
tilt (2.3.0)
@ -557,6 +591,7 @@ DEPENDENCIES
spring-commands-rspec
sprockets
stimulus-rails
telegram-bot-ruby (~> 1.0)
turbo-rails
uglifier
webdrivers

View File

@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

View File

@ -0,0 +1,3 @@
// Place all the styles related to the Bots controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: https://sass-lang.com/

View File

@ -0,0 +1,71 @@
class BotsController < ApplicationController
before_action :set_bot, only: %i[ show edit update destroy ]
# GET /bots or /bots.json
def index
authorize @bots = policy_scope(Bot)
end
# GET /bots/1 or /bots/1.json
def show
authorize @bot
end
# GET /bots/new
def new
authorize @bot = current_user.bots.new
end
# GET /bots/1/edit
def edit
end
# POST /bots or /bots.json
def create
authorize @bot = Bot.new(bot_params)
respond_to do |format|
if @bot.save
format.html { redirect_to bot_url(@bot), notice: "Bot was successfully created." }
format.json { render :show, status: :created, location: @bot }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: @bot.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /bots/1 or /bots/1.json
def update
respond_to do |format|
if @bot.update(bot_params)
format.html { redirect_to bot_url(@bot), notice: "Bot was successfully updated." }
format.json { render :show, status: :ok, location: @bot }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @bot.errors, status: :unprocessable_entity }
end
end
end
# DELETE /bots/1 or /bots/1.json
def destroy
@bot.destroy
respond_to do |format|
format.html { redirect_to bots_url, notice: "Bot was successfully destroyed." }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_bot
@bot = Bot.find(params[:id])
end
# Only allow a list of trusted parameters through.
def bot_params
params.require(:bot).permit(:name, :telegram_api_token, :discord_api_token, :user_id, :profile_id)
end
end

View File

@ -0,0 +1,2 @@
module BotsHelper
end

41
app/models/bot.rb Normal file
View File

@ -0,0 +1,41 @@
require 'telegram/bot'
class Bot < ActiveRecord::Base
belongs_to :user
belongs_to :profile, optional: true
# def send_text
# if self.telegram_api_token
# Telegram::Bot::Client.run(telegram_api_token) do |bot|
# bot.api.send_message(chat_id: message.chat.id, text: response_text, parse_mode: "Markdown")
# end
# end
# end
def fortune
`fortune -a`
end
def telegram_listen
return unless self.telegram_api_token
Telegram::Bot::Client.run(telegram_api_token) do |bot|
bot.listen do |message|
# TODO Logging
# File.open("./logs/#{@botname}.log", 'a') { |f| f.write(["\n", Time.now, message.chat.id, message.from.id, message.from.first_name, message].join("\t")) }
case message
when Telegram::Bot::Types::Message
# Debugging use next to skip/drop the queue
# next
case message.text
when /^\/fortune/i
response_text = `fortune -a`
begin
bot.api.send_message(chat_id: message.chat.id, reply_to_message_id: message.message_id, text: response_text, parse_mode: "Markdown")
rescue
puts "Error from fortune"
end
end
end
end
end
end
end

View File

@ -6,6 +6,7 @@ class Profile < ActiveRecord::Base
dependent: :destroy
# has_many :followers, through: :passive_relationships, source: :follower
has_many :microposts, as: :micropostable
has_many :bots
def followers
Relationship.where("followable_id = ? AND followable_type = ?", self.id, self.class.to_s)

View File

@ -14,6 +14,7 @@ class User < ActiveRecord::Base
has_many :following_profiles, through: :active_relationships, source: :followable, source_type: "Profile"
has_many :following_projects, through: :active_relationships, source: :followable, source_type: "Project"
has_many :microposts, as: :micropostable
has_many :bots
def icid
stp = SoftTokenPrincipal.where(soft_token: self.soft_token).first_or_initialize

View File

@ -0,0 +1,55 @@
class BotPolicy
attr_reader :user, :model
def initialize(user, model)
@user = user || User.new
@bot = model
end
class Scope
def initialize(user, scope)
@user = user
@scope = scope
end
def resolve
if user.admin?
scope.all
else
scope.all
end
end
private
attr_reader :user, :scope
end
def index?
@user.admin?
end
def show?
@user.admin? || @user == @bot.user
end
def new?
@user.admin?
end
def create?
@user.admin?
end
def edit?
@user.admin? || @user == @bot.user
end
def update?
@user.admin? || @user == @bot.user
end
def destroy?
@user.admin? || @user == @bot.user
end
end

View File

@ -0,0 +1,2 @@
json.extract! bot, :id, :name, :telegram_api_token, :discord_api_token, :user_id, :profile_id, :created_at, :updated_at
json.url bot_url(bot, format: :json)

View File

@ -0,0 +1,12 @@
= simple_form_for(@bot) do |f|
= f.error_notification
.form-inputs
= f.input :name
= f.input :telegram_api_token
= f.input :discord_api_token
= f.hidden_field :user_id
= f.association :profile
.form-actions
= f.button :submit

View File

@ -0,0 +1,7 @@
%h1 Editing bot
= render 'form'
= link_to 'Show', @bot
\|
= link_to 'Back', bots_path

View File

@ -0,0 +1,51 @@
.contain
%h1 Listing bots
%div{style: "align-self: start;"}
- if policy(Bot).new?
= link_to '+ New Bot', new_bot_path
.contain
- @bots.each do |bot|
= link_to bot, class: "bot" do
%h1= bot.name
-# %span= bot.summary
-# .social-links
- bot.social_links.each do |social_link|
= social_link.to_icon.html_safe
-# %p= bot.about
-# %span #{bot.followers.count} Followers
-# &nbsp;&nbsp;
-# %span #{bot.microposts.count} Posts
- if policy(bot).destroy?
= link_to 'Edit', edit_bot_path(bot)
= link_to 'Delete', bot, method: :delete, data: { confirm: 'Are you sure?' }
-# %h1 Listing bots
-# %table
-# %thead
-# %tr
-# %th Name
-# %th Telegram api token
-# %th Discord api token
-# %th User
-# %th Profile
-# %th
-# %th
-# %th
-# %tbody
-# - @bots.each do |bot|
-# %tr
-# %td= bot.name
-# %td= bot.telegram_api_token
-# %td= bot.discord_api_token
-# %td= bot.user
-# %td= bot.profile
-# %td= link_to 'Show', bot
-# %td= link_to 'Edit', edit_bot_path(bot)
-# %td= link_to 'Destroy', bot, method: :delete, data: { confirm: 'Are you sure?' }
-# %br
-# = link_to 'New Bot', new_bot_path

View File

@ -0,0 +1 @@
json.array! @bots, partial: "bots/bot", as: :bot

View File

@ -0,0 +1,5 @@
%h1 New bot
= render 'form'
= link_to 'Back', bots_path

View File

@ -0,0 +1,21 @@
.contain
%span 🤖
%h1
= @bot.name
- if policy(@bot).edit?
= link_to 'Edit', edit_bot_path(@bot)
%p
%b Telegram api token:
= @bot.telegram_api_token
%p
%b Discord api token:
= @bot.discord_api_token
%p
%b User:
= link_to @bot.user, @bot.user
- if @bot.profile
%p
%b Profile:
= link_to @bot.profile.display_name, @bot.profile

View File

@ -0,0 +1 @@
json.partial! "bots/bot", bot: @bot

View File

@ -8,6 +8,9 @@
<li><%= link_to 'Profiles', profiles_path %></li>
<li><%= link_to 'Projects', projects_path %></li>
<li><%= link_to 'Doodads', doodads_path, data: { turbo: false } %></li>
<% if policy(:bot).index? %>
<li><%= link_to 'Bots', bots_path %></li>
<% end %>
<% if policy(:table).index? %>
<li><%= link_to 'Tables', tables_path %></li>
<% end %>

View File

@ -1,4 +1,5 @@
Rails.application.routes.draw do
resources :bots
resources :social_links
resources :profiles do
member do

View File

@ -0,0 +1,13 @@
class CreateBots < ActiveRecord::Migration[6.1]
def change
create_table :bots do |t|
t.string :name
t.string :telegram_api_token
t.string :discord_api_token
t.belongs_to :user, null: false, foreign_key: true
t.belongs_to :profile, foreign_key: true
t.timestamps
end
end
end

View File

@ -10,11 +10,23 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2023_11_22_115709) do
ActiveRecord::Schema.define(version: 2023_11_26_154137) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "bots", force: :cascade do |t|
t.string "name"
t.string "telegram_api_token"
t.string "discord_api_token"
t.bigint "user_id", null: false
t.bigint "profile_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["profile_id"], name: "index_bots_on_profile_id"
t.index ["user_id"], name: "index_bots_on_user_id"
end
create_table "canisters", force: :cascade do |t|
t.string "icid"
t.string "name"
@ -224,6 +236,8 @@ ActiveRecord::Schema.define(version: 2023_11_22_115709) do
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
add_foreign_key "bots", "profiles"
add_foreign_key "bots", "users"
add_foreign_key "canisters", "users"
add_foreign_key "doodads", "users"
add_foreign_key "profiles", "users"

9
spec/factories/bots.rb Normal file
View File

@ -0,0 +1,9 @@
FactoryGirl.define do
factory :bot do
name "MyString"
telegram_api_token "MyString"
discord_api_token "MyString"
user nil
profile nil
end
end

View File

@ -0,0 +1,15 @@
require 'rails_helper'
# Specs in this file have access to a helper object that includes
# the BotsHelper. For example:
#
# describe BotsHelper 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 BotsHelper, type: :helper do
pending "add some examples to (or delete) #{__FILE__}"
end

5
spec/models/bot_spec.rb Normal file
View File

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe Bot, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end

135
spec/requests/bots_spec.rb Normal file
View File

@ -0,0 +1,135 @@
require 'rails_helper'
# This spec was generated by rspec-rails when you ran the scaffold generator.
# It demonstrates how one might use RSpec to test the controller code that
# was generated by Rails when you ran the scaffold generator.
#
# It assumes that the implementation code is generated by the rails scaffold
# generator. If you are using any extension libraries to generate different
# controller code, this generated spec may or may not pass.
#
# It only uses APIs available in rails and/or rspec-rails. There are a number
# of tools you can use to make these specs even more expressive, but we're
# sticking to rails and rspec-rails APIs to keep things simple and stable.
RSpec.describe "/bots", type: :request do
# This should return the minimal set of attributes required to create a valid
# Bot. As you add validations to Bot, be sure to
# adjust the attributes here as well.
let(:valid_attributes) {
skip("Add a hash of attributes valid for your model")
}
let(:invalid_attributes) {
skip("Add a hash of attributes invalid for your model")
}
describe "GET /index" do
it "renders a successful response" do
Bot.create! valid_attributes
get bots_url
expect(response).to be_successful
end
end
describe "GET /show" do
it "renders a successful response" do
bot = Bot.create! valid_attributes
get bot_url(bot)
expect(response).to be_successful
end
end
describe "GET /new" do
it "renders a successful response" do
get new_bot_url
expect(response).to be_successful
end
end
describe "GET /edit" do
it "renders a successful response" do
bot = Bot.create! valid_attributes
get edit_bot_url(bot)
expect(response).to be_successful
end
end
describe "POST /create" do
context "with valid parameters" do
it "creates a new Bot" do
expect {
post bots_url, params: { bot: valid_attributes }
}.to change(Bot, :count).by(1)
end
it "redirects to the created bot" do
post bots_url, params: { bot: valid_attributes }
expect(response).to redirect_to(bot_url(Bot.last))
end
end
context "with invalid parameters" do
it "does not create a new Bot" do
expect {
post bots_url, params: { bot: invalid_attributes }
}.to change(Bot, :count).by(0)
end
it "renders a successful response (i.e. to display the 'new' template)" do
post bots_url, params: { bot: invalid_attributes }
expect(response).to be_successful
end
end
end
describe "PATCH /update" do
context "with valid parameters" do
let(:new_attributes) {
skip("Add a hash of attributes valid for your model")
}
it "updates the requested bot" do
bot = Bot.create! valid_attributes
patch bot_url(bot), params: { bot: new_attributes }
bot.reload
skip("Add assertions for updated state")
end
it "redirects to the bot" do
bot = Bot.create! valid_attributes
patch bot_url(bot), params: { bot: new_attributes }
bot.reload
expect(response).to redirect_to(bot_url(bot))
end
end
context "with invalid parameters" do
it "renders a successful response (i.e. to display the 'edit' template)" do
bot = Bot.create! valid_attributes
patch bot_url(bot), params: { bot: invalid_attributes }
expect(response).to be_successful
end
end
end
describe "DELETE /destroy" do
it "destroys the requested bot" do
bot = Bot.create! valid_attributes
expect {
delete bot_url(bot)
}.to change(Bot, :count).by(-1)
end
it "redirects to the bots list" do
bot = Bot.create! valid_attributes
delete bot_url(bot)
expect(response).to redirect_to(bots_url)
end
end
end

View File

@ -0,0 +1,38 @@
require "rails_helper"
RSpec.describe BotsController, type: :routing do
describe "routing" do
it "routes to #index" do
expect(get: "/bots").to route_to("bots#index")
end
it "routes to #new" do
expect(get: "/bots/new").to route_to("bots#new")
end
it "routes to #show" do
expect(get: "/bots/1").to route_to("bots#show", id: "1")
end
it "routes to #edit" do
expect(get: "/bots/1/edit").to route_to("bots#edit", id: "1")
end
it "routes to #create" do
expect(post: "/bots").to route_to("bots#create")
end
it "routes to #update via PUT" do
expect(put: "/bots/1").to route_to("bots#update", id: "1")
end
it "routes to #update via PATCH" do
expect(patch: "/bots/1").to route_to("bots#update", id: "1")
end
it "routes to #destroy" do
expect(delete: "/bots/1").to route_to("bots#destroy", id: "1")
end
end
end

View File

@ -0,0 +1,34 @@
require 'rails_helper'
RSpec.describe "bots/edit", type: :view do
let(:bot) {
Bot.create!(
name: "MyString",
telegram_api_token: "MyString",
discord_api_token: "MyString",
user: nil,
profile: nil
)
}
before(:each) do
assign(:bot, bot)
end
it "renders the edit bot form" do
render
assert_select "form[action=?][method=?]", bot_path(bot), "post" do
assert_select "input[name=?]", "bot[name]"
assert_select "input[name=?]", "bot[telegram_api_token]"
assert_select "input[name=?]", "bot[discord_api_token]"
assert_select "input[name=?]", "bot[user_id]"
assert_select "input[name=?]", "bot[profile_id]"
end
end
end

View File

@ -0,0 +1,32 @@
require 'rails_helper'
RSpec.describe "bots/index", type: :view do
before(:each) do
assign(:bots, [
Bot.create!(
name: "Name",
telegram_api_token: "Telegram Api Token",
discord_api_token: "Discord Api Token",
user: nil,
profile: nil
),
Bot.create!(
name: "Name",
telegram_api_token: "Telegram Api Token",
discord_api_token: "Discord Api Token",
user: nil,
profile: nil
)
])
end
it "renders a list of bots" do
render
cell_selector = Rails::VERSION::STRING >= '7' ? 'div>p' : 'tr>td'
assert_select cell_selector, text: Regexp.new("Name".to_s), count: 2
assert_select cell_selector, text: Regexp.new("Telegram Api Token".to_s), count: 2
assert_select cell_selector, text: Regexp.new("Discord Api Token".to_s), count: 2
assert_select cell_selector, text: Regexp.new(nil.to_s), count: 2
assert_select cell_selector, text: Regexp.new(nil.to_s), count: 2
end
end

View File

@ -0,0 +1,30 @@
require 'rails_helper'
RSpec.describe "bots/new", type: :view do
before(:each) do
assign(:bot, Bot.new(
name: "MyString",
telegram_api_token: "MyString",
discord_api_token: "MyString",
user: nil,
profile: nil
))
end
it "renders new bot form" do
render
assert_select "form[action=?][method=?]", bots_path, "post" do
assert_select "input[name=?]", "bot[name]"
assert_select "input[name=?]", "bot[telegram_api_token]"
assert_select "input[name=?]", "bot[discord_api_token]"
assert_select "input[name=?]", "bot[user_id]"
assert_select "input[name=?]", "bot[profile_id]"
end
end
end

View File

@ -0,0 +1,22 @@
require 'rails_helper'
RSpec.describe "bots/show", type: :view do
before(:each) do
assign(:bot, Bot.create!(
name: "Name",
telegram_api_token: "Telegram Api Token",
discord_api_token: "Discord Api Token",
user: nil,
profile: nil
))
end
it "renders attributes in <p>" do
render
expect(rendered).to match(/Name/)
expect(rendered).to match(/Telegram Api Token/)
expect(rendered).to match(/Discord Api Token/)
expect(rendered).to match(//)
expect(rendered).to match(//)
end
end