diff --git a/.gitignore b/.gitignore
index 4a7f73a..5bdfc11 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,5 @@ logs
.env
.env.*
!.env.example
+
+.vscode
\ No newline at end of file
diff --git a/app/components/AppSidebar.vue b/app/components/AppSidebar.vue
index 5e9617d..6a29023 100644
--- a/app/components/AppSidebar.vue
+++ b/app/components/AppSidebar.vue
@@ -26,6 +26,26 @@ router.afterEach(() => {
});
const nav = useState("navigation");
+
+const auth = useUserSession();
+
+const authOptions = [
+ {
+ route: "/auth/yandex",
+ label: "Yandex",
+ icon: "tabler:brand-yandex",
+ },
+ {
+ route: "/auth/google",
+ label: "Google",
+ icon: "tabler:brand-google",
+ },
+ {
+ route: "/auth/github",
+ label: "GitHub",
+ icon: "tabler:brand-github",
+ },
+];
@@ -146,6 +166,51 @@ const nav = useState("navigation");
+
+
+
+
Авторизация
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/components/ui/avatar/Avatar.vue b/app/components/ui/avatar/Avatar.vue
new file mode 100644
index 0000000..bb7e669
--- /dev/null
+++ b/app/components/ui/avatar/Avatar.vue
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/avatar/AvatarFallback.vue b/app/components/ui/avatar/AvatarFallback.vue
new file mode 100644
index 0000000..16b588a
--- /dev/null
+++ b/app/components/ui/avatar/AvatarFallback.vue
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/avatar/AvatarImage.vue b/app/components/ui/avatar/AvatarImage.vue
new file mode 100644
index 0000000..24a8166
--- /dev/null
+++ b/app/components/ui/avatar/AvatarImage.vue
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/avatar/index.ts b/app/components/ui/avatar/index.ts
new file mode 100644
index 0000000..cf0e003
--- /dev/null
+++ b/app/components/ui/avatar/index.ts
@@ -0,0 +1,3 @@
+export { default as Avatar } from "./Avatar.vue"
+export { default as AvatarFallback } from "./AvatarFallback.vue"
+export { default as AvatarImage } from "./AvatarImage.vue"
diff --git a/app/components/ui/button-group/ButtonGroup.vue b/app/components/ui/button-group/ButtonGroup.vue
new file mode 100644
index 0000000..9dbef6a
--- /dev/null
+++ b/app/components/ui/button-group/ButtonGroup.vue
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/button-group/ButtonGroupSeparator.vue b/app/components/ui/button-group/ButtonGroupSeparator.vue
new file mode 100644
index 0000000..e069dd5
--- /dev/null
+++ b/app/components/ui/button-group/ButtonGroupSeparator.vue
@@ -0,0 +1,24 @@
+
+
+
+
+
diff --git a/app/components/ui/button-group/ButtonGroupText.vue b/app/components/ui/button-group/ButtonGroupText.vue
new file mode 100644
index 0000000..c436843
--- /dev/null
+++ b/app/components/ui/button-group/ButtonGroupText.vue
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/button-group/index.ts b/app/components/ui/button-group/index.ts
new file mode 100644
index 0000000..474566f
--- /dev/null
+++ b/app/components/ui/button-group/index.ts
@@ -0,0 +1,25 @@
+import type { VariantProps } from "class-variance-authority"
+import { cva } from "class-variance-authority"
+
+export { default as ButtonGroup } from "./ButtonGroup.vue"
+export { default as ButtonGroupSeparator } from "./ButtonGroupSeparator.vue"
+export { default as ButtonGroupText } from "./ButtonGroupText.vue"
+
+export const buttonGroupVariants = cva(
+ "flex w-fit items-stretch [&>*]:focus-visible:z-10 [&>*]:focus-visible:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md has-[>[data-slot=button-group]]:gap-2",
+ {
+ variants: {
+ orientation: {
+ horizontal:
+ "[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none",
+ vertical:
+ "flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none",
+ },
+ },
+ defaultVariants: {
+ orientation: "horizontal",
+ },
+ },
+)
+
+export type ButtonGroupVariants = VariantProps
diff --git a/app/components/ui/item/Item.vue b/app/components/ui/item/Item.vue
new file mode 100644
index 0000000..0cec259
--- /dev/null
+++ b/app/components/ui/item/Item.vue
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/item/ItemActions.vue b/app/components/ui/item/ItemActions.vue
new file mode 100644
index 0000000..b525712
--- /dev/null
+++ b/app/components/ui/item/ItemActions.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/item/ItemContent.vue b/app/components/ui/item/ItemContent.vue
new file mode 100644
index 0000000..212d64f
--- /dev/null
+++ b/app/components/ui/item/ItemContent.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/item/ItemDescription.vue b/app/components/ui/item/ItemDescription.vue
new file mode 100644
index 0000000..278e6b8
--- /dev/null
+++ b/app/components/ui/item/ItemDescription.vue
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/item/ItemFooter.vue b/app/components/ui/item/ItemFooter.vue
new file mode 100644
index 0000000..fd57ccf
--- /dev/null
+++ b/app/components/ui/item/ItemFooter.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/item/ItemGroup.vue b/app/components/ui/item/ItemGroup.vue
new file mode 100644
index 0000000..121a0c2
--- /dev/null
+++ b/app/components/ui/item/ItemGroup.vue
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/item/ItemHeader.vue b/app/components/ui/item/ItemHeader.vue
new file mode 100644
index 0000000..c9fb17a
--- /dev/null
+++ b/app/components/ui/item/ItemHeader.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/item/ItemMedia.vue b/app/components/ui/item/ItemMedia.vue
new file mode 100644
index 0000000..db229f3
--- /dev/null
+++ b/app/components/ui/item/ItemMedia.vue
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/item/ItemSeparator.vue b/app/components/ui/item/ItemSeparator.vue
new file mode 100644
index 0000000..21065d2
--- /dev/null
+++ b/app/components/ui/item/ItemSeparator.vue
@@ -0,0 +1,18 @@
+
+
+
+
+
diff --git a/app/components/ui/item/ItemTitle.vue b/app/components/ui/item/ItemTitle.vue
new file mode 100644
index 0000000..81c0827
--- /dev/null
+++ b/app/components/ui/item/ItemTitle.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/item/index.ts b/app/components/ui/item/index.ts
new file mode 100644
index 0000000..948421d
--- /dev/null
+++ b/app/components/ui/item/index.ts
@@ -0,0 +1,54 @@
+import type { VariantProps } from "class-variance-authority"
+import { cva } from "class-variance-authority"
+
+export { default as Item } from "./Item.vue"
+export { default as ItemActions } from "./ItemActions.vue"
+export { default as ItemContent } from "./ItemContent.vue"
+export { default as ItemDescription } from "./ItemDescription.vue"
+export { default as ItemFooter } from "./ItemFooter.vue"
+export { default as ItemGroup } from "./ItemGroup.vue"
+export { default as ItemHeader } from "./ItemHeader.vue"
+export { default as ItemMedia } from "./ItemMedia.vue"
+export { default as ItemSeparator } from "./ItemSeparator.vue"
+export { default as ItemTitle } from "./ItemTitle.vue"
+
+export const itemVariants = cva(
+ "group/item flex items-center border border-transparent text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
+ {
+ variants: {
+ variant: {
+ default: "bg-transparent",
+ outline: "border-border",
+ muted: "bg-muted/50",
+ },
+ size: {
+ default: "p-4 gap-4 ",
+ sm: "py-3 px-4 gap-2.5",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ },
+)
+
+export const itemMediaVariants = cva(
+ "flex shrink-0 items-center justify-center gap-2 group-has-[[data-slot=item-description]]/item:self-start [&_svg]:pointer-events-none group-has-[[data-slot=item-description]]/item:translate-y-0.5",
+ {
+ variants: {
+ variant: {
+ default: "bg-transparent",
+ icon: "size-8 border rounded-sm bg-muted [&_svg:not([class*='size-'])]:size-4",
+ image:
+ "size-10 rounded-sm overflow-hidden [&_img]:size-full [&_img]:object-cover",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ },
+)
+
+export type ItemVariants = VariantProps
+export type ItemMediaVariants = VariantProps
diff --git a/app/components/ui/popover/Popover.vue b/app/components/ui/popover/Popover.vue
new file mode 100644
index 0000000..4efdb98
--- /dev/null
+++ b/app/components/ui/popover/Popover.vue
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/popover/PopoverAnchor.vue b/app/components/ui/popover/PopoverAnchor.vue
new file mode 100644
index 0000000..49e01db
--- /dev/null
+++ b/app/components/ui/popover/PopoverAnchor.vue
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/popover/PopoverContent.vue b/app/components/ui/popover/PopoverContent.vue
new file mode 100644
index 0000000..cf1e55c
--- /dev/null
+++ b/app/components/ui/popover/PopoverContent.vue
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/components/ui/popover/PopoverTrigger.vue b/app/components/ui/popover/PopoverTrigger.vue
new file mode 100644
index 0000000..fd3b497
--- /dev/null
+++ b/app/components/ui/popover/PopoverTrigger.vue
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
diff --git a/app/components/ui/popover/index.ts b/app/components/ui/popover/index.ts
new file mode 100644
index 0000000..66edf89
--- /dev/null
+++ b/app/components/ui/popover/index.ts
@@ -0,0 +1,4 @@
+export { default as Popover } from "./Popover.vue"
+export { default as PopoverAnchor } from "./PopoverAnchor.vue"
+export { default as PopoverContent } from "./PopoverContent.vue"
+export { default as PopoverTrigger } from "./PopoverTrigger.vue"
diff --git a/app/components/ui/separator/Separator.vue b/app/components/ui/separator/Separator.vue
index 5429089..78d60ec 100644
--- a/app/components/ui/separator/Separator.vue
+++ b/app/components/ui/separator/Separator.vue
@@ -17,7 +17,7 @@ const delegatedProps = reactiveOmit(props, "class")
- Я сделал эту штуку за 10 часов не судите строго
-
=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="],
+ "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
+
"c12": ["c12@3.3.2", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^17.2.3", "exsolve": "^1.0.8", "giget": "^2.0.0", "jiti": "^2.6.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^2.0.0", "pkg-types": "^2.3.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "*" }, "optionalPeers": ["magicast"] }, "sha512-QkikB2X5voO1okL3QsES0N690Sn/K9WokXqUsDQsWy5SnYb+psYQFGA10iy1bZHj3fjISKsI67Q90gruvWWM3A=="],
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
@@ -880,6 +918,8 @@
"caniuse-lite": ["caniuse-lite@1.0.30001754", "", {}, "sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg=="],
+ "case-anything": ["case-anything@3.1.2", "", {}, "sha512-wljhAjDDIv/hM2FzgJnYQg90AWmZMNtESCjTeLH680qTzdo0nErlCxOmgzgX4ZsZAtIvqHyD87ES8QyriXB+BQ=="],
+
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
@@ -1168,6 +1208,8 @@
"events-universal": ["events-universal@1.0.1", "", { "dependencies": { "bare-events": "^2.7.0" } }, "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw=="],
+ "eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="],
+
"execa": ["execa@8.0.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^8.0.1", "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" } }, "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg=="],
"expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="],
@@ -1214,6 +1256,8 @@
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
+ "flattie": ["flattie@1.1.1", "", {}, "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ=="],
+
"follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="],
"fontaine": ["fontaine@0.7.0", "", { "dependencies": { "@capsizecss/unpack": "^3.0.0", "css-tree": "^3.1.0", "magic-regexp": "^0.10.0", "magic-string": "^0.30.21", "pathe": "^2.0.3", "ufo": "^1.6.1", "unplugin": "^2.3.10" } }, "sha512-vlaWLyoJrOnCBqycmFo/CA8ZmPzuyJHYmgu261KYKByZ4YLz9sTyHZ4qoHgWSYiDsZXhiLo2XndVMz0WOAyZ8Q=="],
@@ -1436,6 +1480,8 @@
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
+ "jose": ["jose@6.1.2", "", {}, "sha512-MpcPtHLE5EmztuFIqB0vzHAWJPpmN1E6L4oo+kze56LIs3MyXIj9ZHMDxqOvkP38gBR7K1v3jqd4WU2+nrfONQ=="],
+
"js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
@@ -1446,6 +1492,8 @@
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
+ "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
+
"json-schema-to-typescript": ["json-schema-to-typescript@15.0.4", "", { "dependencies": { "@apidevtools/json-schema-ref-parser": "^11.5.5", "@types/json-schema": "^7.0.15", "@types/lodash": "^4.17.7", "is-glob": "^4.0.3", "js-yaml": "^4.1.0", "lodash": "^4.17.21", "minimist": "^1.2.8", "prettier": "^3.2.5", "tinyglobby": "^0.2.9" }, "bin": { "json2ts": "dist/src/cli.js" } }, "sha512-Su9oK8DR4xCmDsLlyvadkXzX6+GGXJpbhwoLtOGArAG61dvbW4YQmSEno2y66ahpIdmLMg6YUf/QHLgiwvkrHQ=="],
"json-schema-to-typescript-lite": ["json-schema-to-typescript-lite@15.0.0", "", { "dependencies": { "@apidevtools/json-schema-ref-parser": "^14.1.1", "@types/json-schema": "^7.0.15" } }, "sha512-5mMORSQm9oTLyjM4mWnyNBi2T042Fhg1/0gCIB6X8U/LVpM2A+Nmj2yEyArqVouDmFThDxpEXcnTgSrjkGJRFA=="],
@@ -1728,6 +1776,8 @@
"nuxt": ["nuxt@4.2.1", "", { "dependencies": { "@dxup/nuxt": "^0.2.1", "@nuxt/cli": "^3.30.0", "@nuxt/devtools": "^3.0.1", "@nuxt/kit": "4.2.1", "@nuxt/nitro-server": "4.2.1", "@nuxt/schema": "4.2.1", "@nuxt/telemetry": "^2.6.6", "@nuxt/vite-builder": "4.2.1", "@unhead/vue": "^2.0.19", "@vue/shared": "^3.5.23", "c12": "^3.3.1", "chokidar": "^4.0.3", "compatx": "^0.2.0", "consola": "^3.4.2", "cookie-es": "^2.0.0", "defu": "^6.1.4", "destr": "^2.0.5", "devalue": "^5.4.2", "errx": "^0.1.0", "escape-string-regexp": "^5.0.0", "exsolve": "^1.0.7", "h3": "^1.15.4", "hookable": "^5.5.3", "ignore": "^7.0.5", "impound": "^1.0.0", "jiti": "^2.6.1", "klona": "^2.0.6", "knitwork": "^1.2.0", "magic-string": "^0.30.21", "mlly": "^1.8.0", "nanotar": "^0.2.0", "nypm": "^0.6.2", "ofetch": "^1.5.1", "ohash": "^2.0.11", "on-change": "^6.0.1", "oxc-minify": "^0.96.0", "oxc-parser": "^0.96.0", "oxc-transform": "^0.96.0", "oxc-walker": "^0.5.2", "pathe": "^2.0.3", "perfect-debounce": "^2.0.0", "pkg-types": "^2.3.0", "radix3": "^1.1.2", "scule": "^1.3.0", "semver": "^7.7.3", "std-env": "^3.10.0", "tinyglobby": "^0.2.15", "ufo": "^1.6.1", "ultrahtml": "^1.6.0", "uncrypto": "^0.1.3", "unctx": "^2.4.1", "unimport": "^5.5.0", "unplugin": "^2.3.10", "unplugin-vue-router": "^0.16.1", "untyped": "^2.0.0", "vue": "^3.5.23", "vue-router": "^4.6.3" }, "peerDependencies": { "@parcel/watcher": "^2.1.0", "@types/node": ">=18.12.0" }, "optionalPeers": ["@parcel/watcher", "@types/node"], "bin": { "nuxi": "bin/nuxt.mjs", "nuxt": "bin/nuxt.mjs" } }, "sha512-OE5ONizgwkKhjTGlUYB3ksE+2q2/I30QIYFl3N1yYz1r2rwhunGA3puUvqkzXwgLQ3AdsNcigPDmyQsqjbSdoQ=="],
+ "nuxt-auth-utils": ["nuxt-auth-utils@0.5.25", "", { "dependencies": { "@adonisjs/hash": "^9.1.1", "@nuxt/kit": "^4.1.2", "defu": "^6.1.4", "h3": "^1.15.4", "hookable": "^5.5.3", "jose": "^6.1.0", "ofetch": "^1.4.1", "openid-client": "^6.8.0", "pathe": "^2.0.3", "scule": "^1.3.0", "uncrypto": "^0.1.3" }, "peerDependencies": { "@atproto/api": "^0.13.15", "@atproto/oauth-client-node": "^0.2.0", "@simplewebauthn/browser": "^11.0.0", "@simplewebauthn/server": "^11.0.0" }, "optionalPeers": ["@atproto/api", "@atproto/oauth-client-node", "@simplewebauthn/browser", "@simplewebauthn/server"] }, "sha512-tL9Y0duW3a133BZxy5917KvZ9iLX900PW47Qr80IPytwFspEzyqD7c1/zWACUVPv7QPTTD3LxT7LOtK4aJnfEw=="],
+
"nuxt-component-meta": ["nuxt-component-meta@0.14.2", "", { "dependencies": { "@nuxt/kit": "^4.2.1", "citty": "^0.1.6", "json-schema-to-zod": "^2.6.1", "mlly": "^1.8.0", "ohash": "^2.0.11", "scule": "^1.3.0", "typescript": "^5.9.3", "ufo": "^1.6.1", "vue-component-meta": "^3.1.3" }, "bin": { "nuxt-component-meta": "bin/nuxt-component-meta.mjs" } }, "sha512-pxEnARUzRmq3zbOm8fJkWLPR8mL82NeP5Pu/iYff4otl6jPJwgC1Cbpz3Z77HlVeYVtzhSpQRsCSo/+296CwYw=="],
"nuxt-link-checker": ["nuxt-link-checker@4.3.6", "", { "dependencies": { "@nuxt/devtools-kit": "^2.6.5", "@nuxt/kit": "^4.1.3", "@vueuse/core": "^13.9.0", "consola": "^3.4.2", "diff": "^8.0.2", "fuse.js": "^7.1.0", "magic-string": "^0.30.19", "nuxt-site-config": "^3.2.9", "ofetch": "^1.4.1", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "radix3": "^1.1.2", "sirv": "^3.0.2", "std-env": "^3.10.0", "ufo": "^1.6.1", "ultrahtml": "^1.6.0", "unstorage": "^1.17.1" } }, "sha512-qL7P5EQpmPKBdHI4L/2euaQveNq+IuoNyDWhxi6G68R4F8Qr//Inqg0bsLFdIre95/bGfnz6fzT7cEnBrUWdiA=="],
@@ -1746,6 +1796,8 @@
"nypm": ["nypm@0.6.2", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.2", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "tinyexec": "^1.0.1" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g=="],
+ "oauth4webapi": ["oauth4webapi@3.8.3", "", {}, "sha512-pQ5BsX3QRTgnt5HxgHwgunIRaDXBdkT23tf8dfzmtTIL2LTpdmxgbpbBm0VgFWAIDlezQvQCTgnVIUmHupXHxw=="],
+
"object-deep-merge": ["object-deep-merge@2.0.0", "", {}, "sha512-3DC3UMpeffLTHiuXSy/UG4NOIYTLlY9u3V82+djSCLYClWobZiS4ivYzpIUWrRY/nfsJ8cWsKyG3QfyLePmhvg=="],
"ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="],
@@ -1768,6 +1820,8 @@
"open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="],
+ "openid-client": ["openid-client@6.8.1", "", { "dependencies": { "jose": "^6.1.0", "oauth4webapi": "^3.8.2" } }, "sha512-VoYT6enBo6Vj2j3Q5Ec0AezS+9YGzQo1f5Xc42lreMGlfP4ljiXPKVDvCADh+XHCV/bqPu/wWSiCVXbJKvrODw=="],
+
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
"oxc-minify": ["oxc-minify@0.96.0", "", { "optionalDependencies": { "@oxc-minify/binding-android-arm64": "0.96.0", "@oxc-minify/binding-darwin-arm64": "0.96.0", "@oxc-minify/binding-darwin-x64": "0.96.0", "@oxc-minify/binding-freebsd-x64": "0.96.0", "@oxc-minify/binding-linux-arm-gnueabihf": "0.96.0", "@oxc-minify/binding-linux-arm-musleabihf": "0.96.0", "@oxc-minify/binding-linux-arm64-gnu": "0.96.0", "@oxc-minify/binding-linux-arm64-musl": "0.96.0", "@oxc-minify/binding-linux-riscv64-gnu": "0.96.0", "@oxc-minify/binding-linux-s390x-gnu": "0.96.0", "@oxc-minify/binding-linux-x64-gnu": "0.96.0", "@oxc-minify/binding-linux-x64-musl": "0.96.0", "@oxc-minify/binding-wasm32-wasi": "0.96.0", "@oxc-minify/binding-win32-arm64-msvc": "0.96.0", "@oxc-minify/binding-win32-x64-msvc": "0.96.0" } }, "sha512-dXeeGrfPJJ4rMdw+NrqiCRtbzVX2ogq//R0Xns08zql2HjV3Zi2SBJ65saqfDaJzd2bcHqvGWH+M44EQCHPAcA=="],
@@ -2024,6 +2078,8 @@
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
+ "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="],
+
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"sass": ["sass@1.93.3", "", { "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" }, "bin": { "sass": "sass.js" } }, "sha512-elOcIZRTM76dvxNAjqYrucTSI0teAF/L2Lv0s6f6b7FOwcwIuA357bIE871580AjHJuSvLIRUosgV+lIWx6Rgg=="],
@@ -2076,6 +2132,8 @@
"scule": ["scule@1.3.0", "", {}, "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g=="],
+ "secure-json-parse": ["secure-json-parse@4.1.0", "", {}, "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA=="],
+
"semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"send": ["send@1.2.0", "", { "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.0", "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.1" } }, "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw=="],
@@ -2240,6 +2298,8 @@
"trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
+ "truncatise": ["truncatise@0.0.8", "", {}, "sha512-cXzueh9pzBCsLzhToB4X4gZCb3KYkrsAcBAX97JnazE74HOl3cpBJYEV7nabHeG/6/WXCU5Yujlde/WPBUwnsg=="],
+
"ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
@@ -2528,6 +2588,10 @@
"@vue/devtools-core/nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="],
+ "@vueuse/nuxt/@vueuse/core": ["@vueuse/core@14.0.0", "", { "dependencies": { "@types/web-bluetooth": "^0.0.21", "@vueuse/metadata": "14.0.0", "@vueuse/shared": "14.0.0" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-d6tKRWkZE8IQElX2aHBxXOMD478fHIYV+Dzm2y9Ag122ICBpNKtGICiXKOhWU3L1kKdttDD9dCMS4bGP3jhCTQ=="],
+
+ "@vueuse/nuxt/@vueuse/metadata": ["@vueuse/metadata@14.0.0", "", {}, "sha512-6yoGqbJcMldVCevkFiHDBTB1V5Hq+G/haPlGIuaFZHpXC0HADB0EN1ryQAAceiW+ryS3niUwvdFbGiqHqBrfVA=="],
+
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"archiver/readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="],
@@ -2710,6 +2774,8 @@
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
+ "@vueuse/nuxt/@vueuse/core/@vueuse/shared": ["@vueuse/shared@14.0.0", "", { "peerDependencies": { "vue": "^3.5.0" } }, "sha512-mTCA0uczBgurRlwVaQHfG0Ja7UdGe4g9mwffiJmvLiTtp1G4AQyIjej6si/k8c8pUwTfVpNufck+23gXptPAkw=="],
+
"archiver-utils/readable-stream/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
"archiver/readable-stream/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
diff --git a/nuxt.config.ts b/nuxt.config.ts
index af8d6eb..19af343 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -54,17 +54,18 @@ export default defineNuxtConfig({
pageTransition: { name: "page", mode: "out-in" },
},
modules: [
- "@nuxt/eslint",
- "@nuxt/content",
- "@nuxt/fonts",
- "@nuxtjs/color-mode",
- "shadcn-nuxt",
- "@vueuse/nuxt",
- "@nuxt/icon",
- "@nuxt/scripts",
- // "nuxt-studio",
- "@nuxtjs/seo",
- "nuxt-posthog",
+ "@nuxt/eslint",
+ "@nuxt/content",
+ "@nuxt/fonts",
+ "@nuxtjs/color-mode",
+ "shadcn-nuxt",
+ "@vueuse/nuxt",
+ "@nuxt/icon",
+ "@nuxt/scripts",
+ // "nuxt-studio",
+ "@nuxtjs/seo",
+ "nuxt-posthog",
+ "nuxt-auth-utils",
],
site: {
name: "Конспекты",
@@ -85,4 +86,4 @@ export default defineNuxtConfig({
colorMode: {
classSuffix: "",
},
-});
+});
\ No newline at end of file
diff --git a/package.json b/package.json
index 3f36460..c9d6b38 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,8 @@
"postinstall": "nuxt prepare"
},
"dependencies": {
+ "@ai-sdk/anthropic": "^2.0.50",
+ "@ai-sdk/openai": "^2.0.74",
"@nuxt/content": "^3.8.2",
"@nuxt/eslint": "1.10.0",
"@nuxt/fonts": "0.12.1",
@@ -18,8 +20,9 @@
"@nuxtjs/seo": "3.2.2",
"@tailwindcss/vite": "^4.1.17",
"@unhead/vue": "^2.0.3",
- "@vueuse/core": "^14.0.0",
+ "@vueuse/core": "^14.1.0",
"@vueuse/nuxt": "14.0.0",
+ "ai": "^5.0.104",
"better-sqlite3": "^12.4.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@@ -29,6 +32,7 @@
"lucide-vue-next": "^0.553.0",
"motion-v": "^1.7.4",
"nuxt": "^4.2.1",
+ "nuxt-auth-utils": "^0.5.25",
"nuxt-posthog": "1.6.3",
"ogl": "^1.0.11",
"rehype-mathjax": "^7.1.0",
diff --git a/server/routes/auth/github.get.ts b/server/routes/auth/github.get.ts
new file mode 100644
index 0000000..a2573cc
--- /dev/null
+++ b/server/routes/auth/github.get.ts
@@ -0,0 +1,20 @@
+export default defineOAuthGitHubEventHandler({
+ config: {
+ emailRequired: true,
+ },
+ async onSuccess(event, { user }) {
+ await setUserSession(event, {
+ user: {
+ githubId: user.id,
+ avatarUrl: user.avatar_url,
+ name: user.name,
+ },
+ });
+ return sendRedirect(event, "/");
+ },
+ // Optional, will return a json error and 401 status code by default
+ onError(event, error) {
+ console.error("GitHub OAuth error:", error);
+ return sendRedirect(event, "/");
+ },
+});
diff --git a/server/routes/auth/google.get.ts b/server/routes/auth/google.get.ts
new file mode 100644
index 0000000..f36fef6
--- /dev/null
+++ b/server/routes/auth/google.get.ts
@@ -0,0 +1,17 @@
+export default defineOAuthGoogleEventHandler({
+ async onSuccess(event, { user }) {
+ await setUserSession(event, {
+ user: {
+ googleId: user.sub,
+ avatarUrl: user.picture,
+ name: `${user.given_name} ${user.family_name ?? ""}`.trim(),
+ },
+ });
+ return sendRedirect(event, "/");
+ },
+ // Optional, will return a json error and 401 status code by default
+ onError(event, error) {
+ console.error("Google OAuth error:", error);
+ return sendRedirect(event, "/");
+ },
+});
diff --git a/server/routes/auth/yandex.get.ts b/server/routes/auth/yandex.get.ts
new file mode 100644
index 0000000..7f2f8f8
--- /dev/null
+++ b/server/routes/auth/yandex.get.ts
@@ -0,0 +1,17 @@
+export default defineOAuthYandexEventHandler({
+ async onSuccess(event, { user }) {
+ await setUserSession(event, {
+ user: {
+ yandexId: user.id,
+ avatarUrl: `https://avatars.yandex.net/get-yapic/${user.default_avatar_id}/islands-200`,
+ name: user.display_name ?? user.real_name,
+ },
+ });
+ return sendRedirect(event, "/");
+ },
+ // Optional, will return a json error and 401 status code by default
+ onError(event, error) {
+ console.error("Yandex OAuth error:", error);
+ return sendRedirect(event, "/");
+ },
+});