Inception
This commit is contained in:
commit
a58a21f1b9
19 changed files with 12005 additions and 0 deletions
430
.gitignore
vendored
Normal file
430
.gitignore
vendored
Normal file
|
@ -0,0 +1,430 @@
|
||||||
|
|
||||||
|
# Created by https://www.gitignore.io/api/vim,node,rust,react,linux,macos,emacs,windows,eclipse,intellij+all,visualstudiocode
|
||||||
|
# Edit at https://www.gitignore.io/?templates=vim,node,rust,react,linux,macos,emacs,windows,eclipse,intellij+all,visualstudiocode
|
||||||
|
|
||||||
|
### Eclipse ###
|
||||||
|
.metadata
|
||||||
|
bin/
|
||||||
|
tmp/
|
||||||
|
*.tmp
|
||||||
|
*.bak
|
||||||
|
*.swp
|
||||||
|
*~.nib
|
||||||
|
local.properties
|
||||||
|
.settings/
|
||||||
|
.loadpath
|
||||||
|
.recommenders
|
||||||
|
|
||||||
|
# External tool builders
|
||||||
|
.externalToolBuilders/
|
||||||
|
|
||||||
|
# Locally stored "Eclipse launch configurations"
|
||||||
|
*.launch
|
||||||
|
|
||||||
|
# PyDev specific (Python IDE for Eclipse)
|
||||||
|
*.pydevproject
|
||||||
|
|
||||||
|
# CDT-specific (C/C++ Development Tooling)
|
||||||
|
.cproject
|
||||||
|
|
||||||
|
# CDT- autotools
|
||||||
|
.autotools
|
||||||
|
|
||||||
|
# Java annotation processor (APT)
|
||||||
|
.factorypath
|
||||||
|
|
||||||
|
# PDT-specific (PHP Development Tools)
|
||||||
|
.buildpath
|
||||||
|
|
||||||
|
# sbteclipse plugin
|
||||||
|
.target
|
||||||
|
|
||||||
|
# Tern plugin
|
||||||
|
.tern-project
|
||||||
|
|
||||||
|
# TeXlipse plugin
|
||||||
|
.texlipse
|
||||||
|
|
||||||
|
# STS (Spring Tool Suite)
|
||||||
|
.springBeans
|
||||||
|
|
||||||
|
# Code Recommenders
|
||||||
|
.recommenders/
|
||||||
|
|
||||||
|
# Annotation Processing
|
||||||
|
.apt_generated/
|
||||||
|
|
||||||
|
# Scala IDE specific (Scala & Java development for Eclipse)
|
||||||
|
.cache-main
|
||||||
|
.scala_dependencies
|
||||||
|
.worksheet
|
||||||
|
|
||||||
|
### Eclipse Patch ###
|
||||||
|
# Eclipse Core
|
||||||
|
.project
|
||||||
|
|
||||||
|
# JDT-specific (Eclipse Java Development Tools)
|
||||||
|
.classpath
|
||||||
|
|
||||||
|
# Annotation Processing
|
||||||
|
.apt_generated
|
||||||
|
|
||||||
|
.sts4-cache/
|
||||||
|
|
||||||
|
### Emacs ###
|
||||||
|
# -*- mode: gitignore; -*-
|
||||||
|
*~
|
||||||
|
\#*\#
|
||||||
|
/.emacs.desktop
|
||||||
|
/.emacs.desktop.lock
|
||||||
|
*.elc
|
||||||
|
auto-save-list
|
||||||
|
tramp
|
||||||
|
.\#*
|
||||||
|
|
||||||
|
# Org-mode
|
||||||
|
.org-id-locations
|
||||||
|
*_archive
|
||||||
|
|
||||||
|
# flymake-mode
|
||||||
|
*_flymake.*
|
||||||
|
|
||||||
|
# eshell files
|
||||||
|
/eshell/history
|
||||||
|
/eshell/lastdir
|
||||||
|
|
||||||
|
# elpa packages
|
||||||
|
/elpa/
|
||||||
|
|
||||||
|
# reftex files
|
||||||
|
*.rel
|
||||||
|
|
||||||
|
# AUCTeX auto folder
|
||||||
|
/auto/
|
||||||
|
|
||||||
|
# cask packages
|
||||||
|
.cask/
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# Flycheck
|
||||||
|
flycheck_*.el
|
||||||
|
|
||||||
|
# server auth directory
|
||||||
|
/server/
|
||||||
|
|
||||||
|
# projectiles files
|
||||||
|
.projectile
|
||||||
|
|
||||||
|
# directory configuration
|
||||||
|
.dir-locals.el
|
||||||
|
|
||||||
|
# network security
|
||||||
|
/network-security.data
|
||||||
|
|
||||||
|
|
||||||
|
### Intellij+all ###
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
|
||||||
|
# Gradle and Maven with auto-import
|
||||||
|
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||||
|
# since they will be recreated, and may cause churn. Uncomment if using
|
||||||
|
# auto-import.
|
||||||
|
# .idea/modules.xml
|
||||||
|
# .idea/*.iml
|
||||||
|
# .idea/modules
|
||||||
|
# *.iml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# Mongo Explorer plugin
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
|
||||||
|
# File-based project format
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Cursive Clojure plugin
|
||||||
|
.idea/replstate.xml
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
# Editor-based Rest Client
|
||||||
|
.idea/httpRequests
|
||||||
|
|
||||||
|
# Android studio 3.1+ serialized cache file
|
||||||
|
.idea/caches/build_file_checksums.ser
|
||||||
|
|
||||||
|
### Intellij+all Patch ###
|
||||||
|
# Ignores the whole .idea folder and all .iml files
|
||||||
|
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
|
||||||
|
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
|
||||||
|
|
||||||
|
*.iml
|
||||||
|
modules.xml
|
||||||
|
.idea/misc.xml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
# Sonarlint plugin
|
||||||
|
.idea/sonarlint
|
||||||
|
|
||||||
|
### Linux ###
|
||||||
|
|
||||||
|
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||||
|
.fuse_hidden*
|
||||||
|
|
||||||
|
# KDE directory preferences
|
||||||
|
.directory
|
||||||
|
|
||||||
|
# Linux trash folder which might appear on any partition or disk
|
||||||
|
.Trash-*
|
||||||
|
|
||||||
|
# .nfs files are created when an open file is removed but is still being accessed
|
||||||
|
.nfs*
|
||||||
|
|
||||||
|
### macOS ###
|
||||||
|
# General
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
|
||||||
|
# Icon must end with two \r
|
||||||
|
Icon
|
||||||
|
|
||||||
|
# Thumbnails
|
||||||
|
._*
|
||||||
|
|
||||||
|
# Files that might appear in the root of a volume
|
||||||
|
.DocumentRevisions-V100
|
||||||
|
.fseventsd
|
||||||
|
.Spotlight-V100
|
||||||
|
.TemporaryItems
|
||||||
|
.Trashes
|
||||||
|
.VolumeIcon.icns
|
||||||
|
.com.apple.timemachine.donotpresent
|
||||||
|
|
||||||
|
# Directories potentially created on remote AFP share
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
.apdisk
|
||||||
|
|
||||||
|
### Node ###
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# TypeScript v1 declaration files
|
||||||
|
typings/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
.env.test
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# next.js build output
|
||||||
|
.next
|
||||||
|
|
||||||
|
# nuxt.js build output
|
||||||
|
.nuxt
|
||||||
|
|
||||||
|
# react / gatsby
|
||||||
|
public/
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
### react ###
|
||||||
|
.DS_*
|
||||||
|
**/*.backup.*
|
||||||
|
**/*.back.*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
bower_componets
|
||||||
|
|
||||||
|
*.sublime*
|
||||||
|
|
||||||
|
psd
|
||||||
|
thumb
|
||||||
|
sketch
|
||||||
|
|
||||||
|
build/
|
||||||
|
|
||||||
|
### Rust ###
|
||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||||
|
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||||
|
Cargo.lock
|
||||||
|
|
||||||
|
# These are backup files generated by rustfmt
|
||||||
|
**/*.rs.bk
|
||||||
|
|
||||||
|
### Vim ###
|
||||||
|
# Swap
|
||||||
|
[._]*.s[a-v][a-z]
|
||||||
|
[._]*.sw[a-p]
|
||||||
|
[._]s[a-rt-v][a-z]
|
||||||
|
[._]ss[a-gi-z]
|
||||||
|
[._]sw[a-p]
|
||||||
|
|
||||||
|
# Session
|
||||||
|
Session.vim
|
||||||
|
Sessionx.vim
|
||||||
|
|
||||||
|
# Temporary
|
||||||
|
.netrwhist
|
||||||
|
# Auto-generated tag files
|
||||||
|
tags
|
||||||
|
# Persistent undo
|
||||||
|
[._]*.un~
|
||||||
|
|
||||||
|
### VisualStudioCode ###
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
|
||||||
|
### VisualStudioCode Patch ###
|
||||||
|
# Ignore all local history of files
|
||||||
|
.history
|
||||||
|
|
||||||
|
### Windows ###
|
||||||
|
# Windows thumbnail cache files
|
||||||
|
Thumbs.db
|
||||||
|
Thumbs.db:encryptable
|
||||||
|
ehthumbs.db
|
||||||
|
ehthumbs_vista.db
|
||||||
|
|
||||||
|
# Dump file
|
||||||
|
*.stackdump
|
||||||
|
|
||||||
|
# Folder config file
|
||||||
|
[Dd]esktop.ini
|
||||||
|
|
||||||
|
# Recycle Bin used on file shares
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
# Windows Installer files
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# Windows shortcuts
|
||||||
|
*.lnk
|
||||||
|
|
||||||
|
# End of https://www.gitignore.io/api/vim,node,rust,react,linux,macos,emacs,windows,eclipse,intellij+all,visualstudiocode
|
37
mailhug-ui/package.json
Normal file
37
mailhug-ui/package.json
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
{
|
||||||
|
"name": "mailhug-ui",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@testing-library/jest-dom": "^4.2.4",
|
||||||
|
"@testing-library/react": "^9.3.2",
|
||||||
|
"@testing-library/user-event": "^7.1.2",
|
||||||
|
"dotenv": "^8.2.0",
|
||||||
|
"grommet": "^2.8.1",
|
||||||
|
"react": "^16.12.0",
|
||||||
|
"react-dom": "^16.12.0",
|
||||||
|
"react-scripts": "3.3.0",
|
||||||
|
"styled-components": "^4.4.1"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"build": "react-scripts build",
|
||||||
|
"test": "react-scripts test",
|
||||||
|
"eject": "react-scripts eject"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": "react-app"
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
43
mailhug-ui/src/App.css
Normal file
43
mailhug-ui/src/App.css
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
.App {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.App-logo {
|
||||||
|
height: 40vmin;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
.App-logo {
|
||||||
|
animation: App-logo-spin infinite 20s linear;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.App-header {
|
||||||
|
background-color: #282c34;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: calc(10px + 2vmin);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.App-link {
|
||||||
|
color: #61dafb;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes App-logo-spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
border: none;
|
||||||
|
width: 100%;
|
||||||
|
}
|
59
mailhug-ui/src/App.js
Normal file
59
mailhug-ui/src/App.js
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import { Box, Grommet, Table, TableHeader, TableRow, TableCell, TableBody, Collapsible } from 'grommet';
|
||||||
|
import { grommet } from "grommet/themes";
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import './App.css';
|
||||||
|
|
||||||
|
const host = (url) => url;
|
||||||
|
|
||||||
|
const MailhugTableHeader = () => {return(
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell scope="col" border="bottom"> Subject </TableCell>
|
||||||
|
<TableCell scope="col" border="bottom"> Recipient </TableCell>
|
||||||
|
<TableCell scope="col" border="bottom"> Date </TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
);};
|
||||||
|
|
||||||
|
class App extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {mailtable: [], mailurl: "/mail/99"};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
fetch("/mails")
|
||||||
|
.then(mt => mt.json())
|
||||||
|
.then(data => this.setState({mailtable: data}));
|
||||||
|
}
|
||||||
|
|
||||||
|
resizeIframe(obj){
|
||||||
|
obj.style.height = 0;
|
||||||
|
obj.style.height = obj.contentWindow.document.body.scrollHeight + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Grommet theme={grommet} height={{min: "100vh"}} fill>
|
||||||
|
<Box direction="row" pad="small" height={{ max: "small"}} overflow="auto" border fill>
|
||||||
|
<Table>
|
||||||
|
<MailhugTableHeader/>
|
||||||
|
<TableBody>
|
||||||
|
{this.state.mailtable.map((mail) => {
|
||||||
|
return (<TableRow onClick={() => this.setState({mailurl: host(`mail/${mail.id}`)})} hoverIndicator="background">
|
||||||
|
<TableCell scope="row"> <strong>{mail.subject}</strong> </TableCell>
|
||||||
|
<TableCell>{mail.from}</TableCell>
|
||||||
|
</TableRow>);
|
||||||
|
})}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
<Box direction="row" pad="medium" height={{min: "large"}} fill>
|
||||||
|
<iframe title="mail" src={this.state.mailurl} />
|
||||||
|
</Box>
|
||||||
|
</Grommet>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
9
mailhug-ui/src/App.test.js
Normal file
9
mailhug-ui/src/App.test.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { render } from '@testing-library/react';
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
test('renders learn react link', () => {
|
||||||
|
const { getByText } = render(<App />);
|
||||||
|
const linkElement = getByText(/learn react/i);
|
||||||
|
expect(linkElement).toBeInTheDocument();
|
||||||
|
});
|
13
mailhug-ui/src/index.css
Normal file
13
mailhug-ui/src/index.css
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||||
|
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||||
|
sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||||
|
monospace;
|
||||||
|
}
|
12
mailhug-ui/src/index.js
Normal file
12
mailhug-ui/src/index.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import './index.css';
|
||||||
|
import App from './App';
|
||||||
|
import * as serviceWorker from './serviceWorker';
|
||||||
|
|
||||||
|
ReactDOM.render(<App />, document.getElementById('root'));
|
||||||
|
|
||||||
|
// If you want your app to work offline and load faster, you can change
|
||||||
|
// unregister() to register() below. Note this comes with some pitfalls.
|
||||||
|
// Learn more about service workers: https://bit.ly/CRA-PWA
|
||||||
|
serviceWorker.unregister();
|
7
mailhug-ui/src/logo.svg
Normal file
7
mailhug-ui/src/logo.svg
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
|
||||||
|
<g fill="#61DAFB">
|
||||||
|
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
|
||||||
|
<circle cx="420.9" cy="296.5" r="45.7"/>
|
||||||
|
<path d="M520.5 78.1z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
137
mailhug-ui/src/serviceWorker.js
Normal file
137
mailhug-ui/src/serviceWorker.js
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
// This optional code is used to register a service worker.
|
||||||
|
// register() is not called by default.
|
||||||
|
|
||||||
|
// This lets the app load faster on subsequent visits in production, and gives
|
||||||
|
// it offline capabilities. However, it also means that developers (and users)
|
||||||
|
// will only see deployed updates on subsequent visits to a page, after all the
|
||||||
|
// existing tabs open on the page have been closed, since previously cached
|
||||||
|
// resources are updated in the background.
|
||||||
|
|
||||||
|
// To learn more about the benefits of this model and instructions on how to
|
||||||
|
// opt-in, read https://bit.ly/CRA-PWA
|
||||||
|
|
||||||
|
const isLocalhost = Boolean(
|
||||||
|
window.location.hostname === 'localhost' ||
|
||||||
|
// [::1] is the IPv6 localhost address.
|
||||||
|
window.location.hostname === '[::1]' ||
|
||||||
|
// 127.0.0.0/8 are considered localhost for IPv4.
|
||||||
|
window.location.hostname.match(
|
||||||
|
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
export function register(config) {
|
||||||
|
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||||
|
// The URL constructor is available in all browsers that support SW.
|
||||||
|
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
|
||||||
|
if (publicUrl.origin !== window.location.origin) {
|
||||||
|
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||||
|
// from what our page is served on. This might happen if a CDN is used to
|
||||||
|
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||||
|
|
||||||
|
if (isLocalhost) {
|
||||||
|
// This is running on localhost. Let's check if a service worker still exists or not.
|
||||||
|
checkValidServiceWorker(swUrl, config);
|
||||||
|
|
||||||
|
// Add some additional logging to localhost, pointing developers to the
|
||||||
|
// service worker/PWA documentation.
|
||||||
|
navigator.serviceWorker.ready.then(() => {
|
||||||
|
console.log(
|
||||||
|
'This web app is being served cache-first by a service ' +
|
||||||
|
'worker. To learn more, visit https://bit.ly/CRA-PWA'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Is not localhost. Just register service worker
|
||||||
|
registerValidSW(swUrl, config);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerValidSW(swUrl, config) {
|
||||||
|
navigator.serviceWorker
|
||||||
|
.register(swUrl)
|
||||||
|
.then(registration => {
|
||||||
|
registration.onupdatefound = () => {
|
||||||
|
const installingWorker = registration.installing;
|
||||||
|
if (installingWorker == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
installingWorker.onstatechange = () => {
|
||||||
|
if (installingWorker.state === 'installed') {
|
||||||
|
if (navigator.serviceWorker.controller) {
|
||||||
|
// At this point, the updated precached content has been fetched,
|
||||||
|
// but the previous service worker will still serve the older
|
||||||
|
// content until all client tabs are closed.
|
||||||
|
console.log(
|
||||||
|
'New content is available and will be used when all ' +
|
||||||
|
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Execute callback
|
||||||
|
if (config && config.onUpdate) {
|
||||||
|
config.onUpdate(registration);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// At this point, everything has been precached.
|
||||||
|
// It's the perfect time to display a
|
||||||
|
// "Content is cached for offline use." message.
|
||||||
|
console.log('Content is cached for offline use.');
|
||||||
|
|
||||||
|
// Execute callback
|
||||||
|
if (config && config.onSuccess) {
|
||||||
|
config.onSuccess(registration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error during service worker registration:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkValidServiceWorker(swUrl, config) {
|
||||||
|
// Check if the service worker can be found. If it can't reload the page.
|
||||||
|
fetch(swUrl, {
|
||||||
|
headers: { 'Service-Worker': 'script' }
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
// Ensure service worker exists, and that we really are getting a JS file.
|
||||||
|
const contentType = response.headers.get('content-type');
|
||||||
|
if (
|
||||||
|
response.status === 404 ||
|
||||||
|
(contentType != null && contentType.indexOf('javascript') === -1)
|
||||||
|
) {
|
||||||
|
// No service worker found. Probably a different app. Reload the page.
|
||||||
|
navigator.serviceWorker.ready.then(registration => {
|
||||||
|
registration.unregister().then(() => {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Service worker found. Proceed as normal.
|
||||||
|
registerValidSW(swUrl, config);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.log(
|
||||||
|
'No internet connection found. App is running in offline mode.'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unregister() {
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
navigator.serviceWorker.ready.then(registration => {
|
||||||
|
registration.unregister();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
5
mailhug-ui/src/setupTests.js
Normal file
5
mailhug-ui/src/setupTests.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||||
|
// allows you to do things like:
|
||||||
|
// expect(element).toHaveTextContent(/react/i)
|
||||||
|
// learn more: https://github.com/testing-library/jest-dom
|
||||||
|
import '@testing-library/jest-dom/extend-expect';
|
10916
mailhug-ui/yarn.lock
Normal file
10916
mailhug-ui/yarn.lock
Normal file
File diff suppressed because it is too large
Load diff
19
mailhug/Cargo.toml
Normal file
19
mailhug/Cargo.toml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
[package]
|
||||||
|
name = "mailhug"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Armin Friedl <dev@friedl.net>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rocket = "0.4.2"
|
||||||
|
rocket_contrib = "0.4.2"
|
||||||
|
serde = {version = "1.0", features = ["derive"]}
|
||||||
|
mailin-embedded = "0.5"
|
||||||
|
log = "0.4"
|
||||||
|
env_logger = "0.7"
|
||||||
|
structopt = "0.3"
|
||||||
|
quick-error = "1.2"
|
||||||
|
mailparse = "0.1"
|
||||||
|
rand = "0.7"
|
21
mailhug/src/api.rs
Normal file
21
mailhug/src/api.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use log::{debug, error, info, trace, warn};
|
||||||
|
|
||||||
|
use rocket::get;
|
||||||
|
use rocket::http::ContentType;
|
||||||
|
use rocket::response::content::Content;
|
||||||
|
use rocket::State;
|
||||||
|
use rocket_contrib::json::Json;
|
||||||
|
|
||||||
|
use crate::maildir::MailInfo;
|
||||||
|
use crate::maildir::Maildir;
|
||||||
|
|
||||||
|
#[get("/mail/<id>")]
|
||||||
|
pub fn mail(id: String, maildir: State<Maildir>) -> Content<String> {
|
||||||
|
Content(ContentType::HTML, maildir.mail_content(&id))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/mails")]
|
||||||
|
pub fn mailtable(maildir: State<Maildir>) -> Json<Vec<MailInfo>> {
|
||||||
|
Json(maildir.mail_infos())
|
||||||
|
}
|
42
mailhug/src/maildir/mem.rs
Normal file
42
mailhug/src/maildir/mem.rs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
use std::io::{Read,Write};
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
use serde::{Serialize};
|
||||||
|
|
||||||
|
use mailparse::{parse_headers, MailHeaderMap};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct MailMem {
|
||||||
|
pub mailinfos: Vec<MailInfo>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Clone)]
|
||||||
|
pub struct MailInfo {
|
||||||
|
id: String,
|
||||||
|
from: String,
|
||||||
|
to: String,
|
||||||
|
subject: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MailMem {
|
||||||
|
pub fn init(&mut self, cur_dir: &Path) {
|
||||||
|
|
||||||
|
for entry in fs::read_dir(cur_dir).unwrap() {
|
||||||
|
let entry: fs::DirEntry = entry.unwrap();
|
||||||
|
let metadata: fs::Metadata = entry.metadata().unwrap();
|
||||||
|
assert!(metadata.is_file());
|
||||||
|
|
||||||
|
let mut file = fs::File::open(entry.path()).unwrap();
|
||||||
|
let mut raw_mail = Vec::new();
|
||||||
|
file.read_to_end(&mut raw_mail).unwrap();
|
||||||
|
let (headers, _) = parse_headers(&raw_mail).unwrap();
|
||||||
|
|
||||||
|
let id = entry.file_name().into_string().unwrap();
|
||||||
|
let from = headers.get_first_value("From").unwrap().unwrap();
|
||||||
|
let to = headers.get_first_value("To").unwrap().unwrap();
|
||||||
|
let subject = headers.get_first_value("Subject").unwrap().unwrap();
|
||||||
|
|
||||||
|
self.mailinfos.push(MailInfo {id, from, to, subject});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
86
mailhug/src/maildir/mod.rs
Normal file
86
mailhug/src/maildir/mod.rs
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use log::{error, warn, info, debug, trace};
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
mod mem;
|
||||||
|
mod tempfile;
|
||||||
|
|
||||||
|
pub use tempfile::TempFile;
|
||||||
|
pub use mem::MailInfo;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Maildir (std::sync::Arc<std::sync::RwLock<MaildirInternal>>);
|
||||||
|
|
||||||
|
impl Maildir {
|
||||||
|
pub fn mail_infos(&self) -> Vec<MailInfo> {
|
||||||
|
self.0.read().unwrap().mem.mailinfos.to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mail_content(&self, id: &str) -> String {
|
||||||
|
let cur_dir = self.0.read().unwrap().cur_dir();
|
||||||
|
let new_dir = self.0.read().unwrap().new_dir();
|
||||||
|
|
||||||
|
let mut mail_file = match std::fs::File::open(cur_dir.join(id)) {
|
||||||
|
Ok(f) => f,
|
||||||
|
_ => std::fs::File::open(new_dir.join(id)).unwrap()
|
||||||
|
};
|
||||||
|
let mut mail_buf = Vec::new();
|
||||||
|
mail_file.read_to_end(&mut mail_buf).unwrap();
|
||||||
|
|
||||||
|
let mail: mailparse::ParsedMail = mailparse::parse_mail(&mail_buf).unwrap();
|
||||||
|
mail.get_body().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tmp_file(&self) -> TempFile {
|
||||||
|
tempfile::TempFile::new(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PathBuf> for Maildir {
|
||||||
|
fn from(path: PathBuf) -> Self {
|
||||||
|
let mut md_internal = MaildirInternal{
|
||||||
|
path: path.clone(),
|
||||||
|
mem: mem::MailMem::default()};
|
||||||
|
|
||||||
|
if !path.exists() {
|
||||||
|
std::fs::create_dir_all(&path).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if !md_internal.cur_dir().exists() {
|
||||||
|
std::fs::create_dir_all(&md_internal.cur_dir()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if !md_internal.new_dir().exists() {
|
||||||
|
std::fs::create_dir_all(&md_internal.new_dir()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if !md_internal.tmp_dir().exists() {
|
||||||
|
std::fs::create_dir_all(&md_internal.tmp_dir()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
md_internal.mem.init(&md_internal.new_dir());
|
||||||
|
|
||||||
|
Maildir(std::sync::Arc::new(std::sync::RwLock::new(md_internal)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MaildirInternal {
|
||||||
|
path: PathBuf,
|
||||||
|
mem: mem::MailMem
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MaildirInternal {
|
||||||
|
fn cur_dir(&self) -> PathBuf {
|
||||||
|
self.path.join("cur")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tmp_dir(&self) -> PathBuf {
|
||||||
|
self.path.join("tmp")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_dir(&self) -> PathBuf {
|
||||||
|
self.path.join("new")
|
||||||
|
}
|
||||||
|
}
|
60
mailhug/src/maildir/tempfile.rs
Normal file
60
mailhug/src/maildir/tempfile.rs
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use log::{error, warn, info, debug, trace};
|
||||||
|
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
|
use super::Maildir;
|
||||||
|
|
||||||
|
pub struct TempFile {
|
||||||
|
buffer: Vec<u8>,
|
||||||
|
maildir: Maildir
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TempFile {
|
||||||
|
pub fn new(maildir: &Maildir) -> Self {
|
||||||
|
TempFile {
|
||||||
|
buffer: Vec::new(),
|
||||||
|
maildir: maildir.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Write for TempFile {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||||
|
self.buffer.write(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> std::io::Result<()> {
|
||||||
|
self.buffer.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for TempFile {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||||
|
self.buffer.as_slice().read(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for TempFile {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let eid = std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::SystemTime::UNIX_EPOCH)
|
||||||
|
.expect("Could not get current time stamp")
|
||||||
|
.as_nanos();
|
||||||
|
let pid = std::process::id();
|
||||||
|
let rid = rand::random::<u32>();
|
||||||
|
|
||||||
|
let tmp_name = format!{"{:x}{:x}{:x}", eid, pid, rid};
|
||||||
|
let tmp_file_path = self.maildir.0.read().unwrap()
|
||||||
|
.tmp_dir().join(&tmp_name);
|
||||||
|
let new_file_path = self.maildir.0.read().unwrap()
|
||||||
|
.new_dir().join(&tmp_name);
|
||||||
|
|
||||||
|
debug!{"Writing into tmp file {}", tmp_file_path.display()}
|
||||||
|
|
||||||
|
let mut tmp_file = std::fs::File::create(&tmp_file_path).unwrap();
|
||||||
|
tmp_file.write(&self.buffer).unwrap();
|
||||||
|
|
||||||
|
std::fs::rename(tmp_file_path, new_file_path).unwrap();
|
||||||
|
}
|
||||||
|
}
|
71
mailhug/src/main.rs
Normal file
71
mailhug/src/main.rs
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
#![feature(proc_macro_hygiene, decl_macro)]
|
||||||
|
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use log::{error, warn, info, debug, trace};
|
||||||
|
|
||||||
|
use rocket::fairing::AdHoc;
|
||||||
|
use rocket::http::hyper::header::AccessControlAllowOrigin;
|
||||||
|
use rocket::routes;
|
||||||
|
|
||||||
|
use structopt::StructOpt;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use mailin_embedded::{Server, SslConfig};
|
||||||
|
|
||||||
|
mod api;
|
||||||
|
mod ui;
|
||||||
|
mod smtp;
|
||||||
|
mod maildir;
|
||||||
|
|
||||||
|
use smtp::MailinHandler;
|
||||||
|
|
||||||
|
#[derive(StructOpt)]
|
||||||
|
struct Config {
|
||||||
|
#[structopt(env="MAILDIR", parse(from_os_str), default_value="./maildir")]
|
||||||
|
maildir: PathBuf,
|
||||||
|
|
||||||
|
#[structopt(env="HOST_NAME", default_value="example.com")]
|
||||||
|
host_name: String,
|
||||||
|
|
||||||
|
#[structopt(env="HOST_IP", default_value="127.0.0.1")]
|
||||||
|
host_ip: String,
|
||||||
|
|
||||||
|
#[structopt(env="HTTP_PORT", default_value="8000")]
|
||||||
|
ui_port: u16,
|
||||||
|
|
||||||
|
#[structopt(env="SMTP_PORT", default_value="9025")]
|
||||||
|
smtp_port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
env_logger::init();
|
||||||
|
let config = Config::from_args();
|
||||||
|
|
||||||
|
info!{"Initializing Maildir"}
|
||||||
|
let maildir = maildir::Maildir::from(config.maildir);
|
||||||
|
|
||||||
|
info!{"Spawning SMTP sink"}
|
||||||
|
let mailin_handler = MailinHandler::new(maildir.clone());
|
||||||
|
let mut server = Server::new(mailin_handler);
|
||||||
|
|
||||||
|
server.with_name(config.host_name)
|
||||||
|
.with_ssl(SslConfig::None)
|
||||||
|
.expect("Could not configure SMTP sink without SSL")
|
||||||
|
.with_addr((config.host_ip.as_ref(), config.smtp_port))
|
||||||
|
.expect(&format!{"Could set SMTP sink address to {}:{}",
|
||||||
|
config.host_ip, config.smtp_port});
|
||||||
|
|
||||||
|
server.serve().expect("Could not start SMTP sink");
|
||||||
|
|
||||||
|
info!{"Spawning HTTP services"}
|
||||||
|
rocket::ignite()
|
||||||
|
.attach(AdHoc::on_response("CORS", |_req, resp|
|
||||||
|
{ resp.set_header(AccessControlAllowOrigin::Any);}))
|
||||||
|
.manage(maildir)
|
||||||
|
.mount("/", routes![ui::index,
|
||||||
|
ui::statics,
|
||||||
|
api::mailtable,
|
||||||
|
api::mail])
|
||||||
|
.launch();
|
||||||
|
}
|
22
mailhug/src/smtp.rs
Normal file
22
mailhug/src/smtp.rs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use log::{error, warn, info, debug, trace};
|
||||||
|
|
||||||
|
use mailin_embedded::{Handler, DataResult};
|
||||||
|
use crate::maildir::Maildir;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct MailinHandler {
|
||||||
|
maildir: Maildir
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MailinHandler {
|
||||||
|
pub fn new(maildir: Maildir) -> Self {
|
||||||
|
MailinHandler{maildir}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler for MailinHandler {
|
||||||
|
fn data(&mut self, _domain: &str, _from: &str, _is8bit: bool, _to: &[String]) -> DataResult {
|
||||||
|
DataResult::Ok(Box::new(self.maildir.tmp_file()))
|
||||||
|
}
|
||||||
|
}
|
16
mailhug/src/ui.rs
Normal file
16
mailhug/src/ui.rs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use log::{error, warn, info, debug, trace};
|
||||||
|
|
||||||
|
use std::path::{Path,PathBuf};
|
||||||
|
use rocket::get;
|
||||||
|
use rocket::response::NamedFile;
|
||||||
|
|
||||||
|
#[get("/")]
|
||||||
|
pub fn index() -> Result<NamedFile, std::io::Error> {
|
||||||
|
NamedFile::open("../mailhug-ui/build/index.html")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/static/<path..>")]
|
||||||
|
pub fn statics(path: PathBuf) -> Result<NamedFile, std::io::Error> {
|
||||||
|
NamedFile::open(Path::new("../mailhug-ui/build/static/").join(path))
|
||||||
|
}
|
Loading…
Reference in a new issue