Compare commits

..

No commits in common. "devel" and "master" have entirely different histories.

163 changed files with 1896 additions and 2513 deletions

1
.gitignore vendored
View File

@ -1,7 +1,6 @@
/result*
/repl-result*
temp/
.tmp
dist/
/.venv

View File

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 129 KiB

View File

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

View File

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View File

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

View File

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 130 KiB

View File

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 122 KiB

View File

@ -15,7 +15,7 @@
<strong><em>Materia is a simple and fast cloud storage</em></strong>
</p>
**Documentation**: [https://materia.elnafo.ru/docs](https://materia.elnafo.ru/docs)
**Documentation**: [https://storage.elnafo.ru/docs](https://storage.elnafo.ru/docs)
**Source**: [https://vcs.elnafo.ru/L-Nafaryus/materia](https://vcs.elnafo.ru/L-Nafaryus/materia)
@ -148,4 +148,4 @@ pdm build
## License
**materia** is licensed under the terms of the [MIT License](https://vcs.elnafo.ru/L-Nafaryus/materia/src/branch/master/LICENSE).
**materia** is licensed under the terms of the [MIT License](LICENSE).

1
docs/reference/app.md Normal file
View File

@ -0,0 +1 @@
::: materia.app

1
docs/reference/core.md Normal file
View File

@ -0,0 +1 @@
::: materia.core

1
docs/reference/models.md Normal file
View File

@ -0,0 +1 @@
::: materia.models

View File

@ -0,0 +1 @@
::: materia.routers

View File

@ -0,0 +1 @@
::: materia.security

1
docs/reference/tasks.md Normal file
View File

@ -0,0 +1 @@
::: materia.tasks

View File

@ -207,21 +207,6 @@
"type": "github"
}
},
"nix-std": {
"locked": {
"lastModified": 1710870712,
"narHash": "sha256-e+7MJF2gsgTBuOWv4mCimSP0D9+naeFSw9a7N3yEmv4=",
"owner": "chessai",
"repo": "nix-std",
"rev": "31bbc925750cc9d8f828fe55cee1a2bd985e0c00",
"type": "github"
},
"original": {
"owner": "chessai",
"repo": "nix-std",
"type": "github"
}
},
"nixos-mailserver": {
"inputs": {
"blobs": "blobs",
@ -452,7 +437,6 @@
"inputs": {
"bonfire": "bonfire",
"dream2nix": "dream2nix",
"nix-std": "nix-std",
"nixpkgs": "nixpkgs_3"
}
},

547
flake.nix
View File

@ -8,7 +8,6 @@
inputs.nixpkgs.follows = "nixpkgs";
};
bonfire.url = "github:L-Nafaryus/bonfire";
nix-std.url = "github:chessai/nix-std";
};
outputs = {
@ -16,7 +15,6 @@
nixpkgs,
dream2nix,
bonfire,
nix-std,
...
}: let
system = "x86_64-linux";
@ -45,6 +43,45 @@
// {inherit meta;};
in {
packages.x86_64-linux = {
materia-server = dreamBuildPackage {
module = {
config,
lib,
dream2nix,
...
}: {
imports = [dream2nix.modules.dream2nix.WIP-python-pdm];
pdm.lockfile = ./pdm.lock;
pdm.pyproject = ./pyproject.toml;
deps = _: {
python = pkgs.python312;
};
mkDerivation = {
src = ./.;
buildInputs = [
pkgs.python312.pkgs.pdm-backend
];
nativeBuildInputs = [
pkgs.python312.pkgs.wrapPython
];
configurePhase = ''
${lib.getExe pkgs.mkdocs} build -d src/materia/docs/
'';
# TODO: include docs
};
};
meta = with nixpkgs.lib; {
description = "Materia";
license = licenses.mit;
maintainers = with bonLib.maintainers; [L-Nafaryus];
broken = false;
mainProgram = "materia";
};
};
materia-frontend-vue = dreamBuildPackage {
module = {
lib,
@ -60,7 +97,11 @@
];
mkDerivation = {
src = ./packages/frontend;
src = ./workspaces/frontend;
configurePhase = ''
${self.packages.x86_64-linux.materia-server}/bin/materia export openapi --path ./
npm run openapi
'';
};
deps = {nixpkgs, ...}: {
@ -76,8 +117,7 @@
};
};
meta = with nixpkgs.lib; {
description = "Materia is a simple and fast cloud storage (vue)";
homepage = "https://materia.elnafo.ru";
description = "Materia frontend (nodejs)";
license = licenses.mit;
maintainers = with bonLib.maintainers; [L-Nafaryus];
broken = false;
@ -97,128 +137,70 @@
}: {
imports = [dream2nix.modules.dream2nix.WIP-python-pdm];
pdm.lockfile = ./pdm.lock;
pdm.pyproject = ./packages/frontend/pyproject.toml;
pdm.lockfile = ./workspaces/frontend/pdm.lock;
pdm.pyproject = ./workspaces/frontend/pyproject.toml;
deps = _: {
python = pkgs.python312;
};
mkDerivation = {
src = ./packages/frontend;
src = ./workspaces/frontend;
buildInputs = [
pkgs.python312.pkgs.pdm-backend
];
configurePhase = ''
mkdir -p target/materia_frontend
cp -rv ${materia-frontend-vue}/dist/* ./target/materia_frontend/
cp -rv templates ./target/materia_frontend/
touch target/materia_frontend/__init__.py
cp -rv ${materia-frontend-vue}/dist ./src/materia_frontend/
'';
};
};
meta = with nixpkgs.lib; {
description = "Materia is a simple and fast cloud storage (frontend)";
homepage = "https://materia.elnafo.ru";
description = "Materia frontend";
license = licenses.mit;
maintainers = with bonLib.maintainers; [L-Nafaryus];
broken = false;
};
};
materia-server = pkgs.callPackage ({
withFrontend ? true,
withDocs ? true,
...
}:
dreamBuildPackage {
extraArgs = {
inherit (self.packages.x86_64-linux) materia-frontend materia-docs;
};
module = {
config,
lib,
dream2nix,
materia-frontend,
materia-docs,
...
}: {
imports = [dream2nix.modules.dream2nix.WIP-python-pdm];
pdm.lockfile = ./pdm.lock;
pdm.pyproject = ./packages/server/pyproject.toml;
deps = _: {
python = pkgs.python312;
};
mkDerivation = {
src = ./packages/server;
buildInputs = [
pkgs.python312.pkgs.pdm-backend
];
nativeBuildInputs = [
pkgs.python312.pkgs.wrapPython
];
propagatedBuildInputs =
lib.optionals withFrontend [
materia-frontend
]
++ lib.optionals withDocs [materia-docs];
};
};
meta = with nixpkgs.lib; {
description = "Materia is a simple and fast cloud storage";
homepage = "https://materia.elnafo.ru";
license = licenses.mit;
maintainers = with bonLib.maintainers; [L-Nafaryus];
broken = false;
mainProgram = "materia";
};
}) {};
materia-docs = dreamBuildPackage {
materia = dreamBuildPackage {
extraArgs = {
materia-server = self.packages.x86_64-linux.materia-server.override {
withFrontend = false;
withDocs = false;
};
inherit (self.packages.x86_64-linux) materia-frontend;
};
module = {
config,
lib,
dream2nix,
materia-server,
materia-frontend,
...
}: {
imports = [dream2nix.modules.dream2nix.WIP-python-pdm];
pdm.lockfile = ./pdm.lock;
pdm.pyproject = ./packages/docs/pyproject.toml;
pdm.pyproject = ./pyproject.toml;
deps = _: {
python = pkgs.python312;
};
mkDerivation = {
src = ./packages/docs;
src = ./.;
buildInputs = [
pkgs.python312.pkgs.pdm-backend
];
nativeBuildInputs = [pkgs.mkdocs materia-server];
configurePhase = ''
mkdir -p target/materia_docs
mkdocs build
touch target/materia_docs/__init__.py
'';
nativeBuildInputs = [
pkgs.python312.pkgs.wrapPython
];
propagatedBuildInputs = [
materia-frontend
];
};
};
meta = with nixpkgs.lib; {
description = "Materia is a simple and fast cloud storage (docs)";
homepage = "https://materia.elnafo.ru";
description = "Materia";
license = licenses.mit;
maintainers = with bonLib.maintainers; [L-Nafaryus];
broken = false;
mainProgram = "materia";
};
};
@ -242,7 +224,7 @@
pathsToLink = ["/bin" "/etc" "/"];
paths = with pkgs; [
bash
self.packages.x86_64-linux.materia-server
self.packages.x86_64-linux.materia
entryPoint
];
};
@ -275,406 +257,5 @@
# greenlet requires libstdc++
LD_LIBRARY_PATH = nixpkgs.lib.makeLibraryPath [pkgs.stdenv.cc.cc];
};
nixosModules = rec {
materia = {
config,
lib,
pkgs,
...
}:
with lib; let
cfg = config.services.materia;
in {
options.services.materia = {
enable = mkEnableOption "Enables the Materia service";
package = mkOption {
type = types.package;
default = self.packages.x86_64-linux.materia-server;
description = "The package to use.";
};
application = mkOption {
type = types.submodule {
options = {
user = mkOption {
type = types.str;
};
group = mkOption {
type = types.str;
};
mode = mkOption {
type = types.str;
};
working_directory = mkOption {
type = types.path;
default = "/var/lib/materia";
};
};
};
default = {
user = "materia";
group = "materia";
mode = "production";
working_directory = "/var/lib/materia";
};
};
server = mkOption {
type = types.submodule {
options = {
scheme = mkOption {
type = types.str;
};
address = mkOption {
type = types.str;
};
port = mkOption {
type = types.port;
};
domain = mkOption {
type = types.str;
};
};
};
default = {
scheme = "http";
address = "127.0.0.1";
port = 54601;
domain = "localhost";
};
};
database = mkOption {
type = types.submodule {
options = {
backend = mkOption {
type = types.str;
default = "postgresql";
};
scheme = mkOption {
type = types.str;
default = "postgresql+asyncpg";
};
address = mkOption {
type = types.str;
default = "127.0.0.1";
};
port = mkOption {
type = types.port;
default = 5432;
};
name = mkOption {
type = types.nullOr types.str;
default = "materia";
};
user = mkOption {
type = types.str;
default = "materia";
};
password = mkOption {
type = types.nullOr (types.oneOf [types.str types.path]);
default = null;
};
};
};
default = {
backend = "postgresql";
scheme = "postgresql+asyncpg";
address = "127.0.0.1";
port = 5432;
name = "materia";
user = "materia";
password = null;
};
};
cache = mkOption {
type = types.submodule {
options = {
backend = mkOption {
type = types.str;
default = "redis";
};
scheme = mkOption {
type = types.str;
default = "redis";
};
address = mkOption {
type = types.str;
default = "127.0.0.1";
};
port = mkOption {
type = types.port;
default = 6379;
};
database = mkOption {
type = types.nullOr types.int;
default = 0;
};
user = mkOption {
type = types.str;
default = "materia";
};
password = mkOption {
type = types.nullOr (types.oneOf [types.str types.path]);
default = null;
};
};
};
default = {
backed = "redis";
scheme = "redis";
address = "127.0.0.1";
port = 6379;
database = 0;
user = "materia";
password = null;
};
};
security = mkOption {
type = types.submodule {
options = {
secret_key = mkOption {
type = types.nullOr (types.oneOf [types.str types.path]);
};
password_min_length = mkOption {
type = types.int;
};
password_hash_algo = mkOption {
type = types.nullOr types.str;
};
cookie_http_only = mkOption {
type = types.bool;
};
cookie_access_token_name = mkOption {
type = types.str;
};
cookie_refresh_token_name = mkOption {
type = types.str;
};
};
};
default = {
secret_key = null;
password_min_length = 8;
password_hash_algo = "bcrypt";
cookie_http_only = true;
cookie_access_token_name = "materia_at";
cookie_refresh_token_name = "materia_rt";
};
};
oauth2 = mkOption {
type = types.submodule {
options = {
enabled = mkOption {
type = types.bool;
};
jwt_signing_algo = mkOption {
type = types.str;
};
jwt_singing_key = mkOption {
type = types.nullOr (types.oneOf [types.str types.path]);
};
jwt_secret = mkOption {
type = types.nullOr (types.oneOf [types.str types.path]);
};
access_token_lifetime = mkOption {
type = types.int;
};
refresh_token_lifetime = mkOption {
type = types.int;
};
};
};
default = {
enabled = true;
jwt_signing_algo = "HS256";
jwt_singing_key = null;
jwt_secret = "changeme";
access_token_lifetime = 3600;
refresh_token_lifetime = 730 * 60;
};
};
mailer = mkOption {
type = types.submodule {
options = {
enabled = mkOption {
type = types.bool;
default = false;
};
scheme = mkOption {
type = types.nullOr types.str;
default = null;
};
address = mkOption {
type = types.nullOr types.str;
default = null;
};
port = mkOption {
type = types.nullOr types.int;
default = null;
};
helo = mkOption {
type = types.bool;
default = true;
};
cert_file = mkOption {
type = types.nullOr types.path;
default = null;
};
key_file = mkOption {
type = types.nullOr types.path;
default = null;
};
sender = mkOption {
type = types.nullOr types.str;
default = null;
};
user = mkOption {
type = types.nullOr types.str;
default = null;
};
password = mkOption {
type = types.nullOr (types.oneOf [types.str types.path]);
default = null;
};
plain_text = mkOption {
type = types.bool;
default = false;
};
};
};
default = {};
};
cron = mkOption {
type = types.submodule {
options = {
workers_count = mkOption {
type = types.int;
};
};
};
default = {
workers_count = 1;
};
};
repository = mkOption {
type = types.submodule {
options = {
capacity = mkOption {
type = types.int;
};
};
};
default = {capacity = 5368709120;};
};
misc = mkOption {
type = types.submodule {
options = {
enable_client = mkOption {
type = types.bool;
};
enable_docs = mkOption {
type = types.bool;
};
enable_api_docs = mkOption {
type = types.bool;
};
};
};
default = {
enable_client = true;
enable_docs = true;
enable_api_docs = false;
};
};
};
config = mkIf cfg.enable {
users.users.materia = {
description = "Materia service user";
home = cfg.application.working_directory;
createHome = true;
isSystemUser = true;
group = "materia";
};
users.groups.materia = {};
systemd.services.materia = {
description = "Materia service";
wantedBy = ["multi-user.target"];
after = ["network.target"];
serviceConfig = {
Restart = "always";
ExecStart = "${lib.getExe cfg.package} start";
User = "materia";
WorkingDirectory = cfg.application.working_directory;
};
preStart = let
toTOML = nix-std.lib.serde.toTOML;
configFile = pkgs.writeText "config.toml" ''
${toTOML {inherit (cfg) application server database cache security oauth2 mailer cron repository misc;}}
'';
in ''
ln -sf ${configFile} ${cfg.application.working_directory}/config.toml
'';
};
};
};
};
nixosConfigurations.materia = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
self.nixosModules.materia
({
pkgs,
config,
...
}: {
boot.isContainer = true;
networking.hostName = "materia";
networking.useDHCP = false;
services.redis.servers.materia = {
enable = true;
port = 6379;
databases = 1;
};
services.postgresql = {
enable = true;
enableTCPIP = true;
authentication = ''
host materia all 127.0.0.1/32 trust
'';
initialScript = pkgs.writeText "init" ''
CREATE ROLE materia WITH LOGIN PASSWORD 'test';
CREATE DATABASE materia OWNER materia;
'';
ensureDatabases = ["materia"];
};
services.materia = {
enable = true;
cache.port = config.services.redis.servers.materia.port;
database.password = "test";
};
system.stateVersion = "24.05";
})
];
};
};
}

View File

@ -1,6 +1,3 @@
docs_dir: src
site_dir: target/materia_docs
site_name: Materia Documentation
site_description: Materia cloud storage
#site_url:
@ -56,7 +53,7 @@ plugins:
- mkdocstrings:
handlers:
python:
paths: [../server/src] # search packages in the src folder
paths: [src] # search packages in the src folder
options:
extensions:
- griffe_typingdoc
@ -79,6 +76,18 @@ plugins:
filters:
- '!^_'
nav:
- Materia: index.md
- Reference:
- reference/index.md
- reference/app.md
- reference/core.md
- reference/models.md
- reference/routers.md
- reference/security.md
- reference/tasks.md
- API: api.md
markdown_extensions:
# Python Markdown
abbr:
@ -122,20 +131,3 @@ markdown_extensions:
pymdownx.blocks.details:
pymdownx.blocks.tab:
alternate_style: True
nav:
- Usage:
- Introduction: index.md
- usage/getting-started.md
- Reference:
- reference/index.md
- reference/app.md
- reference/core.md
- reference/models.md
- reference/routers.md
- reference/security.md
- reference/tasks.md
- Development:
- devel/contrib.md
- devel/local.md
- API: api.md

View File

@ -1 +0,0 @@
/target

View File

@ -1,40 +0,0 @@
[project]
name = "materia-docs"
version = "0.1.1"
description = "Materia documentation"
authors = [
{name = "L-Nafaryus", email = "l.nafaryus@gmail.com"},
]
dependencies = [
"mkdocs-material>=9.5.38",
"mkdocstrings-python>=1.11.1",
"griffe-typingdoc>=0.2.7",
"pymdown-extensions>=10.11",
"black<24.0.0,>=23.3.0",
]
requires-python = ">=3.12,<3.13"
license = {text = "MIT"}
[project.urls]
Homepage = "https://materia.elnafo.ru"
Repository = "https://github.com/L-Nafaryus/materia"
Documentation = "https://materia.elnafo.ru/docs"
[build-system]
requires = ["pdm-backend"]
build-backend = "pdm.backend"
[tool.pdm]
distribution = true
[tool.pdm.build]
package-dir = "target"
includes = ["target/materia_docs"]
[tool.pdm.scripts]
# mkdocs creates "target/materia_docs"
mkdocs-build.cmd = "mkdocs build"
top-level.shell = "touch target/materia_docs/__init__.py"
pre_build.composite = [ "mkdocs-build", "top-level" ]
doc.cmd = "mkdocs serve"

View File

@ -1,58 +0,0 @@
# Contributing
First off, thanks for taking the time to contribute! Contributions include but are not restricted to:
- Reporting bugs
- Contributing to code
- Writing tests
- Writing documentation
The following is a set of guidelines for contributing.
## A recommended flow of contributing to an Open Source project
This section is for beginners to OSS. If you are an experienced OSS developer, you can skip
this section.
1. First, fork this project to your own namespace using the fork button at the top right of the repository page.
2. Clone the **upstream** repository to local:
```sh
git clone https://github.com/L-Nafaryus/materia.git
# Or if you prefer SSH clone:
git clone git@github.com:L-Nafaryus/materia.git
```
3. Add the fork as a new remote:
```sh
git remote add fork https://github.com/yourname/materia.git
git fetch fork
```
where `fork` is the remote name of the fork repository.
/// tip
1. Don't modify code on the master branch, the master branch should always keep track of origin/master.
To update master branch to date:
```sh
git pull origin master
# In rare cases that your local master branch diverges from the remote master:
git fetch origin && git reset --hard master
```
2. Create a new branch based on the up-to-date master branch for new patches.
3. Create a Pull Request from that patch branch.
///
## Local development
See [Local development](./local.md)
## Code style
*Soon*
## Realease
*Soon*

View File

@ -1,3 +0,0 @@
# Local development
*Soon*

View File

@ -1 +0,0 @@
::: materia_server.app

View File

@ -1 +0,0 @@
::: materia_server.core

View File

@ -1 +0,0 @@
::: materia_server.models

View File

@ -1 +0,0 @@
::: materia_server.routers

View File

@ -1 +0,0 @@
::: materia_server.security

View File

@ -1 +0,0 @@
::: materia_server.tasks

View File

@ -1 +0,0 @@
# Getting started

File diff suppressed because one or more lines are too long

View File

@ -1,35 +0,0 @@
[project]
name = "materia-frontend"
version = "0.1.2"
description = "Materia is a simple and fast cloud storage (frontend)"
authors = [
{name = "L-Nafaryus", email = "l.nafaryus@gmail.com"},
]
requires-python = ">=3.12,<3.13"
license = {text = "MIT"}
[project.urls]
Homepage = "https://materia.elnafo.ru"
Repository = "https://github.com/L-Nafaryus/materia"
Documentation = "https://materia.elnafo.ru/docs"
[build-system]
requires = ["pdm-backend"]
build-backend = "pdm.backend"
[tool.pdm]
distribution = true
[tool.pdm.build]
package-dir = "target"
includes = ["target/materia_frontend"]
[tool.pdm.scripts]
npm-install.cmd = "npm install"
openapi-client.cmd = "npm run openapi"
npm-build.cmd = "npm run simple-build -- --outDir target/materia_frontend"
top-level.shell = "touch target/materia_frontend/__init__.py"
copy-templates.shell = "cp -r ./templates target/materia_frontend/"
pre_build.composite = [ "npm-install", "openapi-client", "npm-build", "top-level", "copy-templates" ]
openapi-json.cmd = "pdm run -p ../server python -m materia_server export openapi --path ./openapi.json"

View File

@ -1,18 +0,0 @@
/result*
/repl-result*
temp/
dist/
/.venv
__pycache__/
*.egg-info
.pdm.toml
.pdm-python
.pdm-build
.pytest_cache
.coverage
/site
src/materia/docs

View File

@ -1,114 +0,0 @@
[project]
name = "materia-server"
version = "0.1.1"
description = "Materia is a simple and fast cloud storage"
authors = [
{name = "L-Nafaryus", email = "l.nafaryus@gmail.com"},
]
dependencies = [
"fastapi<1.0.0,>=0.111.0",
"uvicorn[standard]<1.0.0,>=0.29.0",
"psycopg2-binary<3.0.0,>=2.9.9",
"toml<1.0.0,>=0.10.2",
"sqlalchemy[asyncio]<3.0.0,>=2.0.30",
"asyncpg<1.0.0,>=0.29.0",
"eventlet<1.0.0,>=0.36.1",
"bcrypt==4.1.2",
"pyjwt<3.0.0,>=2.8.0",
"requests<3.0.0,>=2.31.0",
"pillow<11.0.0,>=10.3.0",
"sqids<1.0.0,>=0.4.1",
"alembic<2.0.0,>=1.13.1",
"authlib<2.0.0,>=1.3.0",
"redis[hiredis]<6.0.0,>=5.0.4",
"aiosmtplib<4.0.0,>=3.0.1",
"emails<1.0,>=0.6",
"pydantic-settings<3.0.0,>=2.2.1",
"email-validator<3.0.0,>=2.1.1",
"pydanclick<1.0.0,>=0.2.0",
"loguru<1.0.0,>=0.7.2",
"alembic-postgresql-enum<2.0.0,>=1.2.0",
"gunicorn>=22.0.0",
"uvicorn-worker>=0.2.0",
"httpx>=0.27.0",
"cryptography>=43.0.0",
"python-multipart>=0.0.9",
"jinja2>=3.1.4",
"aiofiles>=24.1.0",
"aioshutil>=1.5",
"Celery>=5.4.0",
"streaming-form-data>=1.16.0",
]
requires-python = ">=3.12,<3.13"
license = {text = "MIT"}
[project.urls]
Homepage = "https://materia.elnafo.ru"
Repository = "https://github.com/L-Nafaryus/materia"
Documentation = "https://materia.elnafo.ru/docs"
[project.optional-dependencies]
docs = [
]
frontend = [
#"materia-frontend>=0.1.1",
]
all = [
#"materia[frontend]",
]
[build-system]
requires = ["pdm-backend"]
build-backend = "pdm.backend"
[project.scripts]
materia = "materia_server.app.cli:cli"
[tool.pyright]
reportGeneralTypeIssues = false
[tool.pytest.ini_options]
pythonpath = ["."]
testpaths = ["tests"]
[tool.pdm]
distribution = true
[tool.pdm.dev-dependencies]
dev = [
"black<24.0.0,>=23.3.0",
"pytest<8.0.0,>=7.3.2",
"pyflakes<4.0.0,>=3.0.1",
"pyright<2.0.0,>=1.1.314",
"pytest-asyncio>=0.23.7",
"asgi-lifespan>=2.1.0",
"pytest-cov>=5.0.0",
]
[tool.pdm.build]
package-dir = "src"
includes = ["src/materia_server"]
[tool.pdm.scripts]
start.cmd = "python ./src/materia/main.py {args:start --app-mode development --log-level debug}"
setup.cmd = "psql -U postgres -h 127.0.0.1 -p 54320 -d postgres -c 'create role materia login;' -c 'create database materia owner materia;'"
teardown.cmd = "psql -U postgres -h 127.0.0.1 -p 54320 -d postgres -c 'drop database materia;' -c 'drop role materia;'"
rev.cmd = "alembic revision {args:--autogenerate}"
upgrade.cmd = "alembic upgrade {args:head}"
downgrade.cmd = "alembic downgrade {args:base}"
remove-revs.shell = "rm -v ./src/materia/models/migrations/versions/*.py"
test.cmd = "pytest"
coverage.cmd = "pytest --cov=src/materia_server"
[tool.pdm.resolution]
respect-source-order = true
[[tool.pdm.source]]
name = "pypi"
url = "https://pypi.org/simple"
[[tool.pdm.source]]
name = "elnafo-vcs"
url = "https://vcs.elnafo.ru/api/packages/L-Nafaryus/pypi"
verify_ssl = true

View File

@ -1,3 +0,0 @@
from materia_server.app import cli
cli()

View File

@ -1,2 +0,0 @@
from materia_server.app.app import Context, Application
from materia_server.app.cli import cli

View File

@ -1,13 +0,0 @@
from materia_server.core.logging import Logger, LoggerInstance, LogLevel, LogMode
from materia_server.core.database import (
DatabaseError,
DatabaseMigrationError,
Database,
SessionMaker,
SessionContext,
ConnectionContext,
)
from materia_server.core.filesystem import FileSystem, FileSystemError, TemporaryFileTarget
from materia_server.core.config import Config
from materia_server.core.cache import Cache, CacheError
from materia_server.core.cron import Cron, CronError

View File

@ -1,3 +0,0 @@
from materia_server.models.auth.source import LoginType, LoginSource
# from materia_server.models.auth.oauth2 import OAuth2Application, OAuth2Grant, OAuth2AuthorizationCode

View File

@ -1 +0,0 @@
from materia_server.routers import middleware, api, client, docs, api_docs

View File

@ -1,40 +0,0 @@
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse
router = APIRouter()
@router.get("/api/docs", response_class=HTMLResponse, include_in_schema=False)
async def rapidoc(request: Request):
return f"""
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<link href='http://fonts.googleapis.com/css?family=Roboto' rel='stylesheet' type='text/css'>
<script
type="module"
src="https://unpkg.com/rapidoc/dist/rapidoc-min.js"
></script>
</head>
<body>
<rapi-doc
spec-url="{request.app.openapi_url}"
theme = "dark"
show-header = "false"
show-info = "true"
allow-authentication = "true"
allow-server-selection = "true"
allow-api-list-style-selection = "true"
theme = "dark"
render-style = "focused"
bg-color="#1e2129"
primary-color="#a47bea"
regular-font="Roboto"
mono-font="Roboto Mono"
show-method-in-nav-bar="as-colored-text">
<img slot="logo" style="display: none"/>
</rapi-doc>
</body>
</html>
"""

View File

@ -1,33 +0,0 @@
from pathlib import Path
from fastapi import APIRouter, Request, Response, status
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
import mimetypes
router = APIRouter()
try:
import materia_frontend
except ModuleNotFoundError:
pass
else:
templates = Jinja2Templates(
directory=Path(materia_frontend.__path__[0], "templates")
)
@router.get("/assets/{filename}")
async def assets(filename: str, include_in_schema=False):
path = Path(materia_frontend.__path__[0]).joinpath("assets", filename)
if not path.exists():
return Response(status_code=status.HTTP_404_NOT_FOUND)
content = path.read_bytes()
mime = mimetypes.guess_type(path)[0]
return Response(content, media_type=mime)
@router.get("/{spa:path}", response_class=HTMLResponse, include_in_schema=False)
async def root(request: Request):
return templates.TemplateResponse(request, "base.html", {"view": "app"})

View File

@ -1,3 +0,0 @@
from materia_server.security.secret_key import generate_key, encrypt_payload
from materia_server.security.token import TokenClaims, generate_token, validate_token
from materia_server.security.password import hash_password, validate_password

View File

@ -1 +0,0 @@
from materia_server.tasks.file import remove_cache_file

1198
pdm.lock

File diff suppressed because it is too large Load Diff

View File

@ -5,14 +5,66 @@ description = "Materia is a simple and fast cloud storage"
authors = [
{name = "L-Nafaryus", email = "l.nafaryus@gmail.com"},
]
dependencies = [
"fastapi<1.0.0,>=0.111.0",
"uvicorn[standard]<1.0.0,>=0.29.0",
"psycopg2-binary<3.0.0,>=2.9.9",
"toml<1.0.0,>=0.10.2",
"sqlalchemy[asyncio]<3.0.0,>=2.0.30",
"asyncpg<1.0.0,>=0.29.0",
"eventlet<1.0.0,>=0.36.1",
"bcrypt==4.1.2",
"pyjwt<3.0.0,>=2.8.0",
"requests<3.0.0,>=2.31.0",
"pillow<11.0.0,>=10.3.0",
"sqids<1.0.0,>=0.4.1",
"alembic<2.0.0,>=1.13.1",
"authlib<2.0.0,>=1.3.0",
"redis[hiredis]<6.0.0,>=5.0.4",
"aiosmtplib<4.0.0,>=3.0.1",
"emails<1.0,>=0.6",
"pydantic-settings<3.0.0,>=2.2.1",
"email-validator<3.0.0,>=2.1.1",
"pydanclick<1.0.0,>=0.2.0",
"loguru<1.0.0,>=0.7.2",
"alembic-postgresql-enum<2.0.0,>=1.2.0",
"gunicorn>=22.0.0",
"uvicorn-worker>=0.2.0",
"httpx>=0.27.0",
"cryptography>=43.0.0",
"python-multipart>=0.0.9",
"jinja2>=3.1.4",
"aiofiles>=24.1.0",
"aioshutil>=1.5",
"Celery>=5.4.0",
"streaming-form-data>=1.16.0",
]
requires-python = ">=3.12,<3.13"
readme = "README.md"
license = {text = "MIT"}
[project.optional-dependencies]
docs = [
"mkdocs-material>=9.5.38",
"mkdocstrings-python>=1.11.1",
"griffe-typingdoc>=0.2.7",
"pymdown-extensions>=10.11",
]
frontend = [
"materia-frontend>=0.1.1",
]
all = [
"materia[docs,frontend]",
]
[build-system]
requires = ["pdm-backend"]
build-backend = "pdm.backend"
[project.scripts]
materia = "materia.app.cli:cli"
[tool.pyright]
reportGeneralTypeIssues = false
@ -21,15 +73,34 @@ pythonpath = ["."]
testpaths = ["tests"]
[tool.pdm]
distribution = false
distribution = true
[tool.pdm.dev-dependencies]
dev = [
"-e file:///${PROJECT_ROOT}/packages/server",
"-e file:///${PROJECT_ROOT}/packages/frontend",
"-e file:///${PROJECT_ROOT}/packages/docs",
"-e file:///${PROJECT_ROOT}/workspaces/frontend",
"black<24.0.0,>=23.3.0",
"pytest<8.0.0,>=7.3.2",
"pyflakes<4.0.0,>=3.0.1",
"pyright<2.0.0,>=1.1.314",
"pytest-asyncio>=0.23.7",
"asgi-lifespan>=2.1.0",
"pytest-cov>=5.0.0",
]
[tool.pdm.build]
includes = ["src/materia"]
[tool.pdm.scripts]
start.cmd = "python ./src/materia/main.py {args:start --app-mode development --log-level debug}"
setup.cmd = "psql -U postgres -h 127.0.0.1 -p 54320 -d postgres -c 'create role materia login;' -c 'create database materia owner materia;'"
teardown.cmd = "psql -U postgres -h 127.0.0.1 -p 54320 -d postgres -c 'drop database materia;' -c 'drop role materia;'"
rev.cmd = "alembic revision {args:--autogenerate}"
upgrade.cmd = "alembic upgrade {args:head}"
downgrade.cmd = "alembic downgrade {args:base}"
remove-revs.shell = "rm -v ./src/materia/models/migrations/versions/*.py"
docs.shell = "pdm run mkdocs build -d src/materia/docs/"
pre_build.composite = [ "docs" ]
[tool.pdm.resolution]
respect-source-order = true

3
src/materia/__main__.py Normal file
View File

@ -0,0 +1,3 @@
from materia.app import cli
cli()

View File

@ -0,0 +1,2 @@
from materia.app.app import Context, Application
from materia.app.cli import cli

View File

@ -9,7 +9,7 @@ from fastapi import FastAPI
from fastapi.routing import APIRoute
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from materia_server.core import (
from materia.core import (
Config,
Logger,
LoggerInstance,
@ -17,8 +17,8 @@ from materia_server.core import (
Cache,
Cron,
)
from materia_server import routers
from materia_server.core.misc import optional, optional_string
from materia import routers
from materia.core.misc import optional, optional_string
class Context(TypedDict):
@ -66,6 +66,13 @@ class Application:
app.logger.error(" ".join(e.args))
sys.exit()
try:
import materia_frontend
except ModuleNotFoundError:
app.logger.warning(
"`materia_frontend` is not installed. No user interface will be served."
)
return app
def prepare_logger(self):
@ -127,25 +134,10 @@ class Application:
allow_methods=["*"],
allow_headers=["*"],
)
self.backend.include_router(routers.docs.router)
self.backend.include_router(routers.api.router)
if self.config.misc.enable_api_docs:
self.backend.include_router(routers.api_docs.router)
if self.config.misc.enable_docs:
try:
import materia_docs
except ModuleNotFoundError:
self.logger.error(
"The `misc.enable_docs` option was enabled but the `materia_docs` package was not found."
)
self.backend.include_router(routers.docs.router)
if self.config.misc.enable_client:
try:
import materia_frontend
except ModuleNotFoundError:
self.logger.error(
"The `misc.enable_client` option was enabled but the `materia_frontend` package was not found."
)
self.backend.include_router(routers.client.router)
self.backend.include_router(routers.resources.router)
self.backend.include_router(routers.root.router)
for route in self.backend.routes:
if isinstance(route, APIRoute):
@ -175,5 +167,5 @@ class Application:
self.logger.info("Exiting...")
sys.exit()
except Exception as e:
self.logger.exception(" ".join(e.args), backtrace=False)
self.logger.error(" ".join(e.args))
sys.exit()

View File

@ -1,8 +1,8 @@
from os import environ
from pathlib import Path
from uvicorn.workers import UvicornWorker
from materia_server.config import Config
from materia_server._logging import uvicorn_log_config
from materia.config import Config
from materia._logging import uvicorn_log_config
class MateriaWorker(UvicornWorker):

View File

@ -1,9 +1,9 @@
from pathlib import Path
import sys
import click
from materia_server.core.config import Config
from materia_server.core.logging import Logger
from materia_server.app import Application
from materia.core.config import Config
from materia.core.logging import Logger
from materia.app import Application
import asyncio
import json
@ -42,6 +42,7 @@ def start(config: Path, debug: bool):
config = Config.open(config_path)
except Exception as e:
logger.error("Failed to read configuration file: {}", e)
else:
logger.info("Using the default configuration.")
config = Config()
else:
@ -133,9 +134,6 @@ def export_openapi(path: Path):
path = path.resolve()
logger = Logger.new()
config = Config()
config.misc.enable_client = False
config.misc.enable_docs = False
config.misc.enable_api_docs = False
app = Application(config)
app.prepare_server()

View File

@ -0,0 +1,13 @@
from materia.core.logging import Logger, LoggerInstance, LogLevel, LogMode
from materia.core.database import (
DatabaseError,
DatabaseMigrationError,
Database,
SessionMaker,
SessionContext,
ConnectionContext,
)
from materia.core.filesystem import FileSystem, FileSystemError, TemporaryFileTarget
from materia.core.config import Config
from materia.core.cache import Cache, CacheError
from materia.core.cron import Cron, CronError

View File

@ -3,7 +3,7 @@ from typing import Any, AsyncGenerator, Self
from pydantic import RedisDsn
from redis import asyncio as aioredis
from redis.asyncio.client import Pipeline
from materia_server.core.logging import Logger
from materia.core.logging import Logger
class CacheError(Exception):

View File

@ -110,7 +110,7 @@ class OAuth2(BaseModel):
# check if signing algo need a key or generate it | HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512, EdDSA
jwt_signing_key: Optional[Union[str, Path]] = None
jwt_secret: Optional[Union[str, Path]] = (
"changeme" # None # only for HS256, HS384, HS512 | generate
"changeme" # None # only for HS256, HS384, HS512 | generate
)
access_token_lifetime: int = 3600
refresh_token_lifetime: int = 730 * 60
@ -136,9 +136,9 @@ class Mailer(BaseModel):
cert_file: Optional[Path] = None
key_file: Optional[Path] = None
sender: Optional[NameEmail] = None
from_: Optional[NameEmail] = None
user: Optional[str] = None
password: Optional[Union[str, Path]] = None
password: Optional[str] = None
plain_text: bool = False
@ -150,12 +150,6 @@ class Repository(BaseModel):
capacity: int = 5 << 30
class Misc(BaseModel):
enable_client: bool = True
enable_docs: bool = True
enable_api_docs: bool = False
class Config(BaseSettings, env_prefix="materia_", env_nested_delimiter="__"):
application: Application = Application()
log: Log = Log()
@ -167,7 +161,6 @@ class Config(BaseSettings, env_prefix="materia_", env_nested_delimiter="__"):
mailer: Mailer = Mailer()
cron: Cron = Cron()
repository: Repository = Repository()
misc: Misc = Misc()
@staticmethod
def open(path: Path) -> Self | None:

View File

@ -2,7 +2,7 @@ from typing import Optional, Self
from celery import Celery
from pydantic import RedisDsn
from threading import Thread
from materia_server.core.logging import Logger
from materia.core.logging import Logger
class CronError(Exception):

View File

@ -18,7 +18,7 @@ from alembic.runtime.migration import MigrationContext
from alembic.script.base import ScriptDirectory
import alembic_postgresql_enum
from fastapi import HTTPException
from materia_server.core.logging import Logger
from materia.core.logging import Logger
class DatabaseError(Exception):
@ -111,7 +111,7 @@ class Database:
await session.close()
def run_sync_migrations(self, connection: Connection):
from materia_server.models.base import Base
from materia.models.base import Base
aconfig = AlembicConfig()
aconfig.set_main_option("sqlalchemy.url", str(self.url))
@ -142,7 +142,7 @@ class Database:
await connection.run_sync(self.run_sync_migrations) # type: ignore
def rollback_sync_migrations(self, connection: Connection):
from materia_server.models.base import Base
from materia.models.base import Base
aconfig = AlembicConfig()
aconfig.set_main_option("sqlalchemy.url", str(self.url))

View File

@ -8,7 +8,7 @@ import re
from tempfile import NamedTemporaryFile
from streaming_form_data.targets import BaseTarget
from uuid import uuid4
from materia_server.core.misc import optional
from materia.core.misc import optional
valid_path = re.compile(r"^/(.*/)*([^/]*)$")

View File

@ -109,8 +109,8 @@ class Logger:
"version": 1,
"disable_existing_loggers": False,
"handlers": {
"default": {"class": "materia_server.core.logging.InterceptHandler"},
"access": {"class": "materia_server.core.logging.InterceptHandler"},
"default": {"class": "materia.core.logging.InterceptHandler"},
"access": {"class": "materia.core.logging.InterceptHandler"},
},
"loggers": {
"uvicorn": {

View File

@ -1,18 +1,18 @@
from materia_server.models.auth import (
from materia.models.auth import (
LoginType,
LoginSource,
# OAuth2Application,
# OAuth2Grant,
# OAuth2AuthorizationCode,
)
from materia_server.models.user import User, UserCredentials, UserInfo
from materia_server.models.repository import (
from materia.models.user import User, UserCredentials, UserInfo
from materia.models.repository import (
Repository,
RepositoryInfo,
RepositoryContent,
RepositoryError,
)
from materia_server.models.directory import (
from materia.models.directory import (
Directory,
DirectoryLink,
DirectoryInfo,
@ -21,7 +21,7 @@ from materia_server.models.directory import (
DirectoryRename,
DirectoryCopyMove,
)
from materia_server.models.file import (
from materia.models.file import (
File,
FileLink,
FileInfo,

View File

@ -0,0 +1,3 @@
from materia.models.auth.source import LoginType, LoginSource
# from materia.models.auth.oauth2 import OAuth2Application, OAuth2Grant, OAuth2AuthorizationCode

View File

@ -8,9 +8,9 @@ from sqlalchemy import BigInteger, ForeignKey, JSON, and_, select
from sqlalchemy.orm import mapped_column, Mapped, relationship
from pydantic import BaseModel, HttpUrl
from materia_server.models.base import Base
from materia_server.core import Database, Cache
from materia_server import security
from materia.models.base import Base
from materia.core import Database, Cache
from materia import security
class OAuth2Application(Base):

View File

@ -4,7 +4,7 @@ from time import time
from sqlalchemy import BigInteger
from sqlalchemy.orm import Mapped, mapped_column
from materia_server.models.base import Base
from materia.models.base import Base
class LoginType(enum.Enum):

View File

@ -7,8 +7,8 @@ from sqlalchemy.orm import mapped_column, Mapped, relationship
import sqlalchemy as sa
from pydantic import BaseModel, ConfigDict
from materia_server.models.base import Base
from materia_server.core import SessionContext, Config, FileSystem
from materia.models.base import Base
from materia.core import SessionContext, Config, FileSystem
class DirectoryError(Exception):
@ -306,5 +306,5 @@ class DirectoryCopyMove(BaseModel):
force: Optional[bool] = False
from materia_server.models.repository import Repository
from materia_server.models.file import File, FileInfo
from materia.models.repository import Repository
from materia.models.file import File, FileInfo

View File

@ -7,8 +7,8 @@ from sqlalchemy.orm import mapped_column, Mapped, relationship
import sqlalchemy as sa
from pydantic import BaseModel, ConfigDict
from materia_server.models.base import Base
from materia_server.core import SessionContext, Config, FileSystem
from materia.models.base import Base
from materia.core import SessionContext, Config, FileSystem
class FileError(Exception):
@ -273,5 +273,5 @@ class FileCopyMove(BaseModel):
force: Optional[bool] = False
from materia_server.models.repository import Repository
from materia_server.models.directory import Directory
from materia.models.repository import Repository
from materia.models.directory import Directory

View File

@ -8,13 +8,13 @@ from sqlalchemy.ext.asyncio import async_engine_from_config
from alembic import context
import alembic_postgresql_enum
from materia_server.core import Config
from materia_server.models.base import Base
import materia_server.models.user
import materia_server.models.auth
import materia_server.models.repository
import materia_server.models.directory
import materia_server.models.file
from materia.core import Config
from materia.models.base import Base
import materia.models.user
import materia.models.auth
import materia.models.repository
import materia.models.directory
import materia.models.file
# this is the Alembic Config object, which provides

View File

@ -8,8 +8,8 @@ from sqlalchemy.orm import mapped_column, Mapped, relationship
import sqlalchemy as sa
from pydantic import BaseModel, ConfigDict
from materia_server.models.base import Base
from materia_server.core import SessionContext, Config
from materia.models.base import Base
from materia.core import SessionContext, Config
class RepositoryError(Exception):
@ -126,6 +126,6 @@ class RepositoryContent(BaseModel):
directories: list["DirectoryInfo"]
from materia_server.models.user import User
from materia_server.models.directory import Directory, DirectoryInfo
from materia_server.models.file import File, FileInfo
from materia.models.user import User
from materia.models.directory import Directory, DirectoryInfo
from materia.models.file import File, FileInfo

View File

@ -11,10 +11,10 @@ from PIL import Image
from sqids.sqids import Sqids
from aiofiles import os as async_os
from materia_server import security
from materia_server.models.base import Base
from materia_server.models.auth.source import LoginType
from materia_server.core import SessionContext, Config, FileSystem
from materia import security
from materia.models.base import Base
from materia.models.auth.source import LoginType
from materia.core import SessionContext, Config, FileSystem
valid_username = re.compile(r"^[\da-zA-Z][-.\w]*$")
invalid_username = re.compile(r"[-._]{2,}|[-._]$")
@ -222,4 +222,4 @@ class UserInfo(BaseModel):
avatar: Optional[str]
from materia_server.models.repository import Repository
from materia.models.repository import Repository

View File

@ -0,0 +1 @@
from materia.routers import middleware, api, resources, root, docs

View File

@ -1,6 +1,6 @@
from fastapi import APIRouter, HTTPException
from materia_server.routers.api.auth import auth, oauth
from materia_server.routers.api import docs, user, repository, directory, file, avatar
from materia.routers.api.auth import auth, oauth
from materia.routers.api import docs, user, repository, directory, file
router = APIRouter(prefix="/api")
router.include_router(docs.router)
@ -10,7 +10,6 @@ router.include_router(user.router)
router.include_router(repository.router)
router.include_router(directory.router)
router.include_router(file.router)
router.include_router(avatar.router)
@router.get("/api/{catchall:path}", status_code=404, include_in_schema=False)

View File

@ -1,9 +1,9 @@
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Response, status
from materia_server import security
from materia_server.routers.middleware import Context
from materia_server.models import LoginType, User, UserCredentials
from materia import security
from materia.routers.middleware import Context
from materia.models import LoginType, User, UserCredentials
router = APIRouter(tags=["auth"])

View File

@ -7,8 +7,8 @@ from fastapi.security.oauth2 import OAuth2PasswordRequestForm
from pydantic import BaseModel, HttpUrl
from starlette.status import HTTP_500_INTERNAL_SERVER_ERROR
from materia_server.models import User
from materia_server.routers.middleware import Context
from materia.models import User
from materia.routers.middleware import Context
router = APIRouter(tags = ["oauth2"])

View File

@ -1,6 +1,6 @@
from pathlib import Path
from fastapi import APIRouter, Depends, HTTPException, status
from materia_server.models import (
from materia.models import (
User,
Directory,
DirectoryInfo,
@ -10,8 +10,8 @@ from materia_server.models import (
DirectoryCopyMove,
Repository,
)
from materia_server.core import SessionContext, Config, FileSystem
from materia_server.routers import middleware
from materia.core import SessionContext, Config, FileSystem
from materia.routers import middleware
router = APIRouter(tags=["directory"])

View File

@ -11,7 +11,7 @@ from fastapi import (
Form,
)
from fastapi.responses import JSONResponse
from materia_server.models import (
from materia.models import (
User,
File,
FileInfo,
@ -20,20 +20,20 @@ from materia_server.models import (
FileRename,
FileCopyMove,
)
from materia_server.core import (
from materia.core import (
SessionContext,
Config,
FileSystem,
TemporaryFileTarget,
Database,
)
from materia_server.routers import middleware
from materia_server.routers.api.directory import validate_target_directory
from materia.routers import middleware
from materia.routers.api.directory import validate_target_directory
from streaming_form_data import StreamingFormDataParser
from streaming_form_data.targets import ValueTarget
from starlette.requests import ClientDisconnect
from aiofiles import ospath as async_path
from materia_server.tasks import remove_cache_file
from materia.tasks import remove_cache_file
router = APIRouter(tags=["file"])

View File

@ -1,5 +1,5 @@
from fastapi import APIRouter, Depends, HTTPException, status
from materia_server.models import (
from materia.models import (
User,
Repository,
RepositoryInfo,
@ -7,7 +7,7 @@ from materia_server.models import (
FileInfo,
DirectoryInfo,
)
from materia_server.routers import middleware
from materia.routers import middleware
router = APIRouter(tags=["repository"])

View File

@ -1,8 +1,8 @@
import uuid
import io
from fastapi import APIRouter, Depends, HTTPException, status, UploadFile
from materia_server.models import User, UserInfo
from materia_server.routers import middleware
from materia.models import User, UserInfo
from materia.routers import middleware
router = APIRouter(tags=["user"])

View File

@ -1,23 +1,22 @@
from fastapi import APIRouter, Request, status, HTTPException, Depends
from fastapi.responses import FileResponse, RedirectResponse
from fastapi import APIRouter, Request, Response, status, HTTPException, Depends
from fastapi.responses import HTMLResponse, FileResponse
from fastapi.templating import Jinja2Templates
from fastapi.staticfiles import StaticFiles
import mimetypes
from pathlib import Path
from materia_server.core.misc import optional
from materia_server.routers import middleware
from materia.core.misc import optional
from materia.routers import middleware
router = APIRouter()
try:
import materia_docs
except ModuleNotFoundError:
from materia import docs as materia_docs
except ImportError:
pass
else:
@router.get("/docs", include_in_schema=False)
async def docs_root():
return RedirectResponse(url="/docs/")
@router.get("/docs/{catchall:path}", include_in_schema=False)
async def docs(request: Request, ctx: middleware.Context = Depends()):
docs_directory = Path(materia_docs.__path__[0]).resolve()

View File

@ -18,8 +18,8 @@ from fastapi.security import (
APIKeyHeader,
)
from materia_server import security
from materia_server.models import User, Repository
from materia import security
from materia.models import User, Repository
class Context:

View File

@ -1,14 +1,16 @@
from fastapi import APIRouter, Depends, HTTPException, status, Response
from PIL import Image
import io
from pathlib import Path
import mimetypes
from materia_server.routers import middleware
from materia_server.core import Config
from materia.routers import middleware
from materia.core import Config
router = APIRouter(tags=["avatar"], prefix="/avatar")
router = APIRouter(tags=["resources"], prefix="/resources")
@router.get("/{avatar_id}")
@router.get("/avatars/{avatar_id}")
async def avatar(
avatar_id: str, format: str = "png", ctx: middleware.Context = Depends()
):
@ -35,3 +37,24 @@ async def avatar(
)
return Response(content=buffer.getvalue(), media_type=Image.MIME[format])
try:
import materia_frontend
except ModuleNotFoundError:
pass
else:
@router.get("/assets/{filename}")
async def assets(filename: str):
path = Path(materia_frontend.__path__[0]).joinpath(
"dist", "resources", "assets", filename
)
if not path.exists():
return Response(status_code=status.HTTP_404_NOT_FOUND)
content = path.read_bytes()
mime = mimetypes.guess_type(path)[0]
return Response(content, media_type=mime)

View File

@ -0,0 +1,19 @@
from pathlib import Path
from fastapi import APIRouter, Request, HTTPException
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
router = APIRouter(tags=["root"])
try:
import materia_frontend
except ModuleNotFoundError:
pass
else:
templates = Jinja2Templates(directory=Path(materia_frontend.__path__[0]) / "dist")
@router.get("/{spa:path}", response_class=HTMLResponse, include_in_schema=False)
async def root(request: Request):
# raise HTTPException(404)
return templates.TemplateResponse(request, "base.html", {"view": "app"})

View File

@ -0,0 +1,3 @@
from materia.security.secret_key import generate_key, encrypt_payload
from materia.security.token import TokenClaims, generate_token, validate_token
from materia.security.password import hash_password, validate_password

View File

@ -0,0 +1 @@
from materia.tasks.file import remove_cache_file

View File

@ -1,10 +1,10 @@
from materia_server.core import Cron, CronError, SessionContext, Config, Database
from materia.core import Cron, CronError, SessionContext, Config, Database
from celery import shared_task
from fastapi import UploadFile
from materia_server.models import File
from materia.models import File
import asyncio
from pathlib import Path
from materia_server.core import FileSystem, Config
from materia.core import FileSystem, Config
@shared_task(name="remove_cache_file")

0
tests/__init__.py Normal file
View File

View File

@ -1,12 +1,12 @@
import pytest_asyncio
from materia_server.models import (
from materia.models import (
User,
LoginType,
)
from materia_server.models.base import Base
from materia_server import security
from materia_server.app import Application
from materia_server.core import Config, Database, Cache, Cron
from materia.models.base import Base
from materia import security
from materia.app import Application
from materia.core import Config, Database, Cache, Cron
import sqlalchemy as sa
from sqlalchemy.pool import NullPool
from httpx import AsyncClient, ASGITransport, Cookies

View File

@ -1,5 +1,5 @@
import pytest
from materia_server.core import Config
from materia.core import Config
from httpx import AsyncClient, Cookies
from io import BytesIO

View File

@ -1,15 +1,15 @@
import pytest_asyncio
import pytest
from pathlib import Path
from materia_server.models import (
from materia.models import (
User,
Repository,
Directory,
RepositoryError,
File,
)
from materia_server.core import Config, SessionContext
from materia_server import security
from materia.core import Config, SessionContext
from materia import security
import sqlalchemy as sa
from sqlalchemy.orm.session import make_transient
from sqlalchemy import inspect

View File

@ -11,6 +11,5 @@ node_modules/
*.mjs
*.log
#openapi.json
/target
openapi.json
src/client

Some files were not shown because too many files have changed in this diff Show More