21 Commits

Author SHA1 Message Date
8e0d076e67 Merge pull request 'fix(#13): Show empty results instead of 404' (#17) from no-results into master
All checks were successful
Docker Image CI / build (push) Successful in 7m31s
Reviewed-on: #17
Reviewed-by: nikolar <nikolar@artixlinux.org>
2024-06-15 00:50:42 +02:00
31d3cb5f27 fix(#13): Show empty results instead of 404
All checks were successful
Docker Image CI / build (pull_request) Successful in 6m46s
2024-06-14 12:16:59 -05:00
a1e748c0a9 Merge pull request 'feat: allow user to choose how links behave' (#16) from remove-target-blank into master
All checks were successful
Docker Image CI / build (push) Successful in 6m48s
Reviewed-on: #16
2024-06-14 00:46:09 +02:00
4f2c810a2f feat: allow user to choose how links behave
All checks were successful
Docker Image CI / build (pull_request) Successful in 6m55s
2024-06-13 17:43:18 -05:00
3ce6daea15 Merge pull request 'feat: drop universe, add gremlins repos' (#12) from repos-update into master
All checks were successful
Docker Image CI / build (push) Successful in 7m25s
Reviewed-on: #12
Reviewed-by: artoo <artoo@artixlinux.org>
Reviewed-by: nikolar <nikolar@artixlinux.org>
2024-06-14 00:17:31 +02:00
16fd963c8d use printf instead of echo
All checks were successful
Docker Image CI / build (pull_request) Successful in 7m44s
2024-06-13 17:09:44 -05:00
6d3cd0566e feat: drop universe, add gremlins repos
All checks were successful
Docker Image CI / build (pull_request) Successful in 7m20s
2024-06-13 15:40:48 -05:00
42fd6f186f Back to development: 0.1.2 (#11)
All checks were successful
Docker Image CI / build (push) Successful in 7m22s
Reviewed-on: #11
2024-06-13 17:40:29 +02:00
98699fcdeb Merge pull request 'fix gitea image name' (#10) from gitea-deploy into master
Some checks failed
Docker Image CI / build (push) Failing after 7m29s
Reviewed-on: #10
2024-06-13 16:45:57 +02:00
e2c8035b99 fix gitea image name
Some checks failed
Docker Image CI / build (pull_request) Has been cancelled
2024-06-13 09:38:06 -05:00
7bc9725f63 Merge pull request 'fix workflow' (#9) from action_fix into master
Some checks failed
Docker Image CI / build (push) Has been cancelled
Reviewed-on: #9
2024-06-13 06:55:06 +02:00
eb12a979b6 fix workflow
All checks were successful
Docker Image CI / build (pull_request) Successful in 8m13s
2024-06-12 23:46:15 -05:00
1e33c89223 Merge pull request 'change version to v0.1.1' (#8) from corysanin/artixweb_packages:first-release into master
Some checks failed
Docker Image CI / build (push) Failing after 1m0s
Reviewed-on: #8
2024-06-13 00:04:35 +02:00
784fe9a438 change version to v0.1.1
Some checks failed
Docker Image CI / build (pull_request) Has been cancelled
2024-06-12 16:53:22 -05:00
3e2857dcb1 Merge pull request 'enable builds on pushes to master' (#7) from corysanin/artixweb_packages:build-on-push into master
All checks were successful
Docker Image CI / build (push) Successful in 12m35s
Reviewed-on: #7
2024-06-12 22:59:24 +02:00
0f6cbfa52a enable builds on pushes to master
Some checks failed
Docker Image CI / build (pull_request) Failing after 1m38s
2024-06-12 15:58:50 -05:00
eae61ea52e Merge pull request 'Update dependencies, create action' (#6) from corysanin/artixweb_packages:action into master
Reviewed-on: #6
2024-06-12 04:34:11 +02:00
dc3bc810bb bump alpm to 3.0.4 2024-03-25 22:47:31 -05:00
f57e542126 fix: remove unused step 2024-03-19 21:55:36 -05:00
0752725119 feat: new streamlined Dockerfile 2024-03-19 21:41:10 -05:00
80015040f1 feat: use gitea pipeline to build docker image 2024-03-19 20:54:41 -05:00
27 changed files with 3067 additions and 430 deletions

View File

@@ -0,0 +1,117 @@
name: Docker Image CI
on:
workflow_dispatch:
branches: [ master ]
push:
branches: [ master ]
tags:
- 'v*'
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 90
strategy:
fail-fast: true
env:
REGISTRY: gitea.artixlinux.org
DH_REGISTRY: docker.io
REPO_ORG: ${{ gitea.repository_owner }}
DH_ORG: artixlinux
IMAGE_NAME: artixweb_packages
DH_IMAGE_NAME: artixweb-packages
ABSOLUTE_IMAGE: ${{ env.REGISTRY }}/${{ env.REPO_ORG }}/${{ env.IMAGE_NAME }}
ABSOLUTE_DH_IMAGE: ${{ env.DH_REGISTRY }}/${{ env.DH_ORG }}/${{ env.DH_IMAGE_NAME }}
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: https://github.com/actions/checkout@v4
- name: Set up docker
run: curl -fsSL https://get.docker.com | sh
- name: Set up QEMU
uses: https://github.com/docker/setup-qemu-action@v2
- name: Set up Docker Buildx
id: buildx
uses: https://github.com/docker/setup-buildx-action@v2
with:
install: true
- name: Log in to the Container registry
uses: https://github.com/docker/login-action@v2
if: startsWith(gitea.ref, 'refs/tags/v')
with:
registry: ${{ env.REGISTRY }}
username: corysanin
password: ${{ secrets.PAT }}
- name: Log in to the Docker Hub
uses: https://github.com/docker/login-action@v2
if: startsWith(gitea.ref, 'refs/tags/v')
with:
registry: ${{ env.DH_REGISTRY }}
username: ${{ secrets.DOCKERHUB_USER }}
password: ${{ secrets.DOCKERHUB }}
- name: Define metadata variables
if: startsWith(gitea.ref, 'refs/tags/v')
run: |
sed -i "s/LABEL Version=.*/ARG version=${{ gitea.ref_name }}/" Dockerfile
cat Dockerfile
- name: Extract metadata for release Docker image
if: startsWith(gitea.ref, 'refs/tags/v')
id: meta
uses: https://github.com/docker/metadata-action@v5
with:
images: |
${{ env.ABSOLUTE_DH_IMAGE }}
# ${{ env.ABSOLUTE_IMAGE }}
##unexpected status from PUT request: 413 Request Entity Too Large
tags: |
type=raw,value=latest
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- name: Extract metadata for develop Docker image
if: "!startsWith(gitea.ref, 'refs/tags/v')"
id: meta-develop
uses: https://github.com/docker/metadata-action@v5
with:
images: |
${{ env.ABSOLUTE_IMAGE }}
tags: |
type=ref,enable=true,priority=600,prefix=,suffix=,event=branch
type=ref,enable=true,priority=600,prefix=pr-,suffix=,event=pr
- name: Build and push release Docker image
if: startsWith(gitea.ref, 'refs/tags/v')
uses: https://github.com/docker/build-push-action@v4
with:
file: Dockerfile.ci
target: deploy
pull: true
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64
- name: Build develop Docker image
if: "!startsWith(gitea.ref, 'refs/tags/v')"
uses: https://github.com/docker/build-push-action@v4
with:
file: Dockerfile.ci
target: deploy
pull: true
push: false
tags: ${{ steps.meta-develop.outputs.tags }}
labels: ${{ steps.meta-develop.outputs.labels }}
platforms: linux/amd64

2
.gitignore vendored
View File

@@ -1,6 +1,6 @@
# Cargo stuff # Cargo stuff
target/ target/
*Cargo.lock */Cargo.lock
# editor stuff # editor stuff
.vscode/ .vscode/

2627
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

58
Dockerfile.ci Normal file
View File

@@ -0,0 +1,58 @@
# This file is part of Artix Web Packages. See LICENSE file for details
# Copyright 2022 - Artix Linux
FROM artixlinux/artixlinux:base as build
# update pacman
# install clang and pkg-config
# install rustup and put $HOME/.cargo/bin in the $PATH
# install stable and nightly toolchains
# install rustfmt-preview and clippy-preview in the nightly toolchain
RUN set -x \
&& pacman -Syu --noconfirm \
&& pacman -S --noconfirm clang pkgconf \
&& pacman -Scc --noconfirm \
&& curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \
&& export PATH="$HOME/.cargo/bin:$PATH" \
&& rustup toolchain add stable nightly \
&& rustup component add --toolchain nightly rustfmt-preview clippy-preview
# Update PATH in the container
ENV PATH="/root/.cargo/bin:${PATH}"
ENV RUSTUP_TOOLCHAIN=stable
RUN pacman -Sy --noconfirm postgresql-libs
WORKDIR /build
COPY . ./
RUN cargo build --release
RUN set -x \
&& install -vd /usr/share/artixweb_packages/assets \
&& cp -rv crates/artixweb-packages/assets/* /usr/share/artixweb_packages/assets/ \
&& install -vd /usr/share/artixweb_packages/templates \
&& cp -v crates/artixweb-packages/templates/* /usr/share/artixweb_packages/templates/ \
&& install -vDm 755 target/release/artixweb_packages -t "/usr/share/artixweb_packages"
FROM artixlinux/artixlinux:base AS deploy
LABEL Maintainer="damnwidget@artixlinux.org"
LABEL Name="Artix Web Packages Container"
LABEL Version="Develop"
EXPOSE 1936/tcp
RUN pacman -Sy --noconfirm postgresql-libs \
&& set -x \
&& pacman -Syu --noconfirm \
&& pacman -Scc --noconfirm \
&& printf "[lib32]\nInclude = /etc/pacman.d/mirrorlist\n" >> /etc/pacman.conf \
&& printf "[system-gremlins]\nInclude = /etc/pacman.d/mirrorlist\n" >> /etc/pacman.conf \
&& printf "[world-gremlins]\nInclude = /etc/pacman.d/mirrorlist\n" >> /etc/pacman.conf \
&& printf "[galaxy-gremlins]\nInclude = /etc/pacman.d/mirrorlist\n" >> /etc/pacman.conf \
&& printf "[lib32-gremlins]\nInclude = /etc/pacman.d/mirrorlist\n" >> /etc/pacman.conf \
&& pacman -Sy --noconfirm
WORKDIR /usr/share/artixweb_packages
COPY --from=0 /usr/share/artixweb_packages ./
CMD ["./artixweb_packages"]

View File

@@ -2,7 +2,7 @@
# Copyright 2022 - Artix Linux # Copyright 2022 - Artix Linux
FROM artixlinux/artixweb-packages-ci:latest AS build FROM artixlinux/artixweb-packages-ci:latest AS build
LABEL Maintainer="nikolar@artixlinux.org" LABEL Maintainer="damnwidget@artixlinux.org"
LABEL Name="Artix Web Packages Container" LABEL Name="Artix Web Packages Container"
LABEL Version="0.1.0" LABEL Version="0.1.0"

View File

@@ -1,24 +1,21 @@
[package] [package]
name = "artix-gitea" name = "artix-gitea"
authors = ["Oscar Campos <damnwidget@artixlinux.org>"]
description = "Search for packages in Artix Linux repositories" description = "Search for packages in Artix Linux repositories"
authors = [
"Oscar Campos <damnwidget@artixlinux.org>",
"Nikola Radojević <nikolar@artixlinux.org>"
]
exclude = [".gitignore", ".cargo/config"] exclude = [".gitignore", ".cargo/config"]
repository = "gitea.artixlinux.org/artix/artixweb_packaes" repository = "gitea.artixlinux.org/artix/artixweb_packaes"
homepage = "https://packages.artixlinux.org" homepage = "https://packages.artixlinux.org"
keywords = ["artix", "packages", "gitea"] keywords = ["artix", "packages", "gitea"]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2021" edition = "2021"
version = "0.1.0" version = "0.1.2"
[lib] [lib]
name = "artix_gitea" name = "artix_gitea"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
actix-web = "4.3.1" actix-web = "4.0.1"
serde = { version = "1.0.163", features = ["derive"] } serde = { version = "1.0.136", features = ["derive"] }
awc = { version = "3.1.1", features = ["rustls"] } awc = { version = "3.0.0", features = ["rustls"] }
chrono = { version = "0.4.24", features = ["serde"] } chrono = { version = "0.4.19", features = ["serde"] }

View File

@@ -1,17 +1,14 @@
[package] [package]
name = "artix-pkglib" name = "artix-pkglib"
authors = ["Oscar Campos <damnwidget@artixlinux.org>"]
description = "Manipulate Artix Linux packages and databases" description = "Manipulate Artix Linux packages and databases"
authors = [
"Oscar Campos <damnwidget@artixlinux.org>",
"Nikola Radojević <nikolar@artixlinux.org>"
]
exclude = [".gitignore", ".cargo/config"] exclude = [".gitignore", ".cargo/config"]
repository = "gitea.artixlinux.org/artix/artixweb_packages" repository = "gitea.artixlinux.org/artix/artixweb_packages"
homepage = "https://packages.artixlinux.org" homepage = "https://packages.artixlinux.org"
keywords = ["artix", "packages"] keywords = ["artix", "packages"]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2021" edition = "2021"
version = "0.1.0" version = "0.1.2"
[lib] [lib]
name = "artix_pkglib" name = "artix_pkglib"
@@ -19,7 +16,7 @@ path = "src/lib.rs"
[dependencies] [dependencies]
alpm = "2.2.2" alpm = "3.0.4"
alpm-utils = "2.0.0" alpm-utils = "3.0.2"
pacmanconf = "2.0.0" pacmanconf = "2.1.0"
thiserror = "1.0.40" thiserror = "1.0.58"

View File

@@ -11,14 +11,14 @@ use alpm::{Alpm, Db, Package};
/// Data structure used to returns information about an specific packages search /// Data structure used to returns information about an specific packages search
pub struct PackagesResultData<'a> { pub struct PackagesResultData<'a> {
packages: Vec<Package<'a>>, packages: Vec<&'a Package>,
total: usize, total: usize,
} }
impl<'a> PackagesResultData<'a> { impl<'a> PackagesResultData<'a> {
/// Retrieve the packages of this result if any /// Retrieve the packages of this result if any
#[must_use] #[must_use]
pub fn packages(&self) -> &Vec<Package<'a>> { pub fn packages(&self) -> &Vec<&Package> {
&self.packages &self.packages
} }
@@ -32,22 +32,19 @@ impl<'a> PackagesResultData<'a> {
/// Returns back all the packages contained in the given database /// Returns back all the packages contained in the given database
/// If the database is invalid it returns None /// If the database is invalid it returns None
#[must_use] #[must_use]
pub fn get_packages(db: Db<'_>, limit: usize, offset: usize) -> Vec<Package<'_>> { pub fn get_packages<'a>(db: &'a Db, limit: usize, offset: usize) -> Vec<&'a Package> {
let mut ret = Vec::new(); let mut ret = Vec::new();
if db.is_valid().is_ok() { if db.is_valid().is_ok() {
let pkgs = db.pkgs(); let pkgs = db.pkgs();
if pkgs.is_empty() {
return ret;
}
ret.extend(pkgs); ret.extend(pkgs);
// if offset is set, split the vector at the given offset // if offset is set, truncate the vector at the given offset
if offset != 0 { if offset != 0 && offset < ret.len() {
ret = ret.split_at(offset).1.to_vec(); ret = ret.split_off(offset);
} }
// if limit is set, truncate the vector at it // if limit is set, truncate the vector at it
if limit != 0 { if limit != 0 && limit < ret.len() {
ret.truncate(limit); ret.truncate(limit);
} }
} }
@@ -85,7 +82,7 @@ pub fn get_all_packages<'a>(
if ret.is_empty() { if ret.is_empty() {
return PackagesResultData { return PackagesResultData {
packages: ret, packages: Vec::new(),
total: 0, total: 0,
}; };
} }
@@ -126,14 +123,14 @@ mod test {
let alpm = Alpm::new("/", &db_location).unwrap(); let alpm = Alpm::new("/", &db_location).unwrap();
let db = alpm.register_syncdb("world", SigLevel::NONE).unwrap(); let db = alpm.register_syncdb("world", SigLevel::NONE).unwrap();
let pkgs = get_packages(db, 0, 0); let pkgs = get_packages(*db, 0, 0);
assert!(!pkgs.is_empty()); assert!(!pkgs.is_empty());
let pkgs = get_packages(db, 2, 0); let pkgs = get_packages(*db, 2, 0);
assert_eq!(pkgs.len(), 2); assert_eq!(pkgs.len(), 2);
let pkgs = get_packages(db, 0, 0); let pkgs = get_packages(*db, 0, 0);
let pkgs2 = get_packages(db, 0, 10); let pkgs2 = get_packages(*db, 0, 10);
assert_eq!(pkgs2[0].name(), pkgs[10].name()); assert_eq!(pkgs2[0].name(), pkgs[10].name());
} }

View File

@@ -5,7 +5,7 @@
Copyright (c) 2022 - Oscar Campos <damnwidget@artixlinux.org> Copyright (c) 2022 - Oscar Campos <damnwidget@artixlinux.org>
*/ */
use alpm::{Alpm, AlpmList, Dep, Package, Ver}; use alpm::{Alpm, AlpmList, Dep, Package, Pkg, Ver};
use crate::error::Error; use crate::error::Error;
@@ -17,7 +17,7 @@ use crate::error::Error;
pub fn get_package_dependencies<'a>( pub fn get_package_dependencies<'a>(
alpm: &'a Alpm, alpm: &'a Alpm,
package_name: &'a str, package_name: &'a str,
) -> Result<AlpmList<'a, Dep<'a>>, Error<'a>> { ) -> Result<AlpmList<'a, &'a Dep>, Error<'a>> {
for db in alpm.syncdbs() { for db in alpm.syncdbs() {
let db_pkgs = db.search([package_name].iter()); let db_pkgs = db.search([package_name].iter());
if let Ok(pkgs) = db_pkgs { if let Ok(pkgs) = db_pkgs {
@@ -40,7 +40,7 @@ pub fn get_package_dependencies<'a>(
pub fn get_package_optional_dependencies<'a>( pub fn get_package_optional_dependencies<'a>(
alpm: &'a Alpm, alpm: &'a Alpm,
package_name: &'a str, package_name: &'a str,
) -> Result<AlpmList<'a, Dep<'a>>, Error<'a>> { ) -> Result<AlpmList<'a, &'a Dep>, Error<'a>> {
for db in alpm.syncdbs() { for db in alpm.syncdbs() {
let db_pkgs = db.search([package_name].iter()); let db_pkgs = db.search([package_name].iter());
if let Ok(pkgs) = db_pkgs { if let Ok(pkgs) = db_pkgs {
@@ -84,7 +84,7 @@ pub fn get_package_version<'a>(
/// ///
/// If the package can not be found a [`Error::PackageNotFound`] error is returned /// If the package can not be found a [`Error::PackageNotFound`] error is returned
#[allow(clippy::module_name_repetitions)] #[allow(clippy::module_name_repetitions)]
pub fn get_package<'a>(alpm: &'a Alpm, package_name: &'a str) -> Result<Package<'a>, Error<'a>> { pub fn get_package<'a>(alpm: &'a Alpm, package_name: &'a str) -> Result<&'a Pkg, Error<'a>> {
for db in alpm.syncdbs() { for db in alpm.syncdbs() {
let db_pkgs = db.search([package_name].iter()); let db_pkgs = db.search([package_name].iter());
if let Ok(pkgs) = db_pkgs { if let Ok(pkgs) = db_pkgs {
@@ -110,15 +110,15 @@ pub fn retrieve_providers<'a>(
arch: &str, arch: &str,
alpm: &'a Alpm, alpm: &'a Alpm,
requirement: &'a str, requirement: &'a str,
) -> Vec<Package<'a>> { ) -> Vec<&'a Package> {
let mut pkgs = Vec::new(); let mut pkgs: Vec<&'a Package> = Vec::new();
for db in alpm.syncdbs() { for db in alpm.syncdbs() {
if arch.to_lowercase() != "any" && db.name() == "lib32" { if arch.to_lowercase() != "any" && db.name() == "lib32" {
continue; continue;
} }
for pkg in db.pkgs() { for pkg in db.pkgs() {
let mut a: Vec<Dep<'_>> = Vec::new(); let mut a: Vec<&'a Dep> = Vec::new();
a.extend(pkg.provides().iter().filter(|d| d.name() == requirement)); a.extend(pkg.provides().iter().filter(|d| d.name() == requirement));
if !a.is_empty() { if !a.is_empty() {
pkgs.push(pkg); pkgs.push(pkg);

View File

@@ -1,16 +1,13 @@
[package] [package]
name = "artixweb_packages" name = "artixweb_packages"
authors = ["Oscar Campos <damnwidget@artixlinux.org>"]
description = "Artix Linux Packages Information Website" description = "Artix Linux Packages Information Website"
authors = [
"Oscar Campos <damnwidget@artixlinux.org>",
"Nikola Radojević <nikolar@artixlinux.org>"
]
exclude = [".gitignore", ".cargo/config"] exclude = [".gitignore", ".cargo/config"]
repository = "gitea.artixlinux.org/artix/artixweb_packages" repository = "gitea.artixlinux.org/artix/artixweb_packages"
keywords = ["artix", "packages"] keywords = ["artix", "packages"]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2021" edition = "2021"
version = "0.1.0" version = "0.1.2"
[[bin]] [[bin]]
name = "artixweb_packages" name = "artixweb_packages"
@@ -18,33 +15,32 @@ test = false
bench = false bench = false
[dependencies] [dependencies]
artix-gitea = { path = "../artix-gitea", version = "=0.1.0" } artix-gitea = { path = "../artix-gitea", version = "=0.1.2" }
artix-pkglib = { path = "../artix-pkglib", version = "=0.1.0" } artix-pkglib = { path = "../artix-pkglib", version = "=0.1.2" }
actix-files = "0.6.2" actix-files = "0.6.0"
actix-identity = "0.4.0" actix-identity = "0.4"
argon2 = "0.5.0" argon2 = "0.4.0"
askama = "0.12.0" askama = "0.11.1"
derive_more = "0.99.17" derive_more = "0.99.17"
env_logger = "0.10.0" env_logger = "0.9.0"
lazy_static = "1.4.0" lazy_static = "1.4.0"
lettre = "0.10.4" lettre = "0.10.0-rc.4"
log = "0.4.17" log = "0.4.14"
r2d2 = "0.8.10" r2d2 = "0.8"
time = "0.3.21" time = "0.3"
actix-session = { version = "0.7.2", features = ["cookie-session"] } actix-session = { version = "0.6.0", features = ["cookie-session"] }
actix-web = { version = "4.3.1", features = ["rustls"] } actix-web = { version = "4.0.1", features = ["rustls"] }
clap = { version = "4.3.0", features = ["cargo", "suggestions", "env"] } clap = { version = "3.1.5", features = ["cargo", "suggestions", "env"] }
chrono = { version = "0.4.24", features = ["serde"] } chrono = { version = "0.4.19", features = ["serde"] }
diesel = { version = "2.0.4", features = [ diesel = { version = "1.4.8", features = [
"postgres", "postgres",
"sqlite", "uuidv07",
"uuid",
"r2d2", "r2d2",
"chrono", "chrono",
] } ] }
rand_core = { version = "0.6.4", features = ["std"] } rand_core = { version = "0.6", features = ["std"] }
serde = { version = "^1.0", features = ["derive"] } serde = { version = "^1.0", features = ["derive"] }
serde_json = { version = "1.0.96" } serde_json = { version = "1.0.79" }
uuid = { version = "1.3.1", features = ["serde", "v4"] } uuid = { version = "0.8", features = ["serde", "v4"] }

View File

@@ -3,41 +3,30 @@
Copyright (c) 2022 - Artix Linux Copyright (c) 2022 - Artix Linux
Copyright (c) 2022 - Oscar Campos <damnwidget@artixlinux.org> Copyright (c) 2022 - Oscar Campos <damnwidget@artixlinux.org>
Copyright (c) 2023 - Nikola Radojević <nikolar@artixlinux.org>
*/ */
use std::{env, fs::File, io::Read, path::Path}; use std::{env, fs::File, io::Read, path::Path};
use argon2::{ use argon2::{password_hash::SaltString, Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
password_hash::SaltString, Argon2, PasswordHash, PasswordHasher,
PasswordVerifier,
};
use artix_pkglib::prelude::{Alpm, SigLevel}; use artix_pkglib::prelude::{Alpm, SigLevel};
use rand_core::OsRng; use rand_core::OsRng;
use super::utils::errors::ServiceError; use super::lib::errors::ServiceError;
lazy_static::lazy_static! { lazy_static::lazy_static! {
/// Global constant application settings /// Global constant application settings
pub static ref SETTINGS: AppSettings = AppSettings { pub static ref SETTINGS: AppSettings = AppSettings {
app_name: env::var("APP_NAME") app_name: env::var("APP_NAME").unwrap_or_else(|_| String::from("ArtixWeb Packages")),
.unwrap_or_else(|_| String::from("ArtixWeb Packages")), gitea_url: env::var("GITEA_URL").unwrap_or_else(|_| String::from(artix_gitea::prelude::GITEA_URL)),
gitea_url: env::var("GITEA_URL") gitea_api_url: env::var("GITEA_API_URL").unwrap_or_else(|_| String::from(artix_gitea::prelude::GITEA_API_URL)),
.unwrap_or_else(|_| String::from(artix_gitea::prelude::GITEA_URL)), gitea_token: env::var("GITEA_TOKEN").unwrap_or_else(|_| String::new()),
gitea_api_url: env::var("GITEA_API_URL") databases_path: env::var("DATABASES_PATH").unwrap_or_else(|_| String::from("/var/lib/pacman")),
.unwrap_or_else(|_| String::from(artix_gitea::prelude::GITEA_API_URL)), api_token: env::var("API_TOKEN").unwrap_or_else(|_| generate_random_token()),
gitea_token: env::var("GITEA_TOKEN")
.unwrap_or_else(|_| String::new()),
databases_path: env::var("DATABASES_PATH")
.unwrap_or_else(|_| String::from("/var/lib/pacman")),
api_token: env::var("API_TOKEN")
.unwrap_or_else(|_| generate_random_token()),
}; };
} }
lazy_static::lazy_static! { lazy_static::lazy_static! {
pub static ref SECRET_KEY: String = std::env::var("SECRET_KEY") pub static ref SECRET_KEY: String = std::env::var("SECRET_KEY").unwrap_or_else(|_| generate_random_token());
.unwrap_or_else(|_| generate_random_token());
} }
/// Data structure containing the site configuration /// Data structure containing the site configuration
@@ -84,8 +73,16 @@ fn generate_random_token() -> String {
token token
} }
const DATABASESS: [&str; 5] = const DATABASESS: [&str; 8] = [
["system", "world", "galaxy", "universe", "lib32"]; "system",
"world",
"galaxy",
"lib32",
"system-gremlins",
"world-gremlins",
"galaxy-gremlins",
"lib32-gremlins"
];
const MIN_PASSWORD_LENGTH: usize = 8; const MIN_PASSWORD_LENGTH: usize = 8;
/// Syncs the configured databases /// Syncs the configured databases

View File

@@ -3,24 +3,21 @@
Copyright (c) 2022 - Artix Linux Copyright (c) 2022 - Artix Linux
Copyright (c) 2022 - Oscar Campos <damnwidget@artixlinux.org> Copyright (c) 2022 - Oscar Campos <damnwidget@artixlinux.org>
Copyright (c) 2022 - Nikola Radojević <nikolar@artixlinux.org>
*/ */
use std::future::{ready, Ready}; use std::future::{ready, Ready};
use actix_identity::Identity; use actix_identity::Identity;
use actix_session::Session; use actix_session::Session;
use actix_web::{ use actix_web::{dev::Payload, http, web, Error, FromRequest, HttpRequest, HttpResponse};
dev::Payload, http, web, Error, FromRequest, HttpRequest, HttpResponse,
};
use askama::Template; use askama::Template;
use diesel::prelude::*; use diesel::prelude::*;
use diesel::PgConnection; use diesel::PgConnection;
use serde::Deserialize; use serde::Deserialize;
use crate::config::verify; use crate::config::verify;
use crate::lib::errors::ServiceError;
use crate::models::{Pool, SlimUser, User}; use crate::models::{Pool, SlimUser, User};
use crate::utils::errors::ServiceError;
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Data { pub struct Data {
@@ -79,8 +76,7 @@ pub async fn login(
session: Session, session: Session,
pool: web::Data<Pool>, pool: web::Data<Pool>,
) -> Result<HttpResponse, actix_web::Error> { ) -> Result<HttpResponse, actix_web::Error> {
let user = let user = web::block(move || query(&auth_data.into_inner(), &pool)).await??;
web::block(move || query(&auth_data.into_inner(), &pool)).await??;
let user_string = serde_json::to_string(&user).unwrap(); let user_string = serde_json::to_string(&user).unwrap();
id.remember(user_string); id.remember(user_string);
session.insert("user_email", user.email)?; session.insert("user_email", user.email)?;
@@ -98,12 +94,9 @@ pub async fn logout(id: Identity, session: Session) -> HttpResponse {
.finish() .finish()
} }
fn query( fn query(auth_data: &Data, pool: &web::Data<Pool>) -> Result<SlimUser, ServiceError> {
auth_data: &Data,
pool: &web::Data<Pool>,
) -> Result<SlimUser, ServiceError> {
use crate::schema::users::dsl::{email, users}; use crate::schema::users::dsl::{email, users};
let conn: &mut PgConnection = &mut pool.get().unwrap(); let conn: &PgConnection = &pool.get().unwrap();
let mut items = users let mut items = users
.filter(email.eq(&auth_data.email)) .filter(email.eq(&auth_data.email))
.load::<User>(conn)?; .load::<User>(conn)?;

View File

@@ -3,7 +3,6 @@
Copyright (c) 2022 - Artix Linux Copyright (c) 2022 - Artix Linux
Copyright (c) 2022 - Oscar Campos <damnwidget@artixlinux.org> Copyright (c) 2022 - Oscar Campos <damnwidget@artixlinux.org>
Copyright (c) 2022 - Nikola Radojević <nikolar@artixlinux.org>
*/ */
#![allow(clippy::unused_async, clippy::module_name_repetitions)] #![allow(clippy::unused_async, clippy::module_name_repetitions)]
@@ -39,12 +38,11 @@ pub async fn package_details(
pool: web::Data<Pool>, pool: web::Data<Pool>,
) -> Result<HttpResponse> { ) -> Result<HttpResponse> {
let start_time = std::time::Instant::now(); let start_time = std::time::Instant::now();
let user_email = let user_email = if let Some(email) = session.get::<String>("user_email").unwrap() {
if let Some(email) = session.get::<String>("user_email").unwrap() { email
email } else {
} else { String::new()
String::new() };
};
let s = if let Ok(details) = get_packages_details_inner(&data, pool).await { let s = if let Ok(details) = get_packages_details_inner(&data, pool).await {
PackageDetailsTemplate { PackageDetailsTemplate {
@@ -91,12 +89,10 @@ mod filters {
#[allow(clippy::trivially_copy_pass_by_ref)] #[allow(clippy::trivially_copy_pass_by_ref)]
pub fn show_date(timestamp: &i64) -> ::askama::Result<NaiveDateTime> { pub fn show_date(timestamp: &i64) -> ::askama::Result<NaiveDateTime> {
Ok(NaiveDateTime::from_timestamp_opt(*timestamp, 0).unwrap()) Ok(NaiveDateTime::from_timestamp(*timestamp, 0))
} }
pub(crate) fn provides_or_replaces( pub(crate) fn provides_or_replaces(deps: &[Dependency]) -> ::askama::Result<String> {
deps: &[Dependency],
) -> ::askama::Result<String> {
let mut elements = Vec::new(); let mut elements = Vec::new();
elements.extend( elements.extend(
deps.iter() deps.iter()

View File

@@ -23,6 +23,7 @@ use super::packages::{get_packages_inner, Response};
#[template(path = "base_index.html")] #[template(path = "base_index.html")]
struct BaseTemplate { struct BaseTemplate {
packages: Vec<Response>, packages: Vec<Response>,
repos: Vec<String>,
limit: usize, limit: usize,
query: String, query: String,
user_email: String, user_email: String,
@@ -33,6 +34,7 @@ struct BaseTemplate {
#[template(path = "packages_nav.html")] #[template(path = "packages_nav.html")]
struct PackagesNavigation { struct PackagesNavigation {
packages: Vec<Response>, packages: Vec<Response>,
repos: Vec<String>,
total: usize, total: usize,
offset: usize, offset: usize,
limit: usize, limit: usize,
@@ -62,7 +64,16 @@ pub async fn index(
String::new() String::new()
}; };
let valid_repos = vec!["world", "galaxy", "system", "universe", "lib32"]; let valid_repos = vec![
"system",
"world",
"galaxy",
"lib32",
"system-gremlins",
"world-gremlins",
"galaxy-gremlins",
"lib32-gremlins"
];
for parameter in query.0 { for parameter in query.0 {
let key: &str = &parameter.0; let key: &str = &parameter.0;
match key { match key {
@@ -110,38 +121,37 @@ pub async fn index(
repos.join(":") repos.join(":")
}; };
let s = if let Ok(result) = let mut s = PackagesNavigation {
user_email,
packages: Vec::new(),
repos,
total: 0,
query: query_url.join("&"),
total_pages: 1,
generation_time: 0,
offset,
limit
};
if let Ok(result) =
get_packages_inner(&repos_criteria, limit, offset, search_criteria, Some(&pool)).await get_packages_inner(&repos_criteria, limit, offset, search_criteria, Some(&pool)).await
{ {
PackagesNavigation { s.packages = result.0;
user_email, s.total = result.1;
packages: result.0, s.total_pages = if limit > 0 {
total: result.1, (result.1 as f64 / limit as f64).ceil() as usize
query: query_url.join("&"), } else {
total_pages: if limit > 0 { 1
(result.1 as f64 / limit as f64).ceil() as usize };
} else { }
1 s.generation_time = start_time.elapsed().as_millis();
},
generation_time: start_time.elapsed().as_millis(),
offset,
limit,
}
.render()
.unwrap()
} else {
return Ok(HttpResponse::NotFound()
.content_type("text/html")
.body("<html><body>404 - Page Not Found!</body></html>"));
};
Ok(HttpResponse::Ok().content_type("text/html").body(s)) Ok(HttpResponse::Ok().content_type("text/html").body(s.render().unwrap()))
} }
#[allow(clippy::unnecessary_wraps)] #[allow(clippy::unnecessary_wraps)]
mod filters { mod filters {
pub fn selected_repo(query: &str, repo: &str) -> ::askama::Result<bool> { pub fn selected_repo(repos: &Vec<String>, repo: &str) -> ::askama::Result<bool> {
Ok(query.to_lowercase().contains(&repo.to_lowercase())) Ok(repos.iter().any(|r| r == repo))
} }
pub fn details_url(pkg_name: &str) -> ::askama::Result<String> { pub fn details_url(pkg_name: &str) -> ::askama::Result<String> {

View File

@@ -3,23 +3,18 @@
Copyright (c) 2022 - Artix Linux Copyright (c) 2022 - Artix Linux
Copyright (c) 2022 - Oscar Campos <damnwidget@artixlinux.org> Copyright (c) 2022 - Oscar Campos <damnwidget@artixlinux.org>
Copyright (c) 2022 - Nikola Radojević <nikolar@artixlinux.org>
*/ */
use actix_web::{web, HttpResponse}; use actix_web::{web, HttpResponse};
use askama::Template; use askama::Template;
use diesel::{prelude::*, PgConnection}; use diesel::{prelude::*, PgConnection};
use lettre::{ use lettre::{transport::smtp::authentication::Credentials, Message, SmtpTransport, Transport};
transport::smtp::authentication::Credentials, Message, SmtpTransport,
Transport,
};
use serde::Deserialize; use serde::Deserialize;
use crate::utils;
use crate::{ use crate::{
config::{SmtpOptions, SETTINGS}, config::{SmtpOptions, SETTINGS},
lib::templates::EmailTemplate,
models::{Invitation, Pool}, models::{Invitation, Pool},
utils::templates::EmailTemplate,
}; };
#[derive(Deserialize)] #[derive(Deserialize)]
@@ -36,11 +31,7 @@ pub async fn post(
if let Some(token) = req.headers().get("x-admin-token") { if let Some(token) = req.headers().get("x-admin-token") {
if token == &SETTINGS.api_token { if token == &SETTINGS.api_token {
web::block(move || { web::block(move || {
create_invitation( create_invitation(invitation_data.into_inner().email, &pool, &smtp_cfg)
invitation_data.into_inner().email,
&pool,
&smtp_cfg,
)
}) })
.await??; .await??;
Ok(HttpResponse::Ok().finish()) Ok(HttpResponse::Ok().finish())
@@ -56,7 +47,7 @@ fn create_invitation(
eml: String, eml: String,
pool: &web::Data<Pool>, pool: &web::Data<Pool>,
smtp_cfg: &web::Data<SmtpOptions>, smtp_cfg: &web::Data<SmtpOptions>,
) -> Result<(), utils::errors::ServiceError> { ) -> Result<(), crate::lib::errors::ServiceError> {
let invitation = query(eml, pool)?; let invitation = query(eml, pool)?;
if let Err(err) = send_invitation(&invitation, smtp_cfg) { if let Err(err) = send_invitation(&invitation, smtp_cfg) {
dbg!(err); dbg!(err);
@@ -68,7 +59,7 @@ fn create_invitation(
fn send_invitation( fn send_invitation(
invitation: &Invitation, invitation: &Invitation,
smtp_cfg: &web::Data<SmtpOptions>, smtp_cfg: &web::Data<SmtpOptions>,
) -> Result<(), utils::errors::ServiceError> { ) -> Result<(), crate::lib::errors::ServiceError> {
let email = Message::builder() let email = Message::builder()
.from( .from(
"ArtixWeb Packages <artixweb@artixlinux.org>" "ArtixWeb Packages <artixweb@artixlinux.org>"
@@ -80,11 +71,8 @@ fn send_invitation(
.body(EmailTemplate { invitation }.render().unwrap()) .body(EmailTemplate { invitation }.render().unwrap())
.unwrap(); .unwrap();
let smtp_credentials = let smtp_credentials = Credentials::new(smtp_cfg.user.clone(), smtp_cfg.password.clone());
Credentials::new(smtp_cfg.user.clone(), smtp_cfg.password.clone()); let mailer = if let Ok(transport) = SmtpTransport::starttls_relay(&smtp_cfg.relay.clone()) {
let mailer = if let Ok(transport) =
SmtpTransport::starttls_relay(&smtp_cfg.relay.clone())
{
transport.credentials(smtp_credentials).build() transport.credentials(smtp_credentials).build()
} else { } else {
// open a local connection on port 25 // open a local connection on port 25
@@ -96,7 +84,7 @@ fn send_invitation(
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(e) => { Err(e) => {
log::error!("{}", e); log::error!("{}", e);
Err(utils::errors::ServiceError::InternalServerError) Err(crate::lib::errors::ServiceError::InternalServerError)
} }
} }
} }
@@ -105,11 +93,11 @@ fn send_invitation(
fn query( fn query(
eml: String, eml: String,
pool: &web::Data<Pool>, pool: &web::Data<Pool>,
) -> Result<Invitation, utils::errors::ServiceError> { ) -> Result<Invitation, crate::lib::errors::ServiceError> {
use crate::schema::invitations::dsl::invitations; use crate::schema::invitations::dsl::invitations;
let new_invitation: Invitation = eml.into(); let new_invitation: Invitation = eml.into();
let conn: &mut PgConnection = &mut pool.get().unwrap(); let conn: &PgConnection = &pool.get().unwrap();
let inserted_invitation = diesel::insert_into(invitations) let inserted_invitation = diesel::insert_into(invitations)
.values(&new_invitation) .values(&new_invitation)

View File

@@ -3,7 +3,6 @@
Copyright (c) 2022 - Artix Linux Copyright (c) 2022 - Artix Linux
Copyright (c) 2022 - Oscar Campos <damnwidget@artixlinux.org> Copyright (c) 2022 - Oscar Campos <damnwidget@artixlinux.org>
Copyright (c) 2022 - Nikola Radojević <nikolar@artixlinux.org>
*/ */
//! Packages route handlers definitions //! Packages route handlers definitions
@@ -21,24 +20,20 @@ use askama::Template;
use chrono::prelude::NaiveDateTime; use chrono::prelude::NaiveDateTime;
use diesel::prelude::*; use diesel::prelude::*;
use diesel::PgConnection; use diesel::PgConnection;
use lettre::{ use lettre::{transport::smtp::authentication::Credentials, Message, SmtpTransport, Transport};
transport::smtp::authentication::Credentials, Message, SmtpTransport,
Transport,
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::config::SmtpOptions; use crate::config::SmtpOptions;
use crate::models::PackageWithFlagView; use crate::models::PackageWithFlagView;
use crate::models::{Package as PackageMeta, PrivatePackage}; use crate::models::{Package as PackageMeta, PrivatePackage};
use crate::utils;
use crate::{ use crate::{
config::{sync_databases, SETTINGS}, config::{sync_databases, SETTINGS},
models::{PackageFlag, Pool}, lib::{
utils::{
database::packages, database::packages,
errors::{ArtixWebPackageError, ServiceError}, errors::{ArtixWebPackageError, ServiceError},
templates::PackageFlagEmail, templates::PackageFlagEmail,
}, },
models::{PackageFlag, Pool},
}; };
use super::auth::LoggedUser; use super::auth::LoggedUser;
@@ -174,11 +169,7 @@ pub async fn flag_package(
let user_name = logged_user.email.clone(); let user_name = logged_user.email.clone();
let package_doublet = format!("{}-{}", data.0, data.1); let package_doublet = format!("{}-{}", data.0, data.1);
web::block(move || { web::block(move || {
flag_package_query( flag_package_query(&format!("{}-{}", data.0, data.1), &logged_user.email, &pool)
&format!("{}-{}", data.0, data.1),
&logged_user.email,
&pool,
)
}) })
.await??; .await??;
@@ -204,11 +195,8 @@ pub async fn flag_package(
) )
.unwrap(); .unwrap();
let smtp_credentials = let smtp_credentials = Credentials::new(smtp_cfg.user.clone(), smtp_cfg.password.clone());
Credentials::new(smtp_cfg.user.clone(), smtp_cfg.password.clone()); let mailer = if let Ok(transport) = SmtpTransport::starttls_relay(&smtp_cfg.relay.clone()) {
let mailer = if let Ok(transport) =
SmtpTransport::starttls_relay(&smtp_cfg.relay.clone())
{
transport.credentials(smtp_credentials).build() transport.credentials(smtp_credentials).build()
} else { } else {
// open a local connection on port 25 // open a local connection on port 25
@@ -220,7 +208,7 @@ pub async fn flag_package(
Ok(_) => Ok(HttpResponse::Ok().finish()), Ok(_) => Ok(HttpResponse::Ok().finish()),
Err(e) => { Err(e) => {
log::error!("{}", e); log::error!("{}", e);
Err(utils::errors::ServiceError::BadRequest( Err(crate::lib::errors::ServiceError::BadRequest(
format!("could not send email: {:?}", e), format!("could not send email: {:?}", e),
Some(start_time), Some(start_time),
) )
@@ -248,9 +236,7 @@ pub async fn flag_package_delete(
} }
} }
if let Ok(result) = if let Ok(result) = web::block(move || flag_package_delete_query(&package_doublet, &pool)).await
web::block(move || flag_package_delete_query(&package_doublet, &pool))
.await
{ {
match result { match result {
Ok(_) => HttpResponse::Ok().finish(), Ok(_) => HttpResponse::Ok().finish(),
@@ -265,12 +251,11 @@ pub async fn flag_package_delete(
fn flag_package_delete_query( fn flag_package_delete_query(
package_doublet: &str, package_doublet: &str,
pool: &web::Data<Pool>, pool: &web::Data<Pool>,
) -> Result<(), utils::errors::ServiceError> { ) -> Result<(), crate::lib::errors::ServiceError> {
use crate::schema::package_flags::dsl::{package_flags, package_name}; use crate::schema::package_flags::dsl::{package_flags, package_name};
let conn: &mut PgConnection = &mut pool.get().unwrap(); let conn: &PgConnection = &pool.get().unwrap();
diesel::delete(package_flags.filter(package_name.eq(package_doublet))) diesel::delete(package_flags.filter(package_name.eq(package_doublet))).execute(conn)?;
.execute(conn)?;
Ok(()) Ok(())
} }
@@ -279,11 +264,11 @@ fn flag_package_query(
package_doublet: &str, package_doublet: &str,
email: &str, email: &str,
pool: &web::Data<Pool>, pool: &web::Data<Pool>,
) -> Result<(), utils::errors::ServiceError> { ) -> Result<(), crate::lib::errors::ServiceError> {
use crate::schema::package_flags::dsl::package_flags; use crate::schema::package_flags::dsl::package_flags;
let pf = PackageFlag::from_details(email, package_doublet); let pf = PackageFlag::from_details(email, package_doublet);
let conn: &mut PgConnection = &mut pool.get().unwrap(); let conn: &PgConnection = &pool.get().unwrap();
diesel::insert_into(package_flags) diesel::insert_into(package_flags)
.values(&pf) .values(&pf)
.execute(conn)?; .execute(conn)?;
@@ -325,10 +310,7 @@ pub async fn flags_package_ui(
// the user has already flag this package_name (plus version) // the user has already flag this package_name (plus version)
return Ok(HttpResponse::Ok().content_type("text/html").body( return Ok(HttpResponse::Ok().content_type("text/html").body(
PackageAlreadyFlaggedTemplate { PackageAlreadyFlaggedTemplate {
error_message: format!( error_message: format!("You have already flag package {}", package_doublet),
"You have already flag package {}",
package_doublet
),
user_email: logged_user.email, user_email: logged_user.email,
generation_time: start_time.elapsed().as_millis(), generation_time: start_time.elapsed().as_millis(),
} }
@@ -385,12 +367,10 @@ fn get_package_flags_query(
email: &str, email: &str,
pool: &web::Data<Pool>, pool: &web::Data<Pool>,
start_time: std::time::Instant, start_time: std::time::Instant,
) -> Result<bool, utils::errors::ServiceError> { ) -> Result<bool, crate::lib::errors::ServiceError> {
use crate::schema::package_flags::dsl::{ use crate::schema::package_flags::dsl::{package_flags, package_name, user_email};
package_flags, package_name, user_email,
};
let conn: &mut PgConnection = &mut pool.get().unwrap(); let conn: &PgConnection = &pool.get().unwrap();
package_flags package_flags
.filter(package_name.eq_all(pkg_id)) .filter(package_name.eq_all(pkg_id))
.filter(user_email.eq_all(email)) .filter(user_email.eq_all(email))
@@ -418,9 +398,7 @@ fn get_package_flags_query(
pub async fn get_packages<'a>( pub async fn get_packages<'a>(
data: web::Path<(String, usize, usize)>, data: web::Path<(String, usize, usize)>,
) -> Result<HttpResponse, ArtixWebPackageError> { ) -> Result<HttpResponse, ArtixWebPackageError> {
if let Ok(result) = if let Ok(result) = get_packages_inner(&data.0, data.1, data.2, None, None).await {
get_packages_inner(&data.0, data.1, data.2, None, None).await
{
return Ok(HttpResponse::Ok() return Ok(HttpResponse::Ok()
.content_type("application/json; charset=utf-8") .content_type("application/json; charset=utf-8")
.insert_header(("X-Packages-Number", result.0.len())) .insert_header(("X-Packages-Number", result.0.len()))
@@ -535,10 +513,8 @@ pub(crate) async fn get_packages_details_inner(
let pkg_name_version = format!("{}-{}", pkg.name(), pkg.version()); let pkg_name_version = format!("{}-{}", pkg.name(), pkg.version());
let p_pool = pool.clone(); let p_pool = pool.clone();
if let Ok(Ok(metadata)) = web::block(move || { if let Ok(Ok(metadata)) =
get_package_metadata(&pkg_name_version, &pool) web::block(move || get_package_metadata(&pkg_name_version, &pool)).await
})
.await
{ {
result.last_updated = metadata.last_update.timestamp(); result.last_updated = metadata.last_update.timestamp();
result.gitea_url = metadata.gitea_url; result.gitea_url = metadata.gitea_url;
@@ -555,12 +531,9 @@ pub(crate) async fn get_packages_details_inner(
return Ok(result); return Ok(result);
} }
// retrieve a valid organization name for gitea packages
let org_name = get_org_name(&pkg);
// look for this package in gitea // look for this package in gitea
if let Some(gitea_repo) = Repository::retrieve_repository( if let Some(gitea_repo) = Repository::retrieve_repository(
&org_name, &String::from("packages"),
package_name, package_name,
Some(&SETTINGS.gitea_api_url), Some(&SETTINGS.gitea_api_url),
) )
@@ -574,11 +547,7 @@ pub(crate) async fn get_packages_details_inner(
let metadata = PackageMeta { let metadata = PackageMeta {
package_name: format!("{}-{}", pkg.name(), pkg.version()), package_name: format!("{}-{}", pkg.name(), pkg.version()),
gitea_url: result.gitea_url.clone(), gitea_url: result.gitea_url.clone(),
last_update: chrono::NaiveDateTime::from_timestamp_opt( last_update: chrono::NaiveDateTime::from_timestamp(result.last_updated, 0),
result.last_updated,
0,
)
.unwrap(),
}; };
if web::block(move || add_package_metadata(&metadata, &p_pool)) if web::block(move || add_package_metadata(&metadata, &p_pool))
.await .await
@@ -601,10 +570,8 @@ pub(crate) async fn get_packages_details_inner(
fn add_package_metadata(metadata: &PackageMeta, pool: &web::Data<Pool>) { fn add_package_metadata(metadata: &PackageMeta, pool: &web::Data<Pool>) {
use crate::schema::packages::dsl::packages; use crate::schema::packages::dsl::packages;
let conn: &mut PgConnection = &mut pool.get().unwrap(); let conn: &PgConnection = &pool.get().unwrap();
if let Err(err) = if let Err(err) = diesel::insert_into(packages).values(metadata).execute(conn) {
diesel::insert_into(packages).values(metadata).execute(conn)
{
dbg!(err.to_string()); dbg!(err.to_string());
} }
} }
@@ -613,34 +580,31 @@ fn add_package_metadata(metadata: &PackageMeta, pool: &web::Data<Pool>) {
fn get_package_metadata<'a>( fn get_package_metadata<'a>(
pkg_doublet: &'a str, pkg_doublet: &'a str,
pool: &'a web::Data<Pool>, pool: &'a web::Data<Pool>,
) -> Result<PrivatePackage, utils::errors::ServiceError> { ) -> Result<PrivatePackage, crate::lib::errors::ServiceError> {
use crate::schema::{package_flags, packages}; use crate::schema::{package_flags, packages};
let start_time = std::time::Instant::now(); let start_time = std::time::Instant::now();
let conn: &mut PgConnection = &mut pool.get().unwrap(); let conn: &PgConnection = &pool.get().unwrap();
let metadata: Vec<( let metadata: Vec<(PackageMeta, Option<chrono::NaiveDateTime>, Option<String>)> =
PackageMeta, packages::table
Option<chrono::NaiveDateTime>, .find(pkg_doublet)
Option<String>, .left_join(
)> = packages::table package_flags::table.on(package_flags::package_name
.find(pkg_doublet) .eq(packages::package_name)
.left_join( .and(package_flags::flag_on.ge(packages::last_update))),
package_flags::table.on(package_flags::package_name
.eq(packages::package_name)
.and(package_flags::flag_on.ge(packages::last_update))),
)
.select((
packages::all_columns,
package_flags::flag_on.nullable(),
package_flags::user_email.nullable(),
))
.load(conn)
.map_err(|_db_error| {
ServiceError::BadRequest(
format!("Could not find package {}", pkg_doublet),
Some(start_time),
) )
})?; .select((
packages::all_columns,
package_flags::flag_on.nullable(),
package_flags::user_email.nullable(),
))
.load(conn)
.map_err(|_db_error| {
ServiceError::BadRequest(
format!("Could not find package {}", pkg_doublet),
Some(start_time),
)
})?;
if let Some(user_metadata) = metadata.first() { if let Some(user_metadata) = metadata.first() {
let flaggers = metadata.iter().map(|meta| meta.2.clone()).collect(); let flaggers = metadata.iter().map(|meta| meta.2.clone()).collect();
@@ -654,33 +618,6 @@ fn get_package_metadata<'a>(
} }
} }
// retrieves a valid (hopefully) organization name from a package name or base
fn get_org_name(pkg: &Package<'_>) -> String {
let mut pkg_name = pkg.name();
let mut org_name = if let Some(base) = pkg.base() {
pkg_name = base;
format!(
"packages{}",
base.to_uppercase().chars().next().unwrap_or('_')
)
} else {
format!(
"packages{}",
pkg_name.to_uppercase().chars().next().unwrap_or('_')
)
};
if pkg_name.starts_with("python-") {
org_name = String::from("packagesPython");
} else if pkg.name().starts_with("perl-") {
org_name = String::from("packagesPerl");
} else if pkg.name().starts_with("ruby-") {
org_name = String::from("packagesRuby");
}
org_name
}
pub(crate) async fn get_packages_inner( pub(crate) async fn get_packages_inner(
repo_names: &str, repo_names: &str,
limit: usize, limit: usize,
@@ -712,9 +649,7 @@ pub(crate) async fn get_packages_inner(
let pkgs = pkgs.unwrap(); let pkgs = pkgs.unwrap();
let flagged_packages = if let Some(pool) = pool { let flagged_packages = if let Some(pool) = pool {
if let Ok(flagged_packages) = if let Ok(flagged_packages) = get_flagged_packages(start_time, pool).await {
get_flagged_packages(start_time, pool).await
{
flagged_packages flagged_packages
} else { } else {
Vec::new() Vec::new()
@@ -742,11 +677,7 @@ pub(crate) async fn get_packages_inner(
}, },
package_name: pkg.name().to_string(), package_name: pkg.name().to_string(),
version: pkg.version().as_str().to_string(), version: pkg.version().as_str().to_string(),
last_update: NaiveDateTime::from_timestamp_opt( last_update: NaiveDateTime::from_timestamp(pkg.build_date(), 0),
pkg.build_date(),
0,
)
.unwrap(),
description: String::from(pkg.desc().unwrap_or_default()), description: String::from(pkg.desc().unwrap_or_default()),
flag_on, flag_on,
flagged, flagged,
@@ -766,28 +697,24 @@ pub(crate) async fn get_packages_inner(
async fn get_flagged_packages( async fn get_flagged_packages(
start_time: std::time::Instant, start_time: std::time::Instant,
pool: &web::Data<Pool>, pool: &web::Data<Pool>,
) -> Result<Vec<PackageWithFlagView>, utils::errors::ServiceError> { ) -> Result<Vec<PackageWithFlagView>, crate::lib::errors::ServiceError> {
use crate::schema::package_flags::dsl::{ use crate::schema::package_flags::dsl::{
flag_on, package_flags, package_name as package_flag_name, flag_on, package_flags, package_name as package_flag_name,
}; };
use crate::schema::packages::dsl::{last_update, package_name, packages}; use crate::schema::packages::dsl::{last_update, package_name, packages};
let conn: &mut PgConnection = &mut pool.get().unwrap(); let conn: &PgConnection = &pool.get().unwrap();
let packages_data: Vec<(PackageMeta, Option<chrono::NaiveDateTime>)> = let packages_data: Vec<(PackageMeta, Option<chrono::NaiveDateTime>)> = packages
packages .inner_join(
.inner_join( package_flags.on(flag_on
package_flags.on(flag_on .ge(last_update)
.ge(last_update) .and(package_name.eq(package_flag_name))),
.and(package_name.eq(package_flag_name))), )
) .select((packages::all_columns(), flag_on.nullable()))
.select((packages::all_columns(), flag_on.nullable())) .load(conn)
.load(conn) .map_err(|_db_error| {
.map_err(|_db_error| { ServiceError::BadRequest(String::from("Could not find packages"), Some(start_time))
ServiceError::BadRequest( })?;
String::from("Could not find packages"),
Some(start_time),
)
})?;
Ok(packages_data Ok(packages_data
.iter() .iter()
@@ -807,8 +734,7 @@ async fn retrieve_maintainers(gitea_repo: &Repository) -> Vec<String> {
for line in lines { for line in lines {
let lower = line.to_lowercase(); let lower = line.to_lowercase();
if lower.contains("maintainer") || lower.contains("contributor") { if lower.contains("maintainer") || lower.contains("contributor") {
let data: Vec<String> = let data: Vec<String> = line.split(':').map(|m| m.trim().to_string()).collect();
line.split(':').map(|m| m.trim().to_string()).collect();
if data.len() > 1 { if data.len() > 1 {
maintainers.push(data[1].clone()); maintainers.push(data[1].clone());
} }
@@ -826,7 +752,7 @@ async fn retrieve_maintainers(gitea_repo: &Repository) -> Vec<String> {
fn get_depends_from_package<'a>( fn get_depends_from_package<'a>(
alpm: &'a Alpm, alpm: &'a Alpm,
arch: &str, arch: &str,
deps: AlpmList<'_, Dep<'_>>, deps: AlpmList<'_, &Dep>,
kind: &DependencyKind, kind: &DependencyKind,
) -> Vec<Dependency> { ) -> Vec<Dependency> {
match kind { match kind {
@@ -852,7 +778,7 @@ fn get_depends_from_package<'a>(
// constructs a Dependency instance with the given data and returns it back // constructs a Dependency instance with the given data and returns it back
#[allow(clippy::needless_pass_by_value)] #[allow(clippy::needless_pass_by_value)]
fn construct_dependency(arch: &str, alpm: &Alpm, dep: Dep<'_>) -> Dependency { fn construct_dependency(arch: &str, alpm: &Alpm, dep: &Dep) -> Dependency {
let kind = if dep let kind = if dep
.name() .name()
.rsplit('.') .rsplit('.')
@@ -865,33 +791,25 @@ fn construct_dependency(arch: &str, alpm: &Alpm, dep: Dep<'_>) -> Dependency {
DependencyKind::Depend DependencyKind::Depend
}; };
common_dependency_data(arch, alpm, &dep, kind) common_dependency_data(arch, alpm, dep, kind)
} }
// constructs a (make) Dependency instance with the given data and return it back // constructs a (make) Dependency instance with the given data and return it back
#[allow(clippy::needless_pass_by_value)] #[allow(clippy::needless_pass_by_value)]
fn construct_make_dependency( fn construct_make_dependency(arch: &str, alpm: &Alpm, dep: &Dep) -> Dependency {
arch: &str, common_dependency_data(arch, alpm, dep, DependencyKind::Make)
alpm: &Alpm,
dep: Dep<'_>,
) -> Dependency {
common_dependency_data(arch, alpm, &dep, DependencyKind::Make)
} }
// constructs a (opt) Dependency instance with the given data and return it back // constructs a (opt) Dependency instance with the given data and return it back
#[allow(clippy::needless_pass_by_value)] #[allow(clippy::needless_pass_by_value)]
fn construct_opt_dependency( fn construct_opt_dependency(arch: &str, alpm: &Alpm, dep: &Dep) -> Dependency {
arch: &str, common_dependency_data(arch, alpm, dep, DependencyKind::Opt)
alpm: &Alpm,
dep: Dep<'_>,
) -> Dependency {
common_dependency_data(arch, alpm, &dep, DependencyKind::Opt)
} }
fn common_dependency_data<'a>( fn common_dependency_data<'a>(
arch: &str, arch: &str,
alpm: &'a Alpm, alpm: &'a Alpm,
dep: &Dep<'_>, dep: &Dep,
kind: DependencyKind, kind: DependencyKind,
) -> Dependency { ) -> Dependency {
let ver = if let Some(ver) = dep.version() { let ver = if let Some(ver) = dep.version() {

View File

@@ -3,7 +3,6 @@
Copyright (c) 2022 - Artix Linux Copyright (c) 2022 - Artix Linux
Copyright (c) 2022 - Oscar Campos <damnwidget@artixlinux.org> Copyright (c) 2022 - Oscar Campos <damnwidget@artixlinux.org>
Copyright (c) 2022 - Nikola Radojević <nikolar@artixlinux.org>
*/ */
use actix_session::Session; use actix_session::Session;
@@ -14,9 +13,8 @@ use diesel::PgConnection;
use serde::Deserialize; use serde::Deserialize;
use crate::config::{check_password_strength, hash_password}; use crate::config::{check_password_strength, hash_password};
use crate::lib::errors::ServiceError;
use crate::models::{Invitation, Pool, SlimUser, User}; use crate::models::{Invitation, Pool, SlimUser, User};
use crate::utils;
use crate::utils::errors::ServiceError;
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct UserData { pub struct UserData {
@@ -95,20 +93,17 @@ pub async fn invitation(
fn check_invitation( fn check_invitation(
invitation_id: &str, invitation_id: &str,
pool: &web::Data<Pool>, pool: &web::Data<Pool>,
) -> Result<bool, utils::errors::ServiceError> { ) -> Result<bool, crate::lib::errors::ServiceError> {
use crate::schema::invitations::dsl::{id, invitations}; use crate::schema::invitations::dsl::{id, invitations};
let invitation_id = uuid::Uuid::parse_str(invitation_id)?; let invitation_id = uuid::Uuid::parse_str(invitation_id)?;
let conn: &mut PgConnection = &mut pool.get().unwrap(); let conn: &PgConnection = &pool.get().unwrap();
let inv = invitations let inv = invitations
.filter(id.eq(invitation_id)) .filter(id.eq(invitation_id))
.load::<Invitation>(conn) .load::<Invitation>(conn)
.map_err(|_db_error| { .map_err(|_db_error| {
ServiceError::BadRequest( ServiceError::BadRequest("Invalid or expired Invitation".into(), None)
"Invalid or expired Invitation".into(),
None,
)
})?; })?;
Ok(!inv.is_empty()) Ok(!inv.is_empty())
@@ -146,8 +141,7 @@ pub async fn new_user(
} }
// create user from the input data in the form // create user from the input data in the form
web::block(move || query(&invitation_id.into_inner(), &password, &pool)) web::block(move || query(&invitation_id.into_inner(), &password, &pool)).await??;
.await??;
Ok(HttpResponse::SeeOther() Ok(HttpResponse::SeeOther()
.append_header((http::header::LOCATION, "/login")) .append_header((http::header::LOCATION, "/login"))
.finish()) .finish())
@@ -157,13 +151,13 @@ fn query(
invitation_id: &str, invitation_id: &str,
password: &str, password: &str,
pool: &web::Data<Pool>, pool: &web::Data<Pool>,
) -> Result<SlimUser, utils::errors::ServiceError> { ) -> Result<SlimUser, crate::lib::errors::ServiceError> {
use crate::schema::invitations::dsl::{id, invitations}; use crate::schema::invitations::dsl::{id, invitations};
use crate::schema::users::dsl::users; use crate::schema::users::dsl::users;
let start_time = std::time::Instant::now(); let start_time = std::time::Instant::now();
let invitation_id = uuid::Uuid::parse_str(invitation_id)?; let invitation_id = uuid::Uuid::parse_str(invitation_id)?;
let conn: &mut PgConnection = &mut pool.get().unwrap(); let conn: &PgConnection = &pool.get().unwrap();
invitations invitations
.filter(id.eq(invitation_id)) .filter(id.eq(invitation_id))
.load::<Invitation>(conn) .load::<Invitation>(conn)
@@ -181,9 +175,8 @@ fn query(
let password = hash_password(password)?; let password = hash_password(password)?;
let user = User::from_details(invitation.email, password); let user = User::from_details(invitation.email, password);
let inserted_user: User = diesel::insert_into(users) let inserted_user: User =
.values(&user) diesel::insert_into(users).values(&user).get_result(conn)?;
.get_result(conn)?;
return Ok(inserted_user.into()); return Ok(inserted_user.into());
} }

View File

@@ -3,12 +3,11 @@
Copyright (c) 2022 - Artix Linux Copyright (c) 2022 - Artix Linux
Copyright (c) 2022 - Oscar Campos <damnwidget@artixlinux.org> Copyright (c) 2022 - Oscar Campos <damnwidget@artixlinux.org>
Copyright (c) 2022 - Nikola Radojević <nikolar@artixlinux.org>
*/ */
use artix_pkglib::prelude::{get_all_packages, Alpm, PackagesResultData}; use artix_pkglib::prelude::{get_all_packages, Alpm, PackagesResultData};
use crate::utils::errors::ArtixWebPackageError; use crate::lib::errors::ArtixWebPackageError;
/// Retrieve all the packages in all the databases in the system /// Retrieve all the packages in all the databases in the system
/// If `limit` is used then the results are limited to its value /// If `limit` is used then the results are limited to its value

View File

@@ -3,7 +3,6 @@
Copyright (c) 2022 - Artix Linux Copyright (c) 2022 - Artix Linux
Copyright (c) 2022 - Oscar Campos <damnwidget@artixlinux.org> Copyright (c) 2022 - Oscar Campos <damnwidget@artixlinux.org>
Copyright (c) 2022 - Nikola Radojević <nikolar@artixlinux.org>
*/ */
use actix_web::{ use actix_web::{
@@ -71,11 +70,9 @@ struct UnauthorizedTemplate {
impl error::ResponseError for ServiceError { impl error::ResponseError for ServiceError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
match self { match self {
ServiceError::InternalServerError => { ServiceError::InternalServerError => HttpResponse::InternalServerError()
HttpResponse::InternalServerError() .reason("Internal Server Error, Please try later")
.reason("Internal Server Error, Please try later") .body("Internal Server Error, Please try later"),
.body("Internal Server Error, Please try later")
}
ServiceError::BadRequest(ref message, start_time) => { ServiceError::BadRequest(ref message, start_time) => {
let t = if let Some(start_time) = start_time { let t = if let Some(start_time) = start_time {
start_time.elapsed().as_millis() start_time.elapsed().as_millis()
@@ -93,19 +90,15 @@ impl error::ResponseError for ServiceError {
.unwrap(), .unwrap(),
) )
} }
ServiceError::Unauthorized => { ServiceError::Unauthorized => HttpResponse::Unauthorized().reason("Unauthorized").body(
HttpResponse::Unauthorized().reason("Unauthorized").body( UnauthorizedTemplate {
UnauthorizedTemplate { user_email: String::new(),
user_email: String::new(), error_message: String::from("You are not authorized to access this resource"),
error_message: String::from( generation_time: 0,
"You are not authorized to access this resource", }
), .render()
generation_time: 0, .unwrap(),
} ),
.render()
.unwrap(),
)
}
} }
} }
} }
@@ -121,15 +114,9 @@ impl From<DBError> for ServiceError {
match error { match error {
DBError::DatabaseError(kind, info) => { DBError::DatabaseError(kind, info) => {
if let DatabaseErrorKind::UniqueViolation = kind { if let DatabaseErrorKind::UniqueViolation = kind {
let mut message = info let mut message = info.details().unwrap_or_else(|| info.message()).to_string();
.details()
.unwrap_or_else(|| info.message())
.to_string();
if message.contains("Key (email)=") { if message.contains("Key (email)=") {
message = format!( message = format!("Can not register user, already exists: {}", message);
"Can not register user, already exists: {}",
message
);
} }
return ServiceError::BadRequest(message, None); return ServiceError::BadRequest(message, None);
} }

View File

@@ -3,7 +3,6 @@
Copyright (c) 2022 - Artix Linux Copyright (c) 2022 - Artix Linux
Copyright (c) 2022 - Oscar Campos <damnwidget@artixlinux.org> Copyright (c) 2022 - Oscar Campos <damnwidget@artixlinux.org>
Copyright (c) 2022 - Nikola Radojević <nikolar@artixlinux.org>
*/ */
//! `ArtixWeb` Packages Library. //! `ArtixWeb` Packages Library.

View File

@@ -3,7 +3,6 @@
Copyright (c) 2022 - Artix Linux Copyright (c) 2022 - Artix Linux
Copyright (c) 2022 - Oscar Campos <damnwidget@artixlinux.org> Copyright (c) 2022 - Oscar Campos <damnwidget@artixlinux.org>
Copyright (c) 2022 - Nikola Radojević <nikolar@artixlinux.org>
*/ */
use askama::Template; use askama::Template;

View File

@@ -3,7 +3,6 @@
Copyright (c) 2022 - Artix Linux Copyright (c) 2022 - Artix Linux
Copyright (c) 2022 - Oscar Campos <damnwidget@artixlinux.org> Copyright (c) 2022 - Oscar Campos <damnwidget@artixlinux.org>
Copyright (c) 2023 - Nikola Radojević <nikolar@artixlinux.org>
*/ */
//! This crate provides an HTTP API to work with Artix Packages //! This crate provides an HTTP API to work with Artix Packages
@@ -21,10 +20,10 @@ extern crate diesel;
mod config; mod config;
mod handlers; mod handlers;
mod lib;
mod models; mod models;
mod routes; mod routes;
mod schema; mod schema;
mod utils;
use actix_identity::{CookieIdentityPolicy, IdentityService}; use actix_identity::{CookieIdentityPolicy, IdentityService};
use actix_session::{storage::CookieSessionStore, SessionMiddleware}; use actix_session::{storage::CookieSessionStore, SessionMiddleware};
@@ -39,12 +38,6 @@ use diesel::r2d2::ConnectionManager;
use log::{debug, warn}; use log::{debug, warn};
use time::Duration; use time::Duration;
const DEFAULT_BIND_ADDR: &'static str = "127.0.0.1";
const DEFAULT_PORT_STR: &'static str = "1936";
const DEFAULT_PORT: u16 = 1936;
const DEFAULT_DB_URL: &'static str = "localhost";
const DEFAULT_DOMAIN: &'static str = "localhost";
#[actix_web::main] #[actix_web::main]
async fn main() -> std::io::Result<()> { async fn main() -> std::io::Result<()> {
// parse config // parse config
@@ -52,24 +45,20 @@ async fn main() -> std::io::Result<()> {
.arg( .arg(
arg!(-b --bind <BIND_ADDRESS>) arg!(-b --bind <BIND_ADDRESS>)
.required(false) .required(false)
.default_value(DEFAULT_BIND_ADDR) .default_value("127.0.0.1")
.env("BIND_ADDRESS"), .env("BIND_ADDRESS"),
) )
.arg( .arg(arg!(-p --port <PORT>).required(false).default_value("1936"))
arg!(-p --port <PORT>)
.required(false)
.default_value(DEFAULT_PORT_STR),
)
.arg( .arg(
arg!(-u --databaseurl <DATABASE_URL>) arg!(-u --databaseurl <DATABASE_URL>)
.required(false) .required(false)
.default_value(DEFAULT_DB_URL) .default_value("localhost")
.env("DATABASE_URL"), .env("DATABASE_URL"),
) )
.arg( .arg(
arg!(-d --domain <DOMAIN>) arg!(-d --domain <DOMAIN>)
.required(false) .required(false)
.default_value(DEFAULT_DOMAIN), .default_value("localhost"),
) )
.arg( .arg(
arg!(-k --key <SESSION_KEY>) arg!(-k --key <SESSION_KEY>)
@@ -90,41 +79,38 @@ async fn main() -> std::io::Result<()> {
.arg(arg!(-v - -verbose ... )) .arg(arg!(-v - -verbose ... ))
.get_matches(); .get_matches();
// count how many appearances of verbose we got and assign a value // count how many appearances of verbose we got and assign a value for env_logger
// for env_logger let debug_level = match matches.occurrences_of("verbose") {
let debug_level = match matches.get_count("verbose") {
0 => "warn", 0 => "warn",
1 => "info", 1 => "info",
_ => "debug", _ => "debug",
}; };
// configure logger // configure logger
env_logger::init_from_env( env_logger::init_from_env(env_logger::Env::default().default_filter_or(debug_level));
env_logger::Env::default().default_filter_or(debug_level),
);
debug!("environment logger level set to 'debug'"); debug!("environment logger level set to 'debug'");
let host_addr = matches.get_one::<String>("bind").unwrap(); let host_addr = matches.value_of("bind").unwrap_or_default();
let port = matches.get_one::<String>("port").unwrap(); let port = matches.value_of("port").unwrap_or_default();
let host_port = if let Ok(n) = port.parse::<u16>() { let host_port = if let Ok(n) = port.parse::<u16>() {
n n
} else { } else {
warn!("could not parse {} host_port, defaulting to 1936", port); warn!("could not parse {} host_port, defaulting to 1936", port);
DEFAULT_PORT 1936
}; };
let database_url = matches.get_one::<String>("databaseurl").unwrap(); let database_url = matches.value_of("databaseurl").unwrap_or_default();
let domain = matches.get_one::<String>("domain").unwrap(); let domain = matches.value_of("domain").unwrap_or_default();
let master_key = matches.get_one::<String>("key").unwrap(); let master_key = matches.value_of("key").unwrap();
let smtp_config = config::SmtpOptions { let smtp_config = config::SmtpOptions {
user: String::from(matches.get_one::<String>("smtp_user").unwrap()), user: String::from(matches.value_of("smtp_user").unwrap_or_default()),
password: String::from(matches.get_one::<String>("smtp_pwd").unwrap()), password: String::from(matches.value_of("smtp_pwd").unwrap_or_default()),
relay: String::from(matches.get_one::<String>("smtp_relay").unwrap()), relay: String::from(matches.value_of("smtp_relay").unwrap_or_default()),
}; };
// start http server // start http server
debug!("Starting Artix web server on {}:{}", host_addr, host_port); debug!("starting Artix web server on {}:{}", host_addr, host_port);
start_web_server( start_web_server(
host_addr, host_addr,
host_port, host_port,

View File

@@ -3,7 +3,6 @@
Copyright (c) 2022 - Artix Linux Copyright (c) 2022 - Artix Linux
Copyright (c) 2022 - Oscar Campos <damnwidget@artixlinux.org> Copyright (c) 2022 - Oscar Campos <damnwidget@artixlinux.org>
Copyright (c) 2022 - Nikola Radojević <nikolar@artixlinux.org>
*/ */
use diesel::{r2d2::ConnectionManager, PgConnection}; use diesel::{r2d2::ConnectionManager, PgConnection};
@@ -16,7 +15,7 @@ use super::schema::{invitations, package_flags, packages, users};
pub type Pool = r2d2::Pool<ConnectionManager<PgConnection>>; pub type Pool = r2d2::Pool<ConnectionManager<PgConnection>>;
#[derive(Debug, Serialize, Deserialize, Queryable, Insertable)] #[derive(Debug, Serialize, Deserialize, Queryable, Insertable)]
#[diesel(table_name = packages)] #[table_name = "packages"]
pub struct Package { pub struct Package {
pub package_name: String, pub package_name: String,
pub gitea_url: String, pub gitea_url: String,
@@ -24,10 +23,7 @@ pub struct Package {
} }
impl Package { impl Package {
pub fn from_details<S: Into<String>, T: Into<String>>( pub fn from_details<S: Into<String>, T: Into<String>>(name: S, url: T) -> Self {
name: S,
url: T,
) -> Self {
Package { Package {
package_name: name.into(), package_name: name.into(),
gitea_url: url.into(), gitea_url: url.into(),
@@ -83,7 +79,7 @@ impl PackageWithFlagView {
} }
#[derive(Debug, Serialize, Deserialize, Queryable, Insertable)] #[derive(Debug, Serialize, Deserialize, Queryable, Insertable)]
#[diesel(table_name = users)] #[table_name = "users"]
pub struct User { pub struct User {
pub email: String, pub email: String,
pub hash: String, pub hash: String,
@@ -92,10 +88,7 @@ pub struct User {
} }
impl User { impl User {
pub fn from_details<S: Into<String>, T: Into<String>>( pub fn from_details<S: Into<String>, T: Into<String>>(email: S, pwd: T) -> Self {
email: S,
pwd: T,
) -> Self {
User { User {
email: email.into(), email: email.into(),
hash: pwd.into(), hash: pwd.into(),
@@ -106,7 +99,7 @@ impl User {
} }
#[derive(Debug, Serialize, Deserialize, Queryable, Insertable)] #[derive(Debug, Serialize, Deserialize, Queryable, Insertable)]
#[diesel(table_name = invitations)] #[table_name = "invitations"]
pub(crate) struct Invitation { pub(crate) struct Invitation {
pub id: Uuid, pub id: Uuid,
pub email: String, pub email: String,
@@ -122,14 +115,13 @@ where
Invitation { Invitation {
id: uuid::Uuid::new_v4(), id: uuid::Uuid::new_v4(),
email: email.into(), email: email.into(),
expires_at: chrono::Local::now().naive_local() expires_at: chrono::Local::now().naive_local() + chrono::Duration::hours(24),
+ chrono::Duration::hours(24),
} }
} }
} }
#[derive(Debug, Serialize, Deserialize, Queryable, Insertable)] #[derive(Debug, Serialize, Deserialize, Queryable, Insertable)]
#[diesel(table_name = package_flags)] #[table_name = "package_flags"]
pub struct PackageFlag { pub struct PackageFlag {
pub user_email: String, pub user_email: String,
pub package_name: String, pub package_name: String,
@@ -137,10 +129,7 @@ pub struct PackageFlag {
} }
impl PackageFlag { impl PackageFlag {
pub fn from_details<S: Into<String>, T: Into<String>>( pub fn from_details<S: Into<String>, T: Into<String>>(user_email: S, package_name: T) -> Self {
user_email: S,
package_name: T,
) -> Self {
PackageFlag { PackageFlag {
user_email: user_email.into(), user_email: user_email.into(),
package_name: package_name.into(), package_name: package_name.into(),

View File

@@ -3,7 +3,6 @@
Copyright (c) 2022 - Artix Linux Copyright (c) 2022 - Artix Linux
Copyright (c) 2022 - Oscar Campos <damnwidget@artixlinux.org> Copyright (c) 2022 - Oscar Campos <damnwidget@artixlinux.org>
Copyright (c) 2022 - Nikola Radojević <nikolar@artixlinux.org>
*/ */
use actix_files::Files; use actix_files::Files;
@@ -15,10 +14,7 @@ use crate::handlers::{auth, details, index, invitation, packages, register};
pub fn config_app(cfg: &mut web::ServiceConfig) { pub fn config_app(cfg: &mut web::ServiceConfig) {
cfg.service( cfg.service(
web::scope("") web::scope("")
.service( .service(web::resource("/api/invitation").route(web::post().to(invitation::post)))
web::resource("/api/invitation")
.route(web::post().to(invitation::post)),
)
.service( .service(
web::scope("/api/packages") web::scope("/api/packages")
.service( .service(
@@ -26,9 +22,7 @@ pub fn config_app(cfg: &mut web::ServiceConfig) {
.route(web::get().to(packages::get_packages)), .route(web::get().to(packages::get_packages)),
) )
.service(web::scope("/{package_name}").service( .service(web::scope("/{package_name}").service(
web::resource("").route( web::resource("").route(web::get().to(packages::get_package_details)),
web::get().to(packages::get_package_details),
),
)) ))
.default_service( .default_service(
web::route() web::route()
@@ -52,9 +46,7 @@ pub fn config_app(cfg: &mut web::ServiceConfig) {
.route(web::delete().to(auth::logout)) .route(web::delete().to(auth::logout))
.route(web::get().to(auth::ui)), .route(web::get().to(auth::ui)),
) )
.service( .service(web::resource("/logout").route(web::post().to(auth::logout)))
web::resource("/logout").route(web::post().to(auth::logout)),
)
.service( .service(
web::resource("/flag_package/{package_name}/{package_version}") web::resource("/flag_package/{package_name}/{package_version}")
.route(web::post().to(packages::flag_package)) .route(web::post().to(packages::flag_package))

View File

@@ -10,11 +10,14 @@
<div> <div>
<label for="repo_name" title="Repositories to include in the search">Repositories</label> <label for="repo_name" title="Repositories to include in the search">Repositories</label>
<select id="repo_name" name="repo" multiple=""> <select id="repo_name" name="repo" multiple="">
<option value="World" {% if query|selected_repo("World") %} selected {% endif %}>World</option> <option value="system" {% if repos|selected_repo("system") %} selected {% endif %}>System</option>
<option value="Galaxy" {% if query|selected_repo("Galaxy") %} selected {% endif %}>Galaxy</option> <option value="world" {% if repos|selected_repo("world") %} selected {% endif %}>World</option>
<option value="System" {% if query|selected_repo("System") %} selected {% endif %}>System</option> <option value="galaxy" {% if repos|selected_repo("galaxy") %} selected {% endif %}>Galaxy</option>
<option value="Universe" {% if query|selected_repo("Universe") %} selected {% endif %}>Universe</option> <option value="lib32" {% if repos|selected_repo("lib32") %} selected {% endif %}>Lib32</option>
<option value="Lib32" {% if query|selected_repo("Lib32") %} selected {% endif %}>Lib32</option> <option value="system-gremlins" {% if repos|selected_repo("system-gremlins") %} selected {% endif %}>System-Gremlins</option>
<option value="world-gremlins" {% if repos|selected_repo("world-gremlins") %} selected {% endif %}>World-Gremlins</option>
<option value="galaxy-gremlins" {% if repos|selected_repo("galaxy-gremlins") %} selected {% endif %}>Galaxy-Gremlins</option>
<option value="lib32-gremlins" {% if repos|selected_repo("lib32-gremlins") %} selected {% endif %}>Lib32-Gremlins</option>
</select> </select>
</div> </div>
<div> <div>

View File

@@ -35,8 +35,8 @@
<h2>{{ pkg.package_name }}-{{ pkg.version }} {% if pkg.flagged %}(<span class="flagged">flagged</span>){% endif %}</h2> <h2>{{ pkg.package_name }}-{{ pkg.version }} {% if pkg.flagged %}(<span class="flagged">flagged</span>){% endif %}</h2>
<section role="action_panel" class="action_panel"> <section role="action_panel" class="action_panel">
<h4>Actions Panel</h4> <h4>Actions Panel</h4>
<section role="action"><a href="{{ pkg.gitea_url }}" target="blank">View Package Sources</a></section> <section role="action"><a href="{{ pkg.gitea_url }}">View Package Sources</a></section>
<section role="action"><a href="{{ pkg.gitea_url }}/graph" target="blank">View Package Changes</a></section> <section role="action"><a href="{{ pkg.gitea_url }}/graph">View Package Changes</a></section>
{% if !pkg.flagged %} {% if !pkg.flagged %}
<section role="action"><a href="/flag_package/{{ pkg.package_name }}/{{ pkg.version }}">Flag package out-of-date</a></section> <section role="action"><a href="/flag_package/{{ pkg.package_name }}/{{ pkg.version }}">Flag package out-of-date</a></section>
{% else %} {% else %}
@@ -65,7 +65,7 @@
<section role="value">{{ pkg.replaces|provides_or_replaces }}</section> <section role="value">{{ pkg.replaces|provides_or_replaces }}</section>
{% endif %} {% endif %}
<section role="key">Upstream URL:</section> <section role="key">Upstream URL:</section>
<section role="value"><a href="{{ pkg.upstream_url }}" target="_blank">{{ pkg.upstream_url }}</a></section> <section role="value"><a href="{{ pkg.upstream_url }}">{{ pkg.upstream_url }}</a></section>
<section role="key">Size:</section> <section role="key">Size:</section>
<section role="value">{{ pkg.size|human_readable }} MB</section> <section role="value">{{ pkg.size|human_readable }} MB</section>
<section role="key">Installed Size:</section> <section role="key">Installed Size:</section>

View File

@@ -1 +0,0 @@
max_width = 80