Skip to content

Commit 5eb27c3

Browse files
committed
Refactor feed creation UX flow
1 parent fa04c2f commit 5eb27c3

19 files changed

Lines changed: 857 additions & 1085 deletions

File tree

app/api/v1/root_metadata.rb

Lines changed: 8 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
# frozen_string_literal: true
22

3-
require 'uri'
4-
5-
require_relative '../../account_manager'
3+
require_relative '../../auto_source'
64

75
module Html2rss
86
module Web
@@ -21,57 +19,22 @@ def build(router)
2119
description: 'RESTful API for converting websites to RSS feeds',
2220
openapi_url: "#{router.base_url}/api/v1/openapi.yaml"
2321
},
24-
demo: demo_payload
22+
instance: instance_payload(router)
2523
}
2624
end
2725

2826
private
2927

28+
# @param _router [Roda::RodaRequest]
3029
# @return [Hash{Symbol=>Object}]
31-
def demo_payload
32-
account = AccountManager.get_account_by_username('demo')
33-
return { enabled: false, sources: [] } unless account
34-
30+
def instance_payload(_router)
3531
{
36-
enabled: true,
37-
token: account[:token],
38-
strategy: 'ssrf_filter',
39-
sources: Array(account[:allowed_urls]).map.with_index do |url, index|
40-
{ id: demo_source_id(url, index), url: url }
41-
end
32+
feed_creation: {
33+
enabled: AutoSource.enabled?,
34+
access_token_required: AutoSource.enabled?
35+
}
4236
}
4337
end
44-
45-
# @param url [String]
46-
# @param index [Integer]
47-
# @return [String]
48-
def demo_source_id(url, index)
49-
parts = demo_source_parts(url)
50-
return parts.join('-').gsub(/[^a-zA-Z0-9]+/, '-').downcase if parts.any?
51-
52-
fallback_demo_source_id(index)
53-
rescue URI::InvalidURIError
54-
fallback_demo_source_id(index)
55-
end
56-
57-
# @param url [String]
58-
# @return [Array<String>]
59-
def demo_source_parts(url)
60-
uri = URI.parse(url)
61-
[uri.host.to_s.gsub(/^www\./, ''), first_path_segment(uri)].reject(&:empty?)
62-
end
63-
64-
# @param uri [URI::Generic]
65-
# @return [String]
66-
def first_path_segment(uri)
67-
uri.path.to_s.split('/').find { |segment| !segment.empty? }.to_s
68-
end
69-
70-
# @param index [Integer]
71-
# @return [String]
72-
def fallback_demo_source_id(index)
73-
"demo-#{index + 1}"
74-
end
7538
end
7639
end
7740
end

docs/api/v1/openapi.yaml

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -41,41 +41,32 @@ paths:
4141
- description
4242
- openapi_url
4343
type: object
44-
demo:
44+
instance:
4545
properties:
46-
enabled:
47-
type: boolean
48-
sources:
49-
items:
50-
properties:
51-
id:
52-
type: string
53-
url:
54-
type: string
55-
required:
56-
- id
57-
- url
58-
type: object
59-
type: array
60-
strategy:
61-
type: string
62-
token:
63-
type: string
46+
feed_creation:
47+
properties:
48+
access_token_required:
49+
type: boolean
50+
enabled:
51+
type: boolean
52+
required:
53+
- enabled
54+
- access_token_required
55+
type: object
6456
required:
65-
- enabled
66-
- sources
57+
- feed_creation
6758
type: object
6859
required:
6960
- api
70-
- demo
61+
- instance
7162
type: object
7263
success:
7364
type: boolean
7465
required:
7566
- success
7667
- data
7768
type: object
78-
description: returns OpenAPI document URL in metadata
69+
description: returns instance feed-creation capability
7970
security: []
8071
summary: API metadata
8172
tags:

frontend/package-lock.json

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/src/__tests__/App.contract.test.tsx

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
import { describe, it, expect } from 'vitest';
22
import { render, screen, fireEvent, waitFor } from '@testing-library/preact';
3+
import { within } from '@testing-library/preact';
34
import { http, HttpResponse } from 'msw';
45
import { server, buildFeedResponse } from './mocks/server';
56
import { App } from '../components/App';
67

78
describe('App contract', () => {
8-
const username = 'contract-user';
99
const token = 'contract-token';
1010

1111
const authenticate = () => {
12-
window.sessionStorage.setItem('html2rss_username', username);
13-
window.sessionStorage.setItem('html2rss_token', token);
12+
window.sessionStorage.setItem('html2rss_access_token', token);
1413
};
1514

1615
it('shows feed result when API responds with success', async () => {
@@ -40,19 +39,22 @@ describe('App contract', () => {
4039

4140
render(<App />);
4241

43-
await screen.findByText(username);
42+
await screen.findByText('Generate a feed from a web page');
4443

45-
const urlInput = screen.getByLabelText('URL') as HTMLInputElement;
44+
const urlInput = screen.getByLabelText('Source URL') as HTMLInputElement;
4645
fireEvent.input(urlInput, { target: { value: 'https://example.com/articles' } });
4746

48-
fireEvent.click(screen.getByRole('button', { name: 'Convert' }));
47+
fireEvent.click(screen.getByRole('button', { name: 'Generate feed URL' }));
4948

5049
await waitFor(() => {
51-
expect(screen.getByText('Feed created')).toBeInTheDocument();
50+
const resultRegion = document.getElementById('feed-result');
51+
expect(resultRegion).not.toBeNull();
52+
const resultQueries = within(resultRegion!);
53+
54+
expect(screen.getByText('Feed ready')).toBeInTheDocument();
5255
expect(screen.getByText('Example Feed')).toBeInTheDocument();
53-
expect(screen.getByRole('button', { name: 'Copy URL' })).toBeInTheDocument();
54-
expect(screen.getByRole('link', { name: 'Subscribe in reader' })).toBeInTheDocument();
55-
expect(screen.getByText('Opens your default RSS reader if configured.')).toBeInTheDocument();
56+
expect(resultQueries.getByRole('button', { name: 'Copy feed URL' })).toBeInTheDocument();
57+
expect(resultQueries.getByRole('link', { name: 'Open feed' })).toBeInTheDocument();
5658
});
5759
});
5860
});

0 commit comments

Comments
 (0)