Compare commits

...

49 commits

Author SHA1 Message Date
Ivan R. 489c39e16c
feat: draft implementation of oidc protocol 2024-09-17 00:07:00 +05:00
Ivan R. 1eb567cd6d
feat(oauth): login confirmation page 2024-09-10 01:40:05 +05:00
Ivan R. c1a4b839bd
refactor: simplify client id and client secret generation 2024-09-09 21:07:25 +05:00
Ivan R. ba4e90ef51
refactor: use client_id as primary key 2024-09-06 13:12:30 +05:00
Ivan R. d09bcf646e
feat: generate modules for openid apps 2024-09-05 02:27:33 +05:00
Ivan R. 3ebcf2db4d
feat: add styles for table and code tags 2024-08-31 17:16:42 +05:00
Ivan R. 750429fc62
feat: fancy admin panel 2024-08-31 17:06:02 +05:00
Ivan R. a8dbdadb90
fix: reset esbuild config 2024-08-31 17:02:54 +05:00
Ivan R. cdccbb1919
chore: use navigate instead of href for live views 2024-08-31 13:47:03 +05:00
Ivan R. 7235ad0d00
feat: add a warning for unapproved accounts 2024-08-31 13:42:27 +05:00
Ivan R. cc57bddc24
chore: simplify esbuild config, fix hot reload 2024-08-31 13:42:05 +05:00
Ivan R. 80743a50f7
refactor: simplify changeset checks, enable spellcheck 2024-08-29 00:38:29 +05:00
Ivan R. 7aa77b1604
feat: add the ability to approve users 2024-08-28 00:58:20 +05:00
Ivan R. 03256685c0
chore: split css files 2024-08-26 22:10:51 +05:00
Ivan R. 3f042d1d99
feat: show user list in admin panel 2024-08-26 21:05:11 +05:00
Ivan R. 818a7e4a31
refactor: embed all html templates into html modules
For some reason I like it.
2024-08-25 23:58:22 +05:00
Ivan R. 1d52d9c71f
feat: add description for some services 2024-08-25 23:47:58 +05:00
Ivan R. 9e0ed3b640
chore: adjust input/button colors 2024-08-25 23:15:33 +05:00
Ivan R. 40f661ff58
style: run mix format 2024-08-25 23:04:41 +05:00
Ivan R. aba6a76073
refactor(home): create service component, embed html template 2024-08-25 23:04:29 +05:00
Ivan R. f779c5fd82
feat: save user info and approval status 2024-08-16 00:40:09 +05:00
Ivan R. 088baff66a
fix: remove undefined function 2024-08-15 12:35:02 +05:00
Ivan R. e28cdc803c
feat: email delivery 2024-08-15 12:26:43 +05:00
Ivan R. 99ebbe7e84
feat: add support for unix sockets 2024-08-15 01:26:34 +05:00
Ivan R. b2372f4d34
build: add Dockerfile, restore assets.deploy task 2024-08-14 22:28:21 +05:00
Ivan R. 5f73827373
feat: markdown parsing 2024-07-29 20:19:37 +05:00
Ivan R. 107af78925
feat: public note list 2024-07-29 18:07:44 +05:00
Ivan R. 5a00fdf843
feat: admin panel for notes 2024-07-29 16:19:47 +05:00
Ivan R. 45c91eb3bf
feat: admin panel
I just added is_admin field to user schema, /admin scope,
admin page controller and view.

No extra functions were implemented.
2024-07-28 21:52:12 +05:00
Ivan R. 2d81bf20ce
chore: use verified routes in nav bar 2024-07-28 18:03:48 +05:00
Ivan R. 8040c38edf
refactor: store icons as .svg.heex files 2024-07-28 17:52:11 +05:00
Ivan R. a38d13a16a
feat: notes schema 2024-07-27 23:22:09 +05:00
Ivan R. fc799a5c0e
feat: add more information about services 2024-07-23 18:52:18 +05:00
Ivan R. 84681401ec
chore: rename services to blog 2024-07-23 18:51:58 +05:00
Ivan R. e185b154ab
feat: add a list of services 2024-07-23 18:40:52 +05:00
Ivan R. 66bb26380a
chore(users): translate forms, fix colors 2024-07-22 21:12:29 +05:00
Ivan R. eadc784bc3
test: fix failures 2024-07-21 22:22:36 +05:00
Ivan R. 8f34d6a069
test: configure database connection 2024-07-21 22:07:53 +05:00
Ivan R. 3841ee5ed7
style: run mix format 2024-07-21 22:04:18 +05:00
Ivan R. 0a5d70abb6
fix: move account-related links to navbar 2024-07-21 22:03:50 +05:00
Ivan R. 63c28511e3
feat: generate authentication 2024-07-11 01:14:16 +05:00
Ivan R. 63c73d5f08
fix(footer): add more icons, change css class names 2024-07-10 01:20:19 +05:00
Ivan R. 9e8885386a
feat: favicons 2024-07-09 23:19:56 +05:00
Ivan R. 560c2c1c8c
fix(flash): write custom styles, update translations 2024-07-09 23:07:57 +05:00
Ivan R. 1a394277d2
feat: add controllers and views for other pages 2024-07-09 21:21:17 +05:00
Ivan R. c6de8dce34
chore: remove tailwind
I prefer plain css files over tailwind.

I converted some default components,
but it's still a work in progress.
2024-07-06 21:25:42 +05:00
Ivan R. c80f439840
chore: copy layout and main page 2024-06-15 22:04:21 +05:00
Ivan R. 94f33b062b
chore: configure dev database 2024-06-15 15:49:09 +05:00
Ivan R. c8edc05b27
chore: initialize empty phoenix project 2024-05-27 12:55:41 +05:00
259 changed files with 8643 additions and 7990 deletions

View file

@ -1,11 +1,45 @@
LICENSE.md
README.md
node_modules
dist
.github
# This file excludes paths from the Docker build context.
#
# By default, Docker's build context includes all files (and folders) in the
# current directory. Even if a file isn't copied into the container it is still sent to
# the Docker daemon.
#
# There are multiple reasons to exclude files from the build context:
#
# 1. Prevent nested folders from being copied into the container (ex: exclude
# /assets/node_modules when copying /assets)
# 2. Reduce the size of the build context and improve build time (ex. /build, /deps, /doc)
# 3. Avoid sending files containing sensitive information
#
# More information on using .dockerignore is available here:
# https://docs.docker.com/engine/reference/builder/#dockerignore-file
.dockerignore
# Ignore git, but keep git HEAD and refs to access current commit hash if needed:
#
# $ cat .git/HEAD | awk '{print ".git/"$2}' | xargs cat
# d0b8727759e1e0e7aa3d41707d12376e373d5ecc
.git
.editorconfig
.gitignore
convert-images.py
public-src
flake.*
!.git/HEAD
!.git/refs
# Common development/test artifacts
/cover/
/doc/
/test/
/tmp/
.elixir_ls
# Mix artifacts
/_build/
/deps/
*.ez
# Generated on crash by the VM
erl_crash.dump
# Static artifacts - These should be fetched and built inside the Docker image
/assets/node_modules/
/priv/static/assets/
/priv/static/cache_manifest.json

6
.formatter.exs Normal file
View file

@ -0,0 +1,6 @@
[
import_deps: [:ecto, :ecto_sql, :phoenix],
subdirectories: ["priv/*/migrations"],
plugins: [Phoenix.LiveView.HTMLFormatter],
inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}", "priv/*/seeds.exs"]
]

48
.gitignore vendored
View file

@ -1,21 +1,37 @@
# build output
dist/
# The directory Mix will write compiled artifacts to.
/_build/
# generated types
.astro/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# dependencies
node_modules/
# The directory Mix downloads your dependencies sources to.
/deps/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Where 3rd-party dependencies like ExDoc output generated docs.
/doc/
# environment variables
.env
.env.production
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Temporary files, for example, from tests.
/tmp/
# Ignore package tarball (built via "mix hex.build").
comfycamp-*.tar
# Ignore assets that are produced by build tools.
/priv/static/assets/
# Ignore digested assets cache.
/priv/static/cache_manifest.json
# In case you use Node.js/npm, you want to ignore these.
npm-debug.log
/assets/node_modules/
# macOS-specific files
.DS_Store

View file

@ -1,14 +1,97 @@
FROM node:20-alpine AS builder
# Find eligible builder and runner images on Docker Hub. We use Ubuntu/Debian
# instead of Alpine to avoid DNS resolution issues in production.
#
# https://hub.docker.com/r/hexpm/elixir/tags?page=1&name=ubuntu
# https://hub.docker.com/_/ubuntu?tab=tags
#
# This file is based on these images:
#
# - https://hub.docker.com/r/hexpm/elixir/tags - for the build image
# - https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye-20240701-slim - for the release image
# - https://pkgs.org/ - resource for finding needed packages
# - Ex: hexpm/elixir:1.17.2-erlang-27.0-debian-bullseye-20240701-slim
#
ARG ELIXIR_VERSION=1.17.2
ARG OTP_VERSION=27.0
ARG DEBIAN_VERSION=bullseye-20240701-slim
ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}"
FROM ${BUILDER_IMAGE} as builder
# install build dependencies
RUN apt-get update -y && apt-get install -y build-essential git \
&& apt-get clean && rm -f /var/lib/apt/lists/*_*
# prepare build dir
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm i
# install hex + rebar
RUN mix local.hex --force && \
mix local.rebar --force
ADD . .
RUN npm run build
# set build ENV
ENV MIX_ENV="prod"
FROM nginx:alpine
# install mix dependencies
COPY mix.exs mix.lock ./
RUN mix deps.get --only $MIX_ENV
RUN mkdir config
COPY --from=builder /app/dist /usr/share/comfycamp
COPY nginx.conf /etc/nginx/conf.d/default.conf
# copy compile-time config files before we compile dependencies
# to ensure any relevant config change will trigger the dependencies
# to be re-compiled.
COPY config/config.exs config/${MIX_ENV}.exs config/
RUN mix deps.compile
COPY priv priv
COPY lib lib
COPY assets assets
# compile assets
RUN mix assets.deploy
# Compile the release
RUN mix compile
# Changes to config/runtime.exs don't require recompiling the code
COPY config/runtime.exs config/
COPY rel rel
RUN mix release
# start a new build stage so that the final image will only contain
# the compiled release and other runtime necessities
FROM ${RUNNER_IMAGE}
RUN apt-get update -y && \
apt-get install -y libstdc++6 openssl libncurses5 locales ca-certificates \
&& apt-get clean && rm -f /var/lib/apt/lists/*_*
# Set the locale
RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8
WORKDIR "/app"
RUN chown nobody /app
# set runner ENV
ENV MIX_ENV="prod"
# Only copy the final release from the build stage
COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/comfycamp ./
USER nobody
# If using an environment that doesn't automatically reap zombie processes, it is
# advised to add an init process such as tini via `apt-get install`
# above and adding an entrypoint. See https://github.com/krallin/tini for details
# ENTRYPOINT ["/tini", "--"]
CMD ["/app/bin/server"]

View file

@ -1,17 +1,18 @@
# Comfycamp
My personal website.
To start your Phoenix server:
* Run `mix setup` to install and setup dependencies
* Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server`
## Getting started
Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
```bash
npm i
npm run dev
```
Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html).
## Learn more
## Images
The original pictures are in the `public-src` directory.
The `convert-images.py` script converts them into the required formats and resolutions.
* Official website: https://www.phoenixframework.org/
* Guides: https://hexdocs.pm/phoenix/overview.html
* Docs: https://hexdocs.pm/phoenix
* Forum: https://elixirforum.com/c/phoenix-forum
* Source: https://github.com/phoenixframework/phoenix

59
assets/css/admin.css Normal file
View file

@ -0,0 +1,59 @@
.admin-panel {
display: flex;
gap: 32px;
margin-top: 16px;
}
.admin-panel .menu {
padding: 0;
margin: 0;
list-style-type: none;
}
.admin-panel .menu li {
padding: 10px;
background-color: #20232f;
}
.admin-panel .menu li:first-child {
border-radius: 8px 8px 0 0;
}
.admin-panel .menu li:not(:first-child) {
border-top: 1px solid #353544;
}
.admin-panel .menu li:last-child {
border-radius: 0 0 8px 8px;
}
.admin-panel h1,
.admin-panel h3 {
margin-top: 0;
}
.admin-panel .stats {
display: flex;
flex-wrap: wrap;
gap: 16px;
}
.admin-panel .stat {
background-color: #20232f;
padding: 16px;
border-radius: 8px;
display: flex;
flex-direction: column;
gap: 8px;
align-items: center;
width: min-content;
}
.admin-panel .stat .value {
font-size: 48px;
font-weight: bold;
}
.admin-panel .stat .name {
text-align: center;
}

106
assets/css/core/app.css Normal file
View file

@ -0,0 +1,106 @@
@import "./components.css";
@import "./flash.css";
:root {
--bg: #13151a;
--accent: #b283e5;
--input-bg: #28253c;
--input-border: #4c4c6d;
}
html {
font-family: Georgia, serif;
background-color: var(--bg);
color: white;
font-size: 16px;
}
* {
box-sizing: border-box;
}
*::selection {
background-color: var(--accent);
color: var(--bg);
}
a {
color: var(--accent);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
h1 {
margin-top: 36px;
margin-bottom: 24px;
}
h2 {
margin-top: 32px;
}
header,
main,
footer {
justify-content: center;
}
.limiter {
max-width: 800px;
width: 100%;
margin: auto;
}
.icon {
width: 20px;
height: 20px;
}
footer {
margin-top: 32px;
}
.link-list {
margin-top: 16px;
display: flex;
flex-wrap: wrap;
gap: 16px;
}
.link-list a {
display: flex;
align-items: center;
gap: 8px;
}
.navbar {
display: flex;
gap: 16px;
padding-top: 16px;
}
.navbar .space {
flex-grow: 1;
}
table {
border-collapse: collapse;
}
th {
text-align: left;
}
th, td {
border: 1px solid #333;
padding: 5px 8px;
}
code {
background-color: #282735;
padding: 2px 5px;
border-radius: 4px;
}

View file

@ -0,0 +1,349 @@
.label {
display: block;
font-size: 0.875rem;
font-weight: 600;
line-height: 1.5rem;
margin-top: 16px;
}
.error {
display: flex;
margin-top: 0.75rem;
font-size: 0.875rem;
line-height: 1.5rem;
gap: 0.75rem;
}
.list {
margin-top: 3.5rem;
}
.list-description {
margin-top: -1rem;
margin-bottom: -1rem;
border-top-width: 1px;
}
.list-item {
display: flex;
padding-top: 1rem;
padding-bottom: 1rem;
font-size: 0.875rem;
line-height: 1.5rem;
gap: 1rem;
}
@media (min-width: 640px) {
.list-item {
gap: 2rem;
}
}
.list-item-title {
flex: none;
width: 25%;
}
.list-item-description {
}
.modal {
display: none;
position: relative;
z-index: 50;
}
.modal-bg {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
transition-property: opacity;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 300ms;
}
.modal-body {
overflow-y: auto;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
padding: 1rem;
width: 100%;
max-width: 48rem;
}
@media (min-width: 640px) {
.modal-body {
padding: 1.5rem;
}
}
@media (min-width: 1024px) {
.modal-body {
padding-top: 2rem;
padding-bottom: 2rem;
}
}
.modal-close-button-container {
position: absolute;
right: 1.25rem;
top: 1.5rem;
}
.modal-close-button {
padding: 0.75rem;
margin: -0.75rem;
flex: none;
opacity: 0.2;
}
.modal-close-button:hover {
opacity: 0.4;
}
.modal-close-button-icon {
width: 1.25rem;
height: 1.25rem;
}
.modal-container {
display: none;
position: relative;
padding: 3.5rem;
border-radius: 1rem;
box-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
background-color: #ffffff;
transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 300ms;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
.simple-form {
margin-top: 2rem;
}
.simple-form-action {
display: flex;
margin-top: 0.5rem;
gap: 1.5rem;
justify-content: space-between;
align-items: center;
}
.button {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
padding-left: 0.75rem;
padding-right: 0.75rem;
border-radius: 0.5rem;
font-size: 0.875rem;
font-weight: 600;
line-height: 1.5rem;
background-color: var(--input-bg);
border: 2px solid var(--input-border);
color: white;
cursor: pointer;
width: 100%;
}
.input {
display: block;
margin-top: 0.5rem;
border-radius: 0.5rem;
width: 100%;
background-color: var(--input-bg);
border: 2px solid var(--input-border);
padding: 4px 8px;
color: white;
}
.input:focus {
outline: 1px solid var(--input-border);
}
@media (min-width: 640px) {
.input {
font-size: 0.875rem;
line-height: 1.5rem;
}
}
.textarea {
display: block;
margin-top: 0.5rem;
border-radius: 0.5rem;
width: 100%;
background-color: var(--input-bg);
border: 2px solid var(--input-border);
padding: 4px 8px;
color: white;
resize: vertical;
min-height: 50px;
max-height: 330px;
}
.textarea:focus {
outline: 1px solid var(--input-border);
}
@media (min-width: 640px) {
.textarea {
font-size: 0.875rem;
line-height: 1.5rem;
}
}
.select {
display: block;
margin-top: 0.5rem;
border-radius: 0.375rem;
border-width: 1px;
border-color: #D1D5DB;
width: 100%;
background-color: #ffffff;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
}
@media (min-width: 640px) {
.select {
font-size: 0.875rem;
line-height: 1.25rem;
}
}
.checkbox-label {
display: flex;
gap: 1rem;
align-items: center;
font-size: 0.875rem;
line-height: 1.5rem;
}
.checkbox-input {
border-radius: 0.25rem;
}
.show {
transition-property: all;
transition-duration: 300ms;
transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
--transform-translate-y: 1rem;
opacity: 0;
opacity: 1;
}
@media (min-width: 640px) {
.show {
--transform-scale-x: 1;
--transform-scale-y: 1;
--transform-translate-y: 0;
}
}
.hide {
transition-property: all;
transition-duration: 300ms;
transition-timing-function: cubic-bezier(0.4, 0, 1, 1);
--transform-translate-y: 1rem;
opacity: 0;
opacity: 1;
}
@media (min-width: 640px) {
.hide {
--transform-scale-x: .95;
--transform-scale-y: .95;
--transform-translate-y: 0;
}
}
.show-modal {
transition-property: all;
transition-duration: 300ms;
transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
}
.hide-modal {
transition-property: all;
transition-duration: 200ms;
transition-timing-function: cubic-bezier(0.4, 0, 1, 1);
opacity: 1;
}
.back-nav-link {
display: flex;
gap: 8px;
}
.back-nav-link svg {
flex-shrink: 0;
width: 16px;
}
.table-container {
overflow-y: auto;
padding-left: 1rem;
padding-right: 1rem;
}
@media (min-width: 640px) {
.table-container {
overflow: visible;
padding-left: 0;
padding-right: 0;
}
}
.table {
margin-top: 2.75rem;
width: 40rem;
}
@media (min-width: 640px) {
.table {
width: 100%;
}
}
.thead {
font-size: 0.875rem;
line-height: 1.25rem;
line-height: 1.5rem;
text-align: left;
}
.th {
padding: 0;
padding-bottom: 1rem;
padding-right: 1.5rem;
font-weight: 400;
}
.th-actions {
position: relative;
padding: 0;
padding-bottom: 1rem;
}
.tbody {
position: relative;
border-top-width: 1px;
border-top-width: 1px;
font-size: 0.875rem;
line-height: 1.25rem;
line-height: 1.5rem;
}
.td {
position: relative;
padding: 0;
}

51
assets/css/core/flash.css Normal file
View file

@ -0,0 +1,51 @@
.flash {
position: fixed;
top: 0.5rem;
right: 0.5rem;
z-index: 50;
padding: 16px;
border-radius: 0.3rem;
box-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
width: 20rem;
}
.flash-info {
background-color: #15803d;
}
.flash-error {
background-color: #b91c1c;
}
@media (min-width: 640px) {
.flash {
width: 24rem;
}
}
.flash-title {
margin: 0;
display: flex;
gap: 8px;
align-items: center;
font-weight: 600;
}
.flash-body {
margin-bottom: 0;
text-indent: 28px;
}
.flash-close-button {
padding: 0;
position: absolute;
width: 28px;
height: 28px;
top: 0.5rem;
right: 0.5rem;
cursor: pointer;
}
.flash-close-button-icon {
opacity: 0.6;
}

22
assets/css/home.css Normal file
View file

@ -0,0 +1,22 @@
.home .service {
margin-top: 28px;
}
.home .service h3 {
margin-bottom: 8px;
}
.home .service .link {
color: var(--accent);
}
.home .service svg {
width: 16px;
height: 16px;
}
.home .warning {
background-color: #aa4526;
padding: 8px;
border-radius: 8px;
}

47
assets/js/app.js Normal file
View file

@ -0,0 +1,47 @@
// If you want to use Phoenix channels, run `mix help phx.gen.channel`
// to get started and then uncomment the line below.
// import "./user_socket.js"
// You can include dependencies in two ways.
//
// The simplest option is to put them in assets/vendor and
// import them using relative paths:
//
// import "../vendor/some-package.js"
//
// Alternatively, you can `npm install some-package --prefix assets` and import
// them using a path starting with the package name:
//
// import "some-package"
//
// Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
import "phoenix_html"
// Establish Phoenix Socket and LiveView configuration.
import {Socket} from "phoenix"
import {LiveSocket} from "phoenix_live_view"
import topbar from "../vendor/topbar"
import "../css/core/app.css"
import "../css/admin.css"
import "../css/home.css"
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, {
longPollFallbackMs: 2500,
params: {_csrf_token: csrfToken}
})
// Show progress bar on live navigation and form submits
topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"})
window.addEventListener("phx:page-loading-start", _info => topbar.show(300))
window.addEventListener("phx:page-loading-stop", _info => topbar.hide())
// connect if there are any LiveViews on the page
liveSocket.connect()
// expose liveSocket on window for web console debug logs and latency simulation:
// >> liveSocket.enableDebug()
// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
// >> liveSocket.disableLatencySim()
window.liveSocket = liveSocket

165
assets/vendor/topbar.js vendored Normal file
View file

@ -0,0 +1,165 @@
/**
* @license MIT
* topbar 2.0.0, 2023-02-04
* https://buunguyen.github.io/topbar
* Copyright (c) 2021 Buu Nguyen
*/
(function (window, document) {
"use strict";
// https://gist.github.com/paulirish/1579671
(function () {
var lastTime = 0;
var vendors = ["ms", "moz", "webkit", "o"];
for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame =
window[vendors[x] + "RequestAnimationFrame"];
window.cancelAnimationFrame =
window[vendors[x] + "CancelAnimationFrame"] ||
window[vendors[x] + "CancelRequestAnimationFrame"];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function (callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function () {
callback(currTime + timeToCall);
}, timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function (id) {
clearTimeout(id);
};
})();
var canvas,
currentProgress,
showing,
progressTimerId = null,
fadeTimerId = null,
delayTimerId = null,
addEvent = function (elem, type, handler) {
if (elem.addEventListener) elem.addEventListener(type, handler, false);
else if (elem.attachEvent) elem.attachEvent("on" + type, handler);
else elem["on" + type] = handler;
},
options = {
autoRun: true,
barThickness: 3,
barColors: {
0: "rgba(26, 188, 156, .9)",
".25": "rgba(52, 152, 219, .9)",
".50": "rgba(241, 196, 15, .9)",
".75": "rgba(230, 126, 34, .9)",
"1.0": "rgba(211, 84, 0, .9)",
},
shadowBlur: 10,
shadowColor: "rgba(0, 0, 0, .6)",
className: null,
},
repaint = function () {
canvas.width = window.innerWidth;
canvas.height = options.barThickness * 5; // need space for shadow
var ctx = canvas.getContext("2d");
ctx.shadowBlur = options.shadowBlur;
ctx.shadowColor = options.shadowColor;
var lineGradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
for (var stop in options.barColors)
lineGradient.addColorStop(stop, options.barColors[stop]);
ctx.lineWidth = options.barThickness;
ctx.beginPath();
ctx.moveTo(0, options.barThickness / 2);
ctx.lineTo(
Math.ceil(currentProgress * canvas.width),
options.barThickness / 2
);
ctx.strokeStyle = lineGradient;
ctx.stroke();
},
createCanvas = function () {
canvas = document.createElement("canvas");
var style = canvas.style;
style.position = "fixed";
style.top = style.left = style.right = style.margin = style.padding = 0;
style.zIndex = 100001;
style.display = "none";
if (options.className) canvas.classList.add(options.className);
document.body.appendChild(canvas);
addEvent(window, "resize", repaint);
},
topbar = {
config: function (opts) {
for (var key in opts)
if (options.hasOwnProperty(key)) options[key] = opts[key];
},
show: function (delay) {
if (showing) return;
if (delay) {
if (delayTimerId) return;
delayTimerId = setTimeout(() => topbar.show(), delay);
} else {
showing = true;
if (fadeTimerId !== null) window.cancelAnimationFrame(fadeTimerId);
if (!canvas) createCanvas();
canvas.style.opacity = 1;
canvas.style.display = "block";
topbar.progress(0);
if (options.autoRun) {
(function loop() {
progressTimerId = window.requestAnimationFrame(loop);
topbar.progress(
"+" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2)
);
})();
}
}
},
progress: function (to) {
if (typeof to === "undefined") return currentProgress;
if (typeof to === "string") {
to =
(to.indexOf("+") >= 0 || to.indexOf("-") >= 0
? currentProgress
: 0) + parseFloat(to);
}
currentProgress = to > 1 ? 1 : to;
repaint();
return currentProgress;
},
hide: function () {
clearTimeout(delayTimerId);
delayTimerId = null;
if (!showing) return;
showing = false;
if (progressTimerId != null) {
window.cancelAnimationFrame(progressTimerId);
progressTimerId = null;
}
(function loop() {
if (topbar.progress("+.1") >= 1) {
canvas.style.opacity -= 0.05;
if (canvas.style.opacity <= 0.05) {
canvas.style.display = "none";
fadeTimerId = null;
return;
}
}
fadeTimerId = window.requestAnimationFrame(loop);
})();
},
};
if (typeof module === "object" && typeof module.exports === "object") {
module.exports = topbar;
} else if (typeof define === "function" && define.amd) {
define(function () {
return topbar;
});
} else {
this.topbar = topbar;
}
}.call(this, window, document));

View file

@ -1,6 +0,0 @@
import { defineConfig } from 'astro/config'
// https://astro.build/config
export default defineConfig({
site: 'https://comfycamp.space'
})

56
config/config.exs Normal file
View file

@ -0,0 +1,56 @@
# This file is responsible for configuring your application
# and its dependencies with the aid of the Config module.
#
# This configuration file is loaded before any dependency and
# is restricted to this project.
# General application configuration
import Config
config :comfycamp,
ecto_repos: [Comfycamp.Repo],
generators: [timestamp_type: :utc_datetime]
config :comfycamp, ComfycampWeb.Gettext, locales: ~w(en ru), default_locale: "ru"
# Configures the endpoint
config :comfycamp, ComfycampWeb.Endpoint,
url: [host: "localhost"],
adapter: Bandit.PhoenixAdapter,
render_errors: [
formats: [html: ComfycampWeb.ErrorHTML, json: ComfycampWeb.ErrorJSON],
layout: false
],
pubsub_server: Comfycamp.PubSub,
live_view: [signing_salt: "LXPLpJT8"]
# Configures the mailer
#
# By default it uses the "Local" adapter which stores the emails
# locally. You can see the emails in your browser, at "/dev/mailbox".
#
# For production it's recommended to configure a different adapter
# at the `config/runtime.exs`.
config :comfycamp, Comfycamp.Mailer, adapter: Swoosh.Adapters.Local
# Configure esbuild (the version is required)
config :esbuild,
version: "0.17.11",
comfycamp: [
args:
~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),
cd: Path.expand("../assets", __DIR__),
env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
]
# Configures Elixir's Logger
config :logger, :console,
format: "$time $metadata[$level] $message\n",
metadata: [:request_id]
# Use Jason for JSON parsing in Phoenix
config :phoenix, :json_library, Jason
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{config_env()}.exs"

84
config/dev.exs Normal file
View file

@ -0,0 +1,84 @@
import Config
# Configure your database
config :comfycamp, Comfycamp.Repo,
username: "phoenix",
password: "simple-password",
hostname: "localhost",
database: "phoenix",
stacktrace: true,
show_sensitive_data_on_connection_error: true,
pool_size: 10
# For development, we disable any cache and enable
# debugging and code reloading.
#
# The watchers configuration can be used to run external
# watchers to your application. For example, we can use it
# to bundle .js and .css sources.
config :comfycamp, ComfycampWeb.Endpoint,
# Binding to loopback ipv4 address prevents access from other machines.
# Change to `ip: {0, 0, 0, 0}` to allow access from other machines.
http: [ip: {127, 0, 0, 1}, port: 4000],
check_origin: false,
code_reloader: true,
debug_errors: true,
secret_key_base: "6TPjZ6GJcs5FerDbAdr2pHRL5JASsi04nah6WbeQfbPmnuHz0lAUu4e60HNBkKVv",
watchers: [
esbuild: {Esbuild, :install_and_run, [:comfycamp, ~w(--sourcemap=inline --watch)]}
]
# ## SSL Support
#
# In order to use HTTPS in development, a self-signed
# certificate can be generated by running the following
# Mix task:
#
# mix phx.gen.cert
#
# Run `mix help phx.gen.cert` for more information.
#
# The `http:` config above can be replaced with:
#
# https: [
# port: 4001,
# cipher_suite: :strong,
# keyfile: "priv/cert/selfsigned_key.pem",
# certfile: "priv/cert/selfsigned.pem"
# ],
#
# If desired, both `http:` and `https:` keys can be
# configured to run both http and https servers on
# different ports.
# Watch static and templates for browser reloading.
config :comfycamp, ComfycampWeb.Endpoint,
live_reload: [
patterns: [
~r"priv/static/(?!uploads/).*(js|css|png|jpeg|jpg|gif|svg)$",
~r"priv/gettext/.*(po)$",
~r"lib/comfycamp_web/(controllers|live|components)/.*(ex|heex)$"
]
]
# Enable dev routes for dashboard and mailbox
config :comfycamp, dev_routes: true
# Do not include metadata nor timestamps in development logs
config :logger, :console, format: "[$level] $message\n"
# Set a higher stacktrace during development. Avoid configuring such
# in production as building large stacktraces may be expensive.
config :phoenix, :stacktrace_depth, 20
# Initialize plugs at runtime for faster development compilation
config :phoenix, :plug_init_mode, :runtime
config :phoenix_live_view,
# Include HEEx debug annotations as HTML comments in rendered markup
debug_heex_annotations: true,
# Enable helpful, but potentially expensive runtime checks
enable_expensive_runtime_checks: true
# Disable swoosh api client as it is only required for production adapters.
config :swoosh, :api_client, false

20
config/prod.exs Normal file
View file

@ -0,0 +1,20 @@
import Config
# Note we also include the path to a cache manifest
# containing the digested version of static files. This
# manifest is generated by the `mix assets.deploy` task,
# which you should run after static files are built and
# before starting your production server.
config :comfycamp, ComfycampWeb.Endpoint, cache_static_manifest: "priv/static/cache_manifest.json"
# Configures Swoosh API Client
config :swoosh, api_client: Swoosh.ApiClient.Finch, finch_name: Comfycamp.Finch
# Disable Swoosh Local Memory Storage
config :swoosh, local: false
# Do not print debug messages in production
config :logger, level: :info
# Runtime production configuration, including reading
# of environment variables, is done on config/runtime.exs.

119
config/runtime.exs Normal file
View file

@ -0,0 +1,119 @@
import Config
# config/runtime.exs is executed for all environments, including
# during releases. It is executed after compilation and before the
# system starts, so it is typically used to load production configuration
# and secrets from environment variables or elsewhere. Do not define
# any compile-time configuration in here, as it won't be applied.
# The block below contains prod specific runtime configuration.
# ## Using releases
#
# If you use `mix release`, you need to explicitly enable the server
# by passing the PHX_SERVER=true when you start it:
#
# PHX_SERVER=true bin/comfycamp start
#
# Alternatively, you can use `mix phx.gen.release` to generate a `bin/server`
# script that automatically sets the env var above.
if System.get_env("PHX_SERVER") do
config :comfycamp, ComfycampWeb.Endpoint, server: true
end
if config_env() == :prod do
database_url =