From ef2a6c41b4ef2aa398b5862ed8b14f4aedb39ce8 Mon Sep 17 00:00:00 2001 From: zack Date: Tue, 22 Oct 2024 16:51:56 -0400 Subject: [PATCH] update --- assets/js/app.js | 39 +- assets/package-lock.json | 503 ++++++++++++++++++ assets/package.json | 11 +- config/config.exs | 16 + deps.nix | 192 ++++++- img.png | Bin 0 -> 34100 bytes lib/zoeyscomputer/api_keys.ex | 132 +++++ lib/zoeyscomputer/api_keys/api_key.ex | 36 ++ lib/zoeyscomputer/images.ex | 125 +++++ lib/zoeyscomputer/images/image.ex | 20 + .../components/core_components.ex | 30 +- .../controllers/changeset_json.ex | 25 + .../controllers/fallback_controller.ex | 24 + .../controllers/image_controller.ex | 103 ++++ .../controllers/image_json.ex | 24 + .../live/api_key_live/form_component.ex | 70 +++ .../live/api_key_live/index.ex | 54 ++ .../live/api_key_live/index.html.heex | 49 ++ .../live/api_key_live/show.ex | 40 ++ .../live/api_key_live/show.html.heex | 47 ++ .../live/image_live/form_component.ex | 82 +++ .../live/image_live/index.ex | 48 ++ .../live/image_live/index.html.heex | 56 ++ lib/zoeyscomputer_web/live/image_live/show.ex | 21 + .../live/image_live/show.html.heex | 30 ++ .../plugs/api_authentication.ex | 39 ++ lib/zoeyscomputer_web/router.ex | 33 +- mix.exs | 5 + mix.lock | 13 + .../20241022022614_create_images.exs | 11 + .../20241022033717_create_api_keys.exs | 16 + .../20241022195345_add_user_image_field.exs | 9 + test/support/fixtures/api_keys_fixtures.ex | 26 + test/support/fixtures/images_fixtures.ex | 20 + test/zoeyscomputer/api_keys_test.exs | 61 +++ test/zoeyscomputer/images_test.exs | 59 ++ .../controllers/image_controller_test.exs | 84 +++ .../live/api_key_live_test.exs | 113 ++++ .../live/image_live_test.exs | 113 ++++ 39 files changed, 2349 insertions(+), 30 deletions(-) create mode 100644 img.png create mode 100644 lib/zoeyscomputer/api_keys.ex create mode 100644 lib/zoeyscomputer/api_keys/api_key.ex create mode 100644 lib/zoeyscomputer/images.ex create mode 100644 lib/zoeyscomputer/images/image.ex create mode 100644 lib/zoeyscomputer_web/controllers/changeset_json.ex create mode 100644 lib/zoeyscomputer_web/controllers/fallback_controller.ex create mode 100644 lib/zoeyscomputer_web/controllers/image_controller.ex create mode 100644 lib/zoeyscomputer_web/controllers/image_json.ex create mode 100644 lib/zoeyscomputer_web/live/api_key_live/form_component.ex create mode 100644 lib/zoeyscomputer_web/live/api_key_live/index.ex create mode 100644 lib/zoeyscomputer_web/live/api_key_live/index.html.heex create mode 100644 lib/zoeyscomputer_web/live/api_key_live/show.ex create mode 100644 lib/zoeyscomputer_web/live/api_key_live/show.html.heex create mode 100644 lib/zoeyscomputer_web/live/image_live/form_component.ex create mode 100644 lib/zoeyscomputer_web/live/image_live/index.ex create mode 100644 lib/zoeyscomputer_web/live/image_live/index.html.heex create mode 100644 lib/zoeyscomputer_web/live/image_live/show.ex create mode 100644 lib/zoeyscomputer_web/live/image_live/show.html.heex create mode 100644 lib/zoeyscomputer_web/plugs/api_authentication.ex create mode 100644 priv/repo/migrations/20241022022614_create_images.exs create mode 100644 priv/repo/migrations/20241022033717_create_api_keys.exs create mode 100644 priv/repo/migrations/20241022195345_add_user_image_field.exs create mode 100644 test/support/fixtures/api_keys_fixtures.ex create mode 100644 test/support/fixtures/images_fixtures.ex create mode 100644 test/zoeyscomputer/api_keys_test.exs create mode 100644 test/zoeyscomputer/images_test.exs create mode 100644 test/zoeyscomputer_web/controllers/image_controller_test.exs create mode 100644 test/zoeyscomputer_web/live/api_key_live_test.exs create mode 100644 test/zoeyscomputer_web/live/image_live_test.exs diff --git a/assets/js/app.js b/assets/js/app.js index d5e278a..aa0af12 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -16,29 +16,42 @@ // // Include phoenix_html to handle method=PUT/DELETE in forms and buttons. -import "phoenix_html" +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 { Socket } from "phoenix"; +import { LiveSocket } from "phoenix_live_view"; +import topbar from "../vendor/topbar"; -let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") +window.addEventListener("phx:copy", (event) => { + let button = event.detail.dispatcher; + let text = event.target.innerText; + + navigator.clipboard.writeText(text).then(() => { + button.innerText = "Copied!"; + setTimeout(() => { + button.innerText = "Copy"; + }, 2000); + }); +}); + +let csrfToken = document + .querySelector("meta[name='csrf-token']") + .getAttribute("content"); let liveSocket = new LiveSocket("/live", Socket, { longPollFallbackMs: 2500, - params: {_csrf_token: csrfToken} -}) + 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()) +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() +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 - +window.liveSocket = liveSocket; diff --git a/assets/package-lock.json b/assets/package-lock.json index 52c356c..ad4288c 100644 --- a/assets/package-lock.json +++ b/assets/package-lock.json @@ -13,7 +13,12 @@ "phoenix": "^1.7.14", "phoenix_html": "^3.3.4", "phoenix_live_view": "^1.0.0-rc.7", + "shiki": "^1.22.0", "tailwindcss": "^3.4.14" + }, + "devDependencies": { + "@types/phoenix": "^1.6.5", + "@types/phoenix_live_view": "^0.18.5" } }, "node_modules/@alloc/quick-lru": { @@ -530,6 +535,57 @@ "node": ">=14" } }, + "node_modules/@shikijs/core": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.22.0.tgz", + "integrity": "sha512-S8sMe4q71TJAW+qG93s5VaiihujRK6rqDFqBnxqvga/3LvqHEnxqBIOPkt//IdXVtHkQWKu4nOQNk0uBGicU7Q==", + "license": "MIT", + "dependencies": { + "@shikijs/engine-javascript": "1.22.0", + "@shikijs/engine-oniguruma": "1.22.0", + "@shikijs/types": "1.22.0", + "@shikijs/vscode-textmate": "^9.3.0", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.3" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.22.0.tgz", + "integrity": "sha512-AeEtF4Gcck2dwBqCFUKYfsCq0s+eEbCEbkUuFou53NZ0sTGnJnJ/05KHQFZxpii5HMXbocV9URYVowOP2wH5kw==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.22.0", + "@shikijs/vscode-textmate": "^9.3.0", + "oniguruma-to-js": "0.4.3" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.22.0.tgz", + "integrity": "sha512-5iBVjhu/DYs1HB0BKsRRFipRrD7rqjxlWTj4F2Pf+nQSPqc3kcyqFFeZXnBMzDf0HdqaFVvhDRAGiYNvyLP+Mw==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.22.0", + "@shikijs/vscode-textmate": "^9.3.0" + } + }, + "node_modules/@shikijs/types": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.22.0.tgz", + "integrity": "sha512-Fw/Nr7FGFhlQqHfxzZY8Cwtwk5E9nKDUgeLjZgt3UuhcM3yJR9xj3ZGNravZZok8XmEZMiYkSMTPlPkULB8nww==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^9.3.0", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-9.3.0.tgz", + "integrity": "sha512-jn7/7ky30idSkd/O5yDBfAnVt+JJpepofP/POZ1iMOxK59cOfqIgg/Dj0eFsjOTMw+4ycJN0uhZH/Eb0bs/EUA==", + "license": "MIT" + }, "node_modules/@tailwindcss/forms": { "version": "0.5.9", "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.9.tgz", @@ -542,6 +598,53 @@ "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20" } }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/phoenix": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.5.tgz", + "integrity": "sha512-xegpDuR+z0UqG9fwHqNoy3rI7JDlvaPh2TY47Fl80oq6g+hXT+c/LEuE43X48clZ6lOfANl5WrPur9fYO1RJ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/phoenix_live_view": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/@types/phoenix_live_view/-/phoenix_live_view-0.18.5.tgz", + "integrity": "sha512-mxj3KVkp+wX+hLFAILTbfIx5Q890TBgs/jxc6nmmVv6bW6Z9qer/5tZtGOcL3IES75QqqOHSTrjwwz0iZBs0lw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/phoenix": "*" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "license": "ISC" + }, "node_modules/ansi-regex": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", @@ -639,6 +742,36 @@ "node": ">= 6" } }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -693,6 +826,16 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -728,6 +871,28 @@ "node": ">=4" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -923,6 +1088,52 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-to-html": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.3.tgz", + "integrity": "sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -1040,6 +1251,27 @@ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "license": "ISC" }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -1049,6 +1281,95 @@ "node": ">= 8" } }, + "node_modules/micromark-util-character": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz", + "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz", + "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz", + "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -1151,6 +1472,18 @@ "node": ">= 6" } }, + "node_modules/oniguruma-to-js": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/oniguruma-to-js/-/oniguruma-to-js-0.4.3.tgz", + "integrity": "sha512-X0jWUcAlxORhOqqBREgPMgnshB7ZGYszBNspP+tS9hPD3l13CdaXcHbgImoHUHlrvGx/7AvFEkTRhAGYh+jzjQ==", + "license": "MIT", + "dependencies": { + "regex": "^4.3.2" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -1396,6 +1729,16 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, + "node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -1437,6 +1780,12 @@ "node": ">=8.10.0" } }, + "node_modules/regex": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/regex/-/regex-4.3.3.tgz", + "integrity": "sha512-r/AadFO7owAq1QJVeZ/nq9jNS1vyZt+6t1p/E59B56Rn2GCya+gr1KSyOzNL/er+r+B7phv5jG2xU2Nz1YkmJg==", + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -1508,6 +1857,20 @@ "node": ">=8" } }, + "node_modules/shiki": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.22.0.tgz", + "integrity": "sha512-/t5LlhNs+UOKQCYBtl5ZsH/Vclz73GIqT2yQsCBygr8L/ppTdmpL4w3kPLoZJbMKVWtoG77Ue1feOjZfDxvMkw==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "1.22.0", + "@shikijs/engine-javascript": "1.22.0", + "@shikijs/engine-oniguruma": "1.22.0", + "@shikijs/types": "1.22.0", + "@shikijs/vscode-textmate": "^9.3.0", + "@types/hast": "^3.0.4" + } + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -1529,6 +1892,16 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -1588,6 +1961,20 @@ "node": ">=8" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-ansi": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", @@ -1729,18 +2116,124 @@ "node": ">=8.0" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "license": "Apache-2.0" }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -1858,6 +2351,16 @@ "engines": { "node": ">= 14" } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/assets/package.json b/assets/package.json index 73a93c5..0cd28ec 100644 --- a/assets/package.json +++ b/assets/package.json @@ -11,9 +11,14 @@ "@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", + "phoenix": "file:../deps/phoenix", + "phoenix_html": "file:../deps/phoenix_html", + "phoenix_live_view": "file:../deps/phoenix_live_view", + "shiki": "^1.22.0", "tailwindcss": "^3.4.14" + }, + "devDependencies": { + "@types/phoenix": "^1.6.5", + "@types/phoenix_live_view": "^0.18.5" } } diff --git a/config/config.exs b/config/config.exs index 17cccc6..6ddac3e 100644 --- a/config/config.exs +++ b/config/config.exs @@ -53,6 +53,22 @@ config :tailwind, cd: Path.expand("../assets", __DIR__) ] +config :ex_aws, + access_key_id: System.get_env("MINIO_ACCESS_KEY", "PvV9r38q0lA8CmT5bqpU"), + secret_access_key: + System.get_env("MINIO_SECRET_KEY", "wrEhRQ4ughUPw06lKxRlo9Bv4ciBa7i7BDJsRP0o"), + json_codec: Jason + +config :ex_aws, :s3, + scheme: "https://", + host: "s3.zoeys.computer", + port: 443, + region: "us-east-1" + +config :nanoid, + size: 21, + alphabet: "_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + # Configures Elixir's Logger config :logger, :console, format: "$time $metadata[$level] $message\n", diff --git a/deps.nix b/deps.nix index 9361993..2490000 100644 --- a/deps.nix +++ b/deps.nix @@ -154,6 +154,21 @@ let }; }; + certifi = + let + version = "2.12.0"; + in + buildRebar3 { + inherit version; + name = "certifi"; + + src = fetchHex { + inherit version; + pkg = "certifi"; + sha256 = "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"; + }; + }; + comeonin = let version = "5.5.0"; @@ -281,7 +296,7 @@ let sha256 = "6e7f1d619b5f61dfabd0a20aa268e575572b542ac31723293a4c1a567d5ef040"; }; - beamDeps = [ castore ]; + beamDeps = [ castore certifi ]; }; esbuild = @@ -301,6 +316,40 @@ let beamDeps = [ castore jason ]; }; + ex_aws = + let + version = "2.5.6"; + in + buildMix { + inherit version; + name = "ex_aws"; + + src = fetchHex { + inherit version; + pkg = "ex_aws"; + sha256 = "c69eec59e31fdd89d0beeb1d97e16518dd1b23ad95b3d5c9f1dcfec23d97f960"; + }; + + beamDeps = [ hackney jason mime sweet_xml telemetry ]; + }; + + ex_aws_s3 = + let + version = "2.5.4"; + in + buildMix { + inherit version; + name = "ex_aws_s3"; + + src = fetchHex { + inherit version; + pkg = "ex_aws_s3"; + sha256 = "c06e7f68b33f7c0acba1361dbd951c79661a28f85aa2e0582990fccca4425355"; + }; + + beamDeps = [ ex_aws sweet_xml ]; + }; + expo = let version = "1.1.0"; @@ -350,6 +399,23 @@ let beamDeps = [ expo ]; }; + hackney = + let + version = "1.20.1"; + in + buildRebar3 { + inherit version; + name = "hackney"; + + src = fetchHex { + inherit version; + pkg = "hackney"; + sha256 = "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"; + }; + + beamDeps = [ certifi idna metrics mimerl parse_trans ssl_verify_fun unicode_util_compat ]; + }; + hpax = let version = "1.0.0"; @@ -365,6 +431,23 @@ let }; }; + idna = + let + version = "6.1.1"; + in + buildRebar3 { + inherit version; + name = "idna"; + + src = fetchHex { + inherit version; + pkg = "idna"; + sha256 = "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"; + }; + + beamDeps = [ unicode_util_compat ]; + }; + jason = let version = "1.4.4"; @@ -382,6 +465,21 @@ let beamDeps = [ decimal ]; }; + metrics = + let + version = "1.0.1"; + in + buildRebar3 { + inherit version; + name = "metrics"; + + src = fetchHex { + inherit version; + pkg = "metrics"; + sha256 = "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"; + }; + }; + mime = let version = "2.0.6"; @@ -397,6 +495,21 @@ let }; }; + mimerl = + let + version = "1.3.0"; + in + buildRebar3 { + inherit version; + name = "mimerl"; + + src = fetchHex { + inherit version; + pkg = "mimerl"; + sha256 = "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"; + }; + }; + mint = let version = "1.6.2"; @@ -414,6 +527,21 @@ let beamDeps = [ castore hpax ]; }; + nanoid = + let + version = "2.1.0"; + in + buildMix { + inherit version; + name = "nanoid"; + + src = fetchHex { + inherit version; + pkg = "nanoid"; + sha256 = "ebc7a342d02d213534a7f93a091d569b9fea7f26fcd3a638dc655060fc1f76ac"; + }; + }; + nimble_options = let version = "1.1.1"; @@ -444,6 +572,21 @@ let }; }; + parse_trans = + let + version = "3.4.1"; + in + buildRebar3 { + inherit version; + name = "parse_trans"; + + src = fetchHex { + inherit version; + pkg = "parse_trans"; + sha256 = "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"; + }; + }; + phoenix = let version = "1.7.14"; @@ -608,6 +751,36 @@ let beamDeps = [ db_connection decimal jason ]; }; + ssl_verify_fun = + let + version = "1.1.7"; + in + buildMix { + inherit version; + name = "ssl_verify_fun"; + + src = fetchHex { + inherit version; + pkg = "ssl_verify_fun"; + sha256 = "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"; + }; + }; + + sweet_xml = + let + version = "0.7.4"; + in + buildMix { + inherit version; + name = "sweet_xml"; + + src = fetchHex { + inherit version; + pkg = "sweet_xml"; + sha256 = "e7c4b0bdbf460c928234951def54fe87edf1a170f6896675443279e2dbeba167"; + }; + }; + swoosh = let version = "1.17.2"; @@ -622,7 +795,7 @@ let sha256 = "de914359f0ddc134dc0d7735e28922d49d0503f31e4bd66b44e26039c2226d39"; }; - beamDeps = [ bandit finch jason mime plug telemetry ]; + beamDeps = [ bandit ex_aws finch hackney jason mime plug telemetry ]; }; table_rex = @@ -723,6 +896,21 @@ let beamDeps = [ telemetry ]; }; + unicode_util_compat = + let + version = "0.7.0"; + in + buildRebar3 { + inherit version; + name = "unicode_util_compat"; + + src = fetchHex { + inherit version; + pkg = "unicode_util_compat"; + sha256 = "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"; + }; + }; + websock = let version = "0.5.3"; diff --git a/img.png b/img.png new file mode 100644 index 0000000000000000000000000000000000000000..ddaf45e694e7d9f506bf66e2ba2b0ce0d347deb3 GIT binary patch literal 34100 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 literal 0 HcmV?d00001 diff --git a/lib/zoeyscomputer/api_keys.ex b/lib/zoeyscomputer/api_keys.ex new file mode 100644 index 0000000..abd9e1f --- /dev/null +++ b/lib/zoeyscomputer/api_keys.ex @@ -0,0 +1,132 @@ +defmodule Zoeyscomputer.ApiKeys do + @moduledoc """ + The ApiKeys context. + """ + require Logger + + import Ecto.Query, warn: false + alias Hex.API.User + alias Zoeyscomputer.ApiKeys.ApiKey + alias Zoeyscomputer.Repo + alias Zoeyscomputer.Users.User + + alias Zoeyscomputer.ApiKeys.ApiKey + + @doc """ + Returns the list of api_keys. + + ## Examples + + iex> list_api_keys() + [%ApiKey{}, ...] + + """ + def list_api_keys do + Repo.all(ApiKey) + end + + @doc """ + Gets a single api_key. + + Raises `Ecto.NoResultsError` if the Api key does not exist. + + ## Examples + + iex> get_api_key!(123) + %ApiKey{} + + iex> get_api_key!(456) + ** (Ecto.NoResultsError) + + """ + def get_api_key!(id), do: Repo.get!(ApiKey, id) + + @doc """ + Gets an API key by its token + """ + def get_api_key_by_token(token) do + Repo.get_by(ApiKey, token: token) + end + + @doc """ + Creates an API key for a user. + """ + def create_api_key(user, attrs \\ %{}) do + # Convert attrs to string keys and add user_id with string key + attrs = Map.put(attrs, "user_id", user.id) + + result = + %ApiKey{} + |> ApiKey.changeset(attrs) + |> Repo.insert() + + result + end + + @doc """ + Updates a api_key. + + ## Examples + + iex> update_api_key(api_key, %{field: new_value}) + {:ok, %ApiKey{}} + + iex> update_api_key(api_key, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_api_key(%ApiKey{} = api_key, attrs) do + api_key + |> ApiKey.changeset(attrs) + |> Repo.update() + end + + @doc """ + List all API Keys for a given user + """ + def list_api_keys(%User{} = user) do + ApiKey + |> where([a], a.user_id == ^user.id) + |> Repo.all() + end + + @doc """ + Deletes a api_key. + + ## Examples + + iex> delete_api_key(api_key) + {:ok, %ApiKey{}} + + iex> delete_api_key(api_key) + {:error, %Ecto.Changeset{}} + + """ + def delete_api_key(%ApiKey{} = api_key) do + Repo.delete(api_key) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking api_key changes. + + ## Examples + + iex> change_api_key(api_key) + %Ecto.Changeset{data: %ApiKey{}} + + """ + def change_api_key(%ApiKey{} = api_key, attrs \\ %{}) do + ApiKey.changeset(api_key, attrs) + end + + @doc """ + Validates an API key token and returns associated user. + Returns nil if token is invalid + """ + def authenticate_api_key(token) when is_binary(token) do + case get_api_key_by_token(token) do + %ApiKey{} = key -> Repo.preload(key, :user).user + nil -> nil + end + end +end diff --git a/lib/zoeyscomputer/api_keys/api_key.ex b/lib/zoeyscomputer/api_keys/api_key.ex new file mode 100644 index 0000000..67e7bef --- /dev/null +++ b/lib/zoeyscomputer/api_keys/api_key.ex @@ -0,0 +1,36 @@ +defmodule Zoeyscomputer.ApiKeys.ApiKey do + use Ecto.Schema + import Ecto.Changeset + alias Zoeyscomputer.Users.User + + schema "api_keys" do + field :name, :string + field :token, :string + belongs_to :user, User + + timestamps(type: :utc_datetime) + end + + @doc false + def changeset(api_key, attrs) do + api_key + # Make sure both fields are in cast + |> cast(attrs, [:name, :user_id]) + |> validate_required([:name, :user_id]) + # This needs to happen before validation + |> put_token() + # Add token to required fields + |> validate_required([:token]) + end + + defp put_token(changeset) do + case changeset do + %Ecto.Changeset{valid?: true} -> + token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false) + put_change(changeset, :token, token) + + _ -> + changeset + end + end +end diff --git a/lib/zoeyscomputer/images.ex b/lib/zoeyscomputer/images.ex new file mode 100644 index 0000000..d54db74 --- /dev/null +++ b/lib/zoeyscomputer/images.ex @@ -0,0 +1,125 @@ +defmodule Zoeyscomputer.Images do + @moduledoc """ + The Images context. + """ + + import Ecto.Query, warn: false + alias Ecto.Repo + alias Zoeyscomputer.Users.User + alias Zoeyscomputer.Repo + + alias Zoeyscomputer.Images.Image + + @doc """ + Returns the list of images. + + ## Examples + + iex> list_images() + [%Image{}, ...] + + """ + def list_images do + Repo.all(Image) + end + + @doc """ + List Images uploaded by user + """ + + def list_images_by_user(%User{} = user) do + Image + |> where([a], a.user_id == ^user.id) + |> Repo.all() + end + + @doc """ + Gets a single image. + + Raises `Ecto.NoResultsError` if the Image does not exist. + + ## Examples + + iex> get_image!(123) + %Image{} + + iex> get_image!(456) + ** (Ecto.NoResultsError) + + """ + def get_image!(id), do: Repo.get!(Image, id) + + def get_image_by!(file) do + Image + |> Repo.get_by!(file: file) + |> Repo.preload(:user) + end + + @doc """ + Creates a image. + + ## Examples + + iex> create_image(%{field: value}) + {:ok, %Image{}} + + iex> create_image(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_image(attrs \\ %{}) do + # attrs = Map.put(attrs, "user_id", user.id) + # attrs = Map.put(attrs, "s3_key", s3_key) + + %Image{} + |> Image.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a image. + + ## Examples + + iex> update_image(image, %{field: new_value}) + {:ok, %Image{}} + + iex> update_image(image, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_image(%Image{} = image, attrs) do + image + |> Image.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a image. + + ## Examples + + iex> delete_image(image) + {:ok, %Image{}} + + iex> delete_image(image) + {:error, %Ecto.Changeset{}} + + """ + def delete_image(%Image{} = image) do + Repo.delete(image) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking image changes. + + ## Examples + + iex> change_image(image) + %Ecto.Changeset{data: %Image{}} + + """ + def change_image(%Image{} = image, attrs \\ %{}) do + Image.changeset(image, attrs) + end +end diff --git a/lib/zoeyscomputer/images/image.ex b/lib/zoeyscomputer/images/image.ex new file mode 100644 index 0000000..7d63867 --- /dev/null +++ b/lib/zoeyscomputer/images/image.ex @@ -0,0 +1,20 @@ +defmodule Zoeyscomputer.Images.Image do + use Ecto.Schema + import Ecto.Changeset + + @derive {Jason.Encoder, only: [:id, :file, :inserted_at, :updated_at]} + + schema "images" do + field(:file, :string) + belongs_to :user, Zoeyscomputer.Users.User + + timestamps(type: :utc_datetime) + end + + @doc false + def changeset(image, attrs) do + image + |> cast(attrs, [:file, :user_id]) + |> validate_required([:file, :user_id]) + end +end diff --git a/lib/zoeyscomputer_web/components/core_components.ex b/lib/zoeyscomputer_web/components/core_components.ex index 4468051..615bc5d 100644 --- a/lib/zoeyscomputer_web/components/core_components.ex +++ b/lib/zoeyscomputer_web/components/core_components.ex @@ -50,7 +50,11 @@ defmodule ZoeyscomputerWeb.CoreComponents do data-cancel={JS.exec(@on_cancel, "phx-remove")} class="relative z-50 hidden" > -