aven 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +35 -0
  4. data/Rakefile +19 -0
  5. data/app/assets/stylesheets/aven/application.css +14 -0
  6. data/app/assets/stylesheets/aven/application.tailwind.css +7 -0
  7. data/app/assets/stylesheets/aven/tailwind.css +224 -0
  8. data/app/channels/aven/chat/thread_channel.rb +39 -0
  9. data/app/components/aven/application_view_component.rb +15 -0
  10. data/app/components/aven/views/admin/dashboard/index/component.html.erb +1 -0
  11. data/app/components/aven/views/admin/dashboard/index/component.rb +5 -0
  12. data/app/components/aven/views/articles/edit/component.html.erb +14 -0
  13. data/app/components/aven/views/articles/edit/component.rb +14 -0
  14. data/app/components/aven/views/articles/form/component.html.erb +45 -0
  15. data/app/components/aven/views/articles/form/component.rb +27 -0
  16. data/app/components/aven/views/articles/index/component.html.erb +93 -0
  17. data/app/components/aven/views/articles/index/component.rb +29 -0
  18. data/app/components/aven/views/articles/new/component.html.erb +13 -0
  19. data/app/components/aven/views/articles/new/component.rb +14 -0
  20. data/app/components/aven/views/articles/show/component.html.erb +110 -0
  21. data/app/components/aven/views/articles/show/component.rb +34 -0
  22. data/app/components/aven/views/oauth/error/component.html.erb +44 -0
  23. data/app/components/aven/views/oauth/error/component.rb +30 -0
  24. data/app/components/aven/views/static/index/component.html.erb +17 -0
  25. data/app/components/aven/views/static/index/component.rb +16 -0
  26. data/app/components/aven/views/static/index/controller.js +7 -0
  27. data/app/controllers/aven/admin/base.rb +16 -0
  28. data/app/controllers/aven/admin/dashboard_controller.rb +9 -0
  29. data/app/controllers/aven/agentic/agents_controller.rb +56 -0
  30. data/app/controllers/aven/agentic/documents_controller.rb +51 -0
  31. data/app/controllers/aven/agentic/mcp_controller.rb +124 -0
  32. data/app/controllers/aven/agentic/tools_controller.rb +37 -0
  33. data/app/controllers/aven/ai/text_controller.rb +41 -0
  34. data/app/controllers/aven/application_controller.rb +27 -0
  35. data/app/controllers/aven/articles_controller.rb +114 -0
  36. data/app/controllers/aven/auth_controller.rb +12 -0
  37. data/app/controllers/aven/chat/threads_controller.rb +67 -0
  38. data/app/controllers/aven/oauth/auth0_controller.rb +84 -0
  39. data/app/controllers/aven/oauth/base_controller.rb +183 -0
  40. data/app/controllers/aven/oauth/documentation/auth0.md +387 -0
  41. data/app/controllers/aven/oauth/documentation/entra_id.md +608 -0
  42. data/app/controllers/aven/oauth/documentation/github.md +329 -0
  43. data/app/controllers/aven/oauth/documentation/google.md +253 -0
  44. data/app/controllers/aven/oauth/entra_id_controller.rb +92 -0
  45. data/app/controllers/aven/oauth/github_controller.rb +91 -0
  46. data/app/controllers/aven/oauth/google_controller.rb +64 -0
  47. data/app/controllers/aven/static_controller.rb +7 -0
  48. data/app/controllers/aven/tags_controller.rb +44 -0
  49. data/app/controllers/aven/workspaces_controller.rb +20 -0
  50. data/app/controllers/concerns/aven/authentication.rb +49 -0
  51. data/app/controllers/concerns/aven/controller_helpers.rb +38 -0
  52. data/app/helpers/aven/application_helper.rb +16 -0
  53. data/app/javascript/aven/application.js +3 -0
  54. data/app/javascript/aven/controllers/application.js +5 -0
  55. data/app/javascript/aven/controllers/index.js +11 -0
  56. data/app/jobs/aven/agentic/document_embedding_job.rb +28 -0
  57. data/app/jobs/aven/agentic/document_ocr_job.rb +28 -0
  58. data/app/jobs/aven/application_job.rb +4 -0
  59. data/app/jobs/aven/chat/calculate_cost_job.rb +26 -0
  60. data/app/jobs/aven/chat/run_job.rb +27 -0
  61. data/app/mailers/aven/application_mailer.rb +6 -0
  62. data/app/models/aven/agentic/agent.rb +76 -0
  63. data/app/models/aven/agentic/agent_document.rb +37 -0
  64. data/app/models/aven/agentic/agent_tool.rb +37 -0
  65. data/app/models/aven/agentic/document.rb +162 -0
  66. data/app/models/aven/agentic/document_embedding.rb +39 -0
  67. data/app/models/aven/agentic/tool.rb +106 -0
  68. data/app/models/aven/agentic/tool_parameter.rb +56 -0
  69. data/app/models/aven/application_record.rb +5 -0
  70. data/app/models/aven/article.rb +86 -0
  71. data/app/models/aven/article_attachment.rb +18 -0
  72. data/app/models/aven/article_relationship.rb +26 -0
  73. data/app/models/aven/chat/message.rb +135 -0
  74. data/app/models/aven/chat/thread.rb +159 -0
  75. data/app/models/aven/import/entry.rb +45 -0
  76. data/app/models/aven/import/item_link.rb +36 -0
  77. data/app/models/aven/import/processor.rb +123 -0
  78. data/app/models/aven/import.rb +102 -0
  79. data/app/models/aven/item/embed.rb +54 -0
  80. data/app/models/aven/item/embeddable.rb +141 -0
  81. data/app/models/aven/item/linkable.rb +212 -0
  82. data/app/models/aven/item/schema/builder.rb +139 -0
  83. data/app/models/aven/item/schemaed.rb +252 -0
  84. data/app/models/aven/item/schemas/base.rb +108 -0
  85. data/app/models/aven/item.rb +128 -0
  86. data/app/models/aven/item_link.rb +43 -0
  87. data/app/models/aven/item_schema.rb +87 -0
  88. data/app/models/aven/log.rb +66 -0
  89. data/app/models/aven/loggable.rb +20 -0
  90. data/app/models/aven/user.rb +40 -0
  91. data/app/models/aven/workspace.rb +93 -0
  92. data/app/models/aven/workspace_role.rb +46 -0
  93. data/app/models/aven/workspace_user.rb +54 -0
  94. data/app/models/aven/workspace_user_role.rb +38 -0
  95. data/app/models/concerns/aven/agentic/document_embeddable.rb +58 -0
  96. data/app/models/concerns/aven/searchable.rb +61 -0
  97. data/app/services/aven/agentic/dynamic_tool_builder.rb +81 -0
  98. data/app/services/aven/agentic/mcp/adapter.rb +77 -0
  99. data/app/services/aven/agentic/mcp/result_formatter.rb +57 -0
  100. data/app/services/aven/agentic/mcp/server_factory.rb +43 -0
  101. data/app/services/aven/agentic/ocr/base_extractor.rb +39 -0
  102. data/app/services/aven/agentic/ocr/excel_extractor.rb +43 -0
  103. data/app/services/aven/agentic/ocr/image_extractor.rb +22 -0
  104. data/app/services/aven/agentic/ocr/pdf_extractor.rb +48 -0
  105. data/app/services/aven/agentic/ocr/processor.rb +36 -0
  106. data/app/services/aven/agentic/ocr/textract_client.rb +131 -0
  107. data/app/services/aven/agentic/ocr/word_extractor.rb +34 -0
  108. data/app/services/aven/agentic/tool_result_formatter.rb +76 -0
  109. data/app/services/aven/agentic/tools/base.rb +55 -0
  110. data/app/services/aven/agentic/tools/concerns/boolean_filtering.rb +40 -0
  111. data/app/services/aven/agentic/tools/concerns/enum_filtering.rb +47 -0
  112. data/app/services/aven/agentic/tools/concerns/geo_filtering.rb +56 -0
  113. data/app/services/aven/agentic/tools/concerns/range_filtering.rb +51 -0
  114. data/app/services/aven/chat/broadcaster.rb +59 -0
  115. data/app/services/aven/chat/config.rb +93 -0
  116. data/app/services/aven/chat/message_builder.rb +42 -0
  117. data/app/services/aven/chat/orchestrator.rb +69 -0
  118. data/app/services/aven/chat/runner.rb +105 -0
  119. data/app/services/aven/chat/title_generator.rb +61 -0
  120. data/app/services/aven/external/gmail_client.rb +173 -0
  121. data/app/services/aven/external/google_contacts_client.rb +95 -0
  122. data/app/views/layouts/aven/admin.html.erb +16 -0
  123. data/app/views/layouts/aven/application.html.erb +18 -0
  124. data/config/importmap.rb +16 -0
  125. data/config/routes.rb +63 -0
  126. data/db/migrate/20200101000001_create_aven_users.rb +19 -0
  127. data/db/migrate/20200101000002_create_aven_workspaces.rb +14 -0
  128. data/db/migrate/20200101000003_create_aven_workspace_users.rb +12 -0
  129. data/db/migrate/20200101000004_create_aven_workspace_roles.rb +13 -0
  130. data/db/migrate/20200101000005_create_aven_workspace_user_roles.rb +12 -0
  131. data/db/migrate/20200101000006_create_aven_logs.rb +21 -0
  132. data/db/migrate/20200101000009_create_aven_items.rb +17 -0
  133. data/db/migrate/20200101000010_create_aven_item_links.rb +17 -0
  134. data/db/migrate/20200101000011_create_aven_agentic_tools.rb +19 -0
  135. data/db/migrate/20200101000012_create_aven_agentic_tool_parameters.rb +20 -0
  136. data/db/migrate/20200101000013_create_aven_agentic_documents.rb +22 -0
  137. data/db/migrate/20200101000014_create_aven_agentic_document_embeddings.rb +18 -0
  138. data/db/migrate/20200101000015_create_aven_agentic_agents.rb +18 -0
  139. data/db/migrate/20200101000016_create_aven_agentic_agent_tools.rb +13 -0
  140. data/db/migrate/20200101000017_create_aven_agentic_agent_documents.rb +13 -0
  141. data/db/migrate/20200101000018_create_aven_chat_threads.rb +19 -0
  142. data/db/migrate/20200101000019_create_aven_chat_messages.rb +26 -0
  143. data/db/migrate/20200101000020_add_pg_search_support.rb +21 -0
  144. data/db/migrate/20200101000021_create_aven_item_schemas.rb +18 -0
  145. data/db/migrate/20200101000022_create_aven_imports.rb +23 -0
  146. data/db/migrate/20200101000023_create_aven_import_entries.rb +13 -0
  147. data/db/migrate/20200101000024_create_aven_import_item_links.rb +13 -0
  148. data/db/migrate/20200101000025_create_aven_articles.rb +19 -0
  149. data/db/migrate/20200101000026_create_aven_article_attachments.rb +13 -0
  150. data/db/migrate/20200101000027_create_aven_article_relationships.rb +15 -0
  151. data/lib/aven/configuration.rb +87 -0
  152. data/lib/aven/engine.rb +43 -0
  153. data/lib/aven/model/tenant_model.rb +91 -0
  154. data/lib/aven/model.rb +6 -0
  155. data/lib/aven/version.rb +3 -0
  156. data/lib/aven.rb +8 -0
  157. data/lib/tasks/annotate_rb.rake +10 -0
  158. data/lib/tasks/aven_tasks.rake +21 -0
  159. metadata +426 -0
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aven
4
+ class ArticlesController < Aven::ApplicationController
5
+ include Aven::Authentication
6
+
7
+ before_action :authenticate_user!
8
+ before_action :set_article, only: [:show, :edit, :update, :destroy]
9
+
10
+ # GET /articles
11
+ def index
12
+ @articles = current_workspace.aven_articles.recent
13
+
14
+ respond_to do |format|
15
+ format.html { view_component("articles/index", articles: @articles, current_user:) }
16
+ format.json { render json: @articles }
17
+ end
18
+ end
19
+
20
+ # GET /articles/:id
21
+ def show
22
+ respond_to do |format|
23
+ format.html { view_component("articles/show", article: @article, current_user:) }
24
+ format.json { render json: @article }
25
+ end
26
+ end
27
+
28
+ # GET /articles/new
29
+ def new
30
+ @article = current_workspace.aven_articles.build
31
+
32
+ respond_to do |format|
33
+ format.html { view_component("articles/new", article: @article, current_user:) }
34
+ format.json { render json: @article }
35
+ end
36
+ end
37
+
38
+ # GET /articles/:id/edit
39
+ def edit
40
+ respond_to do |format|
41
+ format.html { view_component("articles/edit", article: @article, current_user:) }
42
+ format.json { render json: @article }
43
+ end
44
+ end
45
+
46
+ # POST /articles
47
+ def create
48
+ @article = current_workspace.aven_articles.build(article_params)
49
+ @article.author = current_user
50
+
51
+ if @article.save
52
+ respond_to do |format|
53
+ format.html { redirect_to article_path(@article), notice: "Article was successfully created." }
54
+ format.json { render json: @article, status: :created }
55
+ end
56
+ else
57
+ respond_to do |format|
58
+ format.html do
59
+ response.status = :unprocessable_entity
60
+ view_component("articles/new", article: @article, current_user:)
61
+ end
62
+ format.json { render json: { errors: @article.errors }, status: :unprocessable_entity }
63
+ end
64
+ end
65
+ end
66
+
67
+ # PATCH/PUT /articles/:id
68
+ def update
69
+ if @article.update(article_params)
70
+ respond_to do |format|
71
+ format.html { redirect_to article_path(@article), notice: "Article was successfully updated." }
72
+ format.json { render json: @article }
73
+ end
74
+ else
75
+ respond_to do |format|
76
+ format.html do
77
+ response.status = :unprocessable_entity
78
+ view_component("articles/edit", article: @article, current_user:)
79
+ end
80
+ format.json { render json: { errors: @article.errors }, status: :unprocessable_entity }
81
+ end
82
+ end
83
+ end
84
+
85
+ # DELETE /articles/:id
86
+ def destroy
87
+ @article.destroy
88
+
89
+ respond_to do |format|
90
+ format.html { redirect_to articles_path, notice: "Article was successfully deleted." }
91
+ format.json { head :no_content }
92
+ end
93
+ end
94
+
95
+ private
96
+
97
+ def set_article
98
+ @article = current_workspace.aven_articles.friendly.find(params[:id])
99
+ end
100
+
101
+ def article_params
102
+ params.require(:article).permit(
103
+ :title,
104
+ :intro,
105
+ :description,
106
+ :published_at,
107
+ :main_visual,
108
+ tag_list: [],
109
+ article_attachments_attributes: [:id, :file, :position, :_destroy],
110
+ article_relationships_attributes: [:id, :related_article_id, :position, :_destroy]
111
+ )
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,12 @@
1
+ module Aven
2
+ class AuthController < ApplicationController
3
+ def logout
4
+ sign_out
5
+ begin
6
+ redirect_to(main_app.root_path, notice: "You have been signed out successfully.")
7
+ rescue NoMethodError
8
+ redirect_to(root_path, notice: "You have been signed out successfully.")
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aven
4
+ module Chat
5
+ class ThreadsController < Aven::ApplicationController
6
+ before_action :authenticate_user!
7
+ before_action :set_thread, only: [:show, :ask, :ask_agent]
8
+
9
+ def index
10
+ @threads = current_workspace.aven_chat_threads
11
+ .where(user: current_user)
12
+ .recent
13
+ .limit(50)
14
+
15
+ render json: @threads
16
+ end
17
+
18
+ def show
19
+ render json: @thread.as_json(
20
+ include: {
21
+ messages: { only: [:id, :role, :status, :content, :created_at] }
22
+ }
23
+ )
24
+ end
25
+
26
+ def create
27
+ @thread = current_workspace.aven_chat_threads.build(
28
+ user: current_user,
29
+ **thread_params
30
+ )
31
+
32
+ if @thread.save
33
+ render json: @thread, status: :created
34
+ else
35
+ render json: { errors: @thread.errors }, status: :unprocessable_entity
36
+ end
37
+ end
38
+
39
+ # POST /chat/threads/:id/ask
40
+ def ask
41
+ message = @thread.ask(params[:question])
42
+ render json: { message:, thread: @thread }
43
+ end
44
+
45
+ # POST /chat/threads/:id/ask_agent
46
+ def ask_agent
47
+ agent = current_workspace.aven_agentic_agents.find(params[:agent_id])
48
+ question = params[:question].presence
49
+
50
+ message = @thread.ask_with_agent(agent, question)
51
+ render json: { message:, thread: @thread }
52
+ end
53
+
54
+ private
55
+
56
+ def set_thread
57
+ @thread = current_workspace.aven_chat_threads
58
+ .where(user: current_user)
59
+ .find(params[:id])
60
+ end
61
+
62
+ def thread_params
63
+ params.permit(:title, :context_markdown)
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+
6
+ module Aven
7
+ module Oauth
8
+ class Auth0Controller < BaseController
9
+ # Auth0 uses your domain (e.g., your-tenant.auth0.com or your-tenant.us.auth0.com)
10
+ # These URLs will be constructed dynamically based on the configured domain
11
+ DEFAULT_SCOPE = "openid email profile"
12
+
13
+ protected
14
+
15
+ def authorization_url(state)
16
+ params = {
17
+ client_id: oauth_config[:client_id],
18
+ redirect_uri: callback_url,
19
+ response_type: "code",
20
+ scope: oauth_config[:scope] || DEFAULT_SCOPE,
21
+ state:
22
+ }
23
+
24
+ # Optionally add audience parameter if specified (for API access)
25
+ params[:audience] = oauth_config[:audience] if oauth_config[:audience].present?
26
+
27
+ "#{auth0_authorization_url}?#{params.to_query}"
28
+ end
29
+
30
+ def exchange_code_for_token(code)
31
+ params = {
32
+ grant_type: "authorization_code",
33
+ client_id: oauth_config[:client_id],
34
+ client_secret: oauth_config[:client_secret],
35
+ code:,
36
+ redirect_uri: callback_url
37
+ }
38
+
39
+ oauth_request(URI(auth0_token_url), params)
40
+ end
41
+
42
+ def fetch_user_info(access_token)
43
+ response = oauth_get_request(URI(auth0_userinfo_url), access_token)
44
+
45
+ {
46
+ id: response[:sub],
47
+ email: response[:email],
48
+ name: response[:name] || response[:nickname],
49
+ picture: response[:picture]
50
+ }
51
+ end
52
+
53
+ private
54
+
55
+ def callback_url
56
+ aven.oauth_auth0_callback_url(host: request.host, protocol: request.protocol)
57
+ end
58
+
59
+ def oauth_config
60
+ @oauth_config ||= Aven.configuration.oauth_providers[:auth0] || raise("Auth0 OAuth not configured")
61
+ end
62
+
63
+ def auth0_domain
64
+ @auth0_domain ||= oauth_config[:domain] || raise("Auth0 domain not configured")
65
+ end
66
+
67
+ def auth0_base_url
68
+ @auth0_base_url ||= auth0_domain.start_with?("http") ? auth0_domain : "https://#{auth0_domain}"
69
+ end
70
+
71
+ def auth0_authorization_url
72
+ "#{auth0_base_url}/authorize"
73
+ end
74
+
75
+ def auth0_token_url
76
+ "#{auth0_base_url}/oauth/token"
77
+ end
78
+
79
+ def auth0_userinfo_url
80
+ "#{auth0_base_url}/userinfo"
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aven
4
+ module Oauth
5
+ class BaseController < ApplicationController
6
+ skip_before_action :verify_authenticity_token, only: [:callback]
7
+
8
+ # Initiates OAuth flow
9
+ def create
10
+ state = SecureRandom.hex(16)
11
+ session[:oauth_state] = state
12
+
13
+ redirect_to authorization_url(state), allow_other_host: true
14
+ end
15
+
16
+ # Handles OAuth callback
17
+ def callback
18
+ validate_state!
19
+
20
+ token_data = exchange_code_for_token(params[:code])
21
+ user_info = fetch_user_info(token_data[:access_token])
22
+
23
+ user = find_or_create_user(user_info, token_data)
24
+
25
+ if user.persisted?
26
+ sign_in_and_redirect(user)
27
+ else
28
+ handle_failed_authentication(user)
29
+ end
30
+ rescue => e
31
+ Rails.logger.error("OAuth authentication failed: #{e.class.name} - #{e.message}")
32
+ Rails.logger.error(e.backtrace.first(10).join("\n")) unless Rails.env.production?
33
+
34
+ error_message = if Rails.env.production?
35
+ "Authentication failed. Please try again."
36
+ else
37
+ "#{e.message}"
38
+ end
39
+
40
+ error_class = Rails.env.production? ? nil : e.class.name
41
+ render_error_page(error_message, error_class)
42
+ end
43
+
44
+ # Renders OAuth error page
45
+ def error
46
+ @error_message = params[:message] || "Authentication failed"
47
+ @error_class = params[:error_class]
48
+
49
+ view_component(
50
+ "oauth/error",
51
+ error_message: @error_message,
52
+ error_class: @error_class,
53
+ current_user:
54
+ )
55
+ end
56
+
57
+ protected
58
+
59
+ # Must be implemented by subclasses
60
+ def authorization_url(state)
61
+ raise NotImplementedError
62
+ end
63
+
64
+ def exchange_code_for_token(code)
65
+ raise NotImplementedError
66
+ end
67
+
68
+ def fetch_user_info(access_token)
69
+ raise NotImplementedError
70
+ end
71
+
72
+ # Common helper methods
73
+ def validate_state!
74
+ if params[:state] != session[:oauth_state]
75
+ raise StandardError, "Invalid state parameter"
76
+ end
77
+ session.delete(:oauth_state)
78
+ end
79
+
80
+ def find_or_create_user(user_info, token_data)
81
+ auth_tenant = request.host
82
+
83
+ user = Aven::User.where(auth_tenant:)
84
+ .where("remote_id = ? OR email = ?", user_info[:id].to_s, user_info[:email])
85
+ .first_or_initialize
86
+
87
+ user.tap do |u|
88
+ u.auth_tenant = auth_tenant
89
+ u.remote_id = user_info[:id].to_s
90
+ u.email = user_info[:email]
91
+ u.access_token = token_data[:access_token]
92
+ u.save
93
+ end
94
+ end
95
+
96
+ def sign_in_and_redirect(user)
97
+ sign_in(user)
98
+ set_current_workspace_for(user)
99
+ redirect_to after_sign_in_path_for(user)
100
+ end
101
+
102
+ def set_current_workspace_for(user)
103
+ workspace = user.workspaces.first
104
+
105
+ # Create default workspace if user has none (new sign up)
106
+ if workspace.nil?
107
+ workspace = Aven::Workspace.create!(label: "Default Workspace")
108
+ Aven::WorkspaceUser.create!(user:, workspace:)
109
+ user.reload
110
+ end
111
+
112
+ # Set current workspace in session
113
+ session[:workspace_id] = workspace.id
114
+ end
115
+
116
+ def handle_failed_authentication(user)
117
+ error_message = if !Rails.env.production? && user.errors.any?
118
+ user.errors.full_messages.join(", ")
119
+ else
120
+ "Failed to create user account"
121
+ end
122
+
123
+ error_class = Rails.env.production? ? nil : "User::ValidationError"
124
+ render_error_page(error_message, error_class)
125
+ end
126
+
127
+ def render_error_page(message, error_class = nil)
128
+ view_component(
129
+ "oauth/error",
130
+ error_message: message,
131
+ error_class:,
132
+ current_user:
133
+ )
134
+ end
135
+
136
+ def after_sign_in_path_for(resource)
137
+ stored_location_for(resource) ||
138
+ Aven.configuration.resolve_authenticated_root_path ||
139
+ begin
140
+ main_app.root_path
141
+ rescue NoMethodError
142
+ root_path
143
+ end
144
+ end
145
+
146
+ # HTTP helper for OAuth requests
147
+ def oauth_request(uri, params, headers = {})
148
+ http = Net::HTTP.new(uri.host, uri.port)
149
+ http.use_ssl = true
150
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if Rails.env.development?
151
+
152
+ request = Net::HTTP::Post.new(uri)
153
+ request.set_form_data(params)
154
+ headers.each { |key, value| request[key] = value }
155
+
156
+ response = http.request(request)
157
+
158
+ unless response.is_a?(Net::HTTPSuccess)
159
+ raise StandardError, "OAuth request failed: #{response.body}"
160
+ end
161
+
162
+ JSON.parse(response.body, symbolize_names: true)
163
+ end
164
+
165
+ def oauth_get_request(uri, access_token)
166
+ http = Net::HTTP.new(uri.host, uri.port)
167
+ http.use_ssl = true
168
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if Rails.env.development?
169
+
170
+ request = Net::HTTP::Get.new(uri)
171
+ request["Authorization"] = "Bearer #{access_token}"
172
+
173
+ response = http.request(request)
174
+
175
+ unless response.is_a?(Net::HTTPSuccess)
176
+ raise StandardError, "OAuth request failed: #{response.body}"
177
+ end
178
+
179
+ JSON.parse(response.body, symbolize_names: true)
180
+ end
181
+ end
182
+ end
183
+ end