[READ-ONLY] Mirror of https://github.com/danielroe/flowers.
0

Configure Feed

Select the types of activity you want to include in your feed.

feat: add basic implementation

+350 -15
+4
.gitignore
··· 22 22 .env 23 23 .env.* 24 24 !.env.example 25 + dbschema/query-builder 26 + dbschema/interfaces.ts 27 + 28 + dbschema/edgeql-js
+1 -9
README.md
··· 8 8 9 9 ```bash 10 10 # npm 11 - npm install 12 - 13 - # pnpm 14 - pnpm install 15 - 16 - # yarn 17 - yarn install 18 - 19 - # bun 20 11 bun install 12 + curl --proto '=https' --tlsv1.2 -sSf https://sh.edgedb.com | sh 21 13 ``` 22 14 23 15 ## Development Server
-5
app.vue
··· 1 - <template> 2 - <div> 3 - <NuxtWelcome /> 4 - </div> 5 - </template>
bun.lockb

This is a binary file and will not be displayed.

+45
dbschema/default.esdl
··· 1 + using extension auth; 2 + 3 + module default { 4 + scalar type Role extending enum<admin, attendee>; 5 + 6 + global current_user := ( 7 + assert_single(( 8 + select User { id, name } 9 + filter .identity = global ext::auth::ClientTokenIdentity 10 + )) 11 + ); 12 + 13 + type Comment { 14 + required text: str; 15 + required author: User { 16 + default := global current_user; 17 + }; 18 + public: bool { 19 + default := false; 20 + } 21 + created: datetime { 22 + rewrite insert using (datetime_of_statement()); 23 + } 24 + updated: datetime { 25 + rewrite insert using (datetime_of_statement()); 26 + rewrite update using (datetime_of_statement()); 27 + } 28 + 29 + access policy author_has_full_access 30 + allow all 31 + using (.author ?= global current_user); 32 + access policy others_read_only 33 + allow select; 34 + } 35 + 36 + type User { 37 + required identity: ext::auth::Identity; 38 + name: str; 39 + email := .identity.email; 40 + userRole: Role { 41 + default := "attendee"; 42 + }; 43 + multi comments := .<author[is Comment] 44 + } 45 + }
+44
dbschema/migrations/00001.edgeql
··· 1 + CREATE MIGRATION m1hhv3kqr5bu2h353c7chh7xvjbnqk35odn4e4up2sx4aezswk3pxq 2 + ONTO initial 3 + { 4 + CREATE EXTENSION pgcrypto VERSION '1.3'; 5 + CREATE EXTENSION auth VERSION '1.0'; 6 + CREATE SCALAR TYPE default::Role EXTENDING enum<admin, attendee>; 7 + CREATE TYPE default::User { 8 + CREATE REQUIRED LINK identity: ext::auth::Identity; 9 + CREATE PROPERTY email: std::str; 10 + CREATE PROPERTY name: std::str; 11 + CREATE PROPERTY userRole: default::Role { 12 + SET default := 'attendee'; 13 + }; 14 + }; 15 + CREATE TYPE default::Comment { 16 + CREATE REQUIRED LINK author: default::User; 17 + CREATE PROPERTY created: std::datetime { 18 + CREATE REWRITE 19 + INSERT 20 + USING (std::datetime_of_statement()); 21 + }; 22 + CREATE REQUIRED PROPERTY text: std::str; 23 + CREATE PROPERTY updated: std::datetime { 24 + CREATE REWRITE 25 + INSERT 26 + USING (std::datetime_of_statement()); 27 + CREATE REWRITE 28 + UPDATE 29 + USING (std::datetime_of_statement()); 30 + }; 31 + }; 32 + ALTER TYPE default::User { 33 + CREATE MULTI LINK comments := (.<author[IS default::Comment]); 34 + }; 35 + CREATE GLOBAL default::current_user := (std::assert_single((SELECT 36 + default::User { 37 + id, 38 + name, 39 + email 40 + } 41 + FILTER 42 + (.identity = GLOBAL ext::auth::ClientTokenIdentity) 43 + ))); 44 + };
+24
dbschema/migrations/00002.edgeql
··· 1 + CREATE MIGRATION m1t4rrag55bkwomdbstxokkktpascglc54v77neb3v6topfj5rguca 2 + ONTO m1hhv3kqr5bu2h353c7chh7xvjbnqk35odn4e4up2sx4aezswk3pxq 3 + { 4 + ALTER GLOBAL default::current_user USING (std::assert_single((SELECT 5 + default::User { 6 + id, 7 + name 8 + } 9 + FILTER 10 + (.identity = GLOBAL ext::auth::ClientTokenIdentity) 11 + ))); 12 + ALTER TYPE default::Comment { 13 + ALTER LINK author { 14 + SET default := (GLOBAL default::current_user); 15 + }; 16 + CREATE ACCESS POLICY author_has_full_access 17 + ALLOW ALL USING ((.author ?= GLOBAL default::current_user)); 18 + CREATE ACCESS POLICY others_read_only 19 + ALLOW SELECT ; 20 + CREATE PROPERTY public: std::bool { 21 + SET default := false; 22 + }; 23 + }; 24 + };
+2
edgedb.toml
··· 1 + [edgedb] 2 + server-version = "4.5"
+11 -1
nuxt.config.ts
··· 1 1 // https://nuxt.com/docs/api/configuration/nuxt-config 2 2 export default defineNuxtConfig({ 3 - devtools: { enabled: true } 3 + devtools: { enabled: true }, 4 + modules: ['nuxt-edgedb-module', "@nuxt/ui"], 5 + edgeDb: { 6 + auth: true, 7 + // generateInterfaces: false, 8 + // generateQueries: false, 9 + // generateQueryBuilder: false, 10 + // generateQuiet: false, 11 + // installCli: false, 12 + // projectInit: false, 13 + } 4 14 })
+5
package.json
··· 10 10 "postinstall": "nuxt prepare" 11 11 }, 12 12 "dependencies": { 13 + "@edgedb/generate": "^0.4.1", 13 14 "nuxt": "^3.10.1", 14 15 "vue": "^3.4.15", 15 16 "vue-router": "^4.2.5" 17 + }, 18 + "devDependencies": { 19 + "@nuxt/ui": "latest", 20 + "nuxt-edgedb-module": "0.0.37" 16 21 } 17 22 }
+46
pages/auth/login.vue
··· 1 + <template> 2 + <div class="login"> 3 + <EdgeDbAuthEmailLogin 4 + v-slot="{ email, updateEmail, password, updatePassword, submit, loading }" 5 + redirect-to="/" 6 + > 7 + <UCard> 8 + <template #header> 9 + <h2>Login</h2> 10 + </template> 11 + 12 + <div class="flex flex-col gap-4"> 13 + <UFormGroup label="Email"> 14 + <UInput 15 + type="email" 16 + :value="email" 17 + placeholder="your@email.com" 18 + @change="(e) => updateEmail(e.target.value)" 19 + /> 20 + </UFormGroup> 21 + <UFormGroup label="Password"> 22 + <UInput 23 + type="password" 24 + :value="password" 25 + placeholder="password" 26 + @change="(e) => updatePassword(e.target.value)" 27 + /> 28 + </UFormGroup> 29 + </div> 30 + 31 + <template #footer> 32 + <div class="flex items-center gap-2"> 33 + <UButton 34 + type="button" 35 + :loading="loading" 36 + @click="(e) => submit()" 37 + > 38 + Login 39 + </UButton> 40 + 41 + </div> 42 + </template> 43 + </UCard> 44 + </EdgeDbAuthEmailLogin> 45 + </div> 46 + </template>
+44
pages/auth/signup.vue
··· 1 + <template> 2 + <div class="login"> 3 + <EdgeDbAuthEmailSignup 4 + v-slot="{ email, updateEmail, password, updatePassword, submit, loading }" 5 + redirect-to="/" 6 + > 7 + <UCard> 8 + <template #header> 9 + <h2>Signup</h2> 10 + </template> 11 + <div class="flex flex-col gap-4"> 12 + <UFormGroup label="Email"> 13 + <UInput 14 + type="email" 15 + :value="email" 16 + placeholder="your@email.com" 17 + @change="(e) => updateEmail(e.target.value)" 18 + /> 19 + </UFormGroup> 20 + <UFormGroup label="Password"> 21 + <UInput 22 + type="password" 23 + :value="password" 24 + placeholder="password" 25 + @change="(e) => updatePassword(e.target.value)" 26 + /> 27 + </UFormGroup> 28 + </div> 29 + 30 + <template #footer> 31 + <div class="flex items-center gap-2"> 32 + <UButton 33 + type="button" 34 + :loading="loading" 35 + @click="(e) => submit()" 36 + > 37 + Signup 38 + </UButton> 39 + </div> 40 + </template> 41 + </UCard> 42 + </EdgeDbAuthEmailSignup> 43 + </div> 44 + </template>
+64
pages/index.vue
··· 1 + <template> 2 + <UContainer> 3 + <!-- See all the comments --> 4 + <UTable :rows="data" :columns="[{ key: 'text', label: 'Comment' }]"> 5 + <template #text-data="{ row }"> 6 + <span class="flex flex-row items-center gap-2"> 7 + <span class="rounded-full h-2 w-2" :class="{ 'bg-green-400': row.isCurrent }" title="My comment"> 8 + <span v-if="row.isCurrent" class="sr-only">My comment</span> 9 + </span> 10 + <span class="mr-auto"> 11 + {{ row.text }} 12 + </span> 13 + <UButton v-if="row.isCurrent" @click="deleteComment(row.id)" icon="i-heroicons-trash" size="xs" color="red" variant="solid" :trailing="false" /> 14 + </span> 15 + </template> 16 + </UTable> 17 + <UDivider class="my-4" /> 18 + <!-- Add a new comment --> 19 + <div v-if="!isLoggedIn" class="flex flex-row gap-2"> 20 + <UButton to="/auth/login">Login</UButton> 21 + <UButton to="/auth/signup">Sign up!</UButton> 22 + </div> 23 + <UForm v-else :state="state" class="space-y-4" @submit="onSubmit"> 24 + <UFormGroup label="Comments on the conference" name="text"> 25 + <UTextarea v-model="state.text" /> 26 + </UFormGroup> 27 + <UFormGroup label="Mark as public" name="public"> 28 + <UCheckbox v-model="state.public" /> 29 + </UFormGroup> 30 + 31 + <UButton type="submit"> 32 + Submit 33 + </UButton> 34 + </UForm> 35 + </UContainer> 36 + </template> 37 + 38 + <script setup lang="ts"> 39 + import type { FormSubmitEvent } from '#ui/types' 40 + 41 + const { isLoggedIn } = useEdgeDbIdentity() 42 + const { data, refresh } = await useFetch('/api/comments') 43 + 44 + const state = reactive({ 45 + text: undefined, 46 + public: true 47 + }) 48 + 49 + async function onSubmit (event: FormSubmitEvent<any>) { 50 + const result = await $fetch('/api/comment', { 51 + method: 'POST', 52 + body: state 53 + }) 54 + console.log(({ result })) 55 + refresh() 56 + } 57 + 58 + async function deleteComment(id: string) { 59 + await $fetch(`/api/comment/${id}`, { 60 + method: 'DELETE', 61 + }) 62 + refresh() 63 + } 64 + </script>
+23
server/api/comment.post.ts
··· 1 + import e from "../../dbschema/edgeql-js"; 2 + 3 + export default defineEventHandler(async event => { 4 + const { text, public: isPublic } = await readBody(event) || {} 5 + if (!text || isPublic === undefined) { 6 + throw createError({ statusCode: 422 }) 7 + } 8 + const identity = await useEdgeDbIdentity <{ id: string }>(event) 9 + if (!identity.isLoggedIn) { 10 + throw createError({ statusCode: 401 }) 11 + } 12 + 13 + const client = useEdgeDb(event) 14 + 15 + const query = e.insert(e.Comment, { 16 + text, 17 + author: identity.identity.id, 18 + public: isPublic, 19 + }) 20 + 21 + await query.run(client) 22 + return null 23 + })
+18
server/api/comment/[id].delete.ts
··· 1 + import e from "../../../dbschema/edgeql-js" 2 + 3 + export default defineEventHandler(async event => { 4 + const identity = await useEdgeDbIdentity(event) 5 + const id = getRouterParam(event, 'id') as string 6 + if (!identity.isLoggedIn) { 7 + throw createError({ statusCode: 401 }) 8 + } 9 + 10 + const client = useEdgeDb(event) 11 + 12 + const query = e.delete(e.Comment, (comment) => ({ 13 + filter_single: { id } 14 + })) 15 + 16 + await query.run(client) 17 + return null 18 + })
+19
server/api/comments.get.ts
··· 1 + import e from '../../dbschema/edgeql-js' 2 + 3 + export default defineEventHandler(async event => { 4 + const client = useEdgeDb(event) 5 + 6 + const query = e.select(e.Comment, (comment) => ({ 7 + text: comment.text, 8 + id: true, 9 + isCurrent: e.op(comment.author, '=', e.global.current_user), 10 + filter: e.op( 11 + e.op(comment.public, '=', true), 12 + 'or', 13 + e.op(comment.author, '=', e.global.current_user) 14 + ) 15 + })) 16 + 17 + const comments = await query.run(client) 18 + return comments 19 + })