Browse Source

switch to active record

master
Richard Cagle 1 year ago
parent
commit
7f6ba06299
15 changed files with 214 additions and 264 deletions
  1. 1
    1
      .gitignore
  2. 6
    2
      Gemfile
  3. 10
    2
      Gemfile.lock
  4. 50
    0
      Rakefile
  5. 14
    1
      app.rb
  6. 29
    31
      basecamp3.rb
  7. 16
    0
      database.yml
  8. 0
    227
      db.rb
  9. 60
    0
      db/migrate/001_schema.rb
  10. 3
    0
      models/bucket.rb
  11. 3
    0
      models/person.rb
  12. 4
    0
      models/todo_item.rb
  13. 4
    0
      models/todo_item_assignee.rb
  14. 4
    0
      models/todo_list.rb
  15. 10
    0
      models/token.rb

+ 1
- 1
.gitignore View File

@@ -1,5 +1,5 @@
/.env
/basecamp_tokens.json
/b3-big-picture.sqlite3
/debug.log
.DS_Store

+ 6
- 2
Gemfile View File

@@ -18,6 +18,10 @@ gem "erb_lint", "~> 0.0.30"

gem "dotenv", "~> 2.7"

gem "sqlite3", "~> 1.4"

gem "awesome_print", "~> 1.8"

gem "pg", "~> 1.2"

gem "activerecord", "~> 6.0"

gem "rake", "~> 13.0"

+ 10
- 2
Gemfile.lock View File

@@ -7,6 +7,11 @@ GEM
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
activemodel (6.0.2.1)
activesupport (= 6.0.2.1)
activerecord (6.0.2.1)
activemodel (= 6.0.2.1)
activesupport (= 6.0.2.1)
activesupport (6.0.2.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
@@ -64,6 +69,7 @@ GEM
parallel (1.19.1)
parser (2.7.0.3)
ast (~> 2.4.0)
pg (1.2.2)
rack (2.2.2)
rack-protection (2.0.8.1)
rack
@@ -73,6 +79,7 @@ GEM
rails-html-sanitizer (1.3.0)
loofah (~> 2.3)
rainbow (3.0.0)
rake (13.0.1)
rexml (3.2.4)
rubocop (0.80.1)
jaro_winkler (~> 1.5.1)
@@ -97,7 +104,6 @@ GEM
sinatra (= 2.0.8.1)
tilt (~> 2.0)
smart_properties (1.15.0)
sqlite3 (1.4.2)
thread_safe (0.3.6)
tilt (2.0.10)
tzinfo (1.2.6)
@@ -109,14 +115,16 @@ PLATFORMS
ruby

DEPENDENCIES
activerecord (~> 6.0)
awesome_print (~> 1.8)
dotenv (~> 2.7)
erb_lint (~> 0.0.30)
oauth2 (~> 1.4)
pg (~> 1.2)
rake (~> 13.0)
rubocop (~> 0.80.1)
sinatra (~> 2.0)
sinatra-contrib (~> 2.0)
sqlite3 (~> 1.4)

RUBY VERSION
ruby 2.6.5p114

+ 50
- 0
Rakefile View File

@@ -0,0 +1,50 @@

require "dotenv/load"
require "bundler/setup"
require "active_record"
require "pg"
require "logger"
require "sinatra"
require "sinatra/reloader" if development?
ActiveRecord::Base.logger = Logger.new('debug.log')
configuration = YAML::load(IO.read('database.yml'))
ActiveRecord::Base.establish_connection(configuration[ENV.fetch("APP_ENV")].merge({database: ENV.fetch("DB_NAME")}))

task :default => :migrate

def schema_migrations
ActiveRecord::SchemaMigration.tap do |sm|
sm.create_table
end
end

def migration_context
ActiveRecord::MigrationContext.new('db/migrate', schema_migrations)
end

desc "Create database"
task :create_database do
configuration = YAML::load(IO.read('database.yml'))
ActiveRecord::Base.establish_connection(configuration['development'].merge({'database': 'postgres', 'schema_search_path': 'public'}))
ActiveRecord::Base.connection.create_database(ENV.fetch("DB_NAME"))
puts "Created database: #{ENV.fetch("DB_NAME")}"
end

desc "Run migrations"
task :migrate do
migration_context.migrate
end

desc "Rollback migrations"
task :rollback do
migration_context.rollback
end

desc "Drop database"
task :drop_database do
configuration = YAML::load(IO.read('database.yml'))
ActiveRecord::Base.establish_connection(configuration['development'].merge({'database': 'postgres', 'schema_search_path': 'public'}))
ActiveRecord::Base.connection.drop_database(ENV.fetch("DB_NAME"))
puts "Dropped database: #{ENV.fetch("DB_NAME")}"
end

+ 14
- 1
app.rb View File

@@ -2,10 +2,23 @@

require "dotenv/load"
require "bundler/setup"
require "active_record"
require "pg"
require "logger"
require "sinatra"
require "sinatra/reloader" if development?
ActiveRecord::Base.logger = Logger.new('debug.log')
configuration = YAML::load(IO.read('database.yml'))
ActiveRecord::Base.establish_connection(configuration[ENV.fetch("APP_ENV")].merge({database: ENV.fetch("DB_NAME")}))

require_relative "basecamp"
require_relative "models/bucket.rb"
require_relative "models/person.rb"
require_relative "models/todo_item.rb"
require_relative "models/todo_item_assignee.rb"
require_relative "models/todo_list.rb"
require_relative "models/token.rb"
require_relative "basecamp3"

b3 = Basecamp3.new


basecamp.rb → basecamp3.rb View File

@@ -1,12 +1,9 @@
# frozen_string_literal: true

require "json"
require "oauth2"

require_relative "db"

class Basecamp3
attr_accessor :client, :token, :api_url, :auth_info, :authorize_url, :db
attr_accessor :client, :token, :api_url, :auth_info, :authorize_url

def initialize
@client = OAuth2::Client.new(
@@ -17,13 +14,11 @@ class Basecamp3
token_url: "/authorization/token"
)

@db = DB.new

@authorize_url = @client.auth_code.authorize_url(redirect_uri: ENV["REDIRECT_URL"], type: "web_server")

unless @db.token_data(ENV["BASECAMP_CLIENT_ID"]).nil?
token_data = @db.token_data(ENV["BASECAMP_CLIENT_ID"])
@token = OAuth2::AccessToken.from_hash(@client, token_data)
unless Token.first.blank?
token_record = Token.find_by(client_id: ENV["BASECAMP_CLIENT_ID"])
@token = OAuth2::AccessToken.from_hash(@client, token_record.oauth_hash)
@token = @token.refresh if @token.expired?

@auth_info = authorization
@@ -40,31 +35,34 @@ class Basecamp3

def get_access_token(code)
access_token_response = @client.auth_code.get_token(code, type: "web_server", redirect_uri: ENV["REDIRECT_URL"])

@db.save_token_data(
ENV["BASECAMP_CLIENT_ID"],
access_token_response.token,
access_token_response.refresh_token,
access_token_response.expires_in,
access_token_response.expires_at
@token = Token.find_or_initialize_by(client_id: ENV["BASECAMP_CLIENT_ID"])
token.update!(
access_token: access_token_response.token,
refresh_token: access_token_response.refresh_token,
expires_in: access_token_response.expires_in,
expires_at: DateTime.strptime(access_token_response.expires_at.to_s, "%s")
)
end

def import_all_data
@db.clear_data_tables
Bucket.delete_all
Person.delete_all
TodoItem.delete_all
TodoItemAssignee.delete_all
TodoList.delete_all

todo_items.each do |item|
@db.insert_bucket(id: item["bucket"]["id"], name: item["bucket"]["name"], type: item["bucket"]["type"])
Bucket.find_or_initialize(id: item["bucket"]["id"], name: item["bucket"]["name"], bucket_type: item["bucket"]["type"]).save!

@db.insert_list(
TodoList.create(
id: item["parent"]["id"],
bucket_id: item["bucket"]["id"],
title: item["parent"]["title"],
type: item["parent"]["type"],
list_type: item["parent"]["type"],
url: item["parent"]["url"]
)

@db.insert_person(
Person.create(
id: item["creator"]["id"],
name: item["creator"]["name"],
email_address: item["creator"]["email_address"],
@@ -74,7 +72,7 @@ class Basecamp3
)

item["assignees"].each do |person|
@db.insert_person(
Person.create(
id: person["id"],
name: person["name"],
email_address: person["email_address"],
@@ -83,25 +81,25 @@ class Basecamp3
avatar_url: person["avatar_url"]
)

@db.insert_todo_item_assignee(todo_item_id: item["id"], person_id: person["id"])
TodoItemAssignee.create(todo_item_id: item["id"], person_id: person["id"])
end

@db.insert_todo_item(
TodoItem.create(
id: item["id"],
todo_list_id: item["parent"]["id"],
creator_id: item["creator"]["id"],
status: item["status"],
visible_to_clients: item["visible_to_clients"] ? 1 : 0,
created_at: item["created_at"],
created_at: DateTime.strptime(item["created_at"], "%s"),
title: item["title"],
type: item["type"],
item_type: item["type"],
url: item["url"],
app_url: item["app_url"],
completed: item["completed"] ? 1 : 0,
description: item["description"],
content: item["content"],
starts_on: item["starts_on"],
due_on: item["due_on"]
starts_on: DateTime.strptime(item["starts_on"], "%s"),
due_on: DateTime.strptime(item["due_on"], "%s")
)
end
end
@@ -137,9 +135,9 @@ class Basecamp3

def all_results(result)
results = result.parsed || []
if(result.headers["x-total-count"].to_i > results.count)
while !result.headers["link"].nil?
url = result.headers["link"].gsub("<","").split(">")[0]
if result.headers["x-total-count"].to_i > results.count
until result.headers["link"].nil?
url = result.headers["link"].gsub("<", "").split(">")[0]
result = @token.get(url)
results.concat(result.parsed)
end

+ 16
- 0
database.yml View File

@@ -0,0 +1,16 @@
default: &default
adapter: postgresql
encoding: unicode
pool: 5
host: localhost

development:
<<: *default
database: b3_big_picture_development

test: &test
<<: *default
database: b3_big_picture_test

production:
<<: *default

+ 0
- 227
db.rb View File

@@ -1,227 +0,0 @@
# frozen_string_literal: true

require "sqlite3"

class DB
attr_accessor :sqlite

def initialize
@sqlite = SQLite3::Database.new "b3-big-picture.sqlite3"
@sqlite.results_as_hash = true

setup unless token_table_exists?
end

def save_token_data(client_id, access_token, refresh_token, expires_at, expires_in)
@sqlite.execute "delete from tokens WHERE client_id ='#{client_id}'"
@sqlite.execute "insert into tokens values (?, ?, ?, ?, ?)", [client_id, access_token, refresh_token, expires_at, expires_in]
end

def token_data(client_id)
data = @sqlite.execute("select * from tokens WHERE client_id = '#{client_id}'").first
return nil unless data

data
end

def token_table_exists?
@sqlite.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='tokens';").count.positive?
end

def clear_data_tables
table_list = ["people", "buckets", "todo_lists", "todo_items", "todo_item_assignees"]
table_list.each do |table|
@sqlite.execute "delete from #{table}"
end
end

def insert_bucket(bucket_hash)
@sqlite.execute "insert or replace into buckets (id, name, type) values (?, ?, ?)", [bucket_hash[:id], bucket_hash[:name], bucket_hash[:type]]
end

def insert_list(list_hash)
@sqlite.execute "insert or replace into todo_lists (id, bucket_id, title, type, url)
values (?, ?, ?, ?, ?)", [list_hash[:id], list_hash[:bucket_id], list_hash[:title], list_hash[:type], list_hash[:url]]
end

def insert_person(person_hash)
@sqlite.execute "insert or replace into people (id, name, email_address, title, bio, avatar_url)
values (?, ?, ?, ?, ?, ?)", [person_hash[:id], person_hash[:name], person_hash[:email_address], person_hash[:title], person_hash[:bio], person_hash[:avatar_url]]
end

def insert_todo_item_assignee(todo_item_assignee_hash)
@sqlite.execute "insert or replace into todo_item_assignees (todo_item_id, person_id)
values (?, ?)", [todo_item_assignee_hash[:todo_item_id], todo_item_assignee_hash[:person_id]]
end

def insert_todo_item(todo_item_hash)
@sqlite.execute "insert or replace into todo_items (id, todo_list_id, creator_id, status, visible_to_clients, created_at, title, type, url, app_url, description, completed, content, starts_on, due_on)
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
[todo_item_hash[:id], todo_item_hash[:todo_list_id], todo_item_hash[:creator_id], todo_item_hash[:status], todo_item_hash[:visible_to_clients], todo_item_hash[:created_at], todo_item_hash[:title], todo_item_hash[:type], todo_item_hash[:url], todo_item_hash[:app_url], todo_item_hash[:description], todo_item_hash[:completed], todo_item_hash[:content], todo_item_hash[:starts_on], todo_item_hash[:due_on]]
end

def team
@sqlite.execute <<-SQL
SELECT
*,
people.name AS person_name,
buckets.name AS bucket_name,
todo_items.title AS todo_title,
todo_lists.title AS list_title
FROM
people
JOIN todo_item_assignees
ON people.id = todo_item_assignees.person_id
JOIN todo_items
ON todo_item_assignees.todo_item_id = todo_items.id
JOIN todo_lists
ON todo_items.todo_list_id = todo_lists.id
JOIN buckets
ON todo_lists.bucket_id = buckets.id;
SQL
end

private
def setup
@sqlite.execute <<-SQL
create table tokens (
client_id varchar(40),
access_token varchar(351),
refresh_token varchar(350),
expires_in int,
expires_at int
);
SQL

@sqlite.execute <<-SQL
create table people (
id int primary key,
name varchar,
email_address varchar,
title varchar,
bio varchar,
avatar_url varchar
);
SQL

@sqlite.execute <<-SQL
create table buckets (
id int primary key,
name varchar(25),
type varchar(25)
);
SQL

@sqlite.execute <<-SQL
create table todo_lists (
id int primary key,
bucket_id int,
title varchar(25),
type varchar,
url varchar
);
SQL

@sqlite.execute <<-SQL
create table todo_items (
id integer primary key,
todo_list_id int,
creator_id int,
status varchar(25),
visible_to_clients int,
created_at varchar,
title varchar,
type varchar,
url varchar,
app_url varchar,
description varchar(5000),
completed int,
content varchar(5000),
starts_on varchar,
due_on varchar
);
SQL

@sqlite.execute <<-SQL
create table todo_item_assignees (
todo_item_id int,
person_id int
);
SQL
end
end


# {
# "id"=>2446137772,
# "status"=>"active",
# "visible_to_clients"=>false,
# "created_at"=>"2020-02-26T16:30:02.145Z",
# "updated_at"=>"2020-02-28T02:54:12.645Z",
# "title"=>"Wait for AS400 Endpoint",
# "inherits_status"=>true,
# "type"=>"Todo",
# "url"=>
# "https://3.basecampapi.com/4421833/buckets/15734622/todos/2446137772.json",
# "app_url"=>"https://3.basecamp.com/4421833/buckets/15734622/todos/2446137772",
# "bookmark_url"=>
# "https://3.basecampapi.com/4421833/my/bookmarks/BAh7CEkiCGdpZAY6BkVUSSIuZ2lkOi8vYmMzL1JlY29yZGluZy8yNDQ2MTM3NzcyP2V4cGlyZXNfaW4GOwBUSSIMcHVycG9zZQY7AFRJIg1yZWFkYWJsZQY7AFRJIg9leHBpcmVzX2F0BjsAVDA=--5d63e79b57aec18872ce96f6ce66d70656c08008.json",
# "subscription_url"=>
# "https://3.basecampapi.com/4421833/buckets/15734622/recordings/2446137772/subscription.json",
# "comments_count"=>0,
# "comments_url"=>
# "https://3.basecampapi.com/4421833/buckets/15734622/recordings/2446137772/comments.json",
# "position"=>1,
# "parent"=>
# {"id"=>2446127835,
# "title"=>"Development",
# "type"=>"Todolist",
# "url"=>
# "https://3.basecampapi.com/4421833/buckets/15734622/todolists/2446127835.json",
# "app_url"=>
# "https://3.basecamp.com/4421833/buckets/15734622/todolists/2446127835"},
# "bucket"=>{"id"=>15734622, "name"=>"Special Processing", "type"=>"Project"},
# "creator"=>
# {"id"=>28459237,
# "attachable_sgid"=>
# "BAh7CEkiCGdpZAY6BkVUSSIpZ2lkOi8vYmMzL1BlcnNvbi8yODQ1OTIzNz9leHBpcmVzX2luBjsAVEkiDHB1cnBvc2UGOwBUSSIPYXR0YWNoYWJsZQY7AFRJIg9leHBpcmVzX2F0BjsAVDA=--6b20c7b71da54e3f9e28872c51a0e72e6bd73854",
# "name"=>"Coby Green",
# "email_address"=>"cgreen380@gmail.com",
# "personable_type"=>"User",
# "title"=>"Business Analyst",
# "bio"=>"",
# "created_at"=>"2020-01-17T17:15:45.148Z",
# "updated_at"=>"2020-01-20T15:59:58.389Z",
# "admin"=>false,
# "owner"=>false,
# "client"=>false,
# "time_zone"=>"America/Chicago",
# "avatar_url"=>
# "https://bc3-production-assets-cdn.basecamp-static.com/4421833/people/BAhpBOVAsgE=--3dfc69b131b91980d6aed2a20972feaacb6b8f4d/avatar-64-x4?v=1",
# "company"=>{"id"=>2071100, "name"=>"Fabricut"}},
# "description"=>"<div>Waiting on the AS400 endpoint from Stacy</div>",
# "completed"=>false,
# "content"=>"Wait for AS400 Endpoint",
# "starts_on"=>"2020-02-24",
# "due_on"=>"2020-03-06",
# "assignees"=>
# [{"id"=>28459135,
# "attachable_sgid"=>
# "BAh7CEkiCGdpZAY6BkVUSSIpZ2lkOi8vYmMzL1BlcnNvbi8yODQ1OTEzNT9leHBpcmVzX2luBjsAVEkiDHB1cnBvc2UGOwBUSSIPYXR0YWNoYWJsZQY7AFRJIg9leHBpcmVzX2F0BjsAVDA=--04b470bc798763bbd4983bba365c84a8b9b39778",
# "name"=>"Richard Cagle",
# "email_address"=>"rick.cagle@fabricut.com",
# "personable_type"=>"User",
# "title"=>"Lead Developer",
# "bio"=>"My middle name is mediocre!",
# "created_at"=>"2020-01-17T17:11:00.616Z",
# "updated_at"=>"2020-02-04T20:15:24.598Z",
# "admin"=>true,
# "owner"=>true,
# "client"=>false,
# "time_zone"=>"America/Chicago",
# "avatar_url"=>
# "https://bc3-production-assets-cdn.basecamp-static.com/4421833/people/BAhpBH9AsgE=--d7b4d7044910877623748f6ad04dde523a5c28c2/avatar-64-x4?v=1",
# "company"=>{"id"=>2071100, "name"=>"Fabricut"}}],
# "completion_subscribers"=>[],
# "completion_url"=>
# "https://3.basecampapi.com/4421833/buckets/15734622/todos/2446137772/completion.json"}

+ 60
- 0
db/migrate/001_schema.rb View File

@@ -0,0 +1,60 @@
class Schema < ActiveRecord::Migration[6.0]
def change
create_table :tokens, force: true do |t|
t.string :client_id
t.string :access_token
t.string :refresh_token
t.integer :expires_in
t.datetime :expires_at
t.timestamps
end

create_table :buckets, force: true do |t|
t.string :name
t.string :bucket_type
t.timestamps
end

create_table :people, force: true do |t|
t.string :name
t.string :email_address
t.string :title
t.string :bio
t.string :avatar_url
t.timestamps
end

create_table :todo_lists, force: true do |t|
t.string :name
t.references :bucket
t.string :title
t.string :list_type
t.string :url
t.timestamps
end

create_table :todo_items, force: true do |t|
t.references :todo_list
t.integer :creator_id
t.string :status
t.boolean :visible_to_clients
t.string :title
t.string :item_type
t.string :url
t.string :app_url
t.string :description
t.boolean :completed
t.text :content
t.datetime :starts_on
t.datetime :due_on
t.timestamps

t.index [:creator_id]
end

create_table :todo_item_assignees, force: true, id: false do |t|
t.references :todo_item
t.references :person
end
end
end

+ 3
- 0
models/bucket.rb View File

@@ -0,0 +1,3 @@
class Bucket < ActiveRecord::Base
has_many :todo_lists
end

+ 3
- 0
models/person.rb View File

@@ -0,0 +1,3 @@
class Person < ActiveRecord::Base
has_many :todo_items, primary_key: :creator_id
end

+ 4
- 0
models/todo_item.rb View File

@@ -0,0 +1,4 @@
class TodoItem < ActiveRecord::Base
belongs_to :todo_list
belongs_to :person, foreign_key: :creator_id
end

+ 4
- 0
models/todo_item_assignee.rb View File

@@ -0,0 +1,4 @@
class TodoItemAssignee < ActiveRecord::Base
belongs_to :todo_item
belongs_to :person
end

+ 4
- 0
models/todo_list.rb View File

@@ -0,0 +1,4 @@
class TodoList < ActiveRecord::Base
belongs_to :bucket
has_many :todo_items
end

+ 10
- 0
models/token.rb View File

@@ -0,0 +1,10 @@
class Token < ActiveRecord::Base
def oauth_hash
{
access_token: access_token,
refresh_token: refresh_token,
expires_in: expires_in,
expires_at: expires_at.to_i,
}
end
end

Loading…
Cancel
Save