mirror of
https://github.com/Athou/commafeed.git
synced 2026-03-21 21:37:29 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06319c1eb0 | ||
|
|
b7ede8eba2 | ||
|
|
1a4517d6a3 | ||
|
|
a402c5d7d8 | ||
|
|
408809787e | ||
|
|
d7b0d572c1 | ||
|
|
b356be3e6f | ||
|
|
998385334b | ||
|
|
c6d613d81a | ||
|
|
9981d8763d | ||
|
|
b37680333c | ||
|
|
66d1eb3f1f |
@@ -1,5 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## [3.10.0]
|
||||
|
||||
- added a Fever-compatible API that is usable with mobile clients that support the Fever API (see instructions in Settings -> Profile)
|
||||
- long entry titles are no longer shortened in the detailed view
|
||||
- added the "s" keyboard shortcut to star/unstar entries
|
||||
- http sessions are now stored in the database (they were stored on disk before)
|
||||
- fixed an issue that made it impossible to override the database url in a config.yml mounted in the Docker image
|
||||
|
||||
## [3.9.0]
|
||||
|
||||
- improve performance by disabling the loader when nothing is loading (most noticeable on mobile)
|
||||
|
||||
@@ -4,8 +4,6 @@ EXPOSE 8082
|
||||
|
||||
RUN mkdir -p /commafeed/data
|
||||
VOLUME /commafeed/data
|
||||
ENV CF_SESSION_PATH=/commafeed/data/sessions
|
||||
ENV CF_DATABASE_URL=jdbc:h2:/commafeed/data/db
|
||||
|
||||
COPY commafeed-server/config.yml.example config.yml
|
||||
COPY commafeed-server/target/commafeed.jar .
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.commafeed</groupId>
|
||||
<artifactId>commafeed</artifactId>
|
||||
<version>3.9.0</version>
|
||||
<version>3.10.0</version>
|
||||
</parent>
|
||||
<artifactId>commafeed-client</artifactId>
|
||||
<name>CommaFeed Client</name>
|
||||
|
||||
@@ -113,6 +113,14 @@ export function KeyboardShortcutsHelp() {
|
||||
<Trans>Swipe header to the right</Trans>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Toggle starred status of current entry</Trans>
|
||||
</td>
|
||||
<td>
|
||||
<Kbd>S</Kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Mark all entries as read</Trans>
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
selectEntry,
|
||||
selectNextEntry,
|
||||
selectPreviousEntry,
|
||||
starEntry,
|
||||
} from "app/slices/entries"
|
||||
import { redirectToRootCategory } from "app/slices/redirect"
|
||||
import { toggleSidebar } from "app/slices/tree"
|
||||
@@ -257,6 +258,11 @@ export function FeedEntries() {
|
||||
if (!selectedEntry) return
|
||||
dispatch(markEntry({ entry: selectedEntry, read: !selectedEntry.read }))
|
||||
})
|
||||
useMousetrap("s", () => {
|
||||
// toggle starred status
|
||||
if (!selectedEntry) return
|
||||
dispatch(starEntry({ entry: selectedEntry, starred: !selectedEntry.starred }))
|
||||
})
|
||||
useMousetrap("shift+a", () => {
|
||||
// mark all entries as read
|
||||
dispatch(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Box, createStyles, Text } from "@mantine/core"
|
||||
import { Box, createStyles, Space, Text } from "@mantine/core"
|
||||
import { Entry } from "app/types"
|
||||
import { RelativeDate } from "components/RelativeDate"
|
||||
import { FeedEntryTitle } from "./FeedEntryTitle"
|
||||
@@ -12,17 +12,11 @@ export interface FeedEntryHeaderProps {
|
||||
const useStyles = createStyles((theme, props: FeedEntryHeaderProps) => ({
|
||||
headerText: {
|
||||
fontWeight: theme.colorScheme === "light" && !props.entry.read ? "bold" : "inherit",
|
||||
whiteSpace: props.expanded ? "inherit" : "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
},
|
||||
headerSubtext: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
fontSize: "90%",
|
||||
whiteSpace: props.expanded ? "inherit" : "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
},
|
||||
}))
|
||||
export function FeedEntryHeader(props: FeedEntryHeaderProps) {
|
||||
@@ -33,18 +27,13 @@ export function FeedEntryHeader(props: FeedEntryHeaderProps) {
|
||||
<FeedEntryTitle entry={props.entry} />
|
||||
</Box>
|
||||
<Box className={classes.headerSubtext}>
|
||||
<Box mr={6}>
|
||||
<FeedFavicon url={props.entry.iconUrl} />
|
||||
</Box>
|
||||
<Box>
|
||||
<Text color="dimmed">{props.entry.feedName}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text color="dimmed">
|
||||
<span> · </span>
|
||||
<RelativeDate date={props.entry.date} />
|
||||
</Text>
|
||||
</Box>
|
||||
<FeedFavicon url={props.entry.iconUrl} />
|
||||
<Space w={6} />
|
||||
<Text color="dimmed">
|
||||
{props.entry.feedName}
|
||||
<span> · </span>
|
||||
<RelativeDate date={props.entry.date} />
|
||||
</Text>
|
||||
</Box>
|
||||
{props.expanded && (
|
||||
<Box className={classes.headerSubtext}>
|
||||
|
||||
@@ -77,10 +77,7 @@ export function ProfileSettings() {
|
||||
|
||||
<form onSubmit={form.onSubmit(saveProfile.execute)}>
|
||||
<Stack>
|
||||
<Input.Wrapper label={<Trans>User name</Trans>}>
|
||||
<Box>{profile?.name}</Box>
|
||||
</Input.Wrapper>
|
||||
|
||||
<TextInput label={<Trans>User name</Trans>} readOnly value={profile?.name} />
|
||||
<TextInput label={<Trans>API key</Trans>} readOnly value={profile?.apiKey} />
|
||||
|
||||
<Input.Wrapper
|
||||
@@ -98,6 +95,22 @@ export function ProfileSettings() {
|
||||
</Box>
|
||||
</Input.Wrapper>
|
||||
|
||||
<Input.Wrapper
|
||||
label={<Trans>Fever API</Trans>}
|
||||
description={
|
||||
<Trans>
|
||||
CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client.
|
||||
The username is your user name and the password is your API key.
|
||||
</Trans>
|
||||
}
|
||||
>
|
||||
<Box>
|
||||
<Anchor href={`rest/fever/user/${profile?.id}`} target="_blank">
|
||||
<Trans>Fever API URL</Trans>
|
||||
</Anchor>
|
||||
</Box>
|
||||
</Input.Wrapper>
|
||||
|
||||
<Divider />
|
||||
|
||||
<PasswordInput
|
||||
|
||||
@@ -179,6 +179,10 @@ msgstr "تأكد من عمل الخلاصة"
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed التالي العنصر غير المقروء"
|
||||
@@ -343,6 +347,14 @@ msgstr "موجز URL"
|
||||
msgid "Fetch all my feeds now"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "file is required"
|
||||
msgstr "الملف مطلوب"
|
||||
@@ -832,6 +844,10 @@ msgstr "تبديل قراءة حالة الإدخال الحالي"
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle starred status of current entry"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "جرب CommaFeed باستخدام الحساب التجريبي: تجريبي / تجريبي"
|
||||
|
||||
@@ -179,6 +179,10 @@ msgstr "Comproveu que el canal funciona"
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed següent element no llegit"
|
||||
@@ -343,6 +347,14 @@ msgstr "URL del canal"
|
||||
msgid "Fetch all my feeds now"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "file is required"
|
||||
msgstr "el fitxer és necessari"
|
||||
@@ -832,6 +844,10 @@ msgstr "Canvia l'estat de lectura de l'entrada actual"
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle starred status of current entry"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Proveu CommaFeed amb el compte de demostració: demo/demo"
|
||||
|
||||
@@ -179,6 +179,10 @@ msgstr "Zkontrolujte, zda zdroj funguje"
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed další nepřečtená položka"
|
||||
@@ -343,6 +347,14 @@ msgstr "URL zdroje"
|
||||
msgid "Fetch all my feeds now"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "file is required"
|
||||
msgstr ""
|
||||
@@ -832,6 +844,10 @@ msgstr "Přepne stav čtení aktuálního záznamu"
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle starred status of current entry"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Vyzkoušejte CommaFeed s demo účtem: demo/demo"
|
||||
|
||||
@@ -179,6 +179,10 @@ msgstr "Gwiriwch fod y porthiant yn gweithio"
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed eitem nesaf heb ei darllen"
|
||||
@@ -343,6 +347,14 @@ msgstr "URL porthiant"
|
||||
msgid "Fetch all my feeds now"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "file is required"
|
||||
msgstr "mae angen y ffeil"
|
||||
@@ -832,6 +844,10 @@ msgstr "Toglo statws darllen y cofnod cyfredol"
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle starred status of current entry"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Rhowch gynnig ar CommaFeed gyda'r cyfrif demo: demo / demo"
|
||||
|
||||
@@ -179,6 +179,10 @@ msgstr "Tjek, at foderet virker"
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed næste ulæste element"
|
||||
@@ -343,6 +347,14 @@ msgstr ""
|
||||
msgid "Fetch all my feeds now"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "file is required"
|
||||
msgstr "fil er påkrævet"
|
||||
@@ -832,6 +844,10 @@ msgstr "Skift læsestatus for den aktuelle post"
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle starred status of current entry"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Prøv CommaFeed med demokontoen: demo/demo"
|
||||
|
||||
@@ -179,6 +179,10 @@ msgstr "Überprüfen Sie, ob der Feed funktioniert"
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed nächstes ungelesenes Element"
|
||||
@@ -343,6 +347,14 @@ msgstr "Feed-URL"
|
||||
msgid "Fetch all my feeds now"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "file is required"
|
||||
msgstr "Datei ist erforderlich"
|
||||
@@ -832,6 +844,10 @@ msgstr "Lesestatus des aktuellen Eintrags umschalten"
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle starred status of current entry"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Testen Sie CommaFeed mit dem Demokonto: demo/demo"
|
||||
|
||||
@@ -179,6 +179,10 @@ msgstr "Check that the feed is working"
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||
msgstr "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed next unread item"
|
||||
@@ -343,6 +347,14 @@ msgstr "Feed URL"
|
||||
msgid "Fetch all my feeds now"
|
||||
msgstr "Fetch all my feeds now"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API"
|
||||
msgstr "Fever API"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API URL"
|
||||
msgstr "Fever API URL"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "file is required"
|
||||
msgstr "file is required"
|
||||
@@ -832,6 +844,10 @@ msgstr "Toggle read status of current entry"
|
||||
msgid "Toggle sidebar"
|
||||
msgstr "Toggle sidebar"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle starred status of current entry"
|
||||
msgstr "Toggle starred status of current entry"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Try out CommaFeed with the demo account: demo/demo"
|
||||
|
||||
@@ -179,6 +179,10 @@ msgstr "Compruebe que el feed funciona"
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed siguiente elemento no leído"
|
||||
@@ -343,6 +347,14 @@ msgstr "URL de fuente"
|
||||
msgid "Fetch all my feeds now"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "file is required"
|
||||
msgstr "archivo requerido"
|
||||
@@ -832,6 +844,10 @@ msgstr "Alternar estado de lectura de la entrada actual"
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle starred status of current entry"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Pruebe CommaFeed con la cuenta demo: demo/demo"
|
||||
|
||||
@@ -179,6 +179,10 @@ msgstr "بررسی کنید که خوراک کار می کند"
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "مورد خوانده نشده بعدی CommaFeed"
|
||||
@@ -343,6 +347,14 @@ msgstr "URL فید"
|
||||
msgid "Fetch all my feeds now"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "file is required"
|
||||
msgstr "فایل مورد نیاز است"
|
||||
@@ -832,6 +844,10 @@ msgstr "وضعیت خواندن ورودی فعلی را تغییر دهید"
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle starred status of current entry"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "CommaFeed را با حساب آزمایشی امتحان کنید: دمو/دمو"
|
||||
|
||||
@@ -179,6 +179,10 @@ msgstr "Tarkista, että syöttö toimii"
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed seuraava lukematon kohde"
|
||||
@@ -343,6 +347,14 @@ msgstr "Syötteen URL-osoite"
|
||||
msgid "Fetch all my feeds now"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "file is required"
|
||||
msgstr "tiedosto vaaditaan"
|
||||
@@ -832,6 +844,10 @@ msgstr "Vaihda nykyisen merkinnän lukutila"
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle starred status of current entry"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Kokeile CommaFeediä demotilillä: demo/demo"
|
||||
|
||||
@@ -179,6 +179,10 @@ msgstr "Vérifie que le flux fonctionne"
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr "Extension CommaFeed pour navigateur version {browserExtensionVersion}."
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed prochain article non lu"
|
||||
@@ -343,6 +347,14 @@ msgstr "URL du flux"
|
||||
msgid "Fetch all my feeds now"
|
||||
msgstr "Rafraîchir tous mes flux"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "file is required"
|
||||
msgstr "fichier requis"
|
||||
@@ -832,6 +844,10 @@ msgstr "Marquer l'entrée actuelle comme lue/non lue"
|
||||
msgid "Toggle sidebar"
|
||||
msgstr "Montrer/cacher la barre latérale"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle starred status of current entry"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Essayez CommaFeed avec le compte de démonstration : demo/demo"
|
||||
|
||||
@@ -179,6 +179,10 @@ msgstr "Comproba que a fonte funciona"
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed seguinte elemento non lido"
|
||||
@@ -343,6 +347,14 @@ msgstr "URL da fonte"
|
||||
msgid "Fetch all my feeds now"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "file is required"
|
||||
msgstr "é necesario o ficheiro"
|
||||
@@ -832,6 +844,10 @@ msgstr "alternar o estado de lectura da entrada actual"
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle starred status of current entry"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Proba CommaFeed coa conta de demostración: demo/demo"
|
||||
|
||||
@@ -179,6 +179,10 @@ msgstr "Ellenőrizze, hogy a feed működik-e"
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed következő olvasatlan elem"
|
||||
@@ -343,6 +347,14 @@ msgstr ""
|
||||
msgid "Fetch all my feeds now"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "file is required"
|
||||
msgstr "fájl szükséges"
|
||||
@@ -832,6 +844,10 @@ msgstr "Az aktuális bejegyzés olvasási állapotának váltása"
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle starred status of current entry"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Próbálja ki a CommaFeed-et a demo fiókkal: demo/demo"
|
||||
|
||||
@@ -179,6 +179,10 @@ msgstr "Periksa apakah umpannya berfungsi"
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed item yang belum dibaca berikutnya"
|
||||
@@ -343,6 +347,14 @@ msgstr "URL Umpan"
|
||||
msgid "Fetch all my feeds now"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "file is required"
|
||||
msgstr "file diperlukan"
|
||||
@@ -832,6 +844,10 @@ msgstr "Beralih status baca entri saat ini"
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle starred status of current entry"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Cobalah CommaFeed dengan akun demo: demo/demo"
|
||||
|
||||
@@ -179,6 +179,10 @@ msgstr "Verifica che il feed funzioni"
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed successivo elemento non letto"
|
||||
@@ -343,6 +347,14 @@ msgstr "URL feed"
|
||||
msgid "Fetch all my feeds now"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "file is required"
|
||||
msgstr "è richiesto il file"
|
||||
@@ -832,6 +844,10 @@ msgstr "Commuta lo stato di lettura della voce corrente"
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle starred status of current entry"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Prova CommaFeed con il conto demo: demo/demo"
|
||||
|
||||
@@ -179,6 +179,10 @@ msgstr "フィードが動作していることを確認してください"
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "次の未読アイテムをカンマフィード"
|
||||
@@ -343,6 +347,14 @@ msgstr "フィード URL"
|
||||
msgid "Fetch all my feeds now"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "file is required"
|
||||
msgstr "ファイルが必要です"
|
||||
@@ -832,6 +844,10 @@ msgstr "現在のエントリの読み取りステータスを切り替えます
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle starred status of current entry"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "デモアカウントで CommaFeed を試す: demo/demo"
|
||||
|
||||
@@ -179,6 +179,10 @@ msgstr "피드가 작동하는지 확인"
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "다음 읽지 않은 항목을 쉼표로 피드"
|
||||
@@ -343,6 +347,14 @@ msgstr "피드 URL"
|
||||
msgid "Fetch all my feeds now"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "file is required"
|
||||
msgstr "파일이 필요합니다"
|
||||
@@ -832,6 +844,10 @@ msgstr "현재 항목의 읽기 상태 전환"
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle starred status of current entry"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "데모 계정으로 CommaFeed를 사용해 보세요: demo/demo"
|
||||
|
||||
@@ -179,6 +179,10 @@ msgstr "Semak sama ada suapan berfungsi"
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed item belum dibaca seterusnya"
|
||||
@@ -343,6 +347,14 @@ msgstr "URL Suapan"
|
||||
msgid "Fetch all my feeds now"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "file is required"
|
||||
msgstr "fail diperlukan"
|
||||
@@ -832,6 +844,10 @@ msgstr "Togol status bacaan entri semasa"
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle starred status of current entry"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Cuba CommaFeed dengan akaun demo: demo/demo"
|
||||
|
||||
@@ -179,6 +179,10 @@ msgstr "Sjekk at feeden fungerer"
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed neste uleste element"
|
||||
@@ -343,6 +347,14 @@ msgstr "Feed-URL"
|
||||
msgid "Fetch all my feeds now"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "file is required"
|
||||
msgstr "fil kreves"
|
||||
@@ -832,6 +844,10 @@ msgstr "Veksle lesestatus for gjeldende oppføring"
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle starred status of current entry"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Prøv CommaFeed med demokontoen: demo/demo"
|
||||
|
||||
@@ -179,6 +179,10 @@ msgstr "Controleer of de feed werkt"
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed volgende ongelezen item"
|
||||
@@ -343,6 +347,14 @@ msgstr "Feed-URL"
|
||||
msgid "Fetch all my feeds now"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "file is required"
|
||||
msgstr "bestand is vereist"
|
||||
@@ -832,6 +844,10 @@ msgstr "Toggle leesstatus van huidige invoer"
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle starred status of current entry"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Probeer CommaFeed uit met het demo-account: demo/demo"
|
||||
|
||||
@@ -179,6 +179,10 @@ msgstr "Sjekk at feeden fungerer"
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed neste uleste element"
|
||||
@@ -343,6 +347,14 @@ msgstr "Feed-URL"
|
||||
msgid "Fetch all my feeds now"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "file is required"
|
||||
msgstr "fil kreves"
|
||||
@@ -832,6 +844,10 @@ msgstr "Veksle lesestatus for gjeldende oppføring"
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle starred status of current entry"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Prøv CommaFeed med demokontoen: demo/demo"
|
||||
|
||||
@@ -179,6 +179,10 @@ msgstr "Sprawdź, czy kanał działa"
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "Przecinek następny nieprzeczytany element"
|
||||
@@ -343,6 +347,14 @@ msgstr "URL kanału"
|
||||
msgid "Fetch all my feeds now"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "file is required"
|
||||
msgstr "plik jest wymagany"
|
||||
@@ -832,6 +844,10 @@ msgstr "Przełącz stan odczytu bieżącego wpisu"
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle starred status of current entry"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Wypróbuj CommaFeed z kontem demo: demo/demo"
|
||||
|
||||
@@ -179,6 +179,10 @@ msgstr "Verifique se o feed está funcionando"
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed próximo item não lido"
|
||||
@@ -343,6 +347,14 @@ msgstr "URL do feed"
|
||||
msgid "Fetch all my feeds now"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "file is required"
|
||||
msgstr "o arquivo é obrigatório"
|
||||
@@ -832,6 +844,10 @@ msgstr "Alternar o status de leitura da entrada atual"
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle starred status of current entry"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Experimente o CommaFeed com a conta demo: demo/demo"
|
||||
|
||||
@@ -179,6 +179,10 @@ msgstr "Проверьте, работает ли лента."
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed следующий непрочитанный элемент"
|
||||
@@ -343,6 +347,14 @@ msgstr "URL-адрес фида"
|
||||
msgid "Fetch all my feeds now"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "file is required"
|
||||
msgstr "требуется файл"
|
||||
@@ -832,6 +844,10 @@ msgstr "Переключить статус чтения текущей запи
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle starred status of current entry"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Попробуйте CommaFeed на демо-счете: demo/demo"
|
||||
|
||||
@@ -179,6 +179,10 @@ msgstr "Skontrolujte, či feed funguje"
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed ďalšia neprečítaná položka"
|
||||
@@ -343,6 +347,14 @@ msgstr "URL informačného kanála"
|
||||
msgid "Fetch all my feeds now"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "file is required"
|
||||
msgstr ""
|
||||
@@ -832,6 +844,10 @@ msgstr "Prepne stav čítania aktuálneho záznamu"
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle starred status of current entry"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Vyskúšajte CommaFeed s demo účtom: demo/demo"
|
||||
|
||||
@@ -179,6 +179,10 @@ msgstr "Kontrollera att matningen fungerar"
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed nästa olästa objekt"
|
||||
@@ -343,6 +347,14 @@ msgstr "Flödes-URL"
|
||||
msgid "Fetch all my feeds now"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "file is required"
|
||||
msgstr "fil krävs"
|
||||
@@ -832,6 +844,10 @@ msgstr "Växla lässtatus för aktuell post"
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle starred status of current entry"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "Prova CommaFeed med demokontot: demo/demo"
|
||||
|
||||
@@ -179,6 +179,10 @@ msgstr "Feed'in çalışıp çalışmadığını kontrol edin"
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr "CommaFeed tarayıcı eklentisi sürüm {browserExtensionVersion}."
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed sonraki okunmamış öğe"
|
||||
@@ -343,6 +347,14 @@ msgstr "Feed URL'si"
|
||||
msgid "Fetch all my feeds now"
|
||||
msgstr "Tüm feed'lerimi şimdi çek"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "file is required"
|
||||
msgstr "dosya gerekli"
|
||||
@@ -832,6 +844,10 @@ msgstr "Geçerli girişin okuma durumunu değiştir"
|
||||
msgid "Toggle sidebar"
|
||||
msgstr "Kenar çubuğunu göster/gizle"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle starred status of current entry"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "CommaFeed'i demo hesabıyla deneyin: demo/demo"
|
||||
|
||||
@@ -179,6 +179,10 @@ msgstr "检查提要是否正常工作"
|
||||
msgid "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. The username is your user name and the password is your API key."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed 下一个未读项目"
|
||||
@@ -343,6 +347,14 @@ msgstr "供稿网址"
|
||||
msgid "Fetch all my feeds now"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Fever API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "file is required"
|
||||
msgstr "文件是必需的"
|
||||
@@ -832,6 +844,10 @@ msgstr "切换当前条目的读取状态"
|
||||
msgid "Toggle sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Toggle starred status of current entry"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "Try out CommaFeed with the demo account: demo/demo"
|
||||
msgstr "使用演示帐户试用 CommaFeed:demo/demo"
|
||||
|
||||
@@ -14,6 +14,7 @@ const shownMeters: { [key: string]: string } = {
|
||||
"com.commafeed.backend.feed.FeedRefreshUpdater.feedUpdated": "Feed update rate",
|
||||
"com.commafeed.backend.feed.FeedRefreshUpdater.entryCacheHit": "Entry cache hit rate",
|
||||
"com.commafeed.backend.feed.FeedRefreshUpdater.entryCacheMiss": "Entry cache miss rate",
|
||||
"com.commafeed.backend.service.DatabaseCleaningService.entriesDeleted": "Entries deleted",
|
||||
}
|
||||
|
||||
const shownGauges: { [key: string]: string } = {
|
||||
|
||||
@@ -103,7 +103,7 @@ app:
|
||||
|
||||
database:
|
||||
driverClass: org.h2.Driver
|
||||
url: jdbc:h2:./db/commafeed
|
||||
url: jdbc:h2:/commafeed/data/db
|
||||
user: sa
|
||||
password: sa
|
||||
properties:
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.commafeed</groupId>
|
||||
<artifactId>commafeed</artifactId>
|
||||
<version>3.9.0</version>
|
||||
<version>3.10.0</version>
|
||||
</parent>
|
||||
<artifactId>commafeed-server</artifactId>
|
||||
<name>CommaFeed Server</name>
|
||||
@@ -232,7 +232,7 @@
|
||||
<dependency>
|
||||
<groupId>com.commafeed</groupId>
|
||||
<artifactId>commafeed-client</artifactId>
|
||||
<version>3.9.0</version>
|
||||
<version>3.10.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -42,6 +42,7 @@ import com.commafeed.frontend.resource.FeedREST;
|
||||
import com.commafeed.frontend.resource.PubSubHubbubCallbackREST;
|
||||
import com.commafeed.frontend.resource.ServerREST;
|
||||
import com.commafeed.frontend.resource.UserREST;
|
||||
import com.commafeed.frontend.resource.fever.FeverREST;
|
||||
import com.commafeed.frontend.servlet.AnalyticsServlet;
|
||||
import com.commafeed.frontend.servlet.CustomCssServlet;
|
||||
import com.commafeed.frontend.servlet.CustomJsServlet;
|
||||
@@ -147,7 +148,7 @@ public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
|
||||
Injector injector = Guice.createInjector(new CommaFeedModule(hibernateBundle.getSessionFactory(), config, environment.metrics()));
|
||||
|
||||
// session management
|
||||
environment.servlets().setSessionHandler(config.getSessionHandlerFactory().build());
|
||||
environment.servlets().setSessionHandler(config.getSessionHandlerFactory().build(config.getDataSourceFactory()));
|
||||
|
||||
// support for "@SecurityCheck User user" injection
|
||||
environment.jersey().register(new SecurityCheckFactoryProvider.Binder(injector.getInstance(UserService.class)));
|
||||
@@ -163,6 +164,7 @@ public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
|
||||
environment.jersey().register(injector.getInstance(PubSubHubbubCallbackREST.class));
|
||||
environment.jersey().register(injector.getInstance(ServerREST.class));
|
||||
environment.jersey().register(injector.getInstance(UserREST.class));
|
||||
environment.jersey().register(injector.getInstance(FeverREST.class));
|
||||
|
||||
// Servlets
|
||||
environment.servlets().addServlet("next", injector.getInstance(NextUnreadServlet.class)).addMapping("/next");
|
||||
|
||||
@@ -117,7 +117,7 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
||||
}
|
||||
|
||||
private JPAQuery<FeedEntry> buildQuery(User user, FeedSubscription sub, boolean unreadOnly, List<FeedEntryKeyword> keywords,
|
||||
Date newerThan, int offset, int limit, ReadingOrder order, FeedEntryStatus last, String tag) {
|
||||
Date newerThan, int offset, int limit, ReadingOrder order, FeedEntryStatus last, String tag, Long minEntryId, Long maxEntryId) {
|
||||
|
||||
JPAQuery<FeedEntry> query = query().selectFrom(entry).where(entry.feed.eq(sub.getFeed()));
|
||||
|
||||
@@ -159,6 +159,14 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
||||
query.where(entry.inserted.goe(newerThan));
|
||||
}
|
||||
|
||||
if (minEntryId != null) {
|
||||
query.where(entry.id.gt(minEntryId));
|
||||
}
|
||||
|
||||
if (maxEntryId != null) {
|
||||
query.where(entry.id.lt(maxEntryId));
|
||||
}
|
||||
|
||||
if (last != null) {
|
||||
if (order == ReadingOrder.desc) {
|
||||
query.where(entry.updated.gt(last.getEntryUpdated()));
|
||||
@@ -189,7 +197,7 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
||||
|
||||
public List<FeedEntryStatus> findBySubscriptions(User user, List<FeedSubscription> subs, boolean unreadOnly,
|
||||
List<FeedEntryKeyword> keywords, Date newerThan, int offset, int limit, ReadingOrder order, boolean includeContent,
|
||||
boolean onlyIds, String tag) {
|
||||
boolean onlyIds, String tag, Long minEntryId, Long maxEntryId) {
|
||||
int capacity = offset + limit;
|
||||
|
||||
Comparator<FeedEntryStatus> comparator = order == ReadingOrder.desc ? STATUS_COMPARATOR_DESC : STATUS_COMPARATOR_ASC;
|
||||
@@ -197,7 +205,8 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
||||
FixedSizeSortedSet<FeedEntryStatus> set = new FixedSizeSortedSet<>(capacity, comparator);
|
||||
for (FeedSubscription sub : subs) {
|
||||
FeedEntryStatus last = (order != null && set.isFull()) ? set.last() : null;
|
||||
JPAQuery<FeedEntry> query = buildQuery(user, sub, unreadOnly, keywords, newerThan, -1, capacity, order, last, tag);
|
||||
JPAQuery<FeedEntry> query = buildQuery(user, sub, unreadOnly, keywords, newerThan, -1, capacity, order, last, tag, minEntryId,
|
||||
maxEntryId);
|
||||
List<Tuple> tuples = query.select(entry.id, entry.updated, status.id, entry.content.title).fetch();
|
||||
|
||||
for (Tuple tuple : tuples) {
|
||||
@@ -250,7 +259,7 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
||||
|
||||
public UnreadCount getUnreadCount(User user, FeedSubscription subscription) {
|
||||
UnreadCount uc = null;
|
||||
JPAQuery<FeedEntry> query = buildQuery(user, subscription, true, null, null, -1, -1, null, null, null);
|
||||
JPAQuery<FeedEntry> query = buildQuery(user, subscription, true, null, null, -1, -1, null, null, null, null, null);
|
||||
List<Tuple> tuples = query.select(entry.count(), entry.updated.max()).fetch();
|
||||
for (Tuple tuple : tuples) {
|
||||
Long count = tuple.get(entry.count());
|
||||
|
||||
@@ -6,6 +6,8 @@ import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import com.codahale.metrics.Meter;
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.backend.dao.FeedDAO;
|
||||
import com.commafeed.backend.dao.FeedEntryContentDAO;
|
||||
@@ -32,16 +34,18 @@ public class DatabaseCleaningService {
|
||||
private final FeedEntryDAO feedEntryDAO;
|
||||
private final FeedEntryContentDAO feedEntryContentDAO;
|
||||
private final FeedEntryStatusDAO feedEntryStatusDAO;
|
||||
private final Meter entriesDeletedMeter;
|
||||
|
||||
@Inject
|
||||
public DatabaseCleaningService(CommaFeedConfiguration config, UnitOfWork unitOfWork, FeedDAO feedDAO, FeedEntryDAO feedEntryDAO,
|
||||
FeedEntryContentDAO feedEntryContentDAO, FeedEntryStatusDAO feedEntryStatusDAO) {
|
||||
FeedEntryContentDAO feedEntryContentDAO, FeedEntryStatusDAO feedEntryStatusDAO, MetricRegistry metrics) {
|
||||
this.unitOfWork = unitOfWork;
|
||||
this.feedDAO = feedDAO;
|
||||
this.feedEntryDAO = feedEntryDAO;
|
||||
this.feedEntryContentDAO = feedEntryContentDAO;
|
||||
this.feedEntryStatusDAO = feedEntryStatusDAO;
|
||||
this.batchSize = config.getApplicationSettings().getDatabaseCleanupBatchSize();
|
||||
this.entriesDeletedMeter = metrics.meter(MetricRegistry.name(getClass(), "entriesDeleted"));
|
||||
}
|
||||
|
||||
public void cleanFeedsWithoutSubscriptions() {
|
||||
@@ -55,6 +59,7 @@ public class DatabaseCleaningService {
|
||||
long entriesDeleted;
|
||||
do {
|
||||
entriesDeleted = unitOfWork.call(() -> feedEntryDAO.delete(feed.getId(), batchSize));
|
||||
entriesDeletedMeter.mark(entriesDeleted);
|
||||
entriesTotal += entriesDeleted;
|
||||
log.info("removed {} entries for feeds without subscriptions", entriesTotal);
|
||||
} while (entriesDeleted > 0);
|
||||
@@ -91,6 +96,7 @@ public class DatabaseCleaningService {
|
||||
do {
|
||||
final long rem = remaining;
|
||||
int deleted = unitOfWork.call(() -> feedEntryDAO.deleteOldEntries(feed.getId(), Math.min(batchSize, rem)));
|
||||
entriesDeletedMeter.mark(deleted);
|
||||
total += deleted;
|
||||
remaining -= deleted;
|
||||
log.info("removed {} entries for feeds exceeding capacity", total);
|
||||
|
||||
@@ -111,7 +111,7 @@ public class FeedEntryService {
|
||||
|
||||
public void markSubscriptionEntries(User user, List<FeedSubscription> subscriptions, Date olderThan, List<FeedEntryKeyword> keywords) {
|
||||
List<FeedEntryStatus> statuses = feedEntryStatusDAO.findBySubscriptions(user, subscriptions, true, keywords, null, -1, -1, null,
|
||||
false, false, null);
|
||||
false, false, null, null, null);
|
||||
markList(statuses, olderThan);
|
||||
cache.invalidateUnreadCount(subscriptions.toArray(new FeedSubscription[0]));
|
||||
cache.invalidateUserRootCategory(user);
|
||||
|
||||
@@ -82,6 +82,28 @@ public class UserService {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* try to log in with given fever api key
|
||||
*/
|
||||
public Optional<User> login(long userId, String feverApiKey) {
|
||||
if (feverApiKey == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
User user = userDAO.findById(userId);
|
||||
if (user == null || user.isDisabled() || user.getApiKey() == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
String computedFeverApiKey = DigestUtils.md5Hex(user.getName() + ":" + user.getApiKey());
|
||||
if (!computedFeverApiKey.equals(feverApiKey)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
performPostLoginActivities(user);
|
||||
return Optional.of(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* should triggers after successful login
|
||||
*/
|
||||
|
||||
@@ -141,7 +141,7 @@ public class CategoryREST {
|
||||
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user);
|
||||
removeExcludedSubscriptions(subs, excludedIds);
|
||||
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(user, subs, unreadOnly, entryKeywords, newerThanDate,
|
||||
offset, limit + 1, order, true, onlyIds, tag);
|
||||
offset, limit + 1, order, true, onlyIds, tag, null, null);
|
||||
|
||||
for (FeedEntryStatus status : list) {
|
||||
entries.getEntries().add(Entry.build(status, config.getApplicationSettings().getImageProxyEnabled()));
|
||||
@@ -160,7 +160,7 @@ public class CategoryREST {
|
||||
List<FeedSubscription> subs = feedSubscriptionDAO.findByCategories(user, categories);
|
||||
removeExcludedSubscriptions(subs, excludedIds);
|
||||
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(user, subs, unreadOnly, entryKeywords, newerThanDate,
|
||||
offset, limit + 1, order, true, onlyIds, tag);
|
||||
offset, limit + 1, order, true, onlyIds, tag, null, null);
|
||||
|
||||
for (FeedEntryStatus status : list) {
|
||||
entries.getEntries().add(Entry.build(status, config.getApplicationSettings().getImageProxyEnabled()));
|
||||
|
||||
@@ -172,7 +172,7 @@ public class FeedREST {
|
||||
entries.setFeedLink(subscription.getFeed().getLink());
|
||||
|
||||
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(user, Collections.singletonList(subscription), unreadOnly,
|
||||
entryKeywords, newerThanDate, offset, limit + 1, order, true, onlyIds, null);
|
||||
entryKeywords, newerThanDate, offset, limit + 1, order, true, onlyIds, null, null, null);
|
||||
|
||||
for (FeedEntryStatus status : list) {
|
||||
entries.getEntries().add(Entry.build(status, config.getApplicationSettings().getImageProxyEnabled()));
|
||||
|
||||
@@ -0,0 +1,320 @@
|
||||
package com.commafeed.frontend.resource.fever;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
||||
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
|
||||
|
||||
import com.codahale.metrics.annotation.Timed;
|
||||
import com.commafeed.backend.dao.FeedCategoryDAO;
|
||||
import com.commafeed.backend.dao.FeedEntryDAO;
|
||||
import com.commafeed.backend.dao.FeedEntryStatusDAO;
|
||||
import com.commafeed.backend.dao.FeedSubscriptionDAO;
|
||||
import com.commafeed.backend.favicon.AbstractFaviconFetcher.Favicon;
|
||||
import com.commafeed.backend.model.Feed;
|
||||
import com.commafeed.backend.model.FeedCategory;
|
||||
import com.commafeed.backend.model.FeedEntry;
|
||||
import com.commafeed.backend.model.FeedEntryStatus;
|
||||
import com.commafeed.backend.model.FeedSubscription;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.commafeed.backend.model.UserSettings.ReadingOrder;
|
||||
import com.commafeed.backend.service.FeedEntryService;
|
||||
import com.commafeed.backend.service.FeedService;
|
||||
import com.commafeed.backend.service.UserService;
|
||||
import com.commafeed.frontend.resource.fever.FeverResponse.FeverFavicon;
|
||||
import com.commafeed.frontend.resource.fever.FeverResponse.FeverFeed;
|
||||
import com.commafeed.frontend.resource.fever.FeverResponse.FeverFeedGroup;
|
||||
import com.commafeed.frontend.resource.fever.FeverResponse.FeverGroup;
|
||||
import com.commafeed.frontend.resource.fever.FeverResponse.FeverItem;
|
||||
|
||||
import io.dropwizard.hibernate.UnitOfWork;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* Fever-compatible API
|
||||
*
|
||||
* <ul>
|
||||
* <li>url: /rest/fever/user/${userId}</li>
|
||||
* <li>login: username</li>
|
||||
* <li>password: api key</li>
|
||||
* </ul>
|
||||
*
|
||||
* See https://feedafever.com/api
|
||||
*/
|
||||
@Path("/fever")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
|
||||
@Singleton
|
||||
public class FeverREST {
|
||||
|
||||
private static final String PATH = "/user/{userId}{optionalTrailingFever : (/fever)?}{optionalTrailingSlash : (/)?}";
|
||||
private static final int UNREAD_ITEM_IDS_BATCH_SIZE = 1000;
|
||||
private static final int SAVED_ITEM_IDS_BATCH_SIZE = 1000;
|
||||
private static final int ITEMS_BATCH_SIZE = 200;
|
||||
|
||||
private final UserService userService;
|
||||
private final FeedEntryService feedEntryService;
|
||||
private final FeedService feedService;
|
||||
private final FeedEntryDAO feedEntryDAO;
|
||||
private final FeedSubscriptionDAO feedSubscriptionDAO;
|
||||
private final FeedCategoryDAO feedCategoryDAO;
|
||||
private final FeedEntryStatusDAO feedEntryStatusDAO;
|
||||
|
||||
// expected Fever API
|
||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
@Path(PATH)
|
||||
@POST
|
||||
@UnitOfWork
|
||||
@Timed
|
||||
public FeverResponse formUrlencoded(@Context UriInfo uri, @PathParam("userId") Long userId, MultivaluedMap<String, String> form) {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
uri.getQueryParameters().forEach((k, v) -> params.put(k, v.get(0)));
|
||||
form.forEach((k, v) -> params.put(k, v.get(0)));
|
||||
return handle(userId, params);
|
||||
}
|
||||
|
||||
// workaround for some readers that post data without any media type, and all params in the url
|
||||
// e.g. FeedMe
|
||||
@Path(PATH)
|
||||
@POST
|
||||
@UnitOfWork
|
||||
@Timed
|
||||
public FeverResponse noForm(@Context UriInfo uri, @PathParam("userId") Long userId) {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
uri.getQueryParameters().forEach((k, v) -> params.put(k, v.get(0)));
|
||||
return handle(userId, params);
|
||||
}
|
||||
|
||||
// workaround for some readers that post data using MultiPart FormData instead of the classic POST
|
||||
// e.g. Raven Reader
|
||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||
@Path(PATH)
|
||||
@POST
|
||||
@UnitOfWork
|
||||
@Timed
|
||||
public FeverResponse formData(@Context UriInfo uri, @PathParam("userId") Long userId, FormDataMultiPart form) {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
uri.getQueryParameters().forEach((k, v) -> params.put(k, v.get(0)));
|
||||
form.getFields().forEach((k, v) -> params.put(k, v.get(0).getValue()));
|
||||
return handle(userId, params);
|
||||
}
|
||||
|
||||
public FeverResponse handle(long userId, Map<String, String> params) {
|
||||
User user = auth(userId, params.get("api_key")).orElse(null);
|
||||
if (user == null) {
|
||||
FeverResponse resp = new FeverResponse();
|
||||
resp.setAuth(false);
|
||||
return resp;
|
||||
}
|
||||
|
||||
FeverResponse resp = new FeverResponse();
|
||||
resp.setAuth(true);
|
||||
|
||||
List<FeedSubscription> subscriptions = feedSubscriptionDAO.findAll(user);
|
||||
resp.setLastRefreshedOnTime(buildLastRefreshedOnTime(subscriptions));
|
||||
|
||||
if (params.containsKey("groups") || params.containsKey("feeds")) {
|
||||
resp.setFeedsGroups(buildFeedsGroups(subscriptions));
|
||||
|
||||
if (params.containsKey("groups")) {
|
||||
List<FeedCategory> categories = feedCategoryDAO.findAll(user);
|
||||
resp.setGroups(buildGroups(categories));
|
||||
}
|
||||
|
||||
if (params.containsKey("feeds")) {
|
||||
resp.setFeeds(buildFeeds(subscriptions));
|
||||
}
|
||||
}
|
||||
|
||||
if (params.containsKey("unread_item_ids")) {
|
||||
resp.setUnreadItemIds(buildUnreadItemIds(user, subscriptions));
|
||||
}
|
||||
|
||||
if (params.containsKey("saved_item_ids")) {
|
||||
resp.setSavedItemIds(buildSavedItemIds(user));
|
||||
}
|
||||
|
||||
if (params.containsKey("items")) {
|
||||
if (params.containsKey("with_ids")) {
|
||||
String withIds = params.get("with_ids");
|
||||
List<String> entryIds = Stream.of(withIds.split(",")).map(String::trim).collect(Collectors.toList());
|
||||
resp.setItems(buildItems(user, subscriptions, entryIds));
|
||||
} else {
|
||||
Long sinceId = params.containsKey("since_id") ? Long.valueOf(params.get("since_id")) : null;
|
||||
Long maxId = params.containsKey("max_id") ? Long.valueOf(params.get("max_id")) : null;
|
||||
resp.setItems(buildItems(user, subscriptions, sinceId, maxId));
|
||||
}
|
||||
}
|
||||
|
||||
if (params.containsKey("favicons")) {
|
||||
resp.setFavicons(buildFavicons(subscriptions));
|
||||
}
|
||||
|
||||
if (params.containsKey("links")) {
|
||||
resp.setLinks(Collections.emptyList());
|
||||
}
|
||||
|
||||
if (params.containsKey("mark") && params.containsKey("id") && params.containsKey("as")) {
|
||||
long id = Long.parseLong(params.get("id"));
|
||||
String before = params.get("before");
|
||||
Date olderThan = before == null ? null : Date.from(Instant.ofEpochSecond(Long.parseLong(before)));
|
||||
mark(user, params.get("mark"), id, params.get("as"), olderThan);
|
||||
}
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
private Optional<User> auth(Long userId, String feverApiKey) {
|
||||
return userService.login(userId, feverApiKey);
|
||||
}
|
||||
|
||||
private long buildLastRefreshedOnTime(List<FeedSubscription> subscriptions) {
|
||||
return subscriptions.stream()
|
||||
.map(FeedSubscription::getFeed)
|
||||
.map(Feed::getLastUpdated)
|
||||
.filter(Objects::nonNull)
|
||||
.max(Comparator.naturalOrder())
|
||||
.map(d -> d.toInstant().getEpochSecond())
|
||||
.orElse(0L);
|
||||
}
|
||||
|
||||
private List<FeverFeedGroup> buildFeedsGroups(List<FeedSubscription> subscriptions) {
|
||||
return subscriptions.stream()
|
||||
.collect(Collectors.groupingBy(s -> s.getCategory() == null ? 0 : s.getCategory().getId()))
|
||||
.entrySet()
|
||||
.stream()
|
||||
.map(e -> {
|
||||
FeverFeedGroup fg = new FeverFeedGroup();
|
||||
fg.setGroupId(e.getKey());
|
||||
fg.setFeedIds(e.getValue().stream().map(FeedSubscription::getId).collect(Collectors.toList()));
|
||||
return fg;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<FeverGroup> buildGroups(List<FeedCategory> categories) {
|
||||
return categories.stream().map(c -> {
|
||||
FeverGroup g = new FeverGroup();
|
||||
g.setId(c.getId());
|
||||
g.setTitle(c.getName());
|
||||
return g;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<FeverFeed> buildFeeds(List<FeedSubscription> subscriptions) {
|
||||
return subscriptions.stream().map(s -> {
|
||||
FeverFeed f = new FeverFeed();
|
||||
f.setId(s.getId());
|
||||
f.setFaviconId(s.getId());
|
||||
f.setTitle(s.getTitle());
|
||||
f.setUrl(s.getFeed().getUrl());
|
||||
f.setSiteUrl(s.getFeed().getLink());
|
||||
f.setSpark(false);
|
||||
f.setLastUpdatedOnTime(s.getFeed().getLastUpdated() == null ? 0 : s.getFeed().getLastUpdated().toInstant().getEpochSecond());
|
||||
return f;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<Long> buildUnreadItemIds(User user, List<FeedSubscription> subscriptions) {
|
||||
List<FeedEntryStatus> statuses = feedEntryStatusDAO.findBySubscriptions(user, subscriptions, true, null, null, 0,
|
||||
UNREAD_ITEM_IDS_BATCH_SIZE, ReadingOrder.desc, false, true, null, null, null);
|
||||
return statuses.stream().map(s -> s.getEntry().getId()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<Long> buildSavedItemIds(User user) {
|
||||
List<FeedEntryStatus> statuses = feedEntryStatusDAO.findStarred(user, null, 0, SAVED_ITEM_IDS_BATCH_SIZE, ReadingOrder.desc, false);
|
||||
return statuses.stream().map(s -> s.getEntry().getId()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<FeverItem> buildItems(User user, List<FeedSubscription> subscriptions, List<String> entryIds) {
|
||||
List<FeverItem> items = new ArrayList<>();
|
||||
|
||||
Map<Long, FeedSubscription> subscriptionsByFeedId = subscriptions.stream()
|
||||
.collect(Collectors.toMap(s -> s.getFeed().getId(), s -> s));
|
||||
for (String entryId : entryIds) {
|
||||
FeedEntry entry = feedEntryDAO.findById(Long.parseLong(entryId));
|
||||
FeedSubscription sub = subscriptionsByFeedId.get(entry.getFeed().getId());
|
||||
if (sub != null) {
|
||||
FeedEntryStatus status = feedEntryStatusDAO.getStatus(user, sub, entry);
|
||||
items.add(mapStatus(status));
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private List<FeverItem> buildItems(User user, List<FeedSubscription> subscriptions, Long sinceId, Long maxId) {
|
||||
List<FeedEntryStatus> statuses = feedEntryStatusDAO.findBySubscriptions(user, subscriptions, false, null, null, 0, ITEMS_BATCH_SIZE,
|
||||
ReadingOrder.desc, false, false, null, sinceId, maxId);
|
||||
return statuses.stream().map(this::mapStatus).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private FeverItem mapStatus(FeedEntryStatus s) {
|
||||
FeverItem i = new FeverItem();
|
||||
i.setId(s.getEntry().getId());
|
||||
i.setFeedId(s.getSubscription().getId());
|
||||
i.setTitle(s.getEntry().getContent().getTitle());
|
||||
i.setAuthor(s.getEntry().getContent().getAuthor());
|
||||
i.setHtml(Optional.ofNullable(s.getEntry().getContent().getContent()).orElse(""));
|
||||
i.setUrl(s.getEntry().getUrl());
|
||||
i.setSaved(s.isStarred());
|
||||
i.setRead(s.isRead());
|
||||
i.setCreatedOnTime(s.getEntryUpdated().toInstant().getEpochSecond());
|
||||
return i;
|
||||
}
|
||||
|
||||
private List<FeverFavicon> buildFavicons(List<FeedSubscription> subscriptions) {
|
||||
return subscriptions.stream().map(s -> {
|
||||
Favicon favicon = feedService.fetchFavicon(s.getFeed());
|
||||
|
||||
FeverFavicon f = new FeverFavicon();
|
||||
f.setId(s.getFeed().getId());
|
||||
f.setData(String.format("data:%s;base64,%s", favicon.getMediaType(), Base64.getEncoder().encodeToString(favicon.getIcon())));
|
||||
return f;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void mark(User user, String source, long id, String action, Date olderThan) {
|
||||
if ("item".equals(source)) {
|
||||
if ("read".equals(action) || "unread".equals(action)) {
|
||||
feedEntryService.markEntry(user, id, "read".equals(action));
|
||||
} else if ("saved".equals(action) || "unsaved".equals(action)) {
|
||||
FeedEntry entry = feedEntryDAO.findById(id);
|
||||
FeedSubscription sub = feedSubscriptionDAO.findByFeed(user, entry.getFeed());
|
||||
feedEntryService.starEntry(user, id, sub.getId(), "saved".equals(action));
|
||||
}
|
||||
} else if ("feed".equals(source)) {
|
||||
FeedSubscription subscription = feedSubscriptionDAO.findById(user, id);
|
||||
feedEntryService.markSubscriptionEntries(user, Collections.singletonList(subscription), olderThan, null);
|
||||
} else if ("group".equals(source)) {
|
||||
FeedCategory parent = feedCategoryDAO.findById(user, id);
|
||||
List<FeedCategory> categories = feedCategoryDAO.findAllChildrenCategories(user, parent);
|
||||
List<FeedSubscription> subscriptions = feedSubscriptionDAO.findByCategories(user, categories);
|
||||
feedEntryService.markSubscriptionEntries(user, subscriptions, olderThan, null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
package com.commafeed.frontend.resource.fever;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat.Shape;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
@Data
|
||||
public class FeverResponse {
|
||||
|
||||
@JsonProperty("api_version")
|
||||
private int apiVersion = 3;
|
||||
|
||||
@JsonProperty("auth")
|
||||
@JsonFormat(shape = Shape.NUMBER)
|
||||
private boolean auth;
|
||||
|
||||
@JsonProperty("last_refreshed_on_time")
|
||||
private long lastRefreshedOnTime;
|
||||
|
||||
@JsonProperty("groups")
|
||||
private List<FeverGroup> groups;
|
||||
|
||||
@JsonProperty("feeds")
|
||||
private List<FeverFeed> feeds;
|
||||
|
||||
@JsonProperty("feeds_groups")
|
||||
private List<FeverFeedGroup> feedsGroups;
|
||||
|
||||
@JsonProperty("unread_item_ids")
|
||||
@JsonSerialize(using = LongListToCommaSeparatedStringSerializer.class)
|
||||
private List<Long> unreadItemIds;
|
||||
|
||||
@JsonProperty("saved_item_ids")
|
||||
@JsonSerialize(using = LongListToCommaSeparatedStringSerializer.class)
|
||||
private List<Long> savedItemIds;
|
||||
|
||||
@JsonProperty("items")
|
||||
private List<FeverItem> items;
|
||||
|
||||
@JsonProperty("favicons")
|
||||
private List<FeverFavicon> favicons;
|
||||
|
||||
@JsonProperty("links")
|
||||
private List<FeverLink> links;
|
||||
|
||||
@Data
|
||||
public static class FeverGroup {
|
||||
|
||||
@JsonProperty("id")
|
||||
private long id;
|
||||
|
||||
@JsonProperty("title")
|
||||
private String title;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class FeverFeed {
|
||||
|
||||
@JsonProperty("id")
|
||||
private long id;
|
||||
|
||||
@JsonProperty("favicon_id")
|
||||
private long faviconId;
|
||||
|
||||
@JsonProperty("title")
|
||||
private String title;
|
||||
|
||||
@JsonProperty("url")
|
||||
private String url;
|
||||
|
||||
@JsonProperty("site_url")
|
||||
private String siteUrl;
|
||||
|
||||
@JsonProperty("is_spark")
|
||||
@JsonFormat(shape = Shape.NUMBER)
|
||||
private boolean spark;
|
||||
|
||||
@JsonProperty("last_updated_on_time")
|
||||
private long lastUpdatedOnTime;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class FeverFeedGroup {
|
||||
|
||||
@JsonProperty("group_id")
|
||||
private long groupId;
|
||||
|
||||
@JsonProperty("feed_ids")
|
||||
@JsonSerialize(using = LongListToCommaSeparatedStringSerializer.class)
|
||||
private List<Long> feedIds;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class FeverItem {
|
||||
|
||||
@JsonProperty("id")
|
||||
private long id;
|
||||
|
||||
@JsonProperty("feed_id")
|
||||
private long feedId;
|
||||
|
||||
@JsonProperty("title")
|
||||
private String title;
|
||||
|
||||
@JsonProperty("author")
|
||||
private String author;
|
||||
|
||||
@JsonProperty("html")
|
||||
private String html;
|
||||
|
||||
@JsonProperty("url")
|
||||
private String url;
|
||||
|
||||
@JsonProperty("is_saved")
|
||||
@JsonFormat(shape = Shape.NUMBER)
|
||||
private boolean saved;
|
||||
|
||||
@JsonProperty("is_read")
|
||||
@JsonFormat(shape = Shape.NUMBER)
|
||||
private boolean read;
|
||||
|
||||
@JsonProperty("created_on_time")
|
||||
private long createdOnTime;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class FeverFavicon {
|
||||
|
||||
@JsonProperty("id")
|
||||
private long id;
|
||||
|
||||
@JsonProperty("data")
|
||||
private String data;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class FeverLink {
|
||||
|
||||
}
|
||||
|
||||
public static class LongListToCommaSeparatedStringSerializer extends JsonSerializer<List<Long>> {
|
||||
|
||||
@Override
|
||||
public void serialize(List<Long> input, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
|
||||
String output = input.stream().map(String::valueOf).collect(Collectors.joining(","));
|
||||
jsonGenerator.writeObject(output);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -69,7 +69,7 @@ public class NextUnreadServlet extends HttpServlet {
|
||||
if (StringUtils.isBlank(categoryId) || CategoryREST.ALL.equals(categoryId)) {
|
||||
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user.get());
|
||||
List<FeedEntryStatus> statuses = feedEntryStatusDAO.findBySubscriptions(user.get(), subs, true, null, null, 0, 1, order,
|
||||
true, false, null);
|
||||
true, false, null, null, null);
|
||||
s = Iterables.getFirst(statuses, null);
|
||||
} else {
|
||||
FeedCategory category = feedCategoryDAO.findById(user.get(), Long.valueOf(categoryId));
|
||||
@@ -77,7 +77,7 @@ public class NextUnreadServlet extends HttpServlet {
|
||||
List<FeedCategory> children = feedCategoryDAO.findAllChildrenCategories(user.get(), category);
|
||||
List<FeedSubscription> subscriptions = feedSubscriptionDAO.findByCategories(user.get(), children);
|
||||
List<FeedEntryStatus> statuses = feedEntryStatusDAO.findBySubscriptions(user.get(), subscriptions, true, null, null, 0,
|
||||
1, order, true, false, null);
|
||||
1, order, true, false, null, null, null);
|
||||
s = Iterables.getFirst(statuses, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
package com.commafeed.frontend.session;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import javax.servlet.SessionTrackingMode;
|
||||
|
||||
import org.eclipse.jetty.server.session.DatabaseAdaptor;
|
||||
import org.eclipse.jetty.server.session.DefaultSessionCache;
|
||||
import org.eclipse.jetty.server.session.FileSessionDataStore;
|
||||
import org.eclipse.jetty.server.session.JDBCSessionDataStore;
|
||||
import org.eclipse.jetty.server.session.SessionCache;
|
||||
import org.eclipse.jetty.server.session.SessionHandler;
|
||||
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
import io.dropwizard.db.DataSourceFactory;
|
||||
import io.dropwizard.util.Duration;
|
||||
|
||||
public class SessionHandlerFactory {
|
||||
|
||||
@JsonProperty
|
||||
private String path = "sessions";
|
||||
|
||||
@JsonProperty
|
||||
private Duration cookieMaxAge = Duration.days(30);
|
||||
|
||||
@@ -31,26 +29,24 @@ public class SessionHandlerFactory {
|
||||
@JsonProperty
|
||||
private Duration savePeriod = Duration.minutes(5);
|
||||
|
||||
public SessionHandler build() {
|
||||
SessionHandler sessionHandler = new SessionHandler() {
|
||||
{
|
||||
// no setter available for maxCookieAge
|
||||
_maxCookieAge = (int) cookieMaxAge.toSeconds();
|
||||
}
|
||||
};
|
||||
SessionCache sessionCache = new DefaultSessionCache(sessionHandler);
|
||||
sessionHandler.setSessionCache(sessionCache);
|
||||
FileSessionDataStore dataStore = new FileSessionDataStore();
|
||||
sessionCache.setSessionDataStore(dataStore);
|
||||
|
||||
public SessionHandler build(DataSourceFactory dataSourceFactory) {
|
||||
SessionHandler sessionHandler = new SessionHandler();
|
||||
sessionHandler.setHttpOnly(true);
|
||||
sessionHandler.setSessionTrackingModes(ImmutableSet.of(SessionTrackingMode.COOKIE));
|
||||
sessionHandler.setMaxInactiveInterval((int) maxInactiveInterval.toSeconds());
|
||||
sessionHandler.setRefreshCookieAge((int) cookieRefreshAge.toSeconds());
|
||||
sessionHandler.getSessionCookieConfig().setMaxAge((int) cookieMaxAge.toSeconds());
|
||||
|
||||
dataStore.setDeleteUnrestorableFiles(true);
|
||||
dataStore.setStoreDir(new File(path));
|
||||
SessionCache sessionCache = new DefaultSessionCache(sessionHandler);
|
||||
sessionHandler.setSessionCache(sessionCache);
|
||||
|
||||
JDBCSessionDataStore dataStore = new JDBCSessionDataStore();
|
||||
dataStore.setSavePeriodSec((int) savePeriod.toSeconds());
|
||||
sessionCache.setSessionDataStore(dataStore);
|
||||
|
||||
DatabaseAdaptor adaptor = new DatabaseAdaptor();
|
||||
adaptor.setDatasource(dataSourceFactory.build(new MetricRegistry(), "sessions"));
|
||||
dataStore.setDatabaseAdaptor(adaptor);
|
||||
|
||||
return sessionHandler;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.commafeed;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
|
||||
import io.dropwizard.testing.ResourceHelpers;
|
||||
import io.dropwizard.testing.junit5.DropwizardAppExtension;
|
||||
|
||||
public class CommaFeedDropwizardAppExtension extends DropwizardAppExtension<CommaFeedConfiguration> {
|
||||
|
||||
public CommaFeedDropwizardAppExtension() {
|
||||
super(CommaFeedApplication.class, ResourceHelpers.resourceFilePath("config.test.yml"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void after() {
|
||||
super.after();
|
||||
|
||||
// clean database after each test
|
||||
DataSource dataSource = getConfiguration().getDataSourceFactory().build(new MetricRegistry(), "cleanup");
|
||||
try (Connection connection = dataSource.getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("DROP ALL OBJECTS")) {
|
||||
statement.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException("could not cleanup database", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,20 +3,16 @@ package com.commafeed.e2e;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import com.commafeed.CommaFeedApplication;
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.CommaFeedDropwizardAppExtension;
|
||||
import com.microsoft.playwright.Locator;
|
||||
import com.microsoft.playwright.assertions.PlaywrightAssertions;
|
||||
|
||||
import io.dropwizard.testing.ResourceHelpers;
|
||||
import io.dropwizard.testing.junit5.DropwizardAppExtension;
|
||||
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
|
||||
|
||||
@ExtendWith(DropwizardExtensionsSupport.class)
|
||||
class AuthentificationIT extends PlaywrightTestBase {
|
||||
|
||||
private static final DropwizardAppExtension<CommaFeedConfiguration> EXT = new DropwizardAppExtension<>(CommaFeedApplication.class,
|
||||
ResourceHelpers.resourceFilePath("config.test.yml"));
|
||||
private static final CommaFeedDropwizardAppExtension EXT = new CommaFeedDropwizardAppExtension();
|
||||
|
||||
@Test
|
||||
void loginFail() {
|
||||
|
||||
@@ -12,22 +12,18 @@ import org.mockserver.junit.jupiter.MockServerExtension;
|
||||
import org.mockserver.model.HttpRequest;
|
||||
import org.mockserver.model.HttpResponse;
|
||||
|
||||
import com.commafeed.CommaFeedApplication;
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.CommaFeedDropwizardAppExtension;
|
||||
import com.microsoft.playwright.Locator;
|
||||
import com.microsoft.playwright.Locator.WaitForOptions;
|
||||
import com.microsoft.playwright.assertions.PlaywrightAssertions;
|
||||
|
||||
import io.dropwizard.testing.ResourceHelpers;
|
||||
import io.dropwizard.testing.junit5.DropwizardAppExtension;
|
||||
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
|
||||
|
||||
@ExtendWith(DropwizardExtensionsSupport.class)
|
||||
@ExtendWith(MockServerExtension.class)
|
||||
class ReadingIT extends PlaywrightTestBase {
|
||||
|
||||
private static final DropwizardAppExtension<CommaFeedConfiguration> EXT = new DropwizardAppExtension<CommaFeedConfiguration>(
|
||||
CommaFeedApplication.class, ResourceHelpers.resourceFilePath("config.test.yml"));
|
||||
private static final CommaFeedDropwizardAppExtension EXT = new CommaFeedDropwizardAppExtension();
|
||||
|
||||
private MockServerClient mockServerClient;
|
||||
|
||||
|
||||
@@ -22,21 +22,17 @@ import org.mockserver.junit.jupiter.MockServerExtension;
|
||||
import org.mockserver.model.HttpRequest;
|
||||
import org.mockserver.model.HttpResponse;
|
||||
|
||||
import com.commafeed.CommaFeedApplication;
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.CommaFeedDropwizardAppExtension;
|
||||
import com.commafeed.frontend.model.Entries;
|
||||
import com.commafeed.frontend.model.request.SubscribeRequest;
|
||||
|
||||
import io.dropwizard.testing.ResourceHelpers;
|
||||
import io.dropwizard.testing.junit5.DropwizardAppExtension;
|
||||
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
|
||||
|
||||
@ExtendWith(DropwizardExtensionsSupport.class)
|
||||
@ExtendWith(MockServerExtension.class)
|
||||
class FeedIT {
|
||||
|
||||
private static final DropwizardAppExtension<CommaFeedConfiguration> EXT = new DropwizardAppExtension<CommaFeedConfiguration>(
|
||||
CommaFeedApplication.class, ResourceHelpers.resourceFilePath("config.test.yml")) {
|
||||
private static final CommaFeedDropwizardAppExtension EXT = new CommaFeedDropwizardAppExtension() {
|
||||
@Override
|
||||
protected JerseyClientBuilder clientBuilder() {
|
||||
HttpAuthenticationFeature feature = HttpAuthenticationFeature.basic("admin", "admin");
|
||||
|
||||
Reference in New Issue
Block a user