Add starter template and implement SSR with Feliz.viewengine
This commit is contained in:
88
.build/Build.fs
Normal file
88
.build/Build.fs
Normal file
@@ -0,0 +1,88 @@
|
||||
open Fake.Core
|
||||
open Fake.IO
|
||||
open Farmer
|
||||
open Farmer.Builders
|
||||
|
||||
open Helpers
|
||||
|
||||
initializeContext ()
|
||||
|
||||
let serverPath = Path.getFullName "src/Server"
|
||||
let clientPath = Path.getFullName "src/Client"
|
||||
let testPath = Path.getFullName "test"
|
||||
let libPath = None
|
||||
|
||||
let distPath = Path.getFullName "dist"
|
||||
let packPath = Path.getFullName "packages"
|
||||
let versionFile = Path.getFullName ".version"
|
||||
|
||||
let vite = "node ../../node_modules/vite/bin/vite.js -c ../../vite.config.js"
|
||||
|
||||
Target.create "Clean" (fun _ -> Shell.cleanDir distPath)
|
||||
|
||||
Target.create "InstallClient" (fun _ ->
|
||||
run npm "install" "."
|
||||
run dotnet "tool restore" ".")
|
||||
|
||||
Target.create "Tailwind" (fun _ -> run npm "run tailwind:build" ".")
|
||||
|
||||
Target.create "Bundle" (fun _ ->
|
||||
[ "server", dotnet $"publish -c Release -o {distPath}" serverPath
|
||||
"client", dotnet $"fable -o build/client --run {vite} build --outDir {distPath}/public" clientPath ]
|
||||
|> runParallel)
|
||||
|
||||
Target.create "BundleDebug" (fun _ ->
|
||||
[ "server", dotnet $"publish -c Debug -o {distPath}" serverPath
|
||||
"client", dotnet $"fable -o build/client --run {vite} build --outDir {distPath}/public" clientPath ]
|
||||
|> runParallel)
|
||||
|
||||
Target.create "Pack" (fun _ ->
|
||||
match libPath with
|
||||
| Some p -> run dotnet $"pack -c Release -o \"{packPath}\"" p
|
||||
| None -> ())
|
||||
|
||||
Target.create "Run" (fun _ ->
|
||||
[ "server", dotnet "watch run" serverPath
|
||||
"client", dotnet "fable watch -o build/client --run vite -c ../../vite.config.js" clientPath ]
|
||||
|> runParallel)
|
||||
|
||||
Target.create "Server" (fun _ -> run dotnet "watch run" serverPath)
|
||||
|
||||
Target.create "ServerWatch" (fun _ -> run dotnet "watch build" serverPath)
|
||||
|
||||
Target.create "Client" (fun _ ->
|
||||
run dotnet $"fable watch -o build/client --run vite -c ../../vite.config.js" clientPath)
|
||||
|
||||
Target.create "Format" (fun _ -> run dotnet "fantomas . -r" "src")
|
||||
|
||||
Target.create "Test" (fun _ ->
|
||||
if System.IO.Directory.Exists testPath then
|
||||
[ "server", dotnet "run" (testPath + "/Server")
|
||||
"client", dotnet "fable -o build/client --run vite build" (testPath + "/Client") ]
|
||||
|> runParallel
|
||||
else
|
||||
())
|
||||
|
||||
open Fake.Core.TargetOperators
|
||||
|
||||
let dependencies =
|
||||
[ "Clean" ==> "InstallClient" ==> "Tailwind" ==> "Bundle"
|
||||
|
||||
"Clean" ==> "BundleDebug"
|
||||
|
||||
"Clean" ==> "Test"
|
||||
|
||||
"Clean" ==> "InstallClient" ==> "Tailwind" ==> "Run"
|
||||
|
||||
"Clean" ==> "Pack"
|
||||
|
||||
"Server"
|
||||
|
||||
"ServerWatch"
|
||||
|
||||
"Client"
|
||||
|
||||
]
|
||||
|
||||
[<EntryPoint>]
|
||||
let main args = runOrDefault args
|
||||
102
.build/Helpers.fs
Normal file
102
.build/Helpers.fs
Normal file
@@ -0,0 +1,102 @@
|
||||
module Helpers
|
||||
|
||||
open Fake.Core
|
||||
|
||||
let initializeContext () =
|
||||
let execContext = Context.FakeExecutionContext.Create false "build.fsx" []
|
||||
Context.setExecutionContext (Context.RuntimeContext.Fake execContext)
|
||||
|
||||
module Proc =
|
||||
module Parallel =
|
||||
open System
|
||||
|
||||
let locker = obj ()
|
||||
|
||||
let colors =
|
||||
[| ConsoleColor.Blue
|
||||
ConsoleColor.Yellow
|
||||
ConsoleColor.Magenta
|
||||
ConsoleColor.Cyan
|
||||
ConsoleColor.DarkBlue
|
||||
ConsoleColor.DarkYellow
|
||||
ConsoleColor.DarkMagenta
|
||||
ConsoleColor.DarkCyan |]
|
||||
|
||||
let print color (colored: string) (line: string) =
|
||||
lock locker (fun () ->
|
||||
let currentColor = Console.ForegroundColor
|
||||
Console.ForegroundColor <- color
|
||||
Console.Write colored
|
||||
Console.ForegroundColor <- currentColor
|
||||
Console.WriteLine line)
|
||||
|
||||
let onStdout index name (line: string) =
|
||||
let color = colors.[index % colors.Length]
|
||||
|
||||
if isNull line then
|
||||
print color $"{name}: --- END ---" ""
|
||||
else if String.isNotNullOrEmpty line then
|
||||
print color $"{name}: " line
|
||||
|
||||
let onStderr name (line: string) =
|
||||
let color = ConsoleColor.Red
|
||||
|
||||
if isNull line |> not then
|
||||
print color $"{name}: " line
|
||||
|
||||
let redirect (index, (name, createProcess)) =
|
||||
createProcess
|
||||
|> CreateProcess.redirectOutputIfNotRedirected
|
||||
|> CreateProcess.withOutputEvents (onStdout index name) (onStderr name)
|
||||
|
||||
let printStarting indexed =
|
||||
for (index, (name, c: CreateProcess<_>)) in indexed do
|
||||
let color = colors.[index % colors.Length]
|
||||
let wd = c.WorkingDirectory |> Option.defaultValue ""
|
||||
let exe = c.Command.Executable
|
||||
let args = c.Command.Arguments.ToStartInfo
|
||||
print color $"{name}: {wd}> {exe} {args}" ""
|
||||
|
||||
let run cs =
|
||||
cs
|
||||
|> Seq.toArray
|
||||
|> Array.indexed
|
||||
|> fun x ->
|
||||
printStarting x
|
||||
x
|
||||
|> Array.map redirect
|
||||
|> Array.Parallel.map Proc.run
|
||||
|
||||
let createProcess exe arg dir =
|
||||
CreateProcess.fromRawCommandLine exe arg
|
||||
|> CreateProcess.withWorkingDirectory dir
|
||||
|> CreateProcess.ensureExitCode
|
||||
|
||||
let dotnet = createProcess "dotnet"
|
||||
|
||||
let npm =
|
||||
let npmPath =
|
||||
match ProcessUtils.tryFindFileOnPath "npm" with
|
||||
| Some path -> path
|
||||
| None ->
|
||||
"npm was not found in path. Please install it and make sure it's available from your path. "
|
||||
+ "See https://safe-stack.github.io/docs/quickstart/#install-pre-requisites for more info"
|
||||
|> failwith
|
||||
|
||||
createProcess npmPath
|
||||
|
||||
let run proc arg dir = proc arg dir |> Proc.run |> ignore
|
||||
|
||||
let runParallel processes =
|
||||
processes |> Proc.Parallel.run |> ignore
|
||||
|
||||
let runOrDefault args =
|
||||
try
|
||||
match args with
|
||||
| [| target |] -> Target.runOrDefault target
|
||||
| _ -> Target.runOrDefault "Run"
|
||||
|
||||
0
|
||||
with e ->
|
||||
printfn "%A" e
|
||||
1
|
||||
34
.config/dotnet-tools.json
Normal file
34
.config/dotnet-tools.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"fable": {
|
||||
"version": "4.27.0",
|
||||
"commands": [
|
||||
"fable"
|
||||
],
|
||||
"rollForward": false
|
||||
},
|
||||
"fantomas-tool": {
|
||||
"version": "4.7.9",
|
||||
"commands": [
|
||||
"fantomas"
|
||||
],
|
||||
"rollForward": false
|
||||
},
|
||||
"fantomas": {
|
||||
"version": "7.0.3",
|
||||
"commands": [
|
||||
"fantomas"
|
||||
],
|
||||
"rollForward": false
|
||||
},
|
||||
"fornax": {
|
||||
"version": "0.16.0",
|
||||
"commands": [
|
||||
"fornax"
|
||||
],
|
||||
"rollForward": false
|
||||
}
|
||||
}
|
||||
}
|
||||
8
.dapr/components/pubsub.yaml
Normal file
8
.dapr/components/pubsub.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
apiVersion: dapr.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: pubsub
|
||||
spec:
|
||||
type: pubsub.in-memory
|
||||
version: v1
|
||||
metadata: []
|
||||
10
.dapr/components/statestore.yaml
Normal file
10
.dapr/components/statestore.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
apiVersion: dapr.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: statestore
|
||||
spec:
|
||||
type: state.in-memory
|
||||
version: v1
|
||||
metadata:
|
||||
- name: actorStateStore
|
||||
value: "true"
|
||||
9
.dapr/config.yaml
Normal file
9
.dapr/config.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
apiVersion: dapr.io/v1alpha1
|
||||
kind: Configuration
|
||||
metadata:
|
||||
name: daprConfig
|
||||
spec:
|
||||
tracing:
|
||||
samplingRate: "1"
|
||||
zipkin:
|
||||
endpointAddress: http://localhost:9411/api/v2/spans
|
||||
23
.devcontainer/Dockerfile
Normal file
23
.devcontainer/Dockerfile
Normal file
@@ -0,0 +1,23 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0
|
||||
|
||||
# Add keys and sources lists
|
||||
RUN apt-get update && apt-get install -y ca-certificates gnupg
|
||||
RUN mkdir -p /etc/apt/keyrings
|
||||
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
|
||||
ENV NODE_MAJOR=22
|
||||
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
|
||||
|
||||
# Install node, 7zip, yarn, git, process tools
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y nodejs p7zip-full git procps ssh-client
|
||||
|
||||
# Clean up
|
||||
RUN apt-get autoremove -y \
|
||||
&& apt-get clean -y \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV PATH="/root/.dotnet/tools:${PATH}"
|
||||
|
||||
# Copy endpoint specific user settings into container to specify
|
||||
# .NET Core should be used as the runtime.
|
||||
COPY settings.vscode.json /root/.vscode-remote/data/Machine/settings.json
|
||||
11
.devcontainer/devcontainer.json
Normal file
11
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "SAFE",
|
||||
"dockerFile": "Dockerfile",
|
||||
"appPort": [8080, 8085],
|
||||
"extensions": [
|
||||
"ionide.ionide-fsharp",
|
||||
"ms-dotnettools.csharp",
|
||||
"editorconfig.editorconfig",
|
||||
"msjsdiag.debugger-for-chrome"
|
||||
]
|
||||
}
|
||||
3
.devcontainer/settings.vscode.json
Normal file
3
.devcontainer/settings.vscode.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"FSharp.fsacRuntime":"netcore"
|
||||
}
|
||||
23
.gitignore
vendored
23
.gitignore
vendored
@@ -73,4 +73,25 @@ bin
|
||||
/drush.phar
|
||||
/drush
|
||||
/drupal.phar
|
||||
/drupal
|
||||
/drupal
|
||||
|
||||
.fable/
|
||||
.fake/
|
||||
.vs/
|
||||
.idea/
|
||||
.ionide/
|
||||
obj/
|
||||
bin/
|
||||
packages/
|
||||
node_modules/
|
||||
src/Client/public/js/
|
||||
release.cmd
|
||||
release.sh
|
||||
*.orig
|
||||
*.DotSettings.user
|
||||
deploy/
|
||||
dist/
|
||||
build/
|
||||
*.db
|
||||
build.fsx.lock
|
||||
_*.yaml
|
||||
|
||||
8
.gitlab-ci.yml
Normal file
8
.gitlab-ci.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
variables:
|
||||
PROJECT_NAME: OceanWeb
|
||||
SKIP_TESTS: "true"
|
||||
|
||||
include:
|
||||
- project: oceanbox/gitlab-ci
|
||||
ref: v3
|
||||
file: DotnetDeployment.gitlab-ci.yml
|
||||
41
.releaserc.yaml
Normal file
41
.releaserc.yaml
Normal file
@@ -0,0 +1,41 @@
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
- name: develop
|
||||
prerelease: true
|
||||
|
||||
plugins:
|
||||
- '@semantic-release/commit-analyzer'
|
||||
- '@semantic-release/release-notes-generator'
|
||||
- - '@semantic-release/changelog'
|
||||
- changelogFile: RELEASE_NOTES.md
|
||||
changelogTitle: "# Changelog"
|
||||
- - 'semantic-release-dotnet'
|
||||
- paths: [ "src/*.fsproj", "src/*/*.fsproj" ]
|
||||
- - '@semantic-release/exec'
|
||||
- generateNotesCmd: "echo ${nextRelease.version} > .version"
|
||||
- - '@semantic-release/git'
|
||||
- message: "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}"
|
||||
assets: [ "RELEASE_NOTES.md", ".version", "src/*.fsproj", "src/*/*.fsproj" ]
|
||||
- - '@semantic-release/gitlab'
|
||||
- assets: []
|
||||
|
||||
analyzeCommits:
|
||||
- path: "@semantic-release/commit-analyzer"
|
||||
releaseRules:
|
||||
- type: "fix"
|
||||
release: "patch"
|
||||
- type: "patch"
|
||||
release: "patch"
|
||||
- type: "feat"
|
||||
release: "minor"
|
||||
- type: "feature"
|
||||
release: "minor"
|
||||
- type: "minor"
|
||||
release: "minor"
|
||||
- type: "breaking"
|
||||
release: "major"
|
||||
- type: "major"
|
||||
release: "major"
|
||||
|
||||
|
||||
1
.tiltignore
Normal file
1
.tiltignore
Normal file
@@ -0,0 +1 @@
|
||||
tilt/base/_manifest.yaml
|
||||
20
Build.fsproj
Normal file
20
Build.fsproj
Normal file
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include=".build/Helpers.fs" />
|
||||
<Compile Include=".build/Build.fs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Fake.Core.Target" Version="6.1.4" />
|
||||
<PackageReference Include="Fake.DotNet.Cli" Version="6.1.4" />
|
||||
<PackageReference Include="Fake.IO.FileSystem" Version="6.1.4" />
|
||||
<PackageReference Include="Farmer" Version="1.9.25" />
|
||||
<PackageReference Update="FSharp.Core" Version="9.0.300" />
|
||||
<PackageReference Include="Feliz.ViewEngine" Version="0.27.0" />
|
||||
<PackageReference Include="FSharp.Data" Version="6.6.0" />
|
||||
<PackageReference Include="FSharp.SystemTextJson" Version="1.4.36" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
7
Dockerfile
Normal file
7
Dockerfile
Normal file
@@ -0,0 +1,7 @@
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0
|
||||
ENV SERVER_CONTENT_ROOT /app/public
|
||||
|
||||
COPY dist/ /app
|
||||
|
||||
WORKDIR /app
|
||||
CMD /app/Server
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Serit Tromsø AS
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
46
README.md
46
README.md
@@ -1,19 +1,37 @@
|
||||
# Drupal + Fornax
|
||||
# README
|
||||
|
||||
Small decoupled setup that pulls content from Drupal through JSONAPI and
|
||||
converts it into a static site using Fornax and F#.
|
||||
This template scaffolds a web application with the following compnents configured:
|
||||
|
||||
## What it decoupled
|
||||
- Fetches content from the Drupal site exposed through JSONAPI
|
||||
- Deserializes Drupal into F# types
|
||||
- Uses Fornax loaders + generators to render the content into static HTML
|
||||
#### Server
|
||||
|
||||
## Tech stack (currently)
|
||||
* Saturn
|
||||
* Giraffe
|
||||
* OpenIdConnect, JWT and plain authentication
|
||||
* Fable.Remoting
|
||||
* Fable.SignalR
|
||||
* Dapr
|
||||
* Prometheus metric
|
||||
* Tilt
|
||||
|
||||
- Drupal 11 + JSONAPI
|
||||
- F#
|
||||
- Fornax
|
||||
#### Client
|
||||
|
||||
## Must knows
|
||||
Drupal JSONAPI Extras has been patched with a custom patch to make sure that we
|
||||
can use _default_include to include nested relationships.
|
||||
* Fable.Lit
|
||||
* Fable.Remoting
|
||||
* Fable.SignalR
|
||||
|
||||
## Using Dapr
|
||||
|
||||
This template inludes support for Dapr Actors. The custom `use_multiauth` and `use_oidc` authentication pipelines configure the user (principal) groups and roles (claims) via a `UserActor`, which can easily be migrated to an external Dapr service if need be.
|
||||
|
||||
Install the Dapr CLI and set up Dapr for use with actors:
|
||||
|
||||
```sh
|
||||
dapr init -s
|
||||
cp .dapr/components/* ~/.dapr/componets
|
||||
```
|
||||
|
||||
Run the development server(s) under Dapr:
|
||||
|
||||
```sh
|
||||
dapr run --app-id webapp --app-port 8085 -- dotnet run
|
||||
```
|
||||
|
||||
1
RELEASE_NOTES.md
Normal file
1
RELEASE_NOTES.md
Normal file
@@ -0,0 +1 @@
|
||||
# Changelog
|
||||
93
Tiltfile
Normal file
93
Tiltfile
Normal file
@@ -0,0 +1,93 @@
|
||||
name='OceanWeb'
|
||||
namespace='default'
|
||||
chart='oceanbox/{name}'.format(name=name)
|
||||
repository='registry.gitlab.com/oceanbox/{name}'.format(name=name)
|
||||
|
||||
env=os.getenv('USER')
|
||||
cluster='{env}-vcluster'.format(env=env)
|
||||
|
||||
allow_k8s_contexts(cluster)
|
||||
|
||||
load('ext://restart_process', 'docker_build_with_restart')
|
||||
load('ext://namespace', 'namespace_inject')
|
||||
|
||||
clientPort=os.getenv('CLIENT_PORT')
|
||||
if not clientPort:
|
||||
clientPort=8080
|
||||
os.putenv('CLIENT_PORT', '{0}'.format(clientPort))
|
||||
else:
|
||||
clientPort=int(clientPort)
|
||||
|
||||
serverPort=os.getenv('SERVER_PROXY_PORT')
|
||||
if not serverPort:
|
||||
serverPort=8095
|
||||
os.putenv('SERVER_PROXY_PORT', '{0}'.format(serverPort))
|
||||
else: serverPort=int(serverPort)
|
||||
|
||||
docker_build_with_restart(
|
||||
'{repository}:latest'.format(repository=repository),
|
||||
entrypoint='dotnet /app/Server.dll',
|
||||
context='.',
|
||||
dockerfile='Dockerfile',
|
||||
only=['./dist'],
|
||||
live_update=[sync('./dist', '/app')]
|
||||
)
|
||||
|
||||
manifest=helm(
|
||||
'../manifests/charts/{name}'.format(name=name),
|
||||
name='{env}'.format(env=env),
|
||||
namespace=namespace,
|
||||
values=[ './tilt/values.yaml' ],
|
||||
set=[ 'image.repository={repository}'.format(repository=repository) ]
|
||||
)
|
||||
|
||||
local('cat > _manifest.yaml',
|
||||
dir='./tilt/base',
|
||||
stdin=manifest,
|
||||
echo_off=False, quiet=True)
|
||||
|
||||
kustomizations=str(kustomize('./tilt/tilt')).replace('<x>',"{env}".format(env=env))
|
||||
k8s_yaml(namespace_inject(blob(kustomizations), namespace))
|
||||
|
||||
local_resource(
|
||||
'create-bundle',
|
||||
cmd='dotnet run bundledebug',
|
||||
trigger_mode=TRIGGER_MODE_MANUAL
|
||||
)
|
||||
|
||||
local_resource(
|
||||
'build-server',
|
||||
cmd='dotnet publish -o ./dist src/Server',
|
||||
deps=[
|
||||
'./src/Server',
|
||||
'./src/Shared'
|
||||
],
|
||||
ignore=[
|
||||
'src/Server/bin',
|
||||
'src/Server/obj',
|
||||
'src/Shared/bin',
|
||||
'src/Shared/obj',
|
||||
],
|
||||
resource_deps=['create-bundle'],
|
||||
auto_init=True,
|
||||
labels=['server']
|
||||
)
|
||||
|
||||
local_resource(
|
||||
'run-client',
|
||||
serve_cmd='dotnet fable watch -o build/client --run vite -c ../../vite.config.js',
|
||||
serve_dir='./src/Client',
|
||||
links=['https://{name}.local.oceanbox.io:{port}'.format(name=name, port=clientPort)],
|
||||
resource_deps=['build-server'],
|
||||
auto_init=True,
|
||||
trigger_mode=TRIGGER_MODE_MANUAL,
|
||||
labels=['client']
|
||||
)
|
||||
|
||||
k8s_resource(
|
||||
'{env}-{name}'.format(env=env, name=name),
|
||||
port_forwards=['{port}:8085'.format(port=serverPort)],
|
||||
labels=['server']
|
||||
)
|
||||
|
||||
# vim:ft=python
|
||||
246
composer.json
246
composer.json
@@ -1,124 +1,128 @@
|
||||
{
|
||||
"name": "drupal/recommended-project",
|
||||
"description": "Project template for Drupal projects with a relocated document root",
|
||||
"type": "project",
|
||||
"license": "GPL-2.0-or-later",
|
||||
"homepage": "https://www.drupal.org/project/drupal",
|
||||
"support": {
|
||||
"docs": "https://www.drupal.org/docs/user_guide/en/index.html",
|
||||
"chat": "https://www.drupal.org/node/314178"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "composer",
|
||||
"url": "https://packages.drupal.org/8"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"composer/installers": "^2.3",
|
||||
"cweagans/composer-patches": "^2.0",
|
||||
"drupal/admin_toolbar": "^3.6",
|
||||
"drupal/core-composer-scaffold": "^11.2",
|
||||
"drupal/core-project-message": "^11.2",
|
||||
"drupal/core-recipe-unpack": "^11.2",
|
||||
"drupal/core-recommended": "^11.2",
|
||||
"drupal/devel": "^5.4",
|
||||
"drupal/field_group": "^4.0",
|
||||
"drupal/jsonapi_extras": "^3.27",
|
||||
"drupal/jsonapi_include": "^2.0",
|
||||
"drupal/jsonapi_resources": "^1.3",
|
||||
"drupal/paragraphs": "^1.19",
|
||||
"drupal/pathauto": "^1.14",
|
||||
"drupal/rest_absolute_urls": "^2.0"
|
||||
},
|
||||
"conflict": {
|
||||
"drupal/drupal": "*"
|
||||
},
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true,
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"composer/installers": true,
|
||||
"cweagans/composer-patches": true,
|
||||
"dealerdirect/phpcodesniffer-composer-installer": true,
|
||||
"drupal/core-composer-scaffold": true,
|
||||
"drupal/core-project-message": true,
|
||||
"drupal/core-recipe-unpack": true,
|
||||
"php-http/discovery": true,
|
||||
"php-tuf/composer-integration": true,
|
||||
"phpstan/extension-installer": true
|
||||
},
|
||||
"sort-packages": true
|
||||
},
|
||||
"extra": {
|
||||
"patches": {
|
||||
"drupal/jsonapi_include": {
|
||||
"Issue #3490592: Incompatile with jsonapi_default fix": "https://git.drupalcode.org/project/jsonapi_include/-/merge_requests/28.diff"
|
||||
},
|
||||
"drupal/jsonapi_extras": {
|
||||
"Issue #3373151: Auto include default includes of related entities": "patches/jsonapi_extras_default_includes.patch"
|
||||
}
|
||||
},
|
||||
"drupal-scaffold": {
|
||||
"locations": {
|
||||
"web-root": "web/"
|
||||
}
|
||||
},
|
||||
"installer-paths": {
|
||||
"web/core": [
|
||||
"type:drupal-core"
|
||||
],
|
||||
"web/libraries/{$name}": [
|
||||
"type:drupal-library"
|
||||
],
|
||||
"web/modules/contrib/{$name}": [
|
||||
"type:drupal-module"
|
||||
],
|
||||
"web/profiles/contrib/{$name}": [
|
||||
"type:drupal-profile"
|
||||
],
|
||||
"web/themes/contrib/{$name}": [
|
||||
"type:drupal-theme"
|
||||
],
|
||||
"drush/Commands/contrib/{$name}": [
|
||||
"type:drupal-drush"
|
||||
],
|
||||
"web/modules/custom/{$name}": [
|
||||
"type:drupal-custom-module"
|
||||
],
|
||||
"web/profiles/custom/{$name}": [
|
||||
"type:drupal-custom-profile"
|
||||
],
|
||||
"web/themes/custom/{$name}": [
|
||||
"type:drupal-custom-theme"
|
||||
],
|
||||
"recipes/{$name}": [
|
||||
"type:drupal-recipe"
|
||||
]
|
||||
},
|
||||
"drupal-core-project-message": {
|
||||
"include-keys": [
|
||||
"homepage",
|
||||
"support"
|
||||
],
|
||||
"post-create-project-cmd-message": [
|
||||
"<bg=blue;fg=white> </>",
|
||||
"<bg=blue;fg=white> Congratulations, you’ve installed the Drupal codebase </>",
|
||||
"<bg=blue;fg=white> from the drupal/recommended-project template! </>",
|
||||
"<bg=blue;fg=white> </>",
|
||||
"",
|
||||
"<bg=yellow;fg=black>Next steps</>:",
|
||||
" * Install the site: https://www.drupal.org/docs/installing-drupal",
|
||||
" * Read the user guide: https://www.drupal.org/docs/user_guide/en/index.html",
|
||||
" * Get support: https://www.drupal.org/support",
|
||||
" * Get involved with the Drupal community:",
|
||||
" https://www.drupal.org/getting-involved",
|
||||
" * Remove the plugin that prints this message:",
|
||||
" composer remove drupal/core-project-message"
|
||||
]
|
||||
}
|
||||
},
|
||||
"require-dev": {
|
||||
"drush/drush": "^13.6"
|
||||
"name": "drupal/recommended-project",
|
||||
"description": "Project template for Drupal projects with a relocated document root",
|
||||
"type": "project",
|
||||
"license": "GPL-2.0-or-later",
|
||||
"homepage": "https://www.drupal.org/project/drupal",
|
||||
"support": {
|
||||
"docs": "https://www.drupal.org/docs/user_guide/en/index.html",
|
||||
"chat": "https://www.drupal.org/node/314178"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "composer",
|
||||
"url": "https://packages.drupal.org/8"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"composer/installers": "^2.3",
|
||||
"cweagans/composer-patches": "^2.0",
|
||||
"drupal/admin_toolbar": "^3.6",
|
||||
"drupal/core-composer-scaffold": "^11.2",
|
||||
"drupal/core-project-message": "^11.2",
|
||||
"drupal/core-recipe-unpack": "^11.2",
|
||||
"drupal/core-recommended": "^11.2",
|
||||
"drupal/decoupled_router": "^2.0",
|
||||
"drupal/devel": "^5.4",
|
||||
"drupal/field_group": "^4.0",
|
||||
"drupal/jsonapi_extras": "^3.27",
|
||||
"drupal/jsonapi_include": "^2.0",
|
||||
"drupal/jsonapi_resources": "^1.3",
|
||||
"drupal/jsonapi_site": "^1.0",
|
||||
"drupal/paragraphs": "^1.19",
|
||||
"drupal/pathauto": "^1.14",
|
||||
"drupal/rest_absolute_urls": "^2.0",
|
||||
"mnsami/composer-custom-directory-installer": "^2.0"
|
||||
},
|
||||
"conflict": {
|
||||
"drupal/drupal": "*"
|
||||
},
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true,
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"composer/installers": true,
|
||||
"cweagans/composer-patches": true,
|
||||
"dealerdirect/phpcodesniffer-composer-installer": true,
|
||||
"drupal/core-composer-scaffold": true,
|
||||
"drupal/core-project-message": true,
|
||||
"drupal/core-recipe-unpack": true,
|
||||
"mnsami/composer-custom-directory-installer": true,
|
||||
"php-http/discovery": true,
|
||||
"php-tuf/composer-integration": true,
|
||||
"phpstan/extension-installer": true
|
||||
},
|
||||
"sort-packages": true
|
||||
},
|
||||
"extra": {
|
||||
"patches": {
|
||||
"drupal/jsonapi_include": {
|
||||
"Issue #3490592: Incompatile with jsonapi_default fix": "https://git.drupalcode.org/project/jsonapi_include/-/merge_requests/28.diff"
|
||||
},
|
||||
"drupal/jsonapi_extras": {
|
||||
"Issue #3373151: Auto include default includes of related entities": "patches/jsonapi_extras_default_includes.patch"
|
||||
}
|
||||
},
|
||||
"drupal-scaffold": {
|
||||
"locations": {
|
||||
"web-root": "web/"
|
||||
}
|
||||
},
|
||||
"installer-paths": {
|
||||
"web/core": [
|
||||
"type:drupal-core"
|
||||
],
|
||||
"web/libraries/{$name}": [
|
||||
"type:drupal-library"
|
||||
],
|
||||
"web/modules/contrib/{$name}": [
|
||||
"type:drupal-module"
|
||||
],
|
||||
"web/profiles/contrib/{$name}": [
|
||||
"type:drupal-profile"
|
||||
],
|
||||
"web/themes/contrib/{$name}": [
|
||||
"type:drupal-theme"
|
||||
],
|
||||
"drush/Commands/contrib/{$name}": [
|
||||
"type:drupal-drush"
|
||||
],
|
||||
"web/modules/custom/{$name}": [
|
||||
"type:drupal-custom-module"
|
||||
],
|
||||
"web/profiles/custom/{$name}": [
|
||||
"type:drupal-custom-profile"
|
||||
],
|
||||
"web/themes/custom/{$name}": [
|
||||
"type:drupal-custom-theme"
|
||||
],
|
||||
"recipes/{$name}": [
|
||||
"type:drupal-recipe"
|
||||
]
|
||||
},
|
||||
"drupal-core-project-message": {
|
||||
"include-keys": [
|
||||
"homepage",
|
||||
"support"
|
||||
],
|
||||
"post-create-project-cmd-message": [
|
||||
"<bg=blue;fg=white> </>",
|
||||
"<bg=blue;fg=white> Congratulations, you’ve installed the Drupal codebase </>",
|
||||
"<bg=blue;fg=white> from the drupal/recommended-project template! </>",
|
||||
"<bg=blue;fg=white> </>",
|
||||
"",
|
||||
"<bg=yellow;fg=black>Next steps</>:",
|
||||
" * Install the site: https://www.drupal.org/docs/installing-drupal",
|
||||
" * Read the user guide: https://www.drupal.org/docs/user_guide/en/index.html",
|
||||
" * Get support: https://www.drupal.org/support",
|
||||
" * Get involved with the Drupal community:",
|
||||
" https://www.drupal.org/getting-involved",
|
||||
" * Remove the plugin that prints this message:",
|
||||
" composer remove drupal/core-project-message"
|
||||
]
|
||||
}
|
||||
},
|
||||
"require-dev": {
|
||||
"drush/drush": "^13.6"
|
||||
}
|
||||
}
|
||||
|
||||
226
composer.lock
generated
226
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "3a0bf64ce33c0bbf042694707e132bf6",
|
||||
"content-hash": "2f8c9e92ce5289579c0ab5ad04f25ec1",
|
||||
"packages": [
|
||||
{
|
||||
"name": "asm89/stack-cors",
|
||||
@@ -1458,6 +1458,66 @@
|
||||
"issues": "https://www.drupal.org/project/issues/ctools"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "drupal/decoupled_router",
|
||||
"version": "2.0.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://git.drupalcode.org/project/decoupled_router.git",
|
||||
"reference": "2.0.6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://ftp.drupal.org/files/projects/decoupled_router-2.0.6.zip",
|
||||
"reference": "2.0.6",
|
||||
"shasum": "93a725c93cac4f5c03c729c3984d926c4c52014e"
|
||||
},
|
||||
"require": {
|
||||
"drupal/core": "^10.1 || ^11",
|
||||
"php": ">=8.1"
|
||||
},
|
||||
"conflict": {
|
||||
"drupal/redirect": "<1.12"
|
||||
},
|
||||
"require-dev": {
|
||||
"drupal/redirect": "dev-1.x"
|
||||
},
|
||||
"type": "drupal-module",
|
||||
"extra": {
|
||||
"drupal": {
|
||||
"version": "2.0.6",
|
||||
"datestamp": "1757356868",
|
||||
"security-coverage": {
|
||||
"status": "covered",
|
||||
"message": "Covered by Drupal's security advisory policy"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packages.drupal.org/8/downloads",
|
||||
"license": [
|
||||
"GPL-2.0-or-later"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Mateu Aguiló Bosch",
|
||||
"homepage": "https://www.drupal.org/user/103796",
|
||||
"email": "mateu.aguilo.bosch@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "e0ipso",
|
||||
"homepage": "https://www.drupal.org/user/550110"
|
||||
},
|
||||
{
|
||||
"name": "mglaman",
|
||||
"homepage": "https://www.drupal.org/user/2416470"
|
||||
}
|
||||
],
|
||||
"description": "Provides an endpoint that will help you resolve path aliases and redirects for entity related routes.",
|
||||
"homepage": "https://www.drupal.org/project/decoupled_router",
|
||||
"support": {
|
||||
"source": "https://git.drupalcode.org/project/decoupled_router"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "drupal/devel",
|
||||
"version": "5.4.0",
|
||||
@@ -1843,6 +1903,112 @@
|
||||
"source": "https://git.drupalcode.org/project/jsonapi_resources"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "drupal/jsonapi_site",
|
||||
"version": "1.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://git.drupalcode.org/project/jsonapi_site.git",
|
||||
"reference": "1.0.2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://ftp.drupal.org/files/projects/jsonapi_site-1.0.2.zip",
|
||||
"reference": "1.0.2",
|
||||
"shasum": "d7d05c2aadb8206c4e6a4dab67f3d061abb23ef8"
|
||||
},
|
||||
"require": {
|
||||
"drupal/core": "^9 || ^10 || ^11",
|
||||
"drupal/key_auth": "*"
|
||||
},
|
||||
"type": "drupal-module",
|
||||
"extra": {
|
||||
"drupal": {
|
||||
"version": "1.0.2",
|
||||
"datestamp": "1714930689",
|
||||
"security-coverage": {
|
||||
"status": "covered",
|
||||
"message": "Covered by Drupal's security advisory policy"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packages.drupal.org/8/downloads",
|
||||
"license": [
|
||||
"GPL-2.0-or-later"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "andrew answer",
|
||||
"homepage": "https://www.drupal.org/user/239127"
|
||||
}
|
||||
],
|
||||
"description": "This module let's you read basic site and theme settings via JSON:API.",
|
||||
"homepage": "https://www.drupal.org/project/jsonapi_site",
|
||||
"support": {
|
||||
"source": "https://git.drupalcode.org/project/jsonapi_site"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "drupal/key_auth",
|
||||
"version": "2.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://git.drupalcode.org/project/key_auth.git",
|
||||
"reference": "2.2.0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://ftp.drupal.org/files/projects/key_auth-2.2.0.zip",
|
||||
"reference": "2.2.0",
|
||||
"shasum": "b76e247d974d69199c55f551850bb2b0feb15a36"
|
||||
},
|
||||
"require": {
|
||||
"drupal/core": "^9.5 || ^10 || ^11"
|
||||
},
|
||||
"type": "drupal-module",
|
||||
"extra": {
|
||||
"drupal": {
|
||||
"version": "2.2.0",
|
||||
"datestamp": "1722170641",
|
||||
"security-coverage": {
|
||||
"status": "covered",
|
||||
"message": "Covered by Drupal's security advisory policy"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packages.drupal.org/8/downloads",
|
||||
"license": [
|
||||
"GPL-2.0-or-later"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Rajeshreeputra Pravin Dhondiba Gaikwad",
|
||||
"homepage": "https://www.drupal.org/u/Rajeshreeputra",
|
||||
"role": "Maintainer"
|
||||
},
|
||||
{
|
||||
"name": "Mike Stefanello",
|
||||
"homepage": "https://www.drupal.org/u/mstef",
|
||||
"role": "Maintainer"
|
||||
},
|
||||
{
|
||||
"name": "Dan Bryant",
|
||||
"homepage": "https://www.drupal.org/u/perfectcube",
|
||||
"role": "Maintainer"
|
||||
},
|
||||
{
|
||||
"name": "Ganesh Suryawanshi",
|
||||
"homepage": "https://www.drupal.org/u/ganeshsurya11",
|
||||
"role": "Maintainer"
|
||||
}
|
||||
],
|
||||
"description": "Provides simple key-based authentication on a per-user basis similar to basic_auth but without requiring usernames or passwords.",
|
||||
"homepage": "https://www.drupal.org/project/key_auth",
|
||||
"support": {
|
||||
"source": "https://git.drupalcode.org/project/key_auth",
|
||||
"issues": "https://www.drupal.org/project/issues/key_auth"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "drupal/paragraphs",
|
||||
"version": "1.19.0",
|
||||
@@ -2819,6 +2985,62 @@
|
||||
},
|
||||
"time": "2025-10-10T12:53:17+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mnsami/composer-custom-directory-installer",
|
||||
"version": "2.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mnsami/composer-custom-directory-installer.git",
|
||||
"reference": "85f66323978d0b1cb0e6acc7f69b3e7b912f82d9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/mnsami/composer-custom-directory-installer/zipball/85f66323978d0b1cb0e6acc7f69b3e7b912f82d9",
|
||||
"reference": "85f66323978d0b1cb0e6acc7f69b3e7b912f82d9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"composer-plugin-api": "^1.0 || ^2.0",
|
||||
"php": ">=5.3"
|
||||
},
|
||||
"type": "composer-plugin",
|
||||
"extra": {
|
||||
"class": [
|
||||
"Composer\\CustomDirectoryInstaller\\LibraryPlugin",
|
||||
"Composer\\CustomDirectoryInstaller\\PearPlugin",
|
||||
"Composer\\CustomDirectoryInstaller\\PluginPlugin"
|
||||
],
|
||||
"branch-alias": {
|
||||
"dev-master": "1.1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Composer\\CustomDirectoryInstaller": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Mina Nabil Sami",
|
||||
"email": "mina.nsami@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "A composer plugin, to help install packages of different types in custom paths.",
|
||||
"keywords": [
|
||||
"composer",
|
||||
"composer-installer",
|
||||
"composer-plugin"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/mnsami/composer-custom-directory-installer/issues",
|
||||
"source": "https://github.com/mnsami/composer-custom-directory-installer/tree/2.0.0"
|
||||
},
|
||||
"time": "2020-08-18T11:00:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pear/archive_tar",
|
||||
"version": "1.5.0",
|
||||
@@ -7702,5 +7924,5 @@
|
||||
"prefer-lowest": false,
|
||||
"platform": {},
|
||||
"platform-dev": {},
|
||||
"plugin-api-version": "2.6.0"
|
||||
"plugin-api-version": "2.9.0"
|
||||
}
|
||||
|
||||
32
config.fsx
Executable file
32
config.fsx
Executable file
@@ -0,0 +1,32 @@
|
||||
#r "_lib/Fornax.Core.dll"
|
||||
|
||||
open Config
|
||||
open System.IO
|
||||
|
||||
let staticPredicate (projectRoot: string, page: string) =
|
||||
let ext = Path.GetExtension page
|
||||
let fileShouldBeExcluded =
|
||||
ext = ".fsx" ||
|
||||
ext = ".fs" ||
|
||||
ext = ".md" ||
|
||||
ext = ".fsproj" ||
|
||||
page.Contains ".config" ||
|
||||
page.Contains "src" ||
|
||||
page.Contains "obj" ||
|
||||
page.Contains "_public" ||
|
||||
page.Contains "_bin" ||
|
||||
page.Contains "_lib" ||
|
||||
page.Contains "_data" ||
|
||||
page.Contains "_settings" ||
|
||||
page.Contains "_config.yml" ||
|
||||
page.Contains ".sass-cache" ||
|
||||
page.Contains ".git" ||
|
||||
page.Contains ".ionide"
|
||||
fileShouldBeExcluded |> not
|
||||
|
||||
|
||||
let config = {
|
||||
Generators = [
|
||||
{Script = "basicpagegenerator.fsx"; Trigger = Once; OutputFile = MultipleFiles id }
|
||||
]
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"fornax": {
|
||||
"version": "0.16.0",
|
||||
"commands": [
|
||||
"fornax"
|
||||
],
|
||||
"rollForward": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
#r "_lib/Fornax.Core.dll"
|
||||
|
||||
open Config
|
||||
open System.IO
|
||||
|
||||
let postPredicate (projectRoot: string, page: string) =
|
||||
let fileName = Path.Combine(projectRoot,page)
|
||||
let ext = Path.GetExtension page
|
||||
if ext = ".md" then
|
||||
let ctn = File.ReadAllText fileName
|
||||
page.Contains("_public") |> not
|
||||
&& ctn.Contains("layout: post")
|
||||
else
|
||||
false
|
||||
|
||||
let staticPredicate (projectRoot: string, page: string) =
|
||||
let ext = Path.GetExtension page
|
||||
let fileShouldBeExcluded =
|
||||
ext = ".fsx" ||
|
||||
ext = ".fs" ||
|
||||
ext = ".md" ||
|
||||
ext = ".fsproj" ||
|
||||
page.Contains ".config" ||
|
||||
page.Contains "src" ||
|
||||
page.Contains "obj" ||
|
||||
page.Contains "_public" ||
|
||||
page.Contains "_bin" ||
|
||||
page.Contains "_lib" ||
|
||||
page.Contains "_data" ||
|
||||
page.Contains "_settings" ||
|
||||
page.Contains "_config.yml" ||
|
||||
page.Contains ".sass-cache" ||
|
||||
page.Contains ".git" ||
|
||||
page.Contains ".ionide"
|
||||
fileShouldBeExcluded |> not
|
||||
|
||||
|
||||
let config = {
|
||||
Generators = [
|
||||
{Script = "less.fsx"; Trigger = OnFileExt ".less"; OutputFile = ChangeExtension "css" }
|
||||
{Script = "sass.fsx"; Trigger = OnFileExt ".scss"; OutputFile = ChangeExtension "css" }
|
||||
{Script = "post.fsx"; Trigger = OnFilePredicate postPredicate; OutputFile = ChangeExtension "html" }
|
||||
{Script = "staticfile.fsx"; Trigger = OnFilePredicate staticPredicate; OutputFile = SameFileName }
|
||||
{Script = "index.fsx"; Trigger = Once; OutputFile = MultipleFiles id }
|
||||
{Script = "about.fsx"; Trigger = Once; OutputFile = NewFileName "about.html" }
|
||||
{Script = "contact.fsx"; Trigger = Once; OutputFile = NewFileName "contact.html" }
|
||||
{Script = "basicpage.fsx"; Trigger = Once; OutputFile = MultipleFiles id }
|
||||
]
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="Program.fs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FSharp.Data" Version="6.6.0" />
|
||||
<PackageReference Include="FSharp.SystemTextJson" Version="1.4.36" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,40 +0,0 @@
|
||||
#r "../_lib/Fornax.Core.dll"
|
||||
#load "layout.fsx"
|
||||
|
||||
open Html
|
||||
|
||||
let about = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi nisi diam, vehicula quis blandit id, suscipit sed libero. Proin at diam dolor. In hac habitasse platea dictumst. Donec quis dui vitae quam eleifend dignissim non sed libero. In hac habitasse platea dictumst. In ullamcorper mollis risus, a vulputate quam accumsan at. Donec sed felis sodales, blandit orci id, vulputate orci."
|
||||
|
||||
let generate' (ctx : SiteContents) (_: string) =
|
||||
let siteInfo = ctx.TryGetValue<Globalloader.SiteInfo> ()
|
||||
let desc =
|
||||
siteInfo
|
||||
|> Option.map (fun si -> si.description)
|
||||
|> Option.defaultValue ""
|
||||
|
||||
|
||||
Layout.layout ctx "About" [
|
||||
section [Class "hero is-info is-medium is-bold"] [
|
||||
div [Class "hero-body"] [
|
||||
div [Class "container has-text-centered"] [
|
||||
h1 [Class "title"] [!!desc]
|
||||
]
|
||||
]
|
||||
]
|
||||
div [Class "container"] [
|
||||
section [Class "articles"] [
|
||||
div [Class "column is-8 is-offset-2"] [
|
||||
div [Class "card article"] [
|
||||
div [Class "card-content"] [
|
||||
div [Class "content article-body"] [
|
||||
!! about
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]]
|
||||
|
||||
let generate (ctx : SiteContents) (projectRoot: string) (page: string) =
|
||||
generate' ctx page
|
||||
|> Layout.render ctx
|
||||
@@ -1,60 +0,0 @@
|
||||
#r "../_lib/Fornax.Core.dll"
|
||||
#r "../src/Types/bin/Debug/netstandard2.0/Types.dll"
|
||||
#load "./layout.fsx"
|
||||
|
||||
open Html
|
||||
open Types.Types
|
||||
|
||||
let generate' (ctx: SiteContents) (_: string) =
|
||||
let basicPageContent = ctx.TryGetValues<DocData>() |> Option.defaultValue Seq.empty
|
||||
let siteUrl = "http://oceanbox.ddev.site"
|
||||
let paragraphLayout (paragraph: Paragraph) =
|
||||
match paragraph with
|
||||
| Hero hero ->
|
||||
div [Class "hero"] [
|
||||
h1 [Class "header"] [!! hero.FieldTitle]
|
||||
h2 [Class "sub-header"] [!! hero.FieldSubtitle]
|
||||
match hero.FieldBackgroundImage with
|
||||
| MediaVideo video ->
|
||||
let videoUrl = siteUrl + video.FieldMediaVideoFile.Uri.Url
|
||||
div [] [
|
||||
p [Class "video"] [!! videoUrl]
|
||||
]
|
||||
| MediaImage image ->
|
||||
let imgUrl = siteUrl + image.FieldMediaImage.Uri.Url
|
||||
div [] [
|
||||
img [Src imgUrl ; Alt image.FieldMediaImage.Meta.Alt ]
|
||||
]
|
||||
]
|
||||
| SimpleText simpletext ->
|
||||
// eprintfn "Simple text field body: %s" simpletext.attrs.field_body
|
||||
div [Class "text"] [!! simpletext.FieldBody]
|
||||
| Ignore ->
|
||||
div [Class "ignored"] []
|
||||
|
||||
let pageContentLayout (title, paragraphs) =
|
||||
let processedParagraphs =
|
||||
paragraphs |> Array.map paragraphLayout
|
||||
|
||||
Layout.layout ctx title [
|
||||
div [Class "container"] [
|
||||
!! title
|
||||
div [Class "content"] (processedParagraphs |> Array.toList)
|
||||
]
|
||||
]
|
||||
|
||||
let processTitle (title: string) =
|
||||
title.ToLower().Replace (" ", "-") + ".html"
|
||||
|
||||
let processedContent =
|
||||
basicPageContent
|
||||
|> Seq.toList
|
||||
|> List.map(fun content ->
|
||||
processTitle (content.Title),
|
||||
pageContentLayout (content.Title, content.FieldContent) |> Layout.render ctx
|
||||
)
|
||||
|
||||
processedContent
|
||||
|
||||
let generate (ctx: SiteContents) (projectRoot: string) (page:string) =
|
||||
generate' ctx page
|
||||
@@ -1,40 +0,0 @@
|
||||
#r "../_lib/Fornax.Core.dll"
|
||||
#load "layout.fsx"
|
||||
|
||||
open Html
|
||||
|
||||
let about = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi nisi diam, vehicula quis blandit id, suscipit sed libero. Proin at diam dolor. In hac habitasse platea dictumst. Donec quis dui vitae quam eleifend dignissim non sed libero. In hac habitasse platea dictumst. In ullamcorper mollis risus, a vulputate quam accumsan at. Donec sed felis sodales, blandit orci id, vulputate orci."
|
||||
|
||||
let generate' (ctx : SiteContents) (_: string) =
|
||||
let siteInfo = ctx.TryGetValue<Globalloader.SiteInfo> ()
|
||||
let desc =
|
||||
siteInfo
|
||||
|> Option.map (fun si -> si.description)
|
||||
|> Option.defaultValue ""
|
||||
|
||||
|
||||
Layout.layout ctx "Home" [
|
||||
section [Class "hero is-info is-medium is-bold"] [
|
||||
div [Class "hero-body"] [
|
||||
div [Class "container has-text-centered"] [
|
||||
h1 [Class "title"] [!!desc]
|
||||
]
|
||||
]
|
||||
]
|
||||
div [Class "container"] [
|
||||
section [Class "articles"] [
|
||||
div [Class "column is-8 is-offset-2"] [
|
||||
div [Class "card article"] [
|
||||
div [Class "card-content"] [
|
||||
div [Class "content article-body"] [
|
||||
!! about
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]]
|
||||
|
||||
let generate (ctx : SiteContents) (projectRoot: string) (page: string) =
|
||||
generate' ctx page
|
||||
|> Layout.render ctx
|
||||
@@ -1,67 +0,0 @@
|
||||
#r "../_lib/Fornax.Core.dll"
|
||||
#load "layout.fsx"
|
||||
|
||||
open Html
|
||||
|
||||
let generate' (ctx : SiteContents) (_: string) =
|
||||
let posts = ctx.TryGetValues<Postloader.Post> () |> Option.defaultValue Seq.empty
|
||||
let siteInfo = ctx.TryGetValue<Globalloader.SiteInfo> ()
|
||||
let desc, postPageSize =
|
||||
siteInfo
|
||||
|> Option.map (fun si -> si.description, si.postPageSize)
|
||||
|> Option.defaultValue ("", 10)
|
||||
|
||||
|
||||
let psts =
|
||||
posts
|
||||
|> Seq.sortByDescending Layout.published
|
||||
|> Seq.toList
|
||||
|> List.chunkBySize postPageSize
|
||||
|> List.map (List.map (Layout.postLayout true))
|
||||
|
||||
let pages = List.length psts
|
||||
|
||||
let getFilenameForIndex i =
|
||||
if i = 0 then
|
||||
sprintf "index.html"
|
||||
else
|
||||
sprintf "posts/page%i.html" i
|
||||
|
||||
let layoutForPostSet i psts =
|
||||
let nextPage =
|
||||
if i = (pages - 1) then "#"
|
||||
else "/" + getFilenameForIndex (i + 1)
|
||||
|
||||
let previousPage =
|
||||
if i = 0 then "#"
|
||||
else "/" + getFilenameForIndex (i - 1)
|
||||
|
||||
Layout.layout ctx "Home" [
|
||||
section [Class "hero is-info is-medium is-bold"] [
|
||||
div [Class "hero-body"] [
|
||||
div [Class "container has-text-centered"] [
|
||||
h1 [Class "title"] [!!desc]
|
||||
]
|
||||
]
|
||||
]
|
||||
div [Class "container"] [
|
||||
section [Class "articles"] [
|
||||
div [Class "column is-8 is-offset-2"] psts
|
||||
]
|
||||
]
|
||||
div [Class "container"] [
|
||||
div [Class "container has-text-centered"] [
|
||||
a [Href previousPage] [!! "Previous"]
|
||||
!! (sprintf "%i of %i" (i + 1) pages)
|
||||
a [Href nextPage] [!! "Next"]
|
||||
]
|
||||
]]
|
||||
|
||||
psts
|
||||
|> List.mapi (fun i psts ->
|
||||
getFilenameForIndex i,
|
||||
layoutForPostSet i psts
|
||||
|> Layout.render ctx)
|
||||
|
||||
let generate (ctx : SiteContents) (projectRoot: string) (page: string) =
|
||||
generate' ctx page
|
||||
@@ -1,106 +0,0 @@
|
||||
#r "../_lib/Fornax.Core.dll"
|
||||
#if !FORNAX
|
||||
#load "../loaders/postloader.fsx"
|
||||
#load "../loaders/pageloader.fsx"
|
||||
#load "../loaders/globalloader.fsx"
|
||||
#endif
|
||||
|
||||
open Html
|
||||
|
||||
let injectWebsocketCode (webpage:string) =
|
||||
let websocketScript =
|
||||
"""
|
||||
<script type="text/javascript">
|
||||
var wsUri = "ws://localhost:8080/websocket";
|
||||
function init()
|
||||
{
|
||||
websocket = new WebSocket(wsUri);
|
||||
websocket.onclose = function(evt) { onClose(evt) };
|
||||
}
|
||||
function onClose(evt)
|
||||
{
|
||||
console.log('closing');
|
||||
websocket.close();
|
||||
document.location.reload();
|
||||
}
|
||||
window.addEventListener("load", init, false);
|
||||
</script>
|
||||
"""
|
||||
let head = "<head>"
|
||||
let index = webpage.IndexOf head
|
||||
webpage.Insert ( (index + head.Length + 1),websocketScript)
|
||||
|
||||
let layout (ctx : SiteContents) active bodyCnt =
|
||||
let pages = ctx.TryGetValues<Pageloader.Page> () |> Option.defaultValue Seq.empty
|
||||
let siteInfo = ctx.TryGetValue<Globalloader.SiteInfo> ()
|
||||
let ttl =
|
||||
siteInfo
|
||||
|> Option.map (fun si -> si.title)
|
||||
|> Option.defaultValue ""
|
||||
|
||||
let menuEntries =
|
||||
pages
|
||||
|> Seq.map (fun p ->
|
||||
let cls = if p.title = active then "navbar-item is-active" else "navbar-item"
|
||||
a [Class cls; Href p.link] [!! p.title ])
|
||||
|> Seq.toList
|
||||
|
||||
html [] [
|
||||
head [] [
|
||||
meta [CharSet "utf-8"]
|
||||
meta [Name "viewport"; Content "width=device-width, initial-scale=1"]
|
||||
title [] [!! ttl]
|
||||
link [Rel "icon"; Type "image/png"; Sizes "32x32"; Href "/images/favicon.png"]
|
||||
link [Rel "stylesheet"; Href "https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"]
|
||||
link [Rel "stylesheet"; Href "https://fonts.googleapis.com/css?family=Open+Sans"]
|
||||
link [Rel "stylesheet"; Href "https://unpkg.com/bulma@0.8.0/css/bulma.min.css"]
|
||||
link [Rel "stylesheet"; Type "text/css"; Href "/style/style.css"]
|
||||
|
||||
]
|
||||
body [] [
|
||||
nav [Class "navbar"] [
|
||||
div [Class "container"] [
|
||||
div [Class "navbar-brand"] [
|
||||
a [Class "navbar-item"; Href "/"] [
|
||||
img [Src "/images/bulma.png"; Alt "Logo"]
|
||||
]
|
||||
span [Class "navbar-burger burger"; HtmlProperties.Custom ("data-target", "navbarMenu")] [
|
||||
span [] []
|
||||
span [] []
|
||||
span [] []
|
||||
]
|
||||
]
|
||||
div [Id "navbarMenu"; Class "navbar-menu"] menuEntries
|
||||
]
|
||||
]
|
||||
yield! bodyCnt
|
||||
]
|
||||
]
|
||||
|
||||
let render (ctx : SiteContents) cnt =
|
||||
let disableLiveRefresh = ctx.TryGetValue<Postloader.PostConfig> () |> Option.map (fun n -> n.disableLiveRefresh) |> Option.defaultValue false
|
||||
cnt
|
||||
|> HtmlElement.ToString
|
||||
|> fun n -> if disableLiveRefresh then n else injectWebsocketCode n
|
||||
|
||||
let published (post: Postloader.Post) =
|
||||
post.published
|
||||
|> Option.defaultValue System.DateTime.Now
|
||||
|> fun n -> n.ToString("yyyy-MM-dd")
|
||||
|
||||
let postLayout (useSummary: bool) (post: Postloader.Post) =
|
||||
div [Class "card article"] [
|
||||
div [Class "card-content"] [
|
||||
div [Class "media-content has-text-centered"] [
|
||||
p [Class "title article-title"; ] [ a [Href post.link] [!! post.title]]
|
||||
p [Class "subtitle is-6 article-subtitle"] [
|
||||
a [Href "#"] [!! (defaultArg post.author "")]
|
||||
!! (sprintf "on %s" (published post))
|
||||
]
|
||||
]
|
||||
div [Class "content article-body"] [
|
||||
!! (if useSummary then post.summary else post.content)
|
||||
|
||||
]
|
||||
]
|
||||
]
|
||||
@@ -1,38 +0,0 @@
|
||||
#r "../_lib/Fornax.Core.dll"
|
||||
#load "layout.fsx"
|
||||
|
||||
open Html
|
||||
|
||||
|
||||
let generate' (ctx : SiteContents) (page: string) =
|
||||
let post =
|
||||
ctx.TryGetValues<Postloader.Post> ()
|
||||
|> Option.defaultValue Seq.empty
|
||||
|> Seq.find (fun n -> n.file = page)
|
||||
|
||||
let siteInfo = ctx.TryGetValue<Globalloader.SiteInfo> ()
|
||||
let desc =
|
||||
siteInfo
|
||||
|> Option.map (fun si -> si.description)
|
||||
|> Option.defaultValue ""
|
||||
|
||||
Layout.layout ctx post.title [
|
||||
section [Class "hero is-info is-medium is-bold"] [
|
||||
div [Class "hero-body"] [
|
||||
div [Class "container has-text-centered"] [
|
||||
h1 [Class "title"] [!!desc]
|
||||
]
|
||||
]
|
||||
]
|
||||
div [Class "container"] [
|
||||
section [Class "articles"] [
|
||||
div [Class "column is-8 is-offset-2"] [
|
||||
Layout.postLayout false post
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
let generate (ctx : SiteContents) (projectRoot: string) (page: string) =
|
||||
generate' ctx page
|
||||
|> Layout.render ctx
|
||||
@@ -1,7 +0,0 @@
|
||||
#r "../_lib/Fornax.Core.dll"
|
||||
|
||||
open System.IO
|
||||
|
||||
let generate (ctx : SiteContents) (projectRoot: string) (page: string) =
|
||||
let inputPath = Path.Combine(projectRoot, page)
|
||||
File.ReadAllBytes inputPath
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 41 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 482 B |
@@ -1,66 +0,0 @@
|
||||
|
||||
#r "../_lib/Fornax.Core.dll"
|
||||
#r "../src/Types/bin/Debug/netstandard2.0/Types.dll"
|
||||
#r "nuget: FSharp.Data"
|
||||
#r "nuget: FSharp.SystemTextJson"
|
||||
|
||||
open FSharp.Data
|
||||
open System.Text.Json
|
||||
open System.Text.Json.Serialization
|
||||
open Types.Types
|
||||
|
||||
let baseUrl = "http://oceanbox.ddev.site/jsonapi/node/page"
|
||||
|
||||
type LinkHref = {
|
||||
Href: string
|
||||
}
|
||||
|
||||
type PaginationLinks = {
|
||||
Next: Skippable<LinkHref>
|
||||
}
|
||||
|
||||
type Content = {
|
||||
Data: DocData[]
|
||||
Links: PaginationLinks
|
||||
}
|
||||
|
||||
let options =
|
||||
let opts =
|
||||
JsonFSharpOptions.Default()
|
||||
.WithAllowNullFields(true)
|
||||
.WithUnionInternalTag()
|
||||
.WithUnionNamedFields()
|
||||
.WithUnionUnwrapRecordCases()
|
||||
.WithUnionTagName("type")
|
||||
.ToJsonSerializerOptions()
|
||||
|
||||
opts.PropertyNamingPolicy <- JsonNamingPolicy.SnakeCaseLower
|
||||
|
||||
opts
|
||||
|
||||
let deserialize (str: string) =
|
||||
JsonSerializer.Deserialize<Content>(str,options)
|
||||
|
||||
let fetchPage (url: string): Content =
|
||||
Http.RequestString url |> deserialize
|
||||
|
||||
let rec fetchAllPages (url: string) (acc: DocData[]) =
|
||||
let page = fetchPage url
|
||||
let acc' = Array.append acc page.Data
|
||||
|
||||
match page.Links.Next with
|
||||
| Include nextUrl when not (System.String.IsNullOrWhiteSpace nextUrl.Href) ->
|
||||
fetchAllPages nextUrl.Href acc'
|
||||
| _ -> acc'
|
||||
|
||||
let loader (projectRoot: string) (siteContent: SiteContents) =
|
||||
// TODO - Update to load using pagination
|
||||
|
||||
let json = fetchAllPages baseUrl [||]
|
||||
|
||||
// eprintfn "%A" json
|
||||
json
|
||||
|> Array.iter siteContent.Add
|
||||
|
||||
siteContent
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
#r "../_lib/Fornax.Core.dll"
|
||||
|
||||
type SiteInfo = {
|
||||
title: string
|
||||
description: string
|
||||
postPageSize: int
|
||||
}
|
||||
|
||||
let loader (projectRoot: string) (siteContent: SiteContents) =
|
||||
let siteInfo =
|
||||
{ title = "Sample Fornax blog";
|
||||
description = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
|
||||
postPageSize = 5 }
|
||||
siteContent.Add(siteInfo)
|
||||
|
||||
siteContent
|
||||
@@ -1,13 +0,0 @@
|
||||
#r "../_lib/Fornax.Core.dll"
|
||||
|
||||
type Page = {
|
||||
title: string
|
||||
link: string
|
||||
}
|
||||
|
||||
let loader (projectRoot: string) (siteContent: SiteContents) =
|
||||
siteContent.Add({title = "Home"; link = "/"})
|
||||
siteContent.Add({title = "About"; link = "/about.html"})
|
||||
siteContent.Add({title = "Contact"; link = "/contact.html"})
|
||||
|
||||
siteContent
|
||||
@@ -1,129 +0,0 @@
|
||||
#r "../_lib/Fornax.Core.dll"
|
||||
#r "../_lib/Markdig.dll"
|
||||
|
||||
open System.IO
|
||||
open Markdig
|
||||
|
||||
type PostConfig = {
|
||||
disableLiveRefresh: bool
|
||||
}
|
||||
type Post = {
|
||||
file: string
|
||||
link : string
|
||||
title: string
|
||||
author: string option
|
||||
published: System.DateTime option
|
||||
tags: string list
|
||||
content: string
|
||||
summary: string
|
||||
}
|
||||
|
||||
let contentDir = "posts"
|
||||
|
||||
let markdownPipeline =
|
||||
MarkdownPipelineBuilder()
|
||||
.UsePipeTables()
|
||||
.UseGridTables()
|
||||
.Build()
|
||||
|
||||
let isSeparator (input : string) =
|
||||
input.StartsWith "---"
|
||||
|
||||
let isSummarySeparator (input: string) =
|
||||
input.Contains "<!--more-->"
|
||||
|
||||
|
||||
///`fileContent` - content of page to parse. Usually whole content of `.md` file
|
||||
///returns content of config that should be used for the page
|
||||
let getConfig (fileContent : string) =
|
||||
let fileContent = fileContent.Split '\n'
|
||||
let fileContent = fileContent |> Array.skip 1 //First line must be ---
|
||||
let indexOfSeperator = fileContent |> Array.findIndex isSeparator
|
||||
let splitKey (line: string) =
|
||||
let seperatorIndex = line.IndexOf(':')
|
||||
if seperatorIndex > 0 then
|
||||
let key = line.[.. seperatorIndex - 1].Trim().ToLower()
|
||||
let value = line.[seperatorIndex + 1 ..].Trim()
|
||||
Some(key, value)
|
||||
else
|
||||
None
|
||||
fileContent
|
||||
|> Array.splitAt indexOfSeperator
|
||||
|> fst
|
||||
|> Seq.choose splitKey
|
||||
|> Map.ofSeq
|
||||
|
||||
///`fileContent` - content of page to parse. Usually whole content of `.md` file
|
||||
///returns HTML version of content of the page
|
||||
let getContent (fileContent : string) =
|
||||
let fileContent = fileContent.Split '\n'
|
||||
let fileContent = fileContent |> Array.skip 1 //First line must be ---
|
||||
let indexOfSeperator = fileContent |> Array.findIndex isSeparator
|
||||
let _, content = fileContent |> Array.splitAt indexOfSeperator
|
||||
|
||||
let summary, content =
|
||||
match content |> Array.tryFindIndex isSummarySeparator with
|
||||
| Some indexOfSummary ->
|
||||
let summary, _ = content |> Array.splitAt indexOfSummary
|
||||
summary, content
|
||||
| None ->
|
||||
content, content
|
||||
|
||||
let summary = summary |> Array.skip 1 |> String.concat "\n"
|
||||
let content = content |> Array.skip 1 |> String.concat "\n"
|
||||
|
||||
Markdown.ToHtml(summary, markdownPipeline),
|
||||
Markdown.ToHtml(content, markdownPipeline)
|
||||
|
||||
let trimString (str : string) =
|
||||
str.Trim().TrimEnd('"').TrimStart('"')
|
||||
|
||||
let loadFile (rootDir: string) (n: string) =
|
||||
let text = File.ReadAllText n
|
||||
|
||||
let config = getConfig text
|
||||
let summary, content = getContent text
|
||||
|
||||
let chopLength =
|
||||
if rootDir.EndsWith(Path.DirectorySeparatorChar) then rootDir.Length
|
||||
else rootDir.Length + 1
|
||||
|
||||
let dirPart =
|
||||
n
|
||||
|> Path.GetDirectoryName
|
||||
|> fun x -> x.[chopLength .. ]
|
||||
|
||||
let file = Path.Combine(dirPart, (n |> Path.GetFileNameWithoutExtension) + ".md").Replace("\\", "/")
|
||||
let link = "/" + Path.Combine(dirPart, (n |> Path.GetFileNameWithoutExtension) + ".html").Replace("\\", "/")
|
||||
|
||||
let title = config |> Map.find "title" |> trimString
|
||||
let author = config |> Map.tryFind "author" |> Option.map trimString
|
||||
let published = config |> Map.tryFind "published" |> Option.map (trimString >> System.DateTime.Parse)
|
||||
|
||||
let tags =
|
||||
let tagsOpt =
|
||||
config
|
||||
|> Map.tryFind "tags"
|
||||
|> Option.map (trimString >> fun n -> n.Split ',' |> Array.toList)
|
||||
defaultArg tagsOpt []
|
||||
|
||||
{ file = file
|
||||
link = link
|
||||
title = title
|
||||
author = author
|
||||
published = published
|
||||
tags = tags
|
||||
content = content
|
||||
summary = summary }
|
||||
|
||||
let loader (projectRoot: string) (siteContent: SiteContents) =
|
||||
let postsPath = Path.Combine(projectRoot, contentDir)
|
||||
let options = EnumerationOptions(RecurseSubdirectories = true)
|
||||
let files = Directory.GetFiles(postsPath, "*", options)
|
||||
files
|
||||
|> Array.filter (fun n -> n.EndsWith ".md")
|
||||
|> Array.map (loadFile projectRoot)
|
||||
|> Array.iter siteContent.Add
|
||||
|
||||
siteContent.Add({disableLiveRefresh = false})
|
||||
siteContent
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
layout: post
|
||||
title: Some nice post title
|
||||
author: @k_cieslak
|
||||
published: 2020-02-19
|
||||
---
|
||||
# Introduction
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi nisi diam, vehicula quis blandit id, suscipit sed libero. Proin at diam dolor. In hac habitasse platea dictumst. Donec quis dui vitae quam eleifend dignissim non sed libero. In hac habitasse platea dictumst. In ullamcorper mollis risus, a vulputate quam accumsan at. Donec sed felis sodales, blandit orci id, vulputate orci.
|
||||
|
||||
<!--more-->
|
||||
|
||||
Phasellus aliquam tellus eu augue vulputate laoreet. Nunc tincidunt sed mauris eu vestibulum. Quisque id ex eget erat elementum euismod vel nec ex. Nunc et blandit neque. Duis erat ex, facilisis non consectetur sit amet, consectetur mattis ex. Vestibulum quis ligula pharetra, semper nibh nec, porta augue. In placerat auctor risus, eu dictum purus iaculis et. Vivamus viverra sollicitudin augue, in sollicitudin leo malesuada non.
|
||||
|
||||
In hac habitasse platea dictumst. Quisque a diam egestas, ornare felis quis, gravida arcu. In vel tellus facilisis, rhoncus ligula sit amet, feugiat massa. Interdum et malesuada fames ac ante ipsum primis in faucibus. Curabitur varius interdum dolor, ut pretium augue egestas id. Aenean vulputate commodo nibh tristique egestas. Interdum et malesuada fames ac ante ipsum primis in faucibus. Vivamus elementum non mi sit amet lacinia.
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
layout: post
|
||||
title: Some other post title
|
||||
author: @k_cieslak
|
||||
published: 2020-02-20
|
||||
---
|
||||
# Something else
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi nisi diam, vehicula quis blandit id, suscipit sed libero. Proin at diam dolor. In hac habitasse platea dictumst. Donec quis dui vitae quam eleifend dignissim non sed libero. In hac habitasse platea dictumst. In ullamcorper mollis risus, a vulputate quam accumsan at. Donec sed felis sodales, blandit orci id, vulputate orci.
|
||||
|
||||
<!--more-->
|
||||
|
||||
In est nulla, ornare vitae elit sed, consequat sollicitudin dui. Duis posuere nulla malesuada elit volutpat ultricies. Mauris et tellus tortor. In ligula elit, pellentesque eget est et, mattis rhoncus nisl. Sed orci ex, mollis quis justo eu, dapibus tincidunt turpis. Sed rhoncus odio non lacus ullamcorper volutpat. Suspendisse blandit ullamcorper condimentum. Quisque et viverra nisi. Vivamus in mollis nulla. Nulla faucibus sed ligula et blandit.
|
||||
|
||||
Vivamus nec libero faucibus, cursus ex et, consequat mauris. Pellentesque commodo ullamcorper vestibulum. Donec efficitur, ipsum et dapibus varius, purus mauris gravida augue, eu mattis lorem turpis eget dui. Curabitur nibh erat, posuere sed eros a, blandit venenatis risus. Vestibulum cursus imperdiet ultrices. In eu efficitur dui, eget tempus enim. Nunc imperdiet, enim et sagittis lacinia, lacus metus eleifend purus, blandit pellentesque leo ante ac velit. Nam ac sagittis est. Cras accumsan, odio vel lacinia mollis, metus tortor malesuada nisi, et consectetur neque quam in erat. Sed ultricies aliquam hendrerit. Etiam non aliquam ipsum, id rutrum magna. Morbi id tincidunt mauris. Vestibulum nec iaculis massa. Etiam fringilla, orci quis faucibus vulputate, risus nibh finibus nisl, et vehicula ipsum leo a tortor. Curabitur mauris elit, bibendum vitae velit at, dignissim ornare arcu.
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
layout: post
|
||||
title: On this great day
|
||||
author: @k_cieslak
|
||||
published: 2020-03-20
|
||||
---
|
||||
# On this great day
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi nisi diam, vehicula quis blandit id, suscipit sed libero. Proin at diam dolor. In hac habitasse platea dictumst. Donec quis dui vitae quam eleifend dignissim non sed libero. In hac habitasse platea dictumst. In ullamcorper mollis risus, a vulputate quam accumsan at. Donec sed felis sodales, blandit orci id, vulputate orci.
|
||||
|
||||
<!--more-->
|
||||
|
||||
In est nulla, ornare vitae elit sed, consequat sollicitudin dui. Duis posuere nulla malesuada elit volutpat ultricies. Mauris et tellus tortor. In ligula elit, pellentesque eget est et, mattis rhoncus nisl. Sed orci ex, mollis quis justo eu, dapibus tincidunt turpis. Sed rhoncus odio non lacus ullamcorper volutpat. Suspendisse blandit ullamcorper condimentum. Quisque et viverra nisi. Vivamus in mollis nulla. Nulla faucibus sed ligula et blandit.
|
||||
|
||||
Vivamus nec libero faucibus, cursus ex et, consequat mauris. Pellentesque commodo ullamcorper vestibulum. Donec efficitur, ipsum et dapibus varius, purus mauris gravida augue, eu mattis lorem turpis eget dui. Curabitur nibh erat, posuere sed eros a, blandit venenatis risus. Vestibulum cursus imperdiet ultrices. In eu efficitur dui, eget tempus enim. Nunc imperdiet, enim et sagittis lacinia, lacus metus eleifend purus, blandit pellentesque leo ante ac velit. Nam ac sagittis est. Cras accumsan, odio vel lacinia mollis, metus tortor malesuada nisi, et consectetur neque quam in erat. Sed ultricies aliquam hendrerit. Etiam non aliquam ipsum, id rutrum magna. Morbi id tincidunt mauris. Vestibulum nec iaculis massa. Etiam fringilla, orci quis faucibus vulputate, risus nibh finibus nisl, et vehicula ipsum leo a tortor. Curabitur mauris elit, bibendum vitae velit at, dignissim ornare arcu.
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
layout: post
|
||||
title: We learnt
|
||||
author: @k_cieslak
|
||||
published: 2020-03-25
|
||||
---
|
||||
# We learnt
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi nisi diam, vehicula quis blandit id, suscipit sed libero. Proin at diam dolor. In hac habitasse platea dictumst. Donec quis dui vitae quam eleifend dignissim non sed libero. In hac habitasse platea dictumst. In ullamcorper mollis risus, a vulputate quam accumsan at. Donec sed felis sodales, blandit orci id, vulputate orci.
|
||||
|
||||
<!--more-->
|
||||
|
||||
In est nulla, ornare vitae elit sed, consequat sollicitudin dui. Duis posuere nulla malesuada elit volutpat ultricies. Mauris et tellus tortor. In ligula elit, pellentesque eget est et, mattis rhoncus nisl. Sed orci ex, mollis quis justo eu, dapibus tincidunt turpis. Sed rhoncus odio non lacus ullamcorper volutpat. Suspendisse blandit ullamcorper condimentum. Quisque et viverra nisi. Vivamus in mollis nulla. Nulla faucibus sed ligula et blandit.
|
||||
|
||||
Vivamus nec libero faucibus, cursus ex et, consequat mauris. Pellentesque commodo ullamcorper vestibulum. Donec efficitur, ipsum et dapibus varius, purus mauris gravida augue, eu mattis lorem turpis eget dui. Curabitur nibh erat, posuere sed eros a, blandit venenatis risus. Vestibulum cursus imperdiet ultrices. In eu efficitur dui, eget tempus enim. Nunc imperdiet, enim et sagittis lacinia, lacus metus eleifend purus, blandit pellentesque leo ante ac velit. Nam ac sagittis est. Cras accumsan, odio vel lacinia mollis, metus tortor malesuada nisi, et consectetur neque quam in erat. Sed ultricies aliquam hendrerit. Etiam non aliquam ipsum, id rutrum magna. Morbi id tincidunt mauris. Vestibulum nec iaculis massa. Etiam fringilla, orci quis faucibus vulputate, risus nibh finibus nisl, et vehicula ipsum leo a tortor. Curabitur mauris elit, bibendum vitae velit at, dignissim ornare arcu.
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
layout: post
|
||||
title: All about
|
||||
author: @k_cieslak
|
||||
published: 2020-04-21
|
||||
---
|
||||
# All about
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi nisi diam, vehicula quis blandit id, suscipit sed libero. Proin at diam dolor. In hac habitasse platea dictumst. Donec quis dui vitae quam eleifend dignissim non sed libero. In hac habitasse platea dictumst. In ullamcorper mollis risus, a vulputate quam accumsan at. Donec sed felis sodales, blandit orci id, vulputate orci.
|
||||
|
||||
<!--more-->
|
||||
|
||||
In est nulla, ornare vitae elit sed, consequat sollicitudin dui. Duis posuere nulla malesuada elit volutpat ultricies. Mauris et tellus tortor. In ligula elit, pellentesque eget est et, mattis rhoncus nisl. Sed orci ex, mollis quis justo eu, dapibus tincidunt turpis. Sed rhoncus odio non lacus ullamcorper volutpat. Suspendisse blandit ullamcorper condimentum. Quisque et viverra nisi. Vivamus in mollis nulla. Nulla faucibus sed ligula et blandit.
|
||||
|
||||
Vivamus nec libero faucibus, cursus ex et, consequat mauris. Pellentesque commodo ullamcorper vestibulum. Donec efficitur, ipsum et dapibus varius, purus mauris gravida augue, eu mattis lorem turpis eget dui. Curabitur nibh erat, posuere sed eros a, blandit venenatis risus. Vestibulum cursus imperdiet ultrices. In eu efficitur dui, eget tempus enim. Nunc imperdiet, enim et sagittis lacinia, lacus metus eleifend purus, blandit pellentesque leo ante ac velit. Nam ac sagittis est. Cras accumsan, odio vel lacinia mollis, metus tortor malesuada nisi, et consectetur neque quam in erat. Sed ultricies aliquam hendrerit. Etiam non aliquam ipsum, id rutrum magna. Morbi id tincidunt mauris. Vestibulum nec iaculis massa. Etiam fringilla, orci quis faucibus vulputate, risus nibh finibus nisl, et vehicula ipsum leo a tortor. Curabitur mauris elit, bibendum vitae velit at, dignissim ornare arcu.
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
layout: post
|
||||
title: Paging
|
||||
author: @k_cieslak
|
||||
published: 2020-05-01
|
||||
---
|
||||
# Paging
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi nisi diam, vehicula quis blandit id, suscipit sed libero. Proin at diam dolor. In hac habitasse platea dictumst. Donec quis dui vitae quam eleifend dignissim non sed libero. In hac habitasse platea dictumst. In ullamcorper mollis risus, a vulputate quam accumsan at. Donec sed felis sodales, blandit orci id, vulputate orci.
|
||||
|
||||
<!--more-->
|
||||
|
||||
In est nulla, ornare vitae elit sed, consequat sollicitudin dui. Duis posuere nulla malesuada elit volutpat ultricies. Mauris et tellus tortor. In ligula elit, pellentesque eget est et, mattis rhoncus nisl. Sed orci ex, mollis quis justo eu, dapibus tincidunt turpis. Sed rhoncus odio non lacus ullamcorper volutpat. Suspendisse blandit ullamcorper condimentum. Quisque et viverra nisi. Vivamus in mollis nulla. Nulla faucibus sed ligula et blandit.
|
||||
|
||||
Vivamus nec libero faucibus, cursus ex et, consequat mauris. Pellentesque commodo ullamcorper vestibulum. Donec efficitur, ipsum et dapibus varius, purus mauris gravida augue, eu mattis lorem turpis eget dui. Curabitur nibh erat, posuere sed eros a, blandit venenatis risus. Vestibulum cursus imperdiet ultrices. In eu efficitur dui, eget tempus enim. Nunc imperdiet, enim et sagittis lacinia, lacus metus eleifend purus, blandit pellentesque leo ante ac velit. Nam ac sagittis est. Cras accumsan, odio vel lacinia mollis, metus tortor malesuada nisi, et consectetur neque quam in erat. Sed ultricies aliquam hendrerit. Etiam non aliquam ipsum, id rutrum magna. Morbi id tincidunt mauris. Vestibulum nec iaculis massa. Etiam fringilla, orci quis faucibus vulputate, risus nibh finibus nisl, et vehicula ipsum leo a tortor. Curabitur mauris elit, bibendum vitae velit at, dignissim ornare arcu.
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
layout: post
|
||||
title: A post in sub-dir
|
||||
author: @k_cieslak
|
||||
published: 2020-04-29
|
||||
---
|
||||
# A post in sub-dir
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi nisi diam, vehicula quis blandit id, suscipit sed libero. Proin at diam dolor. In hac habitasse platea dictumst. Donec quis dui vitae quam eleifend dignissim non sed libero. In hac habitasse platea dictumst. In ullamcorper mollis risus, a vulputate quam accumsan at. Donec sed felis sodales, blandit orci id, vulputate orci.
|
||||
|
||||
<!--more-->
|
||||
|
||||
In est nulla, ornare vitae elit sed, consequat sollicitudin dui. Duis posuere nulla malesuada elit volutpat ultricies. Mauris et tellus tortor. In ligula elit, pellentesque eget est et, mattis rhoncus nisl. Sed orci ex, mollis quis justo eu, dapibus tincidunt turpis. Sed rhoncus odio non lacus ullamcorper volutpat. Suspendisse blandit ullamcorper condimentum. Quisque et viverra nisi. Vivamus in mollis nulla. Nulla faucibus sed ligula et blandit.
|
||||
|
||||
Vivamus nec libero faucibus, cursus ex et, consequat mauris. Pellentesque commodo ullamcorper vestibulum. Donec efficitur, ipsum et dapibus varius, purus mauris gravida augue, eu mattis lorem turpis eget dui. Curabitur nibh erat, posuere sed eros a, blandit venenatis risus. Vestibulum cursus imperdiet ultrices. In eu efficitur dui, eget tempus enim. Nunc imperdiet, enim et sagittis lacinia, lacus metus eleifend purus, blandit pellentesque leo ante ac velit. Nam ac sagittis est. Cras accumsan, odio vel lacinia mollis, metus tortor malesuada nisi, et consectetur neque quam in erat. Sed ultricies aliquam hendrerit. Etiam non aliquam ipsum, id rutrum magna. Morbi id tincidunt mauris. Vestibulum nec iaculis massa. Etiam fringilla, orci quis faucibus vulputate, risus nibh finibus nisl, et vehicula ipsum leo a tortor. Curabitur mauris elit, bibendum vitae velit at, dignissim ornare arcu.
|
||||
@@ -1,16 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Lib</OutputType>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="types.fs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FSharp.SystemTextJson" Version="1.4.36" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,75 +0,0 @@
|
||||
open System.Text.Json
|
||||
open System.Text.Json.Serialization
|
||||
|
||||
module Types =
|
||||
|
||||
type Path = {
|
||||
Alias: string
|
||||
Langcode: string
|
||||
}
|
||||
|
||||
type Uri = {
|
||||
Url: string
|
||||
}
|
||||
|
||||
type ImageMeta = {
|
||||
Alt: string
|
||||
Title: string
|
||||
Width: string
|
||||
Height: string
|
||||
}
|
||||
|
||||
type VideoMeta = {
|
||||
display: string
|
||||
description: string
|
||||
}
|
||||
|
||||
type ImageFile = {
|
||||
Uri: Uri
|
||||
Meta: ImageMeta
|
||||
}
|
||||
|
||||
type VideoFile = {
|
||||
Uri: Uri
|
||||
Meta: VideoMeta
|
||||
}
|
||||
|
||||
type MediaImage = {
|
||||
FieldMediaImage: ImageFile
|
||||
}
|
||||
|
||||
type MediaVideo = {
|
||||
FieldMediaVideoFile: VideoFile
|
||||
}
|
||||
|
||||
type Media =
|
||||
| [<JsonName("media--image")>]
|
||||
MediaImage of MediaImage
|
||||
| [<JsonName("media--video")>]
|
||||
MediaVideo of MediaVideo
|
||||
|
||||
type Hero = {
|
||||
FieldTitle: string
|
||||
FieldSubtitle: string
|
||||
FieldHeroSize: string
|
||||
FieldBackgroundImage: Media
|
||||
}
|
||||
|
||||
type SimpleText = {
|
||||
FieldBody: string
|
||||
}
|
||||
|
||||
type Paragraph =
|
||||
| [<JsonName("paragraph--hero_banner")>]
|
||||
Hero of Hero
|
||||
| [<JsonName("paragraph--simple_text")>]
|
||||
SimpleText of SimpleText
|
||||
| Ignore
|
||||
|
||||
type DocData = {
|
||||
Type: string
|
||||
Id: string
|
||||
Title: string
|
||||
Path: Path
|
||||
FieldContent: Paragraph[]
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
html,body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
||||
font-size: 14px;
|
||||
background: #F0F2F4;
|
||||
}
|
||||
.navbar.is-white {
|
||||
background: #F0F2F4;
|
||||
}
|
||||
.navbar-brand .brand-text {
|
||||
font-size: 1.11rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
.hero-body
|
||||
{background-image: url(https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Plum_trees_Kitano_Tenmangu.jpg/1200px-Plum_trees_Kitano_Tenmangu.jpg);
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
height: 500px;
|
||||
}
|
||||
.articles {
|
||||
margin: 5rem 0;
|
||||
margin-top: -200px;
|
||||
}
|
||||
.articles .content p {
|
||||
line-height: 1.9;
|
||||
margin: 15px 0;
|
||||
}
|
||||
.author-image {
|
||||
position: absolute;
|
||||
top: -30px;
|
||||
left: 50%;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin-left: -30px;
|
||||
border: 3px solid #ccc;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.media-center {
|
||||
display: block;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.media-content {
|
||||
margin-top: 3rem;
|
||||
}
|
||||
.article, .promo-block {
|
||||
margin-top: 6rem;
|
||||
}
|
||||
div.column.is-8:first-child {
|
||||
padding-top: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
.article-title {
|
||||
font-size: 2rem;
|
||||
font-weight: lighter;
|
||||
line-height: 2;
|
||||
}
|
||||
.article-subtitle {
|
||||
color: #909AA0;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
.article-body {
|
||||
line-height: 1.4;
|
||||
margin: 0 6rem;
|
||||
}
|
||||
@media screen and (max-width: 991px) {
|
||||
.article-body {
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
.promo-block .container {
|
||||
margin: 1rem 5rem;
|
||||
}
|
||||
33
generators/basicpagegenerator.fsx
Normal file
33
generators/basicpagegenerator.fsx
Normal file
@@ -0,0 +1,33 @@
|
||||
#r "../_lib/Fornax.Core.dll"
|
||||
#r "../src/Shared/bin/Debug/netstandard2.0/Shared.dll"
|
||||
#r "nuget: Feliz.ViewEngine"
|
||||
|
||||
open Shared.Data.Types
|
||||
open Shared.Layout
|
||||
open Shared.Pages
|
||||
open Shared.Data.Serializer
|
||||
open Feliz.ViewEngine
|
||||
|
||||
let processTitle (title: string) =
|
||||
title.ToLower().Replace(" ", "-") + ".html"
|
||||
|
||||
let generate' (ctx: SiteContents) (_: string) =
|
||||
let basicPages = ctx.TryGetValues<DocData>() |> Option.defaultValue Seq.empty
|
||||
|
||||
basicPages
|
||||
|> Seq.map (fun doc ->
|
||||
let fileName = processTitle doc.Title
|
||||
let content = BasicPage.render doc
|
||||
let jsonData = serialize doc
|
||||
let pageLayout = defaultLayout doc.Title jsonData
|
||||
let html =
|
||||
pageLayout content
|
||||
|> Render.htmlDocument
|
||||
|
||||
"basicPage/" + fileName, html
|
||||
)
|
||||
|> Seq.toList
|
||||
|
||||
let generate (ctx: SiteContents) (projectRoot: string) (page: string) =
|
||||
generate' ctx page
|
||||
|
||||
17
loaders/basicpageloader.fsx
Normal file
17
loaders/basicpageloader.fsx
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
#r "../_lib/Fornax.Core.dll"
|
||||
#r "../src/Shared/bin/Debug/netstandard2.0/Shared.dll"
|
||||
// Packages need to be referenced from fsx files as well
|
||||
#r "nuget: FSharp.SystemTextJson"
|
||||
#r "nuget: FSharp.Data"
|
||||
|
||||
open Shared.Data.JsonApi
|
||||
|
||||
let loader (projectRoot: string) (siteContent: SiteContents) =
|
||||
let json = getBasicPages
|
||||
|
||||
json
|
||||
|> Array.iter siteContent.Add
|
||||
|
||||
siteContent
|
||||
|
||||
9896
package-lock.json
generated
Normal file
9896
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
33
package.json
Normal file
33
package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "vite src/Client",
|
||||
"build": "vite build -p",
|
||||
"tailwind:build": "tailwindcss -i src/Client/public/style.css -o dist/public/tailwindStyle.css",
|
||||
"test": "vite test/Client"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@semantic-release/changelog": "^6.0.2",
|
||||
"@semantic-release/exec": "^7.1.0",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"@semantic-release/gitlab": "^13.2.9",
|
||||
"sass": "^1.58.3",
|
||||
"semantic-release": "^25.0.2",
|
||||
"semantic-release-dotnet": "^1.0.0",
|
||||
"vite": "^7.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^7.1.0",
|
||||
"@microsoft/signalr": "^10.0.0",
|
||||
"@sentry/browser": "^10.25.0",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
"daisyui": "^5.5.5",
|
||||
"lit": "^3.1.0",
|
||||
"lit-html": "^3.1.0",
|
||||
"open-sans-fonts": "^1.6.2",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"tailwindcss": "^4.1.17"
|
||||
}
|
||||
}
|
||||
23
shell.nix
Normal file
23
shell.nix
Normal file
@@ -0,0 +1,23 @@
|
||||
with import <nixpkgs> { };
|
||||
let
|
||||
port = 8000;
|
||||
in
|
||||
pkgs.mkShell {
|
||||
nativeBuildInputs = [
|
||||
tilt
|
||||
dapr-cli
|
||||
dotnetCorePackages.sdk_9_0
|
||||
tailwindcss_4
|
||||
bun
|
||||
];
|
||||
|
||||
DOCKER_BUILDKIT = 0;
|
||||
|
||||
LOG_LEVEL = 4;
|
||||
CLIENT_PORT = port + 80;
|
||||
SERVER_PORT = port + 85;
|
||||
SERVER_PROXY_PORT = port + 85;
|
||||
TILT_PORT = port + 50;
|
||||
|
||||
shellHook = '''';
|
||||
}
|
||||
53
src/Client/App.fs
Normal file
53
src/Client/App.fs
Normal file
@@ -0,0 +1,53 @@
|
||||
module App
|
||||
|
||||
open Browser
|
||||
open Elmish
|
||||
open Elmish.React
|
||||
open Shared.Data.ThothTypes
|
||||
open Shared.Data.ThothSerializerManual
|
||||
open Shared.Pages
|
||||
open Thoth.Json
|
||||
open Feliz
|
||||
open Fable.Core.JsInterop
|
||||
|
||||
importSideEffects "./public/style.css"
|
||||
|
||||
open type Html
|
||||
|
||||
type Model = { Page: DocData option }
|
||||
|
||||
type Msg = | NoOp
|
||||
|
||||
let decodeInitialData () =
|
||||
let script = document.getElementById "__FORNIX_DATA__"
|
||||
console.log (script)
|
||||
|
||||
if isNull script then
|
||||
None
|
||||
else
|
||||
let json = script.textContent
|
||||
let jsonPageData = Decode.fromString entityDecoder json
|
||||
|
||||
let pageData =
|
||||
match jsonPageData with
|
||||
| Ok pd -> pd.data
|
||||
| Error err -> failwithf "Couldn't decode the json dat"
|
||||
|
||||
Some pageData
|
||||
|
||||
|
||||
let init () : Model * Cmd<Msg> =
|
||||
{ Page = decodeInitialData () }, Cmd.none
|
||||
|
||||
let update msg model =
|
||||
match msg with
|
||||
| NoOp -> model, Cmd.none
|
||||
|
||||
let view (model: Model) (dispatch: Msg -> unit) =
|
||||
match model.Page with
|
||||
| None -> div [ text "Couldn't match data" ]
|
||||
| Some content -> BasicPage.render content
|
||||
|
||||
let program = Program.mkProgram init update view |> Program.withReactHydrate "app"
|
||||
|
||||
program |> Program.run
|
||||
28
src/Client/Client.fsproj
Normal file
28
src/Client/Client.fsproj
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<DefineConstants>FABLE_COMPILER</DefineConstants>
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../Shared/Shared.fsproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="public/style.scss" />
|
||||
<Compile Include="../Shared/Shared.fs" />
|
||||
<Compile Include="App.fs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FSharp.Data" Version="6.6.0" />
|
||||
<PackageReference Include="Feliz" version="2.9.0" />
|
||||
<PackageReference Include="Fable.Elmish.React" Version="5.0.0" />
|
||||
<PackageReference Include="Fable.Browser.Dom" Version="2.20.0" />
|
||||
<PackageReference Include="Fable.Core" Version="4.5.0" />
|
||||
<PackageReference Include="Fable.FontAwesome.Free" Version="3.0.0" />
|
||||
<PackageReference Include="Fable.Remoting.Client" Version="7.35.0" />
|
||||
<PackageReference Include="Elmish" Version="5.0.2" />
|
||||
<PackageReference Include="Thoth.Json" Version="10.4.1" />
|
||||
<PackageReference Update="FSharp.Core" Version="9.0.303" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
6
src/Client/main.html
Normal file
6
src/Client/main.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<body>
|
||||
<script type="module" src="./build/client/App.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
377
src/Client/packages.lock.json
Normal file
377
src/Client/packages.lock.json
Normal file
@@ -0,0 +1,377 @@
|
||||
{
|
||||
"version": 1,
|
||||
"dependencies": {
|
||||
"net9.0": {
|
||||
"Elmish": {
|
||||
"type": "Direct",
|
||||
"requested": "[5.0.2, )",
|
||||
"resolved": "5.0.2",
|
||||
"contentHash": "EUSEpxcXAZ93L02FSTBwP4AQGIZBnJT3udI/DE2kDIir7rjtcolTSjUjk41AgR+C69MvuHi18cN8TPh4L8EY3w=="
|
||||
},
|
||||
"Fable.Browser.Dom": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.20.0, )",
|
||||
"resolved": "2.20.0",
|
||||
"contentHash": "8MnTBQ3NtWABhT8KXJZ7dracK4La3LrX265eqRJiiXayU6acNvzjBltYsyMmarqUhRf723e3hyHTKNOLQITosQ==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.2",
|
||||
"Fable.Browser.Blob": "1.4.0",
|
||||
"Fable.Browser.Event": "1.7.0",
|
||||
"Fable.Browser.WebStorage": "1.3.0",
|
||||
"Fable.Core": "3.2.8"
|
||||
}
|
||||
},
|
||||
"Fable.Core": {
|
||||
"type": "Direct",
|
||||
"requested": "[4.5.0, )",
|
||||
"resolved": "4.5.0",
|
||||
"contentHash": "ZcX8XN5sQZGCbrS8VYnsa/ynhrCBfpDyqDkTl33GTXOpgfKibxoq0+W0hCSbRzuukVNoLtqGL/B6+8yTNDXbNA=="
|
||||
},
|
||||
"Fable.Elmish.React": {
|
||||
"type": "Direct",
|
||||
"requested": "[5.0.0, )",
|
||||
"resolved": "5.0.0",
|
||||
"contentHash": "KNvaKlGyvPtFu93uOPYYfGAOMBWnaEA3iFrPxbpMd8X1x2RZw8hkd0pYsqSPoDfGTFR1Y7LHwKNj9A13YJTy4g==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.2",
|
||||
"Fable.Elmish": "5.0.0",
|
||||
"Fable.ReactDom.Types": "18.2.0"
|
||||
}
|
||||
},
|
||||
"Fable.FontAwesome.Free": {
|
||||
"type": "Direct",
|
||||
"requested": "[3.0.0, )",
|
||||
"resolved": "3.0.0",
|
||||
"contentHash": "3+MdXEr0mZon0FOZiu39tO50FDqS89IYoq7efUNLWwAZe/XBscSwNNGBnRk4PHTAz5v9WZ7hoYCLkdG5TqCmpw==",
|
||||
"dependencies": {
|
||||
"Fable.Core": "3.7.1",
|
||||
"Fable.FontAwesome": "3.0.0",
|
||||
"Fable.React": "9.2.0"
|
||||
}
|
||||
},
|
||||
"Fable.Remoting.Client": {
|
||||
"type": "Direct",
|
||||
"requested": "[7.35.0, )",
|
||||
"resolved": "7.35.0",
|
||||
"contentHash": "57StsvefN9NZorEbOsjngDXjn0JDxDG36S8ikDQdAC/WTi5n7kZChZ1v+0CMiZ2IU9aarprOLvr1ie5NKN9IZA==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.2",
|
||||
"Fable.Browser.XMLHttpRequest": "1.0.0",
|
||||
"Fable.Core": "3.1.5",
|
||||
"Fable.Remoting.MsgPack": "1.25.0",
|
||||
"Fable.SimpleJson": "3.24.0"
|
||||
}
|
||||
},
|
||||
"Feliz": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.9.0, )",
|
||||
"resolved": "2.9.0",
|
||||
"contentHash": "8nyGREGA60RysdSBamVWmr68MG+3lLy76W17fBiGaKi7uMFbtRcYBLyNtp2NyGZFfnuWCEyDAmAXM5YFnDhbhg==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.2",
|
||||
"Fable.ReactDom.Types": "18.2.0",
|
||||
"Feliz.CompilerPlugins": "2.2.0"
|
||||
}
|
||||
},
|
||||
"FSharp.Core": {
|
||||
"type": "Direct",
|
||||
"requested": "[9.0.303, )",
|
||||
"resolved": "9.0.303",
|
||||
"contentHash": "6JlV8aD8qQvcmfoe/PMOxCHXc0uX4lR23u0fAyQtnVQxYULLoTZgwgZHSnRcuUHOvS3wULFWcwdnP1iwslH60g=="
|
||||
},
|
||||
"FSharp.Data": {
|
||||
"type": "Direct",
|
||||
"requested": "[6.6.0, )",
|
||||
"resolved": "6.6.0",
|
||||
"contentHash": "AC87/VZV1iGrnK1uG6QhX22HZARlFiCqdlckzPDgdE3+fzc0ZNRR3Y4ny5vbLvfDj2rjFoJdmRZy6kzI1C/sQQ==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "6.0.1",
|
||||
"FSharp.Data.Csv.Core": "6.6.0",
|
||||
"FSharp.Data.Html.Core": "6.6.0",
|
||||
"FSharp.Data.Http": "6.6.0",
|
||||
"FSharp.Data.Json.Core": "6.6.0",
|
||||
"FSharp.Data.Runtime.Utilities": "6.6.0",
|
||||
"FSharp.Data.WorldBank.Core": "6.6.0",
|
||||
"FSharp.Data.Xml.Core": "6.6.0"
|
||||
}
|
||||
},
|
||||
"Thoth.Json": {
|
||||
"type": "Direct",
|
||||
"requested": "[10.4.1, )",
|
||||
"resolved": "10.4.1",
|
||||
"contentHash": "hs76/uO+gHhvnlaxQDqbpUX2Y0L97ilEZ1Nx+LA4D6N7fuAYJmNwQWZB/KQLBE7wIeWK5oXMFHCuKdImSrF1Bg==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "5.0.2",
|
||||
"Fable.Core": "4.1.0"
|
||||
}
|
||||
},
|
||||
"Fable.AST": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.2.1",
|
||||
"contentHash": "/4V6U7Qw/WIRRxm9NJ7b+YTXTRCTk6/YKeJnbKYaVbtT45MstA3jkFvRfV0FqVFtkG9AL4uccetreygTjK7nbQ=="
|
||||
},
|
||||
"Fable.Browser.Blob": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.4.0",
|
||||
"contentHash": "UlaxrIXUfMmABjP+8a4XJp/Af+eCRKa8KJ57Olq4sqphmPLn/gNtp3sk5hRNBZ385lwUszbO5yd3Q/rrl9BdOQ==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.2",
|
||||
"Fable.Core": "3.2.8"
|
||||
}
|
||||
},
|
||||
"Fable.Browser.Event": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.7.0",
|
||||
"contentHash": "x+wqXQK0l4VlCnELDp68GC/mZAx6NbicDxYPliyAoNq8RPNDeR3R782icNwI5YmA+ufq11XvG6w1JjsL/ldy7w==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.2",
|
||||
"Fable.Browser.Gamepad": "1.3.0",
|
||||
"Fable.Core": "3.2.8"
|
||||
}
|
||||
},
|
||||
"Fable.Browser.Gamepad": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.3.0",
|
||||
"contentHash": "C4HZDzCgff+U094QjpQlJh425W5j5/vojvOi2FV5UFS34l7TJ6YBgBPpKoro02QhAi/UF3AeocR+V2yiYxHb0A==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.2",
|
||||
"Fable.Core": "3.2.8"
|
||||
}
|
||||
},
|
||||
"Fable.Browser.WebStorage": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.3.0",
|
||||
"contentHash": "x8JL9oEtPiK0JY4GrRTqhomiLxT6Jaiv5uu8VXiNeA78bFvUogZWxQeejsK83iNFGErK5wpdiPd0tsREZTRLeg==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.2",
|
||||
"Fable.Browser.Event": "1.6.0",
|
||||
"Fable.Core": "3.2.8"
|
||||
}
|
||||
},
|
||||
"Fable.Browser.XMLHttpRequest": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.0",
|
||||
"contentHash": "/XHivZ/GQXlLgNSIDyLnAOwWzP4iOUURCobmNrAnm9GCWo6Lxw7Z+vkxjV2BVC8K+TFI38gPOlHPzyDuwXXA2g==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.5.2",
|
||||
"Fable.Browser.Dom": "1.0.0",
|
||||
"Fable.Core": "3.0.0"
|
||||
}
|
||||
},
|
||||
"Fable.Elmish": {
|
||||
"type": "Transitive",
|
||||
"resolved": "5.0.0",
|
||||
"contentHash": "jPWkW8StcCsBIFnDfnCm8RsonJlpCrW9sKmsYySPBbpMeEgzE2xIvTCj5OhsYxwDMpBRS2EDDckjMUyefVJsOg==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.2",
|
||||
"Fable.Core": "4.0.0"
|
||||
}
|
||||
},
|
||||
"Fable.FontAwesome": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.0.0",
|
||||
"contentHash": "RidSfWDoBph4Re6NS5CusvhVMbMjbdWqPHeqeiAvjUc6bVmwyCv17SoVfe5cOc7qIZEcQh3UkOFzGguqvhH2cA==",
|
||||
"dependencies": {
|
||||
"Fable.Core": "3.7.1",
|
||||
"Fable.React": "9.2.0"
|
||||
}
|
||||
},
|
||||
"Fable.Parsimmon": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.0.0",
|
||||
"contentHash": "AaHqEcwjjv8q5S2gCNu6XsVcpChYM8D6aEb3sjjsAiLspwLrNLqm6vOEKdJKGnh0gSLHg6UWzLGA/Q4jrk+t/w==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.6.2",
|
||||
"Fable.Core": "3.0.0"
|
||||
}
|
||||
},
|
||||
"Fable.React": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.2.0",
|
||||
"contentHash": "ZMRkZpBy+6ytUHPk0WjRh15kigncmSGLOAbtf12VaSy48mtgqIW4lC/23X/FEvz8tUGwHvBUen2GzUU1G/L+RQ==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.2",
|
||||
"Fable.React.Types": "18.3.0",
|
||||
"Fable.ReactDom.Types": "18.2.0"
|
||||
}
|
||||
},
|
||||
"Fable.React.Types": {
|
||||
"type": "Transitive",
|
||||
"resolved": "18.3.0",
|
||||
"contentHash": "/b8WZ3Bdfhqy9r60ZK9JGZaGNjIMb0ogsrvWIg3k7KfCEvJs5X6+7hCybVkyjVoxwzn9wLyYGRbh5wmuHQT/Vg==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.2",
|
||||
"Fable.Browser.Dom": "2.4.4",
|
||||
"Fable.Core": "3.2.7"
|
||||
}
|
||||
},
|
||||
"Fable.ReactDom.Types": {
|
||||
"type": "Transitive",
|
||||
"resolved": "18.2.0",
|
||||
"contentHash": "2WoBjsLiSgrvR68OJXko0iVaqeMbkPM5Bx813A1WlxOSCJ50M9fwnlwG/MUEZtiOIhQmku/YTJY5a8E8r1+j2Q==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.2",
|
||||
"Fable.React.Types": "18.3.0"
|
||||
}
|
||||
},
|
||||
"Fable.Remoting.MsgPack": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.25.0",
|
||||
"contentHash": "FyqSj8j8J0W7xTR8XJmk2Q6vZa0NlKUUjQtr/rQhCkV2r5uJ3gtT+2KSiMjdzemwV5X+9eUz6C5q1YN0t3ccug==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.2"
|
||||
}
|
||||
},
|
||||
"Fable.SimpleJson": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.24.0",
|
||||
"contentHash": "mNk5s+8arkrrupT52/840xybT/DmaPUsJ926fTHk2uHOaWLnyNbUPY63Yg8zJZFCxSCzWrFpmB8rS9fcLVLJSg==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.0",
|
||||
"Fable.Core": "3.1.5",
|
||||
"Fable.Parsimmon": "4.0.0"
|
||||
}
|
||||
},
|
||||
"Feliz.CompilerPlugins": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "ACkO++Hp4lUrEx/axeehIL5/3R8jMnak+CYpzd0/kLpejp9BETtrgjHK7oj6Lh3V9fB7WoAKsCxyPSrm4ADN2w==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.2",
|
||||
"Fable.AST": "4.2.1"
|
||||
}
|
||||
},
|
||||
"Feliz.ViewEngine": {
|
||||
"type": "Transitive",
|
||||
"resolved": "0.27.0",
|
||||
"contentHash": "D6OLAuwPUoxa1VcBLjcs/eArfmqxdtyv1N0piaup40lcDjt4V7CQURr7EpUaOkTU770Hbmi2V3RCgq7XmL17Zw==",
|
||||
"dependencies": {
|
||||
"Fsharp.Core": "[4.7.0, 5.0.0)"
|
||||
}
|
||||
},
|
||||
"FSharp.Data.Csv.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.6.0",
|
||||
"contentHash": "gbmHT+XraE1OjUyf/+bmIaluoMMj/34MC5IX0QNWr+K8fu+UjKg854p+fkG5JkzWej0+46CTV7hW/CWP425naA==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "6.0.1",
|
||||
"FSharp.Data.Runtime.Utilities": "6.6.0"
|
||||
}
|
||||
},
|
||||
"FSharp.Data.Html.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.6.0",
|
||||
"contentHash": "YxImykk5fUxw+rwvD26JfZYn/OAUataebsUobXu8KzkLtl/5fOhiPqpI54jAT4ysv2wHD5coCp30+5UsQXqxBQ==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "6.0.1",
|
||||
"FSharp.Data.Csv.Core": "6.6.0",
|
||||
"FSharp.Data.Runtime.Utilities": "6.6.0"
|
||||
}
|
||||
},
|
||||
"FSharp.Data.Http": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.6.0",
|
||||
"contentHash": "7nFDLYPqEps/F3Ay65Xm3YvS3OmARYB24xYv1CszOqurTZ76wM9vnsvyeFOMcCKXA+QgwK+eCa8cgyavmR1YxA==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "6.0.1"
|
||||
}
|
||||
},
|
||||
"FSharp.Data.Json.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.6.0",
|
||||
"contentHash": "VhLkZwyzeMWsJHivY4GjLqIWh+gIm0rWW0BBlAh1M7XzmmumbPLDehUqOh6Nkm1R+N8iBhFgj7HBq6bmQzGnZA==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "6.0.1",
|
||||
"FSharp.Data.Http": "6.6.0",
|
||||
"FSharp.Data.Runtime.Utilities": "6.6.0"
|
||||
}
|
||||
},
|
||||
"FSharp.Data.Runtime.Utilities": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.6.0",
|
||||
"contentHash": "iLkCY0mQGxuIE2sEIFFOov9ZmWPwlQ0Jq51Xgqv+e0hbAPf2p4OaGp/DR4ye6VQCyVzHVCp9XyJQeHOhoWCQvA==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "6.0.1",
|
||||
"FSharp.Data.Http": "6.6.0"
|
||||
}
|
||||
},
|
||||
"FSharp.Data.WorldBank.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.6.0",
|
||||
"contentHash": "Tg6Hk0+61McNviq+zATBhIDoyslV07hZjYsrzz6IHagbze+1JNiz8FCpwsHh0uAzkdNUZK2/qsrHtMQvM0lpdg==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "6.0.1",
|
||||
"FSharp.Data.Http": "6.6.0",
|
||||
"FSharp.Data.Json.Core": "6.6.0",
|
||||
"FSharp.Data.Runtime.Utilities": "6.6.0"
|
||||
}
|
||||
},
|
||||
"FSharp.Data.Xml.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.6.0",
|
||||
"contentHash": "Z7U4mO0FHT5uHOYRLSK67izqJrCSvWpjcDPRQkJmYB5ge0sRfHWoKV8b4ttHLmpEg/eAdu64u00nITxpbZm+CA==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "6.0.1",
|
||||
"FSharp.Data.Http": "6.6.0",
|
||||
"FSharp.Data.Json.Core": "6.6.0",
|
||||
"FSharp.Data.Runtime.Utilities": "6.6.0"
|
||||
}
|
||||
},
|
||||
"FSharp.SystemTextJson": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.4.36",
|
||||
"contentHash": "1xLk0SBF1nedD74B77rcArjD2d+DeZwbNI7BVCAyKwIBERo1VoX8Mf4AtX0OV2L3ZeM/XS1b6BuhXCyoh0KBnw==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.0",
|
||||
"System.Text.Json": "6.0.10"
|
||||
}
|
||||
},
|
||||
"Newtonsoft.Json": {
|
||||
"type": "Transitive",
|
||||
"resolved": "13.0.1",
|
||||
"contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A=="
|
||||
},
|
||||
"System.IO.Pipelines": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.0",
|
||||
"contentHash": "M1eb3nfXntaRJPrrMVM9EFS8I1bDTnt0uvUS6QP/SicZf/ZZjydMD5NiXxfmwW/uQwaMDP/yX2P+zQN1NBHChg=="
|
||||
},
|
||||
"System.Text.Encodings.Web": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.0",
|
||||
"contentHash": "257hh1ep1Gqm1Lm0ulxf7vVBVMJuGN6EL4xSWjpi46DffXzm1058IiWsfSC06zSm7SniN+Tb5160UnXsSa8rRg=="
|
||||
},
|
||||
"System.Text.Json": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.0",
|
||||
"contentHash": "1Dpjwq9peG/Wt5BNbrzIhTpclfOSqBWZsUO28vVr59yQlkvL5jLBWfpfzRmJ1OY+6DciaY0DUcltyzs4fuZHjw==",
|
||||
"dependencies": {
|
||||
"System.IO.Pipelines": "10.0.0",
|
||||
"System.Text.Encodings.Web": "10.0.0"
|
||||
}
|
||||
},
|
||||
"Thoth.Json.Net": {
|
||||
"type": "Transitive",
|
||||
"resolved": "12.0.0",
|
||||
"contentHash": "n2YyONYdWCFS4Pu7wrqgV/l5tPuN+t3gxEfs/2RwqCiQkRnbgKs9dK61zHeZS5YganAoFbxLSwbaNL7SvSrS9g==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.2",
|
||||
"Fable.Core": "3.1.6",
|
||||
"Newtonsoft.Json": "13.0.1"
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "[9.0.303, )",
|
||||
"FSharp.Data": "[6.6.0, )",
|
||||
"FSharp.SystemTextJson": "[1.4.36, )",
|
||||
"Feliz": "[2.9.0, )",
|
||||
"Feliz.ViewEngine": "[0.27.0, )",
|
||||
"System.Text.Json": "[10.0.0, )",
|
||||
"Thoth.Json.Net": "[12.0.0, )"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
src/Client/public/favicon.png
Normal file
BIN
src/Client/public/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 810 B |
6
src/Client/public/style.css
Normal file
6
src/Client/public/style.css
Normal file
@@ -0,0 +1,6 @@
|
||||
@import "tailwindcss";
|
||||
@plugin "daisyui" {
|
||||
themes: all;
|
||||
}
|
||||
@plugin "@tailwindcss/typography";
|
||||
@source "../build/client/**/*.{js,ts,jsx,tsx}";
|
||||
70
src/Server/JsonApi.fs
Normal file
70
src/Server/JsonApi.fs
Normal file
@@ -0,0 +1,70 @@
|
||||
namespace Shared.Data
|
||||
|
||||
open FSharp.Data
|
||||
open Thoth.Json.Net
|
||||
open Shared.Data.ThothTypes
|
||||
open Shared.Data.ThothSerializerManual
|
||||
|
||||
module JsonApi =
|
||||
let baseUrl = "http://oceanbox.ddev.site/"
|
||||
let nodeUrl (contentType: string) =
|
||||
baseUrl + "/jsonapi/node/" + contentType
|
||||
|
||||
let decoupledUrl (alias: string) =
|
||||
baseUrl + $"/router/translate-path?path=/{alias}"
|
||||
|
||||
let getJsonApiPage (url: string) : JsonApiContent =
|
||||
let rawData = Http.RequestString url
|
||||
let jsonData = Decode.fromString pageDecoder rawData
|
||||
|
||||
match jsonData with
|
||||
| Ok jsonApiPage -> jsonApiPage
|
||||
| Error err ->
|
||||
failwithf $"Failed to decode from '{url}': {err}"
|
||||
|
||||
let getJsonApiPageBySlug (alias: string) : EntityContent =
|
||||
let rawDecoupledContent =
|
||||
alias
|
||||
|> decoupledUrl
|
||||
|> Http.RequestString
|
||||
|
||||
let jsonDecoupledContent = Decode.Auto.fromString<DecoupledPage> rawDecoupledContent
|
||||
|
||||
let decoupledContent =
|
||||
match jsonDecoupledContent with
|
||||
| Ok dc -> dc
|
||||
| Error err ->
|
||||
failwithf $"Failed to decode from '{decoupledUrl alias}': {err}"
|
||||
|
||||
let rawJsonApiPage = Http.RequestString decoupledContent.jsonapi.individual
|
||||
|
||||
let jsonApiPageContent = Decode.fromString entityDecoder rawJsonApiPage
|
||||
|
||||
let pageContent =
|
||||
match jsonApiPageContent with
|
||||
| Ok pc -> pc
|
||||
| Error err ->
|
||||
failwithf $"Failed to decode from '{decoupledContent.jsonapi.individual}': {err}"
|
||||
|
||||
pageContent
|
||||
|
||||
let getAll (getPage: string -> JsonApiContent) (startUrl: string): DocData[] =
|
||||
let rec retrieveData (url: string) (acc: DocData[]) =
|
||||
let page = getPage url
|
||||
let acc' = Array.append acc page.data
|
||||
|
||||
match page.links.next with
|
||||
| Some nextUrl when not (System.String.IsNullOrWhiteSpace nextUrl.href) ->
|
||||
retrieveData nextUrl.href acc'
|
||||
| _ -> acc'
|
||||
retrieveData startUrl [||]
|
||||
|
||||
let private getAllJsonApiPages = getAll getJsonApiPage
|
||||
|
||||
let private getAllByContentType (contentType: string): DocData[] =
|
||||
contentType
|
||||
|> nodeUrl
|
||||
|> getAllJsonApiPages
|
||||
|
||||
let getBasicPages =
|
||||
getAllByContentType "page"
|
||||
117
src/Server/Server.fs
Normal file
117
src/Server/Server.fs
Normal file
@@ -0,0 +1,117 @@
|
||||
module Server
|
||||
|
||||
open FSharp.SystemTextJson
|
||||
|
||||
open Shared
|
||||
open Shared.Data.JsonApi
|
||||
open Shared.Data.ThothSerializerManual
|
||||
open Thoth.Json.Net
|
||||
open Shared.Pages
|
||||
open Shared.Layout
|
||||
open System
|
||||
open Feliz.ViewEngine
|
||||
open Microsoft.AspNetCore.Http
|
||||
open Fable.Remoting.Server
|
||||
open Fable.Remoting.Giraffe
|
||||
open FSharp.Control
|
||||
open FSharp.Data
|
||||
open Saturn
|
||||
open Giraffe
|
||||
open Serilog
|
||||
open Serilog.Events
|
||||
open Argu
|
||||
open Prometheus
|
||||
open Settings
|
||||
|
||||
type Arguments =
|
||||
| Log_Level of level: int
|
||||
| Port of port: int
|
||||
| [<MainCommand; Last>] Dir of path: string
|
||||
interface IArgParserTemplate with
|
||||
member this.Usage =
|
||||
match this with
|
||||
| Log_Level _ -> "0=Error, 1=Warning, 2=Info, 3=Debug, 4=Verbose"
|
||||
| Port _ -> "listen port (default 8085)"
|
||||
| Dir _ -> "serve from dir"
|
||||
|
||||
type Foo = {
|
||||
foo: string
|
||||
bar: int[]
|
||||
}
|
||||
|
||||
let myFooPostHandler (next: HttpFunc) (ctx: HttpContext) =
|
||||
task {
|
||||
let! x = ctx.BindJsonAsync<string>()
|
||||
printfn $"result: {x}"
|
||||
return! Successful.OK x next ctx
|
||||
}
|
||||
|
||||
let jsonApiPage (page: string) (next: HttpFunc) (ctx: HttpContext) =
|
||||
task {
|
||||
let pageData = getJsonApiPageBySlug page
|
||||
let content = BasicPage.render pageData.data
|
||||
let jsonData = entityEncoder pageData |> Encode.toString 2
|
||||
let pageLayout = defaultLayout pageData.data.title jsonData
|
||||
let html =
|
||||
pageLayout content
|
||||
|> Render.htmlDocument
|
||||
|
||||
return! htmlString html next ctx
|
||||
}
|
||||
|
||||
let webApp =
|
||||
choose [
|
||||
GET >=> choose [
|
||||
routef "/%s" jsonApiPage
|
||||
route "/" >=> jsonApiPage "frontpage"
|
||||
// routef "/%s" (fun y -> htmlFile $"../dist/public/basicPage/{y}.html")
|
||||
]
|
||||
POST >=> route "/foo" >=> myFooPostHandler
|
||||
]
|
||||
|
||||
let configureSerilog level =
|
||||
let n =
|
||||
match level with
|
||||
| 0 -> LogEventLevel.Error
|
||||
| 1 -> LogEventLevel.Warning
|
||||
| 2 -> LogEventLevel.Information
|
||||
| 3 -> LogEventLevel.Debug
|
||||
| _ -> LogEventLevel.Verbose
|
||||
LoggerConfiguration()
|
||||
.MinimumLevel.Is(n)
|
||||
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
|
||||
.MinimumLevel.Override("System", LogEventLevel.Information)
|
||||
.Filter.ByExcluding("RequestPath like '/health%'")
|
||||
.WriteTo.Console()
|
||||
.CreateLogger()
|
||||
|
||||
let app _ =
|
||||
let metricServer = new KestrelMetricServer(port = 9091)
|
||||
metricServer.Start() |> ignore
|
||||
|
||||
application {
|
||||
url $"http://0.0.0.0:{port}"
|
||||
use_router webApp
|
||||
use_static "public"
|
||||
memory_cache
|
||||
use_json_serializer (Thoth.Json.Giraffe.ThothSerializer())
|
||||
use_gzip
|
||||
logging (fun logger -> logger.AddSerilog() |> ignore)
|
||||
}
|
||||
|
||||
let colorizer =
|
||||
function
|
||||
| ErrorCode.HelpText -> None
|
||||
| _ -> Some ConsoleColor.Red
|
||||
|
||||
let errorHandler = ProcessExiter(colorizer = colorizer)
|
||||
|
||||
[<EntryPoint>]
|
||||
let main argv =
|
||||
let parser =
|
||||
ArgumentParser.Create<Arguments>(programName = "OceanWeb", errorHandler = errorHandler)
|
||||
let args = parser.Parse argv
|
||||
let port = args.GetResult (Port, defaultValue = 8085)
|
||||
Log.Logger <- configureSerilog (args.GetResult(Log_Level, defaultValue = 4))
|
||||
run (app port)
|
||||
0
|
||||
41
src/Server/Server.fsproj
Normal file
41
src/Server/Server.fsproj
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../Shared/Shared.fsproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="JsonApi.fs" />
|
||||
<Compile Include="../Shared/Shared.fs" />
|
||||
<Compile Include="Settings.fs" />
|
||||
<Compile Include="Server.fs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Argu" Version="6.2.5" />
|
||||
<PackageReference Include="FSharp.Data" Version="6.6.0" />
|
||||
<PackageReference Include="FSharp.SystemTextJson" Version="1.4.36" />
|
||||
<PackageReference Include="FSharpPlus" Version="1.8.0" />
|
||||
<PackageReference Include="Fable.Remoting.DotnetClient" Version="3.37.0" />
|
||||
<PackageReference Include="Fable.Remoting.Giraffe" Version="5.24.0" />
|
||||
<PackageReference Include="Fable.Remoting.MsgPack" Version="1.25.0" />
|
||||
<PackageReference Include="Fable.Remoting.Server" Version="5.42.0" />
|
||||
<PackageReference Include="Giraffe" Version="8.2.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
<PackageReference Include="prometheus-net.AspNetCore" Version="8.2.1" />
|
||||
<PackageReference Include="Saturn" Version="0.17.0" />
|
||||
<PackageReference Include="SecurityCodeScan" Version="3.5.4">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Serilog" Version="4.3.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
|
||||
<PackageReference Include="Serilog.Expressions" Version="5.0.0" />
|
||||
<PackageReference Include="Thoth.Json.Giraffe" Version="6.0.0" />
|
||||
<PackageReference Include="Thoth.Json.Net" Version="12.0.0" />
|
||||
<PackageReference Update="FSharp.Core" Version="9.0.300" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
82
src/Server/Settings.fs
Normal file
82
src/Server/Settings.fs
Normal file
@@ -0,0 +1,82 @@
|
||||
module Settings
|
||||
|
||||
open System.IO
|
||||
open Thoth.Json.Net
|
||||
open Serilog
|
||||
open FSharpPlus
|
||||
|
||||
type OpenIdSettings =
|
||||
{
|
||||
issuer: string
|
||||
authorization_endpoint: string
|
||||
token_endpoint: string
|
||||
jwks_uri: string
|
||||
userinfo_endpoint: string
|
||||
device_authorization_endpoint: string
|
||||
clientId: string
|
||||
clientSecret: string
|
||||
scopes: string []
|
||||
redirectUri: string option
|
||||
}
|
||||
|
||||
type SsoSettings =
|
||||
{
|
||||
signedOutRedirectUri: string
|
||||
cookieDomain: string
|
||||
appDomain: string
|
||||
dataProtectionKeys: string
|
||||
}
|
||||
|
||||
type PlainAuth =
|
||||
{
|
||||
username: string
|
||||
password: string
|
||||
groups: string[]
|
||||
roles: string []
|
||||
}
|
||||
|
||||
type Settings =
|
||||
{
|
||||
oidc: OpenIdSettings
|
||||
redis: string
|
||||
sso: SsoSettings
|
||||
allowedOrigins: string []
|
||||
plainAuthUsers: PlainAuth []
|
||||
}
|
||||
|
||||
let tryGetEnv =
|
||||
System.Environment.GetEnvironmentVariable
|
||||
>> function
|
||||
| null
|
||||
| "" -> None
|
||||
| x -> Some x
|
||||
|
||||
let appsettings =
|
||||
let settings =
|
||||
File.ReadAllText "appsettings.json"
|
||||
|> Decode.Auto.fromString<Settings>
|
||||
|> function
|
||||
| Ok s -> s
|
||||
| Error e -> failwith e
|
||||
let users = // support for storing user credentials in Kubernetes secret
|
||||
"PLAIN_AUTH_USERS"
|
||||
|> tryGetEnv
|
||||
|> Option.bind (fun f ->
|
||||
File.ReadAllText f
|
||||
|> Decode.Auto.fromString<PlainAuth []>
|
||||
|> Option.ofResult
|
||||
)
|
||||
|> Option.defaultValue settings.plainAuthUsers
|
||||
{ settings with
|
||||
plainAuthUsers = users
|
||||
}
|
||||
|
||||
let port =
|
||||
"SERVER_PORT"
|
||||
|> tryGetEnv
|
||||
|> Option.map uint16
|
||||
|> Option.defaultValue 8085us
|
||||
|
||||
let listenAddress = "http://0.0.0.0:" + port.ToString()
|
||||
|
||||
sprintf "AppSettings: %A" appsettings |> Log.Debug
|
||||
36
src/Server/appsettings.json
Normal file
36
src/Server/appsettings.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"oidc": {
|
||||
"issuer": "https://idp.app.local/dex",
|
||||
"authorization_endpoint": "https://idp.app.local/dex/auth",
|
||||
"token_endpoint": "https://idp.app.local/dex/token",
|
||||
"jwks_uri": "https://idp.app.local/dex/keys",
|
||||
"userinfo_endpoint": "https://idp.app.local/dex/userinfo",
|
||||
"device_authorization_endpoint": "https://idp.app.local/dex/device/code",
|
||||
"clientId": "app",
|
||||
"clientSecret": "secret",
|
||||
"scopes": [
|
||||
"openid",
|
||||
"email",
|
||||
"offline_access",
|
||||
"profile"
|
||||
]
|
||||
},
|
||||
"redis": "localhost:6379,user=default,password=secret",
|
||||
"sso": {
|
||||
"cookieDomain": "localhost",
|
||||
"signedOutRedirectUri": "https://idp.app.local/dex/static/logout.html",
|
||||
"appDomain": "app",
|
||||
"dataProtectionKeys": "DataProtection-Keys"
|
||||
},
|
||||
"allowedOrigins": [
|
||||
"https://atlantis.local.oceanbox.io:8080"
|
||||
],
|
||||
"plainAuthUsers": [
|
||||
{
|
||||
"username": "demo",
|
||||
"password": "secret",
|
||||
"groups": [ "cli" ],
|
||||
"roles": [ "admin" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
726
src/Server/packages.lock.json
Normal file
726
src/Server/packages.lock.json
Normal file
@@ -0,0 +1,726 @@
|
||||
{
|
||||
"version": 1,
|
||||
"dependencies": {
|
||||
"net9.0": {
|
||||
"Argu": {
|
||||
"type": "Direct",
|
||||
"requested": "[6.2.5, )",
|
||||
"resolved": "6.2.5",
|
||||
"contentHash": "68vfXYZCcyTFtC11Z26+S4cSyEPiUJ8PL+iGuYpIbCsH+TN27bIxV/QczHXus/+iSCOfjMJsBffBC90mlzKXrw==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "6.0.0",
|
||||
"System.Configuration.ConfigurationManager": "4.4.0"
|
||||
}
|
||||
},
|
||||
"Fable.Remoting.DotnetClient": {
|
||||
"type": "Direct",
|
||||
"requested": "[3.37.0, )",
|
||||
"resolved": "3.37.0",
|
||||
"contentHash": "xVTr84e5Kbnb1p/q4iF3ez4EIn7TU2UFeIdA95VIEQ7l67qkcHiOFGlOlznGAjSgn/t2dOILZVvgkAMQmWO5rw==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "6.0.0",
|
||||
"Fable.Remoting.Json": "2.25.0",
|
||||
"Fable.Remoting.MsgPack": "1.25.0"
|
||||
}
|
||||
},
|
||||
"Fable.Remoting.Giraffe": {
|
||||
"type": "Direct",
|
||||
"requested": "[5.24.0, )",
|
||||
"resolved": "5.24.0",
|
||||
"contentHash": "z3QZWK3/WaEmRFimt65Rc3eegA4VKFoqLH0xuQfDVdnpY3uhxe5aHOYVxFvgYPYId4XGtDzA9dFH4OyIwO1bJA==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "6.0.0",
|
||||
"Fable.Remoting.Server": "5.42.0",
|
||||
"Giraffe": "5.0.0",
|
||||
"Microsoft.IO.RecyclableMemoryStream": "[3.0.0, 4.0.0)"
|
||||
}
|
||||
},
|
||||
"Fable.Remoting.MsgPack": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.25.0, )",
|
||||
"resolved": "1.25.0",
|
||||
"contentHash": "FyqSj8j8J0W7xTR8XJmk2Q6vZa0NlKUUjQtr/rQhCkV2r5uJ3gtT+2KSiMjdzemwV5X+9eUz6C5q1YN0t3ccug==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.2"
|
||||
}
|
||||
},
|
||||
"Fable.Remoting.Server": {
|
||||
"type": "Direct",
|
||||
"requested": "[5.42.0, )",
|
||||
"resolved": "5.42.0",
|
||||
"contentHash": "XRRYy2vuF64wV0VYb0pF1eNF0MR23P9MKG7dpdSA6QCjhFJyWR+YLPdxjz34uAFx8WdDb4ujXQ9YbYOsBQTxhw==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "6.0.0",
|
||||
"Fable.Remoting.Json": "2.25.0",
|
||||
"Fable.Remoting.MsgPack": "1.25.0",
|
||||
"Microsoft.AspNetCore.WebUtilities": "2.3.0",
|
||||
"Microsoft.IO.RecyclableMemoryStream": "[3.0.0, 4.0.0)",
|
||||
"Microsoft.Net.Http.Headers": "2.3.0"
|
||||
}
|
||||
},
|
||||
"FSharp.Core": {
|
||||
"type": "Direct",
|
||||
"requested": "[9.0.300, )",
|
||||
"resolved": "9.0.300",
|
||||
"contentHash": "TVt2J7RCE1KCS2IaONF+p8/KIZ1eHNbW+7qmKF6hGoD4tXl+o07ja1mPtFjMqRa5uHMFaTrGTPn/m945WnDLiQ=="
|
||||
},
|
||||
"FSharp.Data": {
|
||||
"type": "Direct",
|
||||
"requested": "[6.6.0, )",
|
||||
"resolved": "6.6.0",
|
||||
"contentHash": "AC87/VZV1iGrnK1uG6QhX22HZARlFiCqdlckzPDgdE3+fzc0ZNRR3Y4ny5vbLvfDj2rjFoJdmRZy6kzI1C/sQQ==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "6.0.1",
|
||||
"FSharp.Data.Csv.Core": "6.6.0",
|
||||
"FSharp.Data.Html.Core": "6.6.0",
|
||||
"FSharp.Data.Http": "6.6.0",
|
||||
"FSharp.Data.Json.Core": "6.6.0",
|
||||
"FSharp.Data.Runtime.Utilities": "6.6.0",
|
||||
"FSharp.Data.WorldBank.Core": "6.6.0",
|
||||
"FSharp.Data.Xml.Core": "6.6.0"
|
||||
}
|
||||
},
|
||||
"FSharp.SystemTextJson": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.4.36, )",
|
||||
"resolved": "1.4.36",
|
||||
"contentHash": "1xLk0SBF1nedD74B77rcArjD2d+DeZwbNI7BVCAyKwIBERo1VoX8Mf4AtX0OV2L3ZeM/XS1b6BuhXCyoh0KBnw==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.0",
|
||||
"System.Text.Json": "6.0.10"
|
||||
}
|
||||
},
|
||||
"FSharpPlus": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.8.0, )",
|
||||
"resolved": "1.8.0",
|
||||
"contentHash": "obZb1vX+wpJkcEB1pseN6ZGosLxY3SM5+M0KQVjeDR88aXs6MqNY7KR2PHfzm8b5ewlRURF8DugKYZC0X2cpTw==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "6.0.6"
|
||||
}
|
||||
},
|
||||
"Giraffe": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.2.0, )",
|
||||
"resolved": "8.2.0",
|
||||
"contentHash": "GlrgfkGKnKbbLfPBpXVBXBEca0wwz+iI9Ba74j3Hcx/rMXLFLqs0WjDhNVfsU7FB585e3VxEcb12XoyOFrsklA==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "6.0.0",
|
||||
"FSharp.SystemTextJson": "1.3.13",
|
||||
"Giraffe.ViewEngine": "1.4.0",
|
||||
"Microsoft.IO.RecyclableMemoryStream": "3.0.1",
|
||||
"System.Text.Json": "8.0.6"
|
||||
}
|
||||
},
|
||||
"Newtonsoft.Json": {
|
||||
"type": "Direct",
|
||||
"requested": "[13.0.4, )",
|
||||
"resolved": "13.0.4",
|
||||
"contentHash": "pdgNNMai3zv51W5aq268sujXUyx7SNdE2bj1wZcWjAQrKMFZV260lbqYop1d2GM67JI1huLRwxo9ZqnfF/lC6A=="
|
||||
},
|
||||
"prometheus-net.AspNetCore": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.2.1, )",
|
||||
"resolved": "8.2.1",
|
||||
"contentHash": "/4TfTvbwIDqpaKTiWvEsjUywiHYF9zZvGZF5sK15avoDsUO/WPQbKsF8TiMaesuphdFQPK2z52P0zk6j26V0rQ==",
|
||||
"dependencies": {
|
||||
"prometheus-net": "8.2.1"
|
||||
}
|
||||
},
|
||||
"Saturn": {
|
||||
"type": "Direct",
|
||||
"requested": "[0.17.0, )",
|
||||
"resolved": "0.17.0",
|
||||
"contentHash": "8tg379A5VKmOODy92LyULLkS3KJOK9zVtj8xEfOdkLxR+KAoh0iY3pA3p0PXs0TH5r8919bB2a4eJbdD36gr/w==",
|
||||
"dependencies": {
|
||||
"FSharp.Control.Websockets": "0.2.2",
|
||||
"Giraffe": "6.4.0",
|
||||
"Microsoft.AspNetCore.Authentication.JwtBearer": "6.0.3"
|
||||
}
|
||||
},
|
||||
"SecurityCodeScan": {
|
||||
"type": "Direct",
|
||||
"requested": "[3.5.4, )",
|
||||
"resolved": "3.5.4",
|
||||
"contentHash": "KNbIxteHSVMk/9K8vl+YQb865egCycsNP/YspbRMLxWLFQev3UiTvvt/iCwlEyB30VA1RitgWXmsw8CC61/4Cw=="
|
||||
},
|
||||
"Serilog": {
|
||||
"type": "Direct",
|
||||
"requested": "[4.3.0, )",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "+cDryFR0GRhsGOnZSKwaDzRRl4MupvJ42FhCE4zhQRVanX0Jpg6WuCBk59OVhVDPmab1bB+nRykAnykYELA9qQ=="
|
||||
},
|
||||
"Serilog.AspNetCore": {
|
||||
"type": "Direct",
|
||||
"requested": "[9.0.0, )",
|
||||
"resolved": "9.0.0",
|
||||
"contentHash": "JslDajPlBsn3Pww1554flJFTqROvK9zz9jONNQgn0D8Lx2Trw8L0A8/n6zEQK1DAZWXrJwiVLw8cnTR3YFuYsg==",
|
||||
"dependencies": {
|
||||
"Serilog": "4.2.0",
|
||||
"Serilog.Extensions.Hosting": "9.0.0",
|
||||
"Serilog.Formatting.Compact": "3.0.0",
|
||||
"Serilog.Settings.Configuration": "9.0.0",
|
||||
"Serilog.Sinks.Console": "6.0.0",
|
||||
"Serilog.Sinks.Debug": "3.0.0",
|
||||
"Serilog.Sinks.File": "6.0.0"
|
||||
}
|
||||
},
|
||||
"Serilog.Expressions": {
|
||||
"type": "Direct",
|
||||
"requested": "[5.0.0, )",
|
||||
"resolved": "5.0.0",
|
||||
"contentHash": "QhZjXtUcA2QfQRA60m+DfyIfidKsQV7HBstbYEDqzJKMbJH/KnKthkkjciRuYrmFE+scWv1JibC5LlXrdtOUmw==",
|
||||
"dependencies": {
|
||||
"Serilog": "4.0.0"
|
||||
}
|
||||
},
|
||||
"Thoth.Json.Giraffe": {
|
||||
"type": "Direct",
|
||||
"requested": "[6.0.0, )",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "Hl0HRrI+HeKGImVvIo2PhjYdYyDYHCOLuGeoTGaPGH6kbSWN+EcxY6yNW15zhlI/Huk6XL35KnALszQGwqH5mA==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "6.0.1",
|
||||
"Giraffe": "6.0.0-alpha-1",
|
||||
"Thoth.Json.Net": "5.0.0"
|
||||
}
|
||||
},
|
||||
"Thoth.Json.Net": {
|
||||
"type": "Direct",
|
||||
"requested": "[12.0.0, )",
|
||||
"resolved": "12.0.0",
|
||||
"contentHash": "n2YyONYdWCFS4Pu7wrqgV/l5tPuN+t3gxEfs/2RwqCiQkRnbgKs9dK61zHeZS5YganAoFbxLSwbaNL7SvSrS9g==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.2",
|
||||
"Fable.Core": "3.1.6",
|
||||
"Newtonsoft.Json": "13.0.1"
|
||||
}
|
||||
},
|
||||
"Fable.AST": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.2.1",
|
||||
"contentHash": "/4V6U7Qw/WIRRxm9NJ7b+YTXTRCTk6/YKeJnbKYaVbtT45MstA3jkFvRfV0FqVFtkG9AL4uccetreygTjK7nbQ=="
|
||||
},
|
||||
"Fable.Browser.Blob": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.1.4",
|
||||
"contentHash": "+6aq2ClPFbh/wFEtKuuIPwoZwi2GlUJ6RRykT0R+i95ZmK22nfE6iWpP+jfKxDnGgrm2AGC+btobAHWXbkC1Sw==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.2",
|
||||
"Fable.Core": "3.0.0"
|
||||
}
|
||||
},
|
||||
"Fable.Browser.Dom": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.4.4",
|
||||
"contentHash": "wFGefU1Idqkwa6kKdvz5oyHL5yP+i+cvTHPeJGlF22fnqOozFurFIuPqGToqpgl9DfyWr7MyNvTiLFOkfXNqnA==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.2",
|
||||
"Fable.Browser.Blob": "1.1.4",
|
||||
"Fable.Browser.Event": "1.4.4",
|
||||
"Fable.Browser.WebStorage": "1.0.4",
|
||||
"Fable.Core": "3.0.0"
|
||||
}
|
||||
},
|
||||
"Fable.Browser.Event": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.4.4",
|
||||
"contentHash": "jGwS4fjwUNBXgGuu+mklDp9sx4ql0E6Ml5F/CI+8gxwgmxG4IdBTo3yIgF8m0doy7Tc9fgBhOfP3xdGMDgJBBQ==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.2",
|
||||
"Fable.Browser.Gamepad": "1.0.3",
|
||||
"Fable.Core": "3.0.0"
|
||||
}
|
||||
},
|
||||
"Fable.Browser.Gamepad": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.3",
|
||||
"contentHash": "3mIdAR8uy7eQoL+Nyp72OVWnIbTegoFD6yCvnqaHeWE2MZ3+Lc9EPMKDiHjX3VbImd1uQI57zg+hQJ6MTekgEA==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.2",
|
||||
"Fable.Core": "3.0.0"
|
||||
}
|
||||
},
|
||||
"Fable.Browser.WebStorage": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.4",
|
||||
"contentHash": "kxKI1nM4TWIRjp4X0QfdbgdU3p1ite5E/to3/RqotnV5BTmS9Wl+yX38L0S4CTbnU+I6v8aPLYlZ/BKmtxggXQ==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.2",
|
||||
"Fable.Browser.Event": "1.4.4",
|
||||
"Fable.Core": "3.0.0"
|
||||
}
|
||||
},
|
||||
"Fable.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.2.7",
|
||||
"contentHash": "XYvisWEqzYap+9USQGRyI4F9DmZY5EE3STMiwnvlSrwoqHmZRPHyQR3Rp2oGv/mpzNy7PViKdgCiKC/eviG2Ng==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.2"
|
||||
}
|
||||
},
|
||||
"Fable.React.Types": {
|
||||
"type": "Transitive",
|
||||
"resolved": "18.3.0",
|
||||
"contentHash": "/b8WZ3Bdfhqy9r60ZK9JGZaGNjIMb0ogsrvWIg3k7KfCEvJs5X6+7hCybVkyjVoxwzn9wLyYGRbh5wmuHQT/Vg==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.2",
|
||||
"Fable.Browser.Dom": "2.4.4",
|
||||
"Fable.Core": "3.2.7"
|
||||
}
|
||||
},
|
||||
"Fable.ReactDom.Types": {
|
||||
"type": "Transitive",
|
||||
"resolved": "18.2.0",
|
||||
"contentHash": "2WoBjsLiSgrvR68OJXko0iVaqeMbkPM5Bx813A1WlxOSCJ50M9fwnlwG/MUEZtiOIhQmku/YTJY5a8E8r1+j2Q==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.2",
|
||||
"Fable.React.Types": "18.3.0"
|
||||
}
|
||||
},
|
||||
"Fable.Remoting.Json": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.25.0",
|
||||
"contentHash": "ycmxT5L7jUKLkSJ4uti+WiX1OU50UYhLLiFQwJpQzwPWpNbSXowPjUtGycl8G5429edzeIHGW/77hUlIAufiAg==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "6.0.0",
|
||||
"Newtonsoft.Json": "13.0.3"
|
||||
}
|
||||
},
|
||||
"Feliz": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.9.0",
|
||||
"contentHash": "8nyGREGA60RysdSBamVWmr68MG+3lLy76W17fBiGaKi7uMFbtRcYBLyNtp2NyGZFfnuWCEyDAmAXM5YFnDhbhg==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.2",
|
||||
"Fable.ReactDom.Types": "18.2.0",
|
||||
"Feliz.CompilerPlugins": "2.2.0"
|
||||
}
|
||||
},
|
||||
"Feliz.CompilerPlugins": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "ACkO++Hp4lUrEx/axeehIL5/3R8jMnak+CYpzd0/kLpejp9BETtrgjHK7oj6Lh3V9fB7WoAKsCxyPSrm4ADN2w==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.7.2",
|
||||
"Fable.AST": "4.2.1"
|
||||
}
|
||||
},
|
||||
"Feliz.ViewEngine": {
|
||||
"type": "Transitive",
|
||||
"resolved": "0.27.0",
|
||||
"contentHash": "D6OLAuwPUoxa1VcBLjcs/eArfmqxdtyv1N0piaup40lcDjt4V7CQURr7EpUaOkTU770Hbmi2V3RCgq7XmL17Zw==",
|
||||
"dependencies": {
|
||||
"Fsharp.Core": "[4.7.0, 5.0.0)"
|
||||
}
|
||||
},
|
||||
"FSharp.Control.Websockets": {
|
||||
"type": "Transitive",
|
||||
"resolved": "0.2.2",
|
||||
"contentHash": "MAHcOmbJdG6NNHBiylN8wIfz/mZ7WvWNYCKK5vXxkD/1tFYdN8l1UsRmoUvIqICRCAQ050qU4hpmaa/B2hd67g==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "4.3.4",
|
||||
"Microsoft.IO.RecyclableMemoryStream": "1.2.2"
|
||||
}
|
||||
},
|
||||
"FSharp.Data.Csv.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.6.0",
|
||||
"contentHash": "gbmHT+XraE1OjUyf/+bmIaluoMMj/34MC5IX0QNWr+K8fu+UjKg854p+fkG5JkzWej0+46CTV7hW/CWP425naA==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "6.0.1",
|
||||
"FSharp.Data.Runtime.Utilities": "6.6.0"
|
||||
}
|
||||
},
|
||||
"FSharp.Data.Html.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.6.0",
|
||||
"contentHash": "YxImykk5fUxw+rwvD26JfZYn/OAUataebsUobXu8KzkLtl/5fOhiPqpI54jAT4ysv2wHD5coCp30+5UsQXqxBQ==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "6.0.1",
|
||||
"FSharp.Data.Csv.Core": "6.6.0",
|
||||
"FSharp.Data.Runtime.Utilities": "6.6.0"
|
||||
}
|
||||
},
|
||||
"FSharp.Data.Http": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.6.0",
|
||||
"contentHash": "7nFDLYPqEps/F3Ay65Xm3YvS3OmARYB24xYv1CszOqurTZ76wM9vnsvyeFOMcCKXA+QgwK+eCa8cgyavmR1YxA==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "6.0.1"
|
||||
}
|
||||
},
|
||||
"FSharp.Data.Json.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.6.0",
|
||||
"contentHash": "VhLkZwyzeMWsJHivY4GjLqIWh+gIm0rWW0BBlAh1M7XzmmumbPLDehUqOh6Nkm1R+N8iBhFgj7HBq6bmQzGnZA==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "6.0.1",
|
||||
"FSharp.Data.Http": "6.6.0",
|
||||
"FSharp.Data.Runtime.Utilities": "6.6.0"
|
||||
}
|
||||
},
|
||||
"FSharp.Data.Runtime.Utilities": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.6.0",
|
||||
"contentHash": "iLkCY0mQGxuIE2sEIFFOov9ZmWPwlQ0Jq51Xgqv+e0hbAPf2p4OaGp/DR4ye6VQCyVzHVCp9XyJQeHOhoWCQvA==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "6.0.1",
|
||||
"FSharp.Data.Http": "6.6.0"
|
||||
}
|
||||
},
|
||||
"FSharp.Data.WorldBank.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.6.0",
|
||||
"contentHash": "Tg6Hk0+61McNviq+zATBhIDoyslV07hZjYsrzz6IHagbze+1JNiz8FCpwsHh0uAzkdNUZK2/qsrHtMQvM0lpdg==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "6.0.1",
|
||||
"FSharp.Data.Http": "6.6.0",
|
||||
"FSharp.Data.Json.Core": "6.6.0",
|
||||
"FSharp.Data.Runtime.Utilities": "6.6.0"
|
||||
}
|
||||
},
|
||||
"FSharp.Data.Xml.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.6.0",
|
||||
"contentHash": "Z7U4mO0FHT5uHOYRLSK67izqJrCSvWpjcDPRQkJmYB5ge0sRfHWoKV8b4ttHLmpEg/eAdu64u00nITxpbZm+CA==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "6.0.1",
|
||||
"FSharp.Data.Http": "6.6.0",
|
||||
"FSharp.Data.Json.Core": "6.6.0",
|
||||
"FSharp.Data.Runtime.Utilities": "6.6.0"
|
||||
}
|
||||
},
|
||||
"Giraffe.ViewEngine": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.4.0",
|
||||
"contentHash": "gWzlp8KGDLaPHMm/xGzfLT1e9wZaJhBKhEuMsPRBSn7wAMkSgMcaqJlWl8mFGCcOOx0r9UyDJvCJJQyljYmo+g==",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "5.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.AspNetCore.Authentication.JwtBearer": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.3",
|
||||
"contentHash": "yXadCTYfe0AhgVZxLhG4ltra1BhteDumot2YRtQLOpJIVIPzanNsbFjeXomXiWWiU8qWNrK++gGYox3DaJBThg==",
|
||||
"dependencies": {
|
||||
"Microsoft.IdentityModel.Protocols.OpenIdConnect": "6.10.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.AspNetCore.WebUtilities": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.3.0",
|
||||
"contentHash": "trbXdWzoAEUVd0PE2yTopkz4kjZaAIA7xUWekd5uBw+7xE8Do/YOVTeb9d9koPTlbtZT539aESJjSLSqD8eYrQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Net.Http.Headers": "2.3.0",
|
||||
"System.Text.Encodings.Web": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.CSharp": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.0",
|
||||
"contentHash": "kaj6Wb4qoMuH3HySFJhxwQfe8R/sJsNJnANrvv8WdFPMoNbKY5htfNscv+LHCu5ipz+49m2e+WQXpLXr9XYemQ=="
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.0",
|
||||
"contentHash": "lqvd7W3FGKUO1+ZoUEMaZ5XDJeWvjpy2/M/ptCGz3tXLD4HWVaSzjufsAsjemasBEg+2SxXVtYVvGt5r2nKDlg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Primitives": "9.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.Binder": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.0",
|
||||
"contentHash": "RiScL99DcyngY9zJA2ROrri7Br8tn5N4hP4YNvGdTN/bvg1A3dwvDOxHnNZ3Im7x2SJ5i4LkX1uPiR/MfSFBLQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.0",
|
||||
"contentHash": "MCPrg7v3QgNMr0vX4vzRXvkNGgLg8vKWX0nKCWUxu2uPyMsaRgiRc1tHBnbTcfJMhMKj2slE/j2M9oGkd25DNw==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.0",
|
||||
"contentHash": "+6f2qv2a3dLwd5w6JanPIPs47CxRbnk+ZocMJUhv9NxP88VlOcJYZs9jY+MYSjxvady08bUZn6qgiNh7DadGgg=="
|
||||
},
|
||||
"Microsoft.Extensions.DependencyModel": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.0",
|
||||
"contentHash": "saxr2XzwgDU77LaQfYFXmddEDRUKHF4DaGMZkNB3qjdVSZlax3//dGJagJkKrGMIPNZs2jVFXITyCCR6UHJNdA=="
|
||||
},
|
||||
"Microsoft.Extensions.Diagnostics.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.0",
|
||||
"contentHash": "1K8P7XzuzX8W8pmXcZjcrqS6x5eSSdvhQohmcpgiQNY/HlDAlnrhR9dvlURfFz428A+RTCJpUyB+aKTA6AgVcQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0",
|
||||
"Microsoft.Extensions.Options": "9.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.0",
|
||||
"contentHash": "uK439QzYR0q2emLVtYzwyK3x+T5bTY4yWsd/k/ZUS9LR6Sflp8MIdhGXW8kQCd86dQD4tLqvcbLkku8qHY263Q==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Primitives": "9.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Hosting.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.0",
|
||||
"contentHash": "yUKJgu81ExjvqbNWqZKshBbLntZMbMVz/P7Way2SBx7bMqA08Mfdc9O7hWDKAiSp+zPUGT6LKcSCQIPeDK+CCw==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.0",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0",
|
||||
"Microsoft.Extensions.Diagnostics.Abstractions": "9.0.0",
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.0",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Http": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.1.0",
|
||||
"contentHash": "DLigdcV0nYaT6/ly0rnfP80BnXq8NNd/h8/SkfY39uio7Bd9LauVntp6RcRh1Kj23N+uf80GgL7Win6P3BCtoQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "3.1.0",
|
||||
"Microsoft.Extensions.Logging": "3.1.0",
|
||||
"Microsoft.Extensions.Options": "3.1.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.0",
|
||||
"contentHash": "crjWyORoug0kK7RSNJBTeSE6VX8IQgLf3nUpTB9m62bPXp/tzbnOsnbe8TXEG0AASNaKZddnpHKw7fET8E++Pg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "9.0.0",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.0",
|
||||
"Microsoft.Extensions.Options": "9.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.0",
|
||||
"contentHash": "g0UfujELzlLbHoVG8kPKVBaW470Ewi+jnptGS9KUi6jcb+k2StujtK3m26DFSGGwQ/+bVgZfsWqNzlP6YOejvw==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.ObjectPool": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.0",
|
||||
"contentHash": "udvKco0sAVgYGTBnHUb0tY9JQzJ/nPDiv/8PIyz69wl1AibeCDZOLVVI+6156dPfHmJH7ws5oUJRiW4ZmAvuuA=="
|
||||
},
|
||||
"Microsoft.Extensions.Options": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.0",
|
||||
"contentHash": "y2146b3jrPI3Q0lokKXdKLpmXqakYbDIPDV6r3M8SqvSf45WwOTzkyfDpxnZXJsJQEpAsAqjUq5Pu8RCJMjubg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0",
|
||||
"Microsoft.Extensions.Primitives": "9.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Primitives": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.0",
|
||||
"contentHash": "N3qEBzmLMYiASUlKxxFIISP4AiwuPTHF5uCh+2CWSwwzAJiIYx0kBJsS30cp1nvhSySFAVi30jecD307jV+8Kg=="
|
||||
},
|
||||
"Microsoft.IdentityModel.JsonWebTokens": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.10.0",
|
||||
"contentHash": "0qjS31rN1MQTc46tAYbzmMTSRfdV5ndZxSjYxIGqKSidd4wpNJfNII/pdhU5Fx8olarQoKL9lqqYw4yNOIwT0Q==",
|
||||
"dependencies": {
|
||||
"Microsoft.IdentityModel.Tokens": "6.10.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.IdentityModel.Logging": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.10.0",
|
||||
"contentHash": "zbcwV6esnNzhZZ/VP87dji6VrUBLB5rxnZBkDMqNYpyG+nrBnBsbm4PUYLCBMUflHCM9EMLDG0rLnqqT+l0ldA=="
|
||||
},
|
||||
"Microsoft.IdentityModel.Protocols": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.10.0",
|
||||
"contentHash": "DFyXD0xylP+DknCT3hzJ7q/Q5qRNu0hO/gCU90O0ATdR0twZmlcuY9RNYaaDofXKVbzcShYNCFCGle2G/o8mkg==",
|
||||
"dependencies": {
|
||||
"Microsoft.IdentityModel.Logging": "6.10.0",
|
||||
"Microsoft.IdentityModel.Tokens": "6.10.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.IdentityModel.Protocols.OpenIdConnect": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.10.0",
|
||||
"contentHash": "LVvMXAWPbPeEWTylDrxunlHH2wFyE4Mv0L4gZrJHC4HTESbWHquKZb/y/S8jgiQEDycOP0PDQvbG4RR/tr2TVQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.IdentityModel.Protocols": "6.10.0",
|
||||
"System.IdentityModel.Tokens.Jwt": "6.10.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.IdentityModel.Tokens": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.10.0",
|
||||
"contentHash": "qbf1NslutDB4oLrriYTJpy7oB1pbh2ej2lEHd2IPDQH9C74ysOdhU5wAC7KoXblldbo7YsNR2QYFOqQM/b0Rsg==",
|
||||
"dependencies": {
|
||||
"Microsoft.CSharp": "4.5.0",
|
||||
"Microsoft.IdentityModel.Logging": "6.10.0",
|
||||
"System.Security.Cryptography.Cng": "4.5.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.IO.RecyclableMemoryStream": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.0.1",
|
||||
"contentHash": "s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g=="
|
||||
},
|
||||
"Microsoft.Net.Http.Headers": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.3.0",
|
||||
"contentHash": "/M0wVg6tJUOHutWD3BMOUVZAioJVXe0tCpFiovzv0T9T12TBf4MnaHP0efO8TCr1a6O9RZgQeZ9Gdark8L9XdA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Primitives": "8.0.0",
|
||||
"System.Buffers": "4.6.0"
|
||||
}
|
||||
},
|
||||
"prometheus-net": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.2.1",
|
||||
"contentHash": "3wVgdEPOCBF752s2xps5T+VH+c9mJK8S8GKEDg49084P6JZMumTZI5Te6aJ9MQpX0sx7om6JOnBpIi7ZBmmiDQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Http": "3.1.0",
|
||||
"Microsoft.Extensions.ObjectPool": "7.0.0"
|
||||
}
|
||||
},
|
||||
"Serilog.Extensions.Hosting": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.0",
|
||||
"contentHash": "u2TRxuxbjvTAldQn7uaAwePkWxTHIqlgjelekBtilAGL5sYyF3+65NWctN4UrwwGLsDC7c3Vz3HnOlu+PcoxXg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0",
|
||||
"Microsoft.Extensions.Hosting.Abstractions": "9.0.0",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.0",
|
||||
"Serilog": "4.2.0",
|
||||
"Serilog.Extensions.Logging": "9.0.0"
|
||||
}
|
||||
},
|
||||
"Serilog.Extensions.Logging": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.0",
|
||||
"contentHash": "NwSSYqPJeKNzl5AuXVHpGbr6PkZJFlNa14CdIebVjK3k/76kYj/mz5kiTRNVSsSaxM8kAIa1kpy/qyT9E4npRQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Logging": "9.0.0",
|
||||
"Serilog": "4.2.0"
|
||||
}
|
||||
},
|
||||
"Serilog.Formatting.Compact": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.0.0",
|
||||
"contentHash": "wQsv14w9cqlfB5FX2MZpNsTawckN4a8dryuNGbebB/3Nh1pXnROHZov3swtu3Nj5oNG7Ba+xdu7Et/ulAUPanQ==",
|
||||
"dependencies": {
|
||||
"Serilog": "4.0.0"
|
||||
}
|
||||
},
|
||||
"Serilog.Settings.Configuration": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.0",
|
||||
"contentHash": "4/Et4Cqwa+F88l5SeFeNZ4c4Z6dEAIKbu3MaQb2Zz9F/g27T5a3wvfMcmCOaAiACjfUb4A6wrlTVfyYUZk3RRQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Binder": "9.0.0",
|
||||
"Microsoft.Extensions.DependencyModel": "9.0.0",
|
||||
"Serilog": "4.2.0"
|
||||
}
|
||||
},
|
||||
"Serilog.Sinks.Console": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "fQGWqVMClCP2yEyTXPIinSr5c+CBGUvBybPxjAGcf7ctDhadFhrQw03Mv8rJ07/wR5PDfFjewf2LimvXCDzpbA==",
|
||||
"dependencies": {
|
||||
"Serilog": "4.0.0"
|
||||
}
|
||||
},
|
||||
"Serilog.Sinks.Debug": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.0.0",
|
||||
"contentHash": "4BzXcdrgRX7wde9PmHuYd9U6YqycCC28hhpKonK7hx0wb19eiuRj16fPcPSVp0o/Y1ipJuNLYQ00R3q2Zs8FDA==",
|
||||
"dependencies": {
|
||||
"Serilog": "4.0.0"
|
||||
}
|
||||
},
|
||||
"Serilog.Sinks.File": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "lxjg89Y8gJMmFxVkbZ+qDgjl+T4yC5F7WSLTvA+5q0R04tfKVLRL/EHpYoJ/MEQd2EeCKDuylBIVnAYMotmh2A==",
|
||||
"dependencies": {
|
||||
"Serilog": "4.0.0"
|
||||
}
|
||||
},
|
||||
"System.Buffers": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.6.0",
|
||||
"contentHash": "lN6tZi7Q46zFzAbRYXTIvfXcyvQQgxnY7Xm6C6xQ9784dEL1amjM6S6Iw4ZpsvesAKnRVsM4scrDQaDqSClkjA=="
|
||||
},
|
||||
"System.Configuration.ConfigurationManager": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.4.0",
|
||||
"contentHash": "gWwQv/Ug1qWJmHCmN17nAbxJYmQBM/E94QxKLksvUiiKB1Ld3Sc/eK1lgmbSjDFxkQhVuayI/cGFZhpBSodLrg==",
|
||||
"dependencies": {
|
||||
"System.Security.Cryptography.ProtectedData": "4.4.0"
|
||||
}
|
||||
},
|
||||
"System.IdentityModel.Tokens.Jwt": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.10.0",
|
||||
"contentHash": "C+Q5ORsFycRkRuvy/Xd0Pv5xVpmWSAvQYZAGs7VQogmkqlLhvfZXTgBIlHqC3cxkstSoLJAYx6xZB7foQ2y5eg==",
|
||||
"dependencies": {
|
||||
"Microsoft.IdentityModel.JsonWebTokens": "6.10.0",
|
||||
"Microsoft.IdentityModel.Tokens": "6.10.0"
|
||||
}
|
||||
},
|
||||
"System.IO.Pipelines": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.0",
|
||||
"contentHash": "M1eb3nfXntaRJPrrMVM9EFS8I1bDTnt0uvUS6QP/SicZf/ZZjydMD5NiXxfmwW/uQwaMDP/yX2P+zQN1NBHChg=="
|
||||
},
|
||||
"System.Security.Cryptography.Cng": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.0",
|
||||
"contentHash": "WG3r7EyjUe9CMPFSs6bty5doUqT+q9pbI80hlNzo2SkPkZ4VTuZkGWjpp77JB8+uaL4DFPRdBsAY+DX3dBK92A=="
|
||||
},
|
||||
"System.Security.Cryptography.ProtectedData": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.4.0",
|
||||
"contentHash": "cJV7ScGW7EhatRsjehfvvYVBvtiSMKgN8bOVI0bQhnF5bU7vnHVIsH49Kva7i7GWaWYvmEzkYVk1TC+gZYBEog=="
|
||||
},
|
||||
"System.Text.Encodings.Web": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.0",
|
||||
"contentHash": "257hh1ep1Gqm1Lm0ulxf7vVBVMJuGN6EL4xSWjpi46DffXzm1058IiWsfSC06zSm7SniN+Tb5160UnXsSa8rRg=="
|
||||
},
|
||||
"System.Text.Json": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.0",
|
||||
"contentHash": "1Dpjwq9peG/Wt5BNbrzIhTpclfOSqBWZsUO28vVr59yQlkvL5jLBWfpfzRmJ1OY+6DciaY0DUcltyzs4fuZHjw==",
|
||||
"dependencies": {
|
||||
"System.IO.Pipelines": "10.0.0",
|
||||
"System.Text.Encodings.Web": "10.0.0"
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"FSharp.Core": "[9.0.303, )",
|
||||
"FSharp.Data": "[6.6.0, )",
|
||||
"FSharp.SystemTextJson": "[1.4.36, )",
|
||||
"Feliz": "[2.9.0, )",
|
||||
"Feliz.ViewEngine": "[0.27.0, )",
|
||||
"System.Text.Json": "[10.0.0, )",
|
||||
"Thoth.Json.Net": "[12.0.0, )"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/Server/public/style.css
Normal file
16
src/Server/public/style.css
Normal file
@@ -0,0 +1,16 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
.full-width-center {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.notification-bar {
|
||||
height: 75px;
|
||||
margin: 5px;
|
||||
border-style: dashed;
|
||||
border-width: 1px;
|
||||
border-color: darkblue;
|
||||
}
|
||||
42
src/Shared/Components/Hero.fs
Normal file
42
src/Shared/Components/Hero.fs
Normal file
@@ -0,0 +1,42 @@
|
||||
namespace Shared.Components
|
||||
|
||||
#if FABLE_COMPILER
|
||||
open Feliz
|
||||
#else
|
||||
open Feliz.ViewEngine
|
||||
#endif
|
||||
|
||||
open Shared.Data.ThothTypes
|
||||
|
||||
open type Html
|
||||
open type prop
|
||||
|
||||
module Hero =
|
||||
let baseUrl = "http://oceanbox.ddev.site/"
|
||||
|
||||
let render (hero: Hero) =
|
||||
let mediaUrl =
|
||||
match hero.field_background_image with
|
||||
| MediaVideo video -> baseUrl + video.field_media_video_file.uri.url
|
||||
| MediaImage image -> baseUrl + image.field_media_image.uri.url
|
||||
|
||||
let heroSize =
|
||||
match hero.field_hero_size with
|
||||
| "small" -> "min-h-[25vh]"
|
||||
| "medium" -> "min-h-[50vh]"
|
||||
| "large" -> "min-h-screen"
|
||||
| _ -> ""
|
||||
|
||||
div
|
||||
[ className $"hero {heroSize}"
|
||||
style [ style.backgroundImage $"url('{mediaUrl}')" ]
|
||||
children
|
||||
[ div [ className "hero-overlay" ]
|
||||
div
|
||||
[ className "hero-content text-neutral-content text-center"
|
||||
children
|
||||
[ div
|
||||
[ className "m-auto max-w-md"
|
||||
children
|
||||
[ h1 [ className "mb-5 text-5xl font-bold"; text hero.field_title ]
|
||||
h2 [ className "mb-5"; text hero.field_subtitle ] ] ] ] ] ] ]
|
||||
18
src/Shared/Components/Paragraph.fs
Normal file
18
src/Shared/Components/Paragraph.fs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace Shared.Components
|
||||
|
||||
#if FABLE_COMPILER
|
||||
open Feliz
|
||||
#else
|
||||
open Feliz.ViewEngine
|
||||
#endif
|
||||
|
||||
open Shared.Data.ThothTypes
|
||||
|
||||
module Paragraph =
|
||||
let render (paragraph: Paragraph) =
|
||||
match paragraph with
|
||||
| Hero hero ->
|
||||
Hero.render hero
|
||||
| SimpleText simple ->
|
||||
SimpleText.render simple
|
||||
|
||||
18
src/Shared/Components/SimpleText.fs
Normal file
18
src/Shared/Components/SimpleText.fs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace Shared.Components
|
||||
|
||||
#if FABLE_COMPILER
|
||||
open Feliz
|
||||
#else
|
||||
open Feliz.ViewEngine
|
||||
#endif
|
||||
|
||||
open Shared.Data.ThothTypes
|
||||
|
||||
open type Html
|
||||
open type prop
|
||||
|
||||
module SimpleText =
|
||||
let render (simple: SimpleText) =
|
||||
div
|
||||
[ className "m-auto max-w-7xl py-16 px-6 lg:px-8 prose"
|
||||
dangerouslySetInnerHTML simple.field_body ]
|
||||
112
src/Shared/Data/ThothSerializer.fs
Normal file
112
src/Shared/Data/ThothSerializer.fs
Normal file
@@ -0,0 +1,112 @@
|
||||
namespace Shared.Data
|
||||
#if FABLE_COMPILER
|
||||
open Thoth.Json
|
||||
#else
|
||||
open Thoth.Json.Net
|
||||
#endif
|
||||
open Shared.Data.ThothTypes
|
||||
|
||||
module ThothSerializerManual =
|
||||
|
||||
let pathDecoder = Decode.Auto.generateDecoder<Path>()
|
||||
let mediaImageDecoder = Decode.Auto.generateDecoder<MediaImage>()
|
||||
let mediaVideoDecoder = Decode.Auto.generateDecoder<MediaVideo>()
|
||||
let simpleTextDecoder = Decode.Auto.generateDecoder<SimpleText>()
|
||||
let pathEncoder = Encode.Auto.generateEncoder<Path>()
|
||||
let mediaImageEncoder = Encode.Auto.generateEncoder<MediaImage>()
|
||||
let mediaVideoEncoder = Encode.Auto.generateEncoder<MediaVideo>()
|
||||
let simpleTextEncoder = Encode.Auto.generateEncoder<SimpleText>()
|
||||
let linkHrefDecoder = Decode.Auto.generateDecoder<LinkHref>()
|
||||
let linkHrefEncoder = Encode.Auto.generateEncoder<LinkHref>()
|
||||
let paginationLinksDecoder = Decode.Auto.generateDecoder<PaginationLinks>()
|
||||
let paginationLinksEncoder = Encode.Auto.generateEncoder<PaginationLinks>()
|
||||
|
||||
let mediaDecoder : Decoder<Media> =
|
||||
Decode.field "type" Decode.string
|
||||
|> Decode.andThen (fun t ->
|
||||
match t with
|
||||
| "media--image" ->
|
||||
mediaImageDecoder |> Decode.map MediaImage
|
||||
| "media--video" ->
|
||||
mediaVideoDecoder |> Decode.map MediaVideo
|
||||
| other ->
|
||||
Decode.fail $"Unsupported media type: {other}"
|
||||
)
|
||||
|
||||
let mediaEncoder (m: Media) =
|
||||
match m with
|
||||
| MediaImage x -> mediaImageEncoder x
|
||||
| MediaVideo x -> mediaVideoEncoder x
|
||||
|
||||
let heroDecoder : Decoder<Hero> =
|
||||
Decode.object (fun get ->
|
||||
{ ``type`` = get.Required.Field "type" Decode.string
|
||||
field_title = get.Required.Field "field_title" Decode.string
|
||||
field_subtitle = get.Required.Field "field_subtitle" Decode.string
|
||||
field_hero_size = get.Required.Field "field_hero_size" Decode.string
|
||||
field_background_image = get.Required.Field "field_background_image" mediaDecoder }
|
||||
)
|
||||
|
||||
let heroEncoder (hero: Hero) =
|
||||
Encode.object
|
||||
[ "type", Encode.string hero.``type``
|
||||
"field_title", Encode.string hero.field_title
|
||||
"field_subtitle", Encode.string hero.field_subtitle
|
||||
"field_hero_size", Encode.string hero.field_hero_size
|
||||
"field_background_image", mediaEncoder hero.field_background_image ]
|
||||
|
||||
let paragraphDecoder : Decoder<Paragraph> =
|
||||
Decode.field "type" Decode.string
|
||||
|> Decode.andThen (fun t ->
|
||||
match t with
|
||||
| "paragraph--hero_banner" ->
|
||||
heroDecoder |> Decode.map Hero
|
||||
| "paragraph--simple_text" ->
|
||||
simpleTextDecoder |> Decode.map SimpleText
|
||||
| other ->
|
||||
Decode.fail $"Unsupported paragraph type: {other}"
|
||||
)
|
||||
|
||||
let paragraphEncoder (paragraph: Paragraph) =
|
||||
match paragraph with
|
||||
| Hero hero -> heroEncoder hero
|
||||
| SimpleText simpleText -> simpleTextEncoder simpleText
|
||||
|
||||
let docDataDecoder : Decoder<DocData> =
|
||||
Decode.object (fun get ->
|
||||
{ ``type`` = get.Required.Field "type" Decode.string
|
||||
id = get.Required.Field "id" Decode.string
|
||||
title = get.Required.Field "title" Decode.string
|
||||
path = get.Required.Field "path" pathDecoder
|
||||
created = get.Required.Field "created" Decode.string
|
||||
changed = get.Required.Field "changed" Decode.string
|
||||
field_content = get.Required.Field "field_content" (Decode.array paragraphDecoder) }
|
||||
)
|
||||
|
||||
let docDataEncoder (data: DocData) =
|
||||
Encode.object
|
||||
[ "type", Encode.string data.``type``
|
||||
"id", Encode.string data.id
|
||||
"title", Encode.string data.title
|
||||
"path", pathEncoder data.path
|
||||
"created", Encode.string data.created
|
||||
"changed", Encode.string data.changed
|
||||
"field_content", data.field_content |> Array.map paragraphEncoder |> Encode.array ]
|
||||
|
||||
let pageDecoder : Decoder<JsonApiContent> =
|
||||
Decode.object (fun get ->
|
||||
{ data = get.Required.Field "data" (Decode.array docDataDecoder)
|
||||
links = get.Required.Field "links" paginationLinksDecoder }
|
||||
)
|
||||
|
||||
let pageEncoder (content: JsonApiContent) =
|
||||
Encode.object
|
||||
[ "data", content.data |> Array.map docDataEncoder |> Encode.array
|
||||
"links", paginationLinksEncoder content.links ]
|
||||
|
||||
let entityDecoder : Decoder<EntityContent> =
|
||||
Decode.object ( fun get -> { data = get.Required.Field "data" docDataDecoder } )
|
||||
|
||||
let entityEncoder (entity: EntityContent) =
|
||||
Encode.object [ "data", docDataEncoder entity.data ]
|
||||
|
||||
103
src/Shared/Data/ThothTypes.fs
Normal file
103
src/Shared/Data/ThothTypes.fs
Normal file
@@ -0,0 +1,103 @@
|
||||
namespace Shared.Data
|
||||
|
||||
module ThothTypes =
|
||||
|
||||
type Path = {
|
||||
alias: string
|
||||
langcode: string
|
||||
}
|
||||
|
||||
type Uri = {
|
||||
url: string
|
||||
}
|
||||
|
||||
type ImageMeta = {
|
||||
alt: string
|
||||
title: string
|
||||
width: string
|
||||
height: string
|
||||
}
|
||||
|
||||
type VideoMeta = {
|
||||
display: string
|
||||
description: string
|
||||
}
|
||||
|
||||
type ImageFile = {
|
||||
uri: Uri
|
||||
meta: ImageMeta
|
||||
}
|
||||
|
||||
type VideoFile = {
|
||||
uri: Uri
|
||||
meta: VideoMeta
|
||||
}
|
||||
|
||||
type MediaImage = {
|
||||
``type``: string
|
||||
field_media_image: ImageFile
|
||||
}
|
||||
|
||||
type MediaVideo = {
|
||||
``type``: string
|
||||
field_media_video_file: VideoFile
|
||||
}
|
||||
|
||||
type Media =
|
||||
| MediaImage of MediaImage
|
||||
| MediaVideo of MediaVideo
|
||||
|
||||
type Hero = {
|
||||
``type``: string
|
||||
field_title: string
|
||||
field_subtitle: string
|
||||
field_hero_size: string
|
||||
field_background_image: Media
|
||||
}
|
||||
|
||||
type SimpleText = {
|
||||
``type``: string
|
||||
field_body: string
|
||||
}
|
||||
|
||||
type Paragraph =
|
||||
| Hero of Hero
|
||||
| SimpleText of SimpleText
|
||||
|
||||
type DocData = {
|
||||
``type``: string
|
||||
id: string
|
||||
title: string
|
||||
path: Path
|
||||
created: string
|
||||
changed: string
|
||||
field_content: Paragraph[]
|
||||
}
|
||||
|
||||
type LinkHref = {
|
||||
href: string
|
||||
}
|
||||
|
||||
type PaginationLinks = {
|
||||
next: LinkHref option
|
||||
}
|
||||
|
||||
type JsonApiContent = {
|
||||
data: DocData[]
|
||||
links: PaginationLinks
|
||||
}
|
||||
|
||||
type EntityContent = {
|
||||
data: DocData
|
||||
}
|
||||
|
||||
type DecoupledPageJsonApi = {
|
||||
individual: string
|
||||
}
|
||||
|
||||
type DecoupledPage = {
|
||||
resolved: string
|
||||
isExternal: bool
|
||||
isHomePath: bool
|
||||
jsonapi: DecoupledPageJsonApi
|
||||
}
|
||||
76
src/Shared/Data/_ThothSerializer.fs
Normal file
76
src/Shared/Data/_ThothSerializer.fs
Normal file
@@ -0,0 +1,76 @@
|
||||
namespace Shared.Data
|
||||
|
||||
open Thoth.Json.Net
|
||||
open Shared.Data.ThothTypes
|
||||
|
||||
module ThothSerializer =
|
||||
// MEDIA DECODERS & ENCODERS
|
||||
let imageDecoder = Decode.Auto.generateDecoder<MediaImage>()
|
||||
let videoDecoder = Decode.Auto.generateDecoder<MediaVideo>()
|
||||
let imageEncoder = Encode.Auto.generateEncoder<MediaImage>()
|
||||
let videoEncoder = Encode.Auto.generateEncoder<MediaVideo>()
|
||||
|
||||
let mediaDecoder: Decoder<Media> =
|
||||
Decode.field "type" Decode.string
|
||||
|> Decode.andThen ( fun t ->
|
||||
match t with
|
||||
| "media--image" ->
|
||||
imageDecoder
|
||||
|> Decode.map MediaImage
|
||||
| "media--video" ->
|
||||
videoDecoder
|
||||
|> Decode.map MediaVideo
|
||||
| other ->
|
||||
Decode.fail $"Unsupported media type: {other}"
|
||||
)
|
||||
|
||||
let mediaEncoder (item: Media) =
|
||||
match item with
|
||||
| MediaImage img -> imageEncoder img
|
||||
| MediaVideo video -> videoEncoder video
|
||||
|
||||
let extraMedia =
|
||||
Extra.empty
|
||||
|> Extra.withCustom mediaEncoder mediaDecoder
|
||||
|
||||
// PARAGRAPH DECODERS & ENCODERS
|
||||
let heroDecoder = Decode.Auto.generateDecoder<Hero>(extra = extraMedia)
|
||||
let simpleTextDecoder = Decode.Auto.generateDecoder<SimpleText>()
|
||||
let heroEncoder = Encode.Auto.generateEncoder<Hero>(extra = extraMedia)
|
||||
let simpleTextEncoder = Encode.Auto.generateEncoder<SimpleText>()
|
||||
|
||||
let paragraphDecoder: Decoder<Paragraph> =
|
||||
Decode.field "type" Decode.string
|
||||
|> Decode.andThen ( fun t ->
|
||||
match t with
|
||||
| "paragraph--simple_text" ->
|
||||
simpleTextDecoder
|
||||
|> Decode.map SimpleText
|
||||
| "paragraph--hero_banner" ->
|
||||
heroDecoder
|
||||
|> Decode.map Hero
|
||||
| other ->
|
||||
Decode.fail $"Unsupported paragraph type: {other}"
|
||||
)
|
||||
|
||||
let paragraphEncoder (item: Paragraph) =
|
||||
match item with
|
||||
| Hero hero -> heroEncoder hero
|
||||
| SimpleText simpleText -> simpleTextEncoder simpleText
|
||||
|
||||
let extraTypes =
|
||||
Extra.empty
|
||||
|> Extra.withCustom mediaEncoder mediaDecoder
|
||||
|> Extra.withCustom paragraphEncoder paragraphDecoder
|
||||
|
||||
let pageDecoder: Decoder<JsonApiContent> =
|
||||
Decode.Auto.generateDecoder<JsonApiContent>(extra = extraTypes)
|
||||
|
||||
let pageEncoder: Encoder<JsonApiContent> =
|
||||
Encode.Auto.generateEncoder<JsonApiContent>(extra = extraTypes)
|
||||
|
||||
let entityDecoder: Decoder<EntityContent> =
|
||||
Decode.Auto.generateDecoder<EntityContent>(extra = extraTypes)
|
||||
|
||||
let entityEncoder: Encoder<EntityContent> =
|
||||
Encode.Auto.generateEncoder<EntityContent>(extra = extraTypes)
|
||||
54
src/Shared/Layout/Layout.fs
Normal file
54
src/Shared/Layout/Layout.fs
Normal file
@@ -0,0 +1,54 @@
|
||||
namespace Shared
|
||||
|
||||
#if FABLE_COMPILER
|
||||
open Feliz
|
||||
#else
|
||||
open Feliz.ViewEngine
|
||||
#endif
|
||||
|
||||
open type Html
|
||||
open type prop
|
||||
|
||||
module Layout =
|
||||
let layout (headElement: ReactElement) (footerElement: ReactElement) (bodyContent: ReactElement) =
|
||||
html
|
||||
[ headElement
|
||||
body
|
||||
[ div [ id "app"; children bodyContent ]
|
||||
script [ src "/js/index.js" ]
|
||||
footerElement ] ]
|
||||
|
||||
let defaultLayout (pageTitle: string) (pageData: string) =
|
||||
let headElement =
|
||||
head
|
||||
[ Html.title pageTitle
|
||||
meta [ charset "utf-8" ]
|
||||
link [ rel "stylesheet"; href "/tailwindStyle.css" ]
|
||||
script
|
||||
[ id "__FORNIX_DATA__"
|
||||
type' "application/json"
|
||||
dangerouslySetInnerHTML pageData ] ]
|
||||
|
||||
let footerElement =
|
||||
footer
|
||||
[ className "footer sm:footer-horizontal bg-neutral text-neutral-content p-10"
|
||||
children
|
||||
[ nav
|
||||
[ children
|
||||
[ h6 [ className "footer-title"; text "Services" ]
|
||||
a [ className "link link-hover"; text "Branding" ]
|
||||
a [ className "link link-hover"; text "Design" ]
|
||||
a [ className "link link-hover"; text "Marketing" ] ] ]
|
||||
nav
|
||||
[ children
|
||||
[ h6 [ className "footer-title"; text "Company" ]
|
||||
a [ className "link link-hover"; text "About us" ]
|
||||
a [ className "link link-hover"; text "Jobs" ]
|
||||
a [ className "link link-hover"; text "Press kit" ] ] ]
|
||||
nav
|
||||
[ children
|
||||
[ h6 [ className "footer-title"; text "Legal" ]
|
||||
a [ className "link link-hover"; text "Terms of use" ]
|
||||
a [ className "link link-hover"; text "Privacy policy" ] ] ] ] ]
|
||||
|
||||
layout headElement footerElement
|
||||
20
src/Shared/Pages/BasicPage.fs
Normal file
20
src/Shared/Pages/BasicPage.fs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace Shared.Pages
|
||||
|
||||
#if FABLE_COMPILER
|
||||
open Feliz
|
||||
#else
|
||||
open Feliz.ViewEngine
|
||||
#endif
|
||||
|
||||
open Shared.Data.ThothTypes
|
||||
open Shared.Components
|
||||
open type Html
|
||||
open type prop
|
||||
|
||||
module BasicPage =
|
||||
let render (doc: DocData) =
|
||||
let paragraphs = doc.field_content |> Array.map Paragraph.render
|
||||
|
||||
main
|
||||
[ className ""
|
||||
children [ section [ className "content"; children (elems = paragraphs) ] ] ]
|
||||
13
src/Shared/Shared.fs
Normal file
13
src/Shared/Shared.fs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Shared
|
||||
|
||||
open System
|
||||
|
||||
module Api =
|
||||
let routeBuilder (typeName: string) (methodName: string) =
|
||||
$"/api/v1/{typeName}/{methodName}"
|
||||
|
||||
type notes =
|
||||
{
|
||||
getNotes: unit -> Async<string []>
|
||||
addNote: string -> Async<Result<string, string> >
|
||||
}
|
||||
27
src/Shared/Shared.fsproj
Normal file
27
src/Shared/Shared.fsproj
Normal file
@@ -0,0 +1,27 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Lib</OutputType>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Feliz" Version="2.9.0" />
|
||||
<PackageReference Include="FSharp.Data" Version="6.6.0" />
|
||||
<PackageReference Include="FSharp.SystemTextJson" Version="1.4.36" />
|
||||
<PackageReference Include="Feliz.ViewEngine" Version="0.27.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="10.0.0" />
|
||||
<PackageReference Include="Thoth.Json.Net" Version="12.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="Data/ThothTypes.fs" />
|
||||
<Compile Include="Data/ThothSerializer.fs" />
|
||||
<Compile Include="Components/Hero.fs" />
|
||||
<Compile Include="Components/SimpleText.fs" />
|
||||
<Compile Include="Components/Paragraph.fs" />
|
||||
<Compile Include="Layout/Layout.fs" />
|
||||
<Compile Include="Pages/BasicPage.fs" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
18
src/Shared/_Settings.fs
Normal file
18
src/Shared/_Settings.fs
Normal file
@@ -0,0 +1,18 @@
|
||||
module Settings
|
||||
|
||||
open System
|
||||
|
||||
let tryGetEnv =
|
||||
Environment.GetEnvironmentVariable
|
||||
>> function
|
||||
| null
|
||||
| "" -> None
|
||||
| x -> Some x
|
||||
|
||||
let env = defaultArg (tryGetEnv "APP_ENV") "dev"
|
||||
|
||||
let baseUrl =
|
||||
match env with
|
||||
// TODO - add actual production URL?
|
||||
| "prod" -> ""
|
||||
| _ -> "http://oceanbox.ddev.site"
|
||||
3
style/style.css
Executable file
3
style/style.css
Executable file
@@ -0,0 +1,3 @@
|
||||
body {
|
||||
}
|
||||
|
||||
25
test/Client/Client.Tests.fs
Normal file
25
test/Client/Client.Tests.fs
Normal file
@@ -0,0 +1,25 @@
|
||||
module Client.Tests
|
||||
|
||||
open Fable.Mocha
|
||||
|
||||
open Index
|
||||
open Shared
|
||||
|
||||
let client =
|
||||
testList
|
||||
"Client"
|
||||
[
|
||||
testCase "Added nonsense"
|
||||
<| fun _ ->
|
||||
Expect.equal 1 1 "There should be 1"
|
||||
]
|
||||
|
||||
let all =
|
||||
testList
|
||||
"All"
|
||||
[
|
||||
client
|
||||
]
|
||||
|
||||
[<EntryPoint>]
|
||||
let main _ = Mocha.runTests all
|
||||
18
test/Client/Client.Tests.fsproj
Normal file
18
test/Client/Client.Tests.fsproj
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<DefineConstants>FABLE_COMPILER</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Shared\Shared.Tests.fs" />
|
||||
<Compile Include="Client.Tests.fs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Client\Client.fsproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Fable.Mocha" Version="2.17.0" />
|
||||
<PackageReference Update="FSharp.Core" Version="8.0.400" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
9
test/Client/index.html
Normal file
9
test/Client/index.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>foo Client Tests</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script type="text/javascript" src="app.js"></script><script type="text/javascript" src="app.js"></script></body>
|
||||
</html>
|
||||
0
test/Client/public/style.scss
Normal file
0
test/Client/public/style.scss
Normal file
24
test/Server/Server.Tests.fs
Normal file
24
test/Server/Server.Tests.fs
Normal file
@@ -0,0 +1,24 @@
|
||||
module Server.Tests
|
||||
|
||||
open Expecto
|
||||
|
||||
open Shared
|
||||
open Server
|
||||
|
||||
let server =
|
||||
testList
|
||||
"Server"
|
||||
[
|
||||
testCase "Valid nonsense"
|
||||
<| fun _ ->
|
||||
let expectedResult = Ok ()
|
||||
|
||||
let result = Ok ()
|
||||
|
||||
Expect.equal result expectedResult "Result should be ok"
|
||||
]
|
||||
|
||||
let all = testList "All" [ Shared.Tests.shared; server ]
|
||||
|
||||
[<EntryPoint>]
|
||||
let main _ = runTests defaultConfig all
|
||||
18
test/Server/Server.Tests.fsproj
Normal file
18
test/Server/Server.Tests.fsproj
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Shared\Shared.Tests.fs" />
|
||||
<Compile Include="Server.Tests.fs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Server\Server.fsproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Expecto" Version="10.2.1" />
|
||||
<PackageReference Update="FSharp.Core" Version="8.0.400" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
15
test/Shared/Shared.Tests.fs
Normal file
15
test/Shared/Shared.Tests.fs
Normal file
@@ -0,0 +1,15 @@
|
||||
module Shared.Tests
|
||||
|
||||
open Expecto
|
||||
open Shared
|
||||
|
||||
let shared =
|
||||
testList
|
||||
"Shared"
|
||||
[
|
||||
testCase "Empty string is not a valid description"
|
||||
<| fun _ ->
|
||||
let expected = false
|
||||
let actual = false
|
||||
Expect.equal actual expected "Should be false"
|
||||
]
|
||||
9
tilt/base/deployment_patch.yaml
Normal file
9
tilt/base/deployment_patch.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
- op: replace
|
||||
path: /spec/template/spec/containers/0/livenessProbe/httpGet/path
|
||||
value: /healthz
|
||||
- op: replace
|
||||
path: /spec/template/spec/containers/0/readinessProbe/httpGet/path
|
||||
value: /healthz
|
||||
- op: add
|
||||
path: /spec/template/spec/containers/0/envFrom
|
||||
value: []
|
||||
15
tilt/base/kustomization.yaml
Normal file
15
tilt/base/kustomization.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: OceanWeb
|
||||
patches:
|
||||
- target:
|
||||
version: v1
|
||||
group: apps
|
||||
kind: Deployment
|
||||
path: deployment_patch.yaml
|
||||
- target:
|
||||
version: v1
|
||||
kind: Service
|
||||
path: service_patch.yaml
|
||||
resources:
|
||||
- _manifest.yaml
|
||||
7
tilt/base/service_patch.yaml
Normal file
7
tilt/base/service_patch.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
- op: add
|
||||
path: /spec/ports/-
|
||||
value:
|
||||
name: intra
|
||||
port: 8000
|
||||
protocol: TCP
|
||||
targetPort: 8000
|
||||
3
tilt/tilt/appsettings.json
Normal file
3
tilt/tilt/appsettings.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"Setting" : "example setting"
|
||||
}
|
||||
1
tilt/tilt/default.env
Normal file
1
tilt/tilt/default.env
Normal file
@@ -0,0 +1 @@
|
||||
SEQ_APIKEY=secret
|
||||
5
tilt/tilt/deployment_patch.yaml
Normal file
5
tilt/tilt/deployment_patch.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
- op: add
|
||||
path: /spec/template/spec/containers/0/envFrom/-
|
||||
value:
|
||||
secretRef:
|
||||
name: <x>-OceanWeb-env
|
||||
18
tilt/tilt/kustomization.yaml
Normal file
18
tilt/tilt/kustomization.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
generatorOptions:
|
||||
disableNameSuffixHash: true
|
||||
configMapGenerator:
|
||||
- name: <x>-OceanWeb-appsettings
|
||||
files:
|
||||
- appsettings.json
|
||||
secretGenerator:
|
||||
- name: <x>-OceanWeb-env
|
||||
envs:
|
||||
- default.env
|
||||
patches:
|
||||
- target:
|
||||
group: apps
|
||||
version: v1
|
||||
kind: Deployment
|
||||
path: deployment_patch.yaml
|
||||
resources:
|
||||
- ../base
|
||||
52
tilt/values.yaml
Normal file
52
tilt/values.yaml
Normal file
@@ -0,0 +1,52 @@
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
tag: latest
|
||||
|
||||
podAnnotations:
|
||||
dapr.io/app-id: "<x>-OceanWeb"
|
||||
dapr.io/enabled: "true"
|
||||
dapr.io/app-port: "8000"
|
||||
dapr.io/config: "tracing"
|
||||
dapr.io/app-protocol: "http"
|
||||
dapr.io/enable-app-health-check: "true"
|
||||
dapr.io/app-health-check-path: "/healthz"
|
||||
dapr.io/app-health-probe-interval: "3"
|
||||
dapr.io/app-health-probe-timeout: "200"
|
||||
dapr.io/app-health-threshold: "2"
|
||||
dapr.io/sidecar-cpu-request: "100m"
|
||||
dapr.io/sidecar-memory-request: "250Mi"
|
||||
dapr.io/sidecar-cpu-limit: "300m"
|
||||
dapr.io/sidecar-memory-limit: "1000Mi"
|
||||
dapr.io/log-as-json: "true"
|
||||
|
||||
ingress:
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-staging
|
||||
nginx.ingress.kubernetes.io/proxy-buffer-size: 128k
|
||||
nginx.ingress.kubernetes.io/whitelist-source-range: 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
|
||||
hosts:
|
||||
- host: <x>-OceanWeb.dev.oceanbox.io
|
||||
paths:
|
||||
- path: /
|
||||
pathType: ImplementationSpecific
|
||||
tls:
|
||||
- hosts:
|
||||
- <x>-OceanWeb.dev.oceanbox.io
|
||||
secretName: <x>-OceanWeb-tls
|
||||
|
||||
securityContext:
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
readOnlyRootFilesystem: false
|
||||
runAsNonRoot: false
|
||||
runAsUser: 0
|
||||
|
||||
# resources:
|
||||
# limits:
|
||||
# cpu: 250m
|
||||
# memory: 1Gi
|
||||
# requests:
|
||||
# cpu: 150m
|
||||
# memory: 1Gi
|
||||
47
vite.config.js
Normal file
47
vite.config.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import { resolve } from 'path'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
|
||||
var clientPort = process.env.CLIENT_PORT == null ? 8080 : parseInt(process.env.CLIENT_PORT);
|
||||
var serverPort = process.env.SERVER_PORT == null ? 8085 : parseInt(process.env.SERVER_PORT);
|
||||
serverPort = process.env.SERVER_PROXY_PORT == null ? serverPort : parseInt(process.env.SERVER_PROXY_PORT);
|
||||
|
||||
var proxy = {
|
||||
target: `http://127.0.0.1:${serverPort}/`,
|
||||
changeOrigin: false,
|
||||
secure: false,
|
||||
ws: true
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
plugins:[
|
||||
tailwindcss()
|
||||
],
|
||||
build: {
|
||||
rollupOptions: {
|
||||
input: {
|
||||
index: resolve(__dirname, './src/Client/main.html'),
|
||||
},
|
||||
output: {
|
||||
// TODO: Extra chunking of deps
|
||||
// manualChunks: {
|
||||
// vendor: vendorDeps,
|
||||
// ...chunksFromDeps(dependencies, vendorDeps)
|
||||
// },
|
||||
entryFileNames: 'js/[name].js',
|
||||
//chunkFileNames: 'js/[name].[hash].chunk.js',
|
||||
assetFileNames: '[ext]/[name].[ext]',
|
||||
},
|
||||
},
|
||||
minify: "oxc",
|
||||
},
|
||||
server: {
|
||||
port: clientPort,
|
||||
host: '0.0.0.0',
|
||||
https: false,
|
||||
cors: true,
|
||||
proxy: {
|
||||
'/': proxy,
|
||||
}
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user