init v0
This commit is contained in:
parent
561ea7543d
commit
43e6b6d318
25 changed files with 8063 additions and 374 deletions
17
assets/default.nix
Normal file
17
assets/default.nix
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# This file has been generated by node2nix 1.11.1. Do not edit!
|
||||||
|
|
||||||
|
{pkgs ? import <nixpkgs> {
|
||||||
|
inherit system;
|
||||||
|
}, system ? builtins.currentSystem, nodejs ? pkgs."nodejs_14"}:
|
||||||
|
|
||||||
|
let
|
||||||
|
nodeEnv = import ./node-env.nix {
|
||||||
|
inherit (pkgs) stdenv lib python2 runCommand writeTextFile writeShellScript;
|
||||||
|
inherit pkgs nodejs;
|
||||||
|
libtool = if pkgs.stdenv.isDarwin then pkgs.cctools or pkgs.darwin.cctools else null;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
import ./node-packages.nix {
|
||||||
|
inherit (pkgs) fetchurl nix-gitignore stdenv lib fetchgit;
|
||||||
|
inherit nodeEnv;
|
||||||
|
}
|
||||||
689
assets/node-env.nix
Normal file
689
assets/node-env.nix
Normal file
|
|
@ -0,0 +1,689 @@
|
||||||
|
# This file originates from node2nix
|
||||||
|
|
||||||
|
{lib, stdenv, nodejs, python2, pkgs, libtool, runCommand, writeTextFile, writeShellScript}:
|
||||||
|
|
||||||
|
let
|
||||||
|
# Workaround to cope with utillinux in Nixpkgs 20.09 and util-linux in Nixpkgs master
|
||||||
|
utillinux = if pkgs ? utillinux then pkgs.utillinux else pkgs.util-linux;
|
||||||
|
|
||||||
|
python = if nodejs ? python then nodejs.python else python2;
|
||||||
|
|
||||||
|
# Create a tar wrapper that filters all the 'Ignoring unknown extended header keyword' noise
|
||||||
|
tarWrapper = runCommand "tarWrapper" {} ''
|
||||||
|
mkdir -p $out/bin
|
||||||
|
|
||||||
|
cat > $out/bin/tar <<EOF
|
||||||
|
#! ${stdenv.shell} -e
|
||||||
|
$(type -p tar) "\$@" --warning=no-unknown-keyword --delay-directory-restore
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x $out/bin/tar
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Function that generates a TGZ file from a NPM project
|
||||||
|
buildNodeSourceDist =
|
||||||
|
{ name, version, src, ... }:
|
||||||
|
|
||||||
|
stdenv.mkDerivation {
|
||||||
|
name = "node-tarball-${name}-${version}";
|
||||||
|
inherit src;
|
||||||
|
buildInputs = [ nodejs ];
|
||||||
|
buildPhase = ''
|
||||||
|
export HOME=$TMPDIR
|
||||||
|
tgzFile=$(npm pack | tail -n 1) # Hooks to the pack command will add output (https://docs.npmjs.com/misc/scripts)
|
||||||
|
'';
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p $out/tarballs
|
||||||
|
mv $tgzFile $out/tarballs
|
||||||
|
mkdir -p $out/nix-support
|
||||||
|
echo "file source-dist $out/tarballs/$tgzFile" >> $out/nix-support/hydra-build-products
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# Common shell logic
|
||||||
|
installPackage = writeShellScript "install-package" ''
|
||||||
|
installPackage() {
|
||||||
|
local packageName=$1 src=$2
|
||||||
|
|
||||||
|
local strippedName
|
||||||
|
|
||||||
|
local DIR=$PWD
|
||||||
|
cd $TMPDIR
|
||||||
|
|
||||||
|
unpackFile $src
|
||||||
|
|
||||||
|
# Make the base dir in which the target dependency resides first
|
||||||
|
mkdir -p "$(dirname "$DIR/$packageName")"
|
||||||
|
|
||||||
|
if [ -f "$src" ]
|
||||||
|
then
|
||||||
|
# Figure out what directory has been unpacked
|
||||||
|
packageDir="$(find . -maxdepth 1 -type d | tail -1)"
|
||||||
|
|
||||||
|
# Restore write permissions to make building work
|
||||||
|
find "$packageDir" -type d -exec chmod u+x {} \;
|
||||||
|
chmod -R u+w "$packageDir"
|
||||||
|
|
||||||
|
# Move the extracted tarball into the output folder
|
||||||
|
mv "$packageDir" "$DIR/$packageName"
|
||||||
|
elif [ -d "$src" ]
|
||||||
|
then
|
||||||
|
# Get a stripped name (without hash) of the source directory.
|
||||||
|
# On old nixpkgs it's already set internally.
|
||||||
|
if [ -z "$strippedName" ]
|
||||||
|
then
|
||||||
|
strippedName="$(stripHash $src)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Restore write permissions to make building work
|
||||||
|
chmod -R u+w "$strippedName"
|
||||||
|
|
||||||
|
# Move the extracted directory into the output folder
|
||||||
|
mv "$strippedName" "$DIR/$packageName"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Change to the package directory to install dependencies
|
||||||
|
cd "$DIR/$packageName"
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Bundle the dependencies of the package
|
||||||
|
#
|
||||||
|
# Only include dependencies if they don't exist. They may also be bundled in the package.
|
||||||
|
includeDependencies = {dependencies}:
|
||||||
|
lib.optionalString (dependencies != []) (
|
||||||
|
''
|
||||||
|
mkdir -p node_modules
|
||||||
|
cd node_modules
|
||||||
|
''
|
||||||
|
+ (lib.concatMapStrings (dependency:
|
||||||
|
''
|
||||||
|
if [ ! -e "${dependency.packageName}" ]; then
|
||||||
|
${composePackage dependency}
|
||||||
|
fi
|
||||||
|
''
|
||||||
|
) dependencies)
|
||||||
|
+ ''
|
||||||
|
cd ..
|
||||||
|
''
|
||||||
|
);
|
||||||
|
|
||||||
|
# Recursively composes the dependencies of a package
|
||||||
|
composePackage = { name, packageName, src, dependencies ? [], ... }@args:
|
||||||
|
builtins.addErrorContext "while evaluating node package '${packageName}'" ''
|
||||||
|
installPackage "${packageName}" "${src}"
|
||||||
|
${includeDependencies { inherit dependencies; }}
|
||||||
|
cd ..
|
||||||
|
${lib.optionalString (builtins.substring 0 1 packageName == "@") "cd .."}
|
||||||
|
'';
|
||||||
|
|
||||||
|
pinpointDependencies = {dependencies, production}:
|
||||||
|
let
|
||||||
|
pinpointDependenciesFromPackageJSON = writeTextFile {
|
||||||
|
name = "pinpointDependencies.js";
|
||||||
|
text = ''
|
||||||
|
var fs = require('fs');
|
||||||
|
var path = require('path');
|
||||||
|
|
||||||
|
function resolveDependencyVersion(location, name) {
|
||||||
|
if(location == process.env['NIX_STORE']) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
var dependencyPackageJSON = path.join(location, "node_modules", name, "package.json");
|
||||||
|
|
||||||
|
if(fs.existsSync(dependencyPackageJSON)) {
|
||||||
|
var dependencyPackageObj = JSON.parse(fs.readFileSync(dependencyPackageJSON));
|
||||||
|
|
||||||
|
if(dependencyPackageObj.name == name) {
|
||||||
|
return dependencyPackageObj.version;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return resolveDependencyVersion(path.resolve(location, ".."), name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceDependencies(dependencies) {
|
||||||
|
if(typeof dependencies == "object" && dependencies !== null) {
|
||||||
|
for(var dependency in dependencies) {
|
||||||
|
var resolvedVersion = resolveDependencyVersion(process.cwd(), dependency);
|
||||||
|
|
||||||
|
if(resolvedVersion === null) {
|
||||||
|
process.stderr.write("WARNING: cannot pinpoint dependency: "+dependency+", context: "+process.cwd()+"\n");
|
||||||
|
} else {
|
||||||
|
dependencies[dependency] = resolvedVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read the package.json configuration */
|
||||||
|
var packageObj = JSON.parse(fs.readFileSync('./package.json'));
|
||||||
|
|
||||||
|
/* Pinpoint all dependencies */
|
||||||
|
replaceDependencies(packageObj.dependencies);
|
||||||
|
if(process.argv[2] == "development") {
|
||||||
|
replaceDependencies(packageObj.devDependencies);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
packageObj.devDependencies = {};
|
||||||
|
}
|
||||||
|
replaceDependencies(packageObj.optionalDependencies);
|
||||||
|
replaceDependencies(packageObj.peerDependencies);
|
||||||
|
|
||||||
|
/* Write the fixed package.json file */
|
||||||
|
fs.writeFileSync("package.json", JSON.stringify(packageObj, null, 2));
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
in
|
||||||
|
''
|
||||||
|
node ${pinpointDependenciesFromPackageJSON} ${if production then "production" else "development"}
|
||||||
|
|
||||||
|
${lib.optionalString (dependencies != [])
|
||||||
|
''
|
||||||
|
if [ -d node_modules ]
|
||||||
|
then
|
||||||
|
cd node_modules
|
||||||
|
${lib.concatMapStrings (dependency: pinpointDependenciesOfPackage dependency) dependencies}
|
||||||
|
cd ..
|
||||||
|
fi
|
||||||
|
''}
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Recursively traverses all dependencies of a package and pinpoints all
|
||||||
|
# dependencies in the package.json file to the versions that are actually
|
||||||
|
# being used.
|
||||||
|
|
||||||
|
pinpointDependenciesOfPackage = { packageName, dependencies ? [], production ? true, ... }@args:
|
||||||
|
''
|
||||||
|
if [ -d "${packageName}" ]
|
||||||
|
then
|
||||||
|
cd "${packageName}"
|
||||||
|
${pinpointDependencies { inherit dependencies production; }}
|
||||||
|
cd ..
|
||||||
|
${lib.optionalString (builtins.substring 0 1 packageName == "@") "cd .."}
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Extract the Node.js source code which is used to compile packages with
|
||||||
|
# native bindings
|
||||||
|
nodeSources = runCommand "node-sources" {} ''
|
||||||
|
tar --no-same-owner --no-same-permissions -xf ${nodejs.src}
|
||||||
|
mv node-* $out
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Script that adds _integrity fields to all package.json files to prevent NPM from consulting the cache (that is empty)
|
||||||
|
addIntegrityFieldsScript = writeTextFile {
|
||||||
|
name = "addintegrityfields.js";
|
||||||
|
text = ''
|
||||||
|
var fs = require('fs');
|
||||||
|
var path = require('path');
|
||||||
|
|
||||||
|
function augmentDependencies(baseDir, dependencies) {
|
||||||
|
for(var dependencyName in dependencies) {
|
||||||
|
var dependency = dependencies[dependencyName];
|
||||||
|
|
||||||
|
// Open package.json and augment metadata fields
|
||||||
|
var packageJSONDir = path.join(baseDir, "node_modules", dependencyName);
|
||||||
|
var packageJSONPath = path.join(packageJSONDir, "package.json");
|
||||||
|
|
||||||
|
if(fs.existsSync(packageJSONPath)) { // Only augment packages that exist. Sometimes we may have production installs in which development dependencies can be ignored
|
||||||
|
console.log("Adding metadata fields to: "+packageJSONPath);
|
||||||
|
var packageObj = JSON.parse(fs.readFileSync(packageJSONPath));
|
||||||
|
|
||||||
|
if(dependency.integrity) {
|
||||||
|
packageObj["_integrity"] = dependency.integrity;
|
||||||
|
} else {
|
||||||
|
packageObj["_integrity"] = "sha1-000000000000000000000000000="; // When no _integrity string has been provided (e.g. by Git dependencies), add a dummy one. It does not seem to harm and it bypasses downloads.
|
||||||
|
}
|
||||||
|
|
||||||
|
if(dependency.resolved) {
|
||||||
|
packageObj["_resolved"] = dependency.resolved; // Adopt the resolved property if one has been provided
|
||||||
|
} else {
|
||||||
|
packageObj["_resolved"] = dependency.version; // Set the resolved version to the version identifier. This prevents NPM from cloning Git repositories.
|
||||||
|
}
|
||||||
|
|
||||||
|
if(dependency.from !== undefined) { // Adopt from property if one has been provided
|
||||||
|
packageObj["_from"] = dependency.from;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(packageJSONPath, JSON.stringify(packageObj, null, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Augment transitive dependencies
|
||||||
|
if(dependency.dependencies !== undefined) {
|
||||||
|
augmentDependencies(packageJSONDir, dependency.dependencies);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(fs.existsSync("./package-lock.json")) {
|
||||||
|
var packageLock = JSON.parse(fs.readFileSync("./package-lock.json"));
|
||||||
|
|
||||||
|
if(![1, 2].includes(packageLock.lockfileVersion)) {
|
||||||
|
process.stderr.write("Sorry, I only understand lock file versions 1 and 2!\n");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(packageLock.dependencies !== undefined) {
|
||||||
|
augmentDependencies(".", packageLock.dependencies);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# Reconstructs a package-lock file from the node_modules/ folder structure and package.json files with dummy sha1 hashes
|
||||||
|
reconstructPackageLock = writeTextFile {
|
||||||
|
name = "reconstructpackagelock.js";
|
||||||
|
text = ''
|
||||||
|
var fs = require('fs');
|
||||||
|
var path = require('path');
|
||||||
|
|
||||||
|
var packageObj = JSON.parse(fs.readFileSync("package.json"));
|
||||||
|
|
||||||
|
var lockObj = {
|
||||||
|
name: packageObj.name,
|
||||||
|
version: packageObj.version,
|
||||||
|
lockfileVersion: 2,
|
||||||
|
requires: true,
|
||||||
|
packages: {
|
||||||
|
"": {
|
||||||
|
name: packageObj.name,
|
||||||
|
version: packageObj.version,
|
||||||
|
license: packageObj.license,
|
||||||
|
bin: packageObj.bin,
|
||||||
|
dependencies: packageObj.dependencies,
|
||||||
|
engines: packageObj.engines,
|
||||||
|
optionalDependencies: packageObj.optionalDependencies
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dependencies: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
function augmentPackageJSON(filePath, packages, dependencies) {
|
||||||
|
var packageJSON = path.join(filePath, "package.json");
|
||||||
|
if(fs.existsSync(packageJSON)) {
|
||||||
|
var packageObj = JSON.parse(fs.readFileSync(packageJSON));
|
||||||
|
packages[filePath] = {
|
||||||
|
version: packageObj.version,
|
||||||
|
integrity: "sha1-000000000000000000000000000=",
|
||||||
|
dependencies: packageObj.dependencies,
|
||||||
|
engines: packageObj.engines,
|
||||||
|
optionalDependencies: packageObj.optionalDependencies
|
||||||
|
};
|
||||||
|
dependencies[packageObj.name] = {
|
||||||
|
version: packageObj.version,
|
||||||
|
integrity: "sha1-000000000000000000000000000=",
|
||||||
|
dependencies: {}
|
||||||
|
};
|
||||||
|
processDependencies(path.join(filePath, "node_modules"), packages, dependencies[packageObj.name].dependencies);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function processDependencies(dir, packages, dependencies) {
|
||||||
|
if(fs.existsSync(dir)) {
|
||||||
|
var files = fs.readdirSync(dir);
|
||||||
|
|
||||||
|
files.forEach(function(entry) {
|
||||||
|
var filePath = path.join(dir, entry);
|
||||||
|
var stats = fs.statSync(filePath);
|
||||||
|
|
||||||
|
if(stats.isDirectory()) {
|
||||||
|
if(entry.substr(0, 1) == "@") {
|
||||||
|
// When we encounter a namespace folder, augment all packages belonging to the scope
|
||||||
|
var pkgFiles = fs.readdirSync(filePath);
|
||||||
|
|
||||||
|
pkgFiles.forEach(function(entry) {
|
||||||
|
if(stats.isDirectory()) {
|
||||||
|
var pkgFilePath = path.join(filePath, entry);
|
||||||
|
augmentPackageJSON(pkgFilePath, packages, dependencies);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
augmentPackageJSON(filePath, packages, dependencies);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processDependencies("node_modules", lockObj.packages, lockObj.dependencies);
|
||||||
|
|
||||||
|
fs.writeFileSync("package-lock.json", JSON.stringify(lockObj, null, 2));
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# Script that links bins defined in package.json to the node_modules bin directory
|
||||||
|
# NPM does not do this for top-level packages itself anymore as of v7
|
||||||
|
linkBinsScript = writeTextFile {
|
||||||
|
name = "linkbins.js";
|
||||||
|
text = ''
|
||||||
|
var fs = require('fs');
|
||||||
|
var path = require('path');
|
||||||
|
|
||||||
|
var packageObj = JSON.parse(fs.readFileSync("package.json"));
|
||||||
|
|
||||||
|
var nodeModules = Array(packageObj.name.split("/").length).fill("..").join(path.sep);
|
||||||
|
|
||||||
|
if(packageObj.bin !== undefined) {
|
||||||
|
fs.mkdirSync(path.join(nodeModules, ".bin"))
|
||||||
|
|
||||||
|
if(typeof packageObj.bin == "object") {
|
||||||
|
Object.keys(packageObj.bin).forEach(function(exe) {
|
||||||
|
if(fs.existsSync(packageObj.bin[exe])) {
|
||||||
|
console.log("linking bin '" + exe + "'");
|
||||||
|
fs.symlinkSync(
|
||||||
|
path.join("..", packageObj.name, packageObj.bin[exe]),
|
||||||
|
path.join(nodeModules, ".bin", exe)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log("skipping non-existent bin '" + exe + "'");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if(fs.existsSync(packageObj.bin)) {
|
||||||
|
console.log("linking bin '" + packageObj.bin + "'");
|
||||||
|
fs.symlinkSync(
|
||||||
|
path.join("..", packageObj.name, packageObj.bin),
|
||||||
|
path.join(nodeModules, ".bin", packageObj.name.split("/").pop())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log("skipping non-existent bin '" + packageObj.bin + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(packageObj.directories !== undefined && packageObj.directories.bin !== undefined) {
|
||||||
|
fs.mkdirSync(path.join(nodeModules, ".bin"))
|
||||||
|
|
||||||
|
fs.readdirSync(packageObj.directories.bin).forEach(function(exe) {
|
||||||
|
if(fs.existsSync(path.join(packageObj.directories.bin, exe))) {
|
||||||
|
console.log("linking bin '" + exe + "'");
|
||||||
|
fs.symlinkSync(
|
||||||
|
path.join("..", packageObj.name, packageObj.directories.bin, exe),
|
||||||
|
path.join(nodeModules, ".bin", exe)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log("skipping non-existent bin '" + exe + "'");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
prepareAndInvokeNPM = {packageName, bypassCache, reconstructLock, npmFlags, production}:
|
||||||
|
let
|
||||||
|
forceOfflineFlag = if bypassCache then "--offline" else "--registry http://www.example.com";
|
||||||
|
in
|
||||||
|
''
|
||||||
|
# Pinpoint the versions of all dependencies to the ones that are actually being used
|
||||||
|
echo "pinpointing versions of dependencies..."
|
||||||
|
source $pinpointDependenciesScriptPath
|
||||||
|
|
||||||
|
# Patch the shebangs of the bundled modules to prevent them from
|
||||||
|
# calling executables outside the Nix store as much as possible
|
||||||
|
patchShebangs .
|
||||||
|
|
||||||
|
# Deploy the Node.js package by running npm install. Since the
|
||||||
|
# dependencies have been provided already by ourselves, it should not
|
||||||
|
# attempt to install them again, which is good, because we want to make
|
||||||
|
# it Nix's responsibility. If it needs to install any dependencies
|
||||||
|
# anyway (e.g. because the dependency parameters are
|
||||||
|
# incomplete/incorrect), it fails.
|
||||||
|
#
|
||||||
|
# The other responsibilities of NPM are kept -- version checks, build
|
||||||
|
# steps, postprocessing etc.
|
||||||
|
|
||||||
|
export HOME=$TMPDIR
|
||||||
|
cd "${packageName}"
|
||||||
|
runHook preRebuild
|
||||||
|
|
||||||
|
${lib.optionalString bypassCache ''
|
||||||
|
${lib.optionalString reconstructLock ''
|
||||||
|
if [ -f package-lock.json ]
|
||||||
|
then
|
||||||
|
echo "WARNING: Reconstruct lock option enabled, but a lock file already exists!"
|
||||||
|
echo "This will most likely result in version mismatches! We will remove the lock file and regenerate it!"
|
||||||
|
rm package-lock.json
|
||||||
|
else
|
||||||
|
echo "No package-lock.json file found, reconstructing..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
node ${reconstructPackageLock}
|
||||||
|
''}
|
||||||
|
|
||||||
|
node ${addIntegrityFieldsScript}
|
||||||
|
''}
|
||||||
|
|
||||||
|
npm ${forceOfflineFlag} --nodedir=${nodeSources} ${npmFlags} ${lib.optionalString production "--production"} rebuild
|
||||||
|
|
||||||
|
runHook postRebuild
|
||||||
|
|
||||||
|
if [ "''${dontNpmInstall-}" != "1" ]
|
||||||
|
then
|
||||||
|
# NPM tries to download packages even when they already exist if npm-shrinkwrap is used.
|
||||||
|
rm -f npm-shrinkwrap.json
|
||||||
|
|
||||||
|
npm ${forceOfflineFlag} --nodedir=${nodeSources} --no-bin-links --ignore-scripts ${npmFlags} ${lib.optionalString production "--production"} install
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Link executables defined in package.json
|
||||||
|
node ${linkBinsScript}
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Builds and composes an NPM package including all its dependencies
|
||||||
|
buildNodePackage =
|
||||||
|
{ name
|
||||||
|
, packageName
|
||||||
|
, version ? null
|
||||||
|
, dependencies ? []
|
||||||
|
, buildInputs ? []
|
||||||
|
, production ? true
|
||||||
|
, npmFlags ? ""
|
||||||
|
, dontNpmInstall ? false
|
||||||
|
, bypassCache ? false
|
||||||
|
, reconstructLock ? false
|
||||||
|
, preRebuild ? ""
|
||||||
|
, dontStrip ? true
|
||||||
|
, unpackPhase ? "true"
|
||||||
|
, buildPhase ? "true"
|
||||||
|
, meta ? {}
|
||||||
|
, ... }@args:
|
||||||
|
|
||||||
|
let
|
||||||
|
extraArgs = removeAttrs args [ "name" "dependencies" "buildInputs" "dontStrip" "dontNpmInstall" "preRebuild" "unpackPhase" "buildPhase" "meta" ];
|
||||||
|
in
|
||||||
|
stdenv.mkDerivation ({
|
||||||
|
name = "${name}${if version == null then "" else "-${version}"}";
|
||||||
|
buildInputs = [ tarWrapper python nodejs ]
|
||||||
|
++ lib.optional (stdenv.isLinux) utillinux
|
||||||
|
++ lib.optional (stdenv.isDarwin) libtool
|
||||||
|
++ buildInputs;
|
||||||
|
|
||||||
|
inherit nodejs;
|
||||||
|
|
||||||
|
inherit dontStrip; # Stripping may fail a build for some package deployments
|
||||||
|
inherit dontNpmInstall preRebuild unpackPhase buildPhase;
|
||||||
|
|
||||||
|
compositionScript = composePackage args;
|
||||||
|
pinpointDependenciesScript = pinpointDependenciesOfPackage args;
|
||||||
|
|
||||||
|
passAsFile = [ "compositionScript" "pinpointDependenciesScript" ];
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
source ${installPackage}
|
||||||
|
|
||||||
|
# Create and enter a root node_modules/ folder
|
||||||
|
mkdir -p $out/lib/node_modules
|
||||||
|
cd $out/lib/node_modules
|
||||||
|
|
||||||
|
# Compose the package and all its dependencies
|
||||||
|
source $compositionScriptPath
|
||||||
|
|
||||||
|
${prepareAndInvokeNPM { inherit packageName bypassCache reconstructLock npmFlags production; }}
|
||||||
|
|
||||||
|
# Create symlink to the deployed executable folder, if applicable
|
||||||
|
if [ -d "$out/lib/node_modules/.bin" ]
|
||||||
|
then
|
||||||
|
ln -s $out/lib/node_modules/.bin $out/bin
|
||||||
|
|
||||||
|
# Fixup all executables
|
||||||
|
ls $out/bin/* | while read i
|
||||||
|
do
|
||||||
|
file="$(readlink -f "$i")"
|
||||||
|
chmod u+rwx "$file"
|
||||||
|
if isScript "$file"
|
||||||
|
then
|
||||||
|
sed -i 's/\r$//' "$file" # convert crlf to lf
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create symlinks to the deployed manual page folders, if applicable
|
||||||
|
if [ -d "$out/lib/node_modules/${packageName}/man" ]
|
||||||
|
then
|
||||||
|
mkdir -p $out/share
|
||||||
|
for dir in "$out/lib/node_modules/${packageName}/man/"*
|
||||||
|
do
|
||||||
|
mkdir -p $out/share/man/$(basename "$dir")
|
||||||
|
for page in "$dir"/*
|
||||||
|
do
|
||||||
|
ln -s $page $out/share/man/$(basename "$dir")
|
||||||
|
done
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Run post install hook, if provided
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
# default to Node.js' platforms
|
||||||
|
platforms = nodejs.meta.platforms;
|
||||||
|
} // meta;
|
||||||
|
} // extraArgs);
|
||||||
|
|
||||||
|
# Builds a node environment (a node_modules folder and a set of binaries)
|
||||||
|
buildNodeDependencies =
|
||||||
|
{ name
|
||||||
|
, packageName
|
||||||
|
, version ? null
|
||||||
|
, src
|
||||||
|
, dependencies ? []
|
||||||
|
, buildInputs ? []
|
||||||
|
, production ? true
|
||||||
|
, npmFlags ? ""
|
||||||
|
, dontNpmInstall ? false
|
||||||
|
, bypassCache ? false
|
||||||
|
, reconstructLock ? false
|
||||||
|
, dontStrip ? true
|
||||||
|
, unpackPhase ? "true"
|
||||||
|
, buildPhase ? "true"
|
||||||
|
, ... }@args:
|
||||||
|
|
||||||
|
let
|
||||||
|
extraArgs = removeAttrs args [ "name" "dependencies" "buildInputs" ];
|
||||||
|
in
|
||||||
|
stdenv.mkDerivation ({
|
||||||
|
name = "node-dependencies-${name}${if version == null then "" else "-${version}"}";
|
||||||
|
|
||||||
|
buildInputs = [ tarWrapper python nodejs ]
|
||||||
|
++ lib.optional (stdenv.isLinux) utillinux
|
||||||
|
++ lib.optional (stdenv.isDarwin) libtool
|
||||||
|
++ buildInputs;
|
||||||
|
|
||||||
|
inherit dontStrip; # Stripping may fail a build for some package deployments
|
||||||
|
inherit dontNpmInstall unpackPhase buildPhase;
|
||||||
|
|
||||||
|
includeScript = includeDependencies { inherit dependencies; };
|
||||||
|
pinpointDependenciesScript = pinpointDependenciesOfPackage args;
|
||||||
|
|
||||||
|
passAsFile = [ "includeScript" "pinpointDependenciesScript" ];
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
source ${installPackage}
|
||||||
|
|
||||||
|
mkdir -p $out/${packageName}
|
||||||
|
cd $out/${packageName}
|
||||||
|
|
||||||
|
source $includeScriptPath
|
||||||
|
|
||||||
|
# Create fake package.json to make the npm commands work properly
|
||||||
|
cp ${src}/package.json .
|
||||||
|
chmod 644 package.json
|
||||||
|
${lib.optionalString bypassCache ''
|
||||||
|
if [ -f ${src}/package-lock.json ]
|
||||||
|
then
|
||||||
|
cp ${src}/package-lock.json .
|
||||||
|
chmod 644 package-lock.json
|
||||||
|
fi
|
||||||
|
''}
|
||||||
|
|
||||||
|
# Go to the parent folder to make sure that all packages are pinpointed
|
||||||
|
cd ..
|
||||||
|
${lib.optionalString (builtins.substring 0 1 packageName == "@") "cd .."}
|
||||||
|
|
||||||
|
${prepareAndInvokeNPM { inherit packageName bypassCache reconstructLock npmFlags production; }}
|
||||||
|
|
||||||
|
# Expose the executables that were installed
|
||||||
|
cd ..
|
||||||
|
${lib.optionalString (builtins.substring 0 1 packageName == "@") "cd .."}
|
||||||
|
|
||||||
|
mv ${packageName} lib
|
||||||
|
ln -s $out/lib/node_modules/.bin $out/bin
|
||||||
|
'';
|
||||||
|
} // extraArgs);
|
||||||
|
|
||||||
|
# Builds a development shell
|
||||||
|
buildNodeShell =
|
||||||
|
{ name
|
||||||
|
, packageName
|
||||||
|
, version ? null
|
||||||
|
, src
|
||||||
|
, dependencies ? []
|
||||||
|
, buildInputs ? []
|
||||||
|
, production ? true
|
||||||
|
, npmFlags ? ""
|
||||||
|
, dontNpmInstall ? false
|
||||||
|
, bypassCache ? false
|
||||||
|
, reconstructLock ? false
|
||||||
|
, dontStrip ? true
|
||||||
|
, unpackPhase ? "true"
|
||||||
|
, buildPhase ? "true"
|
||||||
|
, ... }@args:
|
||||||
|
|
||||||
|
let
|
||||||
|
nodeDependencies = buildNodeDependencies args;
|
||||||
|
extraArgs = removeAttrs args [ "name" "dependencies" "buildInputs" "dontStrip" "dontNpmInstall" "unpackPhase" "buildPhase" ];
|
||||||
|
in
|
||||||
|
stdenv.mkDerivation ({
|
||||||
|
name = "node-shell-${name}${if version == null then "" else "-${version}"}";
|
||||||
|
|
||||||
|
buildInputs = [ python nodejs ] ++ lib.optional (stdenv.isLinux) utillinux ++ buildInputs;
|
||||||
|
buildCommand = ''
|
||||||
|
mkdir -p $out/bin
|
||||||
|
cat > $out/bin/shell <<EOF
|
||||||
|
#! ${stdenv.shell} -e
|
||||||
|
$shellHook
|
||||||
|
exec ${stdenv.shell}
|
||||||
|
EOF
|
||||||
|
chmod +x $out/bin/shell
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Provide the dependencies in a development shell through the NODE_PATH environment variable
|
||||||
|
inherit nodeDependencies;
|
||||||
|
shellHook = lib.optionalString (dependencies != []) ''
|
||||||
|
export NODE_PATH=${nodeDependencies}/lib/node_modules
|
||||||
|
export PATH="${nodeDependencies}/bin:$PATH"
|
||||||
|
'';
|
||||||
|
} // extraArgs);
|
||||||
|
in
|
||||||
|
{
|
||||||
|
buildNodeSourceDist = lib.makeOverridable buildNodeSourceDist;
|
||||||
|
buildNodePackage = lib.makeOverridable buildNodePackage;
|
||||||
|
buildNodeDependencies = lib.makeOverridable buildNodeDependencies;
|
||||||
|
buildNodeShell = lib.makeOverridable buildNodeShell;
|
||||||
|
}
|
||||||
1457
assets/node-packages.nix
Normal file
1457
assets/node-packages.nix
Normal file
File diff suppressed because it is too large
Load diff
1930
assets/out.css
Normal file
1930
assets/out.css
Normal file
File diff suppressed because it is too large
Load diff
1863
assets/package-lock.json
generated
Normal file
1863
assets/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
19
assets/package.json
Normal file
19
assets/package.json
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "zoeys-computer-assets",
|
||||||
|
"scripts": {
|
||||||
|
"build:css": "tailwindcss -i ./css/app.css -o ../priv/static/assets/app.css --minify",
|
||||||
|
"watch:css": "tailwindcss -i ./css/app.css -o ../priv/static/assets/app.css --watch",
|
||||||
|
"build:js": "esbuild js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/* --minify",
|
||||||
|
"deploy": "npm run build:css && npm run build:js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@catppuccin/tailwindcss": "^0.1.6",
|
||||||
|
"@esbuild/linux-x64": "^0.24.0",
|
||||||
|
"@tailwindcss/forms": "^0.5.9",
|
||||||
|
"esbuild": "^0.24.0",
|
||||||
|
"phoenix": "^1.7.14",
|
||||||
|
"phoenix_html": "^3.3.4",
|
||||||
|
"phoenix_live_view": "^1.0.0-rc.7",
|
||||||
|
"tailwindcss": "^3.4.14"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,21 +1,21 @@
|
||||||
// See the Tailwind configuration guide for advanced usage
|
// See the Tailwind configuration guide for advanced usage
|
||||||
// https://tailwindcss.com/docs/configuration
|
// https://tailwindcss.com/docs/configuration
|
||||||
|
|
||||||
const plugin = require("tailwindcss/plugin")
|
const plugin = require("tailwindcss/plugin");
|
||||||
const fs = require("fs")
|
const fs = require("fs");
|
||||||
const path = require("path")
|
const path = require("path");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: [
|
content: [
|
||||||
"./js/**/*.js",
|
"./js/**/*.js",
|
||||||
"../lib/zoeyscomputer_web.ex",
|
"../lib/zoeyscomputer_web.ex",
|
||||||
"../lib/zoeyscomputer_web/**/*.*ex"
|
"../lib/zoeyscomputer_web/**/*.*ex",
|
||||||
],
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
brand: "#FD4F00",
|
brand: "#FD4F00",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|
@ -25,50 +25,75 @@ module.exports = {
|
||||||
//
|
//
|
||||||
// <div class="phx-click-loading:animate-ping">
|
// <div class="phx-click-loading:animate-ping">
|
||||||
//
|
//
|
||||||
plugin(({addVariant}) => addVariant("phx-click-loading", [".phx-click-loading&", ".phx-click-loading &"])),
|
require("@catppuccin/tailwindcss")({
|
||||||
plugin(({addVariant}) => addVariant("phx-submit-loading", [".phx-submit-loading&", ".phx-submit-loading &"])),
|
prefix: "ctp",
|
||||||
plugin(({addVariant}) => addVariant("phx-change-loading", [".phx-change-loading&", ".phx-change-loading &"])),
|
defaultFlavor: "mocha",
|
||||||
|
}),
|
||||||
|
plugin(({ addVariant }) =>
|
||||||
|
addVariant("phx-click-loading", [
|
||||||
|
".phx-click-loading&",
|
||||||
|
".phx-click-loading &",
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
plugin(({ addVariant }) =>
|
||||||
|
addVariant("phx-submit-loading", [
|
||||||
|
".phx-submit-loading&",
|
||||||
|
".phx-submit-loading &",
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
plugin(({ addVariant }) =>
|
||||||
|
addVariant("phx-change-loading", [
|
||||||
|
".phx-change-loading&",
|
||||||
|
".phx-change-loading &",
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
|
||||||
// Embeds Heroicons (https://heroicons.com) into your app.css bundle
|
// Embeds Heroicons (https://heroicons.com) into your app.css bundle
|
||||||
// See your `CoreComponents.icon/1` for more information.
|
// See your `CoreComponents.icon/1` for more information.
|
||||||
//
|
//
|
||||||
plugin(function({matchComponents, theme}) {
|
plugin(function ({ matchComponents, theme }) {
|
||||||
let iconsDir = path.join(__dirname, "../deps/heroicons/optimized")
|
let iconsDir = path.join(__dirname, "../deps/heroicons/optimized");
|
||||||
let values = {}
|
let values = {};
|
||||||
let icons = [
|
let icons = [
|
||||||
["", "/24/outline"],
|
["", "/24/outline"],
|
||||||
["-solid", "/24/solid"],
|
["-solid", "/24/solid"],
|
||||||
["-mini", "/20/solid"],
|
["-mini", "/20/solid"],
|
||||||
["-micro", "/16/solid"]
|
["-micro", "/16/solid"],
|
||||||
]
|
];
|
||||||
icons.forEach(([suffix, dir]) => {
|
icons.forEach(([suffix, dir]) => {
|
||||||
fs.readdirSync(path.join(iconsDir, dir)).forEach(file => {
|
fs.readdirSync(path.join(iconsDir, dir)).forEach((file) => {
|
||||||
let name = path.basename(file, ".svg") + suffix
|
let name = path.basename(file, ".svg") + suffix;
|
||||||
values[name] = {name, fullPath: path.join(iconsDir, dir, file)}
|
values[name] = { name, fullPath: path.join(iconsDir, dir, file) };
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
matchComponents({
|
matchComponents(
|
||||||
"hero": ({name, fullPath}) => {
|
{
|
||||||
let content = fs.readFileSync(fullPath).toString().replace(/\r?\n|\r/g, "")
|
hero: ({ name, fullPath }) => {
|
||||||
let size = theme("spacing.6")
|
let content = fs
|
||||||
|
.readFileSync(fullPath)
|
||||||
|
.toString()
|
||||||
|
.replace(/\r?\n|\r/g, "");
|
||||||
|
let size = theme("spacing.6");
|
||||||
if (name.endsWith("-mini")) {
|
if (name.endsWith("-mini")) {
|
||||||
size = theme("spacing.5")
|
size = theme("spacing.5");
|
||||||
} else if (name.endsWith("-micro")) {
|
} else if (name.endsWith("-micro")) {
|
||||||
size = theme("spacing.4")
|
size = theme("spacing.4");
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
[`--hero-${name}`]: `url('data:image/svg+xml;utf8,${content}')`,
|
[`--hero-${name}`]: `url('data:image/svg+xml;utf8,${content}')`,
|
||||||
"-webkit-mask": `var(--hero-${name})`,
|
"-webkit-mask": `var(--hero-${name})`,
|
||||||
"mask": `var(--hero-${name})`,
|
mask: `var(--hero-${name})`,
|
||||||
"mask-repeat": "no-repeat",
|
"mask-repeat": "no-repeat",
|
||||||
"background-color": "currentColor",
|
"background-color": "currentColor",
|
||||||
"vertical-align": "middle",
|
"vertical-align": "middle",
|
||||||
"display": "inline-block",
|
display: "inline-block",
|
||||||
"width": size,
|
width: size,
|
||||||
"height": size
|
height: size,
|
||||||
}
|
};
|
||||||
}
|
},
|
||||||
}, {values})
|
},
|
||||||
})
|
{ values },
|
||||||
]
|
);
|
||||||
}
|
}),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ import Config
|
||||||
# manifest is generated by the `mix assets.deploy` task,
|
# manifest is generated by the `mix assets.deploy` task,
|
||||||
# which you should run after static files are built and
|
# which you should run after static files are built and
|
||||||
# before starting your production server.
|
# before starting your production server.
|
||||||
config :zoeyscomputer, ZoeyscomputerWeb.Endpoint,
|
|
||||||
cache_static_manifest: "priv/static/cache_manifest.json"
|
config :zoeyscomputer, dev_routes: true
|
||||||
|
|
||||||
# Configures Swoosh API Client
|
# Configures Swoosh API Client
|
||||||
config :swoosh, api_client: Swoosh.ApiClient.Finch, finch_name: Zoeyscomputer.Finch
|
config :swoosh, api_client: Swoosh.ApiClient.Finch, finch_name: Zoeyscomputer.Finch
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ if config_env() == :prod do
|
||||||
You can generate one by calling: mix phx.gen.secret
|
You can generate one by calling: mix phx.gen.secret
|
||||||
"""
|
"""
|
||||||
|
|
||||||
host = System.get_env("PHX_HOST") || "example.com"
|
host = System.get_env("PHX_HOST") || "localhost"
|
||||||
port = String.to_integer(System.get_env("PORT") || "4000")
|
port = String.to_integer(System.get_env("PORT") || "4000")
|
||||||
|
|
||||||
config :zoeyscomputer, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY")
|
config :zoeyscomputer, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY")
|
||||||
|
|
|
||||||
34
deps.nix
34
deps.nix
|
|
@ -233,6 +233,23 @@ let
|
||||||
beamDeps = [ decimal jason telemetry ];
|
beamDeps = [ decimal jason telemetry ];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ecto_psql_extras =
|
||||||
|
let
|
||||||
|
version = "0.8.2";
|
||||||
|
in
|
||||||
|
buildMix {
|
||||||
|
inherit version;
|
||||||
|
name = "ecto_psql_extras";
|
||||||
|
|
||||||
|
src = fetchHex {
|
||||||
|
inherit version;
|
||||||
|
pkg = "ecto_psql_extras";
|
||||||
|
sha256 = "6149c1c4a5ba6602a76cb09ee7a269eb60dab9694a1dbbb797f032555212de75";
|
||||||
|
};
|
||||||
|
|
||||||
|
beamDeps = [ ecto_sql postgrex table_rex ];
|
||||||
|
};
|
||||||
|
|
||||||
ecto_sql =
|
ecto_sql =
|
||||||
let
|
let
|
||||||
version = "3.12.1";
|
version = "3.12.1";
|
||||||
|
|
@ -490,7 +507,7 @@ let
|
||||||
sha256 = "2984aae96994fbc5c61795a73b8fb58153b41ff934019cfb522343d2d3817d59";
|
sha256 = "2984aae96994fbc5c61795a73b8fb58153b41ff934019cfb522343d2d3817d59";
|
||||||
};
|
};
|
||||||
|
|
||||||
beamDeps = [ ecto mime phoenix_live_view telemetry_metrics ];
|
beamDeps = [ ecto ecto_psql_extras mime phoenix_live_view telemetry_metrics ];
|
||||||
};
|
};
|
||||||
|
|
||||||
phoenix_live_view =
|
phoenix_live_view =
|
||||||
|
|
@ -608,6 +625,21 @@ let
|
||||||
beamDeps = [ bandit finch jason mime plug telemetry ];
|
beamDeps = [ bandit finch jason mime plug telemetry ];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
table_rex =
|
||||||
|
let
|
||||||
|
version = "4.0.0";
|
||||||
|
in
|
||||||
|
buildMix {
|
||||||
|
inherit version;
|
||||||
|
name = "table_rex";
|
||||||
|
|
||||||
|
src = fetchHex {
|
||||||
|
inherit version;
|
||||||
|
pkg = "table_rex";
|
||||||
|
sha256 = "c35c4d5612ca49ebb0344ea10387da4d2afe278387d4019e4d8111e815df8f55";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
tailwind =
|
tailwind =
|
||||||
let
|
let
|
||||||
version = "0.2.4";
|
version = "0.2.4";
|
||||||
|
|
|
||||||
142
flake.nix
142
flake.nix
|
|
@ -18,15 +18,122 @@
|
||||||
...
|
...
|
||||||
} @ inputs: let
|
} @ inputs: let
|
||||||
# Set the Erlang version
|
# Set the Erlang version
|
||||||
erlangVersion = "erlang_27";
|
erlangVersion = "erlang";
|
||||||
# Set the Elixir version
|
# Set the Elixir version
|
||||||
elixirVersion = "elixir_1_17";
|
elixirVersion = "elixir";
|
||||||
in
|
in
|
||||||
flake-parts.lib.mkFlake {inherit inputs;} {
|
flake-parts.lib.mkFlake {inherit inputs;} {
|
||||||
systems = import systems;
|
systems = import systems;
|
||||||
|
|
||||||
imports = [inputs.process-compose-flake.flakeModule];
|
imports = [inputs.process-compose-flake.flakeModule];
|
||||||
|
|
||||||
|
flake = {
|
||||||
|
nixosModules.default = {
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
with lib; let
|
||||||
|
cfg = config.sites.zoeycomputer;
|
||||||
|
in {
|
||||||
|
options.sites.zoeycomputer = {
|
||||||
|
enable = mkEnableOption "Enables the zoey computer";
|
||||||
|
|
||||||
|
domain = mkOption rec {
|
||||||
|
type = types.str;
|
||||||
|
default = "zoeys.computer";
|
||||||
|
example = default;
|
||||||
|
description = "The domain name for the website";
|
||||||
|
};
|
||||||
|
|
||||||
|
ssl = mkOption rec {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
example = default;
|
||||||
|
description = "Whether to enable SSL on the domain or not";
|
||||||
|
};
|
||||||
|
|
||||||
|
phx = {
|
||||||
|
port = mkOption rec {
|
||||||
|
type = types.int;
|
||||||
|
default = 4000;
|
||||||
|
example = default;
|
||||||
|
description = "What port should phoenix run on";
|
||||||
|
};
|
||||||
|
|
||||||
|
package = mkOption rec {
|
||||||
|
type = types.derivation;
|
||||||
|
default = self.packages.${pkgs.system}.default;
|
||||||
|
example = default;
|
||||||
|
description = "The phoenix package containing the application";
|
||||||
|
};
|
||||||
|
|
||||||
|
enableServer = mkOption rec {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
example = default;
|
||||||
|
description = "Enable Phoenix Server, why would you not enable this?";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
services.nginx.virtualHosts.${cfg.domain} = {
|
||||||
|
forceSSL = cfg.ssl;
|
||||||
|
enableACME = cfg.ssl;
|
||||||
|
locations."/".proxyPass = "http://127.0.0.1:${cfg.phx.port}";
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.${release_name} = let
|
||||||
|
release_name = "zoeyscomputer";
|
||||||
|
working_directory = "/var/lib/zoeycomputer";
|
||||||
|
in {
|
||||||
|
wantedBy = ["multi-user.target"];
|
||||||
|
after = ["network.target" "postgresql.service"];
|
||||||
|
# note that if you are connecting to a postgres instance on a different host
|
||||||
|
# postgresql.service should not be included in the requires.
|
||||||
|
requires = ["network-online.target" "postgresql.service"];
|
||||||
|
description = "zoey computer";
|
||||||
|
environment = {
|
||||||
|
# RELEASE_TMP is used to write the state of the
|
||||||
|
# VM configuration when the system is running
|
||||||
|
# it needs to be a writable directory
|
||||||
|
RELEASE_TMP = working_directory;
|
||||||
|
# can be generated in an elixir console with
|
||||||
|
# Base.encode32(:crypto.strong_rand_bytes(32))
|
||||||
|
PORT = cfg.phx.port;
|
||||||
|
PHX_HOST = cfg.domain;
|
||||||
|
PHX_SERVER = cfg.phx.enableServer;
|
||||||
|
};
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "exec";
|
||||||
|
DynamicUser = true;
|
||||||
|
WorkingDirectory = working_directory;
|
||||||
|
# Implied by DynamicUser, but just to emphasize due to RELEASE_TMP
|
||||||
|
PrivateTmp = true;
|
||||||
|
ExecStart = pkgs.writeShellScript "start-zoeycomputer" ''
|
||||||
|
${cfg.phx.package}/bin/${release_name} eval "ZoeysComputer.Release.migrate"
|
||||||
|
${cfg.phx.package}/bin/${release_name} start
|
||||||
|
'';
|
||||||
|
ExecStop = ''
|
||||||
|
${cfg.phx.package}/bin/${release_name} stop
|
||||||
|
'';
|
||||||
|
ExecReload = ''
|
||||||
|
${cfg.phx.package}/bin/${release_name} restart
|
||||||
|
'';
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = 5;
|
||||||
|
StartLimitBurst = 3;
|
||||||
|
StartLimitInterval = 10;
|
||||||
|
};
|
||||||
|
# disksup requires bash
|
||||||
|
path = [pkgs.bash];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
perSystem = {
|
perSystem = {
|
||||||
# self',
|
# self',
|
||||||
config,
|
config,
|
||||||
|
|
@ -34,7 +141,17 @@
|
||||||
pkgs,
|
pkgs,
|
||||||
lib,
|
lib,
|
||||||
...
|
...
|
||||||
}: {
|
}: let
|
||||||
|
npmDeps = (pkgs.callPackage ./assets/default.nix {}).shell.nodeDependencies;
|
||||||
|
|
||||||
|
heroicons = pkgs.fetchFromGitHub {
|
||||||
|
owner = "tailwindlabs";
|
||||||
|
repo = "heroicons";
|
||||||
|
rev = "v2.1.1";
|
||||||
|
hash = "sha256-y/kY8HPJmzB2e7ErgkUdQijU7oUhfS3fI093Rsvyvqs=";
|
||||||
|
sparseCheckout = ["optimized"];
|
||||||
|
};
|
||||||
|
in {
|
||||||
# Define a consistent package set for development, testing, and
|
# Define a consistent package set for development, testing, and
|
||||||
# production.
|
# production.
|
||||||
_module.args.pkgs = import nixpkgs {
|
_module.args.pkgs = import nixpkgs {
|
||||||
|
|
@ -54,10 +171,10 @@
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
# Add hydraJob for x86_64-linux
|
# # Add hydraJob for x86_64-linux
|
||||||
hydraJobs = lib.optionalAttrs (system == "x86_64-linux") {
|
# hydraJobs = lib.optionalAttrs (system == "x86_64-linux") {
|
||||||
default = self.packages.${system}.default;
|
# default = self.packages.${system}.default;
|
||||||
};
|
# };
|
||||||
|
|
||||||
# You can build your Elixir application using mixRelease.
|
# You can build your Elixir application using mixRelease.
|
||||||
packages.default = pkgs.beamPackages.mixRelease {
|
packages.default = pkgs.beamPackages.mixRelease {
|
||||||
|
|
@ -71,7 +188,15 @@
|
||||||
};
|
};
|
||||||
buildInputs = with pkgs; [nodejs];
|
buildInputs = with pkgs; [nodejs];
|
||||||
|
|
||||||
MIX_ENV = "prod";
|
postBuild = ''
|
||||||
|
echo ${heroicons}
|
||||||
|
ln -sf ${npmDeps}/lib/node_modules assets/node_modules
|
||||||
|
ln -sf ${heroicons} deps/heroicons
|
||||||
|
ls deps/
|
||||||
|
npm run deploy --prefix ./assets
|
||||||
|
|
||||||
|
mix do deps.loadpaths --no-deps-check, phx.digest
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
# Add dependencies to develop your application using Mix.
|
# Add dependencies to develop your application using Mix.
|
||||||
|
|
@ -84,6 +209,7 @@
|
||||||
# application.
|
# application.
|
||||||
nodejs
|
nodejs
|
||||||
mix2nix
|
mix2nix
|
||||||
|
node2nix
|
||||||
# Add the language server of your choice.
|
# Add the language server of your choice.
|
||||||
inputs.lexical.packages.${system}.default
|
inputs.lexical.packages.${system}.default
|
||||||
# I once added Hex via a Nix development shell, but now I install
|
# I once added Hex via a Nix development shell, but now I install
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ defmodule Zoeyscomputer.Users.User do
|
||||||
field :hashed_password, :string, redact: true
|
field :hashed_password, :string, redact: true
|
||||||
field :current_password, :string, virtual: true, redact: true
|
field :current_password, :string, virtual: true, redact: true
|
||||||
field :confirmed_at, :utc_datetime
|
field :confirmed_at, :utc_datetime
|
||||||
|
field :admin, :boolean, default: false
|
||||||
|
|
||||||
timestamps(type: :utc_datetime)
|
timestamps(type: :utc_datetime)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ defmodule ZoeyscomputerWeb.CoreComponents do
|
||||||
phx-window-keydown={JS.exec("data-cancel", to: "##{@id}")}
|
phx-window-keydown={JS.exec("data-cancel", to: "##{@id}")}
|
||||||
phx-key="escape"
|
phx-key="escape"
|
||||||
phx-click-away={JS.exec("data-cancel", to: "##{@id}")}
|
phx-click-away={JS.exec("data-cancel", to: "##{@id}")}
|
||||||
class="shadow-zinc-700/10 ring-zinc-700/10 relative hidden rounded-2xl bg-white p-14 shadow-lg ring-1 transition"
|
class="shadow-zinc-700/10 ring-zinc-700/10 relative hidden rounded-2xl bg-ctp-base p-14 shadow-lg ring-1 transition"
|
||||||
>
|
>
|
||||||
<div class="absolute top-6 right-5">
|
<div class="absolute top-6 right-5">
|
||||||
<button
|
<button
|
||||||
|
|
@ -202,7 +202,7 @@ defmodule ZoeyscomputerWeb.CoreComponents do
|
||||||
def simple_form(assigns) do
|
def simple_form(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<.form :let={f} for={@for} as={@as} {@rest}>
|
<.form :let={f} for={@for} as={@as} {@rest}>
|
||||||
<div class="mt-10 space-y-8 bg-white">
|
<div class="mt-10 space-y-8 bg-ctp-base">
|
||||||
<%= render_slot(@inner_block, f) %>
|
<%= render_slot(@inner_block, f) %>
|
||||||
<div :for={action <- @actions} class="mt-2 flex items-center justify-between gap-6">
|
<div :for={action <- @actions} class="mt-2 flex items-center justify-between gap-6">
|
||||||
<%= render_slot(action, f) %>
|
<%= render_slot(action, f) %>
|
||||||
|
|
@ -310,7 +310,7 @@ defmodule ZoeyscomputerWeb.CoreComponents do
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<div>
|
<div>
|
||||||
<label class="flex items-center gap-4 text-sm leading-6 text-zinc-600">
|
<label class="flex items-center gap-4 text-sm leading-6 text-ctp-overlay2">
|
||||||
<input type="hidden" name={@name} value="false" disabled={@rest[:disabled]} />
|
<input type="hidden" name={@name} value="false" disabled={@rest[:disabled]} />
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
|
@ -318,7 +318,7 @@ defmodule ZoeyscomputerWeb.CoreComponents do
|
||||||
name={@name}
|
name={@name}
|
||||||
value="true"
|
value="true"
|
||||||
checked={@checked}
|
checked={@checked}
|
||||||
class="rounded border-zinc-300 text-zinc-900 focus:ring-0"
|
class="rounded border-ctp-surface2 text-ctp-overlay2 focus:ring-0"
|
||||||
{@rest}
|
{@rest}
|
||||||
/>
|
/>
|
||||||
<%= @label %>
|
<%= @label %>
|
||||||
|
|
@ -335,7 +335,7 @@ defmodule ZoeyscomputerWeb.CoreComponents do
|
||||||
<select
|
<select
|
||||||
id={@id}
|
id={@id}
|
||||||
name={@name}
|
name={@name}
|
||||||
class="mt-2 block w-full rounded-md border border-gray-300 bg-white shadow-sm focus:border-zinc-400 focus:ring-0 sm:text-sm"
|
class="mt-2 block w-full rounded-md border border-gray-300 bg-ctp-base shadow-sm focus:border-zinc-400 focus:ring-0 sm:text-sm"
|
||||||
multiple={@multiple}
|
multiple={@multiple}
|
||||||
{@rest}
|
{@rest}
|
||||||
>
|
>
|
||||||
|
|
@ -355,8 +355,8 @@ defmodule ZoeyscomputerWeb.CoreComponents do
|
||||||
id={@id}
|
id={@id}
|
||||||
name={@name}
|
name={@name}
|
||||||
class={[
|
class={[
|
||||||
"mt-2 block w-full rounded-lg text-zinc-900 focus:ring-0 sm:text-sm sm:leading-6 min-h-[6rem]",
|
"mt-2 block w-full rounded-lg text-ctp-overlay2 focus:ring-0 sm:text-sm sm:leading-6 min-h-[6rem]",
|
||||||
@errors == [] && "border-zinc-300 focus:border-zinc-400",
|
@errors == [] && "border-ctp-surface2 focus:border-zinc-400",
|
||||||
@errors != [] && "border-rose-400 focus:border-rose-400"
|
@errors != [] && "border-rose-400 focus:border-rose-400"
|
||||||
]}
|
]}
|
||||||
{@rest}
|
{@rest}
|
||||||
|
|
@ -377,8 +377,8 @@ defmodule ZoeyscomputerWeb.CoreComponents do
|
||||||
id={@id}
|
id={@id}
|
||||||
value={Phoenix.HTML.Form.normalize_value(@type, @value)}
|
value={Phoenix.HTML.Form.normalize_value(@type, @value)}
|
||||||
class={[
|
class={[
|
||||||
"mt-2 block w-full rounded-lg text-zinc-900 focus:ring-0 sm:text-sm sm:leading-6",
|
"mt-2 block bg-ctp-base border border-ctp-crust w-full rounded-lg text-ctp-text focus:ring-0 sm:text-sm sm:leading-6",
|
||||||
@errors == [] && "border-zinc-300 focus:border-zinc-400",
|
@errors == [] && "border-ctp-surface2 focus:border-ctp-mauve",
|
||||||
@errors != [] && "border-rose-400 focus:border-rose-400"
|
@errors != [] && "border-rose-400 focus:border-rose-400"
|
||||||
]}
|
]}
|
||||||
{@rest}
|
{@rest}
|
||||||
|
|
@ -396,7 +396,7 @@ defmodule ZoeyscomputerWeb.CoreComponents do
|
||||||
|
|
||||||
def label(assigns) do
|
def label(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<label for={@for} class="block text-sm font-semibold leading-6 text-zinc-800">
|
<label for={@for} class="block text-sm font-semibold leading-6 text-ctp-text">
|
||||||
<%= render_slot(@inner_block) %>
|
<%= render_slot(@inner_block) %>
|
||||||
</label>
|
</label>
|
||||||
"""
|
"""
|
||||||
|
|
@ -429,10 +429,10 @@ defmodule ZoeyscomputerWeb.CoreComponents do
|
||||||
~H"""
|
~H"""
|
||||||
<header class={[@actions != [] && "flex items-center justify-between gap-6", @class]}>
|
<header class={[@actions != [] && "flex items-center justify-between gap-6", @class]}>
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-lg font-semibold leading-8 text-zinc-800">
|
<h1 class="text-lg font-semibold leading-8">
|
||||||
<%= render_slot(@inner_block) %>
|
<%= render_slot(@inner_block) %>
|
||||||
</h1>
|
</h1>
|
||||||
<p :if={@subtitle != []} class="mt-2 text-sm leading-6 text-zinc-600">
|
<p :if={@subtitle != []} class="mt-2 text-sm leading-6 text-ctp-overlay2">
|
||||||
<%= render_slot(@subtitle) %>
|
<%= render_slot(@subtitle) %>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -486,7 +486,7 @@ defmodule ZoeyscomputerWeb.CoreComponents do
|
||||||
<tbody
|
<tbody
|
||||||
id={@id}
|
id={@id}
|
||||||
phx-update={match?(%Phoenix.LiveView.LiveStream{}, @rows) && "stream"}
|
phx-update={match?(%Phoenix.LiveView.LiveStream{}, @rows) && "stream"}
|
||||||
class="relative divide-y divide-zinc-100 border-t border-zinc-200 text-sm leading-6 text-zinc-700"
|
class="relative divide-y divide-zinc-100 border-t border-zinc-200 text-sm leading-6 text-ctp-overlay1"
|
||||||
>
|
>
|
||||||
<tr :for={row <- @rows} id={@row_id && @row_id.(row)} class="group hover:bg-zinc-50">
|
<tr :for={row <- @rows} id={@row_id && @row_id.(row)} class="group hover:bg-zinc-50">
|
||||||
<td
|
<td
|
||||||
|
|
@ -496,7 +496,7 @@ defmodule ZoeyscomputerWeb.CoreComponents do
|
||||||
>
|
>
|
||||||
<div class="block py-4 pr-6">
|
<div class="block py-4 pr-6">
|
||||||
<span class="absolute -inset-y-px right-0 -left-4 group-hover:bg-zinc-50 sm:rounded-l-xl" />
|
<span class="absolute -inset-y-px right-0 -left-4 group-hover:bg-zinc-50 sm:rounded-l-xl" />
|
||||||
<span class={["relative", i == 0 && "font-semibold text-zinc-900"]}>
|
<span class={["relative", i == 0 && "font-semibold text-ctp-overlay2"]}>
|
||||||
<%= render_slot(col, @row_item.(row)) %>
|
<%= render_slot(col, @row_item.(row)) %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -506,7 +506,7 @@ defmodule ZoeyscomputerWeb.CoreComponents do
|
||||||
<span class="absolute -inset-y-px -right-4 left-0 group-hover:bg-zinc-50 sm:rounded-r-xl" />
|
<span class="absolute -inset-y-px -right-4 left-0 group-hover:bg-zinc-50 sm:rounded-r-xl" />
|
||||||
<span
|
<span
|
||||||
:for={action <- @action}
|
:for={action <- @action}
|
||||||
class="relative ml-4 font-semibold leading-6 text-zinc-900 hover:text-zinc-700"
|
class="relative ml-4 font-semibold leading-6 text-ctp-overlay2 hover:text-ctp-overlay1"
|
||||||
>
|
>
|
||||||
<%= render_slot(action, @row_item.(row)) %>
|
<%= render_slot(action, @row_item.(row)) %>
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -539,7 +539,7 @@ defmodule ZoeyscomputerWeb.CoreComponents do
|
||||||
<dl class="-my-4 divide-y divide-zinc-100">
|
<dl class="-my-4 divide-y divide-zinc-100">
|
||||||
<div :for={item <- @item} class="flex gap-4 py-4 text-sm leading-6 sm:gap-8">
|
<div :for={item <- @item} class="flex gap-4 py-4 text-sm leading-6 sm:gap-8">
|
||||||
<dt class="w-1/4 flex-none text-zinc-500"><%= item.title %></dt>
|
<dt class="w-1/4 flex-none text-zinc-500"><%= item.title %></dt>
|
||||||
<dd class="text-zinc-700"><%= render_slot(item) %></dd>
|
<dd class="text-ctp-overlay1"><%= render_slot(item) %></dd>
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -561,7 +561,7 @@ defmodule ZoeyscomputerWeb.CoreComponents do
|
||||||
<div class="mt-16">
|
<div class="mt-16">
|
||||||
<.link
|
<.link
|
||||||
navigate={@navigate}
|
navigate={@navigate}
|
||||||
class="text-sm font-semibold leading-6 text-zinc-900 hover:text-zinc-700"
|
class="text-sm font-semibold leading-6 text-ctp-overlay2 hover:text-ctp-overlay1"
|
||||||
>
|
>
|
||||||
<.icon name="hero-arrow-left-solid" class="h-3 w-3" />
|
<.icon name="hero-arrow-left-solid" class="h-3 w-3" />
|
||||||
<%= render_slot(@inner_block) %>
|
<%= render_slot(@inner_block) %>
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,30 @@
|
||||||
<header class="px-4 sm:px-6 lg:px-8">
|
<%!-- <header class="px-4 sm:px-6 lg:px-8"> --%>
|
||||||
<div class="flex items-center justify-between border-b border-zinc-100 py-3 text-sm">
|
<%!-- <div class="flex items-center justify-between border-b border-zinc-100 py-3 text-sm"> --%>
|
||||||
<div class="flex items-center gap-4">
|
<%!-- <div class="flex items-center gap-4"> --%>
|
||||||
<a href="/">
|
<%!-- <a href="/"> --%>
|
||||||
<img src={~p"/images/logo.svg"} width="36" />
|
<%!-- <img src={~p"/images/logo.svg"} width="36" /> --%>
|
||||||
</a>
|
<%!-- </a> --%>
|
||||||
<p class="bg-brand/5 text-brand rounded-full px-2 font-medium leading-6">
|
<%!-- <p class="bg-brand/5 text-brand rounded-full px-2 font-medium leading-6"> --%>
|
||||||
v<%= Application.spec(:phoenix, :vsn) %>
|
<%!-- v<%= Application.spec(:phoenix, :vsn) %> --%>
|
||||||
</p>
|
<%!-- </p> --%>
|
||||||
</div>
|
<%!-- </div> --%>
|
||||||
<div class="flex items-center gap-4 font-semibold leading-6 text-zinc-900">
|
<%!-- <div class="flex items-center gap-4 font-semibold leading-6 text-zinc-900"> --%>
|
||||||
<a href="https://twitter.com/elixirphoenix" class="hover:text-zinc-700">
|
<%!-- <a href="https://twitter.com/elixirphoenix" class="hover:text-zinc-700"> --%>
|
||||||
@elixirphoenix
|
<%!-- @elixirphoenix --%>
|
||||||
</a>
|
<%!-- </a> --%>
|
||||||
<a href="https://github.com/phoenixframework/phoenix" class="hover:text-zinc-700">
|
<%!-- <a href="https://github.com/phoenixframework/phoenix" class="hover:text-zinc-700"> --%>
|
||||||
GitHub
|
<%!-- GitHub --%>
|
||||||
</a>
|
<%!-- </a> --%>
|
||||||
<a
|
<%!-- <a --%>
|
||||||
href="https://hexdocs.pm/phoenix/overview.html"
|
<%!-- href="https://hexdocs.pm/phoenix/overview.html" --%>
|
||||||
class="rounded-lg bg-zinc-100 px-2 py-1 hover:bg-zinc-200/80"
|
<%!-- class="rounded-lg bg-zinc-100 px-2 py-1 hover:bg-zinc-200/80" --%>
|
||||||
>
|
<%!-- > --%>
|
||||||
Get Started <span aria-hidden="true">→</span>
|
<%!-- Get Started <span aria-hidden="true">→</span> --%>
|
||||||
</a>
|
<%!-- </a> --%>
|
||||||
</div>
|
<%!-- </div> --%>
|
||||||
</div>
|
<%!-- </div> --%>
|
||||||
</header>
|
<%!-- </header> --%>
|
||||||
<main class="px-4 py-20 sm:px-6 lg:px-8">
|
<main class="bg-ctp-base px-4 py-20 sm:px-6 lg:px-8">
|
||||||
<div class="mx-auto max-w-2xl">
|
<div class="mx-auto max-w-2xl">
|
||||||
<.flash_group flash={@flash} />
|
<.flash_group flash={@flash} />
|
||||||
<%= @inner_content %>
|
<%= @inner_content %>
|
||||||
|
|
|
||||||
|
|
@ -4,23 +4,32 @@
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="csrf-token" content={get_csrf_token()} />
|
<meta name="csrf-token" content={get_csrf_token()} />
|
||||||
<.live_title suffix=" · Phoenix Framework">
|
<.live_title suffix=" - zoeys.computer">
|
||||||
<%= assigns[:page_title] || "Zoeyscomputer" %>
|
<%= assigns[:page_title] || "" %>
|
||||||
</.live_title>
|
</.live_title>
|
||||||
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
|
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
|
||||||
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
|
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-white">
|
<body class="ctp-mocha bg-ctp-base text-ctp-overlay0">
|
||||||
<ul class="relative z-10 flex items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end">
|
|
||||||
<%= if @current_user do %>
|
<%= if @current_user do %>
|
||||||
<li class="text-[0.8125rem] leading-6 text-zinc-900">
|
<div class="flex w-full">
|
||||||
|
<div class="p-4 grow">
|
||||||
|
<.link
|
||||||
|
class="text-ctp-mauve font-bold hover:border-b-2 border-ctp-mauve"
|
||||||
|
navigate={~p"/"}
|
||||||
|
>
|
||||||
|
zoey
|
||||||
|
</.link>
|
||||||
|
</div>
|
||||||
|
<ul class="relative z-10 flex items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end">
|
||||||
|
<li class="text-[0.8125rem] leading-6">
|
||||||
<%= @current_user.email %>
|
<%= @current_user.email %>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<.link
|
<.link
|
||||||
href={~p"/users/settings"}
|
href={~p"/users/settings"}
|
||||||
class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
|
class="text-[0.8125rem] leading-6 font-semibold hover:text-ctp-mauve"
|
||||||
>
|
>
|
||||||
Settings
|
Settings
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -29,30 +38,14 @@
|
||||||
<.link
|
<.link
|
||||||
href={~p"/users/log_out"}
|
href={~p"/users/log_out"}
|
||||||
method="delete"
|
method="delete"
|
||||||
class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
|
class="text-[0.8125rem] leading-6 font-semibold hover:text-ctp-mauve"
|
||||||
>
|
>
|
||||||
Log out
|
Log out
|
||||||
</.link>
|
</.link>
|
||||||
</li>
|
</li>
|
||||||
<% else %>
|
|
||||||
<li>
|
|
||||||
<.link
|
|
||||||
href={~p"/users/register"}
|
|
||||||
class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
|
|
||||||
>
|
|
||||||
Register
|
|
||||||
</.link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<.link
|
|
||||||
href={~p"/users/log_in"}
|
|
||||||
class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
|
|
||||||
>
|
|
||||||
Log in
|
|
||||||
</.link>
|
|
||||||
</li>
|
|
||||||
<% end %>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
<%= @inner_content %>
|
<%= @inner_content %>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,222 +1,9 @@
|
||||||
<.flash_group flash={@flash} />
|
<div class="container p-4 flex justify-center align-middle">
|
||||||
<div class="left-[40rem] fixed inset-y-0 right-0 z-0 hidden lg:block xl:left-[50rem]">
|
<div class="border border-ctp-overlay0 rounded-md p-4 flex-col flex items-center">
|
||||||
<svg
|
<h1 class="font-bold text-ctp-mauve">zoey</h1>
|
||||||
viewBox="0 0 1480 957"
|
<p class="text-ctp-text"><i>Software Engineer 🏳️⚧️</i></p>
|
||||||
fill="none"
|
<p class="max-w-96 text-center mt-4 text-ctp-overlay2">
|
||||||
aria-hidden="true"
|
Currently cooking this up, stay tuned... in the meantime, you can monitor my server's resources.
|
||||||
class="absolute inset-0 h-full w-full"
|
|
||||||
preserveAspectRatio="xMinYMid slice"
|
|
||||||
>
|
|
||||||
<path fill="#EE7868" d="M0 0h1480v957H0z" />
|
|
||||||
<path
|
|
||||||
d="M137.542 466.27c-582.851-48.41-988.806-82.127-1608.412 658.2l67.39 810 3083.15-256.51L1535.94-49.622l-98.36 8.183C1269.29 281.468 734.115 515.799 146.47 467.012l-8.928-.742Z"
|
|
||||||
fill="#FF9F92"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M371.028 528.664C-169.369 304.988-545.754 149.198-1361.45 665.565l-182.58 792.025 3014.73 694.98 389.42-1689.25-96.18-22.171C1505.28 697.438 924.153 757.586 379.305 532.09l-8.277-3.426Z"
|
|
||||||
fill="#FA8372"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M359.326 571.714C-104.765 215.795-428.003-32.102-1349.55 255.554l-282.3 1224.596 3047.04 722.01 312.24-1354.467C1411.25 1028.3 834.355 935.995 366.435 577.166l-7.109-5.452Z"
|
|
||||||
fill="#E96856"
|
|
||||||
fill-opacity=".6"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M1593.87 1236.88c-352.15 92.63-885.498-145.85-1244.602-613.557l-5.455-7.105C-12.347 152.31-260.41-170.8-1225-131.458l-368.63 1599.048 3057.19 704.76 130.31-935.47Z"
|
|
||||||
fill="#C42652"
|
|
||||||
fill-opacity=".2"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M1411.91 1526.93c-363.79 15.71-834.312-330.6-1085.883-863.909l-3.822-8.102C72.704 125.95-101.074-242.476-1052.01-408.907l-699.85 1484.267 2837.75 1338.01 326.02-886.44Z"
|
|
||||||
fill="#A41C42"
|
|
||||||
fill-opacity=".2"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M1116.26 1863.69c-355.457-78.98-720.318-535.27-825.287-1115.521l-1.594-8.816C185.286 163.833 112.786-237.016-762.678-643.898L-1822.83 608.665 571.922 2635.55l544.338-771.86Z"
|
|
||||||
fill="#A41C42"
|
|
||||||
fill-opacity=".2"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="px-4 py-10 sm:px-6 sm:py-28 lg:px-8 xl:px-28 xl:py-32">
|
|
||||||
<div class="mx-auto max-w-xl lg:mx-0">
|
|
||||||
<svg viewBox="0 0 71 48" class="h-12" aria-hidden="true">
|
|
||||||
<path
|
|
||||||
d="m26.371 33.477-.552-.1c-3.92-.729-6.397-3.1-7.57-6.829-.733-2.324.597-4.035 3.035-4.148 1.995-.092 3.362 1.055 4.57 2.39 1.557 1.72 2.984 3.558 4.514 5.305 2.202 2.515 4.797 4.134 8.347 3.634 3.183-.448 5.958-1.725 8.371-3.828.363-.316.761-.592 1.144-.886l-.241-.284c-2.027.63-4.093.841-6.205.735-3.195-.16-6.24-.828-8.964-2.582-2.486-1.601-4.319-3.746-5.19-6.611-.704-2.315.736-3.934 3.135-3.6.948.133 1.746.56 2.463 1.165.583.493 1.143 1.015 1.738 1.493 2.8 2.25 6.712 2.375 10.265-.068-5.842-.026-9.817-3.24-13.308-7.313-1.366-1.594-2.7-3.216-4.095-4.785-2.698-3.036-5.692-5.71-9.79-6.623C12.8-.623 7.745.14 2.893 2.361 1.926 2.804.997 3.319 0 4.149c.494 0 .763.006 1.032 0 2.446-.064 4.28 1.023 5.602 3.024.962 1.457 1.415 3.104 1.761 4.798.513 2.515.247 5.078.544 7.605.761 6.494 4.08 11.026 10.26 13.346 2.267.852 4.591 1.135 7.172.555ZM10.751 3.852c-.976.246-1.756-.148-2.56-.962 1.377-.343 2.592-.476 3.897-.528-.107.848-.607 1.306-1.336 1.49Zm32.002 37.924c-.085-.626-.62-.901-1.04-1.228-1.857-1.446-4.03-1.958-6.333-2-1.375-.026-2.735-.128-4.031-.61-.595-.22-1.26-.505-1.244-1.272.015-.78.693-1 1.31-1.184.505-.15 1.026-.247 1.6-.382-1.46-.936-2.886-1.065-4.787-.3-2.993 1.202-5.943 1.06-8.926-.017-1.684-.608-3.179-1.563-4.735-2.408l-.043.03a2.96 2.96 0 0 0 .04-.029c-.038-.117-.107-.12-.197-.054l.122.107c1.29 2.115 3.034 3.817 5.004 5.271 3.793 2.8 7.936 4.471 12.784 3.73A66.714 66.714 0 0 1 37 40.877c1.98-.16 3.866.398 5.753.899Zm-9.14-30.345c-.105-.076-.206-.266-.42-.069 1.745 2.36 3.985 4.098 6.683 5.193 4.354 1.767 8.773 2.07 13.293.51 3.51-1.21 6.033-.028 7.343 3.38.19-3.955-2.137-6.837-5.843-7.401-2.084-.318-4.01.373-5.962.94-5.434 1.575-10.485.798-15.094-2.553Zm27.085 15.425c.708.059 1.416.123 2.124.185-1.6-1.405-3.55-1.517-5.523-1.404-3.003.17-5.167 1.903-7.14 3.972-1.739 1.824-3.31 3.87-5.903 4.604.043.078.054.117.066.117.35.005.699.021 1.047.005 3.768-.17 7.317-.965 10.14-3.7.89-.86 1.685-1.817 2.544-2.71.716-.746 1.584-1.159 2.645-1.07Zm-8.753-4.67c-2.812.246-5.254 1.409-7.548 2.943-1.766 1.18-3.654 1.738-5.776 1.37-.374-.066-.75-.114-1.124-.17l-.013.156c.135.07.265.151.405.207.354.14.702.308 1.07.395 4.083.971 7.992.474 11.516-1.803 2.221-1.435 4.521-1.707 7.013-1.336.252.038.503.083.756.107.234.022.479.255.795.003-2.179-1.574-4.526-2.096-7.094-1.872Zm-10.049-9.544c1.475.051 2.943-.142 4.486-1.059-.452.04-.643.04-.827.076-2.126.424-4.033-.04-5.733-1.383-.623-.493-1.257-.974-1.889-1.457-2.503-1.914-5.374-2.555-8.514-2.5.05.154.054.26.108.315 3.417 3.455 7.371 5.836 12.369 6.008Zm24.727 17.731c-2.114-2.097-4.952-2.367-7.578-.537 1.738.078 3.043.632 4.101 1.728.374.388.763.768 1.182 1.106 1.6 1.29 4.311 1.352 5.896.155-1.861-.726-1.861-.726-3.601-2.452Zm-21.058 16.06c-1.858-3.46-4.981-4.24-8.59-4.008a9.667 9.667 0 0 1 2.977 1.39c.84.586 1.547 1.311 2.243 2.055 1.38 1.473 3.534 2.376 4.962 2.07-.656-.412-1.238-.848-1.592-1.507Zm17.29-19.32c0-.023.001-.045.003-.068l-.006.006.006-.006-.036-.004.021.018.012.053Zm-20 14.744a7.61 7.61 0 0 0-.072-.041.127.127 0 0 0 .015.043c.005.008.038 0 .058-.002Zm-.072-.041-.008-.034-.008.01.008-.01-.022-.006.005.026.024.014Z"
|
|
||||||
fill="#FD4F00"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<h1 class="text-brand mt-10 flex items-center text-sm font-semibold leading-6">
|
|
||||||
Phoenix Framework
|
|
||||||
<small class="bg-brand/5 text-[0.8125rem] ml-3 rounded-full px-2 font-medium leading-6">
|
|
||||||
v<%= Application.spec(:phoenix, :vsn) %>
|
|
||||||
</small>
|
|
||||||
</h1>
|
|
||||||
<p class="text-[2rem] mt-4 font-semibold leading-10 tracking-tighter text-zinc-900 text-balance">
|
|
||||||
Peace of mind from prototype to production.
|
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-4 text-base leading-7 text-zinc-600">
|
|
||||||
Build rich, interactive web applications quickly, with less code and fewer moving parts. Join our growing community of developers using Phoenix to craft APIs, HTML5 apps and more, for fun or at scale.
|
|
||||||
</p>
|
|
||||||
<div class="flex">
|
|
||||||
<div class="w-full sm:w-auto">
|
|
||||||
<div class="mt-10 grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-3">
|
|
||||||
<a
|
|
||||||
href="https://hexdocs.pm/phoenix/overview.html"
|
|
||||||
class="group relative rounded-2xl px-6 py-4 text-sm font-semibold leading-6 text-zinc-900 sm:py-6"
|
|
||||||
>
|
|
||||||
<span class="absolute inset-0 rounded-2xl bg-zinc-50 transition group-hover:bg-zinc-100 sm:group-hover:scale-105">
|
|
||||||
</span>
|
|
||||||
<span class="relative flex items-center gap-4 sm:flex-col">
|
|
||||||
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-6 w-6">
|
|
||||||
<path d="m12 4 10-2v18l-10 2V4Z" fill="#18181B" fill-opacity=".15" />
|
|
||||||
<path
|
|
||||||
d="M12 4 2 2v18l10 2m0-18v18m0-18 10-2v18l-10 2"
|
|
||||||
stroke="#18181B"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Guides & Docs
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="https://github.com/phoenixframework/phoenix"
|
|
||||||
class="group relative rounded-2xl px-6 py-4 text-sm font-semibold leading-6 text-zinc-900 sm:py-6"
|
|
||||||
>
|
|
||||||
<span class="absolute inset-0 rounded-2xl bg-zinc-50 transition group-hover:bg-zinc-100 sm:group-hover:scale-105">
|
|
||||||
</span>
|
|
||||||
<span class="relative flex items-center gap-4 sm:flex-col">
|
|
||||||
<svg viewBox="0 0 24 24" aria-hidden="true" class="h-6 w-6">
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
d="M12 0C5.37 0 0 5.506 0 12.303c0 5.445 3.435 10.043 8.205 11.674.6.107.825-.262.825-.585 0-.292-.015-1.261-.015-2.291C6 21.67 5.22 20.346 4.98 19.654c-.135-.354-.72-1.446-1.23-1.738-.42-.23-1.02-.8-.015-.815.945-.015 1.62.892 1.845 1.261 1.08 1.86 2.805 1.338 3.495 1.015.105-.8.42-1.338.765-1.645-2.67-.308-5.46-1.37-5.46-6.075 0-1.338.465-2.446 1.23-3.307-.12-.308-.54-1.569.12-3.26 0 0 1.005-.323 3.3 1.26.96-.276 1.98-.415 3-.415s2.04.139 3 .416c2.295-1.6 3.3-1.261 3.3-1.261.66 1.691.24 2.952.12 3.26.765.861 1.23 1.953 1.23 3.307 0 4.721-2.805 5.767-5.475 6.075.435.384.81 1.122.81 2.276 0 1.645-.015 2.968-.015 3.383 0 .323.225.707.825.585a12.047 12.047 0 0 0 5.919-4.489A12.536 12.536 0 0 0 24 12.304C24 5.505 18.63 0 12 0Z"
|
|
||||||
fill="#18181B"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Source Code
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href={"https://github.com/phoenixframework/phoenix/blob/v#{Application.spec(:phoenix, :vsn)}/CHANGELOG.md"}
|
|
||||||
class="group relative rounded-2xl px-6 py-4 text-sm font-semibold leading-6 text-zinc-900 sm:py-6"
|
|
||||||
>
|
|
||||||
<span class="absolute inset-0 rounded-2xl bg-zinc-50 transition group-hover:bg-zinc-100 sm:group-hover:scale-105">
|
|
||||||
</span>
|
|
||||||
<span class="relative flex items-center gap-4 sm:flex-col">
|
|
||||||
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-6 w-6">
|
|
||||||
<path
|
|
||||||
d="M12 1v6M12 17v6"
|
|
||||||
stroke="#18181B"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
/>
|
|
||||||
<circle
|
|
||||||
cx="12"
|
|
||||||
cy="12"
|
|
||||||
r="4"
|
|
||||||
fill="#18181B"
|
|
||||||
fill-opacity=".15"
|
|
||||||
stroke="#18181B"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Changelog
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="mt-10 grid grid-cols-1 gap-y-4 text-sm leading-6 text-zinc-700 sm:grid-cols-2">
|
|
||||||
<div>
|
|
||||||
<a
|
|
||||||
href="https://twitter.com/elixirphoenix"
|
|
||||||
class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-zinc-50 hover:text-zinc-900"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
aria-hidden="true"
|
|
||||||
class="h-4 w-4 fill-zinc-400 group-hover:fill-zinc-600"
|
|
||||||
>
|
|
||||||
<path d="M5.403 14c5.283 0 8.172-4.617 8.172-8.62 0-.131 0-.262-.008-.391A6.033 6.033 0 0 0 15 3.419a5.503 5.503 0 0 1-1.65.477 3.018 3.018 0 0 0 1.263-1.676 5.579 5.579 0 0 1-1.824.736 2.832 2.832 0 0 0-1.63-.916 2.746 2.746 0 0 0-1.821.319A2.973 2.973 0 0 0 8.076 3.78a3.185 3.185 0 0 0-.182 1.938 7.826 7.826 0 0 1-3.279-.918 8.253 8.253 0 0 1-2.64-2.247 3.176 3.176 0 0 0-.315 2.208 3.037 3.037 0 0 0 1.203 1.836A2.739 2.739 0 0 1 1.56 6.22v.038c0 .7.23 1.377.65 1.919.42.54 1.004.912 1.654 1.05-.423.122-.866.14-1.297.052.184.602.541 1.129 1.022 1.506a2.78 2.78 0 0 0 1.662.598 5.656 5.656 0 0 1-2.007 1.074A5.475 5.475 0 0 1 1 12.64a7.827 7.827 0 0 0 4.403 1.358" />
|
|
||||||
</svg>
|
|
||||||
Follow on Twitter
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<a
|
|
||||||
href="https://elixirforum.com"
|
|
||||||
class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-zinc-50 hover:text-zinc-900"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
aria-hidden="true"
|
|
||||||
class="h-4 w-4 fill-zinc-400 group-hover:fill-zinc-600"
|
|
||||||
>
|
|
||||||
<path d="M8 13.833c3.866 0 7-2.873 7-6.416C15 3.873 11.866 1 8 1S1 3.873 1 7.417c0 1.081.292 2.1.808 2.995.606 1.05.806 2.399.086 3.375l-.208.283c-.285.386-.01.905.465.85.852-.098 2.048-.318 3.137-.81a3.717 3.717 0 0 1 1.91-.318c.263.027.53.041.802.041Z" />
|
|
||||||
</svg>
|
|
||||||
Discuss on the Elixir Forum
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<a
|
|
||||||
href="https://web.libera.chat/#elixir"
|
|
||||||
class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-zinc-50 hover:text-zinc-900"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
aria-hidden="true"
|
|
||||||
class="h-4 w-4 fill-zinc-400 group-hover:fill-zinc-600"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
d="M6.356 2.007a.75.75 0 0 1 .637.849l-1.5 10.5a.75.75 0 1 1-1.485-.212l1.5-10.5a.75.75 0 0 1 .848-.637ZM11.356 2.008a.75.75 0 0 1 .637.848l-1.5 10.5a.75.75 0 0 1-1.485-.212l1.5-10.5a.75.75 0 0 1 .848-.636Z"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
d="M14 5.25a.75.75 0 0 1-.75.75h-9.5a.75.75 0 0 1 0-1.5h9.5a.75.75 0 0 1 .75.75ZM13 10.75a.75.75 0 0 1-.75.75h-9.5a.75.75 0 0 1 0-1.5h9.5a.75.75 0 0 1 .75.75Z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Chat on Libera IRC
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<a
|
|
||||||
href="https://discord.gg/elixir"
|
|
||||||
class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-zinc-50 hover:text-zinc-900"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
aria-hidden="true"
|
|
||||||
class="h-4 w-4 fill-zinc-400 group-hover:fill-zinc-600"
|
|
||||||
>
|
|
||||||
<path d="M13.545 2.995c-1.02-.46-2.114-.8-3.257-.994a.05.05 0 0 0-.052.024c-.141.246-.297.567-.406.82a12.377 12.377 0 0 0-3.658 0 8.238 8.238 0 0 0-.412-.82.052.052 0 0 0-.052-.024 13.315 13.315 0 0 0-3.257.994.046.046 0 0 0-.021.018C.356 6.063-.213 9.036.066 11.973c.001.015.01.029.02.038a13.353 13.353 0 0 0 3.996 1.987.052.052 0 0 0 .056-.018c.308-.414.582-.85.818-1.309a.05.05 0 0 0-.028-.069 8.808 8.808 0 0 1-1.248-.585.05.05 0 0 1-.005-.084c.084-.062.168-.126.248-.191a.05.05 0 0 1 .051-.007c2.619 1.176 5.454 1.176 8.041 0a.05.05 0 0 1 .053.006c.08.065.164.13.248.192a.05.05 0 0 1-.004.084c-.399.23-.813.423-1.249.585a.05.05 0 0 0-.027.07c.24.457.514.893.817 1.307a.051.051 0 0 0 .056.019 13.31 13.31 0 0 0 4.001-1.987.05.05 0 0 0 .021-.037c.334-3.396-.559-6.345-2.365-8.96a.04.04 0 0 0-.021-.02Zm-8.198 7.19c-.789 0-1.438-.712-1.438-1.587 0-.874.637-1.586 1.438-1.586.807 0 1.45.718 1.438 1.586 0 .875-.637 1.587-1.438 1.587Zm5.316 0c-.788 0-1.438-.712-1.438-1.587 0-.874.637-1.586 1.438-1.586.807 0 1.45.718 1.438 1.586 0 .875-.63 1.587-1.438 1.587Z" />
|
|
||||||
</svg>
|
|
||||||
Join our Discord server
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<a
|
|
||||||
href="https://fly.io/docs/elixir/getting-started/"
|
|
||||||
class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-zinc-50 hover:text-zinc-900"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
aria-hidden="true"
|
|
||||||
class="h-4 w-4 fill-zinc-400 group-hover:fill-zinc-600"
|
|
||||||
>
|
|
||||||
<path d="M1 12.5A4.5 4.5 0 005.5 17H15a4 4 0 001.866-7.539 3.504 3.504 0 00-4.504-4.272A4.5 4.5 0 004.06 8.235 4.502 4.502 0 001 12.5z" />
|
|
||||||
</svg>
|
|
||||||
Deploy your application
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
71
lib/zoeyscomputer_web/live/home_live.ex
Normal file
71
lib/zoeyscomputer_web/live/home_live.ex
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
defmodule ZoeyscomputerWeb.HomeLive do
|
||||||
|
use ZoeyscomputerWeb, :live_view
|
||||||
|
|
||||||
|
def mount(_params, _session, socket) do
|
||||||
|
if connected?(socket) do
|
||||||
|
:timer.send_interval(750, self(), :update_stats)
|
||||||
|
end
|
||||||
|
|
||||||
|
socket = assign(socket, cpu_usage: 0, memory_usage: 0, page_title: "home")
|
||||||
|
|
||||||
|
{:ok, socket}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_info(:update_stats, socket) do
|
||||||
|
{:noreply, assign(socket, get_stats())}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp get_stats do
|
||||||
|
cpu_usage = :cpu_sup.util()
|
||||||
|
mem_data = :memsup.get_system_memory_data()
|
||||||
|
|
||||||
|
total_memory = Keyword.get(mem_data, :total_memory, 0)
|
||||||
|
free_memory = Keyword.get(mem_data, :free_memory, 0)
|
||||||
|
|
||||||
|
memory_usage =
|
||||||
|
if total_memory > 0 do
|
||||||
|
(total_memory - free_memory) / total_memory * 100
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end
|
||||||
|
|
||||||
|
%{
|
||||||
|
cpu_usage: Float.round(cpu_usage, 2),
|
||||||
|
memory_usage: Float.round(100 - memory_usage, 2)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def render(assigns) do
|
||||||
|
~H"""
|
||||||
|
<div class="container p-4 flex justify-center align-middle">
|
||||||
|
<div class="border border-ctp-overlay0 rounded-md p-4 flex-col flex items-center">
|
||||||
|
<h1 class="font-bold text-ctp-mauve">zoey</h1>
|
||||||
|
<p class="text-ctp-text"><i>Software Engineer 🏳️⚧️</i></p>
|
||||||
|
<p class="max-w-96 text-center mt-4 text-ctp-overlay2">
|
||||||
|
Currently cooking this up, stay tuned... in the meantime, you can monitor my server's resources.
|
||||||
|
</p>
|
||||||
|
<div class="grid w-full text-center font-bold mt-4 grid-cols-2 gap-2">
|
||||||
|
<p class="text-ctp-overlay2 bg-ctp-crust p-2 rounded-md w-full border-ctp-mauve border">
|
||||||
|
CPU: <%= @cpu_usage %>%
|
||||||
|
</p>
|
||||||
|
<p class="text-ctp-overlay2 bg-ctp-crust p-2 rounded-md w-full border-ctp-sapphire border">
|
||||||
|
MEM: <%= @memory_usage %>%
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="text-ctp-overlay2 mt-4">
|
||||||
|
<%= if @current_user && @current_user.email == "hi@zoeys.computer" do %>
|
||||||
|
<p>
|
||||||
|
You are already signed in you silly billy
|
||||||
|
</p>
|
||||||
|
<% else %>
|
||||||
|
<p>
|
||||||
|
Are you me?
|
||||||
|
<.link class="font-bold text-ctp-pink" navigate={~p"/users/log_in"}>Log in.</.link>
|
||||||
|
</p>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -3,19 +3,25 @@ defmodule ZoeyscomputerWeb.UserLoginLive do
|
||||||
|
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="mx-auto max-w-sm">
|
<div class="mx-auto max-w-sm text-ctp-overlay2">
|
||||||
<.header class="text-center">
|
<.header class="text-center text-ctp-mauve">
|
||||||
Log in to account
|
Log in to account
|
||||||
<:subtitle>
|
<:subtitle>
|
||||||
Don't have an account?
|
Don't have an account?
|
||||||
<.link navigate={~p"/users/register"} class="font-semibold text-brand hover:underline">
|
<.link navigate={~p"/users/register"} class="font-semibold text-ctp-mauve hover:underline">
|
||||||
Sign up
|
Sign up
|
||||||
</.link>
|
</.link>
|
||||||
for an account now.
|
for an account now.
|
||||||
</:subtitle>
|
</:subtitle>
|
||||||
</.header>
|
</.header>
|
||||||
|
|
||||||
<.simple_form for={@form} id="login_form" action={~p"/users/log_in"} phx-update="ignore">
|
<.simple_form
|
||||||
|
class="bg-ctp-crust"
|
||||||
|
for={@form}
|
||||||
|
id="login_form"
|
||||||
|
action={~p"/users/log_in"}
|
||||||
|
phx-update="ignore"
|
||||||
|
>
|
||||||
<.input field={@form[:email]} type="email" label="Email" required />
|
<.input field={@form[:email]} type="email" label="Email" required />
|
||||||
<.input field={@form[:password]} type="password" label="Password" required />
|
<.input field={@form[:password]} type="password" label="Password" required />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,16 +17,19 @@ defmodule ZoeyscomputerWeb.Router do
|
||||||
plug :accepts, ["json"]
|
plug :accepts, ["json"]
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/", ZoeyscomputerWeb do
|
# scope "/", ZoeyscomputerWeb do
|
||||||
pipe_through :browser
|
# pipe_through :browser
|
||||||
|
#
|
||||||
get "/", PageController, :home
|
# live_session :current_user,
|
||||||
end
|
# on_mount: [{ZoeyscomputerWeb.UserAuth, :mount_current_user}] do
|
||||||
|
# live "/", HomeLive, :index
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
|
||||||
# Other scopes may use custom stacks.
|
# Other scopes may use custom stacks.
|
||||||
# scope "/api", ZoeyscomputerWeb do
|
scope "/api", ZoeyscomputerWeb do
|
||||||
# pipe_through :api
|
pipe_through :api
|
||||||
# end
|
end
|
||||||
|
|
||||||
# Enable LiveDashboard and Swoosh mailbox preview in development
|
# Enable LiveDashboard and Swoosh mailbox preview in development
|
||||||
if Application.compile_env(:zoeyscomputer, :dev_routes) do
|
if Application.compile_env(:zoeyscomputer, :dev_routes) do
|
||||||
|
|
@ -38,9 +41,12 @@ defmodule ZoeyscomputerWeb.Router do
|
||||||
import Phoenix.LiveDashboard.Router
|
import Phoenix.LiveDashboard.Router
|
||||||
|
|
||||||
scope "/dev" do
|
scope "/dev" do
|
||||||
pipe_through :browser
|
pipe_through [:browser, :require_authenticated_admin_user]
|
||||||
|
|
||||||
|
live_dashboard "/dashboard",
|
||||||
|
metrics: ZoeyscomputerWeb.Telemetry,
|
||||||
|
ecto_repos: Zoeyscomputer.Repo
|
||||||
|
|
||||||
live_dashboard "/dashboard", metrics: ZoeyscomputerWeb.Telemetry
|
|
||||||
forward "/mailbox", Plug.Swoosh.MailboxPreview
|
forward "/mailbox", Plug.Swoosh.MailboxPreview
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -82,6 +88,7 @@ defmodule ZoeyscomputerWeb.Router do
|
||||||
on_mount: [{ZoeyscomputerWeb.UserAuth, :mount_current_user}] do
|
on_mount: [{ZoeyscomputerWeb.UserAuth, :mount_current_user}] do
|
||||||
live "/users/confirm/:token", UserConfirmationLive, :edit
|
live "/users/confirm/:token", UserConfirmationLive, :edit
|
||||||
live "/users/confirm", UserConfirmationInstructionsLive, :new
|
live "/users/confirm", UserConfirmationInstructionsLive, :new
|
||||||
|
live "/", HomeLive, :index
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -195,6 +195,31 @@ defmodule ZoeyscomputerWeb.UserAuth do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Used for routes that require an admin user.
|
||||||
|
|
||||||
|
Currently used for the `/dev/` routes.
|
||||||
|
"""
|
||||||
|
def require_authenticated_admin_user(conn, _opts) do
|
||||||
|
if conn.assigns[:current_user] do
|
||||||
|
if conn.assigns[:current_user].admin do
|
||||||
|
conn
|
||||||
|
else
|
||||||
|
conn
|
||||||
|
|> put_flash(:error, "You must be an admin to access this page.")
|
||||||
|
|> maybe_store_return_to()
|
||||||
|
|> redirect(to: ~p"/")
|
||||||
|
|> halt()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
conn
|
||||||
|
|> put_flash(:error, "You must log in to access this page.")
|
||||||
|
|> maybe_store_return_to()
|
||||||
|
|> redirect(to: ~p"/users/log_in")
|
||||||
|
|> halt()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Used for routes that require the user to be authenticated.
|
Used for routes that require the user to be authenticated.
|
||||||
|
|
||||||
|
|
|
||||||
3
mix.exs
3
mix.exs
|
|
@ -19,7 +19,7 @@ defmodule Zoeyscomputer.MixProject do
|
||||||
def application do
|
def application do
|
||||||
[
|
[
|
||||||
mod: {Zoeyscomputer.Application, []},
|
mod: {Zoeyscomputer.Application, []},
|
||||||
extra_applications: [:logger, :runtime_tools]
|
extra_applications: [:logger, :runtime_tools, :os_mon]
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -36,6 +36,7 @@ defmodule Zoeyscomputer.MixProject do
|
||||||
{:phoenix, "~> 1.7.14"},
|
{:phoenix, "~> 1.7.14"},
|
||||||
{:phoenix_ecto, "~> 4.5"},
|
{:phoenix_ecto, "~> 4.5"},
|
||||||
{:ecto_sql, "~> 3.10"},
|
{:ecto_sql, "~> 3.10"},
|
||||||
|
{:ecto_psql_extras, "~> 0.6"},
|
||||||
{:postgrex, ">= 0.0.0"},
|
{:postgrex, ">= 0.0.0"},
|
||||||
{:phoenix_html, "~> 4.1"},
|
{:phoenix_html, "~> 4.1"},
|
||||||
{:phoenix_live_reload, "~> 1.2", only: :dev},
|
{:phoenix_live_reload, "~> 1.2", only: :dev},
|
||||||
|
|
|
||||||
2
mix.lock
2
mix.lock
|
|
@ -8,6 +8,7 @@
|
||||||
"deps_nix": {:hex, :deps_nix, "0.6.0", "4f642620696f389a8826bcbb6cda9a043f82c84a36acfe594842d7a722fea990", [:mix], [], "hexpm", "61cd72c63b7ce58ae86a012b8c96a65018b6201ba9c0a6b55249f246c7252c80"},
|
"deps_nix": {:hex, :deps_nix, "0.6.0", "4f642620696f389a8826bcbb6cda9a043f82c84a36acfe594842d7a722fea990", [:mix], [], "hexpm", "61cd72c63b7ce58ae86a012b8c96a65018b6201ba9c0a6b55249f246c7252c80"},
|
||||||
"dns_cluster": {:hex, :dns_cluster, "0.1.3", "0bc20a2c88ed6cc494f2964075c359f8c2d00e1bf25518a6a6c7fd277c9b0c66", [:mix], [], "hexpm", "46cb7c4a1b3e52c7ad4cbe33ca5079fbde4840dedeafca2baf77996c2da1bc33"},
|
"dns_cluster": {:hex, :dns_cluster, "0.1.3", "0bc20a2c88ed6cc494f2964075c359f8c2d00e1bf25518a6a6c7fd277c9b0c66", [:mix], [], "hexpm", "46cb7c4a1b3e52c7ad4cbe33ca5079fbde4840dedeafca2baf77996c2da1bc33"},
|
||||||
"ecto": {:hex, :ecto, "3.12.4", "267c94d9f2969e6acc4dd5e3e3af5b05cdae89a4d549925f3008b2b7eb0b93c3", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ef04e4101688a67d061e1b10d7bc1fbf00d1d13c17eef08b71d070ff9188f747"},
|
"ecto": {:hex, :ecto, "3.12.4", "267c94d9f2969e6acc4dd5e3e3af5b05cdae89a4d549925f3008b2b7eb0b93c3", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ef04e4101688a67d061e1b10d7bc1fbf00d1d13c17eef08b71d070ff9188f747"},
|
||||||
|
"ecto_psql_extras": {:hex, :ecto_psql_extras, "0.8.2", "79350a53246ac5ec27326d208496aebceb77fa82a91744f66a9154560f0759d3", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "> 0.16.0 and < 0.20.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1 or ~> 4.0.0", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "6149c1c4a5ba6602a76cb09ee7a269eb60dab9694a1dbbb797f032555212de75"},
|
||||||
"ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"},
|
"ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"},
|
||||||
"elixir_make": {:hex, :elixir_make, "0.8.4", "4960a03ce79081dee8fe119d80ad372c4e7badb84c493cc75983f9d3bc8bde0f", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "6e7f1d619b5f61dfabd0a20aa268e575572b542ac31723293a4c1a567d5ef040"},
|
"elixir_make": {:hex, :elixir_make, "0.8.4", "4960a03ce79081dee8fe119d80ad372c4e7badb84c493cc75983f9d3bc8bde0f", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "6e7f1d619b5f61dfabd0a20aa268e575572b542ac31723293a4c1a567d5ef040"},
|
||||||
"esbuild": {:hex, :esbuild, "0.8.2", "5f379dfa383ef482b738e7771daf238b2d1cfb0222bef9d3b20d4c8f06c7a7ac", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "558a8a08ed78eb820efbfda1de196569d8bfa9b51e8371a1934fbb31345feda7"},
|
"esbuild": {:hex, :esbuild, "0.8.2", "5f379dfa383ef482b738e7771daf238b2d1cfb0222bef9d3b20d4c8f06c7a7ac", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "558a8a08ed78eb820efbfda1de196569d8bfa9b51e8371a1934fbb31345feda7"},
|
||||||
|
|
@ -35,6 +36,7 @@
|
||||||
"plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
|
"plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
|
||||||
"postgrex": {:hex, :postgrex, "0.19.1", "73b498508b69aded53907fe48a1fee811be34cc720e69ef4ccd568c8715495ea", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "8bac7885a18f381e091ec6caf41bda7bb8c77912bb0e9285212829afe5d8a8f8"},
|
"postgrex": {:hex, :postgrex, "0.19.1", "73b498508b69aded53907fe48a1fee811be34cc720e69ef4ccd568c8715495ea", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "8bac7885a18f381e091ec6caf41bda7bb8c77912bb0e9285212829afe5d8a8f8"},
|
||||||
"swoosh": {:hex, :swoosh, "1.17.2", "73611f08fc7cb9fa15f4909db36eeb12b70727d5c8b6a7fa0d4a31c6575db29e", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "de914359f0ddc134dc0d7735e28922d49d0503f31e4bd66b44e26039c2226d39"},
|
"swoosh": {:hex, :swoosh, "1.17.2", "73611f08fc7cb9fa15f4909db36eeb12b70727d5c8b6a7fa0d4a31c6575db29e", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "de914359f0ddc134dc0d7735e28922d49d0503f31e4bd66b44e26039c2226d39"},
|
||||||
|
"table_rex": {:hex, :table_rex, "4.0.0", "3c613a68ebdc6d4d1e731bc973c233500974ec3993c99fcdabb210407b90959b", [:mix], [], "hexpm", "c35c4d5612ca49ebb0344ea10387da4d2afe278387d4019e4d8111e815df8f55"},
|
||||||
"tailwind": {:hex, :tailwind, "0.2.4", "5706ec47182d4e7045901302bf3a333e80f3d1af65c442ba9a9eed152fb26c2e", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "c6e4a82b8727bab593700c998a4d98cf3d8025678bfde059aed71d0000c3e463"},
|
"tailwind": {:hex, :tailwind, "0.2.4", "5706ec47182d4e7045901302bf3a333e80f3d1af65c442ba9a9eed152fb26c2e", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "c6e4a82b8727bab593700c998a4d98cf3d8025678bfde059aed71d0000c3e463"},
|
||||||
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
|
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
|
||||||
"telemetry_metrics": {:hex, :telemetry_metrics, "1.0.0", "29f5f84991ca98b8eb02fc208b2e6de7c95f8bb2294ef244a176675adc7775df", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f23713b3847286a534e005126d4c959ebcca68ae9582118ce436b521d1d47d5d"},
|
"telemetry_metrics": {:hex, :telemetry_metrics, "1.0.0", "29f5f84991ca98b8eb02fc208b2e6de7c95f8bb2294ef244a176675adc7775df", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f23713b3847286a534e005126d4c959ebcca68ae9582118ce436b521d1d47d5d"},
|
||||||
|
|
|
||||||
1624
package-lock.json
generated
Normal file
1624
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
5
package.json
Normal file
5
package.json
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"devDependencies": {
|
||||||
|
"@catppuccin/tailwindcss": "^0.1.6"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
defmodule Zoeyscomputer.Repo.Migrations.AddUserAdminField do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
alter table(:users) do
|
||||||
|
add :admin, :boolean
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Add table
Add a link
Reference in a new issue