mirror of
https://github.com/indentlabs/notebook.git
synced 2025-10-26 11:19:22 +00:00
Merge branch 'dev-api' into document-ui-improvements
This commit is contained in:
commit
5f66dac88a
Binary file not shown.
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 21 KiB |
BIN
app/assets/images/screenshots/integration-references.png
Normal file
BIN
app/assets/images/screenshots/integration-references.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
BIN
app/assets/images/screenshots/integrations.png
Normal file
BIN
app/assets/images/screenshots/integrations.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
BIN
app/assets/images/screenshots/page-types.png
Normal file
BIN
app/assets/images/screenshots/page-types.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
3
app/assets/javascripts/api_docs.coffee
Normal file
3
app/assets/javascripts/api_docs.coffee
Normal 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/
|
||||
3
app/assets/javascripts/integration_authorizations.coffee
Normal file
3
app/assets/javascripts/integration_authorizations.coffee
Normal 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/
|
||||
21
app/assets/stylesheets/api_docs.scss
Normal file
21
app/assets/stylesheets/api_docs.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
3
app/assets/stylesheets/integration_authorizations.scss
Normal file
3
app/assets/stylesheets/integration_authorizations.scss
Normal 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/
|
||||
@ -41,3 +41,7 @@
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
}
|
||||
|
||||
#page-lookup-list {
|
||||
min-width: 33em;
|
||||
}
|
||||
@ -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
|
||||
|
||||
|
||||
29
app/controllers/api/api_docs_controller.rb
Normal file
29
app/controllers/api/api_docs_controller.rb
Normal 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
|
||||
69
app/controllers/api/application_integrations_controller.rb
Normal file
69
app/controllers/api/application_integrations_controller.rb
Normal 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
|
||||
24
app/controllers/api/integration_authorizations_controller.rb
Normal file
24
app/controllers/api/integration_authorizations_controller.rb
Normal 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
|
||||
@ -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
|
||||
2
app/helpers/api_docs_helper.rb
Normal file
2
app/helpers/api_docs_helper.rb
Normal file
@ -0,0 +1,2 @@
|
||||
module ApiDocsHelper
|
||||
end
|
||||
2
app/helpers/application_integrations_helper.rb
Normal file
2
app/helpers/application_integrations_helper.rb
Normal file
@ -0,0 +1,2 @@
|
||||
module ApplicationIntegrationsHelper
|
||||
end
|
||||
2
app/helpers/integration_authorizations_helper.rb
Normal file
2
app/helpers/integration_authorizations_helper.rb
Normal file
@ -0,0 +1,2 @@
|
||||
module IntegrationAuthorizationsHelper
|
||||
end
|
||||
@ -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> {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,
|
||||
|
||||
7
app/models/api_request.rb
Normal file
7
app/models/api_request.rb
Normal 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
|
||||
47
app/models/application_integration.rb
Normal file
47
app/models/application_integration.rb
Normal 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
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
6
app/models/integration_authorization.rb
Normal file
6
app/models/integration_authorization.rb
Normal file
@ -0,0 +1,6 @@
|
||||
class IntegrationAuthorization < ApplicationRecord
|
||||
belongs_to :user
|
||||
belongs_to :application_integration
|
||||
|
||||
has_many :api_requests
|
||||
end
|
||||
@ -8,7 +8,6 @@ class Country < ApplicationRecord
|
||||
|
||||
include BelongsToUniverse
|
||||
|
||||
include HasAttributes
|
||||
include IsContentPage
|
||||
|
||||
include Serendipitous::Concern
|
||||
|
||||
@ -8,7 +8,6 @@ class Language < ApplicationRecord
|
||||
|
||||
include BelongsToUniverse
|
||||
|
||||
include HasAttributes
|
||||
include IsContentPage
|
||||
|
||||
include Serendipitous::Concern
|
||||
|
||||
@ -8,7 +8,6 @@ class Town < ApplicationRecord
|
||||
|
||||
include BelongsToUniverse
|
||||
|
||||
include HasAttributes
|
||||
include IsContentPage
|
||||
include Serendipitous::Concern
|
||||
|
||||
|
||||
74
app/models/serializers/api_content_serializer.rb
Normal file
74
app/models/serializers/api_content_serializer.rb
Normal 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
|
||||
@ -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
|
||||
|
||||
1
app/views/api/api_docs/approvals.html.erb
Normal file
1
app/views/api/api_docs/approvals.html.erb
Normal file
@ -0,0 +1 @@
|
||||
approvals
|
||||
41
app/views/api/api_docs/docs.html.erb
Normal file
41
app/views/api/api_docs/docs.html.erb
Normal 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>
|
||||
@ -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>
|
||||
@ -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>
|
||||
@ -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"><id></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>
|
||||
@ -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>
|
||||
@ -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"><id></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>
|
||||
@ -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"><id></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>
|
||||
@ -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"><id></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>
|
||||
|
||||
|
||||
@ -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"><id></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>
|
||||
|
||||
|
||||
76
app/views/api/api_docs/endpoints/users/_profile.html.erb
Normal file
76
app/views/api/api_docs/endpoints/users/_profile.html.erb
Normal 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"><id></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>
|
||||
|
||||
|
||||
161
app/views/api/api_docs/index.html.erb
Normal file
161
app/views/api/api_docs/index.html.erb
Normal 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 — 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>
|
||||
1
app/views/api/api_docs/integrations.html.erb
Normal file
1
app/views/api/api_docs/integrations.html.erb
Normal file
@ -0,0 +1 @@
|
||||
integrations
|
||||
0
app/views/api/api_docs/pricing.html.erb
Normal file
0
app/views/api/api_docs/pricing.html.erb
Normal file
57
app/views/api/api_docs/references.html.erb
Normal file
57
app/views/api/api_docs/references.html.erb
Normal 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>
|
||||
16
app/views/api/application_integrations/_form.html.erb
Normal file
16
app/views/api/application_integrations/_form.html.erb
Normal 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 %>
|
||||
37
app/views/api/application_integrations/authorize.html.erb
Normal file
37
app/views/api/application_integrations/authorize.html.erb
Normal 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>
|
||||
6
app/views/api/application_integrations/edit.html.erb
Normal file
6
app/views/api/application_integrations/edit.html.erb
Normal 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 %>
|
||||
87
app/views/api/application_integrations/index.html.erb
Normal file
87
app/views/api/application_integrations/index.html.erb
Normal 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"> </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 %>
|
||||
5
app/views/api/application_integrations/new.html.erb
Normal file
5
app/views/api/application_integrations/new.html.erb
Normal file
@ -0,0 +1,5 @@
|
||||
<h1>New Application Integration</h1>
|
||||
|
||||
<%= render 'form', application_integration: @application_integration %>
|
||||
|
||||
<%= link_to 'Back', application_integrations_path %>
|
||||
106
app/views/api/application_integrations/show.html.erb
Normal file
106
app/views/api/application_integrations/show.html.erb
Normal 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>
|
||||
2
app/views/integration_authorizations/create.html.erb
Normal file
2
app/views/integration_authorizations/create.html.erb
Normal file
@ -0,0 +1,2 @@
|
||||
<h1>IntegrationAuthorizations#create</h1>
|
||||
<p>Find me in app/views/integration_authorizations/create.html.erb</p>
|
||||
2
app/views/integration_authorizations/show.html.erb
Normal file
2
app/views/integration_authorizations/show.html.erb
Normal file
@ -0,0 +1,2 @@
|
||||
<h1>IntegrationAuthorizations#show</h1>
|
||||
<p>Find me in app/views/integration_authorizations/show.html.erb</p>
|
||||
@ -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 %>
|
||||
|
||||
102
app/views/layouts/_developer_navbar.html.erb
Normal file
102
app/views/layouts/_developer_navbar.html.erb
Normal 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>
|
||||
@ -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.
|
||||
|
||||
@ -20,7 +20,6 @@
|
||||
</ul>
|
||||
|
||||
<%= yield %>
|
||||
<a href="https://plus.google.com/118076966717703203223" rel="publisher"></a>
|
||||
</main>
|
||||
|
||||
</body>
|
||||
|
||||
@ -38,6 +38,5 @@
|
||||
|
||||
<%= render partial: 'content/keyboard_controls_help_modal' %>
|
||||
<%= render 'layouts/ganalytics' %>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
34
app/views/layouts/developer.html.erb
Normal file
34
app/views/layouts/developer.html.erb
Normal 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>
|
||||
@ -11,7 +11,7 @@
|
||||
|
||||
<main>
|
||||
<div class="mobile-navbar-spacer hide-on-med-and-up"> </div>
|
||||
<%#
|
||||
<%=
|
||||
react_component("PageLookupSidebar", {
|
||||
})
|
||||
%>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
18
db/migrate/20200222051539_create_application_integrations.rb
Normal file
18
db/migrate/20200222051539_create_application_integrations.rb
Normal 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
|
||||
@ -0,0 +1,5 @@
|
||||
class AddEventPingUrlToApplicationIntegrations < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
add_column :application_integrations, :event_ping_url, :string
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,5 @@
|
||||
class AddApplicationTokenToApplicationIntegrations < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
add_column :application_integrations, :application_token, :string
|
||||
end
|
||||
end
|
||||
@ -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
|
||||
@ -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
|
||||
@ -0,0 +1,5 @@
|
||||
class AddUserTokenToIntegrationAuthorizations < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
add_column :integration_authorizations, :user_token, :string
|
||||
end
|
||||
end
|
||||
13
db/migrate/20200912000306_create_api_requests.rb
Normal file
13
db/migrate/20200912000306_create_api_requests.rb
Normal 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
|
||||
56
db/schema.rb
56
db/schema.rb
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
9
test/controllers/api_docs_controller_test.rb
Normal file
9
test/controllers/api_docs_controller_test.rb
Normal 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
|
||||
48
test/controllers/application_integrations_controller_test.rb
Normal file
48
test/controllers/application_integrations_controller_test.rb
Normal 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
|
||||
@ -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
11
test/fixtures/api_requests.yml
vendored
Normal 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
|
||||
25
test/fixtures/application_integrations.yml
vendored
Normal file
25
test/fixtures/application_integrations.yml
vendored
Normal 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
|
||||
13
test/fixtures/integration_authorizations.yml
vendored
Normal file
13
test/fixtures/integration_authorizations.yml
vendored
Normal 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
|
||||
7
test/models/api_request_test.rb
Normal file
7
test/models/api_request_test.rb
Normal file
@ -0,0 +1,7 @@
|
||||
require 'test_helper'
|
||||
|
||||
class ApiRequestTest < ActiveSupport::TestCase
|
||||
# test "the truth" do
|
||||
# assert true
|
||||
# end
|
||||
end
|
||||
7
test/models/application_integration_test.rb
Normal file
7
test/models/application_integration_test.rb
Normal file
@ -0,0 +1,7 @@
|
||||
require 'test_helper'
|
||||
|
||||
class ApplicationIntegrationTest < ActiveSupport::TestCase
|
||||
# test "the truth" do
|
||||
# assert true
|
||||
# end
|
||||
end
|
||||
7
test/models/integration_authorization_test.rb
Normal file
7
test/models/integration_authorization_test.rb
Normal file
@ -0,0 +1,7 @@
|
||||
require 'test_helper'
|
||||
|
||||
class IntegrationAuthorizationTest < ActiveSupport::TestCase
|
||||
# test "the truth" do
|
||||
# assert true
|
||||
# end
|
||||
end
|
||||
41
test/system/application_integrations_test.rb
Normal file
41
test/system/application_integrations_test.rb
Normal 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
|
||||
Loading…
Reference in New Issue
Block a user