forked from Archives/Athou_commafeed
Compare commits
91 Commits
7.0.0
...
renovate/b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c425f5cf6a | ||
|
|
fc76d7e609 | ||
|
|
1b24bf33ed | ||
|
|
58ff378735 | ||
|
|
1cee04a233 | ||
|
|
ac11a0efb8 | ||
|
|
f2ea1e3f7a | ||
|
|
153970c146 | ||
|
|
c21287e642 | ||
|
|
284942a82e | ||
|
|
e796916e73 | ||
|
|
004db1762c | ||
|
|
8e05ba8820 | ||
|
|
f4f386b5e5 | ||
|
|
ecbf8bec23 | ||
|
|
5d8c09ccda | ||
|
|
47c5c3d8a0 | ||
|
|
3ddce16d5b | ||
|
|
acefdc44d9 | ||
|
|
d05c5b9d7f | ||
|
|
5d1237d1f4 | ||
|
|
6bac8631ac | ||
|
|
2c803327ad | ||
|
|
451ae5bc51 | ||
|
|
f41ce8c878 | ||
|
|
177c54c813 | ||
|
|
3765e32fd4 | ||
|
|
aab7a16d18 | ||
|
|
f2463af63c | ||
|
|
212c2c3b56 | ||
|
|
3ab00b0cdd | ||
|
|
fad85e9299 | ||
|
|
3cd5e203e2 | ||
|
|
7b081fa870 | ||
|
|
5ce22051d4 | ||
|
|
1e59489fa5 | ||
|
|
1e691b1255 | ||
|
|
61e1bef63f | ||
|
|
46dbb78fbf | ||
|
|
99890707b3 | ||
|
|
f54c841fdc | ||
|
|
e2a6009ee9 | ||
|
|
a34dd15040 | ||
|
|
24c934003d | ||
|
|
5300e1a245 | ||
|
|
90381c670b | ||
|
|
23edea93db | ||
|
|
a51712e363 | ||
|
|
4310e979e1 | ||
|
|
f690b76d87 | ||
|
|
ac29594b67 | ||
|
|
93f535bb87 | ||
|
|
076eb3cf42 | ||
|
|
7b2e0fffbd | ||
|
|
8eaab0dbc3 | ||
|
|
eaa5bc896e | ||
|
|
42d1db5fc3 | ||
|
|
78c017ddaf | ||
|
|
231551d743 | ||
|
|
d0984eaba7 | ||
|
|
15854a72d1 | ||
|
|
7fd2bf0eda | ||
|
|
6a10a2167e | ||
|
|
1ba99d255c | ||
|
|
ff1c2947b6 | ||
|
|
a690d2e0db | ||
|
|
a3df327396 | ||
|
|
ede0016d8e | ||
|
|
c19b091795 | ||
|
|
9e62c8b9f3 | ||
|
|
9f30dc181c | ||
|
|
37fe44f860 | ||
|
|
68aaab8467 | ||
|
|
b951ed1fcd | ||
|
|
9bd9dc568a | ||
|
|
29e4356fee | ||
|
|
0ed31eaa99 | ||
|
|
1e9869b217 | ||
|
|
78cfc2c827 | ||
|
|
a6e5a0d125 | ||
|
|
ea13aecd27 | ||
|
|
d838e8f28f | ||
|
|
c9a7b9e17c | ||
|
|
8fe2d0bc0e | ||
|
|
71e2f1e1e6 | ||
|
|
1ce9d1b9b2 | ||
|
|
b3d6ae467f | ||
|
|
da8d720dc4 | ||
|
|
824c38f8ce | ||
|
|
b0579a70d8 | ||
|
|
f9fe2d0976 |
1
.github/stale.yml
vendored
1
.github/stale.yml
vendored
@@ -7,6 +7,7 @@ exemptLabels:
|
||||
- pinned
|
||||
- security
|
||||
- enhancement
|
||||
- feature-request
|
||||
- bug
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: wontfix
|
||||
|
||||
30
.github/workflows/ci.yml
vendored
30
.github/workflows/ci.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
|
||||
# Setup
|
||||
- name: Set up GraalVM
|
||||
uses: graalvm/setup-graalvm@54b4f5a65c1a84b2fdfdc2078fe43df32819e4b1 # v1
|
||||
uses: graalvm/setup-graalvm@03e8abf916fd0e281b2efe7b2da3378bb0a1d085 # v1
|
||||
with:
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
distribution: "graalvm"
|
||||
@@ -67,14 +67,14 @@ jobs:
|
||||
|
||||
# Upload artifacts
|
||||
- name: Upload cross-platform app
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
if: matrix.os == 'ubuntu-latest' # we only need to upload the cross-platform artifact once per database
|
||||
with:
|
||||
name: commafeed-${{ matrix.database }}-jvm
|
||||
path: commafeed-server/target/commafeed-*.zip
|
||||
|
||||
- name: Upload native executable
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: commafeed-${{ matrix.database }}-${{ runner.os }}-${{ runner.arch }}
|
||||
path: commafeed-server/target/commafeed-*-runner*
|
||||
@@ -104,17 +104,17 @@ jobs:
|
||||
|
||||
# Setup
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
|
||||
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||
|
||||
- name: Install required packages
|
||||
run: sudo apt-get install -y rename unzip
|
||||
|
||||
# Prepare artifacts
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
pattern: commafeed-${{ matrix.database }}-*
|
||||
path: ./artifacts
|
||||
@@ -135,7 +135,7 @@ jobs:
|
||||
|
||||
# Docker
|
||||
- name: Login to Container Registry
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4
|
||||
if: ${{ env.DOCKERHUB_USERNAME != '' }}
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
@@ -143,7 +143,7 @@ jobs:
|
||||
|
||||
## build but don't push for PRs and renovate
|
||||
- name: Docker build - native
|
||||
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
|
||||
with:
|
||||
context: .
|
||||
file: commafeed-server/src/main/docker/Dockerfile.native
|
||||
@@ -151,7 +151,7 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64/v8
|
||||
|
||||
- name: Docker build - jvm
|
||||
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
|
||||
with:
|
||||
context: .
|
||||
file: commafeed-server/src/main/docker/Dockerfile.jvm
|
||||
@@ -160,7 +160,7 @@ jobs:
|
||||
|
||||
## build and push tag
|
||||
- name: Docker build and push tag - native
|
||||
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
with:
|
||||
context: .
|
||||
@@ -172,7 +172,7 @@ jobs:
|
||||
athou/commafeed:${{ github.ref_name }}-${{ matrix.database }}
|
||||
|
||||
- name: Docker build and push tag - jvm
|
||||
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
with:
|
||||
context: .
|
||||
@@ -185,7 +185,7 @@ jobs:
|
||||
|
||||
## build and push master
|
||||
- name: Docker build and push master - native
|
||||
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
|
||||
if: ${{ github.ref_name == 'master' }}
|
||||
with:
|
||||
context: .
|
||||
@@ -195,7 +195,7 @@ jobs:
|
||||
tags: athou/commafeed:master-${{ matrix.database }}
|
||||
|
||||
- name: Docker build and push master - jvm
|
||||
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
|
||||
if: ${{ github.ref_name == 'master' }}
|
||||
with:
|
||||
context: .
|
||||
@@ -220,7 +220,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
pattern: commafeed-*
|
||||
path: ./artifacts
|
||||
@@ -236,7 +236,7 @@ jobs:
|
||||
version: ${{ github.ref_name }}
|
||||
|
||||
- name: Create GitHub release
|
||||
uses: ncipollo/release-action@b7eabc95ff50cbeeedec83973935c8f306dfcd0b # v1
|
||||
uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1
|
||||
with:
|
||||
name: CommaFeed ${{ github.ref_name }}
|
||||
body: ${{ steps.changelog_reader.outputs.changes }}
|
||||
|
||||
2
.mvn/wrapper/maven-wrapper.properties
vendored
2
.mvn/wrapper/maven-wrapper.properties
vendored
@@ -1,3 +1,3 @@
|
||||
wrapperVersion=3.3.4
|
||||
distributionType=only-script
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.14/apache-maven-3.9.14-bin.zip
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.4.2/schema.json",
|
||||
"$schema": "https://biomejs.dev/schemas/2.4.8/schema.json",
|
||||
"formatter": {
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 4,
|
||||
|
||||
4040
commafeed-client/package-lock.json
generated
4040
commafeed-client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -18,19 +18,20 @@
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@fontsource/open-sans": "^5.2.7",
|
||||
"@lingui/core": "^5.9.1",
|
||||
"@lingui/react": "^5.9.1",
|
||||
"@mantine/core": "^8.3.15",
|
||||
"@mantine/form": "^8.3.15",
|
||||
"@mantine/hooks": "^8.3.15",
|
||||
"@mantine/modals": "^8.3.15",
|
||||
"@mantine/notifications": "^8.3.15",
|
||||
"@mantine/spotlight": "^8.3.15",
|
||||
"@lingui/core": "^5.9.3",
|
||||
"@lingui/react": "^5.9.3",
|
||||
"@mantine/core": "^8.3.16",
|
||||
"@mantine/form": "^8.3.16",
|
||||
"@mantine/hooks": "^8.3.16",
|
||||
"@mantine/modals": "^8.3.16",
|
||||
"@mantine/notifications": "^8.3.16",
|
||||
"@mantine/spotlight": "^8.3.16",
|
||||
"@monaco-editor/react": "^4.7.0",
|
||||
"@react-querybuilder/mantine": "^8.14.0",
|
||||
"@reduxjs/toolkit": "^2.11.2",
|
||||
"axios": "^1.13.5",
|
||||
"dayjs": "^1.11.19",
|
||||
"@rolldown/plugin-babel": "^0.2.2",
|
||||
"axios": "^1.13.6",
|
||||
"dayjs": "^1.11.20",
|
||||
"escape-string-regexp": "^5.0.0",
|
||||
"interweave": "^13.1.1",
|
||||
"monaco-editor": "^0.55.1",
|
||||
@@ -40,11 +41,11 @@
|
||||
"react-contexify": "^6.0.0",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-draggable": "^4.5.0",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-icons": "^5.6.0",
|
||||
"react-infinite-scroller": "^1.2.6",
|
||||
"react-querybuilder": "^8.14.0",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router-dom": "^7.13.0",
|
||||
"react-router-dom": "^7.13.1",
|
||||
"react-swipeable": "^7.0.2",
|
||||
"style-to-object": "^1.0.14",
|
||||
"throttle-debounce": "^5.0.2",
|
||||
@@ -53,10 +54,10 @@
|
||||
"websocket-heartbeat-js": "^1.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.4.2",
|
||||
"@lingui/babel-plugin-lingui-macro": "^5.9.1",
|
||||
"@lingui/cli": "^5.9.1",
|
||||
"@lingui/vite-plugin": "^5.9.1",
|
||||
"@biomejs/biome": "^2.4.8",
|
||||
"@lingui/babel-plugin-lingui-macro": "^5.9.3",
|
||||
"@lingui/cli": "^5.9.3",
|
||||
"@lingui/vite-plugin": "^5.9.3",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
@@ -66,16 +67,15 @@
|
||||
"@types/react-infinite-scroller": "^1.2.5",
|
||||
"@types/throttle-debounce": "^5.0.2",
|
||||
"@types/tinycon": "^0.6.7",
|
||||
"@vitejs/plugin-react": "^5.1.4",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"babel-plugin-react-compiler": "1.0.0",
|
||||
"husky": "^9.1.7",
|
||||
"jsdom": "^28.1.0",
|
||||
"lint-staged": "^16.2.7",
|
||||
"jsdom": "^29.0.0",
|
||||
"lint-staged": "^16.4.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.3.1",
|
||||
"vite": "^8.0.0",
|
||||
"vite-plugin-checker": "^0.12.0",
|
||||
"vite-tsconfig-paths": "^6.1.1",
|
||||
"vitest": "^4.0.18",
|
||||
"vitest": "^4.1.0",
|
||||
"yaml": "^2.8.2"
|
||||
},
|
||||
"overrides": {
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
|
||||
<properties>
|
||||
<!-- renovate: datasource=node-version depName=node -->
|
||||
<node.version>v24.13.1</node.version>
|
||||
<node.version>v24.14.0</node.version>
|
||||
<!-- renovate: datasource=npm depName=npm -->
|
||||
<npm.version>11.10.0</npm.version>
|
||||
<npm.version>11.11.1</npm.version>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
@@ -72,7 +72,7 @@
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>3.4.0</version>
|
||||
<version>3.5.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy web interface to resources</id>
|
||||
@@ -94,4 +94,49 @@
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<!-- This profile is used to kill the Biome process on Windows -->
|
||||
<!-- npm ci can fail if Biome is running (e.g., in the IDE) because it locks some files -->
|
||||
<profile>
|
||||
<id>kill-biome</id>
|
||||
<activation>
|
||||
<os>
|
||||
<family>Windows</family>
|
||||
</os>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<version>3.6.3</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>kill-biome</id>
|
||||
<phase>initialize</phase>
|
||||
<goals>
|
||||
<goal>exec</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<executable>taskkill</executable>
|
||||
<arguments>
|
||||
<argument>/F</argument>
|
||||
<argument>/IM</argument>
|
||||
<argument>biome.exe</argument>
|
||||
</arguments>
|
||||
<successCodes>
|
||||
<successCode>0</successCode>
|
||||
<!-- taskkill returns 128 if the process is not found, which is fine in this case -->
|
||||
<successCode>128</successCode>
|
||||
</successCodes>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
</project>
|
||||
@@ -1,5 +1,5 @@
|
||||
import { configureStore } from "@reduxjs/toolkit"
|
||||
import { type TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"
|
||||
import { shallowEqual, type TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"
|
||||
import { entriesSlice } from "@/app/entries/slice"
|
||||
import { redirectSlice } from "@/app/redirect/slice"
|
||||
import { serverSlice } from "@/app/server/slice"
|
||||
@@ -41,3 +41,4 @@ export type AppDispatch = typeof store.dispatch
|
||||
|
||||
export const useAppDispatch: () => AppDispatch = useDispatch
|
||||
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
|
||||
export const useShallowEqualAppSelector: TypedUseSelectorHook<RootState> = selector => useSelector(selector, shallowEqual)
|
||||
|
||||
@@ -6,7 +6,11 @@ const useStyles = tss.create(() => ({
|
||||
badge: {
|
||||
width: "3.2rem",
|
||||
// for some reason, mantine Badge has "cursor: 'default'"
|
||||
cursor: "pointer",
|
||||
cursor: "inherit",
|
||||
},
|
||||
indicator: {
|
||||
// ensure the indicator is not shown above the app header
|
||||
zIndex: 0,
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -23,7 +27,15 @@ export function UnreadCount(
|
||||
const count = props.unreadCount >= 10000 ? "10k+" : props.unreadCount
|
||||
return (
|
||||
<Tooltip label={props.unreadCount} disabled={props.unreadCount === count} openDelay={Constants.tooltip.delay}>
|
||||
<Indicator disabled={!props.showIndicator} size={4} offset={10} position="middle-start">
|
||||
<Indicator
|
||||
disabled={!props.showIndicator}
|
||||
size={4}
|
||||
offset={10}
|
||||
position="middle-start"
|
||||
classNames={{
|
||||
indicator: classes.indicator,
|
||||
}}
|
||||
>
|
||||
<Badge className={`${classes.badge} cf-badge`} variant="light" fullWidth>
|
||||
{count}
|
||||
</Badge>
|
||||
|
||||
@@ -8,9 +8,11 @@ import { Constants } from "@/app/constants"
|
||||
import type { EntrySourceType } from "@/app/entries/slice"
|
||||
import { loadEntries } from "@/app/entries/thunks"
|
||||
import { redirectToCategoryDetails, redirectToFeedDetails, redirectToTagDetails } from "@/app/redirect/thunks"
|
||||
import { useAppDispatch, useAppSelector } from "@/app/store"
|
||||
import { flattenCategoryTree } from "@/app/utils"
|
||||
import { useAppDispatch, useAppSelector, useShallowEqualAppSelector } from "@/app/store"
|
||||
import { categoryHasNewEntries, categoryUnreadCount, flattenCategoryTree } from "@/app/utils"
|
||||
import { FeedEntries } from "@/components/content/FeedEntries"
|
||||
import { UnreadCount } from "@/components/sidebar/UnreadCount"
|
||||
import { useMobile } from "@/hooks/useMobile"
|
||||
import { tss } from "@/tss"
|
||||
|
||||
function NoSubscriptionHelp() {
|
||||
@@ -33,6 +35,12 @@ const useStyles = tss.create(() => ({
|
||||
sourceWebsiteLink: {
|
||||
color: "inherit",
|
||||
textDecoration: "none",
|
||||
overflow: "hidden",
|
||||
},
|
||||
titleText: {
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -48,6 +56,33 @@ export function FeedEntriesPage(props: Readonly<FeedEntriesPageProps>) {
|
||||
const sourceLabel = useAppSelector(state => state.entries.sourceLabel)
|
||||
const sourceWebsiteUrl = useAppSelector(state => state.entries.sourceWebsiteUrl)
|
||||
const hasMore = useAppSelector(state => state.entries.hasMore)
|
||||
const mobile = useMobile()
|
||||
const sidebarVisible = useAppSelector(state => state.tree.sidebarVisible)
|
||||
const { unreadCount, hasNewEntries } = useShallowEqualAppSelector(state => {
|
||||
const root = state.tree.rootCategory
|
||||
if (!root) return { unreadCount: 0, hasNewEntries: false }
|
||||
|
||||
if (props.sourceType === "category") {
|
||||
const category = id === Constants.categories.all.id ? root : flattenCategoryTree(root).find(c => c.id === id)
|
||||
return {
|
||||
unreadCount: categoryUnreadCount(category),
|
||||
hasNewEntries: categoryHasNewEntries(category),
|
||||
}
|
||||
}
|
||||
|
||||
if (props.sourceType === "feed") {
|
||||
const feed = flattenCategoryTree(root)
|
||||
.flatMap(c => c.feeds)
|
||||
.find(f => f.id === +id)
|
||||
return {
|
||||
unreadCount: feed?.unread ?? 0,
|
||||
hasNewEntries: !!feed?.hasNewEntries,
|
||||
}
|
||||
}
|
||||
|
||||
return { unreadCount: 0, hasNewEntries: false }
|
||||
})
|
||||
const showUnreadCount = mobile || !sidebarVisible
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
let title: React.ReactNode = sourceLabel
|
||||
@@ -89,16 +124,23 @@ export function FeedEntriesPage(props: Readonly<FeedEntriesPageProps>) {
|
||||
return (
|
||||
// add some room at the bottom of the page in order to be able to scroll the current entry at the top of the page when expanding
|
||||
<Box mb={viewport.height * 0.7}>
|
||||
<Group gap="xl" className="cf-entries-title">
|
||||
<Group className="cf-entries-title" wrap="nowrap">
|
||||
{sourceWebsiteUrl && (
|
||||
<a href={sourceWebsiteUrl} target="_blank" rel="noreferrer" className={classes.sourceWebsiteLink}>
|
||||
<Title order={3}>{title}</Title>
|
||||
<Title order={3} className={classes.titleText}>
|
||||
{title}
|
||||
</Title>
|
||||
</a>
|
||||
)}
|
||||
{!sourceWebsiteUrl && <Title order={3}>{title}</Title>}
|
||||
{!sourceWebsiteUrl && (
|
||||
<Title order={3} className={classes.titleText}>
|
||||
{title}
|
||||
</Title>
|
||||
)}
|
||||
<ActionIcon onClick={titleClicked} variant="subtle" color={theme.primaryColor}>
|
||||
<TbEdit size={18} />
|
||||
</ActionIcon>
|
||||
{showUnreadCount && <UnreadCount unreadCount={unreadCount} showIndicator={hasNewEntries} />}
|
||||
</Group>
|
||||
|
||||
<FeedEntries />
|
||||
|
||||
@@ -1,25 +1,17 @@
|
||||
import { lingui } from "@lingui/vite-plugin"
|
||||
import react from "@vitejs/plugin-react"
|
||||
import babel from "@rolldown/plugin-babel"
|
||||
import react, { reactCompilerPreset } from "@vitejs/plugin-react"
|
||||
import { defineConfig } from "vite"
|
||||
import checker from "vite-plugin-checker"
|
||||
import tsconfigPaths from "vite-tsconfig-paths"
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig(() => ({
|
||||
plugins: [
|
||||
react({
|
||||
babel: {
|
||||
plugins: [
|
||||
// support for lingui macros
|
||||
// needs to be before the react compiler plugin
|
||||
"@lingui/babel-plugin-lingui-macro",
|
||||
// react compiler
|
||||
["babel-plugin-react-compiler", { target: "19" }],
|
||||
],
|
||||
},
|
||||
react(),
|
||||
babel({
|
||||
presets: [reactCompilerPreset()],
|
||||
plugins: ["@lingui/babel-plugin-lingui-macro"],
|
||||
}),
|
||||
lingui(),
|
||||
tsconfigPaths(),
|
||||
checker({
|
||||
typescript: true,
|
||||
biome: {
|
||||
@@ -43,22 +35,32 @@ export default defineConfig(() => ({
|
||||
"/logout": "http://localhost:8083",
|
||||
},
|
||||
},
|
||||
build: {
|
||||
chunkSizeWarningLimit: 3500,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: id => {
|
||||
// output mantine as its own chunk because it is quite large
|
||||
if (id.includes("@mantine")) {
|
||||
return "mantine"
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
tsconfigPaths: true,
|
||||
},
|
||||
legacy: {
|
||||
// required for websocket-heartbeat-js
|
||||
inconsistentCjsInterop: true,
|
||||
},
|
||||
test: {
|
||||
environment: "jsdom",
|
||||
globals: true,
|
||||
setupFiles: "./src/setupTests.ts",
|
||||
},
|
||||
build: {
|
||||
chunkSizeWarningLimit: 4000,
|
||||
rolldownOptions: {
|
||||
output: {
|
||||
codeSplitting: {
|
||||
groups: [
|
||||
// output mantine as its own chunk because it is quite large
|
||||
{
|
||||
name: "mantine",
|
||||
test: "@mantine",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<packaging>quarkus</packaging>
|
||||
|
||||
<properties>
|
||||
<quarkus.version>3.31.4</quarkus.version>
|
||||
<quarkus.version>3.32.4</quarkus.version>
|
||||
<querydsl.version>7.1</querydsl.version>
|
||||
<rome.version>2.1.0</rome.version>
|
||||
|
||||
@@ -29,6 +29,12 @@
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<!-- the quarkus bom declares a dependency on an old version of protobuf, we need to override it for cel-java -->
|
||||
<dependency>
|
||||
<groupId>com.google.protobuf</groupId>
|
||||
<artifactId>protobuf-java</artifactId>
|
||||
<version>4.34.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
@@ -237,7 +243,7 @@
|
||||
<dependency>
|
||||
<groupId>com.puppycrawl.tools</groupId>
|
||||
<artifactId>checkstyle</artifactId>
|
||||
<version>13.2.0</version>
|
||||
<version>13.3.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<executions>
|
||||
@@ -266,7 +272,7 @@
|
||||
<plugin>
|
||||
<groupId>com.diffplug.spotless</groupId>
|
||||
<artifactId>spotless-maven-plugin</artifactId>
|
||||
<version>3.2.1</version>
|
||||
<version>3.4.0</version>
|
||||
<?m2e ignore?>
|
||||
<executions>
|
||||
<execution>
|
||||
@@ -302,7 +308,7 @@
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.42</version>
|
||||
<version>1.18.44</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -393,7 +399,7 @@
|
||||
<dependency>
|
||||
<groupId>dev.cel</groupId>
|
||||
<artifactId>cel</artifactId>
|
||||
<version>0.11.1</version>
|
||||
<version>0.12.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.passay</groupId>
|
||||
@@ -428,7 +434,7 @@
|
||||
<dependency>
|
||||
<groupId>com.ibm.icu</groupId>
|
||||
<artifactId>icu4j</artifactId>
|
||||
<version>78.2</version>
|
||||
<version>78.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sourceforge.cssparser</groupId>
|
||||
@@ -448,7 +454,7 @@
|
||||
<dependency>
|
||||
<groupId>io.github.hakky54</groupId>
|
||||
<artifactId>ayza-for-apache5</artifactId>
|
||||
<version>10.0.3</version>
|
||||
<version>10.0.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.brotli</groupId>
|
||||
@@ -465,7 +471,7 @@
|
||||
<dependency>
|
||||
<groupId>io.quarkiverse.playwright</groupId>
|
||||
<artifactId>quarkus-playwright</artifactId>
|
||||
<version>2.3.2</version>
|
||||
<version>2.3.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM debian:13.3@sha256:2c91e484d93f0830a7e05a2b9d92a7b102be7cab562198b984a84fdbc7806d91
|
||||
FROM debian:13.4@sha256:55a15a112b42be10bfc8092fcc40b6748dc236f7ef46a358d9392b339e9d60e8
|
||||
ARG TARGETARCH
|
||||
|
||||
EXPOSE 8082
|
||||
|
||||
@@ -10,7 +10,9 @@ import com.commafeed.security.password.PasswordConstraintValidator;
|
||||
import io.quarkus.runtime.ShutdownEvent;
|
||||
import io.quarkus.runtime.StartupEvent;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Singleton
|
||||
@RequiredArgsConstructor
|
||||
public class CommaFeedApplication {
|
||||
@@ -20,6 +22,8 @@ public class CommaFeedApplication {
|
||||
private final CommaFeedConfiguration config;
|
||||
|
||||
public void start(@Observes StartupEvent ev) {
|
||||
log.info("starting up...");
|
||||
|
||||
PasswordConstraintValidator.setMinimumPasswordLength(config.users().minimumPasswordLength());
|
||||
|
||||
feedRefreshEngine.start();
|
||||
@@ -27,6 +31,8 @@ public class CommaFeedApplication {
|
||||
}
|
||||
|
||||
public void stop(@Observes ShutdownEvent ev) {
|
||||
log.info("shutting down...");
|
||||
|
||||
feedRefreshEngine.stop();
|
||||
taskScheduler.stop();
|
||||
}
|
||||
|
||||
@@ -92,6 +92,12 @@ public interface CommaFeedConfiguration {
|
||||
@ConfigDocSection
|
||||
Websocket websocket();
|
||||
|
||||
/**
|
||||
* Duration to wait for the feed refresh engine and the task scheduler to stop when the application is shutting down.
|
||||
*/
|
||||
@WithDefault("2s")
|
||||
Duration shutdownTimeout();
|
||||
|
||||
interface HttpClient {
|
||||
/**
|
||||
* User-Agent string that will be used by the http client, leave empty for the default one.
|
||||
@@ -144,7 +150,7 @@ public interface CommaFeedConfiguration {
|
||||
* Prevent access to local addresses to mitigate server-side request forgery (SSRF) attacks, which could potentially expose internal
|
||||
* resources.
|
||||
*
|
||||
* You may want to enable this if you host a public instance of CommaFeed with regisration open.
|
||||
* You may want to enable this if you host a public instance of CommaFeed with registrations open.
|
||||
*/
|
||||
@WithDefault("false")
|
||||
boolean blockLocalAddresses();
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.commafeed.backend.model.AbstractModel;
|
||||
import com.commafeed.backend.model.Feed;
|
||||
import com.commafeed.backend.model.FeedEntry;
|
||||
import com.commafeed.backend.model.FeedSubscription;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@@ -41,12 +42,12 @@ public class FeedRefreshEngine {
|
||||
|
||||
private final BlockingDeque<Feed> queue;
|
||||
|
||||
private final ExecutorService feedProcessingLoopExecutor;
|
||||
private final ExecutorService refillLoopExecutor;
|
||||
private final ExecutorService refillExecutor;
|
||||
private final ThreadPoolExecutor workerExecutor;
|
||||
private final ThreadPoolExecutor databaseUpdaterExecutor;
|
||||
private final ThreadPoolExecutor notifierExecutor;
|
||||
private ExecutorService feedProcessingLoopExecutor;
|
||||
private ExecutorService refillLoopExecutor;
|
||||
private ThreadPoolExecutor refillExecutor;
|
||||
private ThreadPoolExecutor workerExecutor;
|
||||
private ThreadPoolExecutor databaseUpdaterExecutor;
|
||||
private ThreadPoolExecutor notifierExecutor;
|
||||
|
||||
public FeedRefreshEngine(UnitOfWork unitOfWork, FeedDAO feedDAO, FeedRefreshWorker worker, FeedRefreshUpdater updater,
|
||||
FeedUpdateNotifier notifier, CommaFeedConfiguration config, MetricRegistry metrics) {
|
||||
@@ -60,6 +61,15 @@ public class FeedRefreshEngine {
|
||||
|
||||
this.queue = new LinkedBlockingDeque<>();
|
||||
|
||||
metrics.register(MetricRegistry.name(getClass(), "queue", "size"), (Gauge<Integer>) queue::size);
|
||||
metrics.register(MetricRegistry.name(getClass(), "worker", "active"), (Gauge<Integer>) () -> workerExecutor.getActiveCount());
|
||||
metrics.register(MetricRegistry.name(getClass(), "updater", "active"),
|
||||
(Gauge<Integer>) () -> databaseUpdaterExecutor.getActiveCount());
|
||||
metrics.register(MetricRegistry.name(getClass(), "notifier", "active"), (Gauge<Integer>) () -> notifierExecutor.getActiveCount());
|
||||
metrics.register(MetricRegistry.name(getClass(), "notifier", "queue"), (Gauge<Integer>) () -> notifierExecutor.getQueue().size());
|
||||
}
|
||||
|
||||
private void createExecutors() {
|
||||
this.feedProcessingLoopExecutor = Executors.newSingleThreadExecutor();
|
||||
this.refillLoopExecutor = Executors.newSingleThreadExecutor();
|
||||
this.refillExecutor = newDiscardingSingleThreadExecutorService();
|
||||
@@ -67,15 +77,10 @@ public class FeedRefreshEngine {
|
||||
this.databaseUpdaterExecutor = newBlockingExecutorService(config.feedRefresh().databaseThreads());
|
||||
this.notifierExecutor = newDiscardingExecutorService(config.pushNotifications().threads(),
|
||||
config.pushNotifications().queueCapacity());
|
||||
|
||||
metrics.register(MetricRegistry.name(getClass(), "queue", "size"), (Gauge<Integer>) queue::size);
|
||||
metrics.register(MetricRegistry.name(getClass(), "worker", "active"), (Gauge<Integer>) workerExecutor::getActiveCount);
|
||||
metrics.register(MetricRegistry.name(getClass(), "updater", "active"), (Gauge<Integer>) databaseUpdaterExecutor::getActiveCount);
|
||||
metrics.register(MetricRegistry.name(getClass(), "notifier", "active"), (Gauge<Integer>) notifierExecutor::getActiveCount);
|
||||
metrics.register(MetricRegistry.name(getClass(), "notifier", "queue"), (Gauge<Integer>) () -> notifierExecutor.getQueue().size());
|
||||
}
|
||||
|
||||
public void start() {
|
||||
createExecutors();
|
||||
startFeedProcessingLoop();
|
||||
startRefillLoop();
|
||||
}
|
||||
@@ -197,12 +202,14 @@ public class FeedRefreshEngine {
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
this.feedProcessingLoopExecutor.shutdownNow();
|
||||
this.refillLoopExecutor.shutdownNow();
|
||||
this.refillExecutor.shutdownNow();
|
||||
this.workerExecutor.shutdownNow();
|
||||
this.databaseUpdaterExecutor.shutdownNow();
|
||||
this.notifierExecutor.shutdownNow();
|
||||
MoreExecutors.shutdownAndAwaitTermination(this.feedProcessingLoopExecutor, config.shutdownTimeout());
|
||||
MoreExecutors.shutdownAndAwaitTermination(this.refillLoopExecutor, config.shutdownTimeout());
|
||||
MoreExecutors.shutdownAndAwaitTermination(this.refillExecutor, config.shutdownTimeout());
|
||||
MoreExecutors.shutdownAndAwaitTermination(this.workerExecutor, config.shutdownTimeout());
|
||||
MoreExecutors.shutdownAndAwaitTermination(this.databaseUpdaterExecutor, config.shutdownTimeout());
|
||||
MoreExecutors.shutdownAndAwaitTermination(this.notifierExecutor, config.shutdownTimeout());
|
||||
|
||||
queue.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -54,10 +54,20 @@ public class DatabaseCleaningService {
|
||||
int deleted;
|
||||
long entriesTotal = 0;
|
||||
do {
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
log.info("interrupted, stopping cleanup of feeds without subscriptions");
|
||||
return;
|
||||
}
|
||||
|
||||
List<Feed> feeds = unitOfWork.call(() -> feedDAO.findWithoutSubscriptions(1));
|
||||
for (Feed feed : feeds) {
|
||||
long entriesDeleted;
|
||||
do {
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
log.info("interrupted, stopping cleanup of feeds without subscriptions");
|
||||
return;
|
||||
}
|
||||
|
||||
entriesDeleted = unitOfWork.call(() -> feedEntryDAO.delete(feed.getId(), batchSize));
|
||||
entriesDeletedMeter.mark(entriesDeleted);
|
||||
entriesTotal += entriesDeleted;
|
||||
@@ -76,6 +86,11 @@ public class DatabaseCleaningService {
|
||||
long total = 0;
|
||||
long deleted;
|
||||
do {
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
log.info("interrupted, stopping cleanup of contents without entries");
|
||||
return;
|
||||
}
|
||||
|
||||
deleted = unitOfWork.call(() -> feedEntryContentDAO.deleteWithoutEntries(batchSize));
|
||||
total += deleted;
|
||||
log.debug("removed {} contents without entries", total);
|
||||
@@ -87,6 +102,11 @@ public class DatabaseCleaningService {
|
||||
log.info("cleaning entries exceeding feed capacity");
|
||||
long total = 0;
|
||||
while (true) {
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
log.info("interrupted, stopping cleanup of entries exceeding feed capacity");
|
||||
return;
|
||||
}
|
||||
|
||||
List<FeedCapacity> feeds = unitOfWork
|
||||
.call(() -> feedEntryDAO.findFeedsExceedingCapacity(maxFeedCapacity, batchSize, keepStarredEntries));
|
||||
if (feeds.isEmpty()) {
|
||||
@@ -97,6 +117,11 @@ public class DatabaseCleaningService {
|
||||
long remaining = feed.capacity() - maxFeedCapacity;
|
||||
int deleted;
|
||||
do {
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
log.info("interrupted, stopping cleanup of entries exceeding feed capacity");
|
||||
return;
|
||||
}
|
||||
|
||||
final long rem = remaining;
|
||||
deleted = unitOfWork.call(() -> feedEntryDAO.deleteOldEntries(feed.id(), Math.min(batchSize, rem), keepStarredEntries));
|
||||
entriesDeletedMeter.mark(deleted);
|
||||
@@ -114,6 +139,11 @@ public class DatabaseCleaningService {
|
||||
long total = 0;
|
||||
long deleted;
|
||||
do {
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
log.info("interrupted, stopping cleanup of old entries");
|
||||
return;
|
||||
}
|
||||
|
||||
deleted = unitOfWork.call(() -> feedEntryDAO.deleteEntriesOlderThan(olderThan, batchSize, keepStarredEntries));
|
||||
entriesDeletedMeter.mark(deleted);
|
||||
total += deleted;
|
||||
@@ -127,6 +157,11 @@ public class DatabaseCleaningService {
|
||||
long total = 0;
|
||||
long deleted;
|
||||
do {
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
log.info("interrupted, stopping cleanup of old read statuses");
|
||||
return;
|
||||
}
|
||||
|
||||
deleted = unitOfWork.call(() -> feedEntryStatusDAO.deleteOldStatuses(olderThan, batchSize));
|
||||
total += deleted;
|
||||
log.debug("removed {} old read statuses", total);
|
||||
@@ -139,6 +174,11 @@ public class DatabaseCleaningService {
|
||||
long total = 0;
|
||||
long marked;
|
||||
do {
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
log.info("interrupted, stopping marking entries as read");
|
||||
return;
|
||||
}
|
||||
|
||||
marked = unitOfWork.call(() -> feedEntryStatusDAO.autoMarkAsRead(batchSize));
|
||||
total += marked;
|
||||
log.debug("marked {} entries as read", total);
|
||||
|
||||
@@ -23,8 +23,8 @@ public abstract class ScheduledTask {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
};
|
||||
log.info("registering task {} for execution every {} {}, starting in {} {}", getClass().getSimpleName(), getPeriod(), getTimeUnit(),
|
||||
getInitialDelay(), getTimeUnit());
|
||||
log.debug("registering task {} for execution every {} {}, starting in {} {}", getClass().getSimpleName(), getPeriod(),
|
||||
getTimeUnit(), getInitialDelay(), getTimeUnit());
|
||||
executor.scheduleWithFixedDelay(runnable, getInitialDelay(), getPeriod(), getTimeUnit());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,24 +6,32 @@ import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
import io.quarkus.arc.All;
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
|
||||
import io.quarkus.arc.All;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Singleton
|
||||
public class TaskScheduler {
|
||||
|
||||
private final List<ScheduledTask> tasks;
|
||||
private final ScheduledExecutorService executor;
|
||||
private final CommaFeedConfiguration config;
|
||||
|
||||
public TaskScheduler(@All List<ScheduledTask> tasks) {
|
||||
private ScheduledExecutorService executor;
|
||||
|
||||
public TaskScheduler(@All List<ScheduledTask> tasks, CommaFeedConfiguration config) {
|
||||
this.tasks = tasks;
|
||||
this.executor = Executors.newScheduledThreadPool(tasks.size());
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
tasks.forEach(task -> task.register(executor));
|
||||
this.executor = Executors.newScheduledThreadPool(tasks.size());
|
||||
this.tasks.forEach(task -> task.register(executor));
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
executor.shutdownNow();
|
||||
MoreExecutors.shutdownAndAwaitTermination(executor, config.shutdownTimeout());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ quarkus.native.additional-build-args=-H:PageSize=65536
|
||||
# test profile overrides
|
||||
%test.quarkus.log.category."org.mockserver".level=WARN
|
||||
%test.quarkus.log.category."liquibase".level=WARN
|
||||
%test.commafeed.shutdown-timeout=100ms
|
||||
%test.commafeed.users.create-demo-account=true
|
||||
%test.commafeed.users.allow-registrations=true
|
||||
%test.commafeed.password-recovery-enabled=true
|
||||
|
||||
@@ -6,6 +6,8 @@ import jakarta.persistence.EntityManager;
|
||||
import org.hibernate.Session;
|
||||
import org.kohsuke.MetaInfServices;
|
||||
|
||||
import io.quarkus.runtime.ShutdownEvent;
|
||||
import io.quarkus.runtime.StartupEvent;
|
||||
import io.quarkus.test.junit.callback.QuarkusTestBeforeEachCallback;
|
||||
import io.quarkus.test.junit.callback.QuarkusTestMethodContext;
|
||||
|
||||
@@ -17,12 +19,17 @@ public class DatabaseReset implements QuarkusTestBeforeEachCallback {
|
||||
|
||||
@Override
|
||||
public void beforeEach(QuarkusTestMethodContext context) {
|
||||
CDI.current()
|
||||
.select(EntityManager.class)
|
||||
.get()
|
||||
.unwrap(Session.class)
|
||||
.getSessionFactory()
|
||||
.getSchemaManager()
|
||||
.truncateMappedObjects();
|
||||
// stop the application to make sure that there are no active transactions when we truncate the tables
|
||||
getBean(CommaFeedApplication.class).stop(new ShutdownEvent());
|
||||
|
||||
// truncate all tables so that we have a clean slate for the next test
|
||||
getBean(EntityManager.class).unwrap(Session.class).getSessionFactory().getSchemaManager().truncateMappedObjects();
|
||||
|
||||
// restart the application
|
||||
getBean(CommaFeedApplication.class).start(new StartupEvent());
|
||||
}
|
||||
|
||||
private static <T> T getBean(Class<T> clazz) {
|
||||
return CDI.current().select(clazz).get();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user