Merge branch 'dev-api' into document-ui-improvements

This commit is contained in:
Andrew Brown 2020-09-17 02:55:47 -07:00
commit 5f66dac88a
79 changed files with 2272 additions and 109 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

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 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,21 @@
.api-docs {
h1 {
font-size: 24px;
}
h2 {
font-size: 20px;
}
h3 {
font-size: 16px;
}
.code {
font-family: monospace;
color: white;
background: black;
white-space: pre-wrap;
padding: 0 20px;
}
}

View File

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

View File

@ -41,3 +41,7 @@
transform: rotate(-90deg);
}
}
#page-lookup-list {
min-width: 33em;
}

View File

@ -1,11 +1,11 @@
class ContentAuthorizer < ApplicationAuthorizer
def readable_by? user
[
PermissionService.user_owns_any_containing_universe?(user: user, content: resource),
PermissionService.user_owns_content?(user: user, content: resource),
PermissionService.content_is_public?(content: resource),
PermissionService.content_is_in_a_public_universe?(content: resource),
PermissionService.user_can_contribute_to_containing_universe?(user: user, content: resource)
::PermissionService.user_owns_any_containing_universe?(user: user, content: resource),
::PermissionService.user_owns_content?(user: user, content: resource),
::PermissionService.content_is_public?(content: resource),
::PermissionService.content_is_in_a_public_universe?(content: resource),
::PermissionService.user_can_contribute_to_containing_universe?(user: user, content: resource)
].any?
end

View File

@ -0,0 +1,29 @@
module Api
class ApiDocsController < ApplicationController
layout 'developer', except: [:integrations]
before_action :authenticate_user!, except: [:index, :docs, :references]
def index
end
def docs
end
def integrations
end
def pricing
end
def applications
@applications = current_user.application_integrations
end
def approvals
end
def references
end
end
end

View File

@ -0,0 +1,69 @@
module Api
class ApplicationIntegrationsController < ApplicationController
layout 'developer'
before_action :authenticate_user!
before_action :set_integration, only: [:show, :authorize, :edit, :update, :destroy]
# GET /application_integrations
def index
@applications = current_user.application_integrations
end
# GET /application_integrations/1
def show
end
def authorize
end
# GET /application_integrations/new
def new
@integration = ApplicationIntegration.new
end
# GET /application_integrations/1/edit
def edit
end
# POST /application_integrations
def create
@integration = ApplicationIntegration.new(application_integration_params.merge({user: current_user}))
if @integration.save
redirect_to api_application_path(@integration), notice: 'Application integration was successfully created.'
else
render :new
end
end
# PATCH/PUT /application_integrations/1
def update
if @integration.update(application_integration_params)
redirect_to @integration, notice: 'Application integration was successfully updated.'
else
render :edit
end
end
# DELETE /application_integrations/1
def destroy
@integration.destroy
redirect_to application_integrations_url, notice: 'Application integration was successfully destroyed.'
end
private
# Use callbacks to share common setup or constraints between actions.
def set_integration
@application_integration = current_user.application_integrations.find_by(id: params[:id])
end
# Only allow a trusted parameter "white list" through.
def application_integration_params
params.require(:application_integration).permit(
:name, :description, :organization_name, :organization_url, :website_url, :privacy_policy_url, :authorization_callback_url
)
end
end
end

View File

@ -0,0 +1,24 @@
module Api
class IntegrationAuthorizationsController < ApplicationController
protect_from_forgery
def create
authorization = IntegrationAuthorization.create(integration_authorization_params.merge({
user_id: current_user.id,
referral_url: request.referrer,
ip_address: request.remote_ip,
origin: request.headers['HTTP_ORIGIN'],
content_type: request.headers['CONTENT_TYPE'],
user_agent: request.headers['HTTP_USER_AGENT'],
user_token: SecureRandom.hex(24)
}))
return redirect_to(authorization.application_integration.authorization_callback_url + "?token=#{authorization.user_token}")
end
private
def integration_authorization_params
params.require(:integration_authorization).permit(:application_integration_id)
end
end
end

View File

@ -1,6 +1,96 @@
module Api
module V1
class ApiController < ApplicationController
before_action :initialize_updates_used_tracker
before_action :authenticate_application!
before_action :authenticate_api_user!
after_action :log_api_request
def authenticate_application!
@application_integration = ApplicationIntegration.find_by(application_token: params[:application_token])
unless @application_integration
@request_success = :error
log_api_request
render json: {
"Error" => "Invalid application_token",
"Param" => "application_token",
"Token" => params[:application_token]
}
return
end
end
def authenticate_api_user!
@authorization = @application_integration.integration_authorizations.find_by(user_token: params[:authorization_token])
# todo error on this if not set
@current_api_user = @authorization.try(:user)
unless @current_api_user
@request_success = :error
log_api_request
render json: {
"Error" => "Invalid authorization_token",
"Param" => "authorization_token",
"Token" => params[:authorization_token]
}
return
end
end
def initialize_updates_used_tracker
@updates_used_this_request = 0
end
def log_api_request
ApiRequest.create!(
application_integration: @application_integration,
integration_authorization: @authorization,
result: @request_success || :success,
updates_used: @updates_used_this_request,
ip_address: request.remote_ip
)
end
# Content page list endpoints
Rails.application.config.content_types[:all].each do |content_type|
define_method(content_type.name.downcase.pluralize) do
pages = @current_api_user.send(content_type.name.downcase.pluralize)
render json: pages.map { |page|
{
id: page.id,
name: page.name,
description: page.description,
universe: page.try(:universe).nil? ? nil : {
id: page.universe_id,
name: page.universe.name
},
meta: {
created_at: page.created_at,
updated_at: page.updated_at
}
}
}
end
end
# Content page show endpoints
Rails.application.config.content_types[:all].each do |content_type|
define_method(content_type.name.downcase) do
page = content_type.find_by(id: params[:id])
if page && page.readable_by?(@current_api_user || User.new)
render json: ApiContentSerializer.new(page, include_blank_fields: params.fetch(:include_blank_fields, false)).data
else
render json: { error: "Page not found" }
end
end
end
end
end
end

View File

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

View File

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

View File

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

View File

@ -13,16 +13,17 @@ import Divider from '@material-ui/core/Divider';
import ListItem from '@material-ui/core/ListItem';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import ListItemText from '@material-ui/core/ListItemText';
import InboxIcon from '@material-ui/icons/MoveToInbox';
import MailIcon from '@material-ui/icons/Mail';
import Button from '@material-ui/core/Button';
import ListSubheader from '@material-ui/core/ListSubheader';
import ExpandLess from '@material-ui/icons/ExpandLess';
import ExpandMore from '@material-ui/icons/ExpandMore';
import StarBorder from '@material-ui/icons/StarBorder';
import HelpIcon from '@material-ui/icons/Help';
import Collapse from '@material-ui/core/Collapse';
import axios from 'axios';
class PageLookupSidebar extends React.Component {
constructor(props) {
@ -36,56 +37,37 @@ class PageLookupSidebar extends React.Component {
};
}
loadPage(page_type, page_id) {
async loadPage(page_type, page_id) {
this.setDrawerVisible(true);
// show loading icon
this.setState({ show_data: false });
// make api request
// hide loading icon
// load response into list
this.setState({
page_data: {
name: page_type + ' ' + 'Bob',
categories: [
{
id: 1,
label: 'General',
fields: [
{
label: 'Name',
value: 'Bob',
type: 'text'
},
{
label: 'Age',
value: '55',
type: 'text'
}
]
},
{
id: 2,
label: 'Family',
fields: [
{
label: 'Mom',
value: 'Robin',
type: 'link',
link: ['Character', 534]
}
]
}
]
await axios.get(
"/api/v1/" + page_type.toLowerCase() + "/" + page_id
+ '?application_token=4756de490e82956dc6329e6650aaec664e27ccd27e153e2f'
+ '&authorization_token=167bb93139303904cf67f6480a29e71c9f1eaf7a28e902e1',
{
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
}
).then(response => {
console.log("get request");
console.log(response.data);
// load response into list
this.setState({
page_data: response.data,
show_data: true,
page_type: page_type
});
}).catch(err => {
console.log(err);
return null;
});
console.log("setting show_data = true");
this.setState({ show_data: true });
// this.state.show_data = true;
console.log("show data? " + this.state.show_data);
};
@ -99,6 +81,37 @@ class PageLookupSidebar extends React.Component {
}});
}
fieldData(field) {
switch (field.type) {
case "name":
case "text_area":
return (
<ListItem key={field.id}>
<ListItemText primary={field.label} secondary={field.value} />
</ListItem>
);
case "link":
return(
<React.Fragment key={field.id}>
<ListSubheader component="div">
{field.label}
</ListSubheader>
{field.value.map((link) => (
<ListItem button key={link.id}>
<ListItemText primary={link.name} />
</ListItem>
))}
</React.Fragment>
);
default:
return(
<div>error loading {field.label}</div>
);
}
}
pageData() {
if (this.state.show_data) {
return (
@ -106,22 +119,21 @@ class PageLookupSidebar extends React.Component {
role="presentation"
>
<List
aria-labelledby="nested-list-subheader"
subheader={
<ListSubheader component="div" id="nested-list-subheader">
Quick-reference
</ListSubheader>
<h5>&nbsp;&nbsp;&nbsp;{this.state.page_data.name}</h5>
}
id="page-lookup-list"
>
<ListItem button>
<ListItemText primary={this.state.page_data.name} />
</ListItem>
<Divider></Divider>
<ListSubheader component="div">
Quick-reference
</ListSubheader>
{this.state.page_data.categories.map((category) => (
<React.Fragment>
<React.Fragment key={category.id}>
<ListItem button onClick={() => this.toggleCategoryOpen(category.label)}>
<ListItemIcon>
<InboxIcon />
<HelpIcon />
</ListItemIcon>
<ListItemText primary={category.label} />
{!!this.state.category_open[category.label] ? <ExpandLess /> : <ExpandMore />}
@ -129,22 +141,29 @@ class PageLookupSidebar extends React.Component {
<Collapse in={!!this.state.category_open[category.label]} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
{category.fields.map((field) => (
<ListItem button={field.type == 'link'}>
{field.type == 'text' &&
<ListItemText primary={field.label} secondary={field.value} />
}
{field.type == 'link' &&
<ListItemText primary={field.label} secondary={field.value} />
}
</ListItem>
this.fieldData(field)
))}
</List>
</Collapse>
</React.Fragment>
))}
<Divider></Divider>
<ListItem button>
<ListItemIcon>
<HelpIcon />
<ListItemText primary={"View this " + this.state.page_type} />
</ListItemIcon>
</ListItem>
<ListItem button>
<ListItemIcon>
<HelpIcon />
<ListItemText primary={"Edit this " + this.state.page_type} />
</ListItemIcon>
</ListItem>
</List>
</div>
);
// also add divider + view/edit links
} else { // No data to show yet
return (
@ -152,9 +171,8 @@ class PageLookupSidebar extends React.Component {
role="presentation"
>
<List
aria-labelledby="nested-list-subheader"
subheader={
<ListSubheader component="div" id="nested-list-subheader">
<ListSubheader component="div">
Quick-reference
</ListSubheader>
}
@ -163,6 +181,49 @@ class PageLookupSidebar extends React.Component {
<ListItemText primary="Loading data..." />
</ListItem>
</List>
<div className="center">
<div className="preloader-wrapper big active">
<div className="spinner-layer spinner-blue">
<div className="circle-clipper left">
<div className="circle"></div>
</div><div className="gap-patch">
<div className="circle"></div>
</div><div className="circle-clipper right">
<div className="circle"></div>
</div>
</div>
<div className="spinner-layer spinner-red">
<div className="circle-clipper left">
<div className="circle"></div>
</div><div className="gap-patch">
<div className="circle"></div>
</div><div className="circle-clipper right">
<div className="circle"></div>
</div>
</div>
<div className="spinner-layer spinner-yellow">
<div className="circle-clipper left">
<div className="circle"></div>
</div><div className="gap-patch">
<div className="circle"></div>
</div><div className="circle-clipper right">
<div className="circle"></div>
</div>
</div>
<div className="spinner-layer spinner-green">
<div className="circle-clipper left">
<div className="circle"></div>
</div><div className="gap-patch">
<div className="circle"></div>
</div><div className="circle-clipper right">
<div className="circle"></div>
</div>
</div>
</div>
</div>
</div>
);
}
@ -176,7 +237,8 @@ class PageLookupSidebar extends React.Component {
</Button>
<Drawer anchor='right'
open={this.state['open']}
onClose={() => this.setDrawerVisible(false)}>
onClose={() => this.setDrawerVisible(false)}
>
{this.pageData()}
</Drawer>
</React.Fragment>
@ -184,7 +246,7 @@ class PageLookupSidebar extends React.Component {
}
}
// PrivacyToggle.propTypes = {
// PageLookupSidebar.propTypes = {
// content: PropTypes.exact({
// id: PropTypes.number.isRequired,
// name: PropTypes.string.isRequired,

View File

@ -0,0 +1,7 @@
class ApiRequest < ApplicationRecord
belongs_to :application_integration, optional: true
belongs_to :integration_authorization, optional: true
scope :successful, -> { where(result: 'success') }
scope :errored, -> { where(result: 'error') }
end

View File

@ -0,0 +1,47 @@
class ApplicationIntegration < ApplicationRecord
belongs_to :user
has_many :integration_authorizations
has_many :api_requests
after_create :generate_new_access_token!
def self.icon
'extension'
end
def self.color
'orange'
end
def generate_new_access_token!
self.update(application_token: SecureRandom.hex(24))
end
def request_error_rate
@request_error_rate ||= begin
errored_requests = api_requests.errored.count
total_requests = api_requests.count
return 0 if total_requests.zero?
(errored_requests.to_f / total_requests).round(3)
end
end
def error_rate_color
rate = request_error_rate
case rate
when 0.0..0.1
'green'
when 0.1..0.3
'yellow'
when 0.3..1
'red'
end
end
def current_quota_usage_percentage
@current_quota_usage_percentage ||= ((api_requests.successful.count.to_f / 10_000) * 100).round(2)
end
end

View File

@ -8,25 +8,36 @@ module HasAttributes
after_save :update_custom_attributes
def self.create_default_attribute_categories(user)
# Don't create any attribute categories for AttributeCategories or AttributeFields that share the ContentController
return [] if ['attribute_category', 'attribute_field'].include?(content_name)
YAML.load_file(Rails.root.join('config', 'attributes', "#{content_name}.yml")).map do |category_name, details|
category = user.attribute_categories.create!(
YAML.load_file(Rails.root.join('config', 'attributes', "#{content_name}.yml")).map do |category_name, defaults|
# First, query for the category to see if it already exists
category = user.attribute_categories.find_or_initialize_by(
entity_type: self.content_name,
name: category_name.to_s,
icon: details[:icon],
label: details[:label]
name: category_name.to_s
)
creating_new_category = category.new_record?
category.attribute_fields << details[:attributes].map do |field|
af_field = category.attribute_fields.with_deleted.create!(
old_column_source: field[:name],
user: user,
field_type: field[:field_type].presence || "text_area",
label: field[:label].presence || 'Untitled field'
)
af_field
end if details.key?(:attributes)
# If the category didn't already exist, go ahead and set defaults on it and save
if creating_new_category
category.label = defaults[:label]
category.icon = defaults[:icon]
category.save!
end
# If we created this category for the first time, we also want to make sure we create its default fields, too
if creating_new_category && defaults.key?(:attributes)
category.attribute_fields << defaults[:attributes].map do |field|
af_field = category.attribute_fields.with_deleted.create!(
old_column_source: field[:name],
user: user,
field_type: field[:field_type].presence || "text_area",
label: field[:label].presence || 'Untitled field'
)
af_field
end
end
end.compact
end
@ -37,7 +48,7 @@ module HasAttributes
@cached_attribute_categories_for_this_content = begin
# Always include the flatfile categories (but create AR versions if they don't exist)
categories = YAML.load_file(Rails.root.join('config', 'attributes', "#{content_name}.yml")).map do |category_name, details|
category = AttributeCategory.with_deleted.find_or_initialize_by(
category = ::AttributeCategory.with_deleted.find_or_initialize_by(
entity_type: self.content_name,
name: category_name.to_s,
user: user
@ -102,19 +113,19 @@ module HasAttributes
# # Always include the flatfile categories (but create AR versions if they don't exist)
# categories = YAML.load_file(Rails.root.join('config', 'attributes', "#{content_name}.yml")).map do |category_name, details|
# category = AttributeCategory.with_deleted.find_by(
# category = ::AttributeCategory.with_deleted.find_by(
# entity_type: self.content_name,
# name: category_name.to_s,
# user: user
# )
# category.attribute_fields << details[:attributes].map do |field|
# category.attribute_fields.with_deleted.find_by(
# user: user,
# old_column_source: field[:name],
# field_type: field[:field_type].presence || "text_area"
# )
# end if details.key?(:attributes)
# # category.attribute_fields << details[:attributes].map do |field|
# # category.attribute_fields.with_deleted.find_by(
# # user: user,
# # old_column_source: field[:name],
# # field_type: field[:field_type].presence || "text_area"
# # )
# # end if details.key?(:attributes)
# if show_hidden
# category

View File

@ -38,7 +38,7 @@ module IsContentPage
else
# For all other content pages, we have to fetch document IDs off DocumentEntities that
# match those content pages
document_ids = DocumentAnalysis.where(
document_ids = ::DocumentAnalysis.where(
id: document_entities.pluck(:document_analysis_id)
).pluck(:document_id)
Document.where(id: document_ids)

View File

@ -0,0 +1,6 @@
class IntegrationAuthorization < ApplicationRecord
belongs_to :user
belongs_to :application_integration
has_many :api_requests
end

View File

@ -8,7 +8,6 @@ class Country < ApplicationRecord
include BelongsToUniverse
include HasAttributes
include IsContentPage
include Serendipitous::Concern

View File

@ -8,7 +8,6 @@ class Language < ApplicationRecord
include BelongsToUniverse
include HasAttributes
include IsContentPage
include Serendipitous::Concern

View File

@ -8,7 +8,6 @@ class Town < ApplicationRecord
include BelongsToUniverse
include HasAttributes
include IsContentPage
include Serendipitous::Concern

View File

@ -0,0 +1,74 @@
# This is an implementation of ContentSerializer that only exposes public columns in the
# standard API format and should be preferred when possible.
class ApiContentSerializer
attr_accessor :id, :name, :user, :universe
attr_accessor :categories
attr_accessor :fields
attr_accessor :attribute_values
attr_accessor :page_tags
attr_accessor :documents
attr_accessor :raw_model
attr_accessor :class_name, :class_color, :class_icon
attr_accessor :data
def initialize(content, include_blank_fields: false)
self.categories = content.class.attribute_categories(content.user).where(hidden: false).eager_load(attribute_fields: :attribute_values)
self.fields = AttributeField.where(attribute_category_id: self.categories.map(&:id), hidden: false)
self.attribute_values = Attribute.where(attribute_field_id: self.fields.map(&:id), entity_type: content.page_type, entity_id: content.id).order('created_at desc')
self.universe = (content.class.name == Universe.name) ? nil : content.universe
self.raw_model = content
self.page_tags = content.page_tags.pluck(:tag) || []
self.documents = content.documents || []
self.data = {
name: content.try(:name),
description: content.try(:description),
universe: self.universe.nil? ? nil : {
id: self.universe.id,
name: self.universe.try(:name)
},
meta: {
created_at: content.created_at,
updated_at: content.updated_at
},
categories: self.categories.map { |category|
{
id: category.id,
label: category.label,
fields: category.attribute_fields.order(:position).map { |field|
{
id: field.id,
label: field.label,
type: field.field_type,
value: value_for(field, content)
}
}.reject { |field| !include_blank_fields && field[:value].empty? }
}
}.reject { |category| !include_blank_fields && category[:fields].empty? },
references: []
}
end
def value_for(attribute_field, content)
case attribute_field.field_type
when 'link'
self.raw_model.send(attribute_field.old_column_source)
when 'tags'
self.page_tags
else # text_area, name, universe, etc
self.attribute_values.detect { |value|
value.entity_type == content.page_type &&
value.entity_id == content.id &&
value.attribute_field_id == attribute_field.id
}.try(:value) || ""
end
end
end

View File

@ -107,6 +107,8 @@ class User < ApplicationRecord
message: "can't be larger than 500KB"
}
has_many :application_integrations
def my_universe_ids
@cached_universe_ids ||= universes.pluck(:id)
end

View File

@ -0,0 +1 @@
approvals

View File

@ -0,0 +1,41 @@
<div class="row">
<div class="col s12 m10 offset-m1 l8 offset-l2">
<%= link_to api_path do %>
<%= image_tag 'logos/both-original.png', style: 'width: 100%' %>
<% end %>
</div>
<div class="col s12">
<div class="grey-text uppercase center">SUPPORTED NOTEBOOK ENDPOINTS</div>
<ul class="collapsible">
<li>
<div class="collapsible-header">
<i class="material-icons <%= User.color %>-text"><%= User.icon %></i>
Interacting with users
</div>
<div class="collapsible-body">
<%= render partial: 'api/api_docs/endpoints/users/profile' %>
<%= render partial: 'api/api_docs/endpoints/users/active_page_types' %>
<%= render partial: 'api/api_docs/endpoints/users/authorization_token' %>
</div>
</li>
<% Rails.application.config.content_types[:all].each do |content_type| %>
<li>
<div class="collapsible-header">
<i class="material-icons <%= content_type.color %>-text"><%= content_type.icon %></i>
Interacting with <%= content_type.name.downcase.pluralize %>
</div>
<div class="collapsible-body">
<%= render partial: 'api/api_docs/endpoints/content/fetch_all_content', locals: { content_type: content_type } %>
<%= render partial: 'api/api_docs/endpoints/content/fetch_a_specific_content', locals: { content_type: content_type } %>
<%= render partial: 'api/api_docs/endpoints/content/modify_a_specific_content', locals: { content_type: content_type } %>
<%= render partial: 'api/api_docs/endpoints/content/create_a_new_content', locals: { content_type: content_type } %>
<%= render partial: 'api/api_docs/endpoints/content/reference_a_specific_content', locals: { content_type: content_type } %>
<%= render partial: 'api/api_docs/endpoints/content/delete_a_specific_content', locals: { content_type: content_type } %>
</div>
</li>
<% end %>
</ul>
</div>
</div>

View File

@ -0,0 +1,93 @@
<h2>
<i class="material-icons <%= content_type.color %>-text left"><%= content_type.icon %></i>
Create a new <%= content_type.name.downcase %>
</h2>
<div class="row">
<div class="col s12 m6 l6">
<h3>Endpoint</h3>
<p class="code">
POST /api/v1/<%= content_type.name.downcase.pluralize %>
</p>
<h3>Example call</h3>
<ul class="collection">
<li class="collection-item">
<span class="title">
<strong>application_token</strong>
<span class="blue-text">string</span>
<span class="red-text">required</span>
</span>
<p>
The token for your application.
</p>
</li>
<li class="collection-item">
<span class="title">
<strong>authorization_token</strong>
<span class="blue-text">string</span>
<span class="red-text">required</span>
</span>
<p>
The authorization token for your user.
</p>
</li>
<li class="collection-item">
<span class="title">
<strong>fields</strong>
<span class="blue-text">hash</span>
<span class="red-text">required</span>
</span>
<p>
This hash should pass field IDs as keys with strings to set that field's initial value to. For example,
<div class="code">
{
"123": "Value for field with ID 123",
"124": "Value for another field"
}
</div>
</p>
</li>
</ul>
<% if Rails.application.config.content_types[:premium].include?(content_type) %>
<div class="card-panel blue lighten-5">
Note: Because this is a <strong>Premium</strong> page, either you (the application)
or your authenticated user must have an active Premium subscription to create
a new <%= content_type.name.downcase %> page.
</div>
<% end %>
</div>
<div class="col s12 m6 l6">
<h3>Example response</h3>
<p class="code">
{
"id": 12345,
"name": "Some <%= content_type.name %>",
<% unless content_type.name == Universe.name %>
"universe_id": 2,
<% end %>
"meta": {
"created_at": "2020-02-01 08:24:20 UTC",
"updated_at": "2020-02-09 06:57:12 UTC"
},
"categories": {
"Overview": {
"fields": [
{
"id": 123,
"label": "Description",
"value": "Some Description"
},
{
"id": 124,
"label": "Another Field",
"value": "Some other value"
},
...
],
},
...
},
"references": [...]
}
</p>
</div>
</div>

View File

@ -0,0 +1,10 @@
<h2>
<i class="material-icons <%= content_type.color %>-text left"><%= content_type.icon %></i>
Delete a specific <%= content_type.name.downcase %>
</h2>
<p>
<strong>Not supported:</strong>
Deleting <%= content_type.name.downcase.pluralize %> is currently not supported over the API.
Please ask your user to delete their <%= content_type.name.downcase %> manually, or
<%= link_to 'contact us', 'https://github.com/indentlabs/notebook' %> if you need this permission for your application.
</p>

View File

@ -0,0 +1,106 @@
<h2>
<i class="material-icons <%= content_type.color %>-text left"><%= content_type.icon %></i>
Fetch a specific <%= content_type.name.downcase %>
</h2>
<div class="row">
<div class="col s12 m6 l6">
<h3>Endpoint</h3>
<p class="code">
GET /api/v1/<%= content_type.name.downcase %>/<span class="green-text">&lt;id&gt;</span>
</p>
<h3>Example call</h3>
<ul class="collection">
<li class="collection-item">
<span class="title">
<strong>application_token</strong>
<span class="blue-text">string</span>
<span class="red-text">required</span>
</span>
<p>
The token for your application.
</p>
</li>
<li class="collection-item">
<span class="title">
<strong>authorization_token</strong>
<span class="blue-text">string</span>
<span class="red-text">required</span>
</span>
<p>
The authorization token for your user.
</p>
</li>
<li class="collection-item">
<span class="title">
<strong class="green-text">id</strong>
<span class="blue-text">integer</span>
<span class="red-text">required</span>
</span>
<p>
The ID of the <%= content_type.name.downcase %> you're requesting.
</p>
</li>
<li class="collection-item">
<span class="title">
<strong>include_blank_fields</strong>
<span class="blue-text">boolean</span>
<span class="grey-text text-darken-3">(default = false)</span>
</span>
<p>
The API only returns fields that have been answered by the user. If you wish to return all fields regardless
of whether they have an answer or not, you can set <strong>include_blank_fields</strong> to <strong class="grey-text text-darken-3">true</strong>.
</p>
</li>
</ul>
</div>
<div class="col s12 m6 l6">
<h3>Example response</h3>
<p class="code">
{
"id": <span class="<%= content_type.color %>-text text-lighten-3">1</span>,
"name": <span class="<%= content_type.color %>-text text-lighten-3">"Some <%= content_type.name %>"</span>,
"description": <span class="<%= content_type.color %>-text text-lighten-3">"A description of this page"</span>,
<% unless content_type.name == Universe.name %>
"universe": {
"id": <span class="<%= content_type.color %>-text text-lighten-3">134</span>,
"name": <span class="<%= content_type.color %>-text text-lighten-3">My Super Amazing Universe</span>
},
<% end %>
"meta": {
"created_at": <span class="<%= content_type.color %>-text text-lighten-3">"2020-02-01 08:24:20 UTC"</span>,
"updated_at": <span class="<%= content_type.color %>-text text-lighten-3">"2020-02-09 06:57:12 UTC"</span>
},
"categories": [
{
"id": <span class="<%= content_type.color %>-text text-lighten-3">123</span>,
"label": <span class="<%= content_type.color %>-text text-lighten-3">"Overview"</span>,
"fields": [
{
"id": <span class="<%= content_type.color %>-text text-lighten-3">123</span>,
"type": <span class="<%= content_type.color %>-text text-lighten-3">"text_area"</span>,
"label": <span class="<%= content_type.color %>-text text-lighten-3">"Coolness factor"</span>,
"value": <span class="<%= content_type.color %>-text text-lighten-3">"Off the charts"</span>
},
{
"id": <span class="<%= content_type.color %>-text text-lighten-3">124</span>,
"label": <span class="<%= content_type.color %>-text text-lighten-3">"Related Buildings"</span>,
"type": <span class="<%= content_type.color %>-text text-lighten-3">"link"</span>,
"value": [
{
"id": <span class="<%= content_type.color %>-text text-lighten-3">345</span>,
"type": <span class="<%= content_type.color %>-text text-lighten-3">"Building"</span>,
"name": <span class="<%= content_type.color %>-text text-lighten-3">"The Office of Examples"</span>,
},
...
]
},
...
],
},
...
],
"references": [TBD]
}
</p>
</div>
</div>

View File

@ -0,0 +1,83 @@
<h2>
<i class="material-icons <%= content_type.color %>-text left"><%= content_type.icon %></i>
Fetch all <%= content_type.name.downcase.pluralize %>
</h2>
<div class="row">
<div class="col s12 m6 l6">
<h3>Endpoint</h3>
<p class="code">
GET /api/v1/<%= content_type.name.downcase.pluralize %>
</p>
<h3>Request options</h3>
<ul class="collection">
<li class="collection-item">
<span class="title">
<strong>application_token</strong>
<span class="blue-text">string</span>
<span class="red-text">required</span>
</span>
<p>
The token for your application.
</p>
</li>
<li class="collection-item">
<span class="title">
<strong>authorization_token</strong>
<span class="blue-text">string</span>
<span class="red-text">required</span>
</span>
<p>
The authorization token for your user.
</p>
</li>
<% unless content_type.name == Universe.name %>
<li class="collection-item">
<span class="title">
<strong>universe_id</strong>
<span class="blue-text">integer</span>
</span>
<p>
Limit <%= content_type.name.downcase.pluralize%> returned to only those within a particular universe. </p>
</li>
<% end %>
</ul>
</div>
<div class="col s12 m6 l6">
<h3>Example response</h3>
<p class="code">
[
{
"id": <span class="<%= content_type.color %>-text text-lighten-3">1</span>,
"name": <span class="<%= content_type.color %>-text text-lighten-3">"Some <%= content_type.name %>"</span>,
"description": <span class="<%= content_type.color %>-text text-lighten-3">"This is a user-supplied description of the page"</span>,
<% unless content_type.name == Universe.name %>
"universe": {
"id": <span class="<%= content_type.color %>-text text-lighten-3">2</span>,
"name": <span class="<%= content_type.color %>-text text-lighten-3">"The Great Story World"</span>
}
<% end %>
"meta": {
"created_at": <span class="<%= content_type.color %>-text text-lighten-3">"2020-02-01 08:24:20 UTC"</span>,
"updated_at": <span class="<%= content_type.color %>-text text-lighten-3">"2020-02-09 06:57:12 UTC"</span>
},
},
{
"id": <span class="<%= content_type.color %>-text text-lighten-3">2</span>,
"name": <span class="<%= content_type.color %>-text text-lighten-3">"Some other <%= content_type.name %>"</span>,
"description": <span class="<%= content_type.color %>-text text-lighten-3">"This is an even better page"</span>,
<% unless content_type.name == Universe.name %>
"universe": {
"id": <span class="<%= content_type.color %>-text text-lighten-3">2</span>,
"name": <span class="<%= content_type.color %>-text text-lighten-3">"The Great Story World"</span>
}
<% end %>
"meta": {
"created_at": <span class="<%= content_type.color %>-text text-lighten-3">"2020-02-01 08:24:20 UTC"</span>,
"updated_at": <span class="<%= content_type.color %>-text text-lighten-3">"2020-02-09 06:57:12 UTC"</span>
},
},
...
]
</p>
</div>
</div>

View File

@ -0,0 +1,96 @@
<h2>
<i class="material-icons <%= content_type.color %>-text left"><%= content_type.icon %></i>
Modify a specific <%= content_type.name.downcase %>
</h2>
<div class="row">
<div class="col s12 m6 l6">
<h3>Endpoint</h3>
<p class="code">
POST /api/v1/<%= content_type.name.downcase.pluralize %>/<span class="green-text">&lt;id&gt;</span>
</p>
<h3>Example call</h3>
<ul class="collection">
<li class="collection-item">
<span class="title">
<strong>application_token</strong>
<span class="blue-text">string</span>
<span class="red-text">required</span>
</span>
<p>
The token for your application.
</p>
</li>
<li class="collection-item">
<span class="title">
<strong>authorization_token</strong>
<span class="blue-text">string</span>
<span class="red-text">required</span>
</span>
<p>
The authorization token for your user.
</p>
</li>
<li class="collection-item">
<span class="title">
<strong class="green-text">id</strong>
<span class="blue-text">integer</span>
<span class="red-text">required</span>
</span>
<p>
The ID of the <%= content_type.name.downcase %> you're modifying.
</p>
</li>
<li class="collection-item">
<span class="title">
<strong>fields</strong>
<span class="blue-text">hash</span>
<span class="red-text">required</span>
</span>
<p>
This hash should pass field IDs as keys with strings to update that field's value to. For example,
<div class="code">
{
"123": "Value for field with ID 123",
"124": "Value for another field"
}
</div>
</p>
</li>
</ul>
</div>
<div class="col s12 m6 l6">
<h3>Example response</h3>
<p class="code">
{
"id": 1,
"name": "Some <%= content_type.name %>",
<% unless content_type.name == Universe.name %>
"universe_id": 2,
<% end %>
"meta": {
"created_at": "2020-02-01 08:24:20 UTC",
"updated_at": "2020-02-09 06:57:12 UTC"
},
"categories": {
"Overview": {
"fields": [
{
"id": 123,
"label": "Description",
"value": "Some Description"
},
{
"id": 124,
"label": "Another Field",
"value": "Some other value"
},
...
],
},
...
},
"references": [...]
}
</p>
</div>
</div>

View File

@ -0,0 +1,131 @@
<h2>
<i class="material-icons <%= content_type.color %>-text left"><%= content_type.icon %></i>
Add an online reference to a specific <%= content_type.name.downcase %>
</h2>
<% unless current_page?(api_references_path) %>
<p>
<%= link_to "Click here to read more about how online references work on Notebook.ai.", api_references_path %>
</p>
<% end %>
<div class="row">
<div class="col s12 m6 l6">
<h3>Endpoint</h3>
<p class="code">
POST /api/v1/<%= content_type.name.downcase.pluralize %>/<span class="green-text">&lt;id&gt;</span>/references
</p>
<h3>Example call</h3>
<ul class="collection">
<li class="collection-item">
<span class="title">
<strong>application_token</strong>
<span class="blue-text">string</span>
<span class="red-text">required</span>
</span>
<p>
The token for your application.
</p>
</li>
<li class="collection-item">
<span class="title">
<strong>authorization_token</strong>
<span class="blue-text">string</span>
<span class="red-text">required</span>
</span>
<p>
The authorization token for your user.
</p>
</li>
<li class="collection-item">
<span class="title">
<strong class="green-text">id</strong>
<span class="blue-text">integer</span>
<span class="red-text">required</span>
</span>
<p>
The ID of the <%= content_type.name.downcase %> you're adding a reference to.
</p>
</li>
<li class="collection-item">
<span class="title">
<strong>reference_url</strong>
<span class="blue-text">url</span>
<span class="red-text">required</span>
</span>
<p>
The URL that this reference will link users to when they click it on Notebook.ai.
</p>
</li>
<li class="collection-item">
<span class="title">
<strong>reference_title</strong>
<span class="blue-text">string</span>
</span>
<p>
The title that should be displayed on Notebook.ai when this reference is displayed.
</p>
</li>
<li class="collection-item">
<span class="title">
<strong>reference_description</strong>
<span class="blue-text">string</span>
</span>
<p>
The description that should be shown next to this reference on Notebook.ai.
Only used if <strong>reference_title</strong> is also given.
Maximum of 300 characters; more than that will be truncated with an ellipses when displaying.
</p>
</li>
<li class="collection-item">
<span class="title">
<strong>reference_image</strong>
<span class="blue-text">url</span>
</span>
<p>
An image may optionally be included to display with references in some views when the design permits.
</p>
</li>
</ul>
</div>
<div class="col s12 m6 l6">
<h3>Example response</h3>
<p class="code">
{
"id": 1,
"name": "Some <%= content_type.name %>",
<% unless content_type.name == Universe.name %>
"universe_id": 2,
<% end %>
"meta": {
"created_at": "2020-02-01 08:24:20 UTC",
"updated_at": "2020-02-09 06:57:12 UTC"
},
"categories": {
"Overview": {
"fields": [
{
"id": 123,
"label": "Description",
"value": "Some Description"
},
{
"id": 124,
"label": "Another Field",
"value": "Some other value"
},
...
],
},
...
},
"references": [
{
"url": "https://www.example.com/something/wow",
"title": "Reference name",
"description": "This is something really cool on the Internet that this <%= content_type.name.downcase %> appears in!",
"reference_image": "https://www.example.com/<%= content_type.name.downcase.pluralize %>/12345.png"
}
]
}
</p>
</div>
</div>

View File

@ -0,0 +1,65 @@
<h2>
<i class="material-icons <%= User.color %>-text left"><%= User.icon %></i>
Fetch a user's active page types
</h2>
<div class="row">
<div class="col s12 m6 l6">
<h3>Endpoint</h3>
<p class="code">
GET /api/v1/<%= User.name.downcase.pluralize %>/<span class="green-text">&lt;id&gt;</span>/active_page_types
</p>
<h3>Example call</h3>
<ul class="collection">
<li class="collection-item">
<span class="title">
<strong>application_token</strong>
<span class="blue-text">string</span>
<span class="red-text">required</span>
</span>
<p>
The token for your application.
</p>
</li>
<li class="collection-item">
<span class="title">
<strong>authorization_token</strong>
<span class="blue-text">string</span>
<span class="red-text">required</span>
</span>
<p>
The authorization token for your user.
</p>
</li>
<li class="collection-item">
<span class="title">
<strong class="green-text">id</strong>
<span class="blue-text">integer</span>
<span class="red-text">required</span>
</span>
<p>
The ID of the <%= User.name.downcase %> you're fetching.
</p>
</li>
</ul>
</div>
<div class="col s12 m6 l6">
<h3>Example response</h3>
<p class="code">
{
"id": 1,
"name": "Alice Quinn",
"username": "@alice",
"active_page_types": [
"Character",
"Location",
"Building",
"Landmark",
"Town",
...
]
}
</p>
</div>
</div>

View File

@ -0,0 +1,56 @@
<h2>
<i class="material-icons <%= User.color %>-text left"><%= User.icon %></i>
Fetch a user's authorization token
</h2>
<div class="row">
<div class="col s12 m6 l6">
<h3>Endpoint</h3>
<p class="code">
GET /api/v1/<%= User.name.downcase.pluralize %>/<span class="green-text">&lt;id&gt;</span>/authorization
</p>
<h3>Example call</h3>
<ul class="collection">
<li class="collection-item">
<span class="title">
<strong>application_token</strong>
<span class="blue-text">string</span>
<span class="red-text">required</span>
</span>
<p>
The token for your application.
</p>
</li>
<li class="collection-item">
<span class="title">
<strong class="green-text">id</strong>
<span class="blue-text">integer</span>
<span class="red-text">required</span>
</span>
<p>
The ID of the <%= User.name.downcase %> you're fetching.
</p>
</li>
</ul>
<div class="card-panel blue lighten-5">
<p>
Each user's authorization token is already passed to your app through your
<%= link_to 'callback URL', '#' %> when that user first authorizes your app and should be stored (and associated with your user) at that time.
However, this endpoint is useful if you need to retrieve a particular user's authorization token later.
</p>
<p>
This endpoint will only return an authorization token if the user you're requesting has already authorized your app.
</p>
</div>
</div>
<div class="col s12 m6 l6">
<h3>Example response</h3>
<p class="code">
{
"id": 1,
"token": "a1ed29894c458d0093f74ededa59debc953712d9b412c224"
}
</p>
</div>
</div>

View File

@ -0,0 +1,76 @@
<h2>
<i class="material-icons <%= User.color %>-text left"><%= User.icon %></i>
Fetch a user's profile information
</h2>
<div class="row">
<div class="col s12 m6 l6">
<h3>Endpoint</h3>
<p class="code">
GET /api/v1/<%= User.name.downcase.pluralize %>/<span class="green-text">&lt;id&gt;</span>
</p>
<h3>Example call</h3>
<ul class="collection">
<li class="collection-item">
<span class="title">
<strong>application_token</strong>
<span class="blue-text">string</span>
<span class="red-text">required</span>
</span>
<p>
The token for your application.
</p>
</li>
<li class="collection-item">
<span class="title">
<strong>authorization_token</strong>
<span class="blue-text">string</span>
<span class="red-text">required</span>
</span>
<p>
The authorization token for your user.
</p>
</li>
<li class="collection-item">
<span class="title">
<strong class="green-text">id</strong>
<span class="blue-text">integer</span>
<span class="red-text">required</span>
</span>
<p>
The ID of the <%= User.name.downcase %> you're fetching.
</p>
</li>
</ul>
</div>
<div class="col s12 m6 l6">
<h3>Example response</h3>
<p class="code">
{
"id": 1,
"name": "Alice Quinn",
"username": "@alice",
"meta": {
"referral_code": "ABCDEFGHI-JKLMNOP-QRSTUV-WXYZ",
"premium_active": true
},
"profile": {
"bio": "Just a small-town girl",
"interests": "cooking, going on walks, living life, magic",
"website": "http://www.example.com",
"inspirations": "old Lovecraft stories",
"other_names": "Allie",
"occupation": "I exist solely as an example in some API documentation",
"favorite": {
"author": "Oscar Wilde",
"genre": "Fantasy",
"book": "Harry Potter and the Sorcerer's Stone",
"quote": "This isn't even a real quote!",
"page_type": "Creature"
}
}
}
</p>
</div>
</div>

View File

@ -0,0 +1,161 @@
<div class="row">
<div class="col s12 m10 offset-m1 l8 offset-l2">
<%= link_to api_path do %>
<%= image_tag 'logos/both-original.png', style: 'width: 100%' %>
<% end %>
</div>
<div class="col s12 m10 offset-m1 l8 offset-l2">
<div class="card">
<div class="card-content">
<div class="card-title">
Supercharge your writing applications with Notebook.ai's world-class worldbuilding resources
</div>
<div class="row">
<div class="col s12 m6 l5">
<br />
<p>
<% Rails.application.config.content_types[:all].each do |content_type| %>
<i class="material-icons small <%= content_type.color %>-text tooltipped" data-tooltip="Interact with <%= content_type.name.downcase.pluralize %>"><%= content_type.icon %></i>
<% end %>
</p>
<br />
<p>
We now offer an API for developers that would like to take advantage of the
features and worlds in Notebook.ai.
</p>
<br />
<p>
To get started, you'll want to <%= link_to 'register an application', api_applications_path %>
and get your API key. In order to access any user's notebook, you'll first need
them to <%= link_to 'authenticate your application', api_approvals_path %> and give you an
access key specific to their notebook.
</p>
</div>
<div class="col s12 m6 l6 offset-l1">
<p>
In other words, it's just three easy steps to get started:
</p>
<ul class="stepper">
<li class="step active">
<div data-step-label="Register your application" class="step-title waves-effect waves-dark">Step 1</div>
<div class="step-content">
<div class="row">
<p class="col s12">
To receive an access token for your application, you'll first need to register it for use.
</p>
</div>
<div class="step-actions">
<%= link_to 'Register application', api_applications_path, class: 'blue white-text btn' %>
<button class="waves-effect waves-dark btn white black-text next-step">Next step</button>
</div>
</div>
</li>
<li class="step">
<div data-step-label="Receive user approval" class="step-title waves-effect waves-dark">Step 2</div>
<div class="step-content">
<div class="row">
<p class="col s12">
In order to access or modify a user's notebook pages on their behalf, you'll first need that user to
approve your application. You can set up an approval flow that notifies your application whenever
a user authorizes its use, or you can direct them to <%= link_to 'their integrations page', api_integrations_path %>
and have them paste their authorization code directly into your app.
</p>
</div>
<div class="step-actions">
<%= link_to 'Create approval flow', api_approvals_path, class: 'btn blue white-text' %>
<button class="waves-effect waves-dark btn white black-text next-step">Next step</button>
</div>
</div>
</li>
<li class="step">
<div data-step-label="Make API calls!" class="step-title waves-effect waves-dark">Step 3</div>
<div class="step-content" style="display: none;">
<div class="row">
<p class="col s12">
Once you've received an application token and your first user authorization token, you can start making
API calls on that user's behalf. You will need to provide these tokens with every API call.
</p>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<%= link_to api_docs_path do %>
<div class="card hoverable blue col s12 m10 offset-m1 l8 offset-l2">
<div class="card-content white-text center">
<div class="card-title">Browse available API endpoints</div>
<p>Documentation for developers, by developers</p>
</div>
</div>
<% end %>
<div class="card col s12 m10 offset-m1 l8 offset-l2">
<div class="card-content">
<div class="card-title">
Gain full access to
<% Rails.application.config.content_types[:all_non_universe].each do |type| %>
<span class="<%= type.color %>-text"><%= type.name.downcase.pluralize %>,</span>
<% end %>
and <span class="<%= Universe.color %>-text">universes</span>.
</div>
<div class="row">
<div class="col s12 m12 l6">
<p>
After a user authenticates your application, you'll have full access to integrate their worldbuilding pages into your app.
You can show them their characters, let them edit one of their existing creatures, pin their towns and landmarks to your maps,
create new items, and more &mdash; all without leaving your app.
</p>
<br />
<p>
<%= link_to 'See the available endpoints by clicking here.', api_docs_path %>
</p>
</div>
<div class="col s12 m12 l6">
<%= image_tag 'screenshots/page-types.png', width: '100%', class: 'materialboxed', data: { caption: "A few of the page types available on Notebook.ai" } %>
</div>
</div>
</div>
</div>
<div class="card col s12 m10 offset-m1 l8 offset-l2">
<div class="card-content">
<div class="card-title">
Generate two-way mentions for your content and drive traffic back to your site
</div>
<div class="row">
<div class="col s12 m5 l5">
<%= image_tag 'screenshots/integrations.png', width: '100%', class: 'materialboxed', data: { caption: "Each integration gets its own tab on Notebook.ai pages" } %>
</div>
<div class="col s12 m7 l7">
<p>
When a user links their notebook page in your app, we can automatically show a link back to your page from that notebook page, too.
</p>
<br />
<p>
For example, if a user publishes a story about Alice and Bob on your site and links their Alice and Bob pages from Notebook.ai, we'll
show a link to that story on both Alice and Bob's Notebook.ai pages also.
</p>
<br />
<p>
<%= link_to 'See the available endpoints by clicking here.', api_docs_path %>
</p>
</div>
</div>
</div>
</div>
<div class="col s12">
<p class="grey-text center">
Want to build something our API doesn't support yet?
<%= link_to 'Get in touch!', 'https://github.com/indentlabs/notebook/issues' %>
</p>
</div>
</div>

View File

@ -0,0 +1 @@
integrations

View File

View File

@ -0,0 +1,57 @@
<div class="row">
<div class="col s12">
<div class="card">
<div class="card-content">
<div class="row">
<div class="col s12 m7 l8">
<div class="card-title">Referencing pages around the Internet on Notebook.ai</div>
<p>
Application developers can now add external links directly to individual Notebook.ai pages via <%= link_to 'the API', api_path %>.
</p>
<br />
<p>
Each application receives its own tab under the "References" sidebar when viewing any notebook page, which will list
all external references when clicked. Each reference must have a URL, and may optionally also have a title, description,
and image to accompany it.
</p>
<br />
<p>
Please use this API only to link pages in which the content of a particular Notebook.ai page appears or is predominantly featured.
For a character, for example, consider adding references to stories in which they appear. For a location, consider adding references
to stories set in that location.
</p>
<br />
<div class="center grey lighten-3">
<%= image_tag 'screenshots/integration-references.png', class: 'hoverable' %>
</div>
<br />
<div class="center">
<%= link_to 'Browse the full API documentation', api_path, class: 'blue btn white-text' %>
</div>
</div>
<div class="col s12 m5 l4">
<%= image_tag 'screenshots/integrations.png', class: 'hoverable' %>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col s12">
<ul class="collapsible">
<% content_type = Character %>
<li class="active">
<div class="collapsible-header">
<i class="material-icons <%= content_type.color %>-text"><%= content_type.icon %></i>
Example API request to add an external reference to a <%= content_type.name.downcase %>
</div>
<div class="collapsible-body">
<%= render partial: 'api/api_docs/endpoints/reference_a_specific_content', locals: { content_type: Character } %>
</div>
</li>
</ul>
</div>
</div>

View File

@ -0,0 +1,16 @@
<%= form_for api_applications_path do |f| %>
<p>
If you'd like to create an application that is able to use the <%= link_to 'Notebook.ai API', api_path %>,
please fill out the following form to get started.
</p>
<br />
<% [:name, :description, :organization_name, :organization_url, :website_url, :privacy_policy_url, :authorization_callback_url, :event_ping_url].each do |field| %>
<div class="input-field">
<%= f.label field.to_s.humanize %>
<%= f.text_field field, placeholder: field.to_s.humanize %>
</div>
<% end %>
<%= f.submit 'Register application', class: 'btn white-text blue' %>
<% end %>

View File

@ -0,0 +1,37 @@
<% 1.times do %><br /><% end %>
<div class="row">
<div class="col s10 offset-s1 m6 offset-m3 l2 offset-l5 center">
<%= image_tag 'logos/book-small.png', width: '100%' %>
<div class="blue-text uppercase">Notebook.ai</div>
<i class="material-icons blue-text">add</i>
</div>
<div class="col s12 m10 offset-m l6 offset-l3">
<div class="hoverable card" style="border-bottom: 5px solid #2196F3">
<div class="card-content">
<div class="card-title">
<i class="material-icons left <%= ApplicationIntegration.color %>-text"><%= ApplicationIntegration.icon %></i>
<%= @application_integration.name %>
</div>
<p>
<div class="grey-text uppercase">description</div>
<%= simple_format @application_integration.description %>
</p>
<br />
<p class="grey-text text-darken-1">
Authorizing this application will give it full access to manage your Notebook.ai pages, including your private pages. Please only authorize this application
if you trust the developer and are here on purpose.
</p>
<br />
<p class="grey-text text-darken-1">
You can manage your authorized Notebook.ai integrations at any time in your <strong><%= link_to 'Data Vault', '#' %></strong>.
</p>
<br />
<%= form_for [:api, IntegrationAuthorization.new({ user: current_user, application_integration: @application_integration })] do |f| %>
<%= f.hidden_field :application_integration_id, value: @application_integration.id %>
<%= f.submit "Authorize #{@application_integration.name}", class: "#{ApplicationIntegration.color} btn white-text", style: 'width: 100%' %>
<% end %>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,6 @@
<h1>Editing Application Integration</h1>
<%= render 'form', application_integration: @application_integration %>
<%= link_to 'Show', @application_integration %> |
<%= link_to 'Back', application_integrations_path %>

View File

@ -0,0 +1,87 @@
<div class="row">
<div class="col s12 m10 l8">
<ul class="collapsible">
<li class="<%= 'active' unless @applications.any? %>">
<div class="collapsible-header blue white-text">
<i class="material-icons">extension</i> Register <%= @applications.any? ? 'another' : 'a' %> Notebook.ai application
</div>
<div class="collapsible-body white">
<%= render partial: 'api/application_integrations/form', locals: { application_integration: ApplicationIntegration.new(user: current_user) } %>
</div>
</li>
</ul>
</div>
<div class="col s12 m2 l4">
</div>
</div>
<% if @applications.any? %>
<div class="row">
<div class="col s12 m10 offset-m1 l8">
<h1 style="font-size: 2em" class="grey-text">Your applications</h1>
<% @applications.each do |application| %>
<%= link_to api_application_path(application), class: 'black-text' do %>
<div class="card-panel hoverable">
<p>
<i class="material-icons left medium <%= ApplicationIntegration.color %>-text"><%= ApplicationIntegration.icon %></i>
<strong><%= application.name %></strong>
<!-- (App ID: <%= application.id %>) -->
<span class="orange white-text badge">live</span>
</p>
<p>
<%= truncate(application.description, length: 400) %>
</p>
<br />
<div class="row grey-text text-darken-3">
<div class="col s12 m12 l4">
<i class="material-icons left <%= User.color %>-text"><%= User.icon %></i>
<%= pluralize application.integration_authorizations.count, 'user' %>
</div>
<div class="col s12 m12 l4">
<%= pluralize application.api_requests.successful.count, 'successful request' %>
<div class="progress tooltipped green lighten-5" data-tooltip="<%= application.current_quota_usage_percentage %>% of requests used">
<div class="determinate green" style="width: <%= application.current_quota_usage_percentage %>%"></div>
</div>
</div>
<div class="col s12 m12 l4 <%= application.error_rate_color %>-text text-darken-3">
<span class="badge <%= application.error_rate_color %> lighten-5"><%= (application.request_error_rate * 100).round(2) %>% error rate</span>
<%= pluralize application.api_requests.errored.count, 'error' %>
<!--
<div class="progress red lighten-5">
<div class="determinate red" style="width: 5%"></div>
</div>
-->
</div>
</div>
</div>
<% end %>
<% end %>
</div>
<div class="col s12 m2 l4">
<h1 style="font-size: 2em">&nbsp;</h1>
<div class="hoverable card">
<div class="card-content">
<div class="card-title">Your available features</div>
<ul>
<li>
<i class="material-icons left green-text">check</i> Basic Notebook.ai endpoints
</li>
<li class="clearfix">
<i class="material-icons left red-text">close</i> Premium Notebook.ai endpoints
</li>
</ul>
<p>
Some helpful text here
</p>
<br />
<p class="green-text">
47% API quota left
</p>
</div>
<div class="card-action">
<%= link_to 'Manage your billing plan', '#' %>
</div>
</div>
</div>
</div>
<% end %>

View File

@ -0,0 +1,5 @@
<h1>New Application Integration</h1>
<%= render 'form', application_integration: @application_integration %>
<%= link_to 'Back', application_integrations_path %>

View File

@ -0,0 +1,106 @@
<h5>
<%= link_to api_applications_path, class: 'grey-text tooltipped', style: 'position: relative; top: 4px;', data: {
position: 'bottom',
enterDelay: '500',
tooltip: "Back to your application list"
} do %>
<i class="material-icons">arrow_back</i>
<% end %>
Your applications
</h5>
<div class="row">
<div class="col s12">
<div class="card">
<div class="card-content">
<div class="card-title">
<span class="badge orange white-text">live</span>
<span class="orange-text">
<i class="material-icons left">extension</i>
<%= @application_integration.name %>
</span>
<small>(App ID: <%= @application_integration.id %>)</small>
</div>
<br />
<div class="row">
<div class="col s12 m6">
<% [:description].each do |field| %>
<div class="grey-text uppercase">
<%= field.to_s.humanize %>
</div>
<div>
<%= simple_format @application_integration.send(field) %>
</div>
<br />
<% end %>
<div class="grey-text uppercase">
Organization
</div>
<div>
<%= link_to @application_integration.organization_name, @application_integration.organization_url %>
</div>
<br />
<% [:website_url, :privacy_policy_url, :authorization_callback_url, :event_ping_url].each do |field| %>
<div class="grey-text uppercase"><%= field.to_s.humanize %></div>
<div>
<%= link_to @application_integration.send(field), @application_integration.send(field) %>
</div>
<br />
<% end %>
</div>
<div class="col s12 m6">
<div class="grey-text uppercase">
Application Token
</div>
<pre class="black white-text" style="padding: 1em"><%= @application_integration.application_token || 'No token set' %></pre>
<div class="red-text">
Do <strong>NOT</strong> share this with others you do not trust or make client-side requests that expose this token. This token may be used to make API
calls on your application's behalf and unauthorized use puts you at risk of strangers using up your API quota.
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col s12 m6 l4">
<div class="grey-text uppercase">Billing plan</div>
<% if current_user.on_premium_plan? %>
<div class="card-panel green lighten-3 black-text">
You're on a Premium plan. All endpoints are available.
</div>
<% else %>
<div class="card-panel red lighten-3">
You are not currently on a Premium plan. API requests that require a Premium plan will succeed for users that have Premium, but fail for other users.
<%= link_to 'Upgrade to a Premium plan', '#' %> to ensure your API requests succeed for all users!
</div>
<% end %>
</div>
<div class="col s12 m6 l4">
<div class="grey-text uppercase">API quota</div>
<div class="card-panel yellow lighten-4 black-text">
<div>
<strong><%= @application_integration.current_quota_usage_percentage %>% used in this billing period</strong>
</div>
<div class="progress tooltipped green lighten-5" data-tooltip="<%= @application_integration.current_quota_usage_percentage %>% of requests used">
<div class="determinate green" style="width: <%= @application_integration.current_quota_usage_percentage %>%"></div>
</div>
<p>
You've used <%= number_with_delimiter @application_integration.api_requests.successful.count %> of your alotted 10,000 calls this month.
<%= link_to 'Upgrade to Premium', '#' %> to ensure you don't experience any disruptions when hitting your limit.
</p>
</div>
</div>
<div class="col s12 m6 l4">
<div class="grey-text uppercase">Errors</div>
<div class="card-panel <%= @application_integration.error_rate_color %> lighten-4 black-text">
<div><strong><%= (@application_integration.request_error_rate * 100).round(2) %>% error rate</strong></div>
<div class="progress red lighten-5">
<div class="determinate red" style="width: <%= (@application_integration.request_error_rate * 100).round(2) %>%"></div>
</div>
<!-- Browse recent errors -->
<!-- Set up error callback URL -->
</div>
</div>
</div>

View File

@ -0,0 +1,2 @@
<h1>IntegrationAuthorizations#create</h1>
<p>Find me in app/views/integration_authorizations/create.html.erb</p>

View File

@ -0,0 +1,2 @@
<h1>IntegrationAuthorizations#show</h1>
<p>Find me in app/views/integration_authorizations/show.html.erb</p>

View File

@ -10,6 +10,7 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js" integrity="sha256-KM512VNnjElC30ehFwehXjx1YCHPiQkOPmqnrWtpccM=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui-touch-punch/0.2.3/jquery.ui.touch-punch.min.js" ></script>
<%# todo: Is there a way to play nicer with thredded's jquery? %>
<% unless request.env['REQUEST_PATH'].start_with?('/forum/') %>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-ujs/1.2.2/rails.min.js" integrity="sha256-BbyWhCn0G+F6xbWJ2pcI5LnnpsnpSzyjJNVtl7ABp+M=" crossorigin="anonymous"></script>
<% end %>

View File

@ -0,0 +1,102 @@
<% if user_signed_in? %>
<%= render partial: 'layouts/navbar/recent_content_dropdown' %>
<%= render partial: 'layouts/modals/search' %>
<% end %>
<div class="navbar-fixed">
<nav class="navbar nav-extended <%= 'logged-in' if user_signed_in? %>" style="background-color: <%= @navbar_color.presence || '#2196F3' %>">
<div class="nav-wrapper">
<ul>
<li>
<%= link_to 'My Applications', api_applications_path %>
</li>
<li>
<%= link_to 'API Documentation', api_docs_path %>
</li>
</ul>
<ul class="right">
<% if user_signed_in? %>
<li>
<a class="waves-effect waves-light tooltipped dropdown-trigger" href="#notifications"
data-tooltip="You have <%= pluralize @user_notifications.reject { |n| n.viewed_at? }.count, 'unread notification' %>."
data-target="notifications-dropdown">
<i class="material-icons"><%= @user_notifications.reject { |n| n.viewed_at? }.any? ? 'notifications_active' : 'notifications_none' %></i>
</a>
</li>
<ul id='notifications-dropdown' class='dropdown-content'>
<% @user_notifications.each do |notification| %>
<li class="<%= 'unread-notification' unless notification.viewed_at? %>">
<%= link_to main_app.notification_path(notification), class: 'notification-link' do %>
<i class="material-icons <%= notification.icon_color %>-text"><%= notification.icon %></i>
<%= notification.message_html.html_safe %>
<div>
<small class="grey-text"><%= time_ago_in_words notification.happened_at %> ago</small>
</div>
<% end %>
</li>
<li class="divider"></li>
<% end %>
<li class="divider"></li>
<li class="blue lighten-1">
<%= link_to main_app.notifications_path, class: 'white-text' do %>
<i class="material-icons left">notifications</i>
View all notifications
<% end %>
</li>
<li class="blue lighten-2">
<%= link_to main_app.mark_all_read_path, class: 'white-text' do %>
<i class="material-icons left">notifications_none</i>
Mark all as read
<% end %>
</li>
</ul>
<% else %>
<li>
<%= link_to 'Sign in', main_app.new_user_session_path %>
</li>
<li>
<%= link_to 'Sign up', main_app.new_user_registration_path %>
</li>
<% end %>
</ul>
<ul class="left">
<% if user_signed_in? %>
<li>
<a class="sidenav-trigger" href="#" data-target="sidenav-left">
<i class="material-icons">menu</i>
</a>
</li>
<li class="hide-on-large-only">
<%= link_to main_app.root_path, class: 'tooltipped', data: { tooltip: 'Your dashboard' } do %>
<i class="material-icons">dashboard</i>
<% end %>
</li>
<% else %>
<li>
<%= link_to 'Notebook.ai', main_app.root_path %>
</li>
<% end %>
</ul>
</div>
<% if @navbar_actions.present? && @navbar_actions.any? %>
<style>
main {
padding-top: 50px;
}
</style>
<div class="nav-content">
<ul class="tabs tabs-transparent">
<% @navbar_actions.each do |action| %>
<li class="tab <%= action[:class] %>">
<a class="white-text <%= 'active' if action[:href] == request.env['REQUEST_PATH'] %>" href="<%= action[:href] %>" target="<%= action[:target] || '_self' %>">
<%= action[:label] %>
</a>
</li>
<% end %>
</ul>
</div>
<% end %>
</nav>
</div>

View File

@ -8,7 +8,7 @@
# Default & site-wide values
display_meta_tags site: 'Notebook.ai',
publisher: 'https://plus.google.com/118076966717703203223',
publisher: 'https://www.facebook.com/IndentLabs',
image_src: image_url('logos/both-original.png'),
description: 'Notebook.ai is a set of tools for writers, game designers, and roleplayers to create magnificent universes — and everything within them.',
# Recommended keywords tag length: up to 255 characters, 20 words.

View File

@ -20,7 +20,6 @@
</ul>
<%= yield %>
<a href="https://plus.google.com/118076966717703203223" rel="publisher"></a>
</main>
</body>

View File

@ -38,6 +38,5 @@
<%= render partial: 'content/keyboard_controls_help_modal' %>
<%= render 'layouts/ganalytics' %>
</body>
</html>

View File

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en">
<head>
<%= render 'layouts/common_head' %>
<link rel="stylesheet" href="https://unpkg.com/materialize-stepper@3.1.0/dist/css/mstepper.min.css" />
<script src="https://unpkg.com/materialize-stepper@3.1.0/dist/js/mstepper.min.js"></script>
</head>
<body class="api-docs">
<%= render 'layouts/developer_navbar' %>
<main>
<div class="container">
<div class="card-panel yellow lighten-4">
<strong>Note:</strong> The Notebook.ai is currently in private beta and under active development. Not everything has been implemented and tested yet.
Please <%= link_to 'let me know', 'https://github.com/indentlabs/notebook/issues' %> about any problems.
</div>
</div>
<div class="container-fluid">
<div class="row">
<div class="col s12 m12 l10 offset-l1">
<%= yield %>
</div>
</div>
</div>
</main>
<%= react_component("Footer") %>
<%= render 'layouts/ganalytics' %>
<script>
var stepper = document.querySelector('.stepper');
var stepperInstace = new MStepper(stepper)
</script>
</body>
</html>

View File

@ -11,7 +11,7 @@
<main>
<div class="mobile-navbar-spacer hide-on-med-and-up">&nbsp;</div>
<%#
<%=
react_component("PageLookupSidebar", {
})
%>

View File

@ -9,13 +9,9 @@
<main>
<%= yield %>
<%= render 'layouts/ganalytics' %>
<a href="https://plus.google.com/118076966717703203223" rel="publisher"></a>
</main>
<%= react_component("Footer") unless defined?(@show_footer) && !@show_footer %>
<%= render 'layouts/ganalytics' %>
</body>
</html>

View File

@ -248,6 +248,26 @@ Rails.application.routes.draw do
# API Endpoints
namespace :api do
resources :application_integrations, path: :applications, as: :applications do
get '/authorize', action: :authorize, on: :member
end
scope '/authorizations' do
post '/create', to: 'integration_authorizations#create', as: :integration_authorizations
end
get '/', to: 'api_docs#index'
get '/docs', to: 'api_docs#docs'
# get '/applications', to: 'api_docs#applications'
get '/approvals', to: 'api_docs#approvals'
get '/integrations', to: 'api_docs#integrations'
get '/pricing', to: 'api_docs#pricing'
scope 'docs' do
get '/', to: 'api_docs#index'
get '/references', to: 'api_docs#references'
end
namespace :v1 do
scope '/categories' do
get '/suggest/:entity_type', to: 'attribute_categories#suggest'
@ -258,6 +278,16 @@ Rails.application.routes.draw do
scope '/answers' do
get '/suggest/:entity_type/:field_label', to: 'attributes#suggest'
end
# Content index path
Rails.application.config.content_types[:all].each do |content_type|
get "#{content_type.name.downcase.pluralize}", to: "api##{content_type.name.downcase.pluralize}"
end
# Content show paths
Rails.application.config.content_types[:all].each do |content_type|
get "#{content_type.name.downcase}/:id", to: "api##{content_type.name.downcase}"
end
end
end

View File

@ -0,0 +1,18 @@
class CreateApplicationIntegrations < ActiveRecord::Migration[6.0]
def change
create_table :application_integrations do |t|
t.references :user, null: false, foreign_key: true
t.string :name
t.string :description
t.string :organization_name
t.string :organization_url
t.string :website_url
t.string :privacy_policy_url
t.string :token
t.datetime :last_used_at
t.string :authorization_callback_url
t.timestamps
end
end
end

View File

@ -0,0 +1,5 @@
class AddEventPingUrlToApplicationIntegrations < ActiveRecord::Migration[6.0]
def change
add_column :application_integrations, :event_ping_url, :string
end
end

View File

@ -0,0 +1,5 @@
class AddApplicationTokenToApplicationIntegrations < ActiveRecord::Migration[6.0]
def change
add_column :application_integrations, :application_token, :string
end
end

View File

@ -0,0 +1,12 @@
class CreateIntegrationAuthorizations < ActiveRecord::Migration[6.0]
def change
create_table :integration_authorizations do |t|
t.references :user, null: false, foreign_key: true
t.references :application_integration, null: false, foreign_key: true
t.string :referral_url
t.string :ip_address
t.timestamps
end
end
end

View File

@ -0,0 +1,7 @@
class AddAuthorizationSecurityLogToIntegrationAuthorizations < ActiveRecord::Migration[6.0]
def change
add_column :integration_authorizations, :origin, :string
add_column :integration_authorizations, :content_type, :string
add_column :integration_authorizations, :user_agent, :string
end
end

View File

@ -0,0 +1,5 @@
class AddUserTokenToIntegrationAuthorizations < ActiveRecord::Migration[6.0]
def change
add_column :integration_authorizations, :user_token, :string
end
end

View File

@ -0,0 +1,13 @@
class CreateApiRequests < ActiveRecord::Migration[6.0]
def change
create_table :api_requests do |t|
t.references :application_integration, null: true, foreign_key: true
t.references :integration_authorization, null: true, foreign_key: true
t.string :result
t.integer :updates_used, default: 0
t.string :ip_address
t.timestamps
end
end
end

View File

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2020_07_22_004641) do
ActiveRecord::Schema.define(version: 2020_09_12_000306) do
create_table "active_storage_attachments", force: :cascade do |t|
t.string "name", null: false
@ -41,6 +41,36 @@ ActiveRecord::Schema.define(version: 2020_07_22_004641) do
t.index ["user_id"], name: "index_api_keys_on_user_id"
end
create_table "api_requests", force: :cascade do |t|
t.integer "application_integration_id"
t.integer "integration_authorization_id"
t.string "result"
t.integer "updates_used", default: 0
t.string "ip_address"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["application_integration_id"], name: "index_api_requests_on_application_integration_id"
t.index ["integration_authorization_id"], name: "index_api_requests_on_integration_authorization_id"
end
create_table "application_integrations", force: :cascade do |t|
t.integer "user_id", null: false
t.string "name"
t.string "description"
t.string "organization_name"
t.string "organization_url"
t.string "website_url"
t.string "privacy_policy_url"
t.string "token"
t.datetime "last_used_at"
t.string "authorization_callback_url"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "event_ping_url"
t.string "application_token"
t.index ["user_id"], name: "index_application_integrations_on_user_id"
end
create_table "archenemyships", force: :cascade do |t|
t.integer "user_id"
t.integer "character_id"
@ -1421,6 +1451,21 @@ ActiveRecord::Schema.define(version: 2020_07_22_004641) do
t.index ["user_id"], name: "index_image_uploads_on_user_id"
end
create_table "integration_authorizations", force: :cascade do |t|
t.integer "user_id", null: false
t.integer "application_integration_id", null: false
t.string "referral_url"
t.string "ip_address"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "origin"
t.string "content_type"
t.string "user_agent"
t.string "user_token"
t.index ["application_integration_id"], name: "index_integration_authorizations_on_application_integration_id"
t.index ["user_id"], name: "index_integration_authorizations_on_user_id"
end
create_table "item_magics", force: :cascade do |t|
t.integer "item_id"
t.integer "magic_id"
@ -2168,6 +2213,7 @@ ActiveRecord::Schema.define(version: 2020_07_22_004641) do
t.datetime "updated_at", precision: 6, null: false
t.string "explanation"
t.string "cached_content_name"
t.datetime "deleted_at"
t.index ["content_type", "content_id"], name: "polycontent_collection_index"
t.index ["page_collection_id"], name: "index_page_collection_submissions_on_page_collection_id"
t.index ["user_id"], name: "index_page_collection_submissions_on_user_id"
@ -2187,6 +2233,7 @@ ActiveRecord::Schema.define(version: 2020_07_22_004641) do
t.string "description"
t.boolean "allow_submissions"
t.string "slug"
t.datetime "deleted_at"
t.index ["user_id"], name: "index_page_collections_on_user_id"
end
@ -2243,6 +2290,7 @@ ActiveRecord::Schema.define(version: 2020_07_22_004641) do
t.integer "page_unlock_promo_code_id"
t.string "approval_url"
t.string "payer_id"
t.datetime "deleted_at"
t.index ["page_unlock_promo_code_id"], name: "index_paypal_invoices_on_page_unlock_promo_code_id"
t.index ["user_id"], name: "index_paypal_invoices_on_user_id"
end
@ -3111,6 +3159,7 @@ ActiveRecord::Schema.define(version: 2020_07_22_004641) do
t.integer "position"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.datetime "deleted_at"
t.index ["timeline_id"], name: "index_timeline_events_on_timeline_id"
end
@ -3402,6 +3451,9 @@ ActiveRecord::Schema.define(version: 2020_07_22_004641) do
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
add_foreign_key "api_keys", "users"
add_foreign_key "api_requests", "application_integrations"
add_foreign_key "api_requests", "integration_authorizations"
add_foreign_key "application_integrations", "users"
add_foreign_key "buildings", "universes"
add_foreign_key "buildings", "users"
add_foreign_key "character_birthtowns", "characters"
@ -3561,6 +3613,8 @@ ActiveRecord::Schema.define(version: 2020_07_22_004641) do
add_foreign_key "group_creatures", "groups"
add_foreign_key "group_creatures", "users"
add_foreign_key "image_uploads", "users"
add_foreign_key "integration_authorizations", "application_integrations"
add_foreign_key "integration_authorizations", "users"
add_foreign_key "item_magics", "items"
add_foreign_key "item_magics", "magics"
add_foreign_key "item_magics", "users"

View File

@ -64,6 +64,11 @@ namespace :data_migrations do
end
end
desc "Add developer billing plans"
task create_developer_billing_plans: :environment do
# TODO
end
desc "Add bandwidth bonuses to billing plans"
task billing_plan_bandwidths: :environment do
puts "Updating bandwidths for all billing plans"

View File

@ -0,0 +1,9 @@
require 'test_helper'
class ApiDocsControllerTest < ActionDispatch::IntegrationTest
test "should get index" do
get api_docs_index_url
assert_response :success
end
end

View File

@ -0,0 +1,48 @@
require 'test_helper'
class ApplicationIntegrationsControllerTest < ActionDispatch::IntegrationTest
setup do
@application_integration = application_integrations(:one)
end
test "should get index" do
get application_integrations_url
assert_response :success
end
test "should get new" do
get new_application_integration_url
assert_response :success
end
test "should create application_integration" do
assert_difference('ApplicationIntegration.count') do
post application_integrations_url, params: { application_integration: { } }
end
assert_redirected_to application_integration_url(ApplicationIntegration.last)
end
test "should show application_integration" do
get application_integration_url(@application_integration)
assert_response :success
end
test "should get edit" do
get edit_application_integration_url(@application_integration)
assert_response :success
end
test "should update application_integration" do
patch application_integration_url(@application_integration), params: { application_integration: { } }
assert_redirected_to application_integration_url(@application_integration)
end
test "should destroy application_integration" do
assert_difference('ApplicationIntegration.count', -1) do
delete application_integration_url(@application_integration)
end
assert_redirected_to application_integrations_url
end
end

View File

@ -0,0 +1,14 @@
require 'test_helper'
class IntegrationAuthorizationsControllerTest < ActionDispatch::IntegrationTest
test "should get create" do
get integration_authorizations_create_url
assert_response :success
end
test "should get show" do
get integration_authorizations_show_url
assert_response :success
end
end

11
test/fixtures/api_requests.yml vendored Normal file
View File

@ -0,0 +1,11 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
application_integration: one
integration_authorization: one
result: MyString
two:
application_integration: two
integration_authorization: two
result: MyString

View File

@ -0,0 +1,25 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
user: one
name: MyString
description: MyString
organization_name: MyString
organization_url: MyString
website_url: MyString
privacy_policy_url: MyString
token: MyString
last_used_at: 2020-02-21 23:15:39
authorization_callback_url: MyString
two:
user: two
name: MyString
description: MyString
organization_name: MyString
organization_url: MyString
website_url: MyString
privacy_policy_url: MyString
token: MyString
last_used_at: 2020-02-21 23:15:39
authorization_callback_url: MyString

View File

@ -0,0 +1,13 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
user: one
application_integration: one
referral_url: MyString
ip_address: MyString
two:
user: two
application_integration: two
referral_url: MyString
ip_address: MyString

View File

@ -0,0 +1,7 @@
require 'test_helper'
class ApiRequestTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end

View File

@ -0,0 +1,7 @@
require 'test_helper'
class ApplicationIntegrationTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end

View File

@ -0,0 +1,7 @@
require 'test_helper'
class IntegrationAuthorizationTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end

View File

@ -0,0 +1,41 @@
require "application_system_test_case"
class ApplicationIntegrationsTest < ApplicationSystemTestCase
setup do
@application_integration = application_integrations(:one)
end
test "visiting the index" do
visit application_integrations_url
assert_selector "h1", text: "Application Integrations"
end
test "creating a Application integration" do
visit application_integrations_url
click_on "New Application Integration"
click_on "Create Application integration"
assert_text "Application integration was successfully created"
click_on "Back"
end
test "updating a Application integration" do
visit application_integrations_url
click_on "Edit", match: :first
click_on "Update Application integration"
assert_text "Application integration was successfully updated"
click_on "Back"
end
test "destroying a Application integration" do
visit application_integrations_url
page.accept_confirm do
click_on "Destroy", match: :first
end
assert_text "Application integration was successfully destroyed"
end
end