feat: well known files
This commit is contained in:
parent
6b51cc5e53
commit
068632b117
5
.gitignore
vendored
5
.gitignore
vendored
@ -35,5 +35,6 @@ test.log
|
|||||||
/.stack-work.lock
|
/.stack-work.lock
|
||||||
/.npmrc
|
/.npmrc
|
||||||
/config/webpack.yml
|
/config/webpack.yml
|
||||||
static/wp-*/
|
/static
|
||||||
/config/favicon.json
|
/well-known
|
||||||
|
/**/tmp-*
|
||||||
|
|||||||
@ -33,7 +33,7 @@ npm install:
|
|||||||
- n stable
|
- n stable
|
||||||
- npm install -g npm
|
- npm install -g npm
|
||||||
- hash -r
|
- hash -r
|
||||||
- apt-get install openssh-client -y
|
- apt-get -y install openssh-client exiftool
|
||||||
- install -v -m 0700 -d ~/.ssh
|
- install -v -m 0700 -d ~/.ssh
|
||||||
- install -v -T -m 0644 ${SSH_KNOWN_HOSTS} ~/.ssh/known_hosts
|
- install -v -T -m 0644 ${SSH_KNOWN_HOSTS} ~/.ssh/known_hosts
|
||||||
- install -v -T -m 0400 ${SSH_DEPLOY_KEY} ~/.ssh/deploy && echo "IdentityFile ~/.ssh/deploy" >> ~/.ssh/config;
|
- install -v -T -m 0400 ${SSH_DEPLOY_KEY} ~/.ssh/deploy && echo "IdentityFile ~/.ssh/deploy" >> ~/.ssh/config;
|
||||||
|
|||||||
77
config/favicon.json
Normal file
77
config/favicon.json
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
{
|
||||||
|
"masterPicture": "assets/favicon.svg",
|
||||||
|
"design": {
|
||||||
|
"desktop_browser": {},
|
||||||
|
"ios": {
|
||||||
|
"picture_aspect": "background_and_margin",
|
||||||
|
"margin": "5%",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"startup_image": {
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
},
|
||||||
|
"app_name": "Uni2work",
|
||||||
|
"assets": {
|
||||||
|
"ios6_and_prior_icons": false,
|
||||||
|
"ios7_and_later_icons": true,
|
||||||
|
"precomposed_icons": true,
|
||||||
|
"declare_only_default_icon": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"windows": {
|
||||||
|
"picture_aspect": "white_silhouette",
|
||||||
|
"background_color": "#0a9342",
|
||||||
|
"app_name": "Uni2work"
|
||||||
|
},
|
||||||
|
"firefox_app": {
|
||||||
|
"picture_aspect": "circle",
|
||||||
|
"keep_picture_in_circle": false,
|
||||||
|
"circle_inner_margin": "5%",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"overlay": false,
|
||||||
|
"manifest": {
|
||||||
|
"app_name": "Uni2work",
|
||||||
|
"app_description": {
|
||||||
|
"_i18n": true,
|
||||||
|
"de-de-formal": "Ein webbasiertes Lehrverwaltungssystem der LMU München",
|
||||||
|
"en-eu": "A web based teaching management system at LMU Munich"
|
||||||
|
},
|
||||||
|
"developer_name": "Uni2work-Team",
|
||||||
|
"developer_url": "https://uni2work.ifi.lmu.de/info",
|
||||||
|
"display": "browser",
|
||||||
|
"start_url": "/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"android_chrome": {
|
||||||
|
"picture_aspect": "shadow",
|
||||||
|
"manifest": {
|
||||||
|
"name": "Uni2work",
|
||||||
|
"display": "browser",
|
||||||
|
"orientation": "portrait",
|
||||||
|
"start_url": "/"
|
||||||
|
},
|
||||||
|
"assets": {
|
||||||
|
"legacy_icon": true,
|
||||||
|
"low_resolution_icons": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"safari_pinned_tab": {
|
||||||
|
"picture_aspect": "silhouette",
|
||||||
|
"theme_color": "#0a9342"
|
||||||
|
},
|
||||||
|
"coast": {
|
||||||
|
"picture_aspect": "background_and_margin",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"margin": "10%"
|
||||||
|
},
|
||||||
|
"open_graph": {
|
||||||
|
"picture_aspect": "background_and_margin",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"margin": "10%",
|
||||||
|
"ratio": "square"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"html_code_file": true
|
||||||
|
},
|
||||||
|
"versioning": false
|
||||||
|
}
|
||||||
@ -43,6 +43,8 @@ application/java-vm class
|
|||||||
application/javascript js
|
application/javascript js
|
||||||
application/json json
|
application/json json
|
||||||
application/jsonml+json jsonml
|
application/jsonml+json jsonml
|
||||||
|
application/manifest+json webmanifest
|
||||||
|
application/x-web-app-manifest+json webapp
|
||||||
application/lost+xml lostxml
|
application/lost+xml lostxml
|
||||||
application/mac-binhex40 hqx
|
application/mac-binhex40 hqx
|
||||||
application/mac-compactpro cpt
|
application/mac-compactpro cpt
|
||||||
|
|||||||
@ -4,8 +4,10 @@
|
|||||||
# See https://github.com/yesodweb/yesod/wiki/Configuration#parsing-numeric-values-as-strings
|
# See https://github.com/yesodweb/yesod/wiki/Configuration#parsing-numeric-values-as-strings
|
||||||
|
|
||||||
static-dir: "_env:STATIC_DIR:static"
|
static-dir: "_env:STATIC_DIR:static"
|
||||||
|
well-known-dir: "_env:WELL_KNOWN_DIR:well-known"
|
||||||
|
well-known-link-file: "html_code.html"
|
||||||
|
|
||||||
webpack-manifest: "_env:WEBPACK_MANIFEST:config/webpack.yml"
|
webpack-manifest: "_env:WEBPACK_MANIFEST:config/webpack.yml"
|
||||||
favicon-stats: "_env:FAVICON_STATS:config/favicon.json"
|
|
||||||
host: "_env:HOST:*4" # any IPv4 host
|
host: "_env:HOST:*4" # any IPv4 host
|
||||||
port: "_env:PORT:3000"
|
port: "_env:PORT:3000"
|
||||||
ip-from-header: "_env:IP_FROM_HEADER:false"
|
ip-from-header: "_env:IP_FROM_HEADER:false"
|
||||||
|
|||||||
@ -1208,8 +1208,7 @@ BreadcrumbTerm: Semester
|
|||||||
BreadcrumbSchool: Institut
|
BreadcrumbSchool: Institut
|
||||||
BreadcrumbUser: Benutzer
|
BreadcrumbUser: Benutzer
|
||||||
BreadcrumbStatic: Statische Resource
|
BreadcrumbStatic: Statische Resource
|
||||||
BreadcrumbFavicon: Favicon
|
BreadcrumbWellKnown: Benannte statische Resource
|
||||||
BreadcrumbRobots: robots.txt
|
|
||||||
BreadcrumbMetrics: Metriken
|
BreadcrumbMetrics: Metriken
|
||||||
BreadcrumbLecturerInvite: Einladung zum Kursverwalter
|
BreadcrumbLecturerInvite: Einladung zum Kursverwalter
|
||||||
BreadcrumbExamOfficeUserInvite: Einladung bzgl. Prüfungsleistungen
|
BreadcrumbExamOfficeUserInvite: Einladung bzgl. Prüfungsleistungen
|
||||||
|
|||||||
@ -1207,8 +1207,7 @@ BreadcrumbTerm: Semester
|
|||||||
BreadcrumbSchool: Department
|
BreadcrumbSchool: Department
|
||||||
BreadcrumbUser: User
|
BreadcrumbUser: User
|
||||||
BreadcrumbStatic: Static resource
|
BreadcrumbStatic: Static resource
|
||||||
BreadcrumbFavicon: Favicon
|
BreadcrumbWellKnown: Named static resource
|
||||||
BreadcrumbRobots: robots.txt
|
|
||||||
BreadcrumbMetrics: Metrics
|
BreadcrumbMetrics: Metrics
|
||||||
BreadcrumbLecturerInvite: Invitation to be a course administrator
|
BreadcrumbLecturerInvite: Invitation to be a course administrator
|
||||||
BreadcrumbExamOfficeUserInvite: Invitation regarding exam achievements
|
BreadcrumbExamOfficeUserInvite: Invitation regarding exam achievements
|
||||||
|
|||||||
1964
package-lock.json
generated
1964
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -60,7 +60,6 @@
|
|||||||
"@commitlint/cli": "^8.2.0",
|
"@commitlint/cli": "^8.2.0",
|
||||||
"@commitlint/config-conventional": "^8.2.0",
|
"@commitlint/config-conventional": "^8.2.0",
|
||||||
"@fortawesome/fontawesome-pro": "^5.12.0",
|
"@fortawesome/fontawesome-pro": "^5.12.0",
|
||||||
"app-manifest-webpack-plugin": "^1.2.0",
|
|
||||||
"autoprefixer": "^9.7.3",
|
"autoprefixer": "^9.7.3",
|
||||||
"babel-core": "^6.26.3",
|
"babel-core": "^6.26.3",
|
||||||
"babel-eslint": "^10.0.3",
|
"babel-eslint": "^10.0.3",
|
||||||
@ -73,6 +72,7 @@
|
|||||||
"css-loader": "^2.1.1",
|
"css-loader": "^2.1.1",
|
||||||
"eslint": "^5.16.0",
|
"eslint": "^5.16.0",
|
||||||
"file-loader": "^5.0.2",
|
"file-loader": "^5.0.2",
|
||||||
|
"glob": "^7.1.6",
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"husky": "^2.7.0",
|
"husky": "^2.7.0",
|
||||||
"jasmine-core": "^3.5.0",
|
"jasmine-core": "^3.5.0",
|
||||||
@ -92,6 +92,7 @@
|
|||||||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
||||||
"postcss-loader": "^3.0.0",
|
"postcss-loader": "^3.0.0",
|
||||||
"postcss-preset-env": "^6.7.0",
|
"postcss-preset-env": "^6.7.0",
|
||||||
|
"real-favicon-webpack-plugin": "^0.2.3",
|
||||||
"remove-files-webpack-plugin": "^1.1.3",
|
"remove-files-webpack-plugin": "^1.1.3",
|
||||||
"resolve-url-loader": "^3.1.1",
|
"resolve-url-loader": "^3.1.1",
|
||||||
"sass": "^1.23.7",
|
"sass": "^1.23.7",
|
||||||
@ -100,6 +101,7 @@
|
|||||||
"standard-version": "^6.0.1",
|
"standard-version": "^6.0.1",
|
||||||
"style-loader": "^0.23.1",
|
"style-loader": "^0.23.1",
|
||||||
"terser-webpack-plugin": "^2.2.3",
|
"terser-webpack-plugin": "^2.2.3",
|
||||||
|
"tmp": "^0.1.0",
|
||||||
"typeface-roboto": "0.0.75",
|
"typeface-roboto": "0.0.75",
|
||||||
"typeface-source-sans-pro": "0.0.75",
|
"typeface-source-sans-pro": "0.0.75",
|
||||||
"webpack": "^4.41.2",
|
"webpack": "^4.41.2",
|
||||||
|
|||||||
5
routes
5
routes
@ -39,8 +39,6 @@
|
|||||||
/static StaticR EmbeddedStatic appStatic !free
|
/static StaticR EmbeddedStatic appStatic !free
|
||||||
/auth AuthR Auth getAuth !free
|
/auth AuthR Auth getAuth !free
|
||||||
|
|
||||||
/favicon.ico FaviconR GET !free
|
|
||||||
/robots.txt RobotsR GET !free
|
|
||||||
/metrics MetricsR GET
|
/metrics MetricsR GET
|
||||||
|
|
||||||
/ HomeR GET !free
|
/ HomeR GET !free
|
||||||
@ -204,6 +202,7 @@
|
|||||||
/msgs MessageListR GET POST
|
/msgs MessageListR GET POST
|
||||||
/msg/#{CryptoUUIDSystemMessage} MessageR GET POST !timeANDreadANDauthentication
|
/msg/#{CryptoUUIDSystemMessage} MessageR GET POST !timeANDreadANDauthentication
|
||||||
|
|
||||||
|
|
||||||
!/#UUID CryptoUUIDDispatchR GET !free -- just redirect
|
!/#UUID CryptoUUIDDispatchR GET !free -- just redirect
|
||||||
-- !/*{CI FilePath} CryptoFileNameDispatchR GET !free -- Disabled until preliminary check for valid cID exists
|
-- !/*{CI FilePath} CryptoFileNameDispatchR GET !free -- Disabled until preliminary check for valid cID exists
|
||||||
|
|
||||||
|
!/*WellKnownFileName WellKnownR GET !free
|
||||||
@ -19,7 +19,7 @@ let
|
|||||||
'';
|
'';
|
||||||
|
|
||||||
override = oldAttrs: {
|
override = oldAttrs: {
|
||||||
nativeBuildInputs = oldAttrs.nativeBuildInputs ++ (with pkgs; [ nodejs-12_x postgresql openldap google-chrome ]) ++ (with haskellPackages; [ stack yesod-bin hlint cabal-install weeder ]);
|
nativeBuildInputs = oldAttrs.nativeBuildInputs ++ (with pkgs; [ nodejs-12_x postgresql openldap google-chrome exiftool ]) ++ (with haskellPackages; [ stack yesod-bin hlint cabal-install weeder ]);
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
export PROMPT_INFO="${oldAttrs.name}"
|
export PROMPT_INFO="${oldAttrs.name}"
|
||||||
|
|
||||||
|
|||||||
@ -101,7 +101,6 @@ import Data.List (cycle)
|
|||||||
|
|
||||||
-- Import all relevant handler modules here.
|
-- Import all relevant handler modules here.
|
||||||
-- (HPack takes care to add new modules to our cabal file nowadays.)
|
-- (HPack takes care to add new modules to our cabal file nowadays.)
|
||||||
import Handler.Common
|
|
||||||
import Handler.Home
|
import Handler.Home
|
||||||
import Handler.Info
|
import Handler.Info
|
||||||
import Handler.Help
|
import Handler.Help
|
||||||
|
|||||||
@ -1575,8 +1575,8 @@ siteLayout' headingOverride widget = do
|
|||||||
|
|
||||||
pc <- widgetToPageContent $ do
|
pc <- widgetToPageContent $ do
|
||||||
webpackLinks_main StaticR
|
webpackLinks_main StaticR
|
||||||
faviconLinks
|
|
||||||
toWidget $(juliusFile "templates/i18n.julius")
|
toWidget $(juliusFile "templates/i18n.julius")
|
||||||
|
wellKnownHtmlLinks
|
||||||
|
|
||||||
$(widgetFile "default-layout")
|
$(widgetFile "default-layout")
|
||||||
withUrlRenderer $(hamletFile "templates/default-layout-wrapper.hamlet")
|
withUrlRenderer $(hamletFile "templates/default-layout-wrapper.hamlet")
|
||||||
@ -1627,8 +1627,7 @@ i18nCrumb msg mbR = do
|
|||||||
instance YesodBreadcrumbs UniWorX where
|
instance YesodBreadcrumbs UniWorX where
|
||||||
breadcrumb (AuthR _) = i18nCrumb MsgMenuLogin $ Just HomeR
|
breadcrumb (AuthR _) = i18nCrumb MsgMenuLogin $ Just HomeR
|
||||||
breadcrumb (StaticR _) = i18nCrumb MsgBreadcrumbStatic Nothing
|
breadcrumb (StaticR _) = i18nCrumb MsgBreadcrumbStatic Nothing
|
||||||
breadcrumb FaviconR = i18nCrumb MsgBreadcrumbFavicon Nothing
|
breadcrumb (WellKnownR _) = i18nCrumb MsgBreadcrumbWellKnown Nothing
|
||||||
breadcrumb RobotsR = i18nCrumb MsgBreadcrumbRobots Nothing
|
|
||||||
breadcrumb MetricsR = i18nCrumb MsgBreadcrumbMetrics Nothing
|
breadcrumb MetricsR = i18nCrumb MsgBreadcrumbMetrics Nothing
|
||||||
|
|
||||||
breadcrumb HomeR = i18nCrumb MsgMenuHome Nothing
|
breadcrumb HomeR = i18nCrumb MsgMenuHome Nothing
|
||||||
|
|||||||
@ -1,29 +0,0 @@
|
|||||||
-- | Common handler functions.
|
|
||||||
module Handler.Common
|
|
||||||
( getFaviconR
|
|
||||||
, getRobotsR
|
|
||||||
) where
|
|
||||||
|
|
||||||
import Data.FileEmbed (embedFile)
|
|
||||||
import Import hiding (embedFile)
|
|
||||||
|
|
||||||
-- These handlers embed files in the executable at compile time to avoid a
|
|
||||||
-- runtime dependency, and for efficiency.
|
|
||||||
|
|
||||||
getFaviconR :: Handler TypedContent
|
|
||||||
getFaviconR = do
|
|
||||||
let content = $(embedFile "static/favicon.ico")
|
|
||||||
|
|
||||||
setEtagHashable content
|
|
||||||
|
|
||||||
return $ TypedContent "image/x-icon"
|
|
||||||
$ toContent content
|
|
||||||
|
|
||||||
getRobotsR :: Handler TypedContent
|
|
||||||
getRobotsR = do
|
|
||||||
let content = $(embedFile "static/robots.txt")
|
|
||||||
|
|
||||||
setEtagHashable content
|
|
||||||
|
|
||||||
return $ TypedContent typePlain
|
|
||||||
$ toContent content
|
|
||||||
@ -12,8 +12,9 @@ import Utils.Tokens as Import
|
|||||||
import Utils.Frontend.Modal as Import
|
import Utils.Frontend.Modal as Import
|
||||||
import Utils.Lens as Import
|
import Utils.Lens as Import
|
||||||
|
|
||||||
import Settings as Import
|
import Settings as Import
|
||||||
import Settings.StaticFiles as Import
|
import Settings.StaticFiles as Import
|
||||||
|
import Settings.WellKnownFiles as Import
|
||||||
|
|
||||||
import CryptoID as Import
|
import CryptoID as Import
|
||||||
import Audit as Import
|
import Audit as Import
|
||||||
|
|||||||
@ -78,7 +78,8 @@ data AppSettings = AppSettings
|
|||||||
{ appStaticDir :: FilePath
|
{ appStaticDir :: FilePath
|
||||||
-- ^ Directory from which to serve static files.
|
-- ^ Directory from which to serve static files.
|
||||||
, appWebpackEntrypoints :: FilePath
|
, appWebpackEntrypoints :: FilePath
|
||||||
, appFaviconStats :: FilePath
|
, appWellKnownDir :: FilePath
|
||||||
|
, appWellKnownLinkFile :: FilePath
|
||||||
, appDatabaseConf :: PostgresConf
|
, appDatabaseConf :: PostgresConf
|
||||||
-- ^ Configuration settings for accessing the database.
|
-- ^ Configuration settings for accessing the database.
|
||||||
, appAutoDbMigrate :: Bool
|
, appAutoDbMigrate :: Bool
|
||||||
@ -370,8 +371,9 @@ instance FromJSON AppSettings where
|
|||||||
False
|
False
|
||||||
#endif
|
#endif
|
||||||
appStaticDir <- o .: "static-dir"
|
appStaticDir <- o .: "static-dir"
|
||||||
|
appWellKnownDir <- o .: "well-known-dir"
|
||||||
|
appWellKnownLinkFile <- o .: "well-known-link-file"
|
||||||
appWebpackEntrypoints <- o .: "webpack-manifest"
|
appWebpackEntrypoints <- o .: "webpack-manifest"
|
||||||
appFaviconStats <- o .: "favicon-stats"
|
|
||||||
appDatabaseConf <- o .: "database"
|
appDatabaseConf <- o .: "database"
|
||||||
appAutoDbMigrate <- o .: "auto-db-migrate"
|
appAutoDbMigrate <- o .: "auto-db-migrate"
|
||||||
let nonEmptyHost LdapConf{..} = case ldapHost of
|
let nonEmptyHost LdapConf{..} = case ldapHost of
|
||||||
|
|||||||
@ -1,14 +1,19 @@
|
|||||||
|
{-# OPTIONS_GHC -fno-warn-unused-top-binds #-}
|
||||||
|
-- Listing only files directly used by consumers of this module
|
||||||
|
-- prevents rebuilds if files change, that are not directly used (like
|
||||||
|
-- webpack bundles)
|
||||||
module Settings.StaticFiles
|
module Settings.StaticFiles
|
||||||
( module Settings.StaticFiles
|
( img_lmu_sigillum_svg
|
||||||
|
, webpackLinks_main
|
||||||
|
, embeddedStatic
|
||||||
, module Yesod.EmbeddedStatic
|
, module Yesod.EmbeddedStatic
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import ClassyPrelude.Yesod
|
import ClassyPrelude.Yesod
|
||||||
|
|
||||||
import Settings (appStaticDir, appWebpackEntrypoints, appFaviconStats, compileTimeAppSettings)
|
import Settings (appStaticDir, appWebpackEntrypoints, compileTimeAppSettings)
|
||||||
import Settings.StaticFiles.Generator
|
import Settings.StaticFiles.Generator
|
||||||
import Settings.StaticFiles.Webpack
|
import Settings.StaticFiles.Webpack
|
||||||
import Settings.StaticFiles.Favicon
|
|
||||||
import Yesod.EmbeddedStatic
|
import Yesod.EmbeddedStatic
|
||||||
|
|
||||||
-- This generates easy references to files in the static directory at compile time,
|
-- This generates easy references to files in the static directory at compile time,
|
||||||
@ -28,6 +33,3 @@ import Yesod.EmbeddedStatic
|
|||||||
|
|
||||||
mkEmbeddedStatic DEV_BOOL "embeddedStatic" . pure . staticGenerator $ appStaticDir compileTimeAppSettings
|
mkEmbeddedStatic DEV_BOOL "embeddedStatic" . pure . staticGenerator $ appStaticDir compileTimeAppSettings
|
||||||
mkWebpackEntrypoints (appWebpackEntrypoints compileTimeAppSettings) (pure staticGenerator) $ appStaticDir compileTimeAppSettings
|
mkWebpackEntrypoints (appWebpackEntrypoints compileTimeAppSettings) (pure staticGenerator) $ appStaticDir compileTimeAppSettings
|
||||||
|
|
||||||
faviconLinks :: MonadWidget m => m ()
|
|
||||||
faviconLinks = $(mkFaviconLinks $ appFaviconStats compileTimeAppSettings)
|
|
||||||
|
|||||||
@ -1,38 +0,0 @@
|
|||||||
module Settings.StaticFiles.Favicon
|
|
||||||
( mkFaviconLinks
|
|
||||||
) where
|
|
||||||
|
|
||||||
import ClassyPrelude.Yesod
|
|
||||||
|
|
||||||
import Language.Haskell.TH
|
|
||||||
import Language.Haskell.TH.Syntax hiding (Lift(..))
|
|
||||||
import qualified Language.Haskell.TH.Syntax as TH (Lift(..))
|
|
||||||
|
|
||||||
import qualified Data.Aeson as JSON
|
|
||||||
|
|
||||||
import qualified Data.Map as Map
|
|
||||||
|
|
||||||
import Text.Blaze.Html (preEscapedToHtml)
|
|
||||||
|
|
||||||
|
|
||||||
mkFaviconLinks :: FilePath -- ^ Path to JSON-manifest
|
|
||||||
-> ExpQ
|
|
||||||
mkFaviconLinks manifest = do
|
|
||||||
addDependentFile manifest
|
|
||||||
htmlFragments <- decodeManifest manifest
|
|
||||||
|
|
||||||
doE $ map (\frag -> noBindS [e|toWidgetHead $ preEscapedToHtml $(TH.lift frag)|]) htmlFragments
|
|
||||||
where
|
|
||||||
decodeManifest :: FilePath -> Q [Text]
|
|
||||||
decodeManifest manifest' = do
|
|
||||||
res <- liftIO $ JSON.eitherDecodeFileStrict' manifest'
|
|
||||||
case res of
|
|
||||||
Left err -> fail err
|
|
||||||
Right res'
|
|
||||||
| Just frags' <- res' Map.!? ("html" :: Text)
|
|
||||||
, JSON.Success frags <- JSON.fromJSON frags'
|
|
||||||
-> return frags
|
|
||||||
| otherwise
|
|
||||||
-> fail "Could not parse favicon stats"
|
|
||||||
|
|
||||||
|
|
||||||
11
src/Settings/WellKnownFiles.hs
Normal file
11
src/Settings/WellKnownFiles.hs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
module Settings.WellKnownFiles
|
||||||
|
( WellKnownFileName
|
||||||
|
, getWellKnownR
|
||||||
|
, wellKnownHtmlLinks
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Settings.WellKnownFiles.TH
|
||||||
|
|
||||||
|
import Settings (appWellKnownDir, appWellKnownLinkFile, compileTimeAppSettings)
|
||||||
|
|
||||||
|
mkWellKnown "de-de-formal" (appWellKnownDir compileTimeAppSettings) (appWellKnownLinkFile compileTimeAppSettings)
|
||||||
192
src/Settings/WellKnownFiles/TH.hs
Normal file
192
src/Settings/WellKnownFiles/TH.hs
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
module Settings.WellKnownFiles.TH
|
||||||
|
( mkWellKnown
|
||||||
|
) where
|
||||||
|
|
||||||
|
import ClassyPrelude.Yesod
|
||||||
|
import Utils
|
||||||
|
|
||||||
|
import Language.Haskell.TH
|
||||||
|
import Language.Haskell.TH.Syntax hiding (Lift(..))
|
||||||
|
import qualified Language.Haskell.TH.Syntax as TH (Lift(..))
|
||||||
|
|
||||||
|
import System.Directory.Tree
|
||||||
|
|
||||||
|
import qualified Data.ByteString as BS
|
||||||
|
|
||||||
|
import Utils.Lens.TH
|
||||||
|
import Control.Lens
|
||||||
|
import Data.Set.Lens
|
||||||
|
|
||||||
|
import qualified Data.Text as Text
|
||||||
|
import qualified Data.Text.Encoding as Text
|
||||||
|
import Data.Char as Char (isAlphaNum, toUpper)
|
||||||
|
|
||||||
|
import qualified Data.Map as Map
|
||||||
|
import qualified Data.Set as Set
|
||||||
|
|
||||||
|
import Data.List.NonEmpty (NonEmpty(..))
|
||||||
|
|
||||||
|
import Data.HashMap.Strict (HashMap)
|
||||||
|
import qualified Data.HashMap.Strict as HashMap
|
||||||
|
|
||||||
|
import qualified Data.HashSet as HashSet
|
||||||
|
|
||||||
|
import System.FilePath ((</>), splitDirectories, makeRelative)
|
||||||
|
|
||||||
|
import Settings.Mime
|
||||||
|
|
||||||
|
import Text.Blaze.Html (preEscapedToHtml)
|
||||||
|
|
||||||
|
nWellKnownFileName :: Name
|
||||||
|
nWellKnownFileName = mkName "WellKnownFileName"
|
||||||
|
|
||||||
|
nwellKnownFileNames :: Name
|
||||||
|
nwellKnownFileNames = mkName "wellKnownFileNames"
|
||||||
|
|
||||||
|
ngetWellKnownR :: Name
|
||||||
|
ngetWellKnownR = mkName "getWellKnownR"
|
||||||
|
|
||||||
|
nwellKnownHtmlLinks :: Name
|
||||||
|
nwellKnownHtmlLinks = mkName "wellKnownHtmlLinks"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
mkWellKnown :: Lang -- ^ Default language
|
||||||
|
-> FilePath -- ^ Base directory
|
||||||
|
-> FilePath -- ^ Link file (@html_code.html@)
|
||||||
|
-> DecsQ
|
||||||
|
mkWellKnown defLang wellKnownBase wellKnownLinks = do
|
||||||
|
inputFiles <- fmap dirTree . liftIO $ readDirectoryWith (\f -> (f, ) <$> BS.readFile f) wellKnownBase
|
||||||
|
|
||||||
|
mapM_ qAddDependentFile $ inputFiles ^.. folded . _1
|
||||||
|
|
||||||
|
-- languageFiles :: Map Lang [(FilePath, ByteString)]
|
||||||
|
languageFiles <- if
|
||||||
|
| Dir{contents} <- inputFiles
|
||||||
|
-> return . Map.fromList $ do
|
||||||
|
(language, lContents) <- contents ^.. folded . $(multifocusL 2) _name id
|
||||||
|
Dir{} <- pure lContents
|
||||||
|
let
|
||||||
|
lContents' :: [(FilePath, ByteString)]
|
||||||
|
lContents' = flip mapMaybe (flattenDir lContents) $ \pFile -> do
|
||||||
|
File{..} <- pure pFile
|
||||||
|
guard $ name /= wellKnownLinks
|
||||||
|
return $ file & _1 %~ makeRelative (wellKnownBase </> language)
|
||||||
|
return (Text.pack language, lContents')
|
||||||
|
| otherwise
|
||||||
|
-> fail "wellKnownBase is not a directory"
|
||||||
|
|
||||||
|
fLanguages <- if
|
||||||
|
| defLang `Set.member` Map.keysSet languageFiles
|
||||||
|
, let languages' = Set.delete defLang $ Map.keysSet languageFiles
|
||||||
|
-> return $ defLang :| Set.toList languages'
|
||||||
|
| otherwise
|
||||||
|
-> fail "default language is missing in wellKnownBase"
|
||||||
|
|
||||||
|
-- languageLinks :: Map Lang ByteString
|
||||||
|
languageLinks <- if
|
||||||
|
| Dir{contents} <- inputFiles
|
||||||
|
-> return . Map.fromList $ do
|
||||||
|
(language, lContents) <- contents ^.. folded . $(multifocusL 2) _name id
|
||||||
|
Dir{} <- pure lContents
|
||||||
|
let
|
||||||
|
lContents' :: [ByteString]
|
||||||
|
lContents' = flip mapMaybe (flattenDir lContents) $ \pFile -> do
|
||||||
|
File{..} <- pure pFile
|
||||||
|
guard $ name == wellKnownLinks
|
||||||
|
return $ file ^. _2
|
||||||
|
c <- lContents'
|
||||||
|
return (Text.pack language, c)
|
||||||
|
| otherwise
|
||||||
|
-> fail "wellKnownBase is not a directory"
|
||||||
|
|
||||||
|
lLanguages <- if
|
||||||
|
| defLang `Set.member` Map.keysSet languageLinks
|
||||||
|
, let languages' = Set.delete defLang $ Map.keysSet languageLinks
|
||||||
|
-> return $ defLang :| Set.toList languages'
|
||||||
|
| otherwise
|
||||||
|
-> fail "default language is missing in wellKnownBase"
|
||||||
|
|
||||||
|
|
||||||
|
fVar <- newName "f"
|
||||||
|
hVar <- newName "h"
|
||||||
|
lVar <- newName "l"
|
||||||
|
|
||||||
|
let fileNames = setOf (folded . folded . _1) languageFiles
|
||||||
|
fileContents = Map.fromListWith (<>) $ do
|
||||||
|
(lang, fs) <- Map.toList languageFiles
|
||||||
|
(fName, fContent) <- fs
|
||||||
|
return ((fContent, mimeLookup $ Text.pack fName), Set.singleton (lang, fName))
|
||||||
|
|
||||||
|
wellKnownFileName = dataD
|
||||||
|
(cxt [])
|
||||||
|
nWellKnownFileName
|
||||||
|
[]
|
||||||
|
Nothing
|
||||||
|
[ normalC (mkName $ fNameManip fName) []
|
||||||
|
| fName <- Set.toList fileNames
|
||||||
|
]
|
||||||
|
(pure $ derivClause Nothing [[t|Eq|], [t|Ord|], [t|Bounded|], [t|Enum|], [t|Read|], [t|Show|], [t|Generic|], [t|Typeable|]])
|
||||||
|
wellKnownFileNameMapSig = sigD
|
||||||
|
nwellKnownFileNames
|
||||||
|
[t|HashMap [Text] $(conT nWellKnownFileName)|]
|
||||||
|
wellKnownFileNameMap = funD
|
||||||
|
nwellKnownFileNames
|
||||||
|
[ clause [] (normalB $ [e|HashMap.fromList|] `appE` listE [ [e|($(TH.lift . map Text.pack $ splitDirectories fName), $(conE . mkName $ fNameManip fName))|] | fName <- Set.toList fileNames ]) []
|
||||||
|
]
|
||||||
|
wellKnownFileNamePathMultiPiece = instanceD
|
||||||
|
(cxt [])
|
||||||
|
(conT ''PathMultiPiece `appT` conT nWellKnownFileName)
|
||||||
|
[ funD 'toPathMultiPiece
|
||||||
|
[ clause [conP (mkName $ fNameManip fName) []] (normalB . TH.lift . map Text.pack $ splitDirectories fName) []
|
||||||
|
| fName <- Set.toList fileNames
|
||||||
|
]
|
||||||
|
, funD 'fromPathMultiPiece $
|
||||||
|
[ clause [] (normalB [e|flip HashMap.lookup $(varE nwellKnownFileNames)|]) []
|
||||||
|
]
|
||||||
|
]
|
||||||
|
wellKnownFileNameHashable = instanceD
|
||||||
|
(cxt [])
|
||||||
|
(conT ''Hashable `appT` conT nWellKnownFileName)
|
||||||
|
[]
|
||||||
|
|
||||||
|
getWellKnownRSig = sigD
|
||||||
|
ngetWellKnownR
|
||||||
|
[t|forall m. MonadHandler m => $(conT nWellKnownFileName) -> m TypedContent|]
|
||||||
|
getWellKnownR = funD
|
||||||
|
ngetWellKnownR
|
||||||
|
[ clause [varP fVar] (normalB [e|$(varE hVar) =<< selectLanguage fLanguages|])
|
||||||
|
[ funD hVar $
|
||||||
|
[ clause [varP lVar] (guardedB
|
||||||
|
[ (,) <$> normalG [e|HashSet.member ($(varE lVar), $(varE fVar)) $ HashSet.fromList $(listE [ tupE [TH.lift l, conE . mkName $ fNameManip fName] | (l, fName) <- Set.toList xs ])|]
|
||||||
|
<*> [e|TypedContent mime (toContent fContent) <$ setEtag $(TH.lift $ hashToText (mime, fContent))|]
|
||||||
|
]) []
|
||||||
|
| ((fContent, mime), xs) <- Map.toList fileContents
|
||||||
|
] ++ pure (clause [wildP] (normalB [e|notFound|]) [])
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
wellKnownHtmlLinksSig = sigD
|
||||||
|
nwellKnownHtmlLinks
|
||||||
|
[t|forall m. MonadWidget m => m ()|]
|
||||||
|
wellKnownHtmlLinks = funD
|
||||||
|
nwellKnownHtmlLinks
|
||||||
|
[ clause [] (normalB [e|toWidgetHead . preEscapedToHtml . $(varE hVar) =<< selectLanguage lLanguages|])
|
||||||
|
[ sigD hVar [t|Text -> Text|]
|
||||||
|
, funD hVar $
|
||||||
|
[ clause [varP lVar] (guardedB
|
||||||
|
[ (,) <$> normalG [|$(varE lVar) == lang|]
|
||||||
|
<*> TH.lift (Text.filter (`notElem` ['\r', '\n']) $ Text.decodeUtf8 c)
|
||||||
|
]) []
|
||||||
|
| (lang, c) <- Map.toList languageLinks
|
||||||
|
] ++ pure (clause [wildP] (normalB [e|mempty|]) [])
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
sequence
|
||||||
|
[ wellKnownFileName, wellKnownFileNameMapSig, wellKnownFileNameMap, wellKnownFileNamePathMultiPiece, wellKnownFileNameHashable
|
||||||
|
, getWellKnownRSig, getWellKnownR
|
||||||
|
, wellKnownHtmlLinksSig, wellKnownHtmlLinks
|
||||||
|
]
|
||||||
|
where
|
||||||
|
fNameManip = Text.unpack . mconcat . over (traverse . _head) Char.toUpper . filter (not . null) . Text.split (not . isAlphaNum) . Text.pack
|
||||||
@ -913,11 +913,11 @@ cachedHereBinary = do
|
|||||||
[e| \k -> cachedByBinary (loc, k) |]
|
[e| \k -> cachedByBinary (loc, k) |]
|
||||||
|
|
||||||
hashToText :: Hashable a => a -> Text
|
hashToText :: Hashable a => a -> Text
|
||||||
hashToText = decodeUtf8 . Base64.encode . toStrict . Binary.encode . hash
|
hashToText = Text.dropWhileEnd (== '=') . decodeUtf8 . Base64.encode . toStrict . Binary.encode . hash
|
||||||
|
|
||||||
setEtagHashable, setWeakEtagHashable :: (MonadHandler m, Hashable a) => a -> m ()
|
setEtagHashable, setWeakEtagHashable :: (MonadHandler m, Hashable a) => a -> m ()
|
||||||
setEtagHashable = setEtag . hashToText
|
setEtagHashable = setEtag . hashToText
|
||||||
setWeakEtagHashable = setEtag . hashToText
|
setWeakEtagHashable = setWeakEtag . hashToText
|
||||||
|
|
||||||
setLastModified :: MonadHandler m => UTCTime -> m ()
|
setLastModified :: MonadHandler m => UTCTime -> m ()
|
||||||
setLastModified lastModified = do
|
setLastModified lastModified = do
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 99 KiB |
@ -1,5 +1,11 @@
|
|||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const tmp = require('tmp');
|
||||||
|
tmp.setGracefulCleanup();
|
||||||
|
const fs = require('fs');
|
||||||
|
const glob = require('glob');
|
||||||
|
const { execSync } = require('child_process');
|
||||||
|
|
||||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
||||||
const ManifestPlugin = require('webpack-manifest-plugin');
|
const ManifestPlugin = require('webpack-manifest-plugin');
|
||||||
@ -9,8 +15,8 @@ const TerserPlugin = require('terser-webpack-plugin');
|
|||||||
const yaml = require('js-yaml');
|
const yaml = require('js-yaml');
|
||||||
const HashOutput = require('webpack-plugin-hash-output');
|
const HashOutput = require('webpack-plugin-hash-output');
|
||||||
const postcssPresetEnv = require('postcss-preset-env');
|
const postcssPresetEnv = require('postcss-preset-env');
|
||||||
const AppManifestWebpackPlugin = require('app-manifest-webpack-plugin');
|
|
||||||
const RemovePlugin = require('remove-files-webpack-plugin');
|
const RemovePlugin = require('remove-files-webpack-plugin');
|
||||||
|
const RealFaviconPlugin = require('real-favicon-webpack-plugin');
|
||||||
|
|
||||||
const webpackVersion = require('webpack/package.json').version.split('.').slice(0, 2).join('.');
|
const webpackVersion = require('webpack/package.json').version.split('.').slice(0, 2).join('.');
|
||||||
const packageVersion = require('./package.json').version;
|
const packageVersion = require('./package.json').version;
|
||||||
@ -116,7 +122,9 @@ module.exports = {
|
|||||||
serialize: yaml.safeDump
|
serialize: yaml.safeDump
|
||||||
}),
|
}),
|
||||||
new CleanWebpackPlugin({
|
new CleanWebpackPlugin({
|
||||||
cleanOnceBeforeBuildPatterns: path.resolve(__dirname, 'static', 'wp-*')
|
cleanOnceBeforeBuildPatterns: [ path.resolve(__dirname, 'static'),
|
||||||
|
path.resolve(__dirname, 'well-known'),
|
||||||
|
]
|
||||||
}),
|
}),
|
||||||
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
|
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
|
||||||
new CopyPlugin([
|
new CopyPlugin([
|
||||||
@ -125,38 +133,64 @@ module.exports = {
|
|||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
VERSION: JSON.stringify(packageVersion)
|
VERSION: JSON.stringify(packageVersion)
|
||||||
}),
|
}),
|
||||||
new AppManifestWebpackPlugin({
|
...(() => {
|
||||||
logo: path.resolve(__dirname, 'assets/favicon.svg'),
|
const faviconJson = require('./config/favicon.json');
|
||||||
output: '/[hash]-icons/',
|
const langs = new Set();
|
||||||
prefix: `/static/res/wp-${webpackVersion}/`,
|
function findLangs(json) {
|
||||||
inject: false,
|
if (json && json._i18n) {
|
||||||
emitStats: true,
|
Object.keys(json).forEach(key => {
|
||||||
statsFilename: path.resolve(__dirname, 'config/favicon.json'),
|
if (key !== '_i18n') {
|
||||||
persistentCache: false,
|
langs.add(key);
|
||||||
config: {
|
}
|
||||||
background: '#fff',
|
})
|
||||||
icons: {
|
} else if (Array.isArray(json)) {
|
||||||
android: false,
|
json.forEach(elem => findLangs(elem));
|
||||||
appleIcon: false,
|
} else if (typeof json === 'object') {
|
||||||
appleStartup: false,
|
Object.keys(json).forEach(key => findLangs(json[key]));
|
||||||
coast: false,
|
|
||||||
favicons: true,
|
|
||||||
firefox: false,
|
|
||||||
windows: false,
|
|
||||||
yandex: false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
findLangs(faviconJson);
|
||||||
new RemovePlugin({
|
|
||||||
after: {
|
function selectLang(lang, json) {
|
||||||
test: [
|
if (json && json._i18n) {
|
||||||
{ folder: path.resolve(__dirname, `static/wp-${webpackVersion}`),
|
return json[lang];
|
||||||
method: (filePath) => { return new RegExp(/\/.*-icons\/.*\.(xml|json|webapp)$/, 'm').test(filePath); },
|
} else if (Array.isArray(json)) {
|
||||||
recursive: true
|
return json.map(elem => selectLang(lang, elem));
|
||||||
}
|
} else if (typeof json === 'object') {
|
||||||
]
|
return Object.fromEntries(Object.entries(json).map(([k, v]) => [k, selectLang(lang, v)]));
|
||||||
|
} else {
|
||||||
|
return json;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
const langJsons = {};
|
||||||
|
Array.from(langs).forEach(lang => {
|
||||||
|
langJsons[lang] = selectLang(lang, faviconJson);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Array.from(langs).map(lang => {
|
||||||
|
const tmpobj = tmp.fileSync({ dir: ".", postfix: ".json" });
|
||||||
|
fs.writeSync(tmpobj.fd, JSON.stringify(langJsons[lang]));
|
||||||
|
fs.close(tmpobj.fd);
|
||||||
|
|
||||||
|
return [
|
||||||
|
new RealFaviconPlugin({
|
||||||
|
faviconJson: `./${tmpobj.name}`,
|
||||||
|
outputPath: path.resolve(__dirname, 'well-known', lang),
|
||||||
|
inject: false
|
||||||
|
}),
|
||||||
|
new CopyPlugin([
|
||||||
|
{ from: 'config/robots.txt', to: path.resolve(__dirname, 'well-known', lang, 'robots.txt') },
|
||||||
|
])
|
||||||
|
];
|
||||||
|
}).flat(1);
|
||||||
|
})(),
|
||||||
|
{ apply: compiler => compiler.hooks.afterEmit.tap('AfterEmitPlugin', compilation => {
|
||||||
|
Array.from(glob.sync(path.resolve(__dirname, 'well-known', '**', '*.@(png)'))).forEach(imageFile => {
|
||||||
|
execSync(`exiftool -overwrite_original -all= ${imageFile}`, { stdio: 'inherit' });
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
output: {
|
output: {
|
||||||
|
|||||||
Reference in New Issue
Block a user