permission check for posts page
This commit is contained in:
@@ -2,6 +2,16 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
html {
|
||||
@apply h-full;
|
||||
}
|
||||
body {
|
||||
@apply h-full;
|
||||
}
|
||||
main {
|
||||
@apply h-full;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@layer components {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}")
|
||||
|
||||
1161
test/fixtures/files/domain/fa/parser/legacy/submission_28075884_legacy_old_old.html
vendored
Normal file
1161
test/fixtures/files/domain/fa/parser/legacy/submission_28075884_legacy_old_old.html
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user