diff --git a/apps/docs/next.config.mjs b/apps/docs/next.config.mjs
index 4b981c4..6ac017d 100644
--- a/apps/docs/next.config.mjs
+++ b/apps/docs/next.config.mjs
@@ -1,11 +1,14 @@
-import withMarkdoc from '@markdoc/next.js'
-import withSearch from './src/markdoc/search.mjs'
+import withMarkdoc from '@markdoc/next.js';
+import withSearch from './src/markdoc/search.mjs';
+
+const isProd = process.env.NODE_ENV === 'production';
/** @type {import('next').NextConfig} */
const nextConfig = {
+ assetPrefix: isProd ? 'https://docs.elwood.software' : undefined,
pageExtensions: ['js', 'jsx', 'md', 'ts', 'tsx'],
-}
+};
export default withSearch(
- withMarkdoc({ schemaPath: './src/markdoc' })(nextConfig),
-)
+ withMarkdoc({schemaPath: './src/markdoc'})(nextConfig),
+);
diff --git a/apps/www/eslint.config.js b/apps/www/eslint.config.js
new file mode 100644
index 0000000..8fb4d8c
--- /dev/null
+++ b/apps/www/eslint.config.js
@@ -0,0 +1,9 @@
+/** @type {import("eslint").Linter.Config} */
+module.exports = {
+ extends: ['@elwood/eslint-config/next.js'],
+ parser: '@typescript-eslint/parser',
+ ignorePatterns: ['next.config.mjs'],
+ parserOptions: {
+ project: true,
+ },
+};
diff --git a/apps/www/next-env.d.ts b/apps/www/next-env.d.ts
new file mode 100644
index 0000000..4f11a03
--- /dev/null
+++ b/apps/www/next-env.d.ts
@@ -0,0 +1,5 @@
+///
+///
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/basic-features/typescript for more information.
diff --git a/apps/www/next.config.mjs b/apps/www/next.config.mjs
new file mode 100644
index 0000000..3047ff5
--- /dev/null
+++ b/apps/www/next.config.mjs
@@ -0,0 +1,36 @@
+/** @type {import('next').NextConfig} */
+export default {
+ reactStrictMode: true,
+ transpilePackages: [
+ '@elwood/common',
+ '@elwood/react',
+ '@elwood/js',
+ '@elwood/ui',
+ ],
+ webpack(config) {
+ config.resolve.alias.canvas = false;
+ return config;
+ },
+ async rewrites() {
+ return [
+ {
+ source: '/db/latest.json',
+ destination:
+ 'https://github.com/elwood-software/db/raw/main/versions/latest.json',
+ },
+ {
+ source: '/db/v:version',
+ destination:
+ 'https://github.com/elwood-software/db/raw/main/versions/:version',
+ },
+ {
+ source: '/docs/',
+ destination: 'https://docs.elwood.software/docs/',
+ },
+ {
+ source: '/docs/:path*/',
+ destination: 'https://docs.elwood.software/docs/:path*/',
+ },
+ ];
+ },
+};
diff --git a/apps/www/package.json b/apps/www/package.json
new file mode 100644
index 0000000..69984eb
--- /dev/null
+++ b/apps/www/package.json
@@ -0,0 +1,37 @@
+{
+ "name": "@elwood/apps-www",
+ "version": "0.0.0",
+ "private": true,
+ "scripts": {
+ "build": "next build",
+ "clean": "rm -rf .next",
+ "dev": "next dev -p 3001",
+ "lint": "next lint",
+ "type-check": "tsc --noEmit",
+ "start": "next start"
+ },
+ "dependencies": {
+ "@elwood/common": "workspace:*",
+ "@elwood/ui": "workspace:*",
+ "@supabase/ssr": "^0.3.0",
+ "autoprefixer": "^10.4.19",
+ "geist": "^1.3.0",
+ "next": "^14.2.3",
+ "postcss": "^8.4.38",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-use": "^17.5.0",
+ "tailwindcss": "^3.4.3",
+ "zod": "^3.23.8"
+ },
+ "devDependencies": {
+ "@elwood/eslint-config": "workspace:*",
+ "@elwood/typescript-config": "workspace:*",
+ "@next/eslint-plugin-next": "^14.2.3",
+ "@types/node": "^20.12.12",
+ "@types/react": "^18.3.2",
+ "@types/react-dom": "^18.3.0",
+ "prettier-plugin-tailwindcss": "^0.5.14",
+ "typescript": "^5.4.5"
+ }
+}
diff --git a/apps/www/postcss.config.js b/apps/www/postcss.config.js
new file mode 100644
index 0000000..8caf5cc
--- /dev/null
+++ b/apps/www/postcss.config.js
@@ -0,0 +1 @@
+module.exports = require('@elwood/ui/postcss.config.js');
\ No newline at end of file
diff --git a/apps/www/public/android-chrome-192x192.png b/apps/www/public/android-chrome-192x192.png
new file mode 100644
index 0000000..9d25e5c
Binary files /dev/null and b/apps/www/public/android-chrome-192x192.png differ
diff --git a/apps/www/public/android-chrome-512x512.png b/apps/www/public/android-chrome-512x512.png
new file mode 100644
index 0000000..049b1e2
Binary files /dev/null and b/apps/www/public/android-chrome-512x512.png differ
diff --git a/apps/www/public/apple-touch-icon-114x114.png b/apps/www/public/apple-touch-icon-114x114.png
new file mode 100644
index 0000000..e48e78e
Binary files /dev/null and b/apps/www/public/apple-touch-icon-114x114.png differ
diff --git a/apps/www/public/apple-touch-icon-120x120.png b/apps/www/public/apple-touch-icon-120x120.png
new file mode 100644
index 0000000..aec0277
Binary files /dev/null and b/apps/www/public/apple-touch-icon-120x120.png differ
diff --git a/apps/www/public/apple-touch-icon-144x144.png b/apps/www/public/apple-touch-icon-144x144.png
new file mode 100644
index 0000000..6e5c9fa
Binary files /dev/null and b/apps/www/public/apple-touch-icon-144x144.png differ
diff --git a/apps/www/public/apple-touch-icon-152x152.png b/apps/www/public/apple-touch-icon-152x152.png
new file mode 100644
index 0000000..3ba8154
Binary files /dev/null and b/apps/www/public/apple-touch-icon-152x152.png differ
diff --git a/apps/www/public/apple-touch-icon-167x167.png b/apps/www/public/apple-touch-icon-167x167.png
new file mode 100644
index 0000000..c784b4c
Binary files /dev/null and b/apps/www/public/apple-touch-icon-167x167.png differ
diff --git a/apps/www/public/apple-touch-icon-180x180.png b/apps/www/public/apple-touch-icon-180x180.png
new file mode 100644
index 0000000..b904366
Binary files /dev/null and b/apps/www/public/apple-touch-icon-180x180.png differ
diff --git a/apps/www/public/apple-touch-icon-57x57.png b/apps/www/public/apple-touch-icon-57x57.png
new file mode 100644
index 0000000..6fdab3a
Binary files /dev/null and b/apps/www/public/apple-touch-icon-57x57.png differ
diff --git a/apps/www/public/apple-touch-icon-60x60.png b/apps/www/public/apple-touch-icon-60x60.png
new file mode 100644
index 0000000..78c9113
Binary files /dev/null and b/apps/www/public/apple-touch-icon-60x60.png differ
diff --git a/apps/www/public/apple-touch-icon-72x72.png b/apps/www/public/apple-touch-icon-72x72.png
new file mode 100644
index 0000000..0d2fe22
Binary files /dev/null and b/apps/www/public/apple-touch-icon-72x72.png differ
diff --git a/apps/www/public/apple-touch-icon-76x76.png b/apps/www/public/apple-touch-icon-76x76.png
new file mode 100644
index 0000000..25e9aae
Binary files /dev/null and b/apps/www/public/apple-touch-icon-76x76.png differ
diff --git a/apps/www/public/favicon-128x128.png b/apps/www/public/favicon-128x128.png
new file mode 100644
index 0000000..8d66d64
Binary files /dev/null and b/apps/www/public/favicon-128x128.png differ
diff --git a/apps/www/public/favicon-16x16.png b/apps/www/public/favicon-16x16.png
new file mode 100644
index 0000000..b37f4d7
Binary files /dev/null and b/apps/www/public/favicon-16x16.png differ
diff --git a/apps/www/public/favicon-196x196.png b/apps/www/public/favicon-196x196.png
new file mode 100644
index 0000000..f25b61e
Binary files /dev/null and b/apps/www/public/favicon-196x196.png differ
diff --git a/apps/www/public/favicon-32x32.png b/apps/www/public/favicon-32x32.png
new file mode 100644
index 0000000..dc06d72
Binary files /dev/null and b/apps/www/public/favicon-32x32.png differ
diff --git a/apps/www/public/favicon-96x96.png b/apps/www/public/favicon-96x96.png
new file mode 100644
index 0000000..e5d3664
Binary files /dev/null and b/apps/www/public/favicon-96x96.png differ
diff --git a/apps/www/public/mstile-144x144.png b/apps/www/public/mstile-144x144.png
new file mode 100644
index 0000000..6e5c9fa
Binary files /dev/null and b/apps/www/public/mstile-144x144.png differ
diff --git a/apps/www/public/mstile-150x150.png b/apps/www/public/mstile-150x150.png
new file mode 100644
index 0000000..009b973
Binary files /dev/null and b/apps/www/public/mstile-150x150.png differ
diff --git a/apps/www/public/mstile-310x150.png b/apps/www/public/mstile-310x150.png
new file mode 100644
index 0000000..667415e
Binary files /dev/null and b/apps/www/public/mstile-310x150.png differ
diff --git a/apps/www/public/mstile-310x310.png b/apps/www/public/mstile-310x310.png
new file mode 100644
index 0000000..4687b2a
Binary files /dev/null and b/apps/www/public/mstile-310x310.png differ
diff --git a/apps/www/public/mstile-70x70.png b/apps/www/public/mstile-70x70.png
new file mode 100644
index 0000000..b090fbe
Binary files /dev/null and b/apps/www/public/mstile-70x70.png differ
diff --git a/apps/www/src/app/favicon.ico b/apps/www/src/app/favicon.ico
new file mode 100644
index 0000000..b37f4d7
Binary files /dev/null and b/apps/www/src/app/favicon.ico differ
diff --git a/apps/www/src/app/global.css b/apps/www/src/app/global.css
new file mode 100644
index 0000000..b5c61c9
--- /dev/null
+++ b/apps/www/src/app/global.css
@@ -0,0 +1,3 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
diff --git a/apps/www/src/app/layout.tsx b/apps/www/src/app/layout.tsx
new file mode 100644
index 0000000..63878cc
--- /dev/null
+++ b/apps/www/src/app/layout.tsx
@@ -0,0 +1,28 @@
+import type {PropsWithChildren} from 'react';
+import {type Metadata} from 'next';
+import {cookies} from 'next/headers';
+import {ElwoodThemeProvider} from '@elwood/ui';
+
+import './global.css';
+import '@elwood/ui/style.css';
+
+export const metadata: Metadata = {
+ title: 'Elwood',
+};
+
+export default function RootLayout(props: PropsWithChildren): JSX.Element {
+ const theme = cookies().get('system-theme')?.value ?? '';
+ const validThemes = ['light', 'dark'];
+ const themeClassName = validThemes.includes(theme) ? theme : '';
+
+ return (
+
+
+ {props.children}
+
+
+ );
+}
diff --git a/apps/www/src/app/page.tsx b/apps/www/src/app/page.tsx
new file mode 100644
index 0000000..b75e7d1
--- /dev/null
+++ b/apps/www/src/app/page.tsx
@@ -0,0 +1,28 @@
+import {Logo, Button} from '@elwood/ui';
+
+export default function Page() {
+ return (
+
+
+
+ Elwood
+
+
+
+ Open source Dropbox alternative
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/www/tailwind.config.js b/apps/www/tailwind.config.js
new file mode 100644
index 0000000..2b99add
--- /dev/null
+++ b/apps/www/tailwind.config.js
@@ -0,0 +1,19 @@
+const {theme, plugins} = require('@elwood/ui/tailwind.config.js');
+const {resolve, dirname} = require('path');
+
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ content: [
+ resolve(
+ dirname(require.resolve('@elwood/react')),
+ './**/*.{js,ts,jsx,tsx,mdx}',
+ ),
+ resolve(
+ dirname(require.resolve('@elwood/ui')),
+ './**/*.{js,ts,jsx,tsx,mdx}',
+ ),
+ './src/**/*.{js,ts,jsx,tsx,mdx}',
+ ],
+ theme,
+ plugins: [...plugins],
+};
diff --git a/apps/www/tsconfig.json b/apps/www/tsconfig.json
new file mode 100644
index 0000000..f4801d0
--- /dev/null
+++ b/apps/www/tsconfig.json
@@ -0,0 +1,16 @@
+{
+ "exclude": ["node_modules", "next.config.mjs"],
+ "extends": "@elwood/typescript-config/nextjs.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ },
+ "include": ["src", "next-env.d.ts", ".next/types/**/*.ts"]
+}
diff --git a/apps/www/turbo.json b/apps/www/turbo.json
new file mode 100644
index 0000000..347797e
--- /dev/null
+++ b/apps/www/turbo.json
@@ -0,0 +1,9 @@
+{
+ "extends": ["//"],
+ "globalEnv": [],
+ "pipeline": {
+ "build": {
+ "outputs": [".next/**", "!.next/cache/**"]
+ }
+ }
+}
diff --git a/package.json b/package.json
index 2ddecb0..5c02454 100644
--- a/package.json
+++ b/package.json
@@ -26,7 +26,7 @@
"prettier-plugin-tailwindcss": "^0.5.14",
"turbo": "latest"
},
- "packageManager": "pnpm@9.1.0",
+ "packageManager": "pnpm@9.1.1",
"engines": {
"node": ">=20"
},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a02744a..649a411 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -197,6 +197,70 @@ importers:
specifier: ^5.4.5
version: 5.4.5
+ apps/www:
+ dependencies:
+ '@elwood/common':
+ specifier: workspace:*
+ version: link:../../packages/common
+ '@elwood/ui':
+ specifier: workspace:*
+ version: link:../../packages/ui
+ '@supabase/ssr':
+ specifier: ^0.3.0
+ version: 0.3.0(@supabase/supabase-js@2.43.1)
+ autoprefixer:
+ specifier: ^10.4.19
+ version: 10.4.19(postcss@8.4.38)
+ geist:
+ specifier: ^1.3.0
+ version: 1.3.0(next@14.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
+ next:
+ specifier: ^14.2.3
+ version: 14.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ postcss:
+ specifier: ^8.4.38
+ version: 8.4.38
+ react:
+ specifier: ^18.3.1
+ version: 18.3.1
+ react-dom:
+ specifier: ^18.3.1
+ version: 18.3.1(react@18.3.1)
+ react-use:
+ specifier: ^17.5.0
+ version: 17.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ tailwindcss:
+ specifier: ^3.4.3
+ version: 3.4.3
+ zod:
+ specifier: ^3.23.8
+ version: 3.23.8
+ devDependencies:
+ '@elwood/eslint-config':
+ specifier: workspace:*
+ version: link:../../config/eslint
+ '@elwood/typescript-config':
+ specifier: workspace:*
+ version: link:../../config/typescript
+ '@next/eslint-plugin-next':
+ specifier: ^14.2.3
+ version: 14.2.3
+ '@types/node':
+ specifier: ^20.12.12
+ version: 20.12.12
+ '@types/react':
+ specifier: ^18.3.2
+ version: 18.3.2
+ '@types/react-dom':
+ specifier: ^18.3.0
+ version: 18.3.0
+ prettier-plugin-tailwindcss:
+ specifier: ^0.5.14
+ version: 0.5.14(prettier@3.2.5)
+ typescript:
+ specifier: ^5.4.5
+ version: 5.4.5
+
config/eslint:
devDependencies:
'@vercel/style-guide':
diff --git a/supabase/migrations/20240218234006_schema.sql b/supabase/migrations/20240218234006_schema.sql
deleted file mode 100644
index 2c9fa48..0000000
--- a/supabase/migrations/20240218234006_schema.sql
+++ /dev/null
@@ -1,7 +0,0 @@
-CREATE EXTENSION IF NOT EXISTS vector SCHEMA extensions;
-CREATE SCHEMA IF NOT EXISTS elwood;
-
-grant usage on schema elwood to postgres, anon, authenticated, service_role;
-alter default privileges in schema elwood grant all on tables to postgres, anon, authenticated, service_role;
-alter default privileges in schema elwood grant all on functions to postgres, anon, authenticated, service_role;
-alter default privileges in schema elwood grant all on sequences to postgres, anon, authenticated, service_role;
diff --git a/supabase/migrations/20240218234007_types.sql b/supabase/migrations/20240218234007_types.sql
deleted file mode 100644
index 3adc2f4..0000000
--- a/supabase/migrations/20240218234007_types.sql
+++ /dev/null
@@ -1,89 +0,0 @@
-
--- NODE_TYPE
-DROP TYPE IF EXISTS public.elwood_node_type CASCADE;
-CREATE TYPE public.elwood_node_type AS ENUM (
- 'TREE',
- 'BLOB',
- 'BUCKET'
-);
-
--- NODE
-DROP TYPE IF EXISTS public.elwood_node CASCADE;
-CREATE TYPE public.elwood_node AS (
- "id" text,
- "object_id" uuid,
- "type" public.elwood_node_type,
- "prefix" text[],
- "name" text,
- "mime_type" text,
- "size" int
-);
-
-
-DROP TYPE IF EXISTS public.elwood_storage_search_result CASCADE;
-CREATE TYPE public.elwood_storage_search_result AS (
- name text,
- id uuid,
- updated_at timestamptz,
- created_at timestamptz,
- last_accessed_at timestamptz,
- metadata jsonb
-);
-
-DROP TYPE IF EXISTS public.elwood_get_node_result CASCADE;
-CREATE TYPE public.elwood_get_node_result AS (
- "node" public.elwood_node,
- "parent" public.elwood_node,
- "children" public.elwood_node[],
- "key_children" text[]
-);
-
-
-DROP TYPE IF EXISTS public.elwood_node_tree CASCADE;
-CREATE TYPE public.elwood_node_tree AS (
- "node" public.elwood_node,
- "id" text,
- "parent" text
-);
-
-DROP TYPE IF EXISTS public.elwood_get_node_tree_result CASCADE;
-CREATE TYPE public.elwood_get_node_tree_result AS (
- "rootNodeId" text,
- "expandedIds" text[],
- "tree" public.elwood_node_tree[]
-);
-
-DROP TYPE IF EXISTS public.elwood_member_type CASCADE;
-CREATE TYPE public.elwood_member_type AS ENUM (
- 'USER',
- 'TEAM'
-);
-
---
--- @@ NAMESPACE: ELWOOD
---
-
-DROP TYPE IF EXISTS elwood.get_node_leaf_result CASCADE;
-CREATE TYPE elwood.get_node_leaf_result AS (
- "node" public.elwood_node
-);
-
-DROP TYPE IF EXISTS elwood.follow_type CASCADE;
-CREATE TYPE elwood.follow_type AS ENUM (
- 'SAVE',
- 'SUBSCRIBE'
-);
-
-DROP TYPE IF EXISTS elwood.member_role CASCADE;
-CREATE TYPE elwood.member_role AS ENUM (
- 'ADMIN',
- 'MANAGER',
- 'MEMBER'
-);
-
-DROP TYPE IF EXISTS elwood.activity_type CASCADE;
-CREATE TYPE elwood.activity_type AS ENUM (
- 'COMMENT',
- 'REACTION',
- 'LIKE'
-);
diff --git a/supabase/migrations/20240218234008_tbl_member.sql b/supabase/migrations/20240218234008_tbl_member.sql
deleted file mode 100644
index c61894f..0000000
--- a/supabase/migrations/20240218234008_tbl_member.sql
+++ /dev/null
@@ -1,62 +0,0 @@
-
-
-CREATE TABLE elwood.member (
- "instance_id" uuid NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
- "id" uuid NOT NULL DEFAULT extensions.uuid_generate_v4(),
- "user_id" uuid NOT NULL,
- "type" public.elwood_member_type NOT NULL DEFAULT 'USER',
- "username" text NULL,
- "display_name" text NULL,
- "added_by_user_id" uuid NULL,
- "role" elwood.member_role NOT NULL DEFAULT 'MEMBER',
- "created_at" timestamptz default now(),
- "updated_at" timestamptz default now(),
-
- CONSTRAINT "elwood_member_user_id" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id"),
- CONSTRAINT "elwood_member_added_by_user_id" FOREIGN KEY ("added_by_user_id") REFERENCES "auth"."users"("id"),
- PRIMARY KEY ("id")
-);
-
-
-CREATE UNIQUE INDEX IF NOT EXISTS elwood_idx_member_user_id ON elwood.member("instance_id", "user_id");
-CREATE UNIQUE INDEX IF NOT EXISTS elwood_idx_member_username ON elwood.member("instance_id", "username");
-alter table elwood."member" enable row level security;
-
-DROP FUNCTION IF EXISTS elwood.is_a_member() CASCADE;
-create function elwood.is_a_member()
-returns boolean
-language plpgsql
-security definer
-as $$
-begin
- return exists (
- select 1 from elwood.member
- where auth.uid() = user_id
- );
-end;
-$$;
-
-
-CREATE VIEW public.elwood_member with (security_invoker=on)
- AS SELECT
- "id",
- "user_id",
- "type",
- "username",
- "display_name",
- "added_by_user_id",
- "role",
- "created_at",
- "updated_at"
- FROM elwood.member;
-
-create policy "Members can view all members."
-on elwood.member for select
-to authenticated
-using (elwood.is_a_member());
-
-create policy "Member can update their own member row."
-on elwood.member for update
-to authenticated
-using ( auth.uid() = user_id )
-with check ( auth.uid() = user_id );
\ No newline at end of file
diff --git a/supabase/migrations/20240218234009_tbl_setting.sql b/supabase/migrations/20240218234009_tbl_setting.sql
deleted file mode 100644
index 3f36364..0000000
--- a/supabase/migrations/20240218234009_tbl_setting.sql
+++ /dev/null
@@ -1,14 +0,0 @@
-create table elwood.setting (
- "instance_id" uuid not null default '00000000-0000-0000-0000-000000000000',
- "name" varchar(20) not null primary key,
- "value" jsonb not null default '{}'::jsonb,
- "created_at" timestamptz default now(),
- "updated_at" timestamptz default now()
-);
-
-create unique index if not exists elwood_idx_settings_name on elwood.setting (
- "instance_id",
- "name"
-);
-
-alter table elwood."setting" enable row level security;
diff --git a/supabase/migrations/20240218234010_tbl_activity.sql b/supabase/migrations/20240218234010_tbl_activity.sql
deleted file mode 100644
index 049ee1e..0000000
--- a/supabase/migrations/20240218234010_tbl_activity.sql
+++ /dev/null
@@ -1,75 +0,0 @@
-
-
-CREATE TABLE IF NOT EXISTS elwood.activity (
- "instance_id" uuid NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
- "id" uuid NOT NULL DEFAULT uuid_generate_v4(),
- "user_id" uuid NOT NULL,
- "member_id" uuid NOT NULL,
- "asset_id" text NOT NULL,
- "asset_type" TEXT NOT NULL,
- "is_resolved" BOOLEAN NOT NULL DEFAULT FALSE,
- "is_deleted" BOOLEAN NOT NULL DEFAULT FALSE,
- "type" elwood.activity_type NOT NULL,
- "text" text NOT NULL,
- "attachments" jsonb NOT NULL DEFAULT '{}'::jsonb,
- "created_at" timestamptz default now(),
- "updated_at" timestamptz default now(),
-
- CONSTRAINT "elwood_activity_user_id" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id"),
- CONSTRAINT "elwood_activity_member_id" FOREIGN KEY ("member_id") REFERENCES "elwood"."member"("id"),
- PRIMARY KEY ("id")
-);
-
-alter table elwood."activity" enable row level security;
-
-DROP VIEW IF EXISTS public.elwood_activity;
-CREATE VIEW public.elwood_activity with (security_invoker=on)
- AS SELECT
- "a"."id",
- "a"."user_id",
- "a"."member_id",
- "a"."asset_id",
- "a"."asset_type",
- "a"."is_deleted",
- "a"."is_resolved",
- "a"."type",
- "a"."text",
- "a"."attachments",
- "a"."created_at",
- "a"."updated_at"
- FROM elwood.activity as a;
-
-create policy "Members can view all activity."
-on elwood.activity for select
-to authenticated
-using (elwood.is_a_member());
-
-create policy "Members can create activity."
-on elwood.activity for insert
-to authenticated
-with check (elwood.is_a_member());
-
-
--- BEFORE INSERT
-CREATE OR REPLACE FUNCTION elwood.before_activity_insert()
- RETURNS TRIGGER
- LANGUAGE PLPGSQL
-AS
-$$
-DECLARE
- _user_id uuid := auth.uid();
- _member_id uuid;
-BEGIN
- SELECT id INTO _member_id FROM elwood.member WHERE user_id = _user_id;
- NEW.member_id = _member_id;
- -- users can only insert activity for themselves
- NEW.user_id = _user_id;
- RETURN NEW;
-END;
-$$;
-
-CREATE TRIGGER trigger_before_activity_insert
-BEFORE INSERT
-ON elwood.activity
-FOR EACH ROW
-EXECUTE FUNCTION elwood.before_activity_insert();
diff --git a/supabase/migrations/20240218234011_tbl_follow.sql b/supabase/migrations/20240218234011_tbl_follow.sql
deleted file mode 100644
index 848579d..0000000
--- a/supabase/migrations/20240218234011_tbl_follow.sql
+++ /dev/null
@@ -1,86 +0,0 @@
-create table if not exists elwood.follow (
- "instance_id" uuid not null default '00000000-0000-0000-0000-000000000000',
- "id" uuid not null default uuid_generate_v4(),
- "user_id" uuid not null,
- "type" elwood.follow_type not null DEFAULT 'SAVE',
- "asset_type" text not null,
- "asset_id" text not null,
- "is_active" boolean not null default false,
- "created_at" timestamptz default now(),
- "updated_at" timestamptz default now(),
-
- constraint "elwood_follow_user_id"
- foreign key ("user_id") references "auth"."users" ("id"),
-
- primary key ("id")
-);
-
-create unique index if not exists elwood_idx_follow_user_asset on elwood.follow (
- "user_id",
- "type",
- "asset_type",
- "asset_id"
-);
-
-
-alter table "elwood"."follow" enable row level security;
-
-DROP VIEW IF EXISTS public.elwood_follow;
-CREATE VIEW public.elwood_follow with (security_invoker=on)
- AS SELECT
- "id",
- "user_id",
- "type",
- "asset_type",
- "asset_id",
- "is_active",
- "created_at",
- "updated_at",
- (split_part(asset_id, ':', 3) = 'blob')::boolean as is_object_blob,
- (select name from storage.buckets where id = split_part(asset_id, ':', 4)) as bucket_name,
- replace(
- CASE
- WHEN split_part(asset_id, ':', 5) = '' THEN NULL
- ELSE (select name from storage.objects where id = split_part(asset_id, ':', 5)::uuid)
- END,
- '/.emptyFolderPlaceholder',
- ''
- ) as object_name
- FROM elwood.follow;
-
-create policy "Members can view their own follows."
-on elwood.follow for select
-to authenticated
-using (elwood.is_a_member() AND "user_id" = auth.uid());
-
-create policy "Members can create their own follow."
-on elwood.follow for insert
-to authenticated
-with check (elwood.is_a_member() AND "user_id" = auth.uid());
-
-create policy "Members can update their own follow."
-on elwood.follow for update
-to authenticated
-with check (elwood.is_a_member() AND "user_id" = auth.uid());
-
-
--- BEFORE INSERT
-CREATE OR REPLACE FUNCTION elwood.before_follow_insert()
- RETURNS TRIGGER
- LANGUAGE PLPGSQL
-AS
-$$
-DECLARE
- _user_id uuid := auth.uid();
-BEGIN
- -- users can only insert activity for themselves
- NEW.user_id = _user_id;
- RETURN NEW;
-END;
-$$;
-
-CREATE TRIGGER trigger_before_follow_insert
-BEFORE INSERT
-ON elwood.follow
-FOR EACH ROW
-EXECUTE FUNCTION elwood.before_follow_insert();
diff --git a/supabase/migrations/20240218234012_tbl_notification.sql b/supabase/migrations/20240218234012_tbl_notification.sql
deleted file mode 100644
index f4c831c..0000000
--- a/supabase/migrations/20240218234012_tbl_notification.sql
+++ /dev/null
@@ -1,48 +0,0 @@
-create table if not exists elwood.notification (
- "instance_id" uuid not null default '00000000-0000-0000-0000-000000000000',
- "id" uuid not null default uuid_generate_v4(),
- "user_id" uuid not null,
- "type" text not null default 'GENERIC',
- "data" jsonb not null default '{}'::jsonb,
- "has_seen" boolean not null default false,
- "seen_at" timestamptz null,
- "bucket_id" text null,
- "object_id" uuid null,
- "created_at" timestamptz default now(),
- "updated_at" timestamptz default now(),
-
- constraint "elwood_notification_user_id"
- foreign key ("user_id") references "auth"."users" ("id"),
- constraint "elwood_follow_bucket_id"
- foreign key ("bucket_id") references "storage"."buckets"("id"),
- constraint "elwood_follow_object_id"
- foreign key ("object_id") references "storage"."objects"("id"),
- primary key ("id")
-);
-
-alter table "elwood"."notification" enable row level security;
-
-drop view if exists public.elwood_notification;
-create view public.elwood_notification with (security_invoker=on)
- AS SELECT
- "id",
- "type",
- "data",
- "has_seen",
- "seen_at",
- "bucket_id",
- "object_id",
- "created_at",
- "updated_at"
- from elwood.notification;
-
-
-create policy "Members can view their own notifications."
-on elwood.notification for select
-to authenticated
-using (elwood.is_a_member() AND "user_id" = auth.uid());
-
-create policy "Members can update their own notification."
-on elwood.notification for update
-to authenticated
-with check (elwood.is_a_member() AND "user_id" = auth.uid());
diff --git a/supabase/migrations/20240218234013_triggers.sql b/supabase/migrations/20240218234013_triggers.sql
deleted file mode 100644
index b4d6bac..0000000
--- a/supabase/migrations/20240218234013_triggers.sql
+++ /dev/null
@@ -1,70 +0,0 @@
--- BEFORE INSERT
-
-create or replace function elwood.after_object_insert_or_update()
-returns trigger
-language PLPGSQL
-as $$
-DECLARE
- _path_parts text[];
- _path_parts_length int;
- _prefix text[];
- _part text;
- _path_tokens text[];
-BEGIN
-
- -- ignore if there is a file name
- IF storage.filename(NEW.name) = '.emptyFolderPlaceholder' THEN
- RETURN NEW;
- END IF;
-
- -- split the new path into parts
- _path_parts := regexp_split_to_array(NEW.name, '/');
- _path_parts_length := array_length(_path_parts, 1);
-
- IF _path_parts_length = 1 THEN
- RETURN NEW;
- END IF;
-
- -- now loop through each part and make sure there's an object
- -- for the tree path so we can have an object id
- FOREACH _part IN ARRAY _path_parts[1:_path_parts_length-1] LOOP
- _prefix := _prefix || array[_part];
- _path_tokens := _prefix || array['.emptyFolderPlaceholder'];
-
- -- insert a placeholder object if it doesn't exist
- INSERT INTO storage.objects ("bucket_id", "owner_id", "name") VALUES (
- NEW.bucket_id,
- NEW.owner_id,
- array_to_string(
- _path_tokens,
- '/'
- )
- ) ON CONFLICT ("bucket_id","name") DO NOTHING;
- END LOOP;
-
-
- -- if we think this is a folder, insert a placeholder object
- IF NEW.metadata IS NULL OR NEW.metadata->>'eTag' IS NULL THEN
- _path_tokens := _path_parts || array['.emptyFolderPlaceholder'];
-
- -- insert a placeholder object if it doesn't exist
- INSERT INTO storage.objects ("bucket_id", "owner_id", "name") VALUES (
- NEW.bucket_id,
- NEW.owner_id,
- array_to_string(
- _path_tokens,
- '/'
- )
- ) ON CONFLICT ("bucket_id","name") DO NOTHING;
- END IF;
-
- RETURN NEW;
-END;
-$$;
-
-DROP TRIGGER IF EXISTS trigger_after_object_insert_or_update on storage.objects;
-create trigger trigger_after_object_insert_or_update
-before insert on storage.objects
-for each row
-when (pg_trigger_depth() < 1)
-execute function elwood.after_object_insert_or_update();
diff --git a/supabase/migrations/20240218234019_create_node_id.sql b/supabase/migrations/20240218234019_create_node_id.sql
deleted file mode 100644
index 9b9d8fe..0000000
--- a/supabase/migrations/20240218234019_create_node_id.sql
+++ /dev/null
@@ -1,56 +0,0 @@
-
-DROP FUNCTION IF EXISTS elwood.create_node_id(text, text, uuid);
-CREATE OR REPLACE FUNCTION elwood.create_node_id(
- p_type public.elwood_node_type,
- p_bucket text default null,
- p_object_id uuid default null
-) RETURNS text
-AS $$
-DECLARE
- _node_id text[];
- _prefix_part text;
- _prefix_parts text[];
-BEGIN
- _node_id := ARRAY['urn', 'enid', lower(p_type::text)];
-
- IF p_bucket IS NOT NULL THEN
- _node_id := _node_id || ARRAY[p_bucket::text];
- END IF;
-
- IF p_object_id IS NOT NULL THEN
- _node_id := _node_id || ARRAY[p_object_id::text];
- END IF;
-
- return array_to_string(_node_id,':');
-END;
-$$ language plpgsql;
-
-
-
-DROP FUNCTION IF EXISTS elwood.create_node_id_for_tree(text[]);
-CREATE OR REPLACE FUNCTION elwood.create_node_id_for_tree(
- p_path text[]
-) RETURNS text
-AS $$
-DECLARE
- _bucket_id text;
- _name text[];
- _object_row storage.objects;
- _path_length int;
-BEGIN
- _path_length := array_length(p_path, 1);
- _bucket_id := ARRAY_TO_STRING(p_path[:1], '');
- _name := p_path[2:] || ARRAY['.emptyFolderPlaceholder'];
-
- RAISE WARNING 'create_node_id_for_tree: p_path % || %', p_path, array_to_string(_name, '/');
-
- SELECT * INTO _object_row FROM storage.objects WHERE "bucket_id" = _bucket_id AND "name" = array_to_string(_name, '/');
-
- IF _object_row IS NULL THEN
- return 'urn:enid:no_object_row_found';
- END IF;
-
- return elwood.create_node_id('TREE', _bucket_id, _object_row.id);
-
-END;
-$$ language plpgsql;
diff --git a/supabase/migrations/20240220162656_get_node_children.sql b/supabase/migrations/20240220162656_get_node_children.sql
deleted file mode 100644
index e10d96f..0000000
--- a/supabase/migrations/20240220162656_get_node_children.sql
+++ /dev/null
@@ -1,76 +0,0 @@
-drop function if exists elwood.get_node_children(uuid, text[]);
-create or replace function elwood.get_node_children(p_prefix text[])
-returns jsonb[]
-language PLPGSQL
-as $$
-DECLARE
- _bucket_row storage.buckets;
- _search_row public.elwood_storage_search_result;
- _object_row public.elwood_node;
- _nodes jsonb[];
- _node_type public.elwood_node_type;
- _bucket_id text;
- _prefix text;
- _path text;
- _depth int;
-BEGIN
- _nodes := ARRAY[]::jsonb[];
- _prefix := ARRAY_TO_STRING(p_prefix, '/');
-
- -- if there's nothing in input then return the root
- -- which is all the buckets
- IF array_length(p_prefix, 1) IS NULL THEN
- FOR _bucket_row IN
- SELECT * FROM storage.buckets
- LOOP
- _object_row.id := elwood.create_node_id('BUCKET', _bucket_row.name::text, null);
- _object_row.type := 'BUCKET';
- _object_row.prefix := p_prefix;
- _object_row.name := _bucket_row.name;
- _object_row.mime_type := 'inode/directory';
- _object_row.size := (SELECT SUM(COALESCE((o.metadata->>'size')::int, 0)) FROM storage.objects as o WHERE o.bucket_id = _bucket_row.id);
- _nodes := _nodes || ARRAY[to_jsonb(_object_row)];
- END LOOP;
- END IF;
-
-
- _bucket_id := ARRAY_TO_STRING(p_prefix[:1], '');
- _path := ARRAY_TO_STRING(p_prefix[2:], '/');
- _depth := array_length(p_prefix[2:], 1) + 1;
-
- IF _depth IS NULL THEN
- _depth := 1;
- END IF;
-
- RAISE WARNING 'get_node_children: p_prefix %, _bucket_id: %, _path: %, _depth: %', p_prefix, _bucket_id, _path, _depth;
-
- FOR _search_row IN
- SELECT * FROM storage.search(_path, _bucket_id, 100, _depth)
- LOOP
- RAISE WARNING 'get_node_children: _search_row %', _search_row.name;
-
- IF _search_row.id IS NULL THEN
-
- RAISE WARNING 'xxxx: xxx %', ARRAY[_bucket_id] || string_to_array(_path,'/')|| string_to_array(_search_row.name,'/');
-
- _object_row.type := 'TREE';
- _object_row.id := elwood.create_node_id_for_tree(ARRAY[_bucket_id] || string_to_array(_path,'/')|| string_to_array(_search_row.name,'/'));
- ELSE
- _object_row.type := 'BLOB';
- _object_row.id := elwood.create_node_id(_node_type, _bucket_id, _search_row.id);
- END IF;
-
- IF _search_row.name != '.emptyFolderPlaceholder' THEN
- _object_row.prefix := p_prefix;
- _object_row.name := _search_row.name;
- _object_row.mime_type := _search_row.metadata->>'mimetype';
- _object_row.size := COALESCE((_search_row.metadata->>'size')::int, 0);
- _nodes := _nodes || to_jsonb(_object_row);
- END IF;
- END LOOP;
-
- RAISE WARNING 'get_node_children: length _depth: %', array_length(_nodes, 1);
-
- return _nodes;
-END;
-$$;
diff --git a/supabase/migrations/20240228151359_get_node_leaf.sql b/supabase/migrations/20240228151359_get_node_leaf.sql
deleted file mode 100644
index 6573a27..0000000
--- a/supabase/migrations/20240228151359_get_node_leaf.sql
+++ /dev/null
@@ -1,77 +0,0 @@
-
-
-DROP FUNCTION IF EXISTS elwood.get_node_leaf(text[]);
-CREATE OR REPLACE FUNCTION elwood.get_node_leaf(
- "p_path" text[]
-) RETURNS elwood.get_node_leaf_result LANGUAGE PLPGSQL
-AS $$
-DECLARE
- _path_length int;
- _name text;
- _path text;
- _prefix text[];
- _bucket_id text;
- _node public.elwood_node;
- _bucket_row storage.buckets;
- _object_row storage.objects;
- _result elwood.get_node_leaf_result;
-BEGIN
- _path_length := array_length(p_path, 1);
-
- -- no path means root
- IF _path_length IS NULL THEN
- _node.id = 'root';
- _node.type = 'BUCKET';
- _node.prefix = ARRAY[]::text[];
- END IF;
-
- -- single path means bucket
- IF _path_length = 1 THEN
- SELECT * INTO _bucket_row FROM storage.buckets WHERE "name" = p_path[1];
-
- IF _bucket_row.id IS NULL THEN
- return null;
- END IF;
-
- _node.id = elwood.create_node_id('BUCKET', _bucket_row.name, null);
- _node.type = 'BUCKET';
- _node.prefix = ARRAY[]::text[];
- _node.name = _bucket_row.name;
- _node.mime_type = 'inode/directory';
- END IF;
-
- -- multiple paths means object
- IF _path_length > 1 THEN
- _bucket_id := ARRAY_TO_STRING(p_path[:1], '');
- _prefix := p_path[1:_path_length-1];
- _path := ARRAY_TO_STRING(p_path[2:], '/');
- _name := p_path[_path_length];
-
- SELECT * INTO _object_row FROM storage.objects WHERE "bucket_id" = _bucket_id AND "name" = _path;
-
- IF _object_row.id IS NULL THEN
- _node.id = elwood.create_node_id_for_tree(p_path);
- _node.type = 'TREE';
- _node.prefix = _prefix;
- _node.name = _name;
- _node.mime_type = 'inode/directory';
- END IF;
-
- IF _object_row.id IS NOT NULL THEN
- _node.id = elwood.create_node_id('BLOB', _bucket_id, _object_row.id);
- _node.type = 'BLOB';
- _node.prefix = _prefix;
- _node.name = _name;
- _node.mime_type = _object_row.metadata->>'mimetype';
- _node.size = COALESCE((_object_row.metadata->>'size')::int, 0);
- END IF;
- END IF;
-
- _result.node := _node;
-
- -- give it back
- return _result;
-
-END;
-$$;
-
diff --git a/supabase/migrations/20240301183729__public_get_node.sql b/supabase/migrations/20240301183729__public_get_node.sql
deleted file mode 100644
index ac81514..0000000
--- a/supabase/migrations/20240301183729__public_get_node.sql
+++ /dev/null
@@ -1,54 +0,0 @@
--- @@
--- GET_NODE
--- get a node with all the other info you might need for that node
-drop function if exists public.elwood_get_node(text[], int, int);
-create or replace function public.elwood_get_node(
- p_path text[],
- p_limit int default 100,
- p_offset int default 0
-)
-returns jsonb
-as $$
-DECLARE
- _node_leaf elwood.get_node_leaf_result;
- _parent_leaf elwood.get_node_leaf_result;
- _node public.elwood_node;
- _prefix text[];
- _child_row jsonb;
- _children jsonb[] = ARRAY[]::jsonb[];
- _key_children text[] = ARRAY[]::text[];
-BEGIN
- -- get this node leaf
- _node_leaf := elwood.get_node_leaf(p_path);
- _node = _node_leaf.node;
-
- -- no node means stop
- IF _node.id IS NULL THEN
- return null;
- END IF;
-
- -- if this isn't root, get the parent
- IF _node.id != 'root' THEN
- SELECT * INTO _parent_leaf FROM elwood.get_node_leaf(p_path[1:array_length(p_path, 1)-1]);
- _prefix := _node.prefix || ARRAY[_node.name]::text[];
- END IF;
-
- IF _node.type = 'TREE' OR _node.type = 'BUCKET' THEN
- _children := elwood.get_node_children(_prefix);
-
- FOREACH _child_row IN ARRAY _children LOOP
- IF (SELECT _child_row->>'name' = ANY(ARRAY['readme.md']::text[])) THEN
- _key_children := _key_children || ARRAY[_child_row->>'name'];
- END IF;
- END LOOP;
- END IF;
-
- return jsonb_build_object(
- 'node', to_jsonb(_node),
- 'parent', to_jsonb(_parent_leaf.node),
- 'children', _children,
- 'key_children', _key_children
- );
-END;
-$$
-language plpgsql;
diff --git a/supabase/migrations/20240301183739__public_get_node_tree.sql b/supabase/migrations/20240301183739__public_get_node_tree.sql
deleted file mode 100644
index 63f1cf4..0000000
--- a/supabase/migrations/20240301183739__public_get_node_tree.sql
+++ /dev/null
@@ -1,62 +0,0 @@
-drop function if exists public.elwood_get_node_tree(text[]);
-create or replace function public.elwood_get_node_tree(p_path text[])
-returns jsonb
-as $$
-DECLARE
- _id text;
- _bucket_id text;
- _path text;
- _part text;
- _result jsonb[];
- _row jsonb;
- _node_row jsonb;
- _row_path text[];
- _node_type public.elwood_node_type;
- _expanded_ids text[];
- _path_id text;
- _leaf elwood.get_node_leaf_result;
- _leaf_node public.elwood_node;
- _root_id text := 'root';
-BEGIN
-
- IF array_length(p_path, 1) > 0 THEN
- _root_id := elwood.create_node_id('BUCKET', array_to_string(p_path[0:1],'')::text, null);
- END IF;
-
- FOREACH _part IN ARRAY p_path LOOP
- _row_path := _row_path || _part;
- _node_row := elwood_get_node(_row_path);
-
- -- if this is a blob, it will be added when we loop to the parent
- -- so don't push it to the row now
- IF (_node_row->'node')::jsonb->>'type' != 'BLOB' THEN
-
- _result := _result || jsonb_build_object(
- 'id', (_node_row->>'node')::jsonb->>'id',
- 'node', _node_row->'node',
- 'parent', (_node_row->'parent')::jsonb->>'id'
- );
-
- _expanded_ids := _expanded_ids || ARRAY[(_node_row->>'node')::jsonb->>'id'::text];
-
- FOR _row IN SELECT jsonb_array_elements FROM jsonb_array_elements(_node_row->'children') LOOP
- _result := _result || jsonb_build_object(
- 'id', _row->>'id',
- 'node', _row,
- 'parent', (_node_row->>'node')::jsonb->>'id'
- );
- END LOOP;
- END IF;
-
- END LOOP;
-
-
- RETURN jsonb_build_object(
- 'rootNodeId', _root_id,
- 'expandedIds', _expanded_ids,
- 'tree', _result
- );
-
-END;
-$$
-language plpgsql;
diff --git a/supabase/migrations/20240515173522_elwood.sql b/supabase/migrations/20240515173522_elwood.sql
new file mode 100644
index 0000000..0a353b8
--- /dev/null
+++ b/supabase/migrations/20240515173522_elwood.sql
@@ -0,0 +1,32 @@
+/*
+Requires:
+ - pg_tle: https://github.com/aws/pg_tle
+ - pgsql-http: https://github.com/pramsey/pgsql-http
+*/
+create extension if not exists http with schema extensions;
+create extension if not exists pg_tle;
+create extension if not exists vector schema extensions;
+drop extension if exists "elwood-supabase";
+select pgtle.uninstall_extension_if_exists('elwood-supabase');
+select
+ pgtle.install_extension(
+ 'elwood-supabase',
+ resp.contents ->> 'version',
+ 'Elwood Supabase Database',
+ resp.contents ->> 'sql'
+ )
+from http(
+ (
+ 'GET',
+ 'https://elwood.software/db/latest.json',
+ array[]::http_header[],
+ null,
+ null
+ )
+) x,
+lateral (
+ select
+ ((row_to_json(x) -> 'content') #>> '{}')::json
+) resp(contents);
+
+create extension "elwood-supabase";