{"id":21465342,"url":"https://github.com/christopher-dabrowski/bibliography-cloud","last_synced_at":"2026-04-12T22:55:12.687Z","repository":{"id":35493717,"uuid":"215057070","full_name":"christopher-dabrowski/bibliography-cloud","owner":"christopher-dabrowski","description":"Aplikacji do zarządzania źródłami w pracach naukowych","archived":false,"fork":false,"pushed_at":"2023-03-02T11:18:38.000Z","size":33563,"stargazers_count":1,"open_issues_count":17,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-03-14T16:56:11.518Z","etag":null,"topics":["flask","form-validation","javascript","nosql","react","rest-api","spring-boot","university-project","webapp"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/christopher-dabrowski.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-10-14T13:52:41.000Z","updated_at":"2024-12-02T09:06:44.000Z","dependencies_parsed_at":"2023-02-15T06:16:11.371Z","dependency_job_id":null,"html_url":"https://github.com/christopher-dabrowski/bibliography-cloud","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/christopher-dabrowski%2Fbibliography-cloud","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/christopher-dabrowski%2Fbibliography-cloud/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/christopher-dabrowski%2Fbibliography-cloud/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/christopher-dabrowski%2Fbibliography-cloud/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/christopher-dabrowski","download_url":"https://codeload.github.com/christopher-dabrowski/bibliography-cloud/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243982141,"owners_count":20378605,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["flask","form-validation","javascript","nosql","react","rest-api","spring-boot","university-project","webapp"],"created_at":"2024-11-23T08:10:03.422Z","updated_at":"2026-04-12T22:55:07.634Z","avatar_url":"https://github.com/christopher-dabrowski.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Zarządzanie źródłami do prac naukowych \u003c!-- omit in toc --\u003e\n\n[![Board Status](https://dev.azure.com/01133318/58577f27-c36e-47d0-9166-d776046c9d72/b8c46cd8-ad29-4083-8158-d84b59d074b3/_apis/work/boardbadge/1c7d0226-0595-4aa6-b9e1-2e99eadfd43d)](https://dev.azure.com/01133318/Bibliography%20Cloud/_workitems)\n\nProjekt na Programowanie aplikacji mobilnych i webowych.  \nRealizacja kolejnych etapów projektów laboratoryjnych.\n\n## Cel projektu\n\nNapisanie aplikacji do zarządzania źródłami w pracach naukowych.\n\n## Spis treści\n\n- [Cel projektu](#cel-projektu)\n- [Spis treści](#spis-treści)\n- [Etap 1 - Formularz rejestracyjny](#etap-1---formularz-rejestracyjny)\n  - [Istotne elementy](#istotne-elementy)\n  - [Uruchomienie Formularza](#uruchomienie-formularza)\n    - [Web deployment](#web-deployment)\n    - [Docker](#docker)\n    - [Docker-compose](#docker-compose)\n  - [Opis plików](#opis-plików)\n    - [Pliki projektu](#pliki-projektu)\n    - [Pliki konfiguracji (serwer)](#pliki-konfiguracji-serwer)\n  - [Zakończenie etapu](#zakończenie-etapu)\n- [Etap 2 - Logowanie i przechowywanie plików](#etap-2---logowanie-i-przechowywanie-plików)\n  - [Uruchomienie projektu](#uruchomienie-projektu)\n  - [Istotne elementy](#istotne-elementy-1)\n  - [Projekt systemu](#projekt-systemu)\n  - [Rest API](#rest-api)\n  - [Logowanie](#logowanie)\n- [Etap 3 - Publikacje, RESTFull i klient mobilny](#etap-3---publikacje-restfull-i-klient-mobilny)\n  - [Uruchomienie projektu](#uruchomienie-projektu-1)\n  - [Szyfrowanie połączenia](#szyfrowanie-połączenia)\n  - [Usługa sieciowa](#usługa-sieciowa)\n  - [Klient webowy](#klient-webowy)\n    - [Konfiguracja](#konfiguracja)\n    - [Działanie](#działanie)\n    - [Responsywność](#responsywność)\n  - [Klient mobilny](#klient-mobilny)\n- [Etap 4](#etap-4)\n  - [Uruchomienie projektu](#uruchomienie-projektu-2)\n  - [OAuth2.0](#oauth20)\n    - [Implementacja](#implementacja)\n  - [Powiadomienia o serwera](#powiadomienia-o-serwera)\n    - [Implementacja](#implementacja-1)\n- [Przydatne materiały](#przydatne-materiały)\n\n## Etap 1 - Formularz rejestracyjny\n\nOpracowanie formularza rejestracyjnego dla nowych użytkowników. Formularz musi pozwalać na **walidowanie wszystkich pól na bieżąco**. Kod JavaScript, HTML i CSS muszą być od siebie **odseparowane**. Komunikaty błędów muszą być tworzone dynamicznie przez kod JS. Polę login użytkownika będzie sprawdzane pod kątem dostępności **asynchronicznie**. Dane do rejestracji będą przesyłane do na zewnętrzny serwer. Kod HTML i CSS musi przechodzić walidację.\n\n### Istotne elementy\n\n* czy kod HTML posiada puste węzły na komunikaty (źle, powinny być one dodawane dynamicznie),\n* czy wykorzystywane są elementy HTML5 zamiast generycznych, np. `\u003cdiv class='menu'/\u003e`, `\u003cspan id='footer'\u003e\u003c/span\u003e`,\n* jak analizowana jest odpowiedź o dostępności loginu (czy sprawdzany tylko tekst odpowiedzi, czy też kod statusu).\n\n### Uruchomienie Formularza\n\nStrona wymaga **połączenia z Internetem**, ponieważ ładuje biblioteki z CDN oraz sprawdza poprawność loginu na zewnętrznym serwerze.\n\n#### Web deployment\n\nStan projektu po pierwszym kroku milowym można zobaczyć pod [adresem](https://bibliography-cloud.azurewebsites.net).\n\nProjekt jest hostowany na platformie _Microsoft Azure_ na **darmowym** poziomie - F1.  \n:exclamation: Jeśli aplikacja nie była ostatnio uruchamiana start może zająć nawet **5 minut**. :exclamation:\n\n#### Docker\n\nZbudowanie obrazu z pliku [Dockerfile](./Dockerfile).  \n`docker build -t biblio-cloud .`\n\nUruchomienie kontenera.  \n`run --rm  --name nginx -p 8080:80 -it biblio-cloud`\n\nNastępnie strona powinna być dostępna pod adresem [http://localhost:8080](http://localhost:8080/).\n\n#### Docker-compose\n\nAlternatywnie można uruchomić projekt poleceniem `docker-compose up`.\n\nNastępnie strona powinna być dostępna pod adresem [http://localhost:8080](http://localhost:8080/).\n\n### Opis plików\n\nInformacja o plikach składających się na projekt.\n\n#### Pliki projektu\n\n- **login.html** - Struktura strony logowania\n- **styles/login.css** - Wygląd strony logowania\n- **img/** - Obrazy znajdujące się na stronie\n- **scripts/script.js** - Skrypt bezpośrednio związany z ekranem logowania\n- **scripts/utils.js** - Przydatne funkcje nie będące bezpośrednio związane z projektem\n- **scripts/validateExtensions.js** - Dodatkowe funkcje i walidatory związane z biblioteką _validate.js_\n\n#### Pliki konfiguracji (serwer)\n\n- **nginx.conf** - Konfiguracja serwera _Nginx_\n- **Dockerfile** - Opis jak zbudować kontener serwujący stronę\n\n### Zakończenie etapu\n\nProjekt w stanie bezpośrednio po tym etapie można znaleźć w zakładce [release](https://github.com/SiwyKrzysiek/bibliography-cloud/releases/tag/v1.0).\n\n## Etap 2 - Logowanie i przechowywanie plików\n\nOpracowanie modułu służącego do bezpiecznego logowania i wylogowywania użytkownika. Moduł logowania otrzymuje od użytkownika hasło i login – w przypadku poprawnych danych generowany jest **identyfikator sesji**. Dane sesyjne przechowywane są w bazie danych **Redis**. Należy opracować formularz pozwalający na przechowywanie przez użytkownika plików **PDF** w systemie. Pliki PDF powinny być dostępne do pobrania i serwowane przez **bezstanową aplikację**. Należy wykorzystać **JWT** z krótką datą ważności.\n\n### Uruchomienie projektu\n\nStan projektu po tym etapie można znaleźć w zakładce [release](https://github.com/SiwyKrzysiek/bibliography-cloud/releases/tag/Milestone2).\n\nBy uruchomić projekt należy wykonać `docker-compose up` w głównym katalogu projektu.  \nDomyślnie projekt będzie dostępny pod adresem [http://localhost:8080](http://localhost:8080).\n\n### Istotne elementy\n\n- czy w ciasteczku generowany jest identyfikator sesji czy bezterminowy JWT (to drugie nie pozwala wylogować),\n- czy przy wylogowaniu usuwane są wpisy z _Redis_,\n- czy w formularzu jest `enctype=multipart/form-data`\n- czy aplikacja serwująca dostęp do pliku korzysta z sesji (czy innych informacji poza tymi w żetonie) - jeżeli tak, to źle,\n- czy żeton do pobrania ma krótki czas ważności (kilka minut)\n\n### Projekt systemu\n\nSystem będzie podzielony na 3 główne komponenty.\n\nPierwszy z nich będzie rozbudowaniem aplikacji we _Falsk_ z etapu 1. Jego zdaniem jest serwowanie stron internetowych i bezpośrednia komunikacja z użytkownikiem.\n\nDo przechowywania danych sesyjnych oraz bazy użytkowników wykorzystana zostanie baza nosql _Redis_.\n\nDo obsługi plików wykorzystana zostanie oddzielna usługa typu REST.\n\n![Component diagram displaying system](./doc/ComponentDiagram.svg)\n\n### Rest API\n\nDokumentacja API jest opisana w pliku [restAPI.yml](./doc/resAPI.yml). Dzięki wykorzystaniu usługi Swagger łatwo można je [zobaczyć](https://app.swaggerhub.com/apis-docs/oakbit/biblography-cloud/1.0.0).\n\nZarządzanie plikami użytkownika jest realizowane przez **oddzielny serwer**.\n\nSerwer jest napisany w języku Java przy pomocy biblioteki _spring_.\nOdpowiada on za przechowywanie plików użytkownika. Komunikacja z nim jest możliwa tylko\nprzy podaniu tokenu **JWT** generowanego dynamicznie przez serwer we _Flasku_.\n\nTokeny mają **krótki czas ważności** i są przydzielane bezpośrednio przy podjęciu akcji przez użytkownika. Odpowiednie ścieżki na serwerze _web_ generują tokeny i zwracają **przekierowanie z zapisanym tokenem**\n\n### Logowanie\n\nJest kilka kont użytkownika wpisanych na stałe do bazy\n\n- **Login:** jan **hasło:** AAA\n- **Login:** zupan **hasło:** gros\n- **Login:** Atrox **hasło:** password\n\nModuł kreacji kont aktualnie **nie działa**.\n\nStan zalogowania oraz dane związane z sesją są trzymane w bazie _Redis_.\nPo wylogowaniu wpisy z bazy są kasowane. Na tej podstawie odbywa się dalsze uwierzytelnianie użytkownika.\nUżytkownik dostaje jedynie ciastko z **id sesji**.\n\n## Etap 3 - Publikacje, RESTFull i klient mobilny\n\nCelem etapu jest przygotowanie usługi sieciowej pozwalającej na przechowywanie i modyfikację pozycji bibliograficznych. Usługa sieciowa powinna zwracać powiązane elementy zgodnie z **HATEOAS**.  \nDo aplikacji mają powstać **dwie aplikacje** klienckie. Jedna ma być rozszerzeniem aplikacji webowej, a druga może być aplikacją mobilną, konsolową lub biurkową. Klient powinien dostosowywać swój interface do danych zawartych w HATEOAS.\n\nUsługa sieciowa musi pozwalać na:\n\n- dodawanie pozycji bibliograficznej,\n- listowanie pozycji bibliograficznych,\n- usuwaniu pozycji bibliograficznych,\n- podpinanie i odpinanie plików przy pozycji bibliograficznej,\n- dodawanie, pobieranie i usuwanie plików.\n\n### Uruchomienie projektu\n\nStan projektu po tym etapie można znaleźć w zakładce [release](https://github.com/SiwyKrzysiek/bibliography-cloud/releases/tag/Milestone3).\n\nBy uruchomić projekt należy wykonać `docker-compose up` w głównym katalogu projektu.  \nDomyślnie projekt będzie dostępny pod adresem [https://localhost:443](https://localhost:443).\n\n### Szyfrowanie połączenia\n\nDodatkowo został dodany serwer Nginx pośredniczący w komunikacji z aplikacjami. Dzięki temu możliwe jest połączenie się przez **protokół https**.  \nKonfiguracja serwera znajduje się w pliku [nginx.conf](./nginx.conf).\n\n### Usługa sieciowa\n\nNa potrzeby zarządzania publikacjami została napisana usługa sieciowa zgodnie ze stylem REST i uwzględnieniem HATEOAS.\n\n[Dokumentacja interfejsu](https://app.swaggerhub.com/apis/oakbit/bibliography-cloud-publications/1.0.0) została opisana przy pomocy Open API.\n\nSerwer obsługujący usługę sieciową został napisany przy pomocy frameworka Spring. Dane publikacji są przechowywanie w basie SQL H2.  \nKod źródłowy serwera znajduje się w katalogu [publications](./publications).\n\nUsługa generuje **odpowiednie kody HTTP** oraz wysyła metadane przy pomocy **json+hal**.\n\nDziałanie samej usługi sieciowej można obserwować przy pomocy [kolekcji gotowych zapytań HTTP](https://documenter.getpostman.com/view/6368494/SWLe8ToM). Kolekcja została utworzona z myślą o aplikacji Postman i programie `curl`. Zapytania związane z tym krokiem milowym znajdują się w **katalogu Publications**.\n\n### Klient webowy\n\nW celu urozmaicenia projektu i poznania nowych technologi zdecydowałem się na renderowanie po stronie klienta i wykorzystanie frameworka **React**.\n\nGłówne pliki klienta można znaleźć w katalogu [components](./app/react-publications/src/components).\n\n#### Konfiguracja\n\nPołączenie serwera Flask z elementem w React nie było łatwe.   \nProjekt React został utworzony w katalogu [react-publications](./react-publications) przy pomocy skryptu [create-react-app](https://github.com/facebook/create-react-app). Następnie została wyizolowana konfiguracja tworzenia projektu poleceniem `npm reject`.\n\nDzięki edycji plików [webpack.config.js](./react-publications/config/webpack.config.js), [paths.js](react-publications/config/paths.js) i [package.json](./react-publications/package.json) miejsce tworzenia plików wynikowych przez _webpack_ zostało zmienione na katalogi `static/react/publications` i `templates` w aplikacji Flask.\n\nDodatkowo połączenie aplikacji z szablonami Flaks (templates) wymagało ręcznej zmiany w [skrypcie budującym](./react-publications/scripts/build.js). Ponieważ _webpack_ dopisuje tag `\u003cscript\u003e` na koniec pliku nie znajdował się on w bloku `{% block main %}` i nie był częścią strony. Żeby to naprawić skrypt budujący wykonuje dodatkowy krok ręcznie przenoszący zamknięcie bloku na koniec pliku.\n\nZbudowanie komponentu React można wykonać poleceniem `npm run buld`.\n\n#### Działanie\n\nKlient wykorzystuje asynchroniczne zapytania HTTP w celu pobrania danych z usług całej aplikacji. Jedyne dane, jakie dostaje bezpośrednio to adresy końcówek (endpoints).\n\nMożliwe **akcje są ustalane dynamicznie** na podstawie danych HATEOAS.\n\nPrzykład tworzenia guzika dodawania publikacji:\n\n![Fantastic HATEOAS in action](./doc/HATEOAS_example_1.png)\n\nInterfejs pozwala na pracę w trybie edycji dzięki czemu można łatwo anulować niechciane zmiany.\n\n![Edit mode example](./doc/Edit_mode_example.png)\n\n#### Responsywność\n\nCały klient webowy był tworzony z myślą wsparciu urządzeń mobilnych. Aplikacja webowa w wersji na telefon jest **głównym klientem na urządzenia przenośne**.\n\nDzięki zastosowaniu biblioteki Bootstrap oraz własnych CSS media query aplikacja wygląda dobrze za równo na dużych jak i małych ekranach.\n\nPrzykład trybu edycji publikacji na telefonie:\n\n![Edit mode on phone example](doc/Edit_mode_phone_example.png)\n\n### Klient mobilny\n\nZostały utworzone dwie proste aplikacje będące klientami mobilnymi. Aplikacja Flutter będąca prototypem znajduje się w katalogu [mobile_client_flutter](./mobile/mobile_client_flutter).  \nAplikacja React Native, która jest uproszczeniem klienta webowego jest w katalogu [mobile_client_react_native](./mobile/mobile_client_react_native).\n\n## Etap 4\n\nCelem etapu jest rozszerzenie aplikacji `web` o powiadomienia ze strony serwera o dodaniu nowych publikacji.\nPowiadomienia powinny pojawiać się we wszystkich przeglądarkach, w których zalogowany jest użytkownik.\nPowiadomienia powinny wyświetlać się tylko użytkownikowi, który jest zalogowany.\nDopuszczalne jest wykorzystanie: long-polling, Server Sent Events (EventStream) lub WebSocket. Co do ostatniego, to warto pamiętać, że WebSocket jest wykorzystywany **głównie** w przypadku, gdy wymagana jest dwukierunkowa komunikacja (wykorzystanie tego w kontekście powiadomień jest pewnym naciągnięciem, ale w tym kamieniu milowym jest dopuszczalne).\n\nNależy również o zintegrowanie logowania do aplikacji z wykorzystaniem **OAuth2.0**. Najlepiej wykorzystać auth0.com.\n\n### Uruchomienie projektu\n\nStan projektu po tym etapie można znaleźć w zakładce [release](https://github.com/SiwyKrzysiek/bibliography-cloud/releases/tag/Milestone4).\n\n**Przed uruchomieniem należy wpisać dane Auth0.**  \nW tym celu trzeba ustawić zmienne w pliku [/app/docker.env](./app/docker.env).\n\nBy uruchomić projekt należy wykonać `docker-compose up` w głównym katalogu projektu.  \nDomyślnie projekt będzie dostępny pod adresem [https://localhost:5000](https://localhost:5000).\n\n:exclamation: Adres aplikacji jest **inny** niż w poprzednich etapach.\n\n### OAuth2.0\n\nW celu przejścia na autoryzacje przy pomocy zewnętrznej usługi OAuth2.0 została utworzona aplikacja na stronie **Auth0**. Ponieważ aplikacja jest w głównej części klasyczną aplikacją webową jest w stanie bezpiecznie przechować sekret aplikacji. Dzięki temu możliwe jest zastosowanie modelu [Authorization Code](https://auth0.com/docs/flows/concepts/auth-code).\n\n#### Implementacja\n\nZostało utworzone konto testowe na stronie Auth0.  \n**Email:** 293101@pw.edu.pl  \n**Login:** jan  \n**Hasło:** Pa$$word\n\nPo stronie aplikacji został zdefiniowany adres powrotu, pod który zostanie przekierowany użytkownik po uwierzytelnieniu przy pomocy Auth0. Po poprawnej weryfikacji następne kroki pozostały takie jak w kamieniu milowym 2 (własny moduł logowania użytkowników oparty na ciasteczkach i redisie oraz własny dekorator `login_required`).\n\n### Powiadomienia o serwera\n\nW celu informowania użytkownika o zmianach przeprowadzanych na różnych urządzeniach wykorzystana została technologia Server Send Events. Dzięki temu po zmianie publikacji (utworzenie, aktualizacja, usunięcie) we wszystkich oknach przeglądarki, na których otwarta jest aplikacja użytkownik zobaczy **powiadomienie** o akcji, a lista publikacji zostanie **automatycznie odświeżona**.\n\nNiestety wystąpiły problemy przy połączeniu tej funkcjonalności z przejściem przez serwer Nginx. Z tego powodu w tym kamieniu milowym nie jest aktualnie wspierane połączenie htts. Mimo tego ciągle jest serwowane przez rozwiązanie produkcyjne (gunicorn).\n\n#### Implementacja\n\nPo stronie serwera `web` wykożystnana została biblioteka _flaks-sse_, która wykorzystuje funkcjonalność [pubsub serwera Redis](https://redis.io/topics/pubsub) jako kolejkę komunikatów.\n\nZa rejestrowanie i przekazywanie powiadomień SSE odpowiada aplikacja `web`. Klient po wprowadzeniu zmiany może wykonać zapytanie pod odpowiedni adres. Jeśli zapytanie jest poprawne odpowiednia wiadomość zostanie dodana do kolejki komunikatów **danego użytkownika**.\n\nPod odpowiednim adresem dostępny jest strumień komunikatów (.../api/stream).\n\nKlientem odpowiadającym za wyświetlanie i zgłaszanie komunikatów jest moduł UI publikacji napisany w React jako część kamienia milowego 4. Tworzy on obiekt `EventSource` i nasłuchuje wiadomości dla aktualnego użytkownika. (Plik [app/react-publications/src/components/App.js](./app/react-publications/src/components/App.js) linia 82). Po otrzymaniu komunikatu klient aktualizuję listę publikacji.\nDo bazowego szablonu został dodany kod łączący się ze strumieniem SSE i wyświetlający notyfikacje dla zalogowanego użytkownika. Dzięki temu są one widoczne na dowolnej stronie.  \nŁączenie, tak jak w App.js: [app/templates/base.html](./app/templates/base.html) linia 120  \nGenerowanie powiadomień: [app/static/js/sse.js](./app/static/js/sse.js)\n\nGdy klient wykona zmianę publikacji i otrzyma prawidłową odpowiedź od serwera wysyła powiadomienie o akcji do aplikacji `web`. (Przykład: Plik [app/react-publications/src/components/Publication.js](./app/react-publications/src/components/Publication.js) linia 70)\n\n----------------------\n\n## Przydatne materiały\n\n- [Pozbywanie się Callback Hell](https://www.nafrontendzie.pl/jak-pozbyc-sie-callback-hell)\n- [Czysty kod js](https://github.com/ryanmcdermott/clean-code-javascript)\n- [Testy i mockowanie AJAXa](https://www.nafrontendzie.pl/jquery-deffered-oraz-promise-pigulce)\n- [Kurs Webpack 2](https://www.youtube.com/watch?v=8vnkM8JgjpU\u0026list=PL55RiY5tL51rcCnrOrZixuOsZhAHHy6os\u0026index=4)\n- [Integracja Flask + Webpack](https://codeburst.io/creating-a-full-stack-web-application-with-python-npm-webpack-and-react-8925800503d9)\n- [Kody odpowiedzi HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchristopher-dabrowski%2Fbibliography-cloud","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchristopher-dabrowski%2Fbibliography-cloud","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchristopher-dabrowski%2Fbibliography-cloud/lists"}