├── web ├── .env ├── src │ ├── modules │ │ ├── App │ │ │ ├── styles.less │ │ │ ├── Layout │ │ │ │ ├── styles.less │ │ │ │ ├── Footer │ │ │ │ │ ├── styles.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── Header │ │ │ │ │ ├── styles.less │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── Manager.tsx │ │ │ ├── index.tsx │ │ │ ├── routes.ts │ │ │ └── RouteSwitch.tsx │ │ ├── Help │ │ │ ├── Layout │ │ │ │ ├── styles.less │ │ │ │ ├── Menu │ │ │ │ │ ├── styles.less │ │ │ │ │ └── index.tsx │ │ │ │ └── index.tsx │ │ │ ├── index.ts │ │ │ └── pages │ │ │ │ ├── Overview │ │ │ │ ├── index.tsx │ │ │ │ └── overview.md │ │ │ │ └── Upload │ │ │ │ ├── upload.md │ │ │ │ └── index.tsx │ │ ├── Home │ │ │ ├── pages │ │ │ │ └── Main │ │ │ │ │ ├── PackageList │ │ │ │ │ ├── SearchBar │ │ │ │ │ │ ├── styles.less │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── hooks.ts │ │ │ │ │ ├── ResultTable │ │ │ │ │ │ ├── styles.less │ │ │ │ │ │ ├── Link.tsx │ │ │ │ │ │ ├── Tag.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── styles.less │ │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── Package │ │ │ ├── pages │ │ │ │ ├── PackageDetail │ │ │ │ │ ├── types.ts │ │ │ │ │ ├── Files │ │ │ │ │ │ ├── types.ts │ │ │ │ │ │ ├── Table │ │ │ │ │ │ │ ├── types.ts │ │ │ │ │ │ │ ├── styles.less │ │ │ │ │ │ │ ├── DeleteAction.tsx │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ ├── Filters │ │ │ │ │ │ │ ├── styles.less │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ ├── hooks.ts │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── PackageInfo │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── styles.less │ │ │ │ │ │ ├── InstallationGuide │ │ │ │ │ │ │ ├── styles.less │ │ │ │ │ │ │ ├── CommandGuide.tsx │ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ │ └── PlatformGuide.tsx │ │ │ │ │ │ └── TopCard.tsx │ │ │ │ │ ├── hooks.ts │ │ │ │ │ ├── Header │ │ │ │ │ │ ├── styles.less │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── styles.less │ │ │ │ │ └── index.tsx │ │ │ │ └── ChannelDetail │ │ │ │ │ ├── styles.less │ │ │ │ │ ├── Profile.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── PackageList.tsx │ │ │ └── index.tsx │ │ ├── Account │ │ │ ├── pages │ │ │ │ └── RegisterLogin │ │ │ │ │ ├── tessellation.png │ │ │ │ │ ├── Description │ │ │ │ │ ├── logo.png │ │ │ │ │ ├── styles.less │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── styles.less │ │ │ │ │ ├── Registration │ │ │ │ │ ├── Register │ │ │ │ │ │ ├── Errors.tsx │ │ │ │ │ │ ├── styles.less │ │ │ │ │ │ ├── Submit.tsx │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── EmailInput.tsx │ │ │ │ │ │ ├── ConfirmInput.tsx │ │ │ │ │ │ ├── PasswordInput.tsx │ │ │ │ │ │ ├── utils.ts │ │ │ │ │ │ └── ChannelInput.tsx │ │ │ │ │ ├── Login │ │ │ │ │ │ ├── styles.less │ │ │ │ │ │ ├── Errors.tsx │ │ │ │ │ │ ├── Submit.tsx │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── PasswordInput.tsx │ │ │ │ │ │ ├── UsernameInput.tsx │ │ │ │ │ │ ├── hooks.ts │ │ │ │ │ │ └── reducer.ts │ │ │ │ │ ├── styles.less │ │ │ │ │ └── index.tsx │ │ │ │ │ └── index.tsx │ │ │ └── index.ts │ │ └── UploadPage │ │ │ ├── index.tsx │ │ │ └── pages │ │ │ └── UploadForm │ │ │ ├── styles.less │ │ │ └── hooks.ts │ ├── resource │ │ └── conda.svg │ ├── components │ │ ├── Markdown │ │ │ ├── styles.less │ │ │ └── index.tsx │ │ └── ErrorBoundary │ │ │ ├── NotFound.png │ │ │ ├── styles.less │ │ │ ├── ErrorPage.tsx │ │ │ └── index.tsx │ ├── react-app-env.d.ts │ ├── features │ │ ├── meta │ │ │ ├── selectors.ts │ │ │ ├── types.ts │ │ │ ├── index.ts │ │ │ ├── actions.ts │ │ │ ├── api.ts │ │ │ └── reducer.ts │ │ ├── channel │ │ │ ├── selectors.ts │ │ │ ├── index.ts │ │ │ ├── localstorage.ts │ │ │ ├── types.ts │ │ │ ├── actions.ts │ │ │ └── api.ts │ │ └── package │ │ │ ├── index.ts │ │ │ ├── selectors.ts │ │ │ ├── actions.ts │ │ │ └── types.ts │ ├── infrastructure │ │ ├── hooks.ts │ │ ├── rootState.ts │ │ ├── rootAction.ts │ │ ├── rootReducer.ts │ │ ├── api │ │ │ ├── types.ts │ │ │ └── transformers.ts │ │ └── store.ts │ ├── index.tsx │ └── libs │ │ └── date.ts ├── .dockerignore ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── index.html ├── tsconfig.path.json ├── Makefile ├── README.md ├── .gitignore ├── Dockerfile ├── ssl.Dockerfile ├── nginx.conf ├── tsconfig.json ├── nginx-ssl.conf ├── types │ ├── global.d.ts │ └── markdown-to-jsx.d.ts ├── package.json └── craco.config.js ├── _example ├── .gitignore ├── docker-compose.ssl.yml ├── Makefile ├── docker-compose.yml └── README.md ├── cli ├── .gitignore ├── Makefile ├── config │ ├── keys.go │ ├── channel.go │ ├── root.go │ ├── get.go │ ├── list.go │ ├── set.go │ └── config.go ├── upload │ └── package.go ├── registry │ ├── logout.go │ ├── root.go │ ├── set.go │ └── login.go ├── go.mod ├── request │ └── client.go ├── main.go └── README.md ├── server ├── .gitignore ├── testutils │ └── test-packages │ │ └── .gitignore ├── infrastructure │ ├── database │ │ ├── migrations │ │ │ ├── 01_create_channel_table.down.sql │ │ │ ├── 02_create_package_count.down.sql │ │ │ ├── 01_create_channel_table.up.sql │ │ │ ├── 02_create_package_count.up.sql │ │ │ ├── 03_alter_channel_tables.down.sql │ │ │ └── 03_alter_channel_tables.up.sql │ │ └── postgres │ │ │ ├── utils.go │ │ │ ├── migrate_test.go │ │ │ ├── postgres.go │ │ │ ├── channel.go │ │ │ ├── package_count_test.go │ │ │ ├── channel_test.go │ │ │ ├── postgres_test.go │ │ │ ├── migrate.go │ │ │ └── package_count.go │ ├── conda │ │ ├── filesys │ │ │ ├── errors.go │ │ │ ├── channel_test.go │ │ │ └── filesys_test.go │ │ └── index │ │ │ ├── docker_test.go │ │ │ ├── repofix_test.go │ │ │ └── shell.go │ └── decompressor │ │ ├── metadata.go │ │ └── tarbz2_test.go ├── api │ ├── dto │ │ ├── index.go │ │ ├── channel_test.go │ │ └── package.go │ ├── errors.go │ ├── http.go │ ├── index.go │ ├── index_test.go │ ├── server.go │ └── interfaces │ │ └── interfaces.go ├── libs │ ├── path.go │ └── closer.go ├── conda.Dockerfile ├── docker-compose.yaml ├── main.go ├── Makefile ├── domain │ ├── entity │ │ ├── channel_test.go │ │ ├── package_count.go │ │ └── channel.go │ ├── enum │ │ ├── platforms_test.go │ │ └── platforms.go │ └── condatypes │ │ ├── repodata.go │ │ └── channeldata.go ├── config │ ├── tls.go │ ├── conda.go │ ├── database.go │ ├── defaults.go │ └── config.go ├── go.mod ├── Dockerfile ├── fileserver │ ├── server.go │ └── handler.go └── config.yaml ├── .gitattributes ├── RELEASES.md └── LICENSE /web/.env: -------------------------------------------------------------------------------- 1 | BROWSER=none 2 | -------------------------------------------------------------------------------- /_example/.gitignore: -------------------------------------------------------------------------------- 1 | certs/ 2 | -------------------------------------------------------------------------------- /web/src/modules/App/styles.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cli/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | pcr.exe 3 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.bat 3 | *.exe 4 | -------------------------------------------------------------------------------- /cli/Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | go build -o pcr.exe . 3 | -------------------------------------------------------------------------------- /server/testutils/test-packages/.gitignore: -------------------------------------------------------------------------------- 1 | *.tar.bz2 -------------------------------------------------------------------------------- /web/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | .idea/ 4 | .env.local 5 | -------------------------------------------------------------------------------- /web/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /cli/config/keys.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | const ( 4 | sslVerify = "ssl_verify" 5 | ) 6 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.png filter=lfs diff=lfs merge=lfs -text 2 | *.svg filter=lfs diff=lfs merge=lfs -text 3 | -------------------------------------------------------------------------------- /server/infrastructure/database/migrations/01_create_channel_table.down.sql: -------------------------------------------------------------------------------- 1 | drop table if exists USERS; 2 | -------------------------------------------------------------------------------- /web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielBok/private-conda-repo/HEAD/web/public/favicon.ico -------------------------------------------------------------------------------- /server/infrastructure/database/migrations/02_create_package_count.down.sql: -------------------------------------------------------------------------------- 1 | drop table if exists PACKAGE_COUNTS; 2 | -------------------------------------------------------------------------------- /web/src/modules/Help/Layout/styles.less: -------------------------------------------------------------------------------- 1 | .main { 2 | padding: 40px 60px; 3 | background-color: #fafafa; 4 | } 5 | -------------------------------------------------------------------------------- /web/src/modules/Home/pages/Main/PackageList/SearchBar/styles.less: -------------------------------------------------------------------------------- 1 | .search-box { 2 | margin-bottom: 40px; 3 | } 4 | -------------------------------------------------------------------------------- /web/src/modules/Package/pages/PackageDetail/types.ts: -------------------------------------------------------------------------------- 1 | export type MatchParams = { 2 | channel: string; 3 | pkg: string; 4 | }; 5 | -------------------------------------------------------------------------------- /web/public/logo192.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:aef7569e6ee38949bb8bb4bc3560259241107eb4efb25b1bb495920ee2278729 3 | size 18064 4 | -------------------------------------------------------------------------------- /web/public/logo512.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:440c665f623bba9fdb27d3be46311fd6cfd01bbf012b10ba1d7ae4f30923a5ca 3 | size 71497 4 | -------------------------------------------------------------------------------- /web/src/resource/conda.svg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:9a25401177cb33def472e4f1c960e13b5d238e39f41a985b5565a722f3e35042 3 | size 1805 4 | -------------------------------------------------------------------------------- /web/src/components/Markdown/styles.less: -------------------------------------------------------------------------------- 1 | .default-container { 2 | display: flex; 3 | justify-content: flex-start; 4 | 5 | div { 6 | max-width: 1200px; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /web/src/modules/Home/pages/Main/styles.less: -------------------------------------------------------------------------------- 1 | @import (reference) "../../../App/styles"; 2 | 3 | .container { 4 | min-height: @min-height; 5 | padding: 24px 40px; 6 | } 7 | -------------------------------------------------------------------------------- /server/infrastructure/conda/filesys/errors.go: -------------------------------------------------------------------------------- 1 | package filesys 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrPackageNotFound = errors.New("package specified does not exist") 7 | ) 8 | -------------------------------------------------------------------------------- /web/src/components/ErrorBoundary/NotFound.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:1cdea5359913771a25bde22e156a636173571f5f0133dc983d4373b8861d29a9 3 | size 266273 4 | -------------------------------------------------------------------------------- /web/tsconfig.path.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "@/*": ["./src/*"] 6 | }, 7 | "downlevelIteration": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /cli/config/channel.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Channel struct { 4 | Channel string `mapstructure:"channel" yaml:"channel"` 5 | Password string `mapstructure:"password" yaml:"password"` 6 | } 7 | -------------------------------------------------------------------------------- /web/src/modules/Account/pages/RegisterLogin/tessellation.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:84620dcf03b39e7f8141309f9f9b9b27330720ba1ae33cbcd2c5bdc12488b7ae 3 | size 218854 4 | -------------------------------------------------------------------------------- /web/Makefile: -------------------------------------------------------------------------------- 1 | build: build-normal build-ssl 2 | 3 | build-normal: 4 | docker image build -t danielbok/pcr-web . 5 | 6 | build-ssl: 7 | docker image build -t danielbok/pcr-web-ssl -f ssl.Dockerfile . 8 | -------------------------------------------------------------------------------- /web/src/modules/Account/pages/RegisterLogin/Description/logo.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:a37a0f63e363403c43d95de2cb1837b9d8050dd6a2d358f63c20c245d89a4bee 3 | size 35253 4 | -------------------------------------------------------------------------------- /web/src/modules/App/Layout/styles.less: -------------------------------------------------------------------------------- 1 | .content { 2 | display: flex; 3 | justify-content: center; 4 | 5 | .body { 6 | min-height: @min-height; 7 | 8 | width: 100%; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /web/src/modules/Home/index.tsx: -------------------------------------------------------------------------------- 1 | import Main from "./pages/Main"; 2 | 3 | export default [ 4 | { 5 | component: Main, 6 | path: "/", 7 | title: "Homepage", 8 | }, 9 | ] as ModuleRoute[]; 10 | -------------------------------------------------------------------------------- /web/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | declare namespace NodeJS { 3 | interface ProcessEnv { 4 | NODE_ENV: "development" | "production"; 5 | REACT_APP_API_URL: string; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /web/src/features/meta/selectors.ts: -------------------------------------------------------------------------------- 1 | import { RootState } from "@/infrastructure/rootState"; 2 | import { MetaInfo } from "./types"; 3 | 4 | export const metaInfo = ({ meta: { loading, ...rest } }: RootState): MetaInfo => 5 | rest; 6 | -------------------------------------------------------------------------------- /_example/docker-compose.ssl.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | services: 4 | web: 5 | image: danielbok/pcr-web-ssl:3.0 6 | ports: 7 | - "80:80" 8 | - "443:443" 9 | volumes: 10 | - ./certs:/etc/nginx/certs 11 | -------------------------------------------------------------------------------- /server/api/dto/index.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type ApiMetaInfo struct { 4 | Indexer string `json:"indexer"` 5 | Image string `json:"image"` 6 | Registry string `json:"registry"` 7 | Repository string `json:"repository"` 8 | } 9 | -------------------------------------------------------------------------------- /web/src/modules/UploadPage/index.tsx: -------------------------------------------------------------------------------- 1 | import UploadForm from "./pages/UploadForm"; 2 | 3 | export default [ 4 | { 5 | component: UploadForm, 6 | path: "/", 7 | title: "UploadForm", 8 | }, 9 | ] as ModuleRoute[]; 10 | -------------------------------------------------------------------------------- /web/src/features/channel/selectors.ts: -------------------------------------------------------------------------------- 1 | import { RootState } from "@/infrastructure/rootState"; 2 | 3 | export const channelInfo = (state: RootState) => state.channel; 4 | export const channelValidated = (state: RootState) => state.channel.validated; 5 | -------------------------------------------------------------------------------- /web/src/modules/Account/index.ts: -------------------------------------------------------------------------------- 1 | import RegisterLogin from "./pages/RegisterLogin"; 2 | 3 | export default [ 4 | { 5 | component: RegisterLogin, 6 | path: "/", 7 | title: "Register or Login", 8 | }, 9 | ] as ModuleRoute[]; 10 | -------------------------------------------------------------------------------- /web/src/features/channel/index.ts: -------------------------------------------------------------------------------- 1 | import * as ChnAction from "./actions"; 2 | import * as ChnApi from "./api"; 3 | import * as ChnSelector from "./selectors"; 4 | import * as ChnType from "./types"; 5 | 6 | export { ChnAction, ChnApi, ChnSelector, ChnType }; 7 | -------------------------------------------------------------------------------- /web/src/features/meta/types.ts: -------------------------------------------------------------------------------- 1 | export type Store = MetaInfo & { 2 | loading: LoadingState; 3 | }; 4 | 5 | export type MetaInfo = { 6 | indexer: "shell" | "docker"; 7 | image: string; 8 | registry: string; 9 | repository: string; 10 | }; 11 | -------------------------------------------------------------------------------- /web/src/features/package/index.ts: -------------------------------------------------------------------------------- 1 | import * as PkgAction from "./actions"; 2 | import * as PkgApi from "./api"; 3 | import * as PkgSelector from "./selectors"; 4 | import * as PkgType from "./types"; 5 | 6 | export { PkgAction, PkgApi, PkgSelector, PkgType }; 7 | -------------------------------------------------------------------------------- /web/src/modules/Account/pages/RegisterLogin/styles.less: -------------------------------------------------------------------------------- 1 | .container { 2 | background-color: white; 3 | } 4 | 5 | .background { 6 | background-image: url("./tessellation.png"); 7 | min-height: @min-height; 8 | background-repeat: repeat; 9 | } 10 | -------------------------------------------------------------------------------- /web/src/features/meta/index.ts: -------------------------------------------------------------------------------- 1 | import * as MetaAction from "./actions"; 2 | import * as MetaApi from "./api"; 3 | import * as MetaSelector from "./selectors"; 4 | import * as MetaType from "./types"; 5 | 6 | export { MetaAction, MetaApi, MetaSelector, MetaType }; 7 | -------------------------------------------------------------------------------- /web/src/modules/Home/pages/Main/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PackageList from "./PackageList"; 3 | import styles from "./styles.less"; 4 | 5 | export default () => ( 6 |
7 | 8 |
9 | ); 10 | -------------------------------------------------------------------------------- /web/README.md: -------------------------------------------------------------------------------- 1 | # PCR Web 2 | 3 | This is the web interface for the PCR app. To build this application, run 4 | 5 | ```bash 6 | npm run build 7 | ``` 8 | 9 | 2 Dockerfiles have been provided for you. One with SSL and one without. Feel free 10 | to extend the application as you like. 11 | -------------------------------------------------------------------------------- /server/libs/path.go: -------------------------------------------------------------------------------- 1 | package libs 2 | 3 | import ( 4 | "os" 5 | ) 6 | 7 | // Check that path or file exists. This is a simple check and should suffice 8 | // for most use cases. 9 | func PathExists(path string) bool { 10 | _, err := os.Stat(path) 11 | return !os.IsNotExist(err) 12 | } 13 | -------------------------------------------------------------------------------- /web/src/modules/App/Layout/Footer/styles.less: -------------------------------------------------------------------------------- 1 | .footer { 2 | height: @footer-height; 3 | background-color: #191919 !important; 4 | text-align: center; 5 | 6 | .subtitle { 7 | color: #ffffff; 8 | } 9 | 10 | .text { 11 | color: rgba(255, 255, 255, 0.7); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /web/src/modules/Package/pages/PackageDetail/Files/types.ts: -------------------------------------------------------------------------------- 1 | export type ContextType = { 2 | isAdmin: boolean; 3 | filters: Filter; 4 | setFilters: (f: Partial) => void; 5 | }; 6 | 7 | export type Filter = { 8 | platform: "All" | string; 9 | version: "All" | string; 10 | }; 11 | -------------------------------------------------------------------------------- /web/src/modules/Package/pages/PackageDetail/PackageInfo/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import InstallationGuide from "./InstallationGuide"; 3 | 4 | import TopCard from "./TopCard"; 5 | 6 | export default () => ( 7 | <> 8 | 9 | 10 | 11 | ); 12 | -------------------------------------------------------------------------------- /web/src/modules/Package/pages/PackageDetail/PackageInfo/styles.less: -------------------------------------------------------------------------------- 1 | .main-card { 2 | margin-bottom: 16px !important; 3 | } 4 | 5 | .top-card { 6 | font-size: 16px; 7 | margin-bottom: 4px; 8 | 9 | > i { 10 | margin-right: 6px; 11 | } 12 | } 13 | 14 | .icon { 15 | margin-right: 6px; 16 | } 17 | -------------------------------------------------------------------------------- /web/src/features/meta/actions.ts: -------------------------------------------------------------------------------- 1 | import { createAsyncAction } from "typesafe-actions"; 2 | import * as MetaType from "./types"; 3 | 4 | export const fetchMetaInfoAsync = createAsyncAction( 5 | "FETCH_META_INFO_REQUEST", 6 | "FETCH_META_INFO_SUCCESS", 7 | "FETCH_META_INFO_FAILURE" 8 | )(); 9 | -------------------------------------------------------------------------------- /web/src/modules/Package/pages/PackageDetail/Files/Table/types.ts: -------------------------------------------------------------------------------- 1 | import { PkgType } from "@/features/package"; 2 | 3 | export type DataRow = { 4 | key: number; 5 | name: string; 6 | uploaded: string; 7 | downloads: number; 8 | channel: string; 9 | package: PkgType.RemovePackagePayload["package"]; 10 | }; 11 | -------------------------------------------------------------------------------- /server/infrastructure/database/migrations/01_create_channel_table.up.sql: -------------------------------------------------------------------------------- 1 | create table if not exists USERS 2 | ( 3 | ID serial primary key, 4 | CHANNEL varchar(50) not null unique, 5 | PASSWORD varchar(64) not null, 6 | EMAIL varchar(254) not null, 7 | JOIN_DATE timestamp not null default now() 8 | ); 9 | -------------------------------------------------------------------------------- /web/src/modules/Package/pages/PackageDetail/Files/Filters/styles.less: -------------------------------------------------------------------------------- 1 | .filter-fieldset { 2 | border: 1px solid @primary-color; 3 | margin: 24px 0; 4 | padding: 0 24px; 5 | 6 | > legend { 7 | width: inherit; 8 | margin: 0; 9 | padding: 0 12px; 10 | } 11 | 12 | .select { 13 | width: 100%; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /web/src/modules/Package/pages/PackageDetail/hooks.ts: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import * as Types from "./types"; 3 | 4 | export const PackageContext = React.createContext({ 5 | channel: "", 6 | pkg: "", 7 | }); 8 | 9 | export const usePackageContext = () => useContext(PackageContext); 10 | -------------------------------------------------------------------------------- /web/src/modules/Account/pages/RegisterLogin/Registration/Register/Errors.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from "react"; 2 | 3 | type Props = { 4 | errors: string; 5 | }; 6 | 7 | const Errors: FC = ({ errors }) => { 8 | if (errors === "") return null; 9 | 10 | return {errors}; 11 | }; 12 | 13 | export default Errors; 14 | -------------------------------------------------------------------------------- /web/src/modules/Help/index.ts: -------------------------------------------------------------------------------- 1 | import Overview from "./pages/Overview"; 2 | import Upload from "./pages/Upload"; 3 | 4 | export default [ 5 | { 6 | component: Overview, 7 | path: "/", 8 | title: "Overview", 9 | }, 10 | { 11 | component: Upload, 12 | path: "/upload", 13 | title: "Upload", 14 | }, 15 | ]; 16 | -------------------------------------------------------------------------------- /web/src/modules/App/index.tsx: -------------------------------------------------------------------------------- 1 | import ErrorBoundary from "@/components/ErrorBoundary"; 2 | import React from "react"; 3 | import Layout from "./Layout"; 4 | import RouteSwitch from "./RouteSwitch"; 5 | 6 | export default () => ( 7 | 8 | 9 | 10 | 11 | 12 | ); 13 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /web/src/modules/Home/pages/Main/PackageList/hooks.ts: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | 3 | export const SearchContext = React.createContext<{ 4 | search: string; 5 | setSearch: (v: string) => void; 6 | }>({ 7 | search: "", 8 | setSearch: () => {}, 9 | }); 10 | 11 | export const useSearchContext = () => useContext(SearchContext); 12 | -------------------------------------------------------------------------------- /_example/Makefile: -------------------------------------------------------------------------------- 1 | PROJECT_NAME = "PCR" 2 | 3 | start: stop 4 | docker-compose -p $(PROJECT_NAME) up -d 5 | 6 | stop: 7 | docker-compose -p $(PROJECT_NAME) down 8 | 9 | start-ssl: stop-ssl 10 | docker-compose -p $(PROJECT_NAME) -f docker-compose.ssl.yml up -d 11 | 12 | stop-ssl: 13 | docker-compose -p $(PROJECT_NAME) -f docker-compose.ssl.yml down 14 | -------------------------------------------------------------------------------- /cli/config/root.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | // RootCmd represents the base command when called without any subcommands 8 | var RootCmd = &cobra.Command{ 9 | Use: "config", 10 | Short: "PCR cli configuration", 11 | } 12 | 13 | func init() { 14 | RootCmd.AddCommand(setConfCmd, getConfCmd, listConfCmd) 15 | } 16 | -------------------------------------------------------------------------------- /server/infrastructure/database/postgres/utils.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import ( 4 | "github.com/hashicorp/go-multierror" 5 | ) 6 | 7 | func joinErrors(errors []error) error { 8 | if len(errors) == 1 { 9 | return errors[0] 10 | } 11 | 12 | var err error 13 | 14 | for _, e := range errors { 15 | err = multierror.Append(err, e) 16 | } 17 | return err 18 | } 19 | -------------------------------------------------------------------------------- /web/src/modules/UploadPage/pages/UploadForm/styles.less: -------------------------------------------------------------------------------- 1 | .container { 2 | min-height: @min-height; 3 | padding: 12px 40px 0; 4 | } 5 | 6 | .submit-box { 7 | margin-top: 12px !important; 8 | } 9 | 10 | .success-message { 11 | display: flex; 12 | flex-direction: column; 13 | 14 | div { 15 | display: flex; 16 | justify-content: space-between; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /web/src/infrastructure/hooks.ts: -------------------------------------------------------------------------------- 1 | import { isEqual } from "lodash"; 2 | import { useSelector } from "react-redux"; 3 | import { RootState } from "./rootState"; 4 | 5 | export const useRouter = () => useSelector((state: RootState) => state.router); 6 | 7 | export const useRootSelector = ( 8 | selector: (state: RootState) => T 9 | ) => useSelector(selector, isEqual); 10 | -------------------------------------------------------------------------------- /web/src/modules/Package/pages/PackageDetail/Header/styles.less: -------------------------------------------------------------------------------- 1 | .title { 2 | font-size: 28px; 3 | font-weight: 600; 4 | 5 | .channel { 6 | color: @primary-color; 7 | 8 | &:hover { 9 | color: @primary-color-dark; 10 | } 11 | } 12 | 13 | .subtitle { 14 | margin: 24px 0 32px 0; 15 | font-size: 18px; 16 | font-weight: normal; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /server/conda.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM continuumio/miniconda3:latest 2 | 3 | # Update and clears cache (to reduce image size) 4 | RUN apt-get update && \ 5 | apt-get upgrade -y && \ 6 | apt-get clean -y && \ 7 | conda install conda-build conda-verify -y && \ 8 | conda update --all -y && \ 9 | conda clean --all -y 10 | 11 | WORKDIR /var/condapkg 12 | 13 | ENTRYPOINT ["conda"] 14 | -------------------------------------------------------------------------------- /web/src/infrastructure/rootState.ts: -------------------------------------------------------------------------------- 1 | import { MetaType } from "@/features/meta"; 2 | import { PkgType } from "@/features/package"; 3 | import { ChnType } from "@/features/channel"; 4 | import { RouterState } from "connected-react-router"; 5 | 6 | export type RootState = { 7 | meta: MetaType.Store; 8 | pkg: PkgType.Store; 9 | channel: ChnType.Store; 10 | router: RouterState; 11 | }; 12 | -------------------------------------------------------------------------------- /web/src/infrastructure/rootAction.ts: -------------------------------------------------------------------------------- 1 | import { MetaAction } from "@/features/meta"; 2 | import { PkgAction } from "@/features/package"; 3 | import { ChnAction } from "@/features/channel"; 4 | 5 | import { ActionType } from "typesafe-actions"; 6 | 7 | type AllActions = 8 | | ActionType 9 | | ActionType 10 | | ActionType; 11 | 12 | export default AllActions; 13 | -------------------------------------------------------------------------------- /web/src/modules/Package/index.tsx: -------------------------------------------------------------------------------- 1 | import ChannelDetail from "./pages/ChannelDetail"; 2 | import PackageDetail from "./pages/PackageDetail"; 3 | 4 | export default [ 5 | { 6 | component: ChannelDetail, 7 | path: "/:channel", 8 | title: "Channel Detail", 9 | }, 10 | { 11 | component: PackageDetail, 12 | path: "/:channel/:pkg", 13 | title: "Package Detail", 14 | }, 15 | ] as ModuleRoute[]; 16 | -------------------------------------------------------------------------------- /web/src/modules/Package/pages/PackageDetail/Files/hooks.ts: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import { ContextType } from "./types"; 3 | 4 | export const FileContext = React.createContext({ 5 | filters: { 6 | platform: "All", 7 | version: "All,", 8 | }, 9 | setFilters: () => {}, 10 | isAdmin: false, 11 | }); 12 | 13 | export const useFileContext = () => useContext(FileContext); 14 | -------------------------------------------------------------------------------- /web/src/components/ErrorBoundary/styles.less: -------------------------------------------------------------------------------- 1 | @import "~antd/lib/style/color/colors"; 2 | 3 | .error-page { 4 | background-color: white; 5 | min-height: @min-height; 6 | font-family: "Gotham Rounded SSm A", "Gotham Rounded SSm B", Helvetica, Arial, 7 | sans-serif; 8 | margin: 20px auto; 9 | padding-top: 40px; 10 | display: flex; 11 | flex-direction: column; 12 | align-items: center; 13 | color: #46575e; 14 | } 15 | -------------------------------------------------------------------------------- /web/src/modules/Help/Layout/Menu/styles.less: -------------------------------------------------------------------------------- 1 | .title { 2 | color: @primary-color; 3 | font-size: 22px; 4 | margin-bottom: 6px; 5 | width: 100%; 6 | } 7 | 8 | .link { 9 | font-size: 18px; 10 | color: #797979; 11 | } 12 | 13 | .selected { 14 | color: #1890ff; 15 | } 16 | 17 | .group-container { 18 | display: flex; 19 | flex-direction: column; 20 | 21 | .link-container { 22 | margin-bottom: 6px; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /server/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | # This compose file is for development purposes! 2 | 3 | version: "3.7" 4 | 5 | services: 6 | db: 7 | image: postgres:12-alpine 8 | restart: always 9 | environment: 10 | POSTGRES_USER: user 11 | POSTGRES_PASSWORD: password 12 | POSTGRES_DB: pcrdb 13 | ports: 14 | - "5432:5432" 15 | # volumes: 16 | # - pcrdb:/var/lib/postgresql/data 17 | # 18 | #volumes: 19 | # pcrdb: 20 | -------------------------------------------------------------------------------- /server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | log "github.com/sirupsen/logrus" 5 | ) 6 | 7 | func main() { 8 | setLogger() 9 | 10 | log.Info("Initalizing application") 11 | app, err := NewApp() 12 | if err != nil { 13 | log.Fatal("Fatal error encountered during application startup", err) 14 | } 15 | 16 | <-app.RunServers() 17 | } 18 | 19 | func setLogger() { 20 | log.SetFormatter(&log.TextFormatter{ 21 | FullTimestamp: true, 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /web/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12-stretch as builder 2 | 3 | WORKDIR /app 4 | 5 | RUN apt-get update && apt-get upgrade -y 6 | 7 | COPY ["package.json", "package-lock.json", "./"] 8 | 9 | RUN npm ci 10 | 11 | COPY . . 12 | 13 | RUN npm run build 14 | 15 | FROM nginx:1.17 16 | 17 | RUN apt-get update && apt-get upgrade -y 18 | COPY --from=builder /app/build /usr/share/nginx/html 19 | COPY --from=builder /app/nginx.conf /etc/nginx/conf.d/app.conf 20 | -------------------------------------------------------------------------------- /server/Makefile: -------------------------------------------------------------------------------- 1 | PROJECT_NAME = "PCR" 2 | 3 | server-image: 4 | docker image build -t danielbok/pcr-server . 5 | 6 | conda-image: 7 | docker image build -t danielbok/conda-repo-mgr -f conda.Dockerfile . 8 | docker image prune -f 9 | 10 | start: stop 11 | docker-compose -p $(PROJECT_NAME) up -d 12 | 13 | stop: 14 | docker-compose -p $(PROJECT_NAME) down 15 | 16 | create-migration: 17 | migrate.exe create -dir store/migrations -ext sql -seq -digits 2 18 | -------------------------------------------------------------------------------- /server/api/errors.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | ErrInvalidCredential = errors.New("invalid credential") 9 | ErrOpeningCondaPackage = errors.New("could not open compressed package archive") 10 | ErrSavingPackageToDisk = errors.New("could not save conda package to disk") 11 | ErrParsingFormFile = errors.New("could not parse uploaded file. Please ensure that you have uploaded a valid file with 'file' as the form key") 12 | ) 13 | -------------------------------------------------------------------------------- /web/src/modules/Help/Layout/index.tsx: -------------------------------------------------------------------------------- 1 | import { Col, Row } from "antd"; 2 | import React, { FC } from "react"; 3 | 4 | import Menu from "./Menu"; 5 | import styles from "./styles.less"; 6 | 7 | const Layout: FC = ({ children }) => ( 8 |
9 | 10 | 11 | 12 | 13 | {children} 14 | 15 |
16 | ); 17 | 18 | export default Layout; 19 | -------------------------------------------------------------------------------- /web/ssl.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12-stretch as builder 2 | 3 | WORKDIR /app 4 | 5 | RUN apt-get update && apt-get upgrade -y 6 | 7 | COPY ["package.json", "package-lock.json", "./"] 8 | 9 | RUN npm ci 10 | 11 | COPY . . 12 | 13 | RUN npm run build 14 | 15 | FROM nginx:1.17 16 | 17 | RUN apt-get update && apt-get upgrade -y 18 | COPY --from=builder /app/build /usr/share/nginx/html 19 | COPY --from=builder /app/nginx-ssl.conf /etc/nginx/conf.d/app.conf 20 | -------------------------------------------------------------------------------- /web/src/modules/Home/pages/Main/PackageList/ResultTable/styles.less: -------------------------------------------------------------------------------- 1 | .header { 2 | font-weight: 600; 3 | font-size: 18px; 4 | } 5 | 6 | .list-main { 7 | background-color: white; 8 | 9 | :global(.ant-list-header) { 10 | background: #797979; 11 | color: white; 12 | } 13 | 14 | .alternate { 15 | background-color: #f8f8f8; 16 | } 17 | } 18 | 19 | .link { 20 | color: @primary-color; 21 | 22 | &:hover { 23 | color: @primary-color-dark; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /web/src/modules/Package/pages/PackageDetail/Files/Table/styles.less: -------------------------------------------------------------------------------- 1 | .download-link { 2 | color: @primary-color; 3 | 4 | &:hover { 5 | color: @primary-color-dark; 6 | } 7 | } 8 | 9 | .table { 10 | background-color: white; 11 | } 12 | 13 | .remove-button { 14 | color: #1890ff; 15 | text-decoration: none; 16 | background-color: rgba(0, 0, 0, 0); 17 | outline: none; 18 | cursor: pointer; 19 | -webkit-transition: color 0.3s; 20 | transition: color 0.3s; 21 | } 22 | -------------------------------------------------------------------------------- /server/domain/entity/channel_test.go: -------------------------------------------------------------------------------- 1 | package entity_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | 8 | . "private-conda-repo/domain/entity" 9 | ) 10 | 11 | func TestChannel_HasValidPassword(t *testing.T) { 12 | c := NewChannel("daniel", "good-password", "daniel@gmail.com") 13 | valid := c.HasValidPassword("bad-password") 14 | require.False(t, valid) 15 | 16 | valid = c.HasValidPassword("good-password") 17 | require.True(t, valid) 18 | } 19 | -------------------------------------------------------------------------------- /web/src/features/package/selectors.ts: -------------------------------------------------------------------------------- 1 | import { RootState } from "@/infrastructure/rootState"; 2 | 3 | export const packageMeta = (state: RootState) => state.pkg.packages; 4 | export const packageDetail = (state: RootState) => state.pkg.packageDetail; 5 | export const isAdmin = (state: RootState) => 6 | state.channel.validated && 7 | state.channel.channel === state.pkg.packageDetail.channel; 8 | 9 | export const channelPackages = (state: RootState) => 10 | state.pkg.channelPackages; 11 | -------------------------------------------------------------------------------- /web/src/modules/Home/pages/Main/PackageList/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { SearchContext } from "./hooks"; 3 | import ResultTable from "./ResultTable"; 4 | import SearchBar from "./SearchBar"; 5 | 6 | export default () => { 7 | const [search, setSearch] = useState(""); 8 | 9 | return ( 10 | 11 | 12 | 13 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /server/domain/entity/package_count.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "time" 4 | 5 | type PackageCount struct { 6 | Id int `json:"-"` 7 | ChannelId int `json:"-"` 8 | Package string `json:"package"` 9 | BuildString string `json:"buildString"` 10 | BuildNumber int `json:"buildNumber"` 11 | Version string `json:"version"` 12 | Platform string `json:"platform"` 13 | Count int `json:"count"` 14 | UploadDate time.Time `json:"uploadDate"` 15 | } 16 | -------------------------------------------------------------------------------- /web/src/modules/Account/pages/RegisterLogin/Registration/Register/styles.less: -------------------------------------------------------------------------------- 1 | .welcome { 2 | font-size: 16px; 3 | } 4 | 5 | .submit-button { 6 | background-color: @primary-color !important; 7 | border-color: @primary-color !important; 8 | 9 | &:hover { 10 | background-color: @primary-color-dark !important; 11 | border-color: @primary-color-dark !important; 12 | } 13 | 14 | &:disabled { 15 | background-color: #f5f5f5 !important; 16 | border-color: #d9d9d9 !important; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /cli/upload/package.go: -------------------------------------------------------------------------------- 1 | package upload 2 | 3 | import "fmt" 4 | 5 | type Package struct { 6 | Name string `json:"name"` 7 | Version string `json:"version"` 8 | BuildString string `json:"buildString"` 9 | BuildNumber int `json:"buildNumber"` 10 | Platform string `json:"platform"` 11 | } 12 | 13 | // Returns the package's full filename (i.e. perfana-0.0.6-py_0.tar.bz2) 14 | func (p *Package) Filename() string { 15 | return fmt.Sprintf("%s-%s-%s_%d.tar.bz2", p.Name, p.Version, p.BuildString, p.BuildNumber) 16 | } 17 | -------------------------------------------------------------------------------- /web/src/modules/Account/pages/RegisterLogin/Registration/Login/styles.less: -------------------------------------------------------------------------------- 1 | .submit-button { 2 | background-color: @primary-color !important; 3 | border-color: @primary-color !important; 4 | 5 | &:hover { 6 | background-color: @primary-color-dark !important; 7 | border-color: @primary-color-dark !important; 8 | } 9 | 10 | &:disabled { 11 | background-color: #f5f5f5 !important; 12 | border-color: #d9d9d9 !important; 13 | } 14 | } 15 | 16 | .error { 17 | margin-top: 4px; 18 | color: #ff2f31; 19 | } 20 | -------------------------------------------------------------------------------- /web/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | root /usr/share/nginx/html; 4 | index index.html index.htm; 5 | 6 | location / { 7 | try_files $uri $uri/ /index.html =404; 8 | add_header Cache-Control "no-store, no-cache, must-revalidate"; 9 | } 10 | 11 | location /static { 12 | expires 1y; 13 | add_header Cache-Control "public"; 14 | access_log off; 15 | } 16 | 17 | error_page 500 502 503 504 /50x.html; 18 | location = /50x.html { 19 | root /usr/share/nginx/html; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /web/src/modules/Account/pages/RegisterLogin/Description/styles.less: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | text-align: center; 6 | padding-top: 40px; 7 | 8 | .icon { 9 | width: 112px; 10 | } 11 | 12 | .title { 13 | font-size: 36px; 14 | font-weight: bold; 15 | color: #43b02a; 16 | 17 | > span { 18 | font-weight: normal; 19 | } 20 | } 21 | 22 | .subtitle { 23 | margin-top: 16px; 24 | font-size: 20px; 25 | color: black; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /web/src/modules/Account/pages/RegisterLogin/Registration/Login/Errors.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from "react"; 2 | import { useLoginContext } from "./hooks"; 3 | import { Credential } from "./reducer"; 4 | 5 | type Props = { 6 | field: keyof Credential; 7 | }; 8 | 9 | const Errors: FC = ({ field }) => { 10 | const { errors } = useLoginContext().state; 11 | if (errors[field].length === 0) return null; 12 | 13 | const messages = errors[field].join(" "); 14 | return {messages}; 15 | }; 16 | 17 | export default Errors; 18 | -------------------------------------------------------------------------------- /web/src/modules/App/Layout/index.tsx: -------------------------------------------------------------------------------- 1 | import { Layout } from "antd"; 2 | import React, { FC } from "react"; 3 | import Footer from "./Footer"; 4 | import Header from "./Header"; 5 | 6 | import styles from "./styles.less"; 7 | 8 | const { Content } = Layout; 9 | 10 | const AppLayout: FC = ({ children }) => ( 11 | 12 |
13 | 14 |
{children}
15 |
16 |