permission check for posts page

This commit is contained in:
Dylan Knutson
2023-08-21 10:46:18 -07:00
parent fb78c3a27d
commit 6ec902a859
14 changed files with 1286 additions and 39 deletions

View File

@@ -2,6 +2,16 @@
@tailwind components;
@tailwind utilities;
html {
@apply h-full;
}
body {
@apply h-full;
}
main {
@apply h-full;
}
/*
@layer components {

View File

@@ -29,8 +29,7 @@ class ApplicationController < ActionController::Base
api_token = request.params[:api_token]
user = API_TOKENS[api_token]
if user.nil?
constraint = VpnOnlyRouteConstraint.new
return if constraint.matches?(request)
return if VpnOnlyRouteConstraint.new.matches?(request)
render status: 403, json: { error: "not authenticated" }
end
end

View File

@@ -1,5 +1,6 @@
class Domain::Fa::UsersController < ApplicationController
before_action :set_user, only: %i[ show ]
skip_before_action :validate_api_token, only: %i[ show ]
# GET /domain/fa/users or /domain/fa/users.json
def index

View File

@@ -2,10 +2,6 @@ class PagesController < ApplicationController
skip_before_action :validate_api_token, only: [:root]
def root
if VpnOnlyRouteConstraint.new.matches?(request)
render :root
else
render :under_construction
end
render :root
end
end

View File

@@ -1,4 +1,18 @@
module Domain::Fa::PostsHelper
def hosted_post_link_url_and_options(post)
if VpnOnlyRouteConstraint.new.matches?(request)
[
domain_fa_post_path(post.fa_id),
{},
]
else
[
"https://www.furaffinity.net/view/#{post.fa_id}",
{ target: "_blank", rel: "noreferrer,nofollow" },
]
end
end
def post_state_string(post)
if post.have_file?
"file"

View File

@@ -13,7 +13,9 @@ interface PropTypes {
thumb?: string;
isLast: boolean;
selected: boolean;
href?: string;
style: "item" | "info" | "error";
onClick?: React.MouseEventHandler;
}
export default function ListItem({
@@ -22,6 +24,8 @@ export default function ListItem({
isLast,
selected,
style,
href,
onClick,
}: PropTypes) {
const iconClassName = ["ml-2"];
const textClassName = [
@@ -52,7 +56,12 @@ export default function ListItem({
}
return (
<div className={`relative block`}>
<a
className={`relative block`}
onClick={onClick}
href={href}
target="_blank"
>
{style == "error" ? (
<Icon type="exclamation-circle" className={iconClassName.join(" ")} />
) : null}
@@ -60,6 +69,6 @@ export default function ListItem({
<div className="inline-block w-8">{thumbElem}</div>
<div className="inline-block pl-1">{value}</div>
</div>
</div>
</a>
);
}

View File

@@ -7,6 +7,7 @@ import Trie, { TrieNode } from "../lib/Trie";
// staging
// const HOST = "http://scraper.local:3001";
const LOG = false;
const HOST = "";
const COMMON_LIST_ELEM_CLASSES = `
w-full p-2 pl-8
@@ -27,6 +28,17 @@ const INPUT_ELEM_CLASSES = `
placeholder:font-extralight
`;
function log_info(...args) {
if (LOG) {
console.log(...args);
}
}
function log_error(...args) {
if (LOG) {
console.error(...args);
}
}
interface PropTypes {
isServerRendered?: boolean;
}
@@ -48,6 +60,7 @@ export default function UserSearchBar({ isServerRendered }: PropTypes) {
const [userList, setUserList] = useState<ServerResponse["users"]>([]);
const [selectedIdx, setSelectedIdx] = useState<number | null>(null);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const [listHovered, setListHovered] = useState(false);
const [pendingRequest, setPendingRequest] = useState<AbortController | null>(
null
);
@@ -55,7 +68,9 @@ export default function UserSearchBar({ isServerRendered }: PropTypes) {
const inputRef = useRef(null);
const [isFocused, setIsFocused] = useState(
isServerRendered ? false : document.activeElement == inputRef.current
isServerRendered
? false
: document.activeElement == inputRef.current || true
);
const clearResults = useCallback(() => {
@@ -95,7 +110,7 @@ export default function UserSearchBar({ isServerRendered }: PropTypes) {
}
} catch (err) {
if (!err.message.includes("aborted")) {
console.log("error loading user trie: ", err);
log_error("error loading user trie: ", err);
setPendingRequest(null);
setErrorMessage(`error loading users: ` + err.message);
}
@@ -122,13 +137,29 @@ export default function UserSearchBar({ isServerRendered }: PropTypes) {
const searchForUserDebounced = useCallback(
debounce(async (userName) => {
console.log("sending search for ", userName);
log_info("sending search for ", userName);
setTypingSettled(true);
searchForUser(userName);
}, 250),
[setTypingSettled, searchForUser]
);
function invokeIdx(idx) {
const user = userList[idx];
if (user) {
log_info("selecting user: ", user);
setUserName(user.name);
inputRef.current.value = user.name;
window.location.href = user.show_path;
}
}
function invokeSelected() {
if (selectedIdx != null) {
invokeIdx(selectedIdx);
}
}
function onSearchInputKeyDown(event) {
const { code, shiftKey } = event;
switch (code) {
@@ -146,13 +177,7 @@ export default function UserSearchBar({ isServerRendered }: PropTypes) {
selectPrevListElem();
break;
case "Enter":
if (selectedIdx != null) {
const user = userList[selectedIdx];
console.log("selecting user: ", user);
setUserName(user.name);
inputRef.current.value = user.name;
window.open(user.show_path, "_blank");
}
invokeSelected();
break;
default:
return;
@@ -189,12 +214,22 @@ export default function UserSearchBar({ isServerRendered }: PropTypes) {
pendingRequest == null &&
typingSettled &&
userList.length == 0;
const itemsShown = isFocused && userList.length > 0;
const itemsShown =
!isEmpty(userName) && (isFocused || listHovered) && userList.length > 0;
const anyShown = infoShown || errorShown || itemsShown;
function UserSearchBarItems() {
return (
<div>
<div
onPointerOver={() => {
log_info("hovered list");
setListHovered(true);
}}
onPointerLeave={() => {
log_info("left list");
setListHovered(false);
}}
>
{errorShown ? (
<ListItem
key="error"
@@ -214,7 +249,7 @@ export default function UserSearchBar({ isServerRendered }: PropTypes) {
/>
) : null}
{itemsShown
? userList.map(({ name, thumb }, idx) => (
? userList.map(({ name, thumb, show_path }, idx) => (
<ListItem
key={"name-" + name}
isLast={idx == userList.length - 1}
@@ -222,6 +257,11 @@ export default function UserSearchBar({ isServerRendered }: PropTypes) {
style="item"
value={name}
thumb={thumb}
href={show_path}
onClick={(event) => {
event.preventDefault();
invokeIdx(idx);
}}
/>
))
: null}
@@ -254,7 +294,7 @@ export default function UserSearchBar({ isServerRendered }: PropTypes) {
"placeholder:italic bg-slate-100",
anyShown ? "rounded-b-none" : null,
].join(" ")}
placeholder={"Enter Username"}
placeholder={"Search FurAffinity Users"}
defaultValue={userName}
onChange={(v) => {
setTypingSettled(false);

View File

@@ -47,6 +47,8 @@ class Domain::Fa::Parser::SubmissionParserHelper < Domain::Fa::Parser::Base
@artist_user_page_path ||= case @page_version
when VERSION_2
@elem.css(".submission-id-sub-container a")&.first["href"]
when VERSION_0, VERSION_1
@elem.css("table[align=center] td.cat a")&.first["href"]
else unimplemented_version!
end
end
@@ -59,7 +61,7 @@ class Domain::Fa::Parser::SubmissionParserHelper < Domain::Fa::Parser::Base
@artist_avatar_url ||= case @page_version
when VERSION_2
@elem.css(".submission-user-icon.avatar")&.first&.[]("src")
when VERSION_1
when VERSION_0, VERSION_1
@elem.css("a img.avatar")&.first&.[]("src")
else unimplemented_version!
end

View File

@@ -1,12 +1,16 @@
class VpnOnlyRouteConstraint
def matches?(request)
return false if request.params[:force_vpn_off]
return true if Rails.env.test?
if Rails.env.development? || Rails.env.staging?
if request.params[:force_vpn_off] == "1"
false
elsif Rails.env.test?
true
elsif Rails.env.development? || Rails.env.staging?
request.ip == "127.0.0.1" || request.ip == "::1"
else
elsif Rails.env.production?
# curtus IP on vpn
request.ip == "10.200.0.3"
else
false
end
end
end

View File

@@ -11,10 +11,13 @@
<% posts.each do |post| %>
<li class='p-1 border-b last:border-b-0 border-slate-300 flex'>
<span class='grow'>
<% post_url, opts = hosted_post_link_url_and_options(post) %>
<%= link_to(
post.title,
domain_fa_post_path(post.fa_id),
class: "underline decoration-dashed text-slate-700",
post_url,
opts.merge({
class: "underline decoration-dashed text-slate-700",
})
) %>
</span>
<span class='text-sm'>

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html class=''>
<head>
<title><%= @site_title %></title>
<meta name="viewport" content="width=device-width,initial-scale=1">
@@ -17,7 +17,7 @@
<%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %>
<%= yield :head %>
</head>
<body class="h-full mx-0">
<body class="mx-0 flex flex-col">
<header class="bg-slate-100 border-slate-200 border-b-2">
<div class="mx-auto max-w-5xl py-6 px-6 sm:px-8 flex items-baseline">
<h1 class="text-4xl sm:text-5xl font-bold text-slate-900">
@@ -29,7 +29,7 @@
</h2>
</div>
</header>
<main class="h-full flex flex-col">
<main class="flex flex-col grow">
<%= yield %>
</main>
</body>

View File

@@ -1,11 +1,9 @@
<main class="flex-grow flex w-full">
<div class="
max-w-5xl p-6 sm:p-8 justify-self-center self-center mx-auto
<div class="
max-w-5xl p-6 sm:p-8 justify-self-center self-center m-auto
text-4xl bg-gray-100 drop-shadow-2xl border-gray-400 border-2 text-gray-500
hover:drop-shadow-md hover:bg-gray-50 hover:border-gray-500 hover:text-gray-600
transition-all duration-75
rounded italic font-extralight
">
Under Construction
</div>
</main>
Under Construction
</div>

View File

@@ -302,9 +302,19 @@ describe Domain::Fa::Parser::Page do
assert_page_type parser, :probably_submission?
submission = parser.submission
assert_equal "puppymachine", submission.artist
assert_equal "puppymachine", submission.artist_url_name
assert_equal "//a.facdn.net/1482804755/puppymachine.gif", submission.artist_avatar_url
end
it "is an old-old legacy submission and can be parsed" do
parser = get_parser("submission_28075884_legacy_old_old.html")
assert_page_type parser, :probably_submission?
submission = parser.submission
assert_equal "F-Amiss", submission.artist
assert_equal "f-amiss", submission.artist_url_name
assert_equal "//a.facdn.net/1424255659/f-amiss.gif", submission.artist_avatar_url
end
def get_parser(file, require_logged_in: true)
path = File.join("domain/fa/parser/legacy", file)
contents = SpecUtil.read_fixture_file(path) || raise("Couldn't open #{path}")

File diff suppressed because it is too large Load Diff