Compare commits

...

5 Commits

Author SHA1 Message Date
dea0031fa9 🔧 Miscellaneous Fixes 2023-05-15 16:07:20 -04:00
dc7324747f ️ Config key to expose hostname in a Docker context 2023-04-26 12:14:39 -04:00
fbf6bed4fe 🐳 Fixes to the Dockerfile (#74) 2023-03-31 15:09:23 -04:00
Lars Gohr
18b18c3d81 Updated elgohr/Publish-Docker-Github-Action to a supported version (v5) (#73) 2023-03-30 21:00:12 -04:00
a939bf4c71 🐛UI bugs (#68)
* 🔧 Updated date for PullList on Dashboard

* 🔧 Fixes for broken image paths

* ⬆️ Bumped deps, removed useless ones
2023-03-24 00:00:48 -04:00
16 changed files with 381 additions and 452 deletions

View File

@@ -12,7 +12,7 @@ jobs:
steps: steps:
- uses: actions/checkout@master - uses: actions/checkout@master
- name: Publish to Registry - name: Publish to Registry
uses: elgohr/Publish-Docker-Github-Action@master uses: elgohr/Publish-Docker-Github-Action@v5
with: with:
name: frishi/threetwo name: frishi/threetwo
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}

View File

@@ -1,4 +1,4 @@
FROM node:17.3-alpine FROM node:18.15.0-alpine
LABEL maintainer="Rishi Ghan <rishi.ghan@gmail.com>" LABEL maintainer="Rishi Ghan <rishi.ghan@gmail.com>"
WORKDIR /threetwo WORKDIR /threetwo
@@ -9,7 +9,7 @@ COPY nodemon.json ./
COPY jsdoc.json ./ COPY jsdoc.json ./
# RUN apt-get update && apt-get install -y git python3 build-essential autoconf automake g++ libpng-dev make # RUN apt-get update && apt-get install -y git python3 build-essential autoconf automake g++ libpng-dev make
RUN apk --no-cache add g++ make libpng-dev python3 git libc6-compat autoconf automake bash libjpeg-turbo-dev libpng-dev mesa-dev mesa libxi build-base gcc libtool nasm RUN apk --no-cache add g++ make libpng-dev git python3 libc6-compat autoconf automake libjpeg-turbo-dev libpng-dev mesa-dev mesa libxi build-base gcc libtool nasm
RUN yarn --ignore-engines RUN yarn --ignore-engines

View File

@@ -40,9 +40,8 @@ For debugging and troubleshooting, you can run this app locally using these step
1. Clone this repo using `git clone https://github.com/rishighan/threetwo.git` 1. Clone this repo using `git clone https://github.com/rishighan/threetwo.git`
2. `yarn run dev` (you can ignore the warnings) 2. `yarn run dev` (you can ignore the warnings)
3. This will open `http://localhost:3050` in your default browser 3. This will open `http://localhost:5173` in your default browser
4. For testing `OPDS` functionality, create a folder called `comics` under `/src/server` and put some comics in there. The `OPDS` feed is accessed to `http://localhost:8050/api/opds` 4. Note that this is simply the UI layer and won't offer anything beyond a scaffold. You have to spin up the microservices locally to get it to work.
5. Note that this is simply the UI layer and won't offer anything beyond a scaffold. You have to spin up the microservices locally to get it to work.
## Troubleshooting ## Troubleshooting

View File

@@ -7,7 +7,7 @@
"scripts": { "scripts": {
"build": "vite build", "build": "vite build",
"dev": "rimraf dist && npm run build && vite", "dev": "rimraf dist && npm run build && vite",
"prod": "npm run build && vite", "start": "npm run build && vite",
"docs": "jsdoc -c jsdoc.json", "docs": "jsdoc -c jsdoc.json",
"storybook": "storybook dev -p 6006", "storybook": "storybook dev -p 6006",
"build-storybook": "storybook build" "build-storybook": "storybook build"
@@ -20,32 +20,31 @@
"@dnd-kit/core": "^4.0.0", "@dnd-kit/core": "^4.0.0",
"@dnd-kit/sortable": "^5.0.0", "@dnd-kit/sortable": "^5.0.0",
"@dnd-kit/utilities": "^3.2.0", "@dnd-kit/utilities": "^3.2.0",
"@fortawesome/fontawesome-free": "^6.1.1", "@fortawesome/fontawesome-free": "^6.3.0",
"@redux-devtools/extension": "^3.2.2", "@redux-devtools/extension": "^3.2.2",
"@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-node-resolve": "^15.0.1",
"@tanstack/react-table": "^8.5.11", "@tanstack/react-table": "^8.5.11",
"@types/axios": "^0.14.0",
"@types/mime-types": "^2.1.0", "@types/mime-types": "^2.1.0",
"@types/react": "^17.0.3", "@types/react-router-dom": "^5.3.3",
"@types/react-dom": "^17.0.2",
"@types/react-redux": "^7.1.16",
"@types/react-router-dom": "^5.1.7",
"@types/socket.io": "^3.0.2", "@types/socket.io": "^3.0.2",
"@types/socket.io-client": "^3.0.0", "@types/socket.io-client": "^3.0.0",
"@vitejs/plugin-react": "^3.1.0", "@vitejs/plugin-react": "^3.1.0",
"airdcpp-apisocket": "2.4.5-beta.1", "airdcpp-apisocket": "2.4.5-beta.1",
"axios": "^0.27.2", "axios": "^1.3.4",
"axios-cache-interceptor": "^1.0.1",
"axios-rate-limit": "^1.3.0", "axios-rate-limit": "^1.3.0",
"axios-simple-cache-adapter": "^1.1.0",
"babel-polyfill": "^6.26.0", "babel-polyfill": "^6.26.0",
"babel-preset-minify": "^0.5.2", "babel-preset-minify": "^0.5.2",
"better-docs": "^2.7.2", "better-docs": "^2.7.2",
"date-fns": "^2.28.0", "date-fns": "^2.28.0",
"dayjs": "^1.10.6", "dayjs": "^1.10.6",
"ellipsize": "^0.1.0", "ellipsize": "^0.5.1",
"express": "^4.17.1", "express": "^4.17.1",
"filename-parser": "^1.0.2", "filename-parser": "^1.0.2",
"final-form": "^4.20.2", "final-form": "^4.20.2",
"final-form-arrays": "^3.0.2", "final-form-arrays": "^3.0.2",
"history": "^5.3.0",
"html-to-text": "^8.1.0", "html-to-text": "^8.1.0",
"jsdoc": "^3.6.10", "jsdoc": "^3.6.10",
"lodash": "^4.17.21", "lodash": "^4.17.21",
@@ -64,9 +63,9 @@
"react-loader-spinner": "^4.0.0", "react-loader-spinner": "^4.0.0",
"react-masonry-css": "^1.0.16", "react-masonry-css": "^1.0.16",
"react-modal": "^3.15.1", "react-modal": "^3.15.1",
"react-redux": "^7.2.6", "react-redux": "^8.0.5",
"react-router": "^6.2.2", "react-router": "^6.9.0",
"react-router-dom": "^6.2.2", "react-router-dom": "^6.9.0",
"react-select": "^5.3.2", "react-select": "^5.3.2",
"react-select-async-paginate": "^0.7.2", "react-select-async-paginate": "^0.7.2",
"react-slick": "^0.29.0", "react-slick": "^0.29.0",
@@ -79,12 +78,10 @@
"redux-thunk": "^2.4.2", "redux-thunk": "^2.4.2",
"slick-carousel": "^1.8.1", "slick-carousel": "^1.8.1",
"socket.io-client": "^4.3.2", "socket.io-client": "^4.3.2",
"styled-components": "^5.3.5", "styled-components": "^5.3.9",
"threetwo-ui-typings": "^1.0.14", "threetwo-ui-typings": "^1.0.14",
"vite-plugin-html": "^3.2.0", "vite-plugin-html": "^3.2.0",
"websocket": "^1.0.34", "websocket": "^1.0.34",
"ws": "^7.5.3",
"xml2js": "^0.4.23",
"xregexp": "^5.0.2" "xregexp": "^5.0.2"
}, },
"devDependencies": { "devDependencies": {
@@ -103,20 +100,19 @@
"@storybook/react-vite": "^7.0.0-rc.3", "@storybook/react-vite": "^7.0.0-rc.3",
"@storybook/testing-library": "^0.0.14-next.1", "@storybook/testing-library": "^0.0.14-next.1",
"@tsconfig/node14": "^1.0.0", "@tsconfig/node14": "^1.0.0",
"@types/ellipsize": "^0.1.1",
"@types/express": "^4.17.8", "@types/express": "^4.17.8",
"@types/jest": "^26.0.20", "@types/jest": "^26.0.20",
"@types/lodash": "^4.14.168", "@types/lodash": "^4.14.168",
"@types/node": "^14.14.34", "@types/node": "^14.14.34",
"@types/react": "^17.0.3", "@types/react": "^18.0.28",
"@types/react-dom": "^17.0.2", "@types/react-dom": "^18.0.11",
"@types/react-redux": "^7.1.16", "@types/react-redux": "^7.1.25",
"@typescript-eslint/eslint-plugin": "^4.17.0", "@typescript-eslint/eslint-plugin": "^4.17.0",
"@typescript-eslint/parser": "^4.17.0", "@typescript-eslint/parser": "^4.17.0",
"babel-eslint": "^10.0.0", "babel-eslint": "^10.0.0",
"body-parser": "^1.19.0", "body-parser": "^1.19.0",
"bulma": "^0.9.4", "bulma": "^0.9.4",
"comlink": "^4.3.0",
"concurrently": "^4.0.0",
"eslint": "^7.22.0", "eslint": "^7.22.0",
"eslint-config-airbnb": "^18.2.1", "eslint-config-airbnb": "^18.2.1",
"eslint-config-airbnb-base": "^14.2.1", "eslint-config-airbnb-base": "^14.2.1",
@@ -138,7 +134,7 @@
"sass": "^1.58.1", "sass": "^1.58.1",
"storybook": "^7.0.0-rc.3", "storybook": "^7.0.0-rc.3",
"tslint": "^6.1.3", "tslint": "^6.1.3",
"typescript": "^4.9", "typescript": "^5.0.2",
"vite": "^4.1.1" "vite": "^4.2.0"
} }
} }

View File

@@ -1,9 +1,6 @@
import axios from "axios"; import axios from "axios";
import rateLimiter from "axios-rate-limit"; import rateLimiter from "axios-rate-limit";
import { import { setupCache } from "axios-cache-interceptor";
AxiosCacheRequestConfig,
createCacheAdapter,
} from "axios-simple-cache-adapter";
import qs from "qs"; import qs from "qs";
import { import {
CV_SEARCH_SUCCESS, CV_SEARCH_SUCCESS,
@@ -26,25 +23,22 @@ import {
LIBRARY_SERVICE_BASE_URI, LIBRARY_SERVICE_BASE_URI,
} from "../constants/endpoints"; } from "../constants/endpoints";
const axiosCacheAdapter = createCacheAdapter();
const http = rateLimiter(axios.create(), { const http = rateLimiter(axios.create(), {
maxRequests: 1, maxRequests: 1,
perMilliseconds: 1000, perMilliseconds: 1000,
maxRPS: 1, maxRPS: 1,
}); });
const cachedAxios = setupCache(axios);
export const getWeeklyPullList = (options) => async (dispatch) => { export const getWeeklyPullList = (options) => async (dispatch) => {
try { try {
dispatch({ dispatch({
type: CV_WEEKLY_PULLLIST_CALL_IN_PROGRESS, type: CV_WEEKLY_PULLLIST_CALL_IN_PROGRESS,
}); });
await axios(`${COMICVINE_SERVICE_URI}/getWeeklyPullList`, { await cachedAxios(`${COMICVINE_SERVICE_URI}/getWeeklyPullList`, {
method: "get", method: "get",
params: options, params: options,
axiosCacheAdapter, }).then((response) => {
cache: 1000, // value in MS
} as AxiosCacheRequestConfig).then((response) => {
dispatch({ dispatch({
type: CV_WEEKLY_PULLLIST_FETCHED, type: CV_WEEKLY_PULLLIST_FETCHED,
data: response.data.result, data: response.data.result,
@@ -70,9 +64,6 @@ export const comicinfoAPICall = (options) => async (dispatch) => {
"Content-Type": "application/json", "Content-Type": "application/json",
"Access-Control-Allow-Origin": "*", "Access-Control-Allow-Origin": "*",
}, },
paramsSerializer: (params) => {
return qs.stringify(params, { arrayFormat: "repeat" });
},
}); });
switch (options.callURIAction) { switch (options.callURIAction) {

View File

@@ -127,49 +127,50 @@ export const getComicBooks = (options) => async (dispatch) => {
* @returns Nothing. * @returns Nothing.
* @param payload * @param payload
*/ */
export const importToDB = (sourceName: string, metadata?: any) => (dispatch) => { export const importToDB =
try { (sourceName: string, metadata?: any) => (dispatch) => {
const comicBookMetadata = { try {
importType: "new", const comicBookMetadata = {
payload: { importType: "new",
rawFileDetails: { payload: {
name: "", rawFileDetails: {
}, name: "",
importStatus: {
isImported: true,
tagged: false,
matchedResult: {
score: "0",
}, },
importStatus: {
isImported: true,
tagged: false,
matchedResult: {
score: "0",
},
},
sourcedMetadata: metadata || null,
acquisition: { source: { wanted: true, name: sourceName } },
}, },
sourcedMetadata: metadata || null, };
acquisition: { source: { wanted: true, name: sourceName } }, dispatch({
} type: IMS_CV_METADATA_IMPORT_CALL_IN_PROGRESS,
};
dispatch({
type: IMS_CV_METADATA_IMPORT_CALL_IN_PROGRESS,
});
return axios
.request({
url: `${LIBRARY_SERVICE_BASE_URI}/rawImportToDb`,
method: "POST",
data: comicBookMetadata,
// transformResponse: (r: string) => JSON.parse(r),
})
.then((response) => {
const { data } = response;
dispatch({
type: IMS_CV_METADATA_IMPORT_SUCCESSFUL,
importResult: data,
});
}); });
} catch (error) { return axios
dispatch({ .request({
type: IMS_CV_METADATA_IMPORT_FAILED, url: `${LIBRARY_SERVICE_BASE_URI}/rawImportToDb`,
importError: error, method: "POST",
}); data: comicBookMetadata,
} // transformResponse: (r: string) => JSON.parse(r),
}; })
.then((response) => {
const { data } = response;
dispatch({
type: IMS_CV_METADATA_IMPORT_SUCCESSFUL,
importResult: data,
});
});
} catch (error) {
dispatch({
type: IMS_CV_METADATA_IMPORT_FAILED,
importError: error,
});
}
};
export const fetchVolumeGroups = () => async (dispatch) => { export const fetchVolumeGroups = () => async (dispatch) => {
try { try {
@@ -254,24 +255,23 @@ export const fetchComicVineMatches =
* @returns {any} * @returns {any}
*/ */
export const extractComicArchive = export const extractComicArchive =
(path: string, options: any): any => (path: string, options: any): any =>
async (dispatch) => { async (dispatch) => {
dispatch({ dispatch({
type: IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_CALL_IN_PROGRESS, type: IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_CALL_IN_PROGRESS,
}); });
await axios({ await axios({
method: "POST", method: "POST",
url: `${LIBRARY_SERVICE_BASE_URI}/uncompressFullArchive`, url: `${LIBRARY_SERVICE_BASE_URI}/uncompressFullArchive`,
headers: { headers: {
"Content-Type": "application/json; charset=utf-8", "Content-Type": "application/json; charset=utf-8",
}, },
data: { data: {
filePath: path, filePath: path,
options, options,
}, },
}); });
}; };
/** /**
* Description * Description
@@ -349,4 +349,4 @@ export const analyzeImage =
type: IMG_ANALYSIS_DATA_FETCH_SUCCESS, type: IMG_ANALYSIS_DATA_FETCH_SUCCESS,
result: foo.data, result: foo.data,
}); });
}; };

View File

@@ -1,7 +1,5 @@
import React, { useEffect, useContext, ReactElement } from "react"; import React, { useEffect, useContext, ReactElement } from "react";
import { import { getBundlesForComic } from "../../actions/airdcpp.actions";
getBundlesForComic,
} from "../../actions/airdcpp.actions";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { RootState } from "threetwo-ui-typings"; import { RootState } from "threetwo-ui-typings";
import { isEmpty, isNil, map } from "lodash"; import { isEmpty, isNil, map } from "lodash";

View File

@@ -1,7 +1,7 @@
import React, { ReactElement } from "react"; import React, { ReactElement } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import prettyBytes from "pretty-bytes"; import prettyBytes from "pretty-bytes";
import { isUndefined } from "lodash"; import { isEmpty } from "lodash";
export const RawFileDetails = (props): ReactElement => { export const RawFileDetails = (props): ReactElement => {
const { rawFileDetails, inferredMetadata } = props.data; const { rawFileDetails, inferredMetadata } = props.data;
@@ -34,6 +34,14 @@ export const RawFileDetails = (props): ReactElement => {
</span> </span>
</div> </div>
</div> </div>
<div className="control">
<div className="tags has-addons">
<span className="tag">MIME type</span>
<span className="tag is-primary is-light">
{rawFileDetails.mimeType}
</span>
</div>
</div>
</div> </div>
</dd> </dd>
</dl> </dl>
@@ -53,7 +61,7 @@ export const RawFileDetails = (props): ReactElement => {
</span> </span>
</div> </div>
</div> </div>
{!isUndefined(inferredMetadata.issue.number) ? ( {!isEmpty(inferredMetadata.issue.number) ? (
<div className="control"> <div className="control">
<div className="tags has-addons"> <div className="tags has-addons">
<span className="tag">Number</span> <span className="tag">Number</span>
@@ -81,6 +89,7 @@ RawFileDetails.propTypes = {
fileSize: PropTypes.number, fileSize: PropTypes.number,
path: PropTypes.string, path: PropTypes.string,
extension: PropTypes.string, extension: PropTypes.string,
mimeType: PropTypes.string,
cover: PropTypes.shape({ cover: PropTypes.shape({
filePath: PropTypes.string, filePath: PropTypes.string,
}), }),

View File

@@ -20,7 +20,7 @@ export const PullList = ({ issues }: PullListProps): ReactElement => {
useEffect(() => { useEffect(() => {
dispatch( dispatch(
getWeeklyPullList({ getWeeklyPullList({
startDate: "2023-1-25", startDate: "2023-5-25",
pageSize: "15", pageSize: "15",
currentPage: "1", currentPage: "1",
}), }),

View File

@@ -125,7 +125,7 @@ export const RecentlyImported = ({
<dd className="is-size-9"> <dd className="is-size-9">
<dl> <dl>
<span className="icon custom-icon"> <span className="icon custom-icon">
<img src={`/img/${icon}`} /> <img src={`/src/client/assets/img/${icon}`} />
</span> </span>
</dl> </dl>
<dl> <dl>

View File

@@ -73,7 +73,7 @@ export const LibraryGrid = (libraryGridProps: ILibraryGridProps) => {
<div className="content is-flex is-flex-direction-row"> <div className="content is-flex is-flex-direction-row">
{!isEmpty(sourcedMetadata.comicvine) && ( {!isEmpty(sourcedMetadata.comicvine) && (
<span className="icon cv-icon is-small"> <span className="icon cv-icon is-small">
<img src="/dist/img/cvlogo.svg" /> <img src="/src/client/assets/img/cvlogo.svg" />
</span> </span>
)} )}
{isNil(rawFileDetails) && ( {isNil(rawFileDetails) && (

View File

@@ -85,7 +85,7 @@ const Navbar: React.FunctionComponent = (props) => {
{downloadProgressTick && <div className="pulsating-circle"></div>} {downloadProgressTick && <div className="pulsating-circle"></div>}
</a> </a>
{!isUndefined(downloadProgressTick) ? ( {!isUndefined(downloadProgressTick) ? (
<div className="navbar-dropdown is-right"> <div className="navbar-dropdown is-right is-boxed">
<a className="navbar-item"> <a className="navbar-item">
<DownloadProgressTick data={downloadProgressTick} /> <DownloadProgressTick data={downloadProgressTick} />
</a> </div> </a> </div>
@@ -98,7 +98,7 @@ const Navbar: React.FunctionComponent = (props) => {
<a className="navbar-link is-arrowless has-text-success"> <a className="navbar-link is-arrowless has-text-success">
<i className="fa-solid fa-bolt"></i> <i className="fa-solid fa-bolt"></i>
</a> </a>
<div className="navbar-dropdown pt-4 pr-2 pl-2 is-right airdcpp-status"> <div className="navbar-dropdown pr-2 pl-2 is-right airdcpp-status is-boxed">
{/* AirDC++ Session Information */} {/* AirDC++ Session Information */}
<p> <p>
@@ -133,7 +133,7 @@ const Navbar: React.FunctionComponent = (props) => {
<a className="navbar-link is-arrowless has-text-danger"> <a className="navbar-link is-arrowless has-text-danger">
<i className="fa-solid fa-bolt"></i> <i className="fa-solid fa-bolt"></i>
</a> </a>
<div className="navbar-dropdown pr-2 pl-2 is-right"> <div className="navbar-dropdown pr-2 pl-2 is-right is-boxed">
<pre> <pre>
{JSON.stringify(socketDisconnectionReason, null, 2)} {JSON.stringify(socketDisconnectionReason, null, 2)}
</pre> </pre>

View File

@@ -57,6 +57,12 @@ export const MetadataPanel = (props: IMetadatPanelProps): ReactElement => {
> >
{rawFileDetails.extension} {rawFileDetails.extension}
</span> </span>
<span
className="tag is-success is-light has-text-weight-semibold"
style={props.tagsStyle}
>
{rawFileDetails.mimeType}
</span>
<span <span
className="tag is-success is-light" className="tag is-success is-light"
style={props.tagsStyle} style={props.tagsStyle}

View File

@@ -8,6 +8,7 @@ export const hostURIBuilder = (options: Record<string, string>): string => {
options.apiPath options.apiPath
); );
}; };
console.log(import.meta);
export const CORS_PROXY_SERVER_URI = hostURIBuilder({ export const CORS_PROXY_SERVER_URI = hostURIBuilder({
protocol: "http", protocol: "http",

View File

@@ -6,6 +6,7 @@ export default defineConfig({
publicDir: "public", publicDir: "public",
base: "", base: "",
build: "esnext", build: "esnext",
server: { host: true },
plugins: [ plugins: [
nodeResolve({ nodeResolve({
// browser: true // browser: true

612
yarn.lock

File diff suppressed because it is too large Load Diff