├── .dockerignore ├── .editorconfig ├── .env.sample ├── .gitignore ├── .gitmodules ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── dataSources.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── jarRepositories.xml ├── jsLinters │ └── eslint.xml ├── misc.xml ├── modules │ ├── packages │ │ ├── server-graphql │ │ │ └── rich-multiproject.packages.server-graphql.iml │ │ ├── server-rich-rss │ │ │ └── rich-multiproject.packages.server-rich-rss.iml │ │ └── server-rss-kotlin │ │ │ └── rich-multiproject.packages.server-rss-kotlin.test.iml │ └── server-rich-graph.iml └── vcs.xml ├── .run └── FeedlessApplication.run.xml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── README.mustache.md ├── build.gradle.kts ├── chrome.json ├── docker-compose.legacy.yml ├── docker-compose.plausible.yml ├── docker-compose.prod.yml ├── docker-compose.yml ├── docker ├── aio-with-chromium │ ├── Dockerfile │ └── docker-aio-entrypoint.sh ├── aio-with-web │ └── Dockerfile ├── app │ └── app-config.json ├── ingress │ ├── Dockerfile │ ├── entrypoint.sh │ └── nginx-template.conf ├── legacy │ └── app-config.json ├── loki │ └── loki.yml ├── nitter │ └── nitter.conf ├── plausible │ └── plausible-conf.env ├── prometheus │ └── prometheus.yml ├── promtail │ └── promtail.yml └── your-license.key ├── docs ├── authentication.md ├── blog │ ├── analytics.md │ ├── cd.md │ ├── cofounder.md │ ├── complexity.md │ ├── coroutines.md │ ├── idea.md │ ├── monitoring.md │ ├── monolith.md │ ├── motivation.md │ ├── no-cloud.md │ ├── personal-roadmap.md │ ├── sideproject.md │ ├── testing-hard.md │ └── throttling.md ├── development.md ├── flow.dia ├── from-archive-org │ ├── archive-accumulator.ts │ └── package.json ├── ideas.md ├── inspiration │ ├── feed rate.png │ └── reddit.png ├── license-dump.md ├── opmls │ ├── engineering-feeds-individuals.opml.xml │ ├── engineering_blogs.opml.xml │ ├── feedly.opml.xml │ ├── fever-security.opml.xml │ └── test-sample.opml.xml ├── reader.png ├── reads │ ├── HackerNews Ranking Algorithm_ How would you have done it_.html │ ├── HackerNews Ranking Algorithm_ How would you have done it__files │ │ ├── 027b57bb-36f0-4968-ad6b-a86063674a79_6412x3328(1).jpg │ │ ├── 027b57bb-36f0-4968-ad6b-a86063674a79_6412x3328.jpg │ │ ├── 0952ad26-47a3-4213-85c0-29c8eb1979ce_945x945.jpg │ │ ├── 11d75d52-c322-430a-b382-636457c898b3_2208x1984.png │ │ ├── 66dd34c6-7d29-4428-a8d5-f41f8ffe4a91_400x400.jpg │ │ ├── 6c2ff3e3828e4017b7faf7b63e24cdf8.min.js │ │ ├── 6ca3e7da-886c-438c-a46a-e82686c5af48_1916x984(1).jpg │ │ ├── 6ca3e7da-886c-438c-a46a-e82686c5af48_1916x984.jpg │ │ ├── 80c8d221-3e4f-423f-bed5-929b04d8b301_4000x4000(1).jpg │ │ ├── 80c8d221-3e4f-423f-bed5-929b04d8b301_4000x4000.jpg │ │ ├── 9ca94043-1f3f-41fe-8749-f7e2b89ccf2b_144x144.jpg │ │ ├── AlertDialog-e3a605af.css │ │ ├── Attachments-94e0a9c8.css │ │ ├── AuditionPlayer-5637d633.css │ │ ├── Banner-cc32eba2.css │ │ ├── BetaTag-8b30a2bc.css │ │ ├── ChatAppUpsell-41a3562f.css │ │ ├── CommentBody-f90dd6a2.css │ │ ├── CommunityPostView-2bcc1970.css │ │ ├── CookieConsentFooter-0d9aca19.css │ │ ├── Divider-f25512a6.css │ │ ├── DropzoneOverlay-287820b3.css │ │ ├── EmailTypoHandler-1f1ddc43.css │ │ ├── Field-cea155ef.css │ │ ├── FilePicker-ceb0fe25.css │ │ ├── FlexBox-c1df079b.css │ │ ├── FollowPrompt-a56b28dc.css │ │ ├── HoverCard-41ea4b50.css │ │ ├── ImageViewerModal-5f70a364.css │ │ ├── IntroPopup-96bfa55d.css │ │ ├── Logo-f0548763.css │ │ ├── Menu-9123dbf7.css │ │ ├── Modal-faa641cb.css │ │ ├── NavbarUserWidget-a83cba36.css │ │ ├── NoteComposer-6d7a578b.css │ │ ├── PodcastTranscriptTab-22166d76.css │ │ ├── ProfileHoverCard-0f3ebd95.css │ │ ├── ProfileSetupToast-60e0a843.css │ │ ├── Progress-f0f26f86.css │ │ ├── PubAccentTheme-4d7e77dd.css │ │ ├── PublicationSearch-24228c5a.css │ │ ├── Radio-148681a8.css │ │ ├── Recipe-53df0851.css │ │ ├── RewardBox-5297e7a9.css │ │ ├── SearchModal-bbccf91b.css │ │ ├── Select-cd38999e.css │ │ ├── ShareAssetButtons-9cd347fc.css │ │ ├── ShareableImageModal-6a735338.css │ │ ├── TabBar-a61ccfca.css │ │ ├── TextEditor-9120b69d.css │ │ ├── TextLink-f399b628.css │ │ ├── Tooltip-d0af0cf2.css │ │ ├── UserBadge-fb9c174e.css │ │ ├── VideoVerticalMenu-89d6e55f.css │ │ ├── _baseEach-514c6e37.css │ │ ├── _defineProperty-213a01f6.css │ │ ├── a19b9ff9-60b4-4a6e-ae91-95c701c44c7b_1184x776.jpg │ │ ├── app_install_modal-1b80d39d.css │ │ ├── autocomplete_results-a5e8d737.css │ │ ├── bb5f775e-a62c-48ea-8189-f74b74c84c65_1024x1024.jpg │ │ ├── beacon.min.js │ │ ├── entry-3c13f6d5.css │ │ ├── f0866902-0b32-48e4-b46c-bc36002e518c_144x144(1).jpg │ │ ├── f0866902-0b32-48e4-b46c-bc36002e518c_144x144(2).jpg │ │ ├── f0866902-0b32-48e4-b46c-bc36002e518c_144x144.jpg │ │ ├── f1ea07d3-b3d8-44a8-8b2b-4a8f50175ff3_3088x2316.jpg │ │ ├── facepile-2e21e967.css │ │ ├── free_email_form-25f68dbb.css │ │ ├── homepage_hooks-c28ee672.css │ │ ├── index-bee825e1.css │ │ ├── js │ │ ├── logged-out.png │ │ ├── main-514d96ed.js │ │ ├── main.744438bb3c1e083ebc7a.css │ │ ├── mention-e43b25a5.css │ │ ├── newsletter_item_list-03468b3d.css │ │ ├── overflow_menu-db9a534e.css │ │ ├── podcast_apps-184969d7.css │ │ ├── post-cfd9b3d5.css │ │ ├── recommend_linked_publication_modal-722617bb.css │ │ ├── setup_all_podcasts-b481ea39.css │ │ ├── tex-mml-chtml.js │ │ ├── user-51b02764.css │ │ ├── user_indicator-c1a5a8fb.css │ │ └── video_editor-d2e65442.css │ ├── How to make a good ID in Atom [dive into mark].html │ ├── How to make a good ID in Atom [dive into mark]_files │ │ ├── analytics.js │ │ ├── banner-styles.css │ │ ├── donate.html │ │ ├── donate_data │ │ │ ├── analytics.js │ │ │ ├── banner.css │ │ │ ├── donation-banner.js │ │ │ ├── jquery-1.js │ │ │ ├── js.js │ │ │ └── polyfill.js │ │ ├── iconochive.css │ │ ├── playback.js │ │ └── wombat.js │ ├── Improving the Hacker News Ranking Algorithm _ Felix Dietze’s blog.html │ ├── Improving the Hacker News Ranking Algorithm _ Felix Dietze’s blog_files │ │ ├── all.js │ │ ├── count.js │ │ ├── feedback-loop-balanced.svg │ │ ├── feedback-loop.svg │ │ ├── hacker-news-downvotes-quality-scatterplot.png │ │ ├── hacker-news-normalized-upvote-quality-scatterplot.png │ │ ├── hacker-news-upvote-quality-scatterplot.png │ │ ├── hacker-news-views-as-downvotes-quality-scatterplot.png │ │ ├── normalize.css │ │ ├── open-color.css │ │ ├── styles.css │ │ ├── submission_and_votes_distribution_over_score_intervals.svg │ │ └── votehist.svg │ ├── Introduction to Atom.html │ ├── Introduction to Atom_files │ │ ├── borderBL.gif │ │ ├── borderBR.gif │ │ ├── borderTL.gif │ │ ├── borderTR.gif │ │ └── w3c.png │ ├── The myth of RSS compatibility [dive into mark].html │ ├── The myth of RSS compatibility [dive into mark]_files │ │ ├── 0596101651.jpg │ │ ├── analytics.js │ │ ├── banner-styles.css │ │ ├── donate.html │ │ ├── donate_data │ │ │ ├── analytics.js │ │ │ ├── banner.css │ │ │ ├── donation-banner.js │ │ │ ├── jquery-1.js │ │ │ ├── js.js │ │ │ └── polyfill.js │ │ ├── iconochive.css │ │ ├── playback.js │ │ ├── show_ads.js │ │ └── wombat.js │ ├── Tricks to Monetize your Side Projects – Jeremy Boyd.html │ ├── Tricks to Monetize your Side Projects – Jeremy Boyd_files │ │ ├── 8c64c40d7162ecf436a965d5399d5422.jpeg │ │ ├── 8c64c40d7162ecf436a965d5399d5422_002.jpeg │ │ ├── 8c64c40d7162ecf436a965d5399d5422_003.jpeg │ │ ├── 8c64c40d7162ecf436a965d5399d5422_004.jpeg │ │ ├── 9d173ac02601e81a78d9980ff6ad50cf.jpeg │ │ ├── 9d173ac02601e81a78d9980ff6ad50cf_002.jpeg │ │ ├── CVYD42E.js │ │ ├── a.html │ │ ├── a_data │ │ │ ├── analytics.js │ │ │ ├── archive.css │ │ │ ├── archive.js │ │ │ ├── areact.js │ │ │ ├── bootstrap.js │ │ │ ├── clipboard.js │ │ │ ├── ia-topnav.js │ │ │ ├── ie-dom-node-remove-polyfill.js │ │ │ ├── jquery-1.js │ │ │ ├── jquery-ui.js │ │ │ ├── more-facets.js │ │ │ ├── polyfill.js │ │ │ ├── raven.js │ │ │ ├── react-dom.js │ │ │ ├── react.js │ │ │ ├── regenerator-runtime-polyfill.js │ │ │ ├── styles.css │ │ │ ├── web.css │ │ │ └── webcomponents-bundle.js │ │ ├── analytics_002.js │ │ ├── banner-styles.css │ │ ├── carbon.js │ │ ├── code-820275-825x510.jpg │ │ ├── comment-reply.html │ │ ├── css.css │ │ ├── devicepx-jetpack.js │ │ ├── donate.html │ │ ├── e-201722.js │ │ ├── form.html │ │ ├── forms-api.js │ │ ├── functions.js │ │ ├── genericons.css │ │ ├── gprofiles.js │ │ ├── iconochive.css │ │ ├── jetpack.css │ │ ├── jquery-migrate.js │ │ ├── jquery.html │ │ ├── jquery.js │ │ ├── jquery_002.html │ │ ├── master.html │ │ ├── master_data │ │ │ ├── a.html │ │ │ ├── a_data │ │ │ │ └── a.js │ │ │ ├── jed.js │ │ │ ├── jquery.js │ │ │ ├── jquery_002.js │ │ │ ├── likes-rest.js │ │ │ ├── postmessage.js │ │ │ └── underscore.js │ │ ├── playback.js │ │ ├── postmessage.html │ │ ├── queuehandler.html │ │ ├── related-posts.js │ │ ├── sharing.html │ │ ├── skip-link-focus-fix.js │ │ ├── social-logos.css │ │ ├── style.html │ │ ├── twentysixteen.css │ │ ├── wombat.js │ │ ├── wp-embed.html │ │ ├── wp-emoji-release.html │ │ └── wpgroho.html │ ├── hyphenation.org - TeX Users Group.html │ ├── hyphenation.org - TeX Users Group_files │ │ └── tugstyle.css │ ├── inessential Brian’s Stupid Feed Tricks.html │ └── inessential Brian’s Stupid Feed Tricks_files │ │ ├── css.css │ │ └── styles5.css ├── rfcs │ ├── Pingback 1.0.html │ ├── RFC 3275_ (Extensible Markup Language) XML-Signature Syntax and Processing.html │ ├── RFC 4287 The Atom Syndication Format.html │ └── RFC 5005 Feed Paging and Archiving.html ├── schemas │ ├── rich-rss-0.1.xsd │ └── rss-2_0_1-rev9.xsd ├── screenshot.png ├── web-to-fragment-feed.md └── xsl │ ├── feed.xml │ └── feed.xsl ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── graphql.config.yml ├── lintDockerfile.sh ├── local.hosts ├── package.json ├── packages ├── agent │ ├── .dockerignore │ ├── .gitignore │ ├── .nvmrc │ ├── .prettierignore │ ├── .prettierrc │ ├── .prettierrc.json │ ├── Dockerfile │ ├── README.md │ ├── agent-scope.json │ ├── build-ghostery.sh │ ├── build.gradle.kts │ ├── codegen.yml │ ├── docker-entrypoint.sh │ ├── nest-cli.json │ ├── package.json │ ├── src │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── corrId.ts │ │ ├── graphql-client.ts │ │ ├── graphql │ │ │ └── agent.graphql │ │ ├── main.ts │ │ └── services │ │ │ ├── agent │ │ │ ├── agent.module.ts │ │ │ ├── agent.service.spec.ts │ │ │ └── agent.service.ts │ │ │ ├── common │ │ │ ├── common.module.ts │ │ │ ├── prefix-logger.service.spec.ts │ │ │ ├── prefix-logger.service.ts │ │ │ ├── verbose-config.service.spec.ts │ │ │ └── verbose-config.service.ts │ │ │ └── puppeteer │ │ │ ├── puppeteer.controller.spec.ts │ │ │ ├── puppeteer.controller.ts │ │ │ ├── puppeteer.module.ts │ │ │ ├── puppeteer.service.spec.ts │ │ │ └── puppeteer.service.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── yarn.lock ├── app-web │ ├── .browserslistrc │ ├── .dockerignore │ ├── .editorconfig │ ├── .eslintignore │ ├── .eslintrc.json │ ├── .gitignore │ ├── .nvmrc │ ├── .prettierignore │ ├── .prettierrc.json │ ├── Dockerfile │ ├── README.md │ ├── angular.json │ ├── build.gradle.kts │ ├── codegen.yml │ ├── e2e │ │ └── tsconfig.json │ ├── generate-dev-app-config.sh │ ├── generate-verticals-data.ts │ ├── ionic.config.json │ ├── karma-ci.conf.js │ ├── karma.conf.js │ ├── nginx.conf │ ├── ngsw-config.json │ ├── package.json │ ├── proxy.conf.json │ ├── src │ │ ├── ads.txt │ │ ├── app │ │ │ ├── all-verticals.ts │ │ │ ├── app-load.module.ts │ │ │ ├── app-routing.module.ts │ │ │ ├── app-test.module.ts │ │ │ ├── app.component.html │ │ │ ├── app.component.scss │ │ │ ├── app.component.spec.ts │ │ │ ├── app.component.ts │ │ │ ├── app.config.ts │ │ │ ├── app.module.ts │ │ │ ├── components │ │ │ │ ├── agents-button │ │ │ │ │ ├── agents-button.component.html │ │ │ │ │ ├── agents-button.component.scss │ │ │ │ │ ├── agents-button.component.spec.ts │ │ │ │ │ └── agents-button.component.ts │ │ │ │ ├── agents │ │ │ │ │ ├── agents.component.html │ │ │ │ │ ├── agents.component.scss │ │ │ │ │ ├── agents.component.spec.ts │ │ │ │ │ └── agents.component.ts │ │ │ │ ├── bubble │ │ │ │ │ ├── bubble.component.html │ │ │ │ │ ├── bubble.component.scss │ │ │ │ │ ├── bubble.component.spec.ts │ │ │ │ │ └── bubble.component.ts │ │ │ │ ├── console-button │ │ │ │ │ ├── console-button.component.html │ │ │ │ │ ├── console-button.component.scss │ │ │ │ │ ├── console-button.component.spec.ts │ │ │ │ │ └── console-button.component.ts │ │ │ │ ├── dark-mode-button │ │ │ │ │ ├── dark-mode-button.component.html │ │ │ │ │ ├── dark-mode-button.component.scss │ │ │ │ │ ├── dark-mode-button.component.spec.ts │ │ │ │ │ └── dark-mode-button.component.ts │ │ │ │ ├── email-login │ │ │ │ │ ├── email-login.component.html │ │ │ │ │ ├── email-login.component.scss │ │ │ │ │ ├── email-login.component.spec.ts │ │ │ │ │ └── email-login.component.ts │ │ │ │ ├── embedded-image │ │ │ │ │ ├── embedded-image.component.html │ │ │ │ │ ├── embedded-image.component.scss │ │ │ │ │ ├── embedded-image.component.spec.ts │ │ │ │ │ └── embedded-image.component.ts │ │ │ │ ├── embedded-markup │ │ │ │ │ ├── embedded-markup.component.html │ │ │ │ │ ├── embedded-markup.component.scss │ │ │ │ │ ├── embedded-markup.component.spec.ts │ │ │ │ │ └── embedded-markup.component.ts │ │ │ │ ├── empty-repository │ │ │ │ │ ├── empty-repository.component.html │ │ │ │ │ ├── empty-repository.component.scss │ │ │ │ │ ├── empty-repository.component.spec.ts │ │ │ │ │ └── empty-repository.component.ts │ │ │ │ ├── feed-builder │ │ │ │ │ ├── feed-builder.component.html │ │ │ │ │ ├── feed-builder.component.scss │ │ │ │ │ ├── feed-builder.component.spec.ts │ │ │ │ │ └── feed-builder.component.ts │ │ │ │ ├── feed-details │ │ │ │ │ ├── feed-details.component.html │ │ │ │ │ ├── feed-details.component.scss │ │ │ │ │ ├── feed-details.component.spec.ts │ │ │ │ │ └── feed-details.component.ts │ │ │ │ ├── feedless-header │ │ │ │ │ ├── feedless-header.component.html │ │ │ │ │ ├── feedless-header.component.scss │ │ │ │ │ ├── feedless-header.component.spec.ts │ │ │ │ │ └── feedless-header.component.ts │ │ │ │ ├── fetch-rate-accordion │ │ │ │ │ ├── fetch-rate-accordion.component.html │ │ │ │ │ ├── fetch-rate-accordion.component.scss │ │ │ │ │ ├── fetch-rate-accordion.component.spec.ts │ │ │ │ │ └── fetch-rate-accordion.component.ts │ │ │ │ ├── filter-items-accordion │ │ │ │ │ ├── filter-items-accordion.component.html │ │ │ │ │ ├── filter-items-accordion.component.scss │ │ │ │ │ ├── filter-items-accordion.component.spec.ts │ │ │ │ │ └── filter-items-accordion.component.ts │ │ │ │ ├── histogram │ │ │ │ │ ├── histogram.component.html │ │ │ │ │ ├── histogram.component.scss │ │ │ │ │ ├── histogram.component.spec.ts │ │ │ │ │ └── histogram.component.ts │ │ │ │ ├── image-diff │ │ │ │ │ ├── image-diff.component.html │ │ │ │ │ ├── image-diff.component.scss │ │ │ │ │ ├── image-diff.component.spec.ts │ │ │ │ │ └── image-diff.component.ts │ │ │ │ ├── import-button │ │ │ │ │ ├── import-button.component.html │ │ │ │ │ ├── import-button.component.scss │ │ │ │ │ ├── import-button.component.spec.ts │ │ │ │ │ └── import-button.component.ts │ │ │ │ ├── interactive-website │ │ │ │ │ ├── interactive-website.component.html │ │ │ │ │ ├── interactive-website.component.scss │ │ │ │ │ ├── interactive-website.component.spec.ts │ │ │ │ │ ├── interactive-website.component.ts │ │ │ │ │ ├── source-builder.spec.ts │ │ │ │ │ └── source-builder.ts │ │ │ │ ├── login-button │ │ │ │ │ ├── login-button.component.html │ │ │ │ │ ├── login-button.component.scss │ │ │ │ │ ├── login-button.component.spec.ts │ │ │ │ │ └── login-button.component.ts │ │ │ │ ├── map │ │ │ │ │ ├── map.component.html │ │ │ │ │ ├── map.component.scss │ │ │ │ │ ├── map.component.spec.ts │ │ │ │ │ └── map.component.ts │ │ │ │ ├── native-feed │ │ │ │ │ ├── native-feed.component.html │ │ │ │ │ ├── native-feed.component.scss │ │ │ │ │ ├── native-feed.component.spec.ts │ │ │ │ │ └── native-feed.component.ts │ │ │ │ ├── notebooks │ │ │ │ │ ├── notebooks.component.html │ │ │ │ │ ├── notebooks.component.scss │ │ │ │ │ ├── notebooks.component.spec.ts │ │ │ │ │ └── notebooks.component.ts │ │ │ │ ├── notifications-button │ │ │ │ │ ├── notifications-button.component.html │ │ │ │ │ ├── notifications-button.component.scss │ │ │ │ │ ├── notifications-button.component.spec.ts │ │ │ │ │ └── notifications-button.component.ts │ │ │ │ ├── pagination │ │ │ │ │ ├── pagination.component.html │ │ │ │ │ ├── pagination.component.scss │ │ │ │ │ ├── pagination.component.spec.ts │ │ │ │ │ └── pagination.component.ts │ │ │ │ ├── plan-column │ │ │ │ │ ├── plan-column.component.html │ │ │ │ │ ├── plan-column.component.scss │ │ │ │ │ ├── plan-column.component.spec.ts │ │ │ │ │ └── plan-column.component.ts │ │ │ │ ├── player │ │ │ │ │ ├── player.component.html │ │ │ │ │ ├── player.component.scss │ │ │ │ │ ├── player.component.spec.ts │ │ │ │ │ └── player.component.ts │ │ │ │ ├── pricing │ │ │ │ │ ├── pricing.component.html │ │ │ │ │ ├── pricing.component.scss │ │ │ │ │ ├── pricing.component.spec.ts │ │ │ │ │ └── pricing.component.ts │ │ │ │ ├── product-header │ │ │ │ │ ├── product-header.component.html │ │ │ │ │ ├── product-header.component.scss │ │ │ │ │ ├── product-header.component.spec.ts │ │ │ │ │ └── product-header.component.ts │ │ │ │ ├── product-headline │ │ │ │ │ ├── product-headline.component.html │ │ │ │ │ ├── product-headline.component.scss │ │ │ │ │ ├── product-headline.component.spec.ts │ │ │ │ │ └── product-headline.component.ts │ │ │ │ ├── product-title │ │ │ │ │ ├── product-title.component.html │ │ │ │ │ ├── product-title.component.scss │ │ │ │ │ ├── product-title.component.spec.ts │ │ │ │ │ └── product-title.component.ts │ │ │ │ ├── reader │ │ │ │ │ ├── reader.component.html │ │ │ │ │ ├── reader.component.scss │ │ │ │ │ ├── reader.component.spec.ts │ │ │ │ │ └── reader.component.ts │ │ │ │ ├── remote-feed-item │ │ │ │ │ ├── remote-feed-item.component.html │ │ │ │ │ ├── remote-feed-item.component.scss │ │ │ │ │ ├── remote-feed-item.component.spec.ts │ │ │ │ │ └── remote-feed-item.component.ts │ │ │ │ ├── remote-feed-preview │ │ │ │ │ ├── remote-feed-preview.component.html │ │ │ │ │ ├── remote-feed-preview.component.scss │ │ │ │ │ ├── remote-feed-preview.component.spec.ts │ │ │ │ │ └── remote-feed-preview.component.ts │ │ │ │ ├── repositories-button │ │ │ │ │ ├── repositories-button.component.html │ │ │ │ │ ├── repositories-button.component.scss │ │ │ │ │ ├── repositories-button.component.spec.ts │ │ │ │ │ └── repositories-button.component.ts │ │ │ │ ├── responsive-columns │ │ │ │ │ ├── responsive-columns.component.html │ │ │ │ │ ├── responsive-columns.component.scss │ │ │ │ │ ├── responsive-columns.component.spec.ts │ │ │ │ │ └── responsive-columns.component.ts │ │ │ │ ├── segmented-output │ │ │ │ │ ├── segmented-output.component.html │ │ │ │ │ ├── segmented-output.component.scss │ │ │ │ │ ├── segmented-output.component.spec.ts │ │ │ │ │ └── segmented-output.component.ts │ │ │ │ ├── table │ │ │ │ │ ├── table.component.html │ │ │ │ │ ├── table.component.scss │ │ │ │ │ ├── table.component.spec.ts │ │ │ │ │ └── table.component.ts │ │ │ │ ├── text-diff │ │ │ │ │ ├── text-diff.component.html │ │ │ │ │ ├── text-diff.component.scss │ │ │ │ │ ├── text-diff.component.spec.ts │ │ │ │ │ └── text-diff.component.ts │ │ │ │ ├── transform-website-to-feed │ │ │ │ │ ├── transform-website-to-feed.component.html │ │ │ │ │ ├── transform-website-to-feed.component.scss │ │ │ │ │ ├── transform-website-to-feed.component.spec.ts │ │ │ │ │ └── transform-website-to-feed.component.ts │ │ │ │ ├── trial-warning │ │ │ │ │ ├── trial-warning.component.html │ │ │ │ │ ├── trial-warning.component.scss │ │ │ │ │ ├── trial-warning.component.spec.ts │ │ │ │ │ └── trial-warning.component.ts │ │ │ │ └── workflow-builder │ │ │ │ │ ├── workflow-builder.component.html │ │ │ │ │ ├── workflow-builder.component.scss │ │ │ │ │ ├── workflow-builder.component.spec.ts │ │ │ │ │ └── workflow-builder.component.ts │ │ │ ├── defaults.ts │ │ │ ├── directives │ │ │ │ ├── control-value-accessor │ │ │ │ │ └── control-value-accessor.directive.ts │ │ │ │ ├── localize │ │ │ │ │ └── localize.directive.ts │ │ │ │ └── remove-if-prod │ │ │ │ │ └── remove-if-prod.directive.ts │ │ │ ├── elements │ │ │ │ ├── code-editor │ │ │ │ │ ├── checkbox.widget.ts │ │ │ │ │ ├── code-editor.component.html │ │ │ │ │ ├── code-editor.component.scss │ │ │ │ │ ├── code-editor.component.spec.ts │ │ │ │ │ ├── code-editor.component.ts │ │ │ │ │ ├── hashtag.widget.ts │ │ │ │ │ ├── inline-image.widget.ts │ │ │ │ │ ├── line.decorator.ts │ │ │ │ │ ├── markdown.decorator.ts │ │ │ │ │ ├── markdown.lang.ts │ │ │ │ │ ├── theme.ts │ │ │ │ │ └── url.decorator.ts │ │ │ │ ├── input │ │ │ │ │ ├── input.component.html │ │ │ │ │ ├── input.component.scss │ │ │ │ │ ├── input.component.spec.ts │ │ │ │ │ └── input.component.ts │ │ │ │ ├── menu │ │ │ │ │ ├── menu.component.html │ │ │ │ │ ├── menu.component.scss │ │ │ │ │ ├── menu.component.spec.ts │ │ │ │ │ └── menu.component.ts │ │ │ │ ├── searchbar │ │ │ │ │ ├── searchbar.component.html │ │ │ │ │ ├── searchbar.component.scss │ │ │ │ │ ├── searchbar.component.spec.ts │ │ │ │ │ └── searchbar.component.ts │ │ │ │ └── select │ │ │ │ │ ├── select.component.html │ │ │ │ │ ├── select.component.scss │ │ │ │ │ ├── select.component.spec.ts │ │ │ │ │ └── select.component.ts │ │ │ ├── form-controls.ts │ │ │ ├── graphql │ │ │ │ ├── agents.graphql │ │ │ │ ├── annotation.graphql │ │ │ │ ├── auth.graphql │ │ │ │ ├── connectedApp.graphql │ │ │ │ ├── event.graphql │ │ │ │ ├── feature.graphql │ │ │ │ ├── fragments.graphql │ │ │ │ ├── license.graphql │ │ │ │ ├── order.graphql │ │ │ │ ├── plugins.graphql │ │ │ │ ├── product.graphql │ │ │ │ ├── record.graphql │ │ │ │ ├── report.graphql │ │ │ │ ├── repositories.graphql │ │ │ │ ├── scrape.graphql │ │ │ │ ├── serverConfig.graphql │ │ │ │ ├── session.graphql │ │ │ │ └── types.ts │ │ │ ├── guards │ │ │ │ ├── auth-guard.service.ts │ │ │ │ ├── profile-guard.service.ts │ │ │ │ ├── saas-guard.service.ts │ │ │ │ └── self-hosting-guard.service.ts │ │ │ ├── modals │ │ │ │ ├── code-editor-modal │ │ │ │ │ ├── code-editor-modal.component.html │ │ │ │ │ ├── code-editor-modal.component.scss │ │ │ │ │ ├── code-editor-modal.component.spec.ts │ │ │ │ │ ├── code-editor-modal.component.ts │ │ │ │ │ └── code-editor-modal.module.ts │ │ │ │ ├── feed-builder-modal │ │ │ │ │ ├── feed-builder-modal.component.html │ │ │ │ │ ├── feed-builder-modal.component.scss │ │ │ │ │ ├── feed-builder-modal.component.spec.ts │ │ │ │ │ ├── feed-builder-modal.component.ts │ │ │ │ │ └── feed-builder-modal.module.ts │ │ │ │ ├── import-opml-modal │ │ │ │ │ ├── import-opml-modal.component.html │ │ │ │ │ ├── import-opml-modal.component.scss │ │ │ │ │ ├── import-opml-modal.component.spec.ts │ │ │ │ │ ├── import-opml-modal.component.ts │ │ │ │ │ └── import-opml-modal.module.ts │ │ │ │ ├── interactive-website-modal │ │ │ │ │ ├── interactive-website-controller.ts │ │ │ │ │ ├── interactive-website-modal.component.html │ │ │ │ │ ├── interactive-website-modal.component.scss │ │ │ │ │ ├── interactive-website-modal.component.spec.ts │ │ │ │ │ ├── interactive-website-modal.component.ts │ │ │ │ │ └── interactive-website-modal.module.ts │ │ │ │ ├── map-modal │ │ │ │ │ ├── map-modal.component.html │ │ │ │ │ ├── map-modal.component.scss │ │ │ │ │ ├── map-modal.component.spec.ts │ │ │ │ │ ├── map-modal.component.ts │ │ │ │ │ └── map-modal.module.ts │ │ │ │ ├── repository-modal │ │ │ │ │ ├── repository-modal.component.html │ │ │ │ │ ├── repository-modal.component.scss │ │ │ │ │ ├── repository-modal.component.spec.ts │ │ │ │ │ ├── repository-modal.component.ts │ │ │ │ │ └── repository-modal.module.ts │ │ │ │ ├── search-address-modal │ │ │ │ │ ├── search-address-modal.component.html │ │ │ │ │ ├── search-address-modal.component.scss │ │ │ │ │ ├── search-address-modal.component.spec.ts │ │ │ │ │ ├── search-address-modal.component.ts │ │ │ │ │ └── search-address-modal.module.ts │ │ │ │ ├── selection-modal │ │ │ │ │ ├── selection-modal.component.html │ │ │ │ │ ├── selection-modal.component.scss │ │ │ │ │ ├── selection-modal.component.spec.ts │ │ │ │ │ ├── selection-modal.component.ts │ │ │ │ │ └── selection-modal.module.ts │ │ │ │ └── tags-modal │ │ │ │ │ ├── tags-modal.component.html │ │ │ │ │ ├── tags-modal.component.scss │ │ │ │ │ ├── tags-modal.component.spec.ts │ │ │ │ │ ├── tags-modal.component.ts │ │ │ │ │ └── tags-modal.module.ts │ │ │ ├── offline.module.ts │ │ │ ├── pages │ │ │ │ ├── agents │ │ │ │ │ ├── agents.page.html │ │ │ │ │ ├── agents.page.scss │ │ │ │ │ ├── agents.page.spec.ts │ │ │ │ │ ├── agents.page.ts │ │ │ │ │ └── agents.routes.ts │ │ │ │ ├── billings │ │ │ │ │ ├── billings.page.html │ │ │ │ │ ├── billings.page.scss │ │ │ │ │ ├── billings.page.spec.ts │ │ │ │ │ ├── billings.page.ts │ │ │ │ │ └── billings.routes.ts │ │ │ │ ├── checkout │ │ │ │ │ ├── checkout.page.html │ │ │ │ │ ├── checkout.page.scss │ │ │ │ │ ├── checkout.page.spec.ts │ │ │ │ │ ├── checkout.page.ts │ │ │ │ │ └── checkout.routes.ts │ │ │ │ ├── connect-app │ │ │ │ │ ├── connect-app.page.html │ │ │ │ │ ├── connect-app.page.scss │ │ │ │ │ ├── connect-app.page.spec.ts │ │ │ │ │ ├── connect-app.page.ts │ │ │ │ │ └── connect-app.routes.ts │ │ │ │ ├── contact │ │ │ │ │ ├── contact.page.html │ │ │ │ │ ├── contact.page.scss │ │ │ │ │ ├── contact.page.spec.ts │ │ │ │ │ ├── contact.page.ts │ │ │ │ │ └── contact.routes.ts │ │ │ │ ├── directory │ │ │ │ │ ├── directory.page.html │ │ │ │ │ ├── directory.page.scss │ │ │ │ │ ├── directory.page.spec.ts │ │ │ │ │ ├── directory.page.ts │ │ │ │ │ └── directory.routes.ts │ │ │ │ ├── documents │ │ │ │ │ ├── documents.page.html │ │ │ │ │ ├── documents.page.scss │ │ │ │ │ ├── documents.page.ts │ │ │ │ │ ├── documents.routes.ts │ │ │ │ │ ├── telegram.page.html │ │ │ │ │ ├── telegram.page.ts │ │ │ │ │ ├── terms.page.html │ │ │ │ │ └── terms.page.ts │ │ │ │ ├── feed-builder │ │ │ │ │ ├── feed-builder.page.html │ │ │ │ │ ├── feed-builder.page.scss │ │ │ │ │ ├── feed-builder.page.spec.ts │ │ │ │ │ ├── feed-builder.page.ts │ │ │ │ │ └── feed-builder.routes.ts │ │ │ │ ├── feed-details │ │ │ │ │ ├── feed-details.page.html │ │ │ │ │ ├── feed-details.page.scss │ │ │ │ │ ├── feed-details.page.spec.ts │ │ │ │ │ ├── feed-details.page.ts │ │ │ │ │ └── feed-details.routes.ts │ │ │ │ ├── feeds │ │ │ │ │ ├── feeds.page.html │ │ │ │ │ ├── feeds.page.scss │ │ │ │ │ ├── feeds.page.spec.ts │ │ │ │ │ ├── feeds.page.ts │ │ │ │ │ └── feeds.routes.ts │ │ │ │ ├── license │ │ │ │ │ ├── license.page.html │ │ │ │ │ ├── license.page.scss │ │ │ │ │ ├── license.page.spec.ts │ │ │ │ │ ├── license.page.ts │ │ │ │ │ └── license.routes.ts │ │ │ │ ├── login │ │ │ │ │ ├── login.page.html │ │ │ │ │ ├── login.page.scss │ │ │ │ │ ├── login.page.spec.ts │ │ │ │ │ ├── login.page.ts │ │ │ │ │ └── login.routes.ts │ │ │ │ ├── notebook-details │ │ │ │ │ ├── note-reference-marker.ts │ │ │ │ │ ├── note-reference-widget.ts │ │ │ │ │ ├── notebook-details.page.html │ │ │ │ │ ├── notebook-details.page.scss │ │ │ │ │ ├── notebook-details.page.spec.ts │ │ │ │ │ ├── notebook-details.page.ts │ │ │ │ │ └── notebook-details.routes.ts │ │ │ │ ├── notebooks │ │ │ │ │ ├── notebooks.page.html │ │ │ │ │ ├── notebooks.page.scss │ │ │ │ │ ├── notebooks.page.spec.ts │ │ │ │ │ ├── notebooks.page.ts │ │ │ │ │ └── notebooks.routes.ts │ │ │ │ ├── payment-summary │ │ │ │ │ ├── payment-summary.page.html │ │ │ │ │ ├── payment-summary.page.scss │ │ │ │ │ ├── payment-summary.page.spec.ts │ │ │ │ │ ├── payment-summary.page.ts │ │ │ │ │ └── payment-summary.routes.ts │ │ │ │ ├── payment │ │ │ │ │ ├── payment.page.html │ │ │ │ │ ├── payment.page.scss │ │ │ │ │ ├── payment.page.spec.ts │ │ │ │ │ ├── payment.page.ts │ │ │ │ │ └── payment.routes.ts │ │ │ │ ├── pricing │ │ │ │ │ ├── pricing.page.html │ │ │ │ │ ├── pricing.page.scss │ │ │ │ │ ├── pricing.page.spec.ts │ │ │ │ │ ├── pricing.page.ts │ │ │ │ │ └── pricing.routes.ts │ │ │ │ ├── privacy │ │ │ │ │ ├── privacy.page.html │ │ │ │ │ ├── privacy.page.scss │ │ │ │ │ ├── privacy.page.spec.ts │ │ │ │ │ ├── privacy.page.ts │ │ │ │ │ └── privacy.routes.ts │ │ │ │ ├── profile │ │ │ │ │ ├── profile.page.html │ │ │ │ │ ├── profile.page.scss │ │ │ │ │ ├── profile.page.spec.ts │ │ │ │ │ ├── profile.page.ts │ │ │ │ │ └── profile.routes.ts │ │ │ │ ├── report │ │ │ │ │ ├── report.page.html │ │ │ │ │ ├── report.page.scss │ │ │ │ │ ├── report.page.spec.ts │ │ │ │ │ ├── report.page.ts │ │ │ │ │ └── report.routes.ts │ │ │ │ ├── settings │ │ │ │ │ ├── settings.page.html │ │ │ │ │ ├── settings.page.scss │ │ │ │ │ ├── settings.page.spec.ts │ │ │ │ │ ├── settings.page.ts │ │ │ │ │ └── settings.routes.ts │ │ │ │ ├── tracker-edit │ │ │ │ │ ├── tracker-edit.page.html │ │ │ │ │ ├── tracker-edit.page.scss │ │ │ │ │ ├── tracker-edit.page.spec.ts │ │ │ │ │ ├── tracker-edit.page.ts │ │ │ │ │ └── tracker-edit.routes.ts │ │ │ │ └── workflow-builder │ │ │ │ │ ├── workflow-builder.page.html │ │ │ │ │ ├── workflow-builder.page.scss │ │ │ │ │ ├── workflow-builder.page.spec.ts │ │ │ │ │ ├── workflow-builder.page.ts │ │ │ │ │ └── workflow-builder.routes.ts │ │ │ ├── products │ │ │ │ ├── change-tracker │ │ │ │ │ ├── about │ │ │ │ │ │ ├── about-tracker.page.html │ │ │ │ │ │ ├── about-tracker.page.scss │ │ │ │ │ │ ├── about-tracker.page.spec.ts │ │ │ │ │ │ └── about-tracker.page.ts │ │ │ │ │ ├── change-tracker-product.page.html │ │ │ │ │ ├── change-tracker-product.page.scss │ │ │ │ │ ├── change-tracker-product.page.spec.ts │ │ │ │ │ ├── change-tracker-product.page.ts │ │ │ │ │ ├── change-tracker-product.routes.ts │ │ │ │ │ ├── tracker-details │ │ │ │ │ │ ├── tracker-details.page.html │ │ │ │ │ │ ├── tracker-details.page.scss │ │ │ │ │ │ ├── tracker-details.page.spec.ts │ │ │ │ │ │ └── tracker-details.page.ts │ │ │ │ │ └── tracker-edit │ │ │ │ │ │ ├── tracker-edit-modal.component.html │ │ │ │ │ │ ├── tracker-edit-modal.component.scss │ │ │ │ │ │ ├── tracker-edit-modal.component.spec.ts │ │ │ │ │ │ ├── tracker-edit-modal.component.ts │ │ │ │ │ │ └── tracker-edit-modal.module.ts │ │ │ │ ├── default-routes.ts │ │ │ │ ├── feedless │ │ │ │ │ ├── about │ │ │ │ │ │ ├── about-feedless.page.html │ │ │ │ │ │ ├── about-feedless.page.scss │ │ │ │ │ │ ├── about-feedless.page.spec.ts │ │ │ │ │ │ └── about-feedless.page.ts │ │ │ │ │ ├── feedless-menu │ │ │ │ │ │ ├── feedless-menu.component.html │ │ │ │ │ │ ├── feedless-menu.component.scss │ │ │ │ │ │ ├── feedless-menu.component.spec.ts │ │ │ │ │ │ └── feedless-menu.component.ts │ │ │ │ │ ├── feedless-product.page.html │ │ │ │ │ ├── feedless-product.page.scss │ │ │ │ │ ├── feedless-product.page.spec.ts │ │ │ │ │ ├── feedless-product.page.ts │ │ │ │ │ ├── feedless-product.routes.ts │ │ │ │ │ └── products │ │ │ │ │ │ ├── products-routes.ts │ │ │ │ │ │ ├── products.page.html │ │ │ │ │ │ ├── products.page.scss │ │ │ │ │ │ ├── products.page.spec.ts │ │ │ │ │ │ └── products.page.ts │ │ │ │ ├── reader │ │ │ │ │ ├── reader-menu │ │ │ │ │ │ ├── reader-menu.component.html │ │ │ │ │ │ ├── reader-menu.component.scss │ │ │ │ │ │ ├── reader-menu.component.spec.ts │ │ │ │ │ │ └── reader-menu.component.ts │ │ │ │ │ ├── reader-product.page.html │ │ │ │ │ ├── reader-product.page.scss │ │ │ │ │ ├── reader-product.page.spec.ts │ │ │ │ │ ├── reader-product.page.ts │ │ │ │ │ └── reader-product.routes.ts │ │ │ │ ├── rss-builder │ │ │ │ │ ├── about │ │ │ │ │ │ ├── about-rss-builder.page.html │ │ │ │ │ │ ├── about-rss-builder.page.scss │ │ │ │ │ │ ├── about-rss-builder.page.spec.ts │ │ │ │ │ │ └── about-rss-builder.page.ts │ │ │ │ │ ├── rss-builder-product.page.html │ │ │ │ │ ├── rss-builder-product.page.scss │ │ │ │ │ ├── rss-builder-product.page.spec.ts │ │ │ │ │ ├── rss-builder-product.page.ts │ │ │ │ │ ├── rss-builder-product.routes.ts │ │ │ │ │ └── setup │ │ │ │ │ │ ├── setup.page.html │ │ │ │ │ │ ├── setup.page.scss │ │ │ │ │ │ ├── setup.page.spec.ts │ │ │ │ │ │ └── setup.page.ts │ │ │ │ ├── untold-notes │ │ │ │ │ ├── about │ │ │ │ │ │ ├── about-untold-notes.page.html │ │ │ │ │ │ ├── about-untold-notes.page.scss │ │ │ │ │ │ ├── about-untold-notes.page.spec.ts │ │ │ │ │ │ └── about-untold-notes.page.ts │ │ │ │ │ ├── untold-mixins.scss │ │ │ │ │ ├── untold-notes-product.page.html │ │ │ │ │ ├── untold-notes-product.page.scss │ │ │ │ │ ├── untold-notes-product.page.spec.ts │ │ │ │ │ ├── untold-notes-product.page.ts │ │ │ │ │ └── untold-notes-product.routes.ts │ │ │ │ ├── upcoming │ │ │ │ │ ├── about-us │ │ │ │ │ │ ├── about-us.page.html │ │ │ │ │ │ ├── about-us.page.scss │ │ │ │ │ │ ├── about-us.page.spec.ts │ │ │ │ │ │ └── about-us.page.ts │ │ │ │ │ ├── event │ │ │ │ │ │ ├── event.page.html │ │ │ │ │ │ ├── event.page.scss │ │ │ │ │ │ ├── event.page.spec.ts │ │ │ │ │ │ └── event.page.ts │ │ │ │ │ ├── events │ │ │ │ │ │ ├── events.page.html │ │ │ │ │ │ ├── events.page.scss │ │ │ │ │ │ ├── events.page.spec.ts │ │ │ │ │ │ └── events.page.ts │ │ │ │ │ ├── places.ts │ │ │ │ │ ├── submit-modal │ │ │ │ │ │ ├── submit-modal.component.html │ │ │ │ │ │ ├── submit-modal.component.scss │ │ │ │ │ │ ├── submit-modal.component.spec.ts │ │ │ │ │ │ ├── submit-modal.component.ts │ │ │ │ │ │ └── submit-modal.module.ts │ │ │ │ │ ├── terms │ │ │ │ │ │ ├── terms.page.html │ │ │ │ │ │ ├── terms.page.scss │ │ │ │ │ │ ├── terms.page.spec.ts │ │ │ │ │ │ └── terms.page.ts │ │ │ │ │ ├── upcoming-footer │ │ │ │ │ │ ├── upcoming-footer.component.html │ │ │ │ │ │ ├── upcoming-footer.component.scss │ │ │ │ │ │ ├── upcoming-footer.component.spec.ts │ │ │ │ │ │ └── upcoming-footer.component.ts │ │ │ │ │ ├── upcoming-header │ │ │ │ │ │ ├── upcoming-header.component.html │ │ │ │ │ │ ├── upcoming-header.component.scss │ │ │ │ │ │ ├── upcoming-header.component.spec.ts │ │ │ │ │ │ └── upcoming-header.component.ts │ │ │ │ │ ├── upcoming-mixins.scss │ │ │ │ │ ├── upcoming-product-routes.ts │ │ │ │ │ └── upcoming-product.module.ts │ │ │ │ └── visual-diff │ │ │ │ │ ├── about │ │ │ │ │ ├── about-visual-diff.page.html │ │ │ │ │ ├── about-visual-diff.page.scss │ │ │ │ │ ├── about-visual-diff.page.spec.ts │ │ │ │ │ └── about-visual-diff.page.ts │ │ │ │ │ ├── visual-diff-product-routes.ts │ │ │ │ │ ├── visual-diff-product.page.html │ │ │ │ │ ├── visual-diff-product.page.scss │ │ │ │ │ ├── visual-diff-product.page.spec.ts │ │ │ │ │ └── visual-diff-product.page.ts │ │ │ ├── router-utils.ts │ │ │ ├── services │ │ │ │ ├── agent.service.ts │ │ │ │ ├── annotation.service.ts │ │ │ │ ├── apollo-abort-controller.service.ts │ │ │ │ ├── app-config.service.spec.ts │ │ │ │ ├── app-config.service.ts │ │ │ │ ├── app-update.service.ts │ │ │ │ ├── auth.service.spec.ts │ │ │ │ ├── auth.service.ts │ │ │ │ ├── connected-app.service.ts │ │ │ │ ├── feature.service.ts │ │ │ │ ├── file.service.ts │ │ │ │ ├── geo.service.ts │ │ │ │ ├── http-error-interceptor.service.ts │ │ │ │ ├── license.service.ts │ │ │ │ ├── modal.service.ts │ │ │ │ ├── notebook-health.service.ts │ │ │ │ ├── notebook-repository.ts │ │ │ │ ├── notebook.service.ts │ │ │ │ ├── open-street-map.service.ts │ │ │ │ ├── opml.service.ts │ │ │ │ ├── order.service.ts │ │ │ │ ├── page.service.ts │ │ │ │ ├── plugin.service.ts │ │ │ │ ├── product.service.ts │ │ │ │ ├── record.service.ts │ │ │ │ ├── report.service.ts │ │ │ │ ├── repository.service.ts │ │ │ │ ├── scrape.service.spec.ts │ │ │ │ ├── scrape.service.ts │ │ │ │ ├── server-config.service.spec.ts │ │ │ │ ├── server-config.service.ts │ │ │ │ ├── session.service.spec.ts │ │ │ │ ├── session.service.ts │ │ │ │ ├── upload.service.spec.ts │ │ │ │ └── upload.service.ts │ │ │ └── types.ts │ │ ├── assets │ │ │ ├── archive.jpeg │ │ │ ├── feedless.jpeg │ │ │ ├── i18n │ │ │ │ └── upcoming │ │ │ │ │ ├── de.json │ │ │ │ │ └── en.json │ │ │ ├── icon │ │ │ │ └── favicon.png │ │ │ ├── icons │ │ │ │ ├── icon-128x128.png │ │ │ │ ├── icon-144x144.png │ │ │ │ ├── icon-152x152.png │ │ │ │ ├── icon-192x192.png │ │ │ │ ├── icon-384x384.png │ │ │ │ ├── icon-512x512.png │ │ │ │ ├── icon-72x72.png │ │ │ │ └── icon-96x96.png │ │ │ ├── mail-digest.jpeg │ │ │ ├── reader.jpeg │ │ │ ├── rss-proxy.jpeg │ │ │ ├── tg-bot-start.webp │ │ │ ├── tg-connect.webp │ │ │ ├── tg-link.webp │ │ │ ├── tg-linked.webp │ │ │ ├── tg-push.webp │ │ │ ├── tg-repo-settings.webp │ │ │ ├── tg-search.webp │ │ │ ├── untold.jpeg │ │ │ ├── upcoming.jpeg │ │ │ ├── video-preview.webp │ │ │ └── visual-diff.jpeg │ │ ├── browserconfig.xml │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── global.scss │ │ ├── index.html │ │ ├── main.ts │ │ ├── manifest.webmanifest │ │ ├── mixins.scss │ │ ├── test │ │ │ ├── apollo-client.mock.ts │ │ │ └── sw-update.mock.ts │ │ ├── theme │ │ │ └── variables.scss │ │ └── zone-flags.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── tsconfig.spec.json │ └── yarn.lock └── server-core │ ├── .dockerignore │ ├── .gitignore │ ├── Dockerfile │ ├── README.md │ ├── build.gradle.kts │ ├── docker-entrypoint.sh │ ├── fetchGithubJars.sh │ ├── pre-boot.sh │ ├── src │ ├── main │ │ ├── kotlin │ │ │ └── org │ │ │ │ └── migor │ │ │ │ └── feedless │ │ │ │ ├── AppInitListener.kt │ │ │ │ ├── AppMetrics.kt │ │ │ │ ├── AppProfiles.kt │ │ │ │ ├── Exceptions.kt │ │ │ │ ├── FeedlessApplication.kt │ │ │ │ ├── actions │ │ │ │ ├── ClickPositionActionEntity.kt │ │ │ │ ├── ClickXpathActionEntity.kt │ │ │ │ ├── DomActionEntity.kt │ │ │ │ ├── ExecuteActionEntity.kt │ │ │ │ ├── ExtractBoundingBoxActionEntity.kt │ │ │ │ ├── ExtractXpathActionEntity.kt │ │ │ │ ├── FetchActionEntity.kt │ │ │ │ ├── HeaderActionEntity.kt │ │ │ │ ├── PluginExecutionJsonEntity.kt │ │ │ │ ├── ScrapeActionDAO.kt │ │ │ │ ├── ScrapeActionEntity.kt │ │ │ │ ├── WaitActionEntity.kt │ │ │ │ ├── XPathConstraint.kt │ │ │ │ └── XPathValidator.kt │ │ │ │ ├── agent │ │ │ │ ├── AgentDAO.kt │ │ │ │ ├── AgentEntity.kt │ │ │ │ ├── AgentResolver.kt │ │ │ │ ├── AgentService.kt │ │ │ │ └── AgentSyncExecutor.kt │ │ │ │ ├── analytics │ │ │ │ ├── AnalyticsService.kt │ │ │ │ └── Tracked.kt │ │ │ │ ├── annotation │ │ │ │ ├── AnnotationDAO.kt │ │ │ │ ├── AnnotationEntity.kt │ │ │ │ ├── AnnotationResolver.kt │ │ │ │ ├── AnnotationService.kt │ │ │ │ ├── TextAnnotationDAO.kt │ │ │ │ ├── TextAnnotationEntity.kt │ │ │ │ ├── VoteDAO.kt │ │ │ │ └── VoteEntity.kt │ │ │ │ ├── api │ │ │ │ ├── ApiParams.kt │ │ │ │ ├── ApiUrls.kt │ │ │ │ ├── Mappers.kt │ │ │ │ ├── graphql │ │ │ │ │ ├── GraphQLExceptionHandler.kt │ │ │ │ │ └── ServerConfigResolver.kt │ │ │ │ ├── http │ │ │ │ │ ├── AppErrorController.kt │ │ │ │ │ ├── HttpExceptionHandler.kt │ │ │ │ │ └── TestingEndpoint.kt │ │ │ │ └── throttle │ │ │ │ │ ├── IpThrottleService.kt │ │ │ │ │ ├── ThrottleAspect.kt │ │ │ │ │ └── Throttled.kt │ │ │ │ ├── attachment │ │ │ │ ├── AttachmentController.kt │ │ │ │ ├── AttachmentDAO.kt │ │ │ │ ├── AttachmentEntity.kt │ │ │ │ └── AttachmentService.kt │ │ │ │ ├── common │ │ │ │ ├── CacheKeyGenerator.kt │ │ │ │ ├── CleanupExecutor.kt │ │ │ │ ├── HttpService.kt │ │ │ │ ├── InMemorySinkRepository.kt │ │ │ │ ├── PdfService.kt │ │ │ │ └── PropertyService.kt │ │ │ │ ├── community │ │ │ │ ├── CommentDAO.kt │ │ │ │ ├── CommentEntity.kt │ │ │ │ ├── CommentGraphService.kt │ │ │ │ ├── LanguageService.kt │ │ │ │ ├── PartOfSpeechService.kt │ │ │ │ ├── ScoreService.kt │ │ │ │ ├── StemmerService.kt │ │ │ │ ├── StoryEntity.kt │ │ │ │ ├── TokenizerService.kt │ │ │ │ └── text │ │ │ │ │ ├── complex │ │ │ │ │ ├── CivilityScorer.kt │ │ │ │ │ ├── OriginalityScorer.kt │ │ │ │ │ ├── QualityScorer.kt │ │ │ │ │ └── RelevanceScorer.kt │ │ │ │ │ ├── hyphenation │ │ │ │ │ ├── HyphenationPattern.kt │ │ │ │ │ └── Hyphenator.kt │ │ │ │ │ └── simple │ │ │ │ │ ├── CitationScorer.kt │ │ │ │ │ ├── DuplicateContentScorer.kt │ │ │ │ │ ├── EngagementScorer.kt │ │ │ │ │ ├── KeywordIntersectionScorer.kt │ │ │ │ │ ├── NoveltyScorer.kt │ │ │ │ │ ├── ReadingEaseScorer.kt │ │ │ │ │ ├── SpamScorer.kt │ │ │ │ │ ├── SpellingScorer.kt │ │ │ │ │ ├── VocabularyScorer.kt │ │ │ │ │ └── WordCountScorer.kt │ │ │ │ ├── config │ │ │ │ ├── CacheConfig.kt │ │ │ │ ├── CacheEventLogger.kt │ │ │ │ ├── CustomDgsWebSocketConfig.kt │ │ │ │ ├── CustomSQLDialect.kt │ │ │ │ ├── CustomSQLFunctions.kt │ │ │ │ ├── DatabaseConfig.kt │ │ │ │ ├── FlywayConfig.kt │ │ │ │ ├── GraphqlConfig.kt │ │ │ │ ├── PostStartupVerificationService.kt │ │ │ │ ├── SecurityConfig.kt │ │ │ │ ├── SystemSettingsDAO.kt │ │ │ │ ├── SystemSettingsEntity.kt │ │ │ │ └── TemplateConfig.kt │ │ │ │ ├── data │ │ │ │ └── jpa │ │ │ │ │ ├── EntityWithUUID.kt │ │ │ │ │ ├── Seeder.kt │ │ │ │ │ ├── StandardJpaFields.kt │ │ │ │ │ └── enums │ │ │ │ │ ├── EntityVisibility.kt │ │ │ │ │ ├── ReleaseStatus.kt │ │ │ │ │ └── Vertical.kt │ │ │ │ ├── document │ │ │ │ ├── DocumentController.kt │ │ │ │ ├── DocumentDAO.kt │ │ │ │ ├── DocumentEntity.kt │ │ │ │ ├── DocumentResolver.kt │ │ │ │ ├── DocumentService.kt │ │ │ │ └── filter │ │ │ │ │ └── FilterByExpression.jj │ │ │ │ ├── feature │ │ │ │ ├── FeatureDAO.kt │ │ │ │ ├── FeatureEntity.kt │ │ │ │ ├── FeatureGroupDAO.kt │ │ │ │ ├── FeatureGroupEntity.kt │ │ │ │ ├── FeatureResolver.kt │ │ │ │ ├── FeatureService.kt │ │ │ │ ├── FeatureStates.kt │ │ │ │ ├── FeatureValueDAO.kt │ │ │ │ ├── FeatureValueEntity.kt │ │ │ │ └── FeatureValueType.kt │ │ │ │ ├── feed │ │ │ │ ├── DateClaimer.kt │ │ │ │ ├── FeedParserService.kt │ │ │ │ ├── StandaloneFeedController.kt │ │ │ │ ├── StandaloneFeedService.kt │ │ │ │ ├── discovery │ │ │ │ │ ├── GenericFeedLocator.kt │ │ │ │ │ ├── NativeFeedLocator.kt │ │ │ │ │ └── RemoteNativeFeedRef.kt │ │ │ │ ├── exporter │ │ │ │ │ ├── FeedExporter.kt │ │ │ │ │ ├── FeedlessModule.kt │ │ │ │ │ ├── FeedlessModuleGenerator.kt │ │ │ │ │ ├── FeedlessModuleImpl.kt │ │ │ │ │ ├── FeedlessModuleParser.kt │ │ │ │ │ ├── JsonFeedExporter.kt │ │ │ │ │ └── SyndAtomFeedExporter.kt │ │ │ │ └── parser │ │ │ │ │ ├── BrokenXmlParser.kt │ │ │ │ │ ├── CalendarFeedParser.kt │ │ │ │ │ ├── FeedBodyParser.kt │ │ │ │ │ ├── JsonFeedParser.kt │ │ │ │ │ ├── NullFeedParser.kt │ │ │ │ │ ├── XmlFeedParser.kt │ │ │ │ │ └── json │ │ │ │ │ ├── JsonAttachment.kt │ │ │ │ │ ├── JsonAuthor.kt │ │ │ │ │ ├── JsonFeed.kt │ │ │ │ │ ├── JsonItem.kt │ │ │ │ │ ├── JsonPoint.kt │ │ │ │ │ ├── OpenSearchDescription.kt │ │ │ │ │ ├── OpenSearchQuery.kt │ │ │ │ │ └── OpenSearchResponse.kt │ │ │ │ ├── group │ │ │ │ ├── GroupDAO.kt │ │ │ │ ├── GroupEntity.kt │ │ │ │ ├── GroupResolver.kt │ │ │ │ ├── GroupService.kt │ │ │ │ ├── UserGroupAssignmentDAO.kt │ │ │ │ └── UserGroupAssignmentEntity.kt │ │ │ │ ├── license │ │ │ │ ├── LicenseDAO.kt │ │ │ │ ├── LicenseEntity.kt │ │ │ │ ├── LicenseService.kt │ │ │ │ └── LinceseResolver.kt │ │ │ │ ├── mail │ │ │ │ ├── MailAuthResolver.kt │ │ │ │ ├── MailAuthenticationService.kt │ │ │ │ ├── MailConfig.kt │ │ │ │ ├── MailController.kt │ │ │ │ ├── MailGunService.kt │ │ │ │ ├── MailService.kt │ │ │ │ ├── MailServiceImpl.kt │ │ │ │ ├── NoopMailService.kt │ │ │ │ ├── OneTimePasswordDAO.kt │ │ │ │ ├── OneTimePasswordEntity.kt │ │ │ │ ├── OneTimePasswordService.kt │ │ │ │ └── TemplateService.kt │ │ │ │ ├── message │ │ │ │ └── MessageService.kt │ │ │ │ ├── pipeline │ │ │ │ ├── DocumentPipelineJobDAO.kt │ │ │ │ ├── DocumentPipelineJobEntity.kt │ │ │ │ ├── DocumentPipelineJobExecutor.kt │ │ │ │ ├── DocumentPipelineService.kt │ │ │ │ ├── FeedlessPlugin.kt │ │ │ │ ├── FilterEntityPlugin.kt │ │ │ │ ├── FragmentTransformerPlugin.kt │ │ │ │ ├── MapEntityPlugin.kt │ │ │ │ ├── PipelineJobEntity.kt │ │ │ │ ├── PluginResolver.kt │ │ │ │ ├── PluginService.kt │ │ │ │ ├── ReportPlugin.kt │ │ │ │ ├── SourcePipelineJobDAO.kt │ │ │ │ ├── SourcePipelineJobEntity.kt │ │ │ │ ├── SourcePipelineJobExecutor.kt │ │ │ │ ├── SourcePipelineService.kt │ │ │ │ └── plugins │ │ │ │ │ ├── CompositeFilterPlugin.kt │ │ │ │ │ ├── ConditionalTagPlugin.kt │ │ │ │ │ ├── DetectMediaPlugin.kt │ │ │ │ │ ├── DiffDataForwarderPlugin.kt │ │ │ │ │ ├── EventsReportPlugin.kt │ │ │ │ │ ├── FeedPlugin.kt │ │ │ │ │ ├── FeedsPlugin.kt │ │ │ │ │ ├── FulltextPlugin.kt │ │ │ │ │ └── PrivacyPlugin.kt │ │ │ │ ├── plan │ │ │ │ ├── InvoiceEntity.kt │ │ │ │ ├── OrderDAO.kt │ │ │ │ ├── OrderEntity.kt │ │ │ │ ├── OrderResolver.kt │ │ │ │ ├── OrderService.kt │ │ │ │ ├── PaymentController.kt │ │ │ │ ├── PaymentMethod.kt │ │ │ │ ├── PlanConstraintsService.kt │ │ │ │ ├── PlanDAO.kt │ │ │ │ ├── PlanEntity.kt │ │ │ │ ├── PlanResolver.kt │ │ │ │ ├── PlanService.kt │ │ │ │ ├── PricedProductDAO.kt │ │ │ │ ├── PricedProductEntity.kt │ │ │ │ ├── ProductDAO.kt │ │ │ │ ├── ProductDataLoader.kt │ │ │ │ ├── ProductEntity.kt │ │ │ │ ├── ProductResolver.kt │ │ │ │ └── ProductService.kt │ │ │ │ ├── report │ │ │ │ ├── ReportDAO.kt │ │ │ │ ├── ReportEntity.kt │ │ │ │ ├── ReportJobExecutor.kt │ │ │ │ ├── ReportResolver.kt │ │ │ │ ├── ReportService.kt │ │ │ │ ├── SegmentationDAO.kt │ │ │ │ ├── SegmentationEntity.kt │ │ │ │ └── SegmentationService.kt │ │ │ │ ├── repository │ │ │ │ ├── AbstractRepositoryEntity.kt │ │ │ │ ├── AnalyticsSyncExecutor.kt │ │ │ │ ├── GithubService.kt │ │ │ │ ├── HarvestDAO.kt │ │ │ │ ├── HarvestEntity.kt │ │ │ │ ├── InboxService.kt │ │ │ │ ├── RepositoryController.kt │ │ │ │ ├── RepositoryDAO.kt │ │ │ │ ├── RepositoryEntity.kt │ │ │ │ ├── RepositoryHarvester.kt │ │ │ │ ├── RepositoryHarvesterExecutor.kt │ │ │ │ ├── RepositoryResolver.kt │ │ │ │ └── RepositoryService.kt │ │ │ │ ├── scrape │ │ │ │ ├── MarkupSimplifier.kt │ │ │ │ ├── PageInspectionService.kt │ │ │ │ ├── ScrapeContext.kt │ │ │ │ ├── ScrapeQueryResolver.kt │ │ │ │ ├── ScrapeService.kt │ │ │ │ ├── WebExtractService.kt │ │ │ │ ├── WebToArticleTransformer.kt │ │ │ │ ├── WebToFeedTransformer.kt │ │ │ │ └── WebToTextTransformer.kt │ │ │ │ ├── secrets │ │ │ │ ├── EncryptionConverter.kt │ │ │ │ ├── HashConverter.kt │ │ │ │ ├── SecretsResolver.kt │ │ │ │ ├── UserSecretDAO.kt │ │ │ │ ├── UserSecretEntity.kt │ │ │ │ ├── UserSecretService.kt │ │ │ │ └── UserSecretType.kt │ │ │ │ ├── session │ │ │ │ ├── AuthAnonymousResolver.kt │ │ │ │ ├── AuthService.kt │ │ │ │ ├── AuthWebsocketRepository.kt │ │ │ │ ├── AuthenticationHttpSessionHandshakeInterceptor.kt │ │ │ │ ├── CookieProvider.kt │ │ │ │ ├── IAuthService.kt │ │ │ │ ├── JwtRequestFilter.kt │ │ │ │ ├── NoopAuthService.kt │ │ │ │ ├── PermissionService.kt │ │ │ │ ├── SessionResolver.kt │ │ │ │ ├── SessionService.kt │ │ │ │ └── TokenProvider.kt │ │ │ │ ├── source │ │ │ │ ├── SourceDAO.kt │ │ │ │ ├── SourceEntity.kt │ │ │ │ └── SourceService.kt │ │ │ │ ├── transport │ │ │ │ └── TelegramBotService.kt │ │ │ │ ├── user │ │ │ │ ├── ConnectedAppDAO.kt │ │ │ │ ├── ConnectedAppEntity.kt │ │ │ │ ├── ConnectedAppService.kt │ │ │ │ ├── GithubConnectionDAO.kt │ │ │ │ ├── GithubConnectionEntity.kt │ │ │ │ ├── GithubConnectionService.kt │ │ │ │ ├── TelegramConnectionDAO.kt │ │ │ │ ├── TelegramConnectionEntity.kt │ │ │ │ ├── UserDAO.kt │ │ │ │ ├── UserEntity.kt │ │ │ │ ├── UserResolver.kt │ │ │ │ └── UserService.kt │ │ │ │ └── util │ │ │ │ ├── CryptUtil.kt │ │ │ │ ├── DateUtil.kt │ │ │ │ ├── FeedUtil.kt │ │ │ │ ├── HtmlUtil.kt │ │ │ │ ├── HttpUtil.kt │ │ │ │ ├── JsonUtil.kt │ │ │ │ ├── JtsUtil.kt │ │ │ │ └── SafeGuards.kt │ │ └── resources │ │ │ ├── application-analytics.properties │ │ │ ├── application-database.properties │ │ │ ├── application-debug.properties │ │ │ ├── application-dev.properties │ │ │ ├── application-mail.properties │ │ │ ├── application-metrics.properties │ │ │ ├── application-prod.properties │ │ │ ├── application-saas.properties │ │ │ ├── application-sso.properties │ │ │ ├── application-telegram.properties │ │ │ ├── application.properties │ │ │ ├── certs │ │ │ ├── .gitignore │ │ │ └── feedless.pub │ │ │ ├── db │ │ │ ├── .gitignore │ │ │ ├── migration │ │ │ │ ├── V10__name_fk_constraints.sql │ │ │ │ ├── V11__v2_baseline.sql │ │ │ │ ├── V12__add_tags.sql │ │ │ │ ├── V13__rename_scrape_source.sql │ │ │ │ ├── V14__attachment_field_length.sql │ │ │ │ ├── V15__attachment_add_size.sql │ │ │ │ ├── V16__add_schem_version_fields.sql │ │ │ │ ├── V17__refactor_plans.sql │ │ │ │ ├── V18__refactor_user.sql │ │ │ │ ├── V19__correct_user_email.sql │ │ │ │ ├── V1__baseline.sql │ │ │ │ ├── V20__patch_billing_add_payment_method.sql │ │ │ │ ├── V21__create_table_license.sql │ │ │ │ ├── V22__patch_user_plan_subscription.sql │ │ │ │ ├── V23__name_foreign_keys.sql │ │ │ │ ├── V24__invoice.sql │ │ │ │ ├── V25__patch_feature_value.sql │ │ │ │ ├── V26__patch_user_usage.sql │ │ │ │ ├── V27__patch_scrape-source_add_geo.sql │ │ │ │ ├── V28__patch_document_add_geo.sql │ │ │ │ ├── V29__rename_cloud_subscription_to_plan.sql │ │ │ │ ├── V2__patch_user.sql │ │ │ │ ├── V30__enable_postgis.sql │ │ │ │ ├── V31__all_actions.sql │ │ │ │ ├── V32__sequence.sql │ │ │ │ ├── V33__repo_share_key.sql │ │ │ │ ├── V34__cron_run.sql │ │ │ │ ├── V35__retention_age_by_field.sql │ │ │ │ ├── V36__patch_cron_run.sql │ │ │ │ ├── V37__rename_repo_cron_run.sql │ │ │ │ ├── V38__pipeline_job.sql │ │ │ │ ├── V39__patch_scrape_job.sql │ │ │ │ ├── V3__generalize_plugins.sql │ │ │ │ ├── V40__lat_lon_distance.sql │ │ │ │ ├── V41__notifications.sql │ │ │ │ ├── V42__notifications_as_repo.sql │ │ │ │ ├── V43__alter_repo_for_product.sql │ │ │ │ ├── V44__timestamp_func.sql │ │ │ │ ├── V45__backward_comp.sql │ │ │ │ ├── V46__indexes.sql │ │ │ │ ├── V47__add_source_to_document.sql │ │ │ │ ├── V48__increase_url_length.sql │ │ │ │ ├── V49__fix_repo_share_key.sql │ │ │ │ ├── V4__add_plugin_to_user.sql │ │ │ │ ├── V50__harvest.sql │ │ │ │ ├── V51__source_error_count.sql │ │ │ │ ├── V52__array_functions.sql │ │ │ │ ├── V53__normalize_names.sql │ │ │ │ ├── V54__annotate_repository.sql │ │ │ │ ├── V55__connected_apps.sql │ │ │ │ ├── V56__system_settings.sql │ │ │ │ ├── V57__github_to_connected_apps.sql │ │ │ │ ├── V58__push_notification.sql │ │ │ │ ├── V59__inbox_repo.sql │ │ │ │ ├── V5__plugins_in_webdocument.sql │ │ │ │ ├── V60__create_groups.sql │ │ │ │ ├── V61__fix_annotation_fks.sql │ │ │ │ ├── V62__adds_document_hash.sql │ │ │ │ ├── V63__pricing.sql │ │ │ │ ├── V64__re-enable_sources.sql │ │ │ │ ├── V65__fix_chronounit_value.sql │ │ │ │ ├── V66__report.sql │ │ │ │ ├── V67__source_with_pulls.sql │ │ │ │ ├── V68__fix_report.sql │ │ │ │ ├── V69__add_indixes_to_common_fields.sql │ │ │ │ ├── V6__alter_feed_native.sql │ │ │ │ ├── V70__disactivate_push.sql │ │ │ │ ├── V71__source_last_refreshed_at.sql │ │ │ │ ├── V7__alter_feed_native__rename_lastUpdatedAt.sql │ │ │ │ ├── V8__alter_feed_native__add_errorMessage.sql │ │ │ │ └── V9__alter_feed_native__add_harvestRateFixed.sql │ │ │ └── old-schema.sql │ │ │ ├── debug-sample.html │ │ │ ├── debug-sample.pdf │ │ │ ├── debug-sample.txt │ │ │ ├── feed.xsl │ │ │ ├── logback-loki.xml │ │ │ ├── logback.xml │ │ │ ├── markup-templates │ │ │ ├── mail-auth-code.ftl.html │ │ │ ├── mail-visual-diff-change-detected.ftl.html │ │ │ ├── mail-visual-diff-welcome.ftl.html │ │ │ ├── mail-welcome-paid.ftl.html │ │ │ └── page-tracker-authorized.ftl.html │ │ │ ├── models │ │ │ ├── .gitignore │ │ │ └── hyph-en-us.tex │ │ │ ├── public │ │ │ └── sample.txt │ │ │ ├── rome.properties │ │ │ ├── sampleSchema.graphql.txt │ │ │ ├── schema │ │ │ └── schema.graphqls │ │ │ ├── test-data │ │ │ ├── atom.xml │ │ │ ├── dynamic-website.html │ │ │ ├── rss.xml │ │ │ └── static-website.html │ │ │ └── test.html │ └── test │ │ ├── kotlin │ │ ├── PostgreSQLExtension.kt │ │ └── org │ │ │ └── migor │ │ │ └── feedless │ │ │ ├── ExceptionsTest.kt │ │ │ ├── IntegrationTest.kt │ │ │ ├── TestConfigurations.kt │ │ │ ├── agent │ │ │ └── AgentSyncExecutorTest.kt │ │ │ ├── analytics │ │ │ └── AnalyticsServiceTest.kt │ │ │ ├── annotation │ │ │ └── AnnotationServiceTest.kt │ │ │ ├── api │ │ │ ├── graphql │ │ │ │ ├── AuthenticationTest.kt │ │ │ │ ├── QueryResolverTest.kt │ │ │ │ └── ScrapeQueryResolverTest.kt │ │ │ └── throttle │ │ │ │ └── ThrottleAspectTest.kt │ │ │ ├── common │ │ │ └── HttpServiceTest.kt │ │ │ ├── community │ │ │ ├── LanguageServiceTest.kt │ │ │ ├── PartOfSpeechServiceTest.kt │ │ │ ├── TokenizerServiceTest.kt │ │ │ └── text │ │ │ │ ├── ScoreServiceTest.kt │ │ │ │ ├── complex │ │ │ │ ├── CivilityScorerTest.kt │ │ │ │ └── QualityScorerTest.kt │ │ │ │ ├── hyphenation │ │ │ │ └── HyphenatorTest.kt │ │ │ │ └── simple │ │ │ │ ├── CitationScorerTest.kt │ │ │ │ ├── EngagementScorerTest.kt │ │ │ │ ├── KeywordIntersectionScorerTest.kt │ │ │ │ ├── NoveltyScorerTest.kt │ │ │ │ ├── SpamScorerTest.kt │ │ │ │ ├── SpellingScorerTest.kt │ │ │ │ ├── VocabularyScorerTest.kt │ │ │ │ └── WordCountScorerTest.kt │ │ │ ├── config │ │ │ └── SecurityConfigTest.kt │ │ │ ├── document │ │ │ ├── DocumentControllerTest.kt │ │ │ ├── DocumentResolverTest.kt │ │ │ ├── DocumentServiceTest.kt │ │ │ └── filter │ │ │ │ └── FilterByExpressionTest.kt │ │ │ ├── feed │ │ │ ├── StandaloneFeedControllerTest.kt │ │ │ ├── StandaloneFeedServiceTest.kt │ │ │ ├── exporter │ │ │ │ ├── JsonFeedExporterTest.kt │ │ │ │ └── SyndAtomFeedExporterTest.kt │ │ │ └── parser │ │ │ │ ├── CalendarFeedParserTest.kt │ │ │ │ └── XmlFeedParserTest.kt │ │ │ ├── group │ │ │ └── GroupServiceTest.kt │ │ │ ├── jobs │ │ │ └── CleanupExecutorTest.kt │ │ │ ├── license │ │ │ └── LicenseServiceTest.kt │ │ │ ├── mail │ │ │ └── TemplateServiceTest.kt │ │ │ ├── pipeline │ │ │ ├── DocumentPipelineJobExecutorTest.kt │ │ │ ├── PluginServiceTest.kt │ │ │ ├── SourcePipelineJobExecutorTest.kt │ │ │ └── plugins │ │ │ │ ├── CompositeFilterPluginTest.kt │ │ │ │ ├── FeedPluginTest.kt │ │ │ │ ├── FeedsPluginTest.kt │ │ │ │ ├── FulltextPluginTest.kt │ │ │ │ └── PrivacyPluginTest.kt │ │ │ ├── plan │ │ │ ├── FeatureServiceTest.kt │ │ │ ├── PlanConstraintsServiceImplTest.kt │ │ │ └── ProductServiceTest.kt │ │ │ ├── report │ │ │ ├── ReportJobExecutorTest.kt │ │ │ ├── ReportResolverTest.kt │ │ │ └── ReportServiceTest.kt │ │ │ ├── repository │ │ │ ├── AbstractRepositoryEntityTest.kt │ │ │ ├── AnalyticsSyncExecutorTest.kt │ │ │ ├── RepositoryHarvesterExecutorTest.kt │ │ │ ├── RepositoryHarvesterTest.kt │ │ │ ├── RepositoryResolverTest.kt │ │ │ ├── RepositoryServiceTest.kt │ │ │ └── RepositoryUpdateTest.kt │ │ │ ├── secrets │ │ │ └── UserSecretServiceTest.kt │ │ │ ├── service │ │ │ ├── PdfServiceTest.kt │ │ │ └── ScrapeServiceTest.kt │ │ │ ├── session │ │ │ └── PermissionServiceTest.kt │ │ │ ├── source │ │ │ └── SourceServiceTest.kt │ │ │ ├── transform │ │ │ ├── DateClaimerTest.kt │ │ │ ├── MarkupSimplifierTest.kt │ │ │ ├── WebExtractServiceTest.kt │ │ │ ├── WebToArticleTransformerTest.kt │ │ │ ├── WebToFeedTransformerTest.kt │ │ │ └── WebToTextTransformerTest.kt │ │ │ ├── transport │ │ │ └── TelegramBotServiceTest.kt │ │ │ ├── user │ │ │ └── UserServiceTest.kt │ │ │ └── util │ │ │ ├── DateUtilKtTest.kt │ │ │ └── FeedUtilTest.kt │ │ └── resources │ │ ├── application-database.properties │ │ ├── application-test.properties │ │ ├── images │ │ ├── sample.jpeg │ │ ├── sample.pdf │ │ ├── sample.png │ │ └── sample.webp │ │ ├── logback-test.xml │ │ ├── raw-articles │ │ ├── derstandard_at.html │ │ ├── derstandard_at.json │ │ ├── diepresse_com.html │ │ ├── diepresse_com.json │ │ ├── medium_com.html │ │ ├── medium_com.json │ │ ├── newyorker_com.html │ │ ├── newyorker_com.json │ │ ├── spiegel_de.html │ │ ├── spiegel_de.json │ │ ├── theatlantic_com.html │ │ ├── theatlantic_com.json │ │ ├── wikipedia_org.html │ │ ├── wikipedia_org.json │ │ ├── wordpress_com.html │ │ └── wordpress_com.json │ │ ├── raw-websites │ │ ├── 00-paulgraham-com-articles.input.html │ │ ├── 00-paulgraham-com-articles.output.json │ │ ├── 01-spencermounta-in.input.html │ │ ├── 01-spencermounta-in.output.json │ │ ├── 02-spotify-com.input.html │ │ ├── 02-spotify-com.output.json │ │ ├── 03-telepolis-de.input.html │ │ ├── 03-telepolis-de.output.json │ │ ├── 04-arzg-github-io-lang.input.html │ │ ├── 04-arzg-github-io-lang.output.json │ │ ├── 05-www-brandonsmith-ninja.input.html │ │ ├── 05-www-brandonsmith-ninja.output.json │ │ ├── 06-jon-bo-posts.input.html │ │ ├── 06-jon-bo-posts.output.json │ │ ├── 07-craigslist.input.html │ │ ├── 07-craigslist.output.json │ │ ├── 08-arxiv-org.input.html │ │ ├── 08-arxiv-org.output.json │ │ ├── 09-fool-com.input.html │ │ ├── 09-fool-com.output.json │ │ ├── 10-webiphany-com.input.html │ │ ├── 10-webiphany-com.output.json │ │ ├── 11-audacityteam-org.input.html │ │ ├── 11-audacityteam-org.output.json │ │ ├── 12-sph-ethz-ch.input.html │ │ ├── 12-sph-ethz-ch.output.json │ │ ├── 13-google-cloud-blog.input.html │ │ ├── 13-google-cloud-blog.output.json │ │ ├── 14-linkace-org.input.html │ │ └── 14-linkace-org.output.json │ │ ├── sample.pdf │ │ └── transform │ │ ├── after-on-rss.in.xml │ │ ├── apple-rss.in.xml │ │ ├── medium-rss.in.xml │ │ └── yt-atom.in.xml │ ├── supervisord.conf │ ├── test-profiles.js │ └── test │ └── test-docker.sh ├── renovate.json ├── roadmap.md ├── scripts ├── build.sh ├── deploy.sh ├── performance-test.js ├── semver-tag-docker-images.sh └── wait-for-containers.sh ├── selfhosting.env ├── settings.gradle.kts └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/.gradle 3 | .git 4 | products 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Gradle project-specific cache directory 2 | .gradle 3 | bazel-* 4 | .idea 5 | 6 | # Ignore Gradle build output directory 7 | build 8 | package-lock.json 9 | *.log 10 | docs/inspiration 11 | docs/network.dia 12 | node_modules 13 | packages/server-mgmt 14 | build-cache 15 | .env 16 | LAST_BUILD_COMMIT 17 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "packages/agent/ghostery-extension"] 2 | path = packages/agent/ghostery-extension 3 | url = git@github.com:ghostery/ghostery-extension.git 4 | [submodule "products/rss-proxy"] 5 | path = products/rss-proxy 6 | url = git@github.com:damoeb/rss-proxy.git 7 | [submodule "products/visual-diff"] 8 | path = products/visual-diff 9 | url = git@github.com:damoeb/visual-diff.git 10 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/jsLinters/eslint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/modules/packages/server-rss-kotlin/rich-multiproject.packages.server-rss-kotlin.test.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/modules/server-rich-graph.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to feedless guide 2 | -------------------------------------------------------------------------------- /README.mustache.md: -------------------------------------------------------------------------------- 1 | # {{apps.feedless.title}} 2 | 3 | {{ apps.feedless.descriptionMarkdown }} 4 | 5 | [![Watch the video](screenshot.png)]({{{apps.feedless.videoUrl}}}) 6 | 7 | ## Features 8 | {{#apps.feedless.features}} 9 | - {{{.}}} 10 | {{/apps.feedless.features}} 11 | 12 | ## Version 3.x (Latest) 13 | 14 | {{{localSetup}}} 15 | {{{apps.feedless.localSetup}}} 16 | 17 | # Support & Contact 18 | - Public Mail Group feedless@googlegroups.com 19 | - Contact feedlessapp@proton.me 20 | 21 | ## License 22 | 23 | {{apps.feedless.title}} {{{license}}} 24 | -------------------------------------------------------------------------------- /docker/aio-with-chromium/docker-aio-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd ./agent && sleep 10 && sh docker-entrypoint.sh & 4 | export APP_ROOT_SECRET_KEY=$APP_SECRET_KEY 5 | sh ./docker-entrypoint.sh 6 | -------------------------------------------------------------------------------- /docker/app/app-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiUrl": "http://localhost:8080", 3 | "product": "upcoming", 4 | "eventRepositoryId": "ee5e2fd7-4b3e-4bf0-bf74-1224b5d667ff", 5 | "offlineSupport": false 6 | } 7 | -------------------------------------------------------------------------------- /docker/ingress/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:alpine 2 | 3 | # Copy the template and entrypoint script 4 | COPY nginx-template.conf /etc/nginx/nginx-template.conf 5 | COPY entrypoint.sh /entrypoint.sh 6 | 7 | # Make entrypoint script executable 8 | RUN chmod +x /entrypoint.sh 9 | 10 | # Set entrypoint 11 | ENTRYPOINT ["./entrypoint.sh"] 12 | -------------------------------------------------------------------------------- /docker/ingress/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | envsubst '$API_SERVER_NAMES $APP_SERVER_NAMES $INTERNAL_APP_URL $INTERNAL_API_URL' < /etc/nginx/nginx-template.conf > /etc/nginx/nginx.conf 4 | cat /etc/nginx/nginx.conf 5 | # Run Nginx 6 | exec nginx -g 'daemon off;' 7 | -------------------------------------------------------------------------------- /docker/legacy/app-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiUrl": "http://localhost:8080" 3 | } 4 | -------------------------------------------------------------------------------- /docker/plausible/plausible-conf.env: -------------------------------------------------------------------------------- 1 | BASE_URL=http://localhost 2 | SECRET_KEY_BASE=GLVzDZW04FzuS1gMcmBRVhwgd4Gu9YmSl/k/TqfTUXti7FLBd7aflXeQDdwCj6Cz 3 | TOTP_VAULT_KEY=dsxvbn3jxDd16az2QpsX5B8O+llxjQ2SJE2i5Bzx38I= 4 | -------------------------------------------------------------------------------- /docker/promtail/promtail.yml: -------------------------------------------------------------------------------- 1 | server: 2 | http_listen_port: 9080 3 | grpc_listen_port: 0 4 | 5 | positions: 6 | filename: /tmp/positions.yaml 7 | 8 | clients: 9 | - url: http://loki:3100/loki/api/v1/push 10 | 11 | scrape_configs: 12 | - job_name: local 13 | static_configs: 14 | - targets: 15 | - localhost 16 | labels: 17 | job: varlogs 18 | __path__: /var/log/*log 19 | 20 | - job_name: docker 21 | pipeline_stages: 22 | - docker: {} 23 | static_configs: 24 | - labels: 25 | job: docker 26 | __path__: /var/lib/docker/containers/*/*-json.log 27 | -------------------------------------------------------------------------------- /docker/your-license.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docker/your-license.key -------------------------------------------------------------------------------- /docs/blog/analytics.md: -------------------------------------------------------------------------------- 1 | # Selfhosted Analytics 2 | 3 | ## Problem 4 | - how many users do I have 5 | - Where do they come from 6 | - What are they using 7 | 8 | ## Requirements 9 | - no datapirates -> self hosted 10 | - No invasive JavaScript tracker 11 | 12 | ## Solution 13 | - analytics is monitoring of the user 14 | - OpenSource solution plausible 15 | - push events and read events 16 | 17 | ## Conclusion 18 | - Analytics is simple and effective to implement 19 | - Helps me stay motivated 20 | -------------------------------------------------------------------------------- /docs/blog/cd.md: -------------------------------------------------------------------------------- 1 | # Continuous Delivery is what you want 2 | 3 | # Problem 4 | - deploy fast and often to reduce complexity 5 | 6 | # Solution 7 | - CD 8 | - write a test for every bug 9 | - testing vs verifying 10 | - gradle pipeline 11 | - cron hook to run deployment 12 | - no downtime deployment possible? 13 | -------------------------------------------------------------------------------- /docs/blog/cofounder.md: -------------------------------------------------------------------------------- 1 | get a cofounder/codev? 2 | === 3 | 4 | # Problem 5 | - Expecting 1+1=3? 6 | - Management overhead takes energy (motivation) -> 1+1~=0.5 7 | - Others demotivation may eat your motivation 8 | - 9 | -------------------------------------------------------------------------------- /docs/blog/complexity.md: -------------------------------------------------------------------------------- 1 | # Problem 2 | - c is chaos 3 | - c comes from 4 | - your code 5 | - lines of code 6 | - branches 7 | - concepts 8 | - 3rd party code 9 | - dependencies 10 | - runtime 11 | 12 | - Measurement: how fast can you reason about it? 13 | - High c has invisible toll 14 | - speed down -> motivtion down, user satisfaction down 15 | - unknown constraints 16 | 17 | # Solution 18 | - as lean as possible 19 | - many little steps -> CD with hight test coverage 20 | - strong typed languages 21 | - dog feeding 22 | -------------------------------------------------------------------------------- /docs/blog/idea.md: -------------------------------------------------------------------------------- 1 | # So you have an idea 2 | 3 | # Problem 4 | - you have an idea or you look for an idea, how to validate it? 5 | - global idea space internet converges to one solution 6 | - unclear motivation why someone gives you feedback -> mom test 7 | - what aspects of your idea do you want to get feedback for 8 | - don't expect encouragement, expect a beating 9 | - Even nay-sayers can provide good feedback, but filter their answer, wear a helmet 10 | 11 | # Solution 12 | - ideas are like plants, when they are small they are fragile and it is hard to imagine, 13 | how they will look like when grown 14 | - don't ask for feedback too early 15 | - 16 | -------------------------------------------------------------------------------- /docs/blog/monitoring.md: -------------------------------------------------------------------------------- 1 | # Monitoring 2 | 3 | ## Problem 4 | - How is my service doing? 5 | - Did the last deployment break something? 6 | 7 | # Solution 8 | - grafana monitoring using SRE 9 | -------------------------------------------------------------------------------- /docs/blog/monolith.md: -------------------------------------------------------------------------------- 1 | monoliths is what you want 2 | --- 3 | 4 | # Problem 5 | - monoliths or microservices 6 | 7 | 8 | # Solution 9 | - monoliths on different areas 10 | - m. are coherent 11 | -------------------------------------------------------------------------------- /docs/blog/motivation.md: -------------------------------------------------------------------------------- 1 | motivation is scare 2 | --- 3 | 4 | # Problem 5 | - m is your scarsest resource 6 | -------------------------------------------------------------------------------- /docs/blog/no-cloud.md: -------------------------------------------------------------------------------- 1 | # No-cloud inside 2 | 3 | # Problem 4 | - keep it simple stupid 5 | - avoid vendor lock-in 6 | - controlled pricing 7 | 8 | # Solution 9 | - no need for k8s 10 | - docker-compose is your friend 11 | -------------------------------------------------------------------------------- /docs/blog/personal-roadmap.md: -------------------------------------------------------------------------------- 1 | # personal roadmap 2 | 3 | - be conscious where you are and where you want to be 4 | - fears are homeostasis 5 | -------------------------------------------------------------------------------- /docs/blog/sideproject.md: -------------------------------------------------------------------------------- 1 | Why do a sideproject? 2 | 3 | # Problem 4 | - you produce something vs consume 5 | - you consume with a goal -> achievements 6 | - expectation management 7 | - learn to be intrinsically motivated 8 | - reflect about the world 9 | - good concepts from sideprojects are transferable to other areas 10 | - you connect to likeminded people 11 | -------------------------------------------------------------------------------- /docs/blog/testing-hard.md: -------------------------------------------------------------------------------- 1 | # Setup Spring-Boot App for Integration Testing 2 | 3 | Problem 4 | - Big project 5 | - Unit Testing is not enough 6 | - Test one part efficiently 7 | 8 | Solution 9 | - horizontal and vertically split app using Profiles 10 | -------------------------------------------------------------------------------- /docs/blog/throttling.md: -------------------------------------------------------------------------------- 1 | # Protecting your services - Throttling is hard 2 | 3 | # Problem 4 | - What resource needs protection cause it is limited? 5 | 6 | # Solutions 7 | Other strategies? Caching 8 | 9 | throttling is similar to a semaphore, but more just 10 | 11 | 12 | Simple Case: user is authorized, then bucket throttle per user id 13 | 14 | 15 | 16 | Hard Case: user is not authorized, how to make it just? 17 | identify user by IP, what if it is a NAT/VPN? 18 | 19 | 20 | IP + user agent 21 | 22 | # Conclusion 23 | -------------------------------------------------------------------------------- /docs/development.md: -------------------------------------------------------------------------------- 1 | # Developing feedless 2 | 3 | TBD 4 | -------------------------------------------------------------------------------- /docs/flow.dia: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/flow.dia -------------------------------------------------------------------------------- /docs/inspiration/feed rate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/inspiration/feed rate.png -------------------------------------------------------------------------------- /docs/inspiration/reddit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/inspiration/reddit.png -------------------------------------------------------------------------------- /docs/license-dump.md: -------------------------------------------------------------------------------- 1 | # License Braindump 2 | 3 | https://joinup.ec.europa.eu/collection/eupl/how-use-eupl 4 | https://www.youtube.com/watch?v=9kGrKBOytYM 5 | https://techhq.com/podcast-series/postgres-postgresql-services-contract-support-podcast-s01e04/ 6 | -------------------------------------------------------------------------------- /docs/reader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reader.png -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/027b57bb-36f0-4968-ad6b-a86063674a79_6412x3328(1).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/027b57bb-36f0-4968-ad6b-a86063674a79_6412x3328(1).jpg -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/027b57bb-36f0-4968-ad6b-a86063674a79_6412x3328.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/027b57bb-36f0-4968-ad6b-a86063674a79_6412x3328.jpg -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/0952ad26-47a3-4213-85c0-29c8eb1979ce_945x945.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/0952ad26-47a3-4213-85c0-29c8eb1979ce_945x945.jpg -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/11d75d52-c322-430a-b382-636457c898b3_2208x1984.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/11d75d52-c322-430a-b382-636457c898b3_2208x1984.png -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/66dd34c6-7d29-4428-a8d5-f41f8ffe4a91_400x400.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/66dd34c6-7d29-4428-a8d5-f41f8ffe4a91_400x400.jpg -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/6ca3e7da-886c-438c-a46a-e82686c5af48_1916x984(1).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/6ca3e7da-886c-438c-a46a-e82686c5af48_1916x984(1).jpg -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/6ca3e7da-886c-438c-a46a-e82686c5af48_1916x984.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/6ca3e7da-886c-438c-a46a-e82686c5af48_1916x984.jpg -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/80c8d221-3e4f-423f-bed5-929b04d8b301_4000x4000(1).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/80c8d221-3e4f-423f-bed5-929b04d8b301_4000x4000(1).jpg -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/80c8d221-3e4f-423f-bed5-929b04d8b301_4000x4000.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/80c8d221-3e4f-423f-bed5-929b04d8b301_4000x4000.jpg -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/9ca94043-1f3f-41fe-8749-f7e2b89ccf2b_144x144.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/9ca94043-1f3f-41fe-8749-f7e2b89ccf2b_144x144.jpg -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/AuditionPlayer-5637d633.css: -------------------------------------------------------------------------------- 1 | ._auditionPlayer_d4iax_1{display:flex;align-items:center;gap:var(--size-8);padding:var(--size-16);border:var(--border-default);border-radius:var(--border-radius-sm)} 2 | -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/BetaTag-8b30a2bc.css: -------------------------------------------------------------------------------- 1 | ._betaTag_khn4j_1{margin-left:10px;color:var(--color-bg-primary);background-color:var(--color-primary)} 2 | -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/DropzoneOverlay-287820b3.css: -------------------------------------------------------------------------------- 1 | ._outer_zsn9h_1{opacity:0;pointer-events:none;background:var(--color-bg-modal);transition:var(--animate-hover);position:absolute;width:100%;height:100%;top:0;left:0;will-change:opacity}._outer_zsn9h_1._blur_zsn9h_13{-webkit-backdrop-filter:var(--glass-blur);backdrop-filter:var(--glass-blur)}._outer_zsn9h_1._visible_zsn9h_17{opacity:1;pointer-events:all}._icon_zsn9h_23{transition:var(--animate-hover);transform:scale(.5)}._icon_zsn9h_23._visible_zsn9h_17{transform:scale(1)} 2 | -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/EmailTypoHandler-1f1ddc43.css: -------------------------------------------------------------------------------- 1 | ._animationWrapper_trj0v_1{height:0;width:100%;overflow:visible;z-index:1;position:relative}._wrapper_trj0v_9{max-width:380px;width:100%;margin:0 auto}@media (max-width: 650px){._wrapper_trj0v_9{max-width:none;margin:0}}._secondaryText_trj0v_23{color:var(--cover_print_secondary)}._primaryText_trj0v_27{color:var(--cover_print_primary)}._typoHandler_trj0v_31._typoHandler_trj0v_31{width:100%;border:1px solid var(--cover_border_color);text-align:left;background-color:var(--cover_bg_color)}._buttonWrapper_trj0v_38{width:100%} 2 | -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/HoverCard-41ea4b50.css: -------------------------------------------------------------------------------- 1 | ._reset_1vek6_1{box-sizing:border-box}._color-primary_1vek6_5{color:var(--color-primary)}._color-secondary_1vek6_9{color:var(--color-secondary)}._hoverCardBase_19lhr_1{background:var(--color-bg-elevated);display:block;position:absolute;border-radius:var(--border-radius-lg);box-shadow:var(--shadow-md);border:var(--border-default);max-width:340px;min-width:340px;overflow:hidden;visibility:visible;opacity:1;padding:var(--size-8)} 2 | -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/NoteComposer-6d7a578b.css: -------------------------------------------------------------------------------- 1 | ._videoPlaceholder_16haj_1{border-radius:var(--border-radius-md);filter:saturate(0);opacity:.5}._remove_16haj_7{right:var(--size-8);top:var(--size-8)}._loader_16haj_12{pointer-events:none} 2 | -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/ProfileSetupToast-60e0a843.css: -------------------------------------------------------------------------------- 1 | ._wrapper_1pltb_1{background-color:var(--color-light-bg-primary);max-width:440px;min-height:250px;border-radius:var(--border-radius-md);border:solid 1px var(--color-light-detail);padding:26px 24px 16px;box-sizing:border-box;color:var(--color-primary);font-family:var(--font-family-title);font-size:16px;line-height:24px;box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -2px #0000000d}._headerRow_1pltb_20{flex-direction:row-reverse}@media (max-width: 650px){._headerRow_1pltb_20{flex-wrap:wrap}}._header_1pltb_20{color:var(--color-primary)}._wrapper_1pltb_1 svg{stroke:var(--color-light-secondary)} 2 | -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/PublicationSearch-24228c5a.css: -------------------------------------------------------------------------------- 1 | @media screen and (max-width: 650px){._noQueryZeroState_p93un_1{min-height:85svh}}@media screen and (max-width: 650px){._noResultsZeroState_p93un_7{min-height:85svh}} 2 | -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/RewardBox-5297e7a9.css: -------------------------------------------------------------------------------- 1 | ._rewardBox_3zpeh_1{min-height:160px} 2 | -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/TextLink-f399b628.css: -------------------------------------------------------------------------------- 1 | ._link_1ixw5_1{text-decoration:none;cursor:pointer} 2 | -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/Tooltip-d0af0cf2.css: -------------------------------------------------------------------------------- 1 | ._tooltip_1kahx_1{color:var(--color-dark-primary);background-color:var(--color-bg-tooltip);white-space:pre-wrap}._keys_1kahx_7{color:var(--color-dark-secondary)}._key_1kahx_7{border:1px solid var(--color-dark-detail);background-color:var(--color-dark-bg-secondary)} 2 | -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/UserBadge-fb9c174e.css: -------------------------------------------------------------------------------- 1 | ._inlineContainer_uxvmn_1{display:inline-block}._container_uxvmn_5{display:flex;align-items:center}._container_uxvmn_5 svg{display:block}._popover_uxvmn_14{background:var(--color-bg-elevated);border-radius:var(--border-radius-md);box-shadow:var(--shadow-lg);border:var(--border-default);width:260px;z-index:103} 2 | -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/a19b9ff9-60b4-4a6e-ae91-95c701c44c7b_1184x776.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/a19b9ff9-60b4-4a6e-ae91-95c701c44c7b_1184x776.jpg -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/bb5f775e-a62c-48ea-8189-f74b74c84c65_1024x1024.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/bb5f775e-a62c-48ea-8189-f74b74c84c65_1024x1024.jpg -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/f0866902-0b32-48e4-b46c-bc36002e518c_144x144(1).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/f0866902-0b32-48e4-b46c-bc36002e518c_144x144(1).jpg -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/f0866902-0b32-48e4-b46c-bc36002e518c_144x144(2).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/f0866902-0b32-48e4-b46c-bc36002e518c_144x144(2).jpg -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/f0866902-0b32-48e4-b46c-bc36002e518c_144x144.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/f0866902-0b32-48e4-b46c-bc36002e518c_144x144.jpg -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/f1ea07d3-b3d8-44a8-8b2b-4a8f50175ff3_3088x2316.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/f1ea07d3-b3d8-44a8-8b2b-4a8f50175ff3_3088x2316.jpg -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/logged-out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/logged-out.png -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/mention-e43b25a5.css: -------------------------------------------------------------------------------- 1 | ._mention_1qvs5_1._mention_1qvs5_1._mention_1qvs5_1{white-space:nowrap;border-radius:var(--border-radius-full);padding:0 var(--size-8);text-decoration:none;color:var(--color-primary-themed);background:var(--color-bg-secondary-themed);cursor:pointer;font-weight:var(--font-weight-bold)}._mention_1qvs5_1._mention_1qvs5_1._mention_1qvs5_1>a{text-decoration:none}._mention_1qvs5_1._mention_1qvs5_1._mention_1qvs5_1:hover{background:var(--color-bg-tertiary-themed)} 2 | -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/overflow_menu-db9a534e.css: -------------------------------------------------------------------------------- 1 | ._subMenuTrigger_1fyh8_1{cursor:pointer}._subMenuItem_1fyh8_6._subMenuItem_1fyh8_6._subMenuItem_1fyh8_6._subMenuItem_1fyh8_6._subMenuItem_1fyh8_6._subMenuItem_1fyh8_6._disabled_1fyh8_6{opacity:1}._subMenuItem_1fyh8_6._disabled_1fyh8_6 div{font-weight:var(--font-weight-heavy)}._logo_1fyh8_14{border-radius:var(--border-radius-xs)}._chevron_1fyh8_18{margin-left:3px} 2 | -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/podcast_apps-184969d7.css: -------------------------------------------------------------------------------- 1 | ._popoverBase_1kdyr_1{background:var(--color-bg-elevated);display:block;position:absolute;border-radius:var(--border-radius-md);box-shadow:var(--shadow-lg);border:var(--border-default);max-width:350px;min-width:150px;overflow:hidden;visibility:visible;opacity:1;padding:var(--size-16);padding:8px} 2 | -------------------------------------------------------------------------------- /docs/reads/HackerNews Ranking Algorithm_ How would you have done it__files/user-51b02764.css: -------------------------------------------------------------------------------- 1 | ._img_16u6n_1{display:flex}._object-fit-cover_16u6n_5{-o-object-fit:cover;object-fit:cover} 2 | -------------------------------------------------------------------------------- /docs/reads/Improving the Hacker News Ranking Algorithm _ Felix Dietze’s blog_files/hacker-news-downvotes-quality-scatterplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/Improving the Hacker News Ranking Algorithm _ Felix Dietze’s blog_files/hacker-news-downvotes-quality-scatterplot.png -------------------------------------------------------------------------------- /docs/reads/Improving the Hacker News Ranking Algorithm _ Felix Dietze’s blog_files/hacker-news-normalized-upvote-quality-scatterplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/Improving the Hacker News Ranking Algorithm _ Felix Dietze’s blog_files/hacker-news-normalized-upvote-quality-scatterplot.png -------------------------------------------------------------------------------- /docs/reads/Improving the Hacker News Ranking Algorithm _ Felix Dietze’s blog_files/hacker-news-upvote-quality-scatterplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/Improving the Hacker News Ranking Algorithm _ Felix Dietze’s blog_files/hacker-news-upvote-quality-scatterplot.png -------------------------------------------------------------------------------- /docs/reads/Improving the Hacker News Ranking Algorithm _ Felix Dietze’s blog_files/hacker-news-views-as-downvotes-quality-scatterplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/Improving the Hacker News Ranking Algorithm _ Felix Dietze’s blog_files/hacker-news-views-as-downvotes-quality-scatterplot.png -------------------------------------------------------------------------------- /docs/reads/Introduction to Atom_files/borderBL.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/Introduction to Atom_files/borderBL.gif -------------------------------------------------------------------------------- /docs/reads/Introduction to Atom_files/borderBR.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/Introduction to Atom_files/borderBR.gif -------------------------------------------------------------------------------- /docs/reads/Introduction to Atom_files/borderTL.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/Introduction to Atom_files/borderTL.gif -------------------------------------------------------------------------------- /docs/reads/Introduction to Atom_files/borderTR.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/Introduction to Atom_files/borderTR.gif -------------------------------------------------------------------------------- /docs/reads/Introduction to Atom_files/w3c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/Introduction to Atom_files/w3c.png -------------------------------------------------------------------------------- /docs/reads/The myth of RSS compatibility [dive into mark]_files/0596101651.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/The myth of RSS compatibility [dive into mark]_files/0596101651.jpg -------------------------------------------------------------------------------- /docs/reads/Tricks to Monetize your Side Projects – Jeremy Boyd_files/8c64c40d7162ecf436a965d5399d5422.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/Tricks to Monetize your Side Projects – Jeremy Boyd_files/8c64c40d7162ecf436a965d5399d5422.jpeg -------------------------------------------------------------------------------- /docs/reads/Tricks to Monetize your Side Projects – Jeremy Boyd_files/8c64c40d7162ecf436a965d5399d5422_002.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/Tricks to Monetize your Side Projects – Jeremy Boyd_files/8c64c40d7162ecf436a965d5399d5422_002.jpeg -------------------------------------------------------------------------------- /docs/reads/Tricks to Monetize your Side Projects – Jeremy Boyd_files/8c64c40d7162ecf436a965d5399d5422_003.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/Tricks to Monetize your Side Projects – Jeremy Boyd_files/8c64c40d7162ecf436a965d5399d5422_003.jpeg -------------------------------------------------------------------------------- /docs/reads/Tricks to Monetize your Side Projects – Jeremy Boyd_files/8c64c40d7162ecf436a965d5399d5422_004.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/Tricks to Monetize your Side Projects – Jeremy Boyd_files/8c64c40d7162ecf436a965d5399d5422_004.jpeg -------------------------------------------------------------------------------- /docs/reads/Tricks to Monetize your Side Projects – Jeremy Boyd_files/9d173ac02601e81a78d9980ff6ad50cf.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/Tricks to Monetize your Side Projects – Jeremy Boyd_files/9d173ac02601e81a78d9980ff6ad50cf.jpeg -------------------------------------------------------------------------------- /docs/reads/Tricks to Monetize your Side Projects – Jeremy Boyd_files/9d173ac02601e81a78d9980ff6ad50cf_002.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/Tricks to Monetize your Side Projects – Jeremy Boyd_files/9d173ac02601e81a78d9980ff6ad50cf_002.jpeg -------------------------------------------------------------------------------- /docs/reads/Tricks to Monetize your Side Projects – Jeremy Boyd_files/code-820275-825x510.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/reads/Tricks to Monetize your Side Projects – Jeremy Boyd_files/code-820275-825x510.jpg -------------------------------------------------------------------------------- /docs/reads/Tricks to Monetize your Side Projects – Jeremy Boyd_files/donate.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/schemas/rich-rss-0.1.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/docs/screenshot.png -------------------------------------------------------------------------------- /docs/web-to-fragment-feed.md: -------------------------------------------------------------------------------- 1 | # Web to Fragment Feed 2 | Create a feed from a page fragment and render pixel, markup or text. 3 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | nodejsVersion=22.11.0 2 | gradleNodePluginVersion=5.0.0 3 | dockerImageTag=damoeb/feedless 4 | feedlessVersion=0.3.0 5 | user.timezone=UTC 6 | 7 | #org.gradle.jvmargs=-Xmx2048M 8 | org.gradle.caching=true 9 | #org.gradle.parallel=true 10 | #org.gradle.configuration-cache=true 11 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip 4 | #gradleVersion=latest 5 | networkTimeout=10000 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /graphql.config.yml: -------------------------------------------------------------------------------- 1 | documents: '**/*.graphql' 2 | schema: packages/server-core/src/main/resources/schema/schema.graphqls 3 | extensions: 4 | endpoints: 5 | Default GraphQL Endpoint: 6 | url: http://localhost:8080/graphql 7 | headers: 8 | user-agent: JS GraphQL 9 | introspect: false 10 | 11 | -------------------------------------------------------------------------------- /lintDockerfile.sh: -------------------------------------------------------------------------------- 1 | echo "Linting file $1" 2 | docker run --rm -i hadolint/hadolint < $1 3 | -------------------------------------------------------------------------------- /local.hosts: -------------------------------------------------------------------------------- 1 | 127.0.0.1 untold.feedless.dev 2 | 127.0.0.1 rss-proxy.feedless.dev 3 | 127.0.0.1 visual-diff.feedless.dev 4 | 127.0.0.1 feedless.dev 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "feedless", 3 | "private": true, 4 | "devDependencies": { 5 | "mustache": "^4.2.0" 6 | }, 7 | "scripts": { 8 | "updateReadme": "for pj in products/* .; do mustache packages/app-web/all-verticals.json $pj/README.mustache.md $pj/README.md; done", 9 | "commit": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/damoeb/feedless.git" 14 | }, 15 | "keywords": [], 16 | "author": "Markus Ruepp", 17 | "bugs": { 18 | "url": "https://github.com/damoeb/feedless/issues" 19 | }, 20 | "homepage": "https://github.com/damoeb/feedless#readme" 21 | } 22 | -------------------------------------------------------------------------------- /packages/agent/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | src 3 | tsconfig* 4 | .gradle 5 | -------------------------------------------------------------------------------- /packages/agent/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | dist 3 | node_modules 4 | prisma/dev.db 5 | graphql.ts 6 | -------------------------------------------------------------------------------- /packages/agent/.nvmrc: -------------------------------------------------------------------------------- 1 | v22 2 | -------------------------------------------------------------------------------- /packages/agent/.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .gradle 4 | ghostery-extension 5 | -------------------------------------------------------------------------------- /packages/agent/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } 5 | -------------------------------------------------------------------------------- /packages/agent/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": true, 4 | "trailingComma": "all" 5 | } 6 | -------------------------------------------------------------------------------- /packages/agent/README.md: -------------------------------------------------------------------------------- 1 | # Agent 2 | 3 | `Agents` connect to a server (env.HOST) and establish a websocket connection (graphql subscription) in order to wait for jobs. 4 | Those jobs are usually rendering jobs in a chromium, with optional pre and post processings. 5 | 6 | Agents authenticate using a personalized token. They don't need a public IP, so in theory you could have them running on your local machine. 7 | -------------------------------------------------------------------------------- /packages/agent/agent-scope.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /packages/agent/build-ghostery.sh: -------------------------------------------------------------------------------- 1 | cd ghostery-extension && \ 2 | npm ci && \ 3 | cd extension-manifest-v2 && \ 4 | npm run build.prod 5 | -------------------------------------------------------------------------------- /packages/agent/codegen.yml: -------------------------------------------------------------------------------- 1 | # https://www.graphql-code-generator.com/docs/plugins/typescript-apollo-angular 2 | overwrite: true 3 | schema: '../server-core/src/main/resources/schema/schema.graphqls' 4 | documents: ['./src/**/*.graphql'] 5 | generates: 6 | src/generated/graphql.ts: 7 | plugins: 8 | - add: 9 | content: 'import type { DocumentNode } from "graphql/language/ast";' 10 | - 'typescript' 11 | - 'typescript-operations' 12 | - 'typescript-document-nodes' 13 | config: 14 | wrapFieldDefinitions: true 15 | flattenGeneratedTypes: true 16 | preResolveTypes: false 17 | skipTypename: true 18 | -------------------------------------------------------------------------------- /packages/agent/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | while true 2 | do 3 | echo '[agent] Starting Agent' 4 | node main.js 5 | echo '[agent] Respawning in 10s' 6 | sleep 10 7 | done 8 | -------------------------------------------------------------------------------- /packages/agent/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "deleteOutDir": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/agent/src/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | 5 | describe('AppController', () => { 6 | let appController: AppController; 7 | 8 | beforeEach(async () => { 9 | const app: TestingModule = await Test.createTestingModule({ 10 | controllers: [AppController], 11 | providers: [AppService], 12 | }).compile(); 13 | 14 | appController = app.get(AppController); 15 | }); 16 | 17 | it('should be defined', () => { 18 | expect(appController).toBeDefined(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/agent/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | 3 | @Controller() 4 | export class AppController { 5 | @Get('health') 6 | async health() { 7 | return true; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/agent/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService {} 5 | -------------------------------------------------------------------------------- /packages/agent/src/corrId.ts: -------------------------------------------------------------------------------- 1 | import { v4 } from 'uuid'; 2 | 3 | export function newCorrId(): string { 4 | return v4().substring(0, 5); 5 | } 6 | -------------------------------------------------------------------------------- /packages/agent/src/services/agent/agent.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AgentService } from './agent.service'; 3 | import { PuppeteerModule } from '../puppeteer/puppeteer.module'; 4 | import { CommonModule } from '../common/common.module'; 5 | 6 | @Module({ 7 | providers: [AgentService], 8 | exports: [AgentService], 9 | imports: [PuppeteerModule, CommonModule], 10 | }) 11 | export class AgentModule {} 12 | -------------------------------------------------------------------------------- /packages/agent/src/services/agent/agent.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AgentService } from './agent.service'; 3 | import { AgentModule } from './agent.module'; 4 | 5 | describe('AgentService', () => { 6 | let service: AgentService; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | imports: [AgentModule], 11 | }).compile(); 12 | 13 | service = module.get(AgentService); 14 | }); 15 | 16 | it('should be defined', () => { 17 | expect(service).toBeDefined(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/agent/src/services/common/common.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { PrefixLoggerService } from './prefix-logger.service'; 3 | import { VerboseConfigService } from './verbose-config.service'; 4 | 5 | @Module({ 6 | providers: [PrefixLoggerService, VerboseConfigService], 7 | exports: [PrefixLoggerService, VerboseConfigService], 8 | imports: [], 9 | }) 10 | export class CommonModule {} 11 | -------------------------------------------------------------------------------- /packages/agent/src/services/common/prefix-logger.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { PrefixLoggerService } from './prefix-logger.service'; 3 | import { CommonModule } from './common.module'; 4 | 5 | describe('PrefixLoggerService', () => { 6 | let service: PrefixLoggerService; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | imports: [CommonModule], 11 | }).compile(); 12 | 13 | service = module.get(PrefixLoggerService); 14 | }); 15 | 16 | it('should be defined', () => { 17 | expect(service).toBeDefined(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/agent/src/services/common/prefix-logger.service.ts: -------------------------------------------------------------------------------- 1 | import { ConsoleLogger, Injectable, LogLevel } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class PrefixLoggerService extends ConsoleLogger { 5 | constructor( 6 | levels: LogLevel[], 7 | private prefix: string, 8 | ) { 9 | super(); 10 | super.setLogLevels(levels); 11 | } 12 | 13 | error(message: any, stack?: string, context?: string) { 14 | super.error(`[${this.prefix}] ${message}`, stack, context); 15 | } 16 | 17 | log(message: any, ...optionalParams) { 18 | super.log(`[${this.prefix}] ${message}`, ...optionalParams); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/agent/src/services/puppeteer/puppeteer.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { PuppeteerService } from './puppeteer.service'; 3 | import { PuppeteerController } from './puppeteer.controller'; 4 | import { CommonModule } from '../common/common.module'; 5 | 6 | @Module({ 7 | controllers: [PuppeteerController], 8 | providers: [PuppeteerService], 9 | exports: [PuppeteerService], 10 | imports: [CommonModule], 11 | }) 12 | export class PuppeteerModule {} 13 | -------------------------------------------------------------------------------- /packages/agent/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/agent/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": false, 16 | "noImplicitAny": false, 17 | "strictBindCallApply": false, 18 | "forceConsistentCasingInFileNames": false, 19 | "noFallthroughCasesInSwitch": false 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/app-web/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # For the full list of supported browsers by the Angular framework, please see: 6 | # https://angular.io/guide/browser-support 7 | 8 | # You can see what browsers were selected by your queries by running: 9 | # npx browserslist 10 | 11 | Chrome >=79 12 | ChromeAndroid >=79 13 | Firefox >=70 14 | Edge >=79 15 | Safari >=14 16 | iOS >=14 17 | -------------------------------------------------------------------------------- /packages/app-web/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .angular 3 | src 4 | .gradle 5 | -------------------------------------------------------------------------------- /packages/app-web/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /packages/app-web/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | android 3 | dist 4 | www 5 | src/generated 6 | coverage 7 | -------------------------------------------------------------------------------- /packages/app-web/.gitignore: -------------------------------------------------------------------------------- 1 | # Specifies intentionally untracked files to ignore when using Git 2 | # http://git-scm.com/docs/gitignore 3 | 4 | *~ 5 | *.sw[mnpcod] 6 | .tmp 7 | *.tmp 8 | *.tmp.* 9 | *.sublime-project 10 | *.sublime-workspace 11 | .DS_Store 12 | Thumbs.db 13 | UserInterfaceState.xcuserstate 14 | $RECYCLE.BIN/ 15 | 16 | *.log 17 | log.txt 18 | npm-debug.log* 19 | 20 | /.angular 21 | /.idea 22 | /.ionic 23 | /.sass-cache 24 | /.sourcemaps 25 | /.versions 26 | /.vscode 27 | /coverage 28 | /dist 29 | /node_modules 30 | /platforms 31 | /plugins 32 | /www 33 | /src/generated 34 | src/config.json 35 | all-verticals.json 36 | -------------------------------------------------------------------------------- /packages/app-web/.nvmrc: -------------------------------------------------------------------------------- 1 | v22.11.0 2 | -------------------------------------------------------------------------------- /packages/app-web/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | android 3 | dist 4 | www 5 | src/generated 6 | coverage 7 | .gradle 8 | build 9 | .angular 10 | -------------------------------------------------------------------------------- /packages/app-web/.prettierrc.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /packages/app-web/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:1.23.4-alpine 2 | COPY nginx.conf /etc/nginx/conf.d/default.conf 3 | COPY build/generated /usr/share/nginx/html/verticals-data 4 | COPY www/browser /usr/share/nginx/html 5 | RUN ls -l /usr/share/nginx/html | wc 6 | RUN if [ $(ls -l /usr/share/nginx/html | wc -l) -gt 30 ]; \ 7 | then echo "webapp files copied"; \ 8 | else echo "webapp files are not copied"; false; \ 9 | fi 10 | ARG APP_VERSION 11 | ARG APP_GIT_HASH 12 | ENV APP_VERSION=$APP_VERSION \ 13 | APP_GIT_HASH=$APP_GIT_HASH 14 | -------------------------------------------------------------------------------- /packages/app-web/README.md: -------------------------------------------------------------------------------- 1 | # app 2 | -------------------------------------------------------------------------------- /packages/app-web/codegen.yml: -------------------------------------------------------------------------------- 1 | # https://www.graphql-code-generator.com/docs/plugins/typescript-apollo-angular 2 | overwrite: true 3 | schema: "../server-core/src/main/resources/schema/schema.graphqls" 4 | documents: ["./src/**/*.graphql"] 5 | generates: 6 | src/generated/graphql.ts: 7 | plugins: 8 | - "typescript" 9 | - "typescript-operations" 10 | - "typescript-document-nodes" 11 | config: 12 | wrapFieldDefinitions: true 13 | flattenGeneratedTypes: true 14 | typesPrefix: "Gql" 15 | preResolveTypes: false 16 | skipTypename: true 17 | -------------------------------------------------------------------------------- /packages/app-web/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["node", "@wdio/globals/types"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/app-web/generate-dev-app-config.sh: -------------------------------------------------------------------------------- 1 | PRODUCT=$1 2 | OFFLINE_SUPPORT=$2 3 | echo "width product $PRODUCT $OFFLINE_SUPPORT" 4 | echo "{ 5 | \"apiUrl\": \"http://localhost:8080\", 6 | \"product\": \"${PRODUCT}\", 7 | \"eventRepositoryId\": \"f7618b29-ac30-4b5f-baf2-ee68d581fa90\", 8 | \"operatorName\": \"your name\", 9 | \"operatorAddress\": \"your address\", 10 | \"operatorEmail\": \"your email\", 11 | \"offlineSupport\": ${OFFLINE_SUPPORT} 12 | }" > src/config.json 13 | -------------------------------------------------------------------------------- /packages/app-web/ionic.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "integrations": { 4 | "capacitor": {} 5 | }, 6 | "type": "angular" 7 | } 8 | -------------------------------------------------------------------------------- /packages/app-web/proxy.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "/graphql/*": { 3 | "target": "http://localhost:8080", 4 | "secure": false, 5 | "pathRewrite": { 6 | "^/graphql": "/graphql", 7 | "/subscriptions": { 8 | "target": "/subscriptions", 9 | "secure": false, 10 | "ws": true 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/app-web/src/ads.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/ads.txt -------------------------------------------------------------------------------- /packages/app-web/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { PreloadAllModules, RouterModule } from '@angular/router'; 3 | 4 | @NgModule({ 5 | declarations: [], 6 | imports: [ 7 | RouterModule.forRoot([], { 8 | preloadingStrategy: PreloadAllModules, 9 | paramsInheritanceStrategy: 'always', 10 | enableTracing: false, 11 | }), 12 | ], 13 | }) 14 | export class AppRoutingModule {} 15 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/agents-button/agents-button.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Agents 4 | @if (isLoggedIn && agentCount === 0) { 5 | 6 | } 7 | 8 | @if (isLoggedIn && agentCount > 0) { 9 | {{ agentCount }} 10 | } 11 | 12 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/agents-button/agents-button.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/components/agents-button/agents-button.component.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/components/agents/agents.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/components/agents/agents.component.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/components/bubble/bubble.component.html: -------------------------------------------------------------------------------- 1 | 12 | 13 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/bubble/bubble.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, input } from '@angular/core'; 2 | import { NgClass } from '@angular/common'; 3 | 4 | export type BubbleColor = 'orange' | 'blue' | 'red' | 'gray' | 'green'; 5 | 6 | @Component({ 7 | selector: 'app-bubble', 8 | templateUrl: './bubble.component.html', 9 | styleUrls: ['./bubble.component.scss'], 10 | imports: [NgClass], 11 | standalone: true, 12 | }) 13 | export class BubbleComponent { 14 | readonly color = input('blue'); 15 | } 16 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/console-button/console-button.component.html: -------------------------------------------------------------------------------- 1 |
11 | 12 | Console Output 13 | 14 |
15 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/console-button/console-button.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/components/console-button/console-button.component.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/components/dark-mode-button/dark-mode-button.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/components/dark-mode-button/dark-mode-button.component.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/components/email-login/email-login.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/components/email-login/email-login.component.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/components/embedded-image/embedded-image.component.scss: -------------------------------------------------------------------------------- 1 | .point { 2 | cursor: crosshair; 3 | border: 2px solid red; 4 | } 5 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/embedded-markup/embedded-markup.component.scss: -------------------------------------------------------------------------------- 1 | iframe { 2 | max-width: 100%; 3 | max-height: 100%; 4 | } 5 | 6 | .notification { 7 | width: 300px; 8 | position: absolute; 9 | text-align: center; 10 | cursor: pointer; 11 | opacity: 0.9; 12 | border-radius: 12px; 13 | margin-top: 5px; 14 | } 15 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/empty-repository/empty-repository.component.html: -------------------------------------------------------------------------------- 1 | empty repo 2 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/empty-repository/empty-repository.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/components/empty-repository/empty-repository.component.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/components/empty-repository/empty-repository.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-empty-repository', 5 | templateUrl: './empty-repository.component.html', 6 | styleUrls: ['./empty-repository.component.scss'], 7 | }) 8 | export class EmptyRepositoryComponent { 9 | constructor() {} 10 | } 11 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/feed-builder/feed-builder.component.scss: -------------------------------------------------------------------------------- 1 | app-transform-website-to-feed { 2 | display: flex; 3 | flex: 1; 4 | } 5 | 6 | ion-content::part(scroll) { 7 | display: flex; 8 | flex-direction: column; 9 | } 10 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/feedless-header/feedless-header.component.scss: -------------------------------------------------------------------------------- 1 | ion-toolbar { 2 | --background: transparent; 3 | } 4 | 5 | ion-header { 6 | box-shadow: none; 7 | } 8 | 9 | .title { 10 | display: flex; 11 | 12 | & > * { 13 | align-self: center; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/fetch-rate-accordion/fetch-rate-accordion.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Refresh Rate 4 | 5 | 6 | 7 | @for (option of options; track option) { 8 | {{ option.key }} 10 | 11 | } 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/fetch-rate-accordion/fetch-rate-accordion.component.scss: -------------------------------------------------------------------------------- 1 | @use "../../../mixins"; 2 | 3 | ion-input, 4 | ion-select { 5 | --border-radius: 8px; 6 | min-height: 45px; 7 | 8 | &::part(container) { 9 | width: 100%; 10 | } 11 | 12 | &::part(icon) { 13 | --highlight-color: rgba(var(--ion-color-primary-rgb), 1) !important; 14 | } 15 | 16 | &.has-focus, 17 | &.select-expanded { 18 | --border-color: rgba(var(--ion-color-primary-rgb), 1) !important; 19 | } 20 | 21 | &:not(.has-focus):hover { 22 | --border-color: rgba(var(--ion-color-primary-rgb), 0.6); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/histogram/histogram.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | @if (rate === 0) { 4 | Stalling 5 | } 6 | @if (rate > 0) { 7 | {{ rate }} / Month 8 | } 9 |
10 | 11 | 12 | 13 |
14 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/histogram/histogram.component.scss: -------------------------------------------------------------------------------- 1 | .histogram { 2 | position: absolute; 3 | display: flex; 4 | flex-direction: row; 5 | width: 200px; 6 | height: 100%; 7 | right: 0; 8 | top: 0; 9 | align-items: center; 10 | 11 | .chart { 12 | fill: transparent; 13 | stroke-width: 2px; 14 | stroke: var(--ion-color-dark); 15 | flex: 1; 16 | } 17 | 18 | .rate { 19 | font-size: 0.9rem; 20 | } 21 | 22 | //&.stalling { 23 | // .chart { 24 | // stroke: var(--ion-color-warning); 25 | // } 26 | // .rate { 27 | // color: var(--ion-color-warning); 28 | // font-weight: bold; 29 | // } 30 | //} 31 | } 32 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/image-diff/image-diff.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/components/image-diff/image-diff.component.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/components/import-button/import-button.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/components/import-button/import-button.component.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/components/login-button/login-button.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/components/login-button/login-button.component.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/components/map/map.component.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/map/map.component.scss: -------------------------------------------------------------------------------- 1 | @use "../../../../node_modules/leaflet/dist/leaflet.css"; 2 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/native-feed/native-feed.component.scss: -------------------------------------------------------------------------------- 1 | a { 2 | color: var(--ion-color-dark); 3 | } 4 | 5 | .title { 6 | text-decoration: none; 7 | } 8 | 9 | .remote-feed { 10 | position: absolute; 11 | width: 100%; 12 | } 13 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/notebooks/notebooks.component.scss: -------------------------------------------------------------------------------- 1 | p, 2 | ul { 3 | font-size: 1.1rem; 4 | letter-spacing: -0.003em; 5 | line-height: 32px; 6 | font-weight: 400; 7 | hyphens: auto; 8 | color: rgba(var(--app-foreground-rgb), 0.7); 9 | //text-align: justify; 10 | } 11 | 12 | .notebooks { 13 | ion-item { 14 | border-radius: 8px; 15 | margin-bottom: 2px; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/notifications-button/notifications-button.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/notifications-button/notifications-button.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/components/notifications-button/notifications-button.component.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/components/pagination/pagination.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | @if (currentPage !== 0) { 4 | 5 | Previous 6 | 7 | } 8 | {{ currentPage + 1 }} 9 | @if (!isLastPage()) { 10 | Next 11 | } 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/pagination/pagination.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/components/pagination/pagination.component.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/components/player/player.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/components/player/player.component.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/components/product-header/product-header.component.html: -------------------------------------------------------------------------------- 1 |
2 |
10 |
11 | 12 | 13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/product-header/product-header.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/components/product-header/product-header.component.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/components/product-header/product-header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, input } from '@angular/core'; 2 | import { ProductHeadlineComponent } from '../product-headline/product-headline.component'; 3 | 4 | @Component({ 5 | selector: 'app-product-header', 6 | templateUrl: './product-header.component.html', 7 | styleUrls: ['./product-header.component.scss'], 8 | imports: [ProductHeadlineComponent], 9 | standalone: true, 10 | }) 11 | export class ProductHeaderComponent { 12 | readonly productTitle = input.required(); 13 | 14 | constructor() {} 15 | } 16 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/product-headline/product-headline.component.html: -------------------------------------------------------------------------------- 1 |

11 | 12 | {{ title() }} 13 |

14 |
15 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/product-headline/product-headline.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/components/product-headline/product-headline.component.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/components/product-headline/product-headline.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-product-headline', 5 | templateUrl: './product-headline.component.html', 6 | styleUrls: ['./product-headline.component.scss'], 7 | standalone: true, 8 | }) 9 | export class ProductHeadlineComponent { 10 | readonly title = input.required(); 11 | 12 | constructor() {} 13 | } 14 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/product-title/product-title.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/product-title/product-title.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/components/product-title/product-title.component.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/components/reader/reader.component.html: -------------------------------------------------------------------------------- 1 | @if (hasReadability()) { 2 |
3 | 4 | 5 | 6 |
7 | } 8 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/remote-feed-item/remote-feed-item.component.scss: -------------------------------------------------------------------------------- 1 | a { 2 | color: var(--ion-color-dark); 3 | } 4 | 5 | .title { 6 | text-decoration: none; 7 | } 8 | 9 | .remote-feed { 10 | position: absolute; 11 | width: 100%; 12 | } 13 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/remote-feed-preview/remote-feed-preview.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/components/remote-feed-preview/remote-feed-preview.component.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/components/repositories-button/repositories-button.component.html: -------------------------------------------------------------------------------- 1 | 2 | {{ name() }} 3 | 4 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/repositories-button/repositories-button.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/components/repositories-button/repositories-button.component.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/components/responsive-columns/responsive-columns.component.scss: -------------------------------------------------------------------------------- 1 | .slider-button { 2 | display: flex; 3 | position: absolute; 4 | height: 100%; 5 | width: 100%; 6 | z-index: 100; 7 | justify-content: center; 8 | //background: var(--ion-color-light); 9 | cursor: pointer; 10 | 11 | & > ion-icon { 12 | align-self: center; 13 | } 14 | 15 | &__bg { 16 | position: absolute; 17 | height: 100%; 18 | width: 100%; 19 | background: black; 20 | opacity: 0.3; 21 | } 22 | 23 | &:hover { 24 | .slider-button__bg { 25 | opacity: 0.2; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/segmented-output/segmented-output.component.scss: -------------------------------------------------------------------------------- 1 | @use "../../../mixins"; 2 | 3 | .builder-label { 4 | @include mixins.builder-label; 5 | } 6 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/table/table.component.html: -------------------------------------------------------------------------------- 1 | @if (rows) { 2 |
3 |
4 | @for (header of getHeaders(); track header) { 5 |
{{ header.name }}
6 | } 7 |
8 |
9 | @for (row of getRows(); track row) { 10 |
11 | @for (cell of row; track cell) { 12 |
{{ cell.value }}
13 | } 14 |
15 | } 16 |
17 |
18 | } 19 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/text-diff/text-diff.component.html: -------------------------------------------------------------------------------- 1 |
2 | 8 | 16 |
17 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/text-diff/text-diff.component.scss: -------------------------------------------------------------------------------- 1 | .text-diff { 2 | display: flex; 3 | column-gap: 10px; 4 | max-height: 400px; 5 | overflow-y: auto; 6 | 7 | & > * { 8 | flex: 1; 9 | max-width: 48%; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/transform-website-to-feed/transform-website-to-feed.component.scss: -------------------------------------------------------------------------------- 1 | ion-content::part(scroll) { 2 | display: flex; 3 | } 4 | 5 | .generic-feed-score { 6 | position: absolute; 7 | height: 100%; 8 | bottom: 0; 9 | min-width: 5px; 10 | background: rgba(var(--ion-color-dark-rgb), 0.2); 11 | } 12 | 13 | .marked-border-top { 14 | box-shadow: 0px -20px 25px -8px var(--ion-item-background); 15 | border-top-left-radius: 1rem; 16 | border-top-right-radius: 1rem; 17 | border-top: 2px solid var(--app-foreground); 18 | } 19 | -------------------------------------------------------------------------------- /packages/app-web/src/app/components/trial-warning/trial-warning.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/components/trial-warning/trial-warning.component.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/defaults.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_FETCH_CRON: string = '0 0 0 * * *'; 2 | -------------------------------------------------------------------------------- /packages/app-web/src/app/directives/localize/localize.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, ElementRef, inject } from '@angular/core'; 2 | 3 | @Directive({ 4 | // eslint-disable-next-line @angular-eslint/directive-selector 5 | selector: '[localize-*], [localize]', 6 | }) 7 | export class LocalizeDirective { 8 | constructor() { 9 | const element = inject(ElementRef); 10 | 11 | if (element.nativeElement) { 12 | console.log(element.nativeElement.attributes); 13 | element.nativeElement.replace('hase'); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/app-web/src/app/elements/input/input.component.html: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /packages/app-web/src/app/elements/input/input.component.scss: -------------------------------------------------------------------------------- 1 | .input { 2 | --padding-start: 6px; 3 | --padding-end: 6px; 4 | min-height: auto !important; 5 | height: 100% !important; 6 | } 7 | -------------------------------------------------------------------------------- /packages/app-web/src/app/elements/menu/menu.component.scss: -------------------------------------------------------------------------------- 1 | .--focus { 2 | --background: rgba(var(--ion-color-primary-rgb), 0.2); 3 | } 4 | 5 | .--active { 6 | --background: rgba(var(--ion-color-primary-rgb), 0.4); 7 | } 8 | 9 | ion-header { 10 | padding-top: 5px; 11 | padding-left: 5px; 12 | padding-right: 5px; 13 | } 14 | 15 | ion-content { 16 | --background: var(--ion-item-background); 17 | } 18 | 19 | ion-button.error { 20 | --border-color: var(--ion-color-danger); 21 | --border-style: solid; 22 | --border-width: 1px; 23 | } 24 | -------------------------------------------------------------------------------- /packages/app-web/src/app/elements/select/select.component.html: -------------------------------------------------------------------------------- 1 | 12 | {{ label() }} 13 | 14 | -------------------------------------------------------------------------------- /packages/app-web/src/app/elements/select/select.component.scss: -------------------------------------------------------------------------------- 1 | .--focus { 2 | --background: rgba(var(--ion-color-primary-rgb), 0.2); 3 | } 4 | 5 | .--active { 6 | --background: rgba(var(--ion-color-primary-rgb), 0.4); 7 | } 8 | 9 | ion-header { 10 | padding-top: 5px; 11 | padding-left: 5px; 12 | padding-right: 5px; 13 | } 14 | 15 | ion-content { 16 | --background: var(--ion-item-background); 17 | } 18 | -------------------------------------------------------------------------------- /packages/app-web/src/app/form-controls.ts: -------------------------------------------------------------------------------- 1 | import { FormControl, Validators } from '@angular/forms'; 2 | 3 | export function createEmailFormControl( 4 | defaultValue: T, 5 | ): FormControl { 6 | return new FormControl(defaultValue, [ 7 | Validators.required, 8 | Validators.email, 9 | ]); 10 | } 11 | -------------------------------------------------------------------------------- /packages/app-web/src/app/graphql/agents.graphql: -------------------------------------------------------------------------------- 1 | query agents { 2 | agents { 3 | addedAt 4 | name 5 | openInstance 6 | ownerId 7 | secretKeyId 8 | version 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/app-web/src/app/graphql/annotation.graphql: -------------------------------------------------------------------------------- 1 | mutation createAnnotation($data: CreateAnnotationInput!) { 2 | createAnnotation(data: $data) { 3 | id 4 | upVote { 5 | value 6 | } 7 | downVote { 8 | value 9 | } 10 | flag { 11 | value 12 | } 13 | } 14 | } 15 | 16 | mutation deleteAnnotation($data: DeleteAnnotationInput!) { 17 | deleteAnnotation(data: $data) 18 | } 19 | -------------------------------------------------------------------------------- /packages/app-web/src/app/graphql/connectedApp.graphql: -------------------------------------------------------------------------------- 1 | query connectedAppById($id: String!) { 2 | connectedApp(id: $id) { 3 | authorized 4 | authorizedAt 5 | } 6 | } 7 | 8 | mutation updateConnectedApp($id: String!, $authorize: Boolean!) { 9 | updateConnectedApp(id: $id, authorize: $authorize) 10 | } 11 | 12 | mutation deleteConnectedApp($id: String!) { 13 | deleteConnectedApp(id: $id) 14 | } 15 | -------------------------------------------------------------------------------- /packages/app-web/src/app/graphql/event.graphql: -------------------------------------------------------------------------------- 1 | query findEvents($where: RecordsWhereInput!) { 2 | recordsFrequency(where: $where, groupBy: startingAt) { 3 | count 4 | group 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/app-web/src/app/graphql/feature.graphql: -------------------------------------------------------------------------------- 1 | fragment FeatureGroupFragment on FeatureGroup { 2 | id 3 | parentId 4 | name 5 | features { 6 | ...FeatureFragment 7 | } 8 | } 9 | 10 | mutation updateFeatureValue($data: UpdateFeatureValueInput!) { 11 | updateFeatureValue(data: $data) 12 | } 13 | 14 | query featureGroups($inherit: Boolean!, $where: FeatureGroupWhereInput!) { 15 | featureGroups(inherit: $inherit, where: $where) { 16 | ...FeatureGroupFragment 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/app-web/src/app/graphql/license.graphql: -------------------------------------------------------------------------------- 1 | fragment LicenseFragment on License { 2 | name 3 | email 4 | version 5 | createdAt 6 | scope 7 | } 8 | fragment LocalizedLicenseFragment on LocalizedLicense { 9 | isValid 10 | isLocated 11 | trialUntil 12 | isTrial 13 | data { 14 | ...LicenseFragment 15 | } 16 | } 17 | 18 | mutation updateLicense($data: UpdateLicenseInput!) { 19 | updateLicense(data: $data) { 20 | ...LocalizedLicenseFragment 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/app-web/src/app/graphql/order.graphql: -------------------------------------------------------------------------------- 1 | fragment OrderFragment on Order { 2 | id 3 | product { 4 | ...ProductFragment 5 | } 6 | createdAt 7 | isOffer 8 | isPaid 9 | paymentMethod 10 | invoiceRecipientEmail 11 | invoiceRecipientName 12 | } 13 | 14 | query orders($data: OrdersInput!) { 15 | orders(data: $data) { 16 | ...OrderFragment 17 | } 18 | } 19 | 20 | mutation upsertOrder($data: UpsertOrderInput!) { 21 | upsertOrder(data: $data) { 22 | ...OrderFragment 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/app-web/src/app/graphql/plugins.graphql: -------------------------------------------------------------------------------- 1 | fragment PluginFragment on Plugin { 2 | id 3 | name 4 | listed 5 | } 6 | 7 | query listPlugins { 8 | plugins { 9 | id 10 | name 11 | type 12 | listed 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/app-web/src/app/graphql/product.graphql: -------------------------------------------------------------------------------- 1 | fragment ProductFragment on Product { 2 | id 3 | name 4 | description 5 | featureGroupId 6 | # featureGroup { 7 | # id 8 | # name 9 | # features { 10 | # ...FeatureFragment 11 | # } 12 | # } 13 | isCloud 14 | enterprise 15 | individual 16 | other 17 | partOf 18 | prices { 19 | id 20 | recurringInterval 21 | description 22 | inStock 23 | price 24 | } 25 | } 26 | query listProducts($data: ProductsWhereInput!) { 27 | products(data: $data) { 28 | ...ProductFragment 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/app-web/src/app/graphql/report.graphql: -------------------------------------------------------------------------------- 1 | mutation createReport($repositoryId: ID!, $segmentation: SegmentInput!) { 2 | createReport(repositoryId: $repositoryId, segmentation: $segmentation) { 3 | id 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/app-web/src/app/graphql/serverConfig.graphql: -------------------------------------------------------------------------------- 1 | query serverSettings($data: ServerSettingsContextInput!) { 2 | serverSettings(data: $data) { 3 | profiles 4 | build { 5 | commit 6 | date 7 | } 8 | version 9 | features { 10 | ...FeatureFragment 11 | } 12 | license { 13 | ...LocalizedLicenseFragment 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/app-web/src/app/guards/profile-guard.service.ts: -------------------------------------------------------------------------------- 1 | import { inject, Injectable } from '@angular/core'; 2 | import { AuthService } from '../services/auth.service'; 3 | import { map, Observable } from 'rxjs'; 4 | import { CanActivate, UrlTree } from '@angular/router'; 5 | 6 | @Injectable({ 7 | providedIn: 'root', 8 | }) 9 | export class ProfileGuardService implements CanActivate { 10 | private readonly authService = inject(AuthService); 11 | 12 | canActivate(): Observable { 13 | return this.authService 14 | .authorizationChange() 15 | .pipe(map((authentication) => authentication?.loggedIn)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/app-web/src/app/guards/saas-guard.service.ts: -------------------------------------------------------------------------------- 1 | import { inject, Injectable } from '@angular/core'; 2 | import { Observable, of } from 'rxjs'; 3 | import { ServerConfigService } from '../services/server-config.service'; 4 | import { CanActivate, UrlTree } from '@angular/router'; 5 | 6 | @Injectable({ 7 | providedIn: 'root', 8 | }) 9 | export class SaasGuardService implements CanActivate { 10 | private readonly serverConfig = inject(ServerConfigService); 11 | 12 | canActivate(): Observable { 13 | return of(!this.serverConfig.isSelfHosted()); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/app-web/src/app/guards/self-hosting-guard.service.ts: -------------------------------------------------------------------------------- 1 | import { inject, Injectable } from '@angular/core'; 2 | import { Observable, of } from 'rxjs'; 3 | import { ServerConfigService } from '../services/server-config.service'; 4 | import { CanActivate, UrlTree } from '@angular/router'; 5 | 6 | @Injectable({ 7 | providedIn: 'root', 8 | }) 9 | export class SelfHostingGuardService implements CanActivate { 10 | private readonly serverConfig = inject(ServerConfigService); 11 | 12 | canActivate(): Observable { 13 | return of(this.serverConfig.isSelfHosted()); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/app-web/src/app/modals/code-editor-modal/code-editor-modal.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ title }} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 18 | 19 | -------------------------------------------------------------------------------- /packages/app-web/src/app/modals/code-editor-modal/code-editor-modal.component.scss: -------------------------------------------------------------------------------- 1 | ion-content::part(scroll) { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | 6 | app-code-editor { 7 | flex: 1; 8 | } 9 | -------------------------------------------------------------------------------- /packages/app-web/src/app/modals/feed-builder-modal/feed-builder-modal.component.scss: -------------------------------------------------------------------------------- 1 | ion-content::part(scroll) { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | 6 | app-feed-builder { 7 | display: flex; 8 | flex: 1; 9 | } 10 | -------------------------------------------------------------------------------- /packages/app-web/src/app/modals/import-opml-modal/import-opml-modal.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/modals/import-opml-modal/import-opml-modal.component.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/modals/map-modal/map-modal.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Map 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | -------------------------------------------------------------------------------- /packages/app-web/src/app/modals/map-modal/map-modal.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/modals/map-modal/map-modal.component.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/modals/search-address-modal/search-address-modal.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/modals/search-address-modal/search-address-modal.component.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/modals/selection-modal/selection-modal.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/modals/selection-modal/selection-modal.component.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/modals/tags-modal/tags-modal.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/modals/tags-modal/tags-modal.component.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/offline.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { environment } from '../environments/environment'; 3 | import { ServiceWorkerModule } from '@angular/service-worker'; 4 | 5 | @NgModule({ 6 | imports: [ 7 | ServiceWorkerModule.register('ngsw-worker.js', { 8 | enabled: environment.production, 9 | // Register the ServiceWorker as soon as the application is stable 10 | // or after 30 seconds (whichever comes first). 11 | registrationStrategy: 'registerWhenStable:30000', 12 | }), 13 | ], 14 | }) 15 | export class OfflineModule {} 16 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/agents/agents.page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | Home 6 | Agents 7 | 8 | 9 | 10 | 11 |

Agents

12 |
13 |
14 | 15 | 16 |
17 |
18 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/agents/agents.page.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/pages/agents/agents.page.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/agents/agents.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { AgentsPage } from './agents.page'; 3 | 4 | export const AGENTS_ROUTES: Routes = [ 5 | { 6 | path: '', 7 | component: AgentsPage, 8 | }, 9 | ]; 10 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/billings/billings.page.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/pages/billings/billings.page.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/billings/billings.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { BillingsPage } from './billings.page'; 3 | 4 | export const BILLING_ROUTES: Routes = [ 5 | { 6 | path: '', 7 | component: BillingsPage, 8 | }, 9 | ]; 10 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/checkout/checkout.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { CheckoutPage } from './checkout.page'; 3 | 4 | export const CHECKOUT_ROUTES: Routes = [ 5 | { 6 | path: '', 7 | component: CheckoutPage, 8 | }, 9 | { 10 | path: ':productId', 11 | component: CheckoutPage, 12 | }, 13 | ]; 14 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/connect-app/connect-app.page.scss: -------------------------------------------------------------------------------- 1 | a, 2 | a:visited, 3 | a:active { 4 | color: var(--ion-color-primary); 5 | } 6 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/connect-app/connect-app.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { ConnectAppPage } from './connect-app.page'; 3 | 4 | export const CONNECT_APP_ROUTES: Routes = [ 5 | { 6 | path: '', 7 | component: ConnectAppPage, 8 | }, 9 | { 10 | path: ':link', 11 | component: ConnectAppPage, 12 | }, 13 | ]; 14 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/contact/contact.page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

Contact

5 |
    6 |
  • Github
  • 7 |
  • Email feedlessapp/at/proton/dot/me
  • 8 |
9 |
10 |
11 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/contact/contact.page.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/pages/contact/contact.page.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/contact/contact.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { ContactPage } from './contact.page'; 3 | 4 | export const CONTACT_ROUTES: Routes = [ 5 | { 6 | path: '', 7 | component: ContactPage, 8 | }, 9 | ]; 10 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/directory/directory.page.scss: -------------------------------------------------------------------------------- 1 | app-product-header { 2 | p, 3 | div, 4 | ul { 5 | font-size: 1.1rem; 6 | letter-spacing: -0.003em; 7 | line-height: 32px; 8 | font-weight: 400; 9 | hyphens: auto; 10 | color: rgba(var(--app-foreground-rgb), 0.8); 11 | //text-align: justify; 12 | } 13 | } 14 | 15 | .grid { 16 | display: flex; 17 | 18 | .grid-item { 19 | width: 400px; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/directory/directory.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { DirectoryPage } from './directory.page'; 3 | 4 | export const DIRECTORY_ROUTES: Routes = [ 5 | { 6 | path: '', 7 | component: DirectoryPage, 8 | }, 9 | ]; 10 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/documents/documents.page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
6 |
7 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/documents/documents.page.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/pages/documents/documents.page.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/documents/documents.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { DocumentsPage } from './documents.page'; 3 | import { TermsPage } from './terms.page'; 4 | import { TelegramPage } from './telegram.page'; 5 | 6 | export const DOCUMENTS_ROUTES: Routes = [ 7 | { 8 | path: '', 9 | component: DocumentsPage, 10 | children: [ 11 | { 12 | path: 'terms', 13 | component: TermsPage, 14 | }, 15 | { 16 | path: 'telegram', 17 | component: TelegramPage, 18 | }, 19 | ], 20 | }, 21 | ]; 22 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/documents/telegram.page.ts: -------------------------------------------------------------------------------- 1 | import { Component, inject } from '@angular/core'; 2 | import { AppConfigService } from '../../services/app-config.service'; 3 | import { IonContent } from '@ionic/angular/standalone'; 4 | 5 | @Component({ 6 | selector: 'app-setup-telegram-page', 7 | templateUrl: './telegram.page.html', 8 | imports: [IonContent], 9 | standalone: true, 10 | }) 11 | export class TelegramPage { 12 | constructor() { 13 | const appConfig = inject(AppConfigService); 14 | 15 | appConfig.setPageTitle('Telegram Setup'); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/documents/terms.page.ts: -------------------------------------------------------------------------------- 1 | import { Component, inject } from '@angular/core'; 2 | import { AppConfigService } from '../../services/app-config.service'; 3 | import { IonContent } from '@ionic/angular/standalone'; 4 | 5 | @Component({ 6 | selector: 'app-terms-page', 7 | templateUrl: './terms.page.html', 8 | imports: [IonContent], 9 | standalone: true, 10 | }) 11 | export class TermsPage { 12 | constructor() { 13 | const appConfig = inject(AppConfigService); 14 | 15 | appConfig.setPageTitle('Terms'); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/feed-builder/feed-builder.page.html: -------------------------------------------------------------------------------- 1 | @if (!compact) { 2 | 3 | } 4 | 5 | @if (!compact) { 6 |

RSS Feed Builder

7 | } 8 | 14 |
15 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/feed-builder/feed-builder.page.scss: -------------------------------------------------------------------------------- 1 | app-feed-builder { 2 | display: flex; 3 | flex: 1; 4 | } 5 | 6 | ion-content::part(scroll) { 7 | display: flex; 8 | flex-direction: column; 9 | } 10 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/feed-builder/feed-builder.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { FeedBuilderPage } from './feed-builder.page'; 3 | 4 | export const FEED_BUILDER_ROUTES: Routes = [ 5 | { 6 | path: '', 7 | component: FeedBuilderPage, 8 | }, 9 | ]; 10 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/feed-details/feed-details.page.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/pages/feed-details/feed-details.page.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/feed-details/feed-details.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { FeedDetailsPage } from './feed-details.page'; 3 | 4 | export const FEED_DETAILS_ROUTES: Routes = [ 5 | { 6 | path: '', 7 | component: FeedDetailsPage, 8 | }, 9 | ]; 10 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/feeds/feeds.page.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/pages/feeds/feeds.page.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/feeds/feeds.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { FeedsPage } from './feeds.page'; 3 | 4 | export const FEEDS_ROUTES: Routes = [ 5 | { 6 | path: '', 7 | component: FeedsPage, 8 | }, 9 | ]; 10 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/license/license.page.scss: -------------------------------------------------------------------------------- 1 | p { 2 | line-height: 1.8rem; 3 | } 4 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/license/license.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { LicensePage } from './license.page'; 3 | 4 | export const LICENSE_ROUTES: Routes = [ 5 | { 6 | path: '**', 7 | component: LicensePage, 8 | }, 9 | ]; 10 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/login/login.page.scss: -------------------------------------------------------------------------------- 1 | a, 2 | a:visited, 3 | a:active { 4 | color: var(--ion-color-primary); 5 | } 6 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/login/login.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { LoginPage } from './login.page'; 3 | 4 | export const LOGIN_ROUTES: Routes = [ 5 | { 6 | path: '**', 7 | component: LoginPage, 8 | }, 9 | ]; 10 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/notebook-details/notebook-details.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { NotebookDetailsPage } from './notebook-details.page'; 3 | 4 | export const NOTEBOOK_DETAILS_ROUTES: Routes = [ 5 | { 6 | path: ':notebookId', 7 | component: NotebookDetailsPage, 8 | }, 9 | { 10 | path: ':notebookId/:noteId', 11 | component: NotebookDetailsPage, 12 | }, 13 | ]; 14 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/notebooks/notebooks.page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/notebooks/notebooks.page.scss: -------------------------------------------------------------------------------- 1 | app-workflow-builder { 2 | display: flex; 3 | flex: 1; 4 | justify-content: center; 5 | } 6 | 7 | ion-content::part(scroll) { 8 | display: flex; 9 | flex-direction: column; 10 | } 11 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/notebooks/notebooks.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { NotebooksPage } from './notebooks.page'; 3 | 4 | export const NOTEBOOKS_ROUTING: Routes = [ 5 | { 6 | path: '', 7 | component: NotebooksPage, 8 | }, 9 | ]; 10 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/payment-summary/payment-summary.page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |

Confirmation

6 | {{ order|json }} 7 |
8 |
9 |
10 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/payment-summary/payment-summary.page.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/pages/payment-summary/payment-summary.page.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/payment-summary/payment-summary.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { PaymentSummaryPage } from './payment-summary.page'; 3 | 4 | export const PAYMENT_SUMMARY_ROUTES: Routes = [ 5 | { 6 | path: ':billingId', 7 | component: PaymentSummaryPage, 8 | }, 9 | ]; 10 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/payment/payment.page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |

Payment

6 | 7 | 8 |
9 |
10 |
11 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/payment/payment.page.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/pages/payment/payment.page.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/payment/payment.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { PaymentPage } from './payment.page'; 3 | 4 | export const PAYMENT_ROUTES: Routes = [ 5 | { 6 | path: ':billingId', 7 | component: PaymentPage, 8 | }, 9 | ]; 10 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/pricing/pricing.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { PricingPage } from './pricing.page'; 3 | 4 | export const PRICING_ROUTES: Routes = [ 5 | { 6 | path: '', 7 | component: PricingPage, 8 | }, 9 | { 10 | path: ':productId', 11 | component: PricingPage, 12 | }, 13 | ]; 14 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/privacy/privacy.page.scss: -------------------------------------------------------------------------------- 1 | div { 2 | font-size: 1.1rem; 3 | line-height: 1.2rem; 4 | 5 | li { 6 | margin-bottom: 1rem; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/privacy/privacy.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { PrivacyPage } from './privacy.page'; 3 | 4 | export const PRIVACY_ROUTES: Routes = [ 5 | { 6 | path: '', 7 | component: PrivacyPage, 8 | }, 9 | ]; 10 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/profile/profile.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { ProfilePage } from './profile.page'; 3 | 4 | export const PROFILE_ROUTES: Routes = [ 5 | { 6 | path: '', 7 | component: ProfilePage, 8 | }, 9 | ]; 10 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/report/report.page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |

Report Feed

7 |
8 | 9 |
10 |
11 |
12 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/report/report.page.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/pages/report/report.page.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/report/report.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { ReportPage } from './report.page'; 3 | 4 | export const REPORT_ROUTES: Routes = [ 5 | { 6 | path: '', 7 | component: ReportPage, 8 | }, 9 | ]; 10 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/settings/settings.page.scss: -------------------------------------------------------------------------------- 1 | @use "../../../mixins"; 2 | 3 | //ion-content { 4 | // line-height: 1.4rem; 5 | //} 6 | 7 | ion-item { 8 | ion-input { 9 | flex: 1; 10 | text-align: right; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/settings/settings.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { SettingsPage } from './settings.page'; 3 | 4 | export const SETTINGS_ROUTES: Routes = [ 5 | { 6 | path: '**', 7 | component: SettingsPage, 8 | }, 9 | ]; 10 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/tracker-edit/tracker-edit.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { TrackerEditPage } from './tracker-edit.page'; 3 | 4 | export const TRACKER_EDIT_ROUTES: Routes = [ 5 | { 6 | path: '', 7 | component: TrackerEditPage, 8 | }, 9 | ]; 10 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/workflow-builder/workflow-builder.page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/workflow-builder/workflow-builder.page.scss: -------------------------------------------------------------------------------- 1 | app-workflow-builder { 2 | display: flex; 3 | flex: 1; 4 | justify-content: center; 5 | } 6 | 7 | ion-content::part(scroll) { 8 | display: flex; 9 | flex-direction: column; 10 | } 11 | -------------------------------------------------------------------------------- /packages/app-web/src/app/pages/workflow-builder/workflow-builder.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { WorkflowBuilderPage } from './workflow-builder.page'; 3 | 4 | export const WORKFLOW_BUILDER_ROUTES: Routes = [ 5 | { 6 | path: '', 7 | component: WorkflowBuilderPage, 8 | }, 9 | ]; 10 | -------------------------------------------------------------------------------- /packages/app-web/src/app/products/change-tracker/about/about-tracker.page.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/products/change-tracker/about/about-tracker.page.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/products/change-tracker/change-tracker-product.page.scss: -------------------------------------------------------------------------------- 1 | ion-toolbar { 2 | ion-item { 3 | --border-style: none; 4 | } 5 | } 6 | 7 | ion-header { 8 | ion-select::part(icon) { 9 | display: none; 10 | } 11 | } 12 | 13 | ion-content::part(scroll) { 14 | display: flex; 15 | } 16 | 17 | .title { 18 | display: flex; 19 | 20 | & > * { 21 | align-self: center; 22 | } 23 | } 24 | 25 | .hidden { 26 | display: none; 27 | } 28 | -------------------------------------------------------------------------------- /packages/app-web/src/app/products/change-tracker/tracker-details/tracker-details.page.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/products/change-tracker/tracker-details/tracker-details.page.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/products/change-tracker/tracker-edit/tracker-edit-modal.component.scss: -------------------------------------------------------------------------------- 1 | app-remote-feed-preview { 2 | flex: 1; 3 | display: flex; 4 | } 5 | -------------------------------------------------------------------------------- /packages/app-web/src/app/products/feedless/feedless-menu/feedless-menu.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/products/feedless/feedless-menu/feedless-menu.component.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/products/feedless/feedless-product.page.html: -------------------------------------------------------------------------------- 1 | @if (needsAcceptTerms) { 2 | 3 | 4 | You are almost there! Please accepts page terms and conditions 5 | 6 | Show Terms 9 | Accept Terms 12 | 13 | 14 | 15 | } 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /packages/app-web/src/app/products/feedless/feedless-product.page.scss: -------------------------------------------------------------------------------- 1 | ion-toolbar { 2 | --background: transparent; 3 | } 4 | 5 | ion-header { 6 | box-shadow: none; 7 | } 8 | 9 | .active-link { 10 | border-bottom: 2px solid var(--ion-color-dark); 11 | } 12 | 13 | .active { 14 | border-bottom: 2px solid var(--ion-color-primary); 15 | } 16 | 17 | ion-toolbar { 18 | padding-left: 5px; 19 | padding-right: 5px; 20 | } 21 | 22 | .title { 23 | display: flex; 24 | 25 | & > * { 26 | align-self: center; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/app-web/src/app/products/feedless/products/products-routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { ProductsPage } from './products.page'; 3 | 4 | export const PRODUCT_ROUTES: Routes = [ 5 | { 6 | path: ':productId', 7 | component: ProductsPage, 8 | }, 9 | { 10 | path: ':productId/buy', 11 | loadChildren: () => 12 | import('../../../pages/pricing/pricing.routes').then( 13 | (m) => m.PRICING_ROUTES, 14 | ), 15 | }, 16 | ]; 17 | -------------------------------------------------------------------------------- /packages/app-web/src/app/products/reader/reader-menu/reader-menu.component.html: -------------------------------------------------------------------------------- 1 | 2 | History 3 | 4 | 5 | Empty 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /packages/app-web/src/app/products/reader/reader-menu/reader-menu.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/products/reader/reader-menu/reader-menu.component.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/products/reader/reader-menu/reader-menu.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { 3 | IonItem, 4 | IonLabel, 5 | IonList, 6 | IonListHeader, 7 | } from '@ionic/angular/standalone'; 8 | 9 | @Component({ 10 | selector: 'app-reader-menu', 11 | templateUrl: './reader-menu.component.html', 12 | styleUrls: ['./reader-menu.component.scss'], 13 | imports: [IonList, IonListHeader, IonItem, IonLabel], 14 | standalone: true, 15 | }) 16 | export class ReaderMenuComponent { 17 | constructor() {} 18 | } 19 | -------------------------------------------------------------------------------- /packages/app-web/src/app/products/rss-builder/rss-builder-product.page.scss: -------------------------------------------------------------------------------- 1 | ion-toolbar { 2 | ion-item { 3 | --border-style: none; 4 | } 5 | } 6 | 7 | ion-header { 8 | ion-select::part(icon) { 9 | display: none; 10 | } 11 | } 12 | 13 | ion-content::part(scroll) { 14 | display: flex; 15 | } 16 | 17 | .title { 18 | display: flex; 19 | 20 | & > * { 21 | align-self: center; 22 | } 23 | } 24 | 25 | .hidden { 26 | display: none; 27 | } 28 | -------------------------------------------------------------------------------- /packages/app-web/src/app/products/rss-builder/setup/setup.page.scss: -------------------------------------------------------------------------------- 1 | @use "../../../../mixins"; 2 | 3 | p { 4 | font-size: 1.1rem; 5 | line-height: 1.5rem; 6 | color: var(--ion-color-medium); 7 | } 8 | 9 | ion-content::part(scroll) { 10 | display: flex; 11 | } 12 | 13 | h1 { 14 | color: var(--app-foreground); 15 | } 16 | -------------------------------------------------------------------------------- /packages/app-web/src/app/products/untold-notes/about/about-untold-notes.page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/app-web/src/app/products/untold-notes/about/about-untold-notes.page.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/app/products/untold-notes/about/about-untold-notes.page.scss -------------------------------------------------------------------------------- /packages/app-web/src/app/products/untold-notes/about/about-untold-notes.page.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component } from '@angular/core'; 2 | import { IonContent } from '@ionic/angular/standalone'; 3 | import { NotebooksComponent } from '../../../components/notebooks/notebooks.component'; 4 | 5 | @Component({ 6 | selector: 'app-about-untold-notes', 7 | templateUrl: './about-untold-notes.page.html', 8 | styleUrls: ['./about-untold-notes.page.scss'], 9 | changeDetection: ChangeDetectionStrategy.OnPush, 10 | imports: [IonContent, NotebooksComponent], 11 | standalone: true, 12 | }) 13 | export class AboutUntoldNotesPage {} 14 | -------------------------------------------------------------------------------- /packages/app-web/src/app/products/untold-notes/untold-mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin unless-control { 2 | --app-border-color: rgba(var(--ion-color-medium-rgb), 0.2); 3 | --app-border-color-hover: rgba(var(--ion-color-primary-rgb), 0.5); 4 | --app-border-color-focus: rgba(var(--ion-color-primary-rgb), 1); 5 | border: 2px solid var(--app-border-color); 6 | 7 | &:hover { 8 | border-color: var(--app-border-color-hover); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/app-web/src/app/products/untold-notes/untold-notes-product.page.scss: -------------------------------------------------------------------------------- 1 | ion-toolbar { 2 | ion-item { 3 | --border-style: none; 4 | } 5 | } 6 | 7 | ion-header { 8 | ion-select::part(icon) { 9 | display: none; 10 | } 11 | } 12 | 13 | ion-content::part(scroll) { 14 | display: flex; 15 | } 16 | 17 | .title { 18 | display: flex; 19 | padding-right: 15px; 20 | 21 | & > * { 22 | align-self: center; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/app-web/src/app/products/upcoming/about-us/about-us.page.scss: -------------------------------------------------------------------------------- 1 | @use "../upcoming-mixins"; 2 | 3 | p { 4 | font-size: 1.1rem; 5 | line-height: 1.5rem; 6 | color: var(--ion-color-medium); 7 | } 8 | -------------------------------------------------------------------------------- /packages/app-web/src/app/products/upcoming/about-us/about-us.page.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component } from '@angular/core'; 2 | import { IonContent, IonHeader } from '@ionic/angular/standalone'; 3 | import { RouterLink } from '@angular/router'; 4 | 5 | @Component({ 6 | selector: 'app-upcoming-about-us-page', 7 | templateUrl: './about-us.page.html', 8 | styleUrls: ['./about-us.page.scss'], 9 | changeDetection: ChangeDetectionStrategy.OnPush, 10 | imports: [IonHeader, IonContent, RouterLink], 11 | standalone: true, 12 | }) 13 | export class AboutUsPage { 14 | constructor() {} 15 | } 16 | -------------------------------------------------------------------------------- /packages/app-web/src/app/products/upcoming/event/event.page.scss: -------------------------------------------------------------------------------- 1 | @use "../upcoming-mixins"; 2 | 3 | .desc { 4 | font-size: 1.1rem; 5 | line-height: 2rem; 6 | } 7 | -------------------------------------------------------------------------------- /packages/app-web/src/app/products/upcoming/submit-modal/submit-modal.component.scss: -------------------------------------------------------------------------------- 1 | p { 2 | font-size: 1.1rem; 3 | line-height: 1.5rem; 4 | color: var(--ion-color-medium); 5 | } 6 | -------------------------------------------------------------------------------- /packages/app-web/src/app/products/upcoming/terms/terms.page.scss: -------------------------------------------------------------------------------- 1 | @use "../upcoming-mixins"; 2 | -------------------------------------------------------------------------------- /packages/app-web/src/app/products/upcoming/terms/terms.page.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component } from '@angular/core'; 2 | import { IonContent, IonHeader } from '@ionic/angular/standalone'; 3 | 4 | @Component({ 5 | selector: 'app-upcoming-terms-page', 6 | templateUrl: './terms.page.html', 7 | styleUrls: ['./terms.page.scss'], 8 | changeDetection: ChangeDetectionStrategy.OnPush, 9 | imports: [IonHeader, IonContent], 10 | standalone: true, 11 | }) 12 | export class TermsPage { 13 | constructor() {} 14 | } 15 | -------------------------------------------------------------------------------- /packages/app-web/src/app/products/visual-diff/visual-diff-product.page.scss: -------------------------------------------------------------------------------- 1 | ion-content { 2 | --background: rgba(var(--background), 0.2); 3 | --app-border-radius: 8px; 4 | --font-size: 14px; 5 | --app-border-color-hover: rgba(var(--ion-color-primary-rgb), 0.6); 6 | font-size: var(--font-size); 7 | } 8 | -------------------------------------------------------------------------------- /packages/app-web/src/app/services/apollo-abort-controller.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable({ 4 | providedIn: 'root', 5 | }) 6 | export class ApolloAbortControllerService extends AbortController {} 7 | -------------------------------------------------------------------------------- /packages/app-web/src/app/services/app-config.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { AppConfigService } from './app-config.service'; 4 | 5 | describe('AppConfigService', () => { 6 | let service: AppConfigService; 7 | 8 | beforeEach(async () => { 9 | await TestBed.configureTestingModule({}); 10 | service = TestBed.inject(AppConfigService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/app-web/src/app/services/auth.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { AppTestModule } from '../app-test.module'; 3 | import { AuthService } from './auth.service'; 4 | 5 | describe('AuthService', () => { 6 | let service: AuthService; 7 | 8 | beforeEach(async () => { 9 | await TestBed.configureTestingModule({ 10 | imports: [AppTestModule.withDefaults()], 11 | }); 12 | service = TestBed.inject(AuthService); 13 | }); 14 | 15 | it('should be created', () => { 16 | expect(service).toBeTruthy(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/app-web/src/app/services/scrape.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { AppTestModule } from '../app-test.module'; 3 | import { ScrapeService } from './scrape.service'; 4 | 5 | describe('ScrapeService', () => { 6 | let service: ScrapeService; 7 | 8 | beforeEach(async () => { 9 | await TestBed.configureTestingModule({ 10 | imports: [AppTestModule.withDefaults()], 11 | }); 12 | service = TestBed.inject(ScrapeService); 13 | }); 14 | 15 | it('should be created', () => { 16 | expect(service).toBeTruthy(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/app-web/src/app/services/server-config.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { AppTestModule } from '../app-test.module'; 3 | import { ServerConfigService } from './server-config.service'; 4 | 5 | describe('ServerConfigService', () => { 6 | let service: ServerConfigService; 7 | 8 | beforeEach(async () => { 9 | await TestBed.configureTestingModule({ 10 | imports: [AppTestModule.withDefaults()], 11 | }); 12 | service = TestBed.inject(ServerConfigService); 13 | }); 14 | 15 | it('should be created', () => { 16 | expect(service).toBeTruthy(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/app-web/src/app/services/session.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { AppTestModule } from '../app-test.module'; 3 | import { SessionService } from './session.service'; 4 | 5 | describe('SessionService', () => { 6 | let service: SessionService; 7 | 8 | beforeEach(async () => { 9 | await TestBed.configureTestingModule({ 10 | imports: [AppTestModule.withDefaults()], 11 | }); 12 | service = TestBed.inject(SessionService); 13 | }); 14 | 15 | it('should be created', () => { 16 | expect(service).toBeTruthy(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/app-web/src/app/services/upload.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { UploadService } from './upload.service'; 3 | import { AppTestModule } from '../app-test.module'; 4 | 5 | describe('UploadService', () => { 6 | let service: UploadService; 7 | 8 | beforeEach(async () => { 9 | await TestBed.configureTestingModule({ 10 | imports: [AppTestModule.withDefaults()], 11 | }); 12 | service = TestBed.inject(UploadService); 13 | }); 14 | 15 | it('is defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/app-web/src/assets/archive.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/assets/archive.jpeg -------------------------------------------------------------------------------- /packages/app-web/src/assets/feedless.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/assets/feedless.jpeg -------------------------------------------------------------------------------- /packages/app-web/src/assets/i18n/upcoming/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "perimeter": "Perimeter", 3 | "location": "Your Location", 4 | "today": "Today", 5 | "no_events_found": "No events found", 6 | "place_distance": "{{distance}} Km distanced", 7 | "events": "Events on {{date}} near {{place}}", 8 | "type_location": "Type your postcode pr city", 9 | "support": "Support", 10 | "imprint": "Imprint", 11 | "by": "Developed by Markus Ruepp based on feedless", 12 | "about": "Upcoming is a non-commercial side project to strengthen our small local communal structures" 13 | } 14 | -------------------------------------------------------------------------------- /packages/app-web/src/assets/icon/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/assets/icon/favicon.png -------------------------------------------------------------------------------- /packages/app-web/src/assets/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/assets/icons/icon-128x128.png -------------------------------------------------------------------------------- /packages/app-web/src/assets/icons/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/assets/icons/icon-144x144.png -------------------------------------------------------------------------------- /packages/app-web/src/assets/icons/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/assets/icons/icon-152x152.png -------------------------------------------------------------------------------- /packages/app-web/src/assets/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/assets/icons/icon-192x192.png -------------------------------------------------------------------------------- /packages/app-web/src/assets/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/assets/icons/icon-384x384.png -------------------------------------------------------------------------------- /packages/app-web/src/assets/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/assets/icons/icon-512x512.png -------------------------------------------------------------------------------- /packages/app-web/src/assets/icons/icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/assets/icons/icon-72x72.png -------------------------------------------------------------------------------- /packages/app-web/src/assets/icons/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/assets/icons/icon-96x96.png -------------------------------------------------------------------------------- /packages/app-web/src/assets/mail-digest.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/assets/mail-digest.jpeg -------------------------------------------------------------------------------- /packages/app-web/src/assets/reader.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/assets/reader.jpeg -------------------------------------------------------------------------------- /packages/app-web/src/assets/rss-proxy.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/assets/rss-proxy.jpeg -------------------------------------------------------------------------------- /packages/app-web/src/assets/tg-bot-start.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/assets/tg-bot-start.webp -------------------------------------------------------------------------------- /packages/app-web/src/assets/tg-connect.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/assets/tg-connect.webp -------------------------------------------------------------------------------- /packages/app-web/src/assets/tg-link.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/assets/tg-link.webp -------------------------------------------------------------------------------- /packages/app-web/src/assets/tg-linked.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/assets/tg-linked.webp -------------------------------------------------------------------------------- /packages/app-web/src/assets/tg-push.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/assets/tg-push.webp -------------------------------------------------------------------------------- /packages/app-web/src/assets/tg-repo-settings.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/assets/tg-repo-settings.webp -------------------------------------------------------------------------------- /packages/app-web/src/assets/tg-search.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/assets/tg-search.webp -------------------------------------------------------------------------------- /packages/app-web/src/assets/untold.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/assets/untold.jpeg -------------------------------------------------------------------------------- /packages/app-web/src/assets/upcoming.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/assets/upcoming.jpeg -------------------------------------------------------------------------------- /packages/app-web/src/assets/video-preview.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/assets/video-preview.webp -------------------------------------------------------------------------------- /packages/app-web/src/assets/visual-diff.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/app-web/src/assets/visual-diff.jpeg -------------------------------------------------------------------------------- /packages/app-web/src/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/app-web/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | import { AppEnvironment } from '../app/app.module'; 2 | import { GqlVertical } from '../generated/graphql'; 3 | 4 | export const environment: AppEnvironment = { 5 | production: true, 6 | offlineSupport: false, 7 | product: GqlVertical.Feedless, 8 | officialFeedlessUrl: 'https://feedless.org', 9 | }; 10 | -------------------------------------------------------------------------------- /packages/app-web/src/test/apollo-client.mock.ts: -------------------------------------------------------------------------------- 1 | import { ApolloClient } from '@apollo/client/core'; 2 | 3 | export function createApolloClientMock( 4 | query: jasmine.Spy, 5 | subscribe: jasmine.Spy, 6 | mutate: jasmine.Spy, 7 | ): ApolloClient { 8 | return { 9 | query, 10 | subscribe, 11 | mutate, 12 | } as any; 13 | } 14 | -------------------------------------------------------------------------------- /packages/app-web/src/test/sw-update.mock.ts: -------------------------------------------------------------------------------- 1 | import { Observable, Subject } from 'rxjs'; 2 | import { VersionEvent } from '@angular/service-worker'; 3 | 4 | export class SwUpdateMock { 5 | // public available: Observable = new Subject(); 6 | // public activated: Observable = new Subject(); 7 | public versionUpdates: Observable = new Subject(); 8 | public isEnabled: boolean = false; 9 | 10 | public checkForUpdate(): Promise { 11 | return new Promise((resolve) => resolve()); 12 | } 13 | public activateUpdate(): Promise { 14 | return new Promise((resolve) => resolve()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/app-web/src/zone-flags.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Prevents Angular change detection from 3 | * running with certain Web Component callbacks 4 | */ 5 | // eslint-disable-next-line no-underscore-dangle 6 | (window as any).__Zone_disable_customElements = true; 7 | (window as any)['__zone_symbol__PASSIVE_EVENTS'] = ['scroll']; 8 | -------------------------------------------------------------------------------- /packages/app-web/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": ["src/main.ts"], 9 | "include": ["src/**/*.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/app-web/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": ["jasmine"] 7 | }, 8 | "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/server-core/.dockerignore: -------------------------------------------------------------------------------- 1 | build 2 | !build/libs 3 | !build/app 4 | out 5 | src 6 | .gradle 7 | libs 8 | -------------------------------------------------------------------------------- /packages/server-core/README.md: -------------------------------------------------------------------------------- 1 | # Server core 2 | -------------------------------------------------------------------------------- /packages/server-core/fetchGithubJars.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #PERTWEE_JAR="./libs/pertwee-1.1.0.jar" 4 | #if test -f ${PERTWEE_JAR};then 5 | # echo "$PERTWEE_JAR exists" 6 | #else 7 | # echo "$PERTWEE_JAR does not exist" 8 | # wget -q https://github.com/devilgate/pertwee/releases/download/v1.1.0/pertwee-1.1.0.zip 9 | # echo "Successfully downloaded $PERTWEE_JAR" 10 | # unzip pertwee-1.1.0.zip -d ./libs/ 11 | #fi 12 | -------------------------------------------------------------------------------- /packages/server-core/pre-boot.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | if test -z "$DNS_TEST_URL" 4 | then 5 | echo "Skipping DNS check" 6 | else 7 | echo "Verifying DNS resolution using $DNS_TEST_URL" 8 | nslookup $DNS_TEST_URL | grep $DNS_TEST_URL 9 | fi 10 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/actions/ScrapeActionDAO.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.actions 2 | 3 | import org.migor.feedless.AppLayer 4 | import org.migor.feedless.AppProfiles 5 | import org.springframework.context.annotation.Profile 6 | import org.springframework.data.jpa.repository.JpaRepository 7 | import org.springframework.stereotype.Repository 8 | import java.util.* 9 | 10 | @Repository 11 | @Profile("${AppProfiles.scrape} & ${AppLayer.repository}") 12 | interface ScrapeActionDAO : JpaRepository { 13 | fun findAllBySourceId(id: UUID): List 14 | } 15 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/actions/XPathConstraint.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.actions 2 | 3 | import jakarta.validation.Constraint 4 | import jakarta.validation.Payload 5 | import kotlin.reflect.KClass 6 | 7 | 8 | @Constraint(validatedBy = [XPathValidator::class]) 9 | @Target(AnnotationTarget.FIELD) 10 | @Retention(AnnotationRetention.RUNTIME) 11 | annotation class XPathConstraint( 12 | val message: String = "Invalid xpath value", 13 | val groups: Array> = [], 14 | val payload: Array> = [], 15 | ) 16 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/analytics/Tracked.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.analytics 2 | 3 | @Target(AnnotationTarget.FUNCTION) 4 | @Retention(AnnotationRetention.RUNTIME) 5 | annotation class Tracked 6 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/annotation/AnnotationDAO.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.annotation 2 | 3 | import org.migor.feedless.AppLayer 4 | import org.migor.feedless.AppProfiles 5 | import org.springframework.context.annotation.Profile 6 | import org.springframework.data.jpa.repository.JpaRepository 7 | import org.springframework.stereotype.Repository 8 | import java.util.* 9 | 10 | @Repository 11 | @Profile("${AppProfiles.annotation} & ${AppLayer.repository}") 12 | interface AnnotationDAO : JpaRepository 13 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/api/ApiUrls.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.api 2 | 3 | object ApiUrls { 4 | const val transformFeed = "/api/tf" 5 | const val webToFeed = "/api/w2f" 6 | const val webToFeedVerbose = "/api/web-to-feed" 7 | 8 | // const val webToFeedFromRule = "/api/w2f/rule" 9 | // const val webToFeedFromChange = "/api/w2f/change" 10 | // const val legacyTransformFeed = "/api/legacy/tf" 11 | // const val legacyWebToFeed = "/api/legacy/w2f" 12 | const val mailForwardingAllow = "/mail/forwarding/allow" 13 | } 14 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/api/http/AppErrorController.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.api.http 2 | 3 | import jakarta.servlet.http.HttpServletRequest 4 | import jakarta.servlet.http.HttpServletResponse 5 | import org.springframework.boot.web.servlet.error.ErrorController 6 | import org.springframework.stereotype.Controller 7 | import org.springframework.web.bind.annotation.RequestMapping 8 | 9 | 10 | @Controller 11 | class AppErrorController : ErrorController { 12 | 13 | @RequestMapping("/error") 14 | fun errorHtml(request: HttpServletRequest, response: HttpServletResponse) { 15 | response.sendRedirect("/") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/api/throttle/Throttled.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.api.throttle 2 | 3 | @Target(AnnotationTarget.FUNCTION) 4 | @Retention(AnnotationRetention.RUNTIME) 5 | annotation class Throttled 6 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/attachment/AttachmentDAO.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.attachment 2 | 3 | import org.migor.feedless.AppLayer 4 | import org.migor.feedless.AppProfiles 5 | import org.springframework.context.annotation.Profile 6 | import org.springframework.data.jpa.repository.JpaRepository 7 | import org.springframework.stereotype.Repository 8 | import java.util.* 9 | 10 | @Repository 11 | @Profile("${AppProfiles.attachment} & ${AppLayer.repository}") 12 | interface AttachmentDAO : JpaRepository 13 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/common/InMemorySinkRepository.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.common 2 | 3 | import reactor.core.publisher.FluxSink 4 | 5 | open class InMemorySinkRepository { 6 | private val sinkMap: MutableMap> = mutableMapOf() 7 | fun pop(otp: K): FluxSink { 8 | val sink = sinkMap[otp] 9 | remove(otp) 10 | return sink!! 11 | } 12 | 13 | fun store(otp: K, sink: FluxSink) { 14 | sinkMap[otp] = sink 15 | } 16 | 17 | fun remove(otp: K) { 18 | sinkMap.remove(otp) 19 | } 20 | 21 | fun isEmpty(): Boolean { 22 | return sinkMap.isEmpty() 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/community/CommentDAO.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.community 2 | 3 | import org.migor.feedless.AppLayer 4 | import org.migor.feedless.AppProfiles 5 | import org.springframework.context.annotation.Profile 6 | import org.springframework.data.jpa.repository.JpaRepository 7 | import org.springframework.stereotype.Repository 8 | import java.util.* 9 | 10 | @Repository 11 | @Profile("${AppProfiles.community} & ${AppLayer.repository}") 12 | interface CommentDAO : JpaRepository 13 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/community/CommentEntity.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.community 2 | 3 | import jakarta.persistence.Column 4 | import jakarta.persistence.DiscriminatorValue 5 | import jakarta.persistence.Entity 6 | import org.migor.feedless.document.DocumentEntity 7 | 8 | @Entity 9 | @DiscriminatorValue("comment") 10 | open class CommentEntity : DocumentEntity() { 11 | 12 | @Column(name = "is_original_poster") 13 | open var isOriginalPoster: Boolean = false 14 | } 15 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/community/StoryEntity.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.community 2 | 3 | import jakarta.persistence.DiscriminatorValue 4 | import jakarta.persistence.Entity 5 | import org.migor.feedless.repository.RepositoryEntity 6 | 7 | @Entity 8 | @DiscriminatorValue("story") 9 | open class StoryEntity : RepositoryEntity() 10 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/config/CacheEventLogger.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.config 2 | 3 | import org.ehcache.event.CacheEvent 4 | import org.ehcache.event.CacheEventListener 5 | import org.slf4j.LoggerFactory 6 | 7 | class CacheEventLogger : CacheEventListener { 8 | 9 | private val log = LoggerFactory.getLogger(CacheEventLogger::class.simpleName) 10 | 11 | override fun onEvent(event: CacheEvent?) { 12 | log.debug("${event?.key} ${event?.type}") 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/config/CustomSQLDialect.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.config 2 | 3 | import org.hibernate.dialect.PostgreSQLDialect 4 | 5 | 6 | class CustomSQLDialect : PostgreSQLDialect() 7 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/config/SystemSettingsDAO.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.config 2 | 3 | import org.migor.feedless.AppLayer 4 | import org.springframework.context.annotation.Profile 5 | import org.springframework.data.jpa.repository.JpaRepository 6 | import org.springframework.stereotype.Repository 7 | import java.util.* 8 | 9 | @Repository 10 | @Profile(AppLayer.repository) 11 | interface SystemSettingsDAO : JpaRepository { 12 | fun findByName(name: String): SystemSettingsEntity? 13 | } 14 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/config/TemplateConfig.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.config 2 | 3 | import org.springframework.context.annotation.Bean 4 | import org.springframework.context.annotation.Configuration 5 | import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer 6 | 7 | @Configuration 8 | class TemplateConfig { 9 | 10 | @Bean 11 | fun createFreeMarkerConfigurer(): FreeMarkerConfigurer { 12 | val configurer = FreeMarkerConfigurer() 13 | configurer.setTemplateLoaderPath("classpath:/markup-templates") 14 | configurer.setDefaultEncoding("UTF-8") 15 | return configurer 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/data/jpa/enums/EntityVisibility.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.data.jpa.enums 2 | 3 | enum class EntityVisibility { 4 | isPublic, 5 | isPrivate, 6 | } 7 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/data/jpa/enums/ReleaseStatus.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.data.jpa.enums 2 | 3 | enum class ReleaseStatus { 4 | released, 5 | unreleased, 6 | } 7 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/feature/FeatureDAO.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.feature 2 | 3 | import org.migor.feedless.AppLayer 4 | import org.migor.feedless.AppProfiles 5 | import org.springframework.context.annotation.Profile 6 | import org.springframework.data.jpa.repository.JpaRepository 7 | import org.springframework.stereotype.Repository 8 | import java.util.* 9 | 10 | @Repository 11 | @Profile("${AppProfiles.features} & ${AppLayer.repository}") 12 | interface FeatureDAO : JpaRepository { 13 | fun findByName(name: String): FeatureEntity? 14 | } 15 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/feature/FeatureStates.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.feature 2 | 3 | enum class FeatureScope { 4 | backend, 5 | frontend 6 | } 7 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/feature/FeatureValueType.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.feature 2 | 3 | enum class FeatureValueType { 4 | bool, 5 | number, 6 | numberRange 7 | } 8 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/feed/discovery/RemoteNativeFeedRef.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.feed.discovery 2 | 3 | import org.migor.feedless.feed.parser.FeedType 4 | 5 | 6 | data class RemoteNativeFeedRef(val url: String, val type: FeedType, val title: String, val description: String? = null) 7 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/feed/exporter/FeedlessModule.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.feed.exporter 2 | 3 | import com.rometools.rome.feed.module.Module 4 | import java.io.Serializable 5 | import java.util.* 6 | 7 | interface FeedlessModule : Module, Serializable { 8 | 9 | fun getStartingAt(): Date? 10 | 11 | fun setStartingAt(date: Date?) 12 | 13 | fun setLatLng(value: String?) 14 | fun getLatLng(): String? 15 | 16 | fun setData(data: String?) 17 | fun getData(): String? 18 | 19 | fun setDataType(type: String?) 20 | fun getDataType(): String? 21 | 22 | fun getPage(): Int? 23 | fun setPage(value: Int?) 24 | } 25 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/feed/parser/FeedBodyParser.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.feed.parser 2 | 3 | import org.migor.feedless.common.HttpResponse 4 | import org.migor.feedless.feed.parser.json.JsonFeed 5 | 6 | interface FeedBodyParser { 7 | fun priority(): Int 8 | fun canProcess(feedType: FeedType): Boolean 9 | suspend fun process(response: HttpResponse): JsonFeed 10 | } 11 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/feed/parser/NullFeedParser.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.feed.parser 2 | 3 | import org.migor.feedless.common.HttpResponse 4 | import org.migor.feedless.feed.parser.json.JsonFeed 5 | 6 | class NullFeedParser : FeedBodyParser { 7 | 8 | override fun priority(): Int { 9 | return 0 10 | } 11 | 12 | override fun canProcess(feedType: FeedType): Boolean { 13 | return true 14 | } 15 | 16 | override suspend fun process(response: HttpResponse): JsonFeed { 17 | throw IllegalArgumentException("No feed parser found for ${response.contentType}") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/feed/parser/json/JsonAttachment.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.feed.parser.json 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import java.io.Serializable 5 | 6 | open class JsonAttachment( 7 | @SerializedName("size_in_bytes") 8 | var length: Long?, 9 | @SerializedName("mime_type") 10 | var type: String, 11 | @SerializedName("url") 12 | var url: String, 13 | @SerializedName("duration_in_seconds") 14 | var duration: Long? 15 | ) : Serializable 16 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/feed/parser/json/JsonAuthor.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.feed.parser.json 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import java.io.Serializable 5 | 6 | data class JsonAuthor( 7 | @SerializedName(value = "name") 8 | val name: String?, 9 | @SerializedName(value = "url") 10 | val url: String? = null, 11 | @SerializedName(value = "avatar") 12 | val avatar: String? = null, 13 | @SerializedName(value = "email") 14 | val email: String? = null 15 | ) : Serializable 16 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/feed/parser/json/OpenSearchQuery.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.feed.parser.json 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | open class OpenSearchQuery { 6 | @SerializedName(value = "shortName") 7 | lateinit var shortName: String 8 | } 9 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/group/GroupDAO.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.group 2 | 3 | import org.migor.feedless.AppLayer 4 | import org.migor.feedless.AppProfiles 5 | import org.springframework.context.annotation.Profile 6 | import org.springframework.data.jpa.repository.JpaRepository 7 | import org.springframework.stereotype.Repository 8 | import java.util.* 9 | 10 | @Repository 11 | @Profile("${AppProfiles.user} & ${AppLayer.repository}") 12 | interface GroupDAO : JpaRepository { 13 | fun findByName(name: String): GroupEntity? 14 | } 15 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/license/LicenseDAO.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.license 2 | 3 | import org.migor.feedless.AppLayer 4 | import org.migor.feedless.AppProfiles 5 | import org.springframework.context.annotation.Profile 6 | import org.springframework.data.jpa.repository.JpaRepository 7 | import org.springframework.stereotype.Repository 8 | import java.util.* 9 | 10 | @Repository 11 | @Profile("${AppProfiles.license} & ${AppLayer.repository}") 12 | interface LicenseDAO : JpaRepository { 13 | fun findAllByOrderId(orderId: UUID): List 14 | } 15 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/mail/OneTimePasswordDAO.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.mail 2 | 3 | import org.migor.feedless.AppLayer 4 | import org.migor.feedless.AppProfiles 5 | import org.springframework.context.annotation.Profile 6 | import org.springframework.data.jpa.repository.JpaRepository 7 | import org.springframework.stereotype.Repository 8 | import java.time.LocalDateTime 9 | import java.util.* 10 | 11 | @Repository 12 | @Profile("${AppProfiles.mail} & ${AppLayer.repository}") 13 | interface OneTimePasswordDAO : JpaRepository { 14 | fun deleteAllByValidUntilBefore(now: LocalDateTime) 15 | } 16 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/pipeline/FeedlessPlugin.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.pipeline 2 | 3 | interface FeedlessPlugin { 4 | fun id(): String 5 | 6 | fun name(): String 7 | 8 | fun listed(): Boolean 9 | } 10 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/pipeline/FilterEntityPlugin.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.pipeline 2 | 3 | import org.migor.feedless.actions.PluginExecutionJsonEntity 4 | import org.migor.feedless.feed.parser.json.JsonItem 5 | import org.migor.feedless.scrape.LogCollector 6 | 7 | interface FilterEntityPlugin : FeedlessPlugin { 8 | 9 | suspend fun filterEntity( 10 | item: JsonItem, 11 | params: PluginExecutionJsonEntity, 12 | index: Int, 13 | logCollector: LogCollector 14 | ): Boolean 15 | 16 | } 17 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/pipeline/MapEntityPlugin.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.pipeline 2 | 3 | import org.migor.feedless.actions.PluginExecutionJsonEntity 4 | import org.migor.feedless.document.DocumentEntity 5 | import org.migor.feedless.repository.RepositoryEntity 6 | import org.migor.feedless.scrape.LogCollector 7 | 8 | interface MapEntityPlugin : FeedlessPlugin { 9 | 10 | suspend fun mapEntity( 11 | document: DocumentEntity, 12 | repository: RepositoryEntity, 13 | params: PluginExecutionJsonEntity, 14 | logCollector: LogCollector 15 | ): DocumentEntity 16 | 17 | } 18 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/report/ReportDAO.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.report 2 | 3 | import org.migor.feedless.AppLayer 4 | import org.migor.feedless.AppProfiles 5 | import org.springframework.context.annotation.Profile 6 | import org.springframework.data.jpa.repository.JpaRepository 7 | import org.springframework.stereotype.Repository 8 | import java.time.LocalDateTime 9 | import java.util.* 10 | 11 | @Repository 12 | @Profile("${AppProfiles.report} & ${AppLayer.repository}") 13 | interface ReportDAO : JpaRepository { 14 | } 15 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/report/SegmentationDAO.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.report 2 | 3 | import org.migor.feedless.AppLayer 4 | import org.migor.feedless.AppProfiles 5 | import org.springframework.context.annotation.Profile 6 | import org.springframework.data.jpa.repository.JpaRepository 7 | import org.springframework.stereotype.Repository 8 | import java.util.* 9 | 10 | @Repository 11 | @Profile("${AppProfiles.report} & ${AppLayer.repository}") 12 | interface SegmentationDAO : JpaRepository { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/repository/RepositoryEntity.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.repository 2 | 3 | import jakarta.persistence.DiscriminatorValue 4 | import jakarta.persistence.Entity 5 | import jakarta.persistence.Inheritance 6 | import jakarta.persistence.InheritanceType 7 | 8 | @Entity 9 | @Inheritance(strategy = InheritanceType.SINGLE_TABLE) 10 | @DiscriminatorValue("repository") 11 | open class RepositoryEntity : AbstractRepositoryEntity() 12 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/secrets/UserSecretType.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.secrets 2 | 3 | enum class UserSecretType { 4 | SecretKey, 5 | JWT 6 | } 7 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/user/GithubConnectionDAO.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.user 2 | 3 | import org.migor.feedless.AppLayer 4 | import org.migor.feedless.AppProfiles 5 | import org.springframework.context.annotation.Profile 6 | import org.springframework.data.jpa.repository.JpaRepository 7 | import org.springframework.stereotype.Repository 8 | import java.util.* 9 | 10 | @Repository 11 | @Profile("${AppProfiles.user} & ${AppLayer.repository}") 12 | interface GithubConnectionDAO : JpaRepository { 13 | 14 | fun existsByUserId(userId: UUID): Boolean 15 | fun existsByGithubId(githubId: String): Boolean 16 | } 17 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/user/GithubConnectionEntity.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.user 2 | 3 | import jakarta.persistence.Column 4 | import jakarta.persistence.DiscriminatorValue 5 | import jakarta.persistence.Entity 6 | 7 | @Entity 8 | @DiscriminatorValue("github") 9 | open class GithubConnectionEntity : ConnectedAppEntity() { 10 | @Column(name = "github_id") 11 | open var githubId: String? = null 12 | } 13 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/user/TelegramConnectionEntity.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.user 2 | 3 | import jakarta.persistence.Column 4 | import jakarta.persistence.DiscriminatorValue 5 | import jakarta.persistence.Entity 6 | 7 | @Entity 8 | @DiscriminatorValue("telegram") 9 | open class TelegramConnectionEntity : ConnectedAppEntity() { 10 | @Column(name = "chat_id") 11 | open var chatId: Long = 0 12 | } 13 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/util/JsonUtil.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.util 2 | 3 | import com.google.gson.GsonBuilder 4 | 5 | object JsonUtil { 6 | val gson = GsonBuilder().create() 7 | } 8 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/util/JtsUtil.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.util 2 | 3 | import org.locationtech.jts.geom.Coordinate 4 | import org.locationtech.jts.geom.GeometryFactory 5 | import org.locationtech.jts.geom.Point 6 | import org.locationtech.jts.geom.PrecisionModel 7 | 8 | object JtsUtil { 9 | fun createPoint(lat: Double, lon: Double): Point { 10 | val gf = GeometryFactory(PrecisionModel(), 4326) 11 | return gf.createPoint(Coordinate(lat, lon)) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/server-core/src/main/kotlin/org/migor/feedless/util/SafeGuards.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.util 2 | 3 | import java.io.InputStream 4 | 5 | object SafeGuards { 6 | 7 | fun respectMaxSize(stream: InputStream, maxBytes: Int = 4000000): ByteArray { 8 | val array = stream.readNBytes(maxBytes) 9 | if (stream.available() > 0) { 10 | throw IllegalArgumentException("maxBytes reached (${stream.available()} left)") 11 | } 12 | return array 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/application-analytics.properties: -------------------------------------------------------------------------------- 1 | app.analytics.plausibleUrl=${APP_PLAUSIBLE_URL} 2 | app.analytics.plausibleSite=feedless.org 3 | app.analytics.plausible-api-key=${APP_PLAUSIBLE_API_KEY} 4 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/application-debug.properties: -------------------------------------------------------------------------------- 1 | spring.jpa.properties.hibernate.format_sql=true 2 | spring.jpa.show-sql=true 3 | logging.level.org.hibernate.type=trace 4 | logging.level.org.springframework.security.web.csrf=debug 5 | logging.level.ch.qos.logback=off 6 | #spring.jpa.properties.hibernate.use_sql_comments=true 7 | #logging.file.name=./database.log 8 | #logging.pattern.file=%d{dd-MM-yyyy HH:mm:ss.SSS} [%thread] %-5level %logger{36}.%M - %msg%n 9 | 10 | logging.level.root=INFO 11 | #logging.level.WebToFeedTransformer=DEBUG 12 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/application-mail.properties: -------------------------------------------------------------------------------- 1 | app.mail.sender=${APP_MAIL_SENDER} 2 | spring.mail.host=${APP_MAIL_HOST} 3 | spring.mail.port=${APP_MAIL_PORT} 4 | spring.mail.username=${APP_MAIL_USERNAME} 5 | spring.mail.password=${APP_MAIL_PASSWORD} 6 | spring.mail.properties.mail.smtp.auth=true 7 | spring.mail.properties.mail.smtp.connectiontimeout=10000 8 | spring.mail.properties.mail.smtp.timeout=10000 9 | spring.mail.properties.mail.smtp.writetimeout=10000 10 | spring.mail.test-connection=true 11 | spring.mail.properties.mail.smtp.starttls.enable=true 12 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/application-metrics.properties: -------------------------------------------------------------------------------- 1 | management.endpoint.metrics.enabled=true 2 | management.endpoints.web.exposure.include=* 3 | management.endpoint.prometheus.enabled=true 4 | #management.metrics.export.prometheus.enabled=true 5 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/application-saas.properties: -------------------------------------------------------------------------------- 1 | APP_PEM_FILE=./src/main/resources/certs/feedless.pem 2 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/application-sso.properties: -------------------------------------------------------------------------------- 1 | #spring.security.oauth2.client.registration.google.scope=email, profile 2 | #spring.security.oauth2.client.registration.google.redirect-uri=${app.apiGatewayUrl}/login/oauth2/code/google 3 | spring.security.oauth2.client.registration.github.redirect-uri=${app.apiGatewayUrl}/login/oauth2/code/github 4 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/application-telegram.properties: -------------------------------------------------------------------------------- 1 | app.telegram.bot.token = ${APP_TELEGRAM_TOKEN} 2 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/certs/.gitignore: -------------------------------------------------------------------------------- 1 | *.pem 2 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/certs/feedless.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqLwr0P4skIjt0VH0R+7M 3 | 21d0F4sMlbuk4EQxe5Iv+mlGIJ59U60lpEj7Pc/nhcpypb35R0qKJth631dr/7Fa 4 | wsvNZ8GYAullR/yaxuH9o362WOYsSURHKmr8mSXnFyBDCNMWSfL9225M4+M8sHV1 5 | FBlnspPkMALxHeDK8Lc4ehwje2Wbxohs99ijxzblEbOs0EhSq/Ff6hFlgpJVpZt6 6 | gHAcMf90Nl/yYUm2+hDpdyrdF/Hxn82FtgKKZZyM/XcBHnYVyshKSSd9JJ60jpsu 7 | SBdLciLPWgy9wYtWYJ8FJW2lszSLILOjqCjEnfAdRHaO6a5rLFUKZ+BbTu18ueyt 8 | EQIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/.gitignore: -------------------------------------------------------------------------------- 1 | *.sql 2 | !migration/*.sql 3 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V12__add_tags.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE t_document 2 | ADD COLUMN tags text[]; 3 | ALTER TABLE t_scrape_source 4 | ADD COLUMN tags text[]; 5 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V13__rename_scrape_source.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE t_scrape_source 2 | RENAME TO t_source; 3 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V14__attachment_field_length.sql: -------------------------------------------------------------------------------- 1 | alter table t_attachment 2 | alter column original_url type varchar(1000); 3 | 4 | alter table t_attachment 5 | alter column remote_data_url type varchar(1000); 6 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V15__attachment_add_size.sql: -------------------------------------------------------------------------------- 1 | alter table t_attachment 2 | add column size bigint; 3 | 4 | alter table t_attachment 5 | add column duration bigint; 6 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V16__add_schem_version_fields.sql: -------------------------------------------------------------------------------- 1 | alter table t_pipeline_job 2 | add column schema_version int not null default 0; 3 | alter table t_source 4 | add column schema_version int not null default 0; 5 | alter table t_repository 6 | add column schema_version int not null default 0; 7 | 8 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V18__refactor_user.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE ONLY t_user 2 | DROP CONSTRAINT uniqueuser; 3 | ALTER TABLE ONLY t_user 4 | ADD CONSTRAINT uniqueuser UNIQUE (email); 5 | 6 | ALTER TABLE t_user 7 | ADD COLUMN first_name character varying(255); 8 | ALTER TABLE t_user 9 | ADD COLUMN last_name character varying(255); 10 | ALTER TABLE t_user 11 | ADD COLUMN country character varying(255); 12 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V19__correct_user_email.sql: -------------------------------------------------------------------------------- 1 | UPDATE t_user 2 | SET email = concat(id, '@feedless.com') 3 | WHERE email IS NULL OR email = ''; 4 | 5 | ALTER TABLE t_user 6 | ALTER COLUMN email set NOT NULL; 7 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V21__create_table_license.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS t_license 2 | ( 3 | id uuid NOT NULL, 4 | created_at timestamp(6) without time zone NOT NULL, 5 | billing_id uuid NOT NULL, 6 | payload character varying(1000) NOT NULL, 7 | CONSTRAINT t_license_pkey PRIMARY KEY (id), 8 | CONSTRAINT fkmj0osl4etytha4j8ytxake8jb FOREIGN KEY (billing_id) 9 | REFERENCES t_billing (id) MATCH SIMPLE 10 | ON UPDATE NO ACTION 11 | ON DELETE CASCADE 12 | ); 13 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V25__patch_feature_value.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE t_feature_value ALTER COLUMN value_int TYPE bigint; 2 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V26__patch_user_usage.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE t_user DROP COLUMN product; 2 | ALTER TABLE t_user ADD COLUMN total_usage_mb double precision; 3 | UPDATE t_user SET total_usage_mb = 0.0 WHERE total_usage_mb IS NULL; 4 | ALTER TABLE t_user ALTER COLUMN total_usage_mb SET NOT NULL; 5 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V27__patch_scrape-source_add_geo.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE t_source ADD COLUMN lat float; 2 | ALTER TABLE t_source ADD COLUMN lon float; 3 | ALTER TABLE t_source ADD COLUMN lat_lon_caption varchar(100); 4 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V28__patch_document_add_geo.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE t_document ADD COLUMN lat float; 2 | ALTER TABLE t_document ADD COLUMN lon float; 3 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V29__rename_cloud_subscription_to_plan.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE t_cloud_subscription rename to t_plan 2 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V2__patch_user.sql: -------------------------------------------------------------------------------- 1 | alter table t_user add column purgeScheduledFor timestamp(6) without time zone; 2 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V31__all_actions.sql: -------------------------------------------------------------------------------- 1 | 2 | ALTER TABLE t_browser_action RENAME TO t_scrape_action; 3 | 4 | ALTER TABLE t_scrape_action ADD COLUMN pos INT; 5 | UPDATE t_scrape_action SET pos = 0 WHERE pos is null; 6 | ALTER TABLE t_scrape_action ALTER COLUMN pos SET not null; 7 | 8 | ALTER TABLE t_source DROP COLUMN timeout; 9 | ALTER TABLE t_source DROP COLUMN url; 10 | ALTER TABLE t_source DROP COLUMN viewport; 11 | ALTER TABLE t_source DROP COLUMN IF EXISTS wait_until; 12 | ALTER TABLE t_source DROP COLUMN additional_wait_sec; 13 | ALTER TABLE t_source DROP COLUMN emit; 14 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V33__repo_share_key.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE t_repository ADD COLUMN share_key VARCHAR(10); 2 | UPDATE t_repository SET share_key='' WHERE share_key IS NULL; 3 | ALTER TABLE t_repository ALTER COLUMN share_key SET NOT NULL; 4 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V34__cron_run.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS t_repository_cron_run 2 | ( 3 | id uuid NOT NULL, 4 | created_at timestamp(6) without time zone NOT NULL, 5 | executed_at timestamp(6) without time zone NOT NULL, 6 | successful boolean NOT NULL, 7 | message oid NOT NULL, 8 | repository_id uuid NOT NULL, 9 | CONSTRAINT t_repository_cron_run_pkey PRIMARY KEY (id), 10 | CONSTRAINT fk_repository_run__to__repository FOREIGN KEY (repository_id) 11 | REFERENCES t_repository (id) MATCH SIMPLE 12 | ON UPDATE NO ACTION 13 | ON DELETE CASCADE 14 | ); 15 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V35__retention_age_by_field.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE t_repository ADD COLUMN retention_max_age_days_field character varying(50); 2 | UPDATE t_repository SET retention_max_age_days_field = 'publishedAt' WHERE retention_max_age_days_field IS NULL; 3 | ALTER TABLE t_repository ALTER retention_max_age_days_field SET NOT NULL; 4 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V36__patch_cron_run.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE t_repository_cron_run DROP CONSTRAINT fk_repository_run__to__repository; 2 | ALTER TABLE t_repository_cron_run RENAME COLUMN repository_id TO source_id; 3 | 4 | ALTER TABLE t_repository_cron_run ADD CONSTRAINT fk_repository_run__to__source FOREIGN KEY (source_id) 5 | REFERENCES t_source (id) MATCH SIMPLE 6 | ON UPDATE NO ACTION 7 | ON DELETE CASCADE; 8 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V37__rename_repo_cron_run.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE t_repository_cron_run RENAME TO t_source_pipeline_job; 2 | ALTER TABLE t_source_pipeline_job RENAME CONSTRAINT fk_repository_run__to__source TO fk_source_job__to__source; 3 | 4 | ALTER TABLE t_pipeline_job RENAME TO t_document_pipeline_job; 5 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V39__patch_scrape_job.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE t_pipeline_job ADD COLUMN url character varying(500); 2 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V3__generalize_plugins.sql: -------------------------------------------------------------------------------- 1 | alter table t_feed_native 2 | add column plugins jsonb NOT NULL default '[]', 3 | drop column inlineImages, 4 | drop column harvestItems; 5 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V40__lat_lon_distance.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE FUNCTION fl_latlon_distance(a_lat_lon geometry, b_lat double precision, b_lon double precision) RETURNS double precision 2 | LANGUAGE SQL 3 | IMMUTABLE 4 | RETURNS NULL ON NULL INPUT 5 | RETURN (st_distance(st_transform(a_lat_lon, 3857), st_transform(st_point(b_lat, b_lon, 4326), 3857)) * cosd(42.3521)) / 1000.0; 6 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V41__notifications.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE ONLY t_notification 2 | ADD COLUMN source_id uuid; 3 | ALTER TABLE ONLY t_notification 4 | ADD CONSTRAINT fk_notification__to__source FOREIGN KEY (source_id) REFERENCES t_source(id) ON DELETE CASCADE; 5 | 6 | ALTER TABLE ONLY t_notification 7 | ADD COLUMN repository_id uuid; 8 | ALTER TABLE ONLY t_notification 9 | ADD CONSTRAINT fk_notification__to__repository FOREIGN KEY (repository_id) REFERENCES t_repository(id) ON DELETE CASCADE; 10 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V43__alter_repo_for_product.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE t_repository DROP COLUMN for_product; 2 | ALTER TABLE t_repository ADD COLUMN for_product varchar(100); 3 | 4 | UPDATE t_repository SET for_product ='rssProxy' WHERE for_product IS NULL; 5 | ALTER TABLE t_repository ALTER COLUMN for_product SET NOT NULL; 6 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V44__timestamp_func.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE FUNCTION fl_trunc_timestamp_as_millis(ts timestamp without time zone) RETURNS bigint 2 | LANGUAGE SQL 3 | IMMUTABLE 4 | RETURNS NULL ON NULL INPUT 5 | RETURN EXTRACT(EPOCH FROM date_trunc('day', ts)) * 1000; 6 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V46__indexes.sql: -------------------------------------------------------------------------------- 1 | create index url__idx on t_document (url); 2 | create index repository_id__idx on t_document (repository_id); 3 | 4 | create index feature_group_id__idx on t_feature_value (feature_group_id); 5 | create index feature_id__idx on t_feature_value (feature_id); 6 | 7 | create index name__idx on t_feature (name); 8 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V47__add_source_to_document.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE t_document ADD COLUMN source_id uuid; 2 | ALTER TABLE t_document ADD 3 | CONSTRAINT fk_document__to__source FOREIGN KEY (source_id) 4 | REFERENCES t_source (id) MATCH SIMPLE 5 | ON UPDATE NO ACTION 6 | ON DELETE CASCADE 7 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V48__increase_url_length.sql: -------------------------------------------------------------------------------- 1 | alter table t_attachment 2 | alter column original_url type varchar(1500); 3 | 4 | alter table t_attachment 5 | alter column remote_data_url type varchar(1500); 6 | 7 | alter table t_pipeline_job 8 | alter column url type varchar(1500); 9 | 10 | alter table t_document 11 | alter column url type varchar(1500); 12 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V49__fix_repo_share_key.sql: -------------------------------------------------------------------------------- 1 | UPDATE t_repository SET share_key = substring(gen_random_uuid()::varchar, 0, 9) where t_repository.visibility = 'isPrivate' and share_key=''; 2 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V4__add_plugin_to_user.sql: -------------------------------------------------------------------------------- 1 | alter table t_user 2 | add column plugins jsonb NOT NULL default '{}'; 3 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V51__source_error_count.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE t_source ADD COLUMN errors_in_succession integer; 2 | 3 | UPDATE t_source SET errors_in_succession = 0 WHERE errors_in_succession IS NULL; 4 | 5 | ALTER TABLE t_source ALTER COLUMN errors_in_succession SET NOT NULL ; 6 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V53__normalize_names.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE t_user_secret RENAME lastusedat to last_used_at; 2 | ALTER TABLE t_user_secret RENAME validuntil to valid_until; 3 | ALTER TABLE t_agent RENAME openinstance to open_instance; 4 | ALTER TABLE t_agent RENAME lastsyncedat to last_synced_at; 5 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V54__annotate_repository.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE t_annotation ALTER COLUMN document_id DROP NOT NULL; 2 | ALTER TABLE t_annotation ADD COLUMN repository_id uuid; 3 | 4 | ALTER TABLE ONLY t_annotation 5 | ADD CONSTRAINT fk_annotation__to__repository FOREIGN KEY (id) REFERENCES t_annotation(id); 6 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V56__system_settings.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t_system_settings ( 2 | id uuid NOT NULL, 3 | created_at timestamp(6) without time zone NOT NULL, 4 | name character varying(255) NOT NULL, 5 | value_bool boolean, 6 | value_int integer, 7 | value_text character varying(255) 8 | ); 9 | 10 | ALTER TABLE ONLY t_system_settings 11 | ADD CONSTRAINT unique_system_settin_name UNIQUE (name); 12 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V57__github_to_connected_apps.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO t_connected_app(id, created_at, is_authorized, authorized_at, chat_id, user_id, github_id, app) 2 | SELECT gen_random_uuid(), created_at, true, created_at, null, id, githubid, 'github' FROM t_user 3 | WHERE githubid IS NOT NULL; 4 | 5 | ALTER TABLE t_user DROP COLUMN githubid; 6 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V58__push_notification.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE t_repository 2 | ADD COLUMN push_notifications_muted boolean; 3 | 4 | UPDATE t_repository SET push_notifications_muted=false WHERE push_notifications_muted IS NULL; 5 | 6 | ALTER TABLE t_repository 7 | ALTER COLUMN push_notifications_muted SET NOT NULL; 8 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V59__inbox_repo.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE t_user RENAME COLUMN notification_repository_id TO inbox_repository_id; 2 | 3 | UPDATE t_repository 4 | SET title='Inbox' 5 | WHERE t_repository.id IN ( 6 | SELECT inbox_repository_id FROM t_user WHERE inbox_repository_id IS NOT NULL 7 | ); 8 | 9 | ALTER TABLE IF EXISTS t_user 10 | RENAME CONSTRAINT fk_user__to__notifications_repository TO fk_user__to__inbox_repository; 11 | 12 | UPDATE t_repository 13 | SET description='Messages from telegram and ops notifications' 14 | WHERE t_repository.id IN ( 15 | SELECT inbox_repository_id FROM t_user WHERE inbox_repository_id IS NOT NULL 16 | ); 17 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V5__plugins_in_webdocument.sql: -------------------------------------------------------------------------------- 1 | alter table t_web_document 2 | add column executed_plugins jsonb NOT NULL default '[]'; 3 | 4 | alter table t_web_document 5 | rename column plugins to pending_plugins; 6 | 7 | alter table t_importer 8 | add column plugins jsonb NOT NULL default '[]'; 9 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V61__fix_annotation_fks.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE IF EXISTS t_annotation_text DROP CONSTRAINT IF EXISTS fk2wrv2qn79kvxyjtbe7mach2vg; 2 | 3 | ALTER TABLE t_annotation_text 4 | ADD CONSTRAINT fk_annotation_entity FOREIGN KEY (id) 5 | REFERENCES t_annotation(id) 6 | ON DELETE CASCADE 7 | ON UPDATE NO ACTION; 8 | 9 | ALTER TABLE IF EXISTS t_annotation_vote DROP CONSTRAINT IF EXISTS fk8v9srbb30wqn2nbn8t3yy5i1e; 10 | 11 | ALTER TABLE t_annotation_vote 12 | ADD CONSTRAINT fk_annotation_entity FOREIGN KEY (id) 13 | REFERENCES t_annotation(id) 14 | ON DELETE CASCADE 15 | ON UPDATE NO ACTION; 16 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V62__adds_document_hash.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE t_document ADD COLUMN content_hash character varying(50); 2 | 3 | UPDATE t_document SET content_hash = '' where content_hash IS NULL; 4 | 5 | ALTER TABLE t_document ALTER COLUMN content_hash SET NOT NULL ; 6 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V64__re-enable_sources.sql: -------------------------------------------------------------------------------- 1 | UPDATE t_source SET is_disabled=false where is_disabled=true; 2 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V65__fix_chronounit_value.sql: -------------------------------------------------------------------------------- 1 | UPDATE t_priced_product SET recurring_interval='MONTHS' where recurring_interval = 'MONTHLY'; 2 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V67__source_with_pulls.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE t_source ADD COLUMN last_records_retrieved integer; 2 | 3 | UPDATE t_source SET last_records_retrieved = 0 WHERE last_records_retrieved IS NULL; 4 | 5 | ALTER TABLE t_source ALTER COLUMN last_records_retrieved SET NOT NULL ; 6 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V68__fix_report.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE IF EXISTS t_segment 2 | DROP COLUMN time_interval; 3 | ALTER TABLE IF EXISTS t_segment 4 | ADD COLUMN time_segment__increment character varying(255); 5 | UPDATE t_segment SET time_segment__increment='WEEKLY' where time_segment__increment is null; 6 | ALTER TABLE t_segment ALTER COLUMN time_segment__increment SET NOT NULL; 7 | 8 | ALTER TABLE t_report 9 | RENAME COLUMN email TO recipient_email; 10 | ALTER TABLE IF EXISTS t_report 11 | ADD COLUMN recipient_name character varying(255) NOT NULL; 12 | ALTER TABLE t_report DROP COLUMN repository_id; 13 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V69__add_indixes_to_common_fields.sql: -------------------------------------------------------------------------------- 1 | CREATE INDEX document_latlon_idx 2 | ON t_document 3 | USING GIST (lat_lon); 4 | 5 | CREATE INDEX document_published_at_idx 6 | ON t_document (released_at); 7 | 8 | CREATE INDEX document_starting_at_idx 9 | ON t_document (starting_at); 10 | 11 | CREATE INDEX document_created_at_idx 12 | ON t_document (created_at); 13 | 14 | CREATE INDEX repository_created_at_idx 15 | ON t_repository (created_at); 16 | 17 | CREATE INDEX source_created_at_idx 18 | ON t_source (created_at); 19 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V6__alter_feed_native.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE t_feed_native 2 | drop CONSTRAINT if exists uk_6v95mi2ep5qw29314u15vwsj3; 3 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V70__disactivate_push.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE t_repository RENAME COLUMN push_notifications_muted TO push_notifications_enabled; 2 | UPDATE t_repository SET push_notifications_enabled = TRUE WHERE push_notifications_enabled IS FALSE; 3 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V71__source_last_refreshed_at.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE t_source ADD COLUMN last_refreshed_at timestamp(6) without time zone; 2 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V7__alter_feed_native__rename_lastUpdatedAt.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE t_feed_native 2 | rename column lastupdatedat to lastcheckedat; 3 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V8__alter_feed_native__add_errorMessage.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE t_feed_native 2 | add column errormessage character varying(255); 3 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/db/migration/V9__alter_feed_native__add_harvestRateFixed.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE t_feed_native 2 | add column harvestRateFixed boolean not null default false; 3 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/debug-sample.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/server-core/src/main/resources/debug-sample.pdf -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/markup-templates/mail-auth-code.ftl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 |

Hi,

13 |

you request access to ${domain}. Please enter the following code (valid until ${otp.validUntil} minutes).

14 | 15 |
16 | ${code} 17 |
18 | 19 |
(${description}, CorrelationId: ${corrId})
20 | 21 | 22 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/markup-templates/mail-visual-diff-change-detected.ftl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 |

Hi,

13 |

your tracker ${trackerTitle} noticed a change on site ${website} 14 |

${inlineImages}
15 | 16 | 17 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/markup-templates/mail-visual-diff-welcome.ftl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 |

Hi,

13 |

you created a page tracker ${trackerTitle} for page ${website}

14 |

in order to authorize the tracking emails, click here

15 | 16 |

${info}

17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/markup-templates/mail-welcome-paid.ftl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Welcome to ${productName}

8 |

paid plan

9 |

Regards,

10 |

11 | ${productName} Team
12 |

13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/markup-templates/page-tracker-authorized.ftl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 14 | 15 | 16 |
17 | Great, your Change Tracker is now authorized and will send you emails. 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/models/.gitignore: -------------------------------------------------------------------------------- 1 | *.bin 2 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/public/sample.txt: -------------------------------------------------------------------------------- 1 | activate 'static' profile to serve the public directory 2 | -------------------------------------------------------------------------------- /packages/server-core/src/main/resources/rome.properties: -------------------------------------------------------------------------------- 1 | # register feed element parser 2 | atom_1.0.feed.ModuleParser.classes=org.migor.feedless.feed.exporter.FeedlessModuleParser 3 | 4 | # register item element parser 5 | atom_1.0.item.ModuleParser.classes=org.migor.feedless.feed.exporter.FeedlessModuleParser 6 | 7 | # register feed element generator 8 | atom_1.0.feed.ModuleGenerator.classes=org.migor.feedless.feed.exporter.FeedlessModuleGenerator 9 | 10 | # register item element generator 11 | atom_1.0.item.ModuleGenerator.classes=org.migor.feedless.feed.exporter.FeedlessModuleGenerator 12 | -------------------------------------------------------------------------------- /packages/server-core/src/test/kotlin/org/migor/feedless/document/DocumentResolverTest.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.document 2 | 3 | import org.junit.jupiter.api.BeforeEach 4 | import org.junit.jupiter.api.Disabled 5 | import org.junit.jupiter.api.Test 6 | 7 | class DocumentResolverTest { 8 | 9 | @BeforeEach 10 | fun setUp() { 11 | } 12 | 13 | @Test 14 | @Disabled 15 | fun `anonymous can retrieve records if repo is public`() { 16 | // todo test 17 | } 18 | 19 | @Test 20 | @Disabled 21 | fun `owner can retrieve records if repo is private`() { 22 | // todo test 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/server-core/src/test/kotlin/org/migor/feedless/feed/exporter/JsonFeedExporterTest.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.feed.exporter 2 | 3 | import org.assertj.core.api.Assertions.assertThat 4 | import org.junit.jupiter.api.Test 5 | 6 | class JsonFeedExporterTest { 7 | 8 | @Test 9 | fun toJson() { 10 | val url = "https://foo.bar" 11 | val feed = createJsonFeed(url) 12 | 13 | val exporter = JsonFeedExporter() 14 | assertThat(exporter.toJson(feed)).isNotNull() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/server-core/src/test/kotlin/org/migor/feedless/report/ReportResolverTest.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.report 2 | 3 | import org.junit.jupiter.api.BeforeEach 4 | import org.junit.jupiter.api.Disabled 5 | import org.junit.jupiter.api.Test 6 | 7 | class ReportResolverTest { 8 | 9 | @BeforeEach 10 | fun setUp() { 11 | } 12 | 13 | @Test 14 | @Disabled 15 | fun `report can be created without authorization`() { 16 | // todo test 17 | } 18 | 19 | @Test 20 | @Disabled 21 | fun `report can be deleted without authorization`() { 22 | // todo test 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/server-core/src/test/kotlin/org/migor/feedless/repository/RepositoryHarvesterExecutorTest.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.repository 2 | 3 | import org.assertj.core.api.Assertions.assertThat 4 | import org.junit.jupiter.api.Test 5 | import org.springframework.scheduling.annotation.Scheduled 6 | 7 | class RepositoryHarvesterExecutorTest { 8 | 9 | @Test 10 | fun `verify refreshSubscriptions is annotated with scheduled`() { 11 | val method = RepositoryHarvesterExecutor::class.java.declaredMethods.first { it.name == "refreshSubscriptions" } 12 | assertThat(method.getAnnotation(Scheduled::class.java)).isNotNull() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/server-core/src/test/kotlin/org/migor/feedless/util/DateUtilKtTest.kt: -------------------------------------------------------------------------------- 1 | package org.migor.feedless.util 2 | 3 | class DateUtilKtTest { 4 | 5 | // @Test 6 | // fun toMillis() { 7 | // val now = LocalDateTime.now() 8 | // assertThat(now.toMillis()).isEqualTo(Date(Instant.from(now).toEpochMilli()).time) 9 | // } 10 | } 11 | -------------------------------------------------------------------------------- /packages/server-core/src/test/resources/application-database.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver 2 | spring.jpa.hibernate.ddl-auto=create 3 | 4 | spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect 5 | spring.sql.init.mode=always 6 | spring.datasource.maxActive=20 7 | -------------------------------------------------------------------------------- /packages/server-core/src/test/resources/application-test.properties: -------------------------------------------------------------------------------- 1 | app.dateFormat=dd-MM-yyyy 2 | app.timeFormat=HH:mm 3 | app.appHost=http://localhost:8080 4 | app.rootEmail=foo@bar.com 5 | app.apiGatewayUrl=http://localhost:8080 6 | app.rootSecretKey=QDWSM3OYBPGTEVSPB5FKVDM3CSNCWHVK 7 | app.whitelistedHosts=127.0.0.1 8 | app.jwtSecret=60ae393744191ee58869f700c1d27647b0b64d42 9 | app.actuatorPassword=foo 10 | 11 | logging.level.root=INFO 12 | #logging.level.WebToFeedTransformer=DEBUG 13 | spring.flyway.enabled=false 14 | APP_BUILD_TIMESTAMP=0 15 | -------------------------------------------------------------------------------- /packages/server-core/src/test/resources/images/sample.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/server-core/src/test/resources/images/sample.jpeg -------------------------------------------------------------------------------- /packages/server-core/src/test/resources/images/sample.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/server-core/src/test/resources/images/sample.pdf -------------------------------------------------------------------------------- /packages/server-core/src/test/resources/images/sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/server-core/src/test/resources/images/sample.png -------------------------------------------------------------------------------- /packages/server-core/src/test/resources/images/sample.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/server-core/src/test/resources/images/sample.webp -------------------------------------------------------------------------------- /packages/server-core/src/test/resources/raw-websites/02-spotify-com.output.json: -------------------------------------------------------------------------------- 1 | [ 2 | "https://spotify.com/episode/1oQdQ3gWA4Scx3lHq972i3?si=QXk3XJZHTJ60dncN1d2eeQ", 3 | "https://spotify.com/episode/077Dw15hKhS6r8KFOkDxfO?si=QXk3XJZHTJ60dncN1d2eeQ" 4 | ] 5 | -------------------------------------------------------------------------------- /packages/server-core/src/test/resources/raw-websites/06-jon-bo-posts.output.json: -------------------------------------------------------------------------------- 1 | [ 2 | "https://jon.bo/posts/patch-notes-v11/", 3 | "https://jon.bo/posts/24/", 4 | "https://jon.bo/posts/patch-notes-v10/", 5 | "https://jon.bo/posts/patch-notes-v9/", 6 | "https://jon.bo/posts/patch-notes-v8/" 7 | ] 8 | -------------------------------------------------------------------------------- /packages/server-core/src/test/resources/raw-websites/12-sph-ethz-ch.output.json: -------------------------------------------------------------------------------- 1 | [ 2 | "https://sph.ethz.ch/news/student-learnings-2022", 3 | "https://sph.ethz.ch/news/state-visit-at-sph", 4 | "https://sph.ethz.ch/news/where-future-generations-of-makers-and-innovators-are-born", 5 | "https://sph.ethz.ch/news/student-project-house-opens-in-zentrum", 6 | "https://sph.ethz.ch/news/sph-opens-in-eth-zentrum" 7 | ] 8 | -------------------------------------------------------------------------------- /packages/server-core/src/test/resources/sample.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/feedless/8f4b588eafcc3d1dcc93eb598a570d118310b33a/packages/server-core/src/test/resources/sample.pdf -------------------------------------------------------------------------------- /packages/server-core/supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | 4 | [program:feedless] 5 | command=/bin/bash -c "source /etc/apache2/envvars && exec /usr/feedless/docker-entrypoint.sh -DFOREGROUND" 6 | -------------------------------------------------------------------------------- /packages/server-core/test/test-docker.sh: -------------------------------------------------------------------------------- 1 | # test DNS 2 | GIT_HASH=$1 3 | echo "Using gitHash $GIT_HASH" 4 | docker run damoeb/feedless:core-${GIT_HASH} nslookup br.de | grep br.de 5 | #docker run damoeb/feedless:core-arm nslookup br.de | grep br.de 6 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /roadmap.md: -------------------------------------------------------------------------------- 1 | # Roadmap 2 | 3 | - ocr using [tesseract](https://github.com/tesseract-ocr/tesseract) 4 | - Fulltext search using [elastic search](https://www.elastic.co/) 5 | -------------------------------------------------------------------------------- /scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | echo "Starting deployment" 2 | echo "> Stopping services" 3 | docker-compose stop feedless-ingress feedless-app feedless-agent feedless-core && \ 4 | echo "> Removing services" && \ 5 | docker-compose rm -f feedless-ingress feedless-app feedless-agent feedless-core && \ 6 | echo "> Starting services" && \ 7 | docker-compose up --detach feedless-ingress feedless-app feedless-agent feedless-core && \ 8 | docker-compose scale feedless-agent=2 9 | echo "Done" 10 | -------------------------------------------------------------------------------- /scripts/wait-for-containers.sh: -------------------------------------------------------------------------------- 1 | while ! nc -z localhost 8080; do sleep 1; done; 2 | echo 'core is ready' 3 | -------------------------------------------------------------------------------- /selfhosting.env: -------------------------------------------------------------------------------- 1 | POSTGRES_USER=postgres 2 | POSTGRES_PASSWORD=admin 3 | APP_DATABASE_URL=jdbc:postgresql://postgres:5432/feedless_selfhosted 4 | APP_ACTIVE_PROFILES=database,static,selfHosted,metrics 5 | APP_ROOT_EMAIL=admin@localhost 6 | APP_SECRET_KEY=56512aca95a971bede7693bdaddbd12a20b2e8b32e22c78c4eed186a266dcf20 7 | APP_JWT_SECRET=60ae393744191ee58869f700c1d27647b0b64d42 8 | APP_WHITELISTED_HOSTS=feedless-aio,feedless-aio-chromium 9 | APP_LOG_LEVEL=info 10 | 11 | #APP_MAIL_SENDER=foo@bar 12 | #APP_MAIL_HOST=smtp 13 | #APP_MAIL_PORT=2525 14 | #APP_MAIL_USERNAME= 15 | #APP_MAIL_PASSWORD= 16 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "feedless" 2 | 3 | include("packages:app-web") 4 | include("packages:server-core") 5 | include("packages:agent") 6 | 7 | buildscript { 8 | repositories { 9 | gradlePluginPortal() 10 | } 11 | dependencies { 12 | val gradleNodePluginVersion = "5.0.0" 13 | classpath ("com.github.node-gradle:gradle-node-plugin:$gradleNodePluginVersion") 14 | classpath("org.ajoberstar.grgit:grgit-gradle:5.0.0") 15 | } 16 | } 17 | 18 | // https://docs.gradle.org/current/userguide/build_cache.html 19 | buildCache { 20 | local { 21 | directory = File(rootDir, "build-cache") 22 | removeUnusedEntriesAfterDays = 30 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | mustache@^4.2.0: 6 | version "4.2.0" 7 | resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.2.0.tgz#e5892324d60a12ec9c2a73359edca52972bf6f64" 8 | integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ== 9 | --------------------------------------------------------------------------------