@bigbinary/neeto-unlisted-links-frontend
v0.1.5
Published
Frontend modal and hooks for managing unlisted links across neeto products.
Readme
neeto-unlisted-links-engine
The neeto-unlisted-links-engine manages unlisted public links across neeto
products. An unlisted link is a secret, non-indexed URL that grants read-only
access to a specific resource (e.g. an article, project, board) without making
the resource globally public. Links can optionally auto-expire.
Contents
Development with Host Application
Engine
Installation
- Add the gem to the host application's
Gemfile:gem "neeto-unlisted-links-engine" - Install it:
bundle install - Copy the migration files into your host application:
bin/rails generate neeto_unlisted_links_engine:install - Run the migration:
bin/rails db:migrate
Usage
Include the Unlistable concern on any model whose records can have an unlisted
link. The model should belong_to an organization (or any object that responds
to root_url):
class Article < ApplicationRecord
include NeetoUnlistedLinksEngine::Unlistable
belongs_to :organization
endThe concern wires a has_one :unlisted_link association. You can read/generate
links through the NeetoUnlistedLinksEngine::LinksService:
service = NeetoUnlistedLinksEngine::LinksService.new(article)
service.generate_link
service.regenerate_link(expiration_type: "thirty_days",
expiration_date: 30.days.from_now)
service.update(status: "inactive")Management routes
The host application is responsible for exposing an authenticated,
nested show/regenerate route for the unlistable resource — this is
what the frontend package's resourceUrl points at,
and what LinksService sits behind. For example, for Documents nested
under a project:
resources :projects, param: :sid do
# ...
resources :documents, module: :projects, param: :sid do
resource :unlisted_link, only: %i[show], module: :documents, param: :sid do
member do
post :regenerate
end
end
# ...
end
endThis is distinct from the public-facing route covered below: the routes
above are the authenticated, in-app API used to view/regenerate a link
(and drive the UnlistedLinks modal), while the public route is the
unauthenticated route someone with the secret URL actually visits.
Public access
The engine ships an abstract NeetoUnlistedLinksEngine::UnlistedLinksController
that host applications subclass rather than writing the public-facing
controller from scratch. It handles rendering the unlisted resource (and its
attachments) using a bundled view and layout, and only requires the host
controller to implement a single method: #load_record.
# frozen_string_literal: true
class UnlistedLinksController < NeetoUnlistedLinksEngine::UnlistedLinksController
private
def load_record
return nil if @organization.nil?
unlisted_link = NeetoUnlistedLinksEngine::Link.resolve(params[:unlisted_link_identifier])
document = unlisted_link&.unlistable
return nil unless document.is_a?(Document)
return nil unless document.organization_id == @organization.id
@record = document
@content = NeetoEditorEngine.sanitize(@record.body).html_safe
@attachments = @record.attachments
end
end# config/routes.rb
get "/public/:unlisted_link_identifier", to: "unlisted_links#show"A few things worth calling out in this example:
#load_recordscopes the lookup to the current@organization(set by whateverbefore_actionthe host app already uses to resolve the tenant) and returns early with@recordleft unset if there's no organization, if the link doesn't resolve, if the resolved resource isn't the expected model (Documenthere), or if it doesn't belong to that organization. Any of these cases fall through to#render_not_found.@contentis passed throughNeetoEditorEngine.sanitizeand markedhtml_safebefore being handed to the view, since@record.bodyholds rich, user-authored HTML that shouldn't be rendered unsanitized.- The route param is named to match whatever the host app calls it
(
:unlisted_link_identifierabove); just make sure#load_recordreads the same param name.
How it works
#showcalls#load_record, then rendersneeto_unlisted_links_engine/unlisted_project/showif@recordis present, or calls#render_not_foundotherwise.#load_recordmust be defined by the host controller and is expected to set@record,@content, and@attachments. The base implementation raisesNotImplementedErrorif it isn't overridden.#render_not_foundredirects toRails.application.vault.not_found_redirect_linkby default. Override it in the host controller if you need different not-found behavior.- The view renders
@record.title, the raw@content, and, if@attachmentsis present, a list of attachment links built fromcdn_attachment_url(attachment.blob). - The view is rendered inside the engine's
neeto_unlisted_links_engine/unlistablelayout, a minimal standalone HTML shell (its own<head>, styling, and the neeto editor content stylesheet) so the public page doesn't inherit the host application's normal layout.
# app/controllers/neeto_unlisted_links_engine/unlisted_links_controller.rb (engine)
module NeetoUnlistedLinksEngine
class UnlistedLinksController < ApplicationController
before_action :load_record
layout "neeto_unlisted_links_engine/unlistable"
def show
if @record.nil?
render_not_found
else
render "neeto_unlisted_links_engine/unlisted_project/show"
end
end
private
def load_record
raise NotImplementedError,
"Host controller must define #load_record (the one which will " \
"include the @record and the @content and @attachments )"
end
def render_not_found
redirect_to Rails.application.vault.not_found_redirect_link
end
end
endFrontend package
The matching npm package is
@bigbinary/neeto-unlisted-links-frontend. It exports a modal that, given a
resource URL prefix, displays the unlisted link, its expiry state, and a
regenerate flow.
import { UnlistedLinks } from "@bigbinary/neeto-unlisted-links-frontend";
<UnlistedLinks
isOpen={isOpen}
onClose={onClose}
resourceUrl="/v1/projects/:project_sid/documents/:document_sid/unlisted_link"
/>;resourceUrl should point at the management route
(GET for show, POST .../regenerate for regenerate) for the specific
record the modal is open for.
Instructions for Publishing
- Bump
lib/neeto_unlisted_links_engine/version.rb. - Update
CHANGELOG.md. gem build neeto-unlisted-links-engine.gemspec.gem push neeto-unlisted-links-engine-<version>.gem --host https://private-gems.neeto.com.- For the frontend package, bump
package.json, runyarn build, andnpm publish.
