Compare commits

..

37 Commits

Author SHA1 Message Date
a7eed530a3 cleanup 2023-10-17 09:42:20 +02:00
af15eb1d0b Fixing a Paperless bug 2023-10-17 09:42:09 +02:00
19674310e7 added dynamic getMessages query 2023-09-21 22:03:40 +02:00
5e3631c49d Added Email, Contacts and ChatGPT 2023-09-21 20:45:49 +02:00
0312f50d51 Added Paperless display POC 2023-09-20 20:56:27 +02:00
804a618053 save 2023-09-20 13:10:16 +02:00
5041a3c5a3 Displaying files as well. 2023-09-13 14:41:27 +02:00
91b351ef2e Added Directus System Endpoint and displaying files 2023-09-13 13:49:56 +02:00
b1185b44dd Added basic send transaction POC 2023-09-08 14:34:04 +02:00
fd534bfae7 Added basic bookmarks 2023-09-06 20:01:39 +02:00
d55e4bb203 Added Banking View, displaying balance and transactions 2023-09-06 19:10:41 +02:00
563e9945c3 further improvement of UI 2023-09-06 15:49:50 +02:00
aaf4a7461a Major UI upgrade 2023-09-06 15:08:55 +02:00
102f4fa855 Added dynamic AccessControlConditions interface 2023-09-04 17:18:35 +02:00
35b3167509 cleanup 2023-09-04 12:24:24 +02:00
53e68aa362 wired up JWT with wundergraph auth 2023-09-04 11:59:58 +02:00
8a4ec6cc73 finished proof of concept jwt LIT-ACC retrieval 2023-09-04 10:21:51 +02:00
4ae46a2a75 Some small Ui updates 2023-09-04 09:07:57 +02:00
c13047e281 some minor ux impros 2023-09-02 14:10:46 +02:00
61ba4d0e4f Re-adding signing capabilities and adding better UI 2023-09-02 13:26:05 +02:00
9b9ac7d89e added Tauri native desktop app support 2023-09-02 11:31:05 +02:00
293180c2b5 major refactoring adding and finishing xstate signin flow 2023-09-01 17:38:22 +02:00
24b83ec99e temporary refactor of sign in stateflow 2023-09-01 14:11:27 +02:00
879f7aad3f Work in progress refactor 2023-08-31 19:08:35 +02:00
efc2b41f9e updated UI of login flow 2023-08-31 12:54:49 +02:00
7652938c74 Added basic Tailwind styling and fixed a active session bug 2023-08-31 11:13:53 +02:00
b8012cf9fd Refactored GoogleAuth Signup Flow and added Clear Session 2023-08-31 10:47:08 +02:00
978e438854 external triggering of SignRequest 2023-08-30 19:54:57 +02:00
9aa3bfc0d2 Added verify signature api service 2023-08-30 12:22:33 +02:00
042f9209ed non working LIT jwt 2023-08-30 09:04:58 +02:00
95566d6f36 fixing signer bug 2023-08-29 14:17:19 +02:00
7c9e1129c7 further improved the session logic 2023-08-29 14:02:14 +02:00
a749abe7ed Fixed session UI 2023-08-29 13:31:09 +02:00
370ff822d0 Refactoring and cleanup of Signer 2023-08-28 12:18:57 +02:00
e2a3c680d1 added signing prompts 2023-08-28 10:39:10 +02:00
405c564880 added session persistance 2023-08-28 10:19:42 +02:00
7803294d6f Added Google LIT protocol web3 auth 2023-08-28 08:48:59 +02:00
106 changed files with 22523 additions and 8449 deletions

View File

@ -1,6 +0,0 @@
query Continents {
countries_continents {
name
code
}
}

View File

@ -1,7 +0,0 @@
query Countries($filter: countries_CountryFilterInput) {
countries_countries(filter: $filter) {
code
name
capital
}
}

View File

@ -1,6 +0,0 @@
query Dragons {
spacex_dragons {
name
active
}
}

View File

@ -0,0 +1,8 @@
query {
system_db_files {
id
title
type
filename_disk
}
}

View File

@ -0,0 +1,11 @@
query {
system_db_users_me {
id
first_name
last_name
avatar {
id
}
external_identifier
}
}

View File

@ -1,5 +1,5 @@
query Projects {
directus_projects {
query {
db_projects {
id
text
}

View File

@ -1,5 +1,5 @@
query Todos {
directus_todos {
query {
db_todos {
id
task
user_created {

View File

@ -0,0 +1,27 @@
import { createOperation, z } from '../generated/wundergraph.factory';
import axios from 'axios';
export default createOperation.query({
input: z.object({
address: z.string(),
}),
handler: async ({ input }) => {
console.log('Making request with input:', input);
const { data } = await axios.get('https://api.gnosisscan.io/api', {
params: {
module: 'account',
action: 'balance',
address: input.address,
tag: 'latest',
apikey: process.env.GNOSISSCAN_API,
},
timeout: 10000,
});
console.log('Received response:', data);
return {
balance: parseFloat(data.result),
};
},
});

View File

@ -0,0 +1,8 @@
query {
db_bookmarks {
id
name
url
tags
}
}

View File

@ -0,0 +1,20 @@
// .wundergraph/operations/getChatwootContacts.ts
import { createOperation, z } from '../generated/wundergraph.factory';
import axios from 'axios';
export default createOperation.query({
input: z.object({
}),
handler: async () => {
console.log('Making request to Chatwoot API');
const { data } = await axios.get(`https://chatwoot.andert.me/api/v1/accounts/1/contacts`, {
headers: {
api_access_token: process.env.CHATWOOT_API_ACCESS_TOKEN
},
});
return data;
},
});

View File

@ -0,0 +1,19 @@
// .wundergraph/operations/getChatwootConversations.ts
import { createOperation, z } from '../generated/wundergraph.factory';
import axios from 'axios';
export default createOperation.query({
input: z.object({}),
handler: async () => {
console.log('Making request to Chatwoot API');
const { data } = await axios.get('https://chatwoot.andert.me/api/v1/accounts/1/conversations?status=open&sort_by=last_activity_at', {
headers: {
api_access_token: process.env.CHATWOOT_API_ACCESS_TOKEN
},
});
return data;
},
});

View File

@ -0,0 +1,20 @@
// .wundergraph/operations/getChatwootMessages.ts
import { createOperation, z } from '../generated/wundergraph.factory';
import axios from 'axios';
export default createOperation.query({
input: z.object({
conversationId: z.string(),
}),
handler: async ({ input }) => {
console.log('Making request to Chatwoot API');
const { data } = await axios.get(`https://chatwoot.andert.me/api/v1/accounts/1/conversations/${input.conversationId}/messages`, {
headers: {
api_access_token: process.env.CHATWOOT_API_ACCESS_TOKEN
},
});
return data;
},
});

View File

@ -0,0 +1,70 @@
// .wundergraph/operations/getPaperless.ts
import { createOperation, z } from '../generated/wundergraph.factory';
import axios from 'axios';
export default createOperation.query({
input: z.object({}),
handler: async () => {
console.log('Making request to Paperless API');
const { data } = await axios.get('https://paperless.andert.me/api/documents/', {
headers: {
Authorization: process.env.PAPERLESS_TOKEN,
},
});
// Add download link, thumbnail link, preview link, PDF data, and metadata to each document
const documentsWithLinksDataAndMetadata = await Promise.all(data.results.map(async doc => {
const response = await axios.get(`https://paperless.andert.me/api/documents/${doc.id}/preview/`, {
responseType: 'arraybuffer',
headers: {
Authorization: process.env.PAPERLESS_TOKEN,
},
});
const pdfData = Buffer.from(response.data, 'binary').toString('base64');
let correspondent = null;
if (doc.correspondent) {
const correspondentResponse = await axios.get(`https://paperless.andert.me/api/correspondents/${doc.correspondent}/`, {
headers: {
Authorization: process.env.PAPERLESS_TOKEN,
},
});
correspondent = correspondentResponse.data;
}
let tags = [];
if (doc.tags) {
const tagsResponse = await Promise.all(doc.tags.map(tag => axios.get(`https://paperless.andert.me/api/tags/${tag}/`, {
headers: {
Authorization: process.env.PAPERLESS_TOKEN,
},
})));
tags = tagsResponse.map(response => response.data);
}
let documentType = null;
if (doc.document_type) {
const documentTypeResponse = await axios.get(`https://paperless.andert.me/api/document_types/${doc.document_type}/`, {
headers: {
Authorization: process.env.PAPERLESS_TOKEN,
},
});
documentType = documentTypeResponse.data;
}
return {
...doc,
downloadLink: `https://paperless.andert.me/api/documents/${doc.id}/download/`,
thumbnailLink: `https://paperless.andert.me/api/documents/${doc.id}/thumb/`,
previewLink: `https://paperless.andert.me/api/documents/${doc.id}/preview/`,
pdfData,
correspondent,
tags,
documentType,
};
}));
return documentsWithLinksDataAndMetadata;
},
});

View File

@ -0,0 +1,27 @@
import { createOperation, z } from '../generated/wundergraph.factory';
import axios from 'axios';
export default createOperation.query({
input: z.object({
address: z.string(),
}),
handler: async ({ input }) => {
const { data } = await axios.get('https://api.gnosisscan.io/api', {
params: {
module: 'account',
action: 'txlist',
address: input.address,
startblock: 0,
endblock: 'latest',
sort: 'desc',
apikey: process.env.GNOSISSCAN_API,
},
});
return {
transactions: data.result.map(transaction => ({
...transaction,
timestamp: new Date(transaction.timeStamp * 1000).toISOString(),
})),
};
},
});

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,9 @@ type Query {
projects(filter: projects_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): [projects!]!
projects_by_id(id: ID!): projects
projects_aggregated(groupBy: [String], filter: projects_filter, limit: Int, offset: Int, page: Int, search: String, sort: [String]): [projects_aggregated!]!
bookmarks(filter: bookmarks_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): [bookmarks!]!
bookmarks_by_id(id: ID!): bookmarks
bookmarks_aggregated(groupBy: [String], filter: bookmarks_filter, limit: Int, offset: Int, page: Int, search: String, sort: [String]): [bookmarks_aggregated!]!
todos(filter: todos_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): [todos!]!
todos_by_id(id: ID!): todos
todos_aggregated(groupBy: [String], filter: todos_filter, limit: Int, offset: Int, page: Int, search: String, sort: [String]): [todos_aggregated!]!
@ -10,23 +13,29 @@ type Query {
type Mutation {
create_projects_items(filter: projects_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String, data: [create_projects_input!]): [projects!]!
create_projects_item(data: create_projects_input!): projects
create_bookmarks_items(filter: bookmarks_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String, data: [create_bookmarks_input!]): [bookmarks!]!
create_bookmarks_item(data: create_bookmarks_input!): bookmarks
create_todos_items(filter: todos_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String, data: [create_todos_input!]): [todos!]!
create_todos_item(data: create_todos_input!): todos
update_projects_items(filter: projects_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String, ids: [ID]!, data: update_projects_input!): [projects!]!
update_projects_batch(filter: projects_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String, data: [update_projects_input!]): [projects!]!
update_projects_item(id: ID!, data: update_projects_input!): projects
update_bookmarks_items(filter: bookmarks_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String, ids: [ID]!, data: update_bookmarks_input!): [bookmarks!]!
update_bookmarks_batch(filter: bookmarks_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String, data: [update_bookmarks_input!]): [bookmarks!]!
update_bookmarks_item(id: ID!, data: update_bookmarks_input!): bookmarks
update_todos_items(filter: todos_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String, ids: [ID]!, data: update_todos_input!): [todos!]!
update_todos_batch(filter: todos_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String, data: [update_todos_input!]): [todos!]!
update_todos_item(id: ID!, data: update_todos_input!): todos
delete_projects_items(ids: [ID]!): delete_many
delete_projects_item(id: ID!): delete_one
delete_bookmarks_items(ids: [ID]!): delete_many
delete_bookmarks_item(id: ID!): delete_one
delete_todos_items(ids: [ID]!): delete_many
delete_todos_item(id: ID!): delete_one
}
type Subscription {
projects_mutated(event: EventEnum): projects_mutated
todos_mutated(event: EventEnum): todos_mutated
directus_dashboards_mutated(event: EventEnum): directus_dashboards_mutated
directus_activity_mutated(event: EventEnum): directus_activity_mutated
directus_notifications_mutated(event: EventEnum): directus_notifications_mutated
@ -44,6 +53,8 @@ type Subscription {
directus_users_mutated(event: EventEnum): directus_users_mutated
directus_shares_mutated(event: EventEnum): directus_shares_mutated
directus_webhooks_mutated(event: EventEnum): directus_webhooks_mutated
bookmarks_mutated(event: EventEnum): bookmarks_mutated
todos_mutated(event: EventEnum): todos_mutated
}
"""The `Boolean` scalar type represents `true` or `false`."""
@ -92,6 +103,58 @@ enum EventEnum {
delete
}
type bookmarks {
id: ID!
status: String
sort: Int
user_created(filter: directus_users_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_users
date_created: Date
date_created_func: datetime_functions
user_updated(filter: directus_users_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_users
date_updated: Date
date_updated_func: datetime_functions
url: String
name: String
tags: JSON
tags_func: count_functions
}
type bookmarks_aggregated {
group: JSON
countAll: Int
count: bookmarks_aggregated_count
countDistinct: bookmarks_aggregated_count
avg: bookmarks_aggregated_fields
sum: bookmarks_aggregated_fields
avgDistinct: bookmarks_aggregated_fields
sumDistinct: bookmarks_aggregated_fields
min: bookmarks_aggregated_fields
max: bookmarks_aggregated_fields
}
type bookmarks_aggregated_count {
id: Int
status: Int
sort: Int
user_created: Int
date_created: Int
user_updated: Int
date_updated: Int
url: Int
name: Int
tags: Int
}
type bookmarks_aggregated_fields {
sort: Float
}
type bookmarks_mutated {
key: ID!
event: EventEnum
data: bookmarks
}
type count_functions {
count: Int
}
@ -557,15 +620,16 @@ type projects_mutated {
type todos {
id: ID!
status: String
sort: Int
user_created(filter: directus_users_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_users
date_created: Date
date_created_func: datetime_functions
user_updated(filter: directus_users_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_users
date_updated: Date
date_updated_func: datetime_functions
enddate: Date
enddate_func: datetime_functions
task: String
type: String
}
type todos_aggregated {
@ -573,27 +637,17 @@ type todos_aggregated {
countAll: Int
count: todos_aggregated_count
countDistinct: todos_aggregated_count
avg: todos_aggregated_fields
sum: todos_aggregated_fields
avgDistinct: todos_aggregated_fields
sumDistinct: todos_aggregated_fields
min: todos_aggregated_fields
max: todos_aggregated_fields
}
type todos_aggregated_count {
id: Int
status: Int
sort: Int
user_created: Int
date_created: Int
user_updated: Int
date_updated: Int
enddate: Int
task: Int
}
type todos_aggregated_fields {
sort: Float
type: Int
}
type todos_mutated {
@ -602,6 +656,24 @@ type todos_mutated {
data: todos
}
input bookmarks_filter {
id: string_filter_operators
status: string_filter_operators
sort: number_filter_operators
user_created: directus_users_filter
date_created: date_filter_operators
date_created_func: datetime_function_filter_operators
user_updated: directus_users_filter
date_updated: date_filter_operators
date_updated_func: datetime_function_filter_operators
url: string_filter_operators
name: string_filter_operators
tags: string_filter_operators
tags_func: count_function_filter_operators
_and: [bookmarks_filter]
_or: [bookmarks_filter]
}
input boolean_filter_operators {
_eq: Boolean
_neq: Boolean
@ -613,6 +685,19 @@ input count_function_filter_operators {
count: number_filter_operators
}
input create_bookmarks_input {
id: ID
status: String
sort: Int
user_created: create_directus_users_input
date_created: Date
user_updated: create_directus_users_input
date_updated: Date
url: String
name: String
tags: JSON
}
input create_directus_files_input {
id: ID
storage: String!
@ -692,13 +777,13 @@ input create_projects_input {
input create_todos_input {
id: ID
status: String
sort: Int
user_created: create_directus_users_input
date_created: Date
user_updated: create_directus_users_input
date_updated: Date
enddate: Date
task: String
type: String
}
input date_filter_operators {
@ -979,19 +1064,33 @@ input string_filter_operators {
input todos_filter {
id: string_filter_operators
status: string_filter_operators
sort: number_filter_operators
user_created: directus_users_filter
date_created: date_filter_operators
date_created_func: datetime_function_filter_operators
user_updated: directus_users_filter
date_updated: date_filter_operators
date_updated_func: datetime_function_filter_operators
enddate: date_filter_operators
enddate_func: datetime_function_filter_operators
task: string_filter_operators
type: string_filter_operators
_and: [todos_filter]
_or: [todos_filter]
}
input update_bookmarks_input {
id: ID
status: String
sort: Int
user_created: update_directus_users_input
date_created: Date
user_updated: update_directus_users_input
date_updated: Date
url: String
name: String
tags: JSON
}
input update_directus_files_input {
id: ID
storage: String
@ -1071,11 +1170,11 @@ input update_projects_input {
input update_todos_input {
id: ID
status: String
sort: Int
user_created: update_directus_users_input
date_created: Date
user_updated: update_directus_users_input
date_updated: Date
enddate: Date
task: String
type: String
}

File diff suppressed because it is too large Load Diff

View File

@ -22,8 +22,16 @@ export async function fetchSchemas() {
}
});
// Fetch the GraphQL SDL schema
const { data: systemSchema } = await axios.get(`${serverUrl}/server/specs/graphql/system`, {
headers: {
'Authorization': process.env.DIRECTUS
}
});
// Save the schema to a file
fs.writeFileSync('./.wundergraph/schemas/directus.graphql', schema);
fs.writeFileSync('./.wundergraph/schemas/directus_system.graphql', systemSchema);
}
fetchSchemas().catch(console.error);

View File

@ -0,0 +1,181 @@
{
"openapi": "3.0.0",
"info": {
"version": "1.0.0",
"title": "JSON Placeholder API",
"description": "See https://jsonplaceholder.typicode.com/"
},
"servers": [
{
"url": "https://jsonplaceholder.typicode.com"
}
],
"paths": {
"/posts": {
"get": {
"description": "Returns all posts",
"tags": [
"Posts"
],
"operationId": "getPosts",
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PostsList"
}
}
}
}
}
}
},
"/users": {
"get": {
"description": "Returns all users",
"tags": [
"Users"
],
"operationId": "getUsers",
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserList"
}
}
}
}
}
}
},
"/users/{id}": {
"get": {
"description": "Returns a user by id",
"tags": [
"Users"
],
"operationId": "getUser",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"description": "The user id.",
"schema": {
"type": "integer",
"format": "int64"
}
}
],
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/User"
}
}
}
},
"404": {
"description": "User not found"
}
}
}
},
"/posts/{id}": {
"get": {
"description": "Returns a post by id",
"tags": [
"Posts"
],
"operationId": "getPost",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"description": "The user id.",
"schema": {
"type": "integer",
"format": "int64"
}
}
],
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Post"
}
}
}
},
"404": {
"description": "Post not found"
}
}
}
}
},
"components": {
"schemas": {
"PostsList": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Post"
}
},
"UserList": {
"type": "array",
"items": {
"$ref": "#/components/schemas/User"
}
},
"Post": {
"type": "object",
"required": [
"id",
"userId",
"title"
],
"properties": {
"id": {
"type": "integer"
},
"userId": {
"type": "integer"
},
"title": {
"type": "string"
}
}
},
"User": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"username": {
"type": "string"
},
"email": {
"type": "string"
}
}
}
}
}
}

View File

@ -7,28 +7,36 @@ import dotenv from 'dotenv';
dotenv.config();
const directusSchema = fs.readFileSync(path.join(path.resolve(), './schemas/directus.graphql'), 'utf8');
const directusSystemSchema = fs.readFileSync(path.join(path.resolve(), './schemas/directus_system.graphql'), 'utf8');
const countries = introspect.graphql({
apiNamespace: 'countries',
url: 'https://countries.trevorblades.com/',
});
const spaceX = introspect.graphql({
apiNamespace: 'spacex',
url: 'https://spacex-api.fly.dev/graphql/',
});
const directus = introspect.graphql({
apiNamespace: 'directus',
const db = introspect.graphql({
apiNamespace: 'db',
loadSchemaFromString: directusSchema,
url: 'https://directus.andert.me/graphql',
headers: (builder) => builder
.addStaticHeader('Authorization', new EnvironmentVariable('DIRECTUS', process.env.DIRECTUS))
});
const placeholder = introspect.openApiV2({
apiNamespace: 'placeholder',
source: {
kind: "file",
filePath: "./schemas/placeholder.json"
},
baseURL: 'https://jsonplaceholder.typicode.com',
});
const system_db = introspect.graphql({
apiNamespace: 'system_db',
loadSchemaFromString: directusSystemSchema,
url: 'https://directus.andert.me/graphql/system',
headers: (builder) => builder
.addStaticHeader('Authorization', new EnvironmentVariable('DIRECTUS', process.env.DIRECTUS))
});
// configureWunderGraph emits the configuration
configureWunderGraphApplication({
apis: [countries, spaceX, directus],
apis: [db, system_db, placeholder],
server,
operations,
generate: {
@ -59,12 +67,12 @@ configureWunderGraphApplication({
tokenBased: {
providers: [
{
userInfoEndpoint: 'http://localhost:3000/api/auth/session',
userInfoEndpoint: 'http://localhost:3000/server/wundergraph',
},
],
},
},
authorization: {
roles: ['admin'],
roles: ['owner'],
},
});

View File

@ -16,34 +16,65 @@
"devDependencies": {
"@fontsource/fira-mono": "^4.5.10",
"@neoconfetti/svelte": "^1.0.0",
"@skeletonlabs/skeleton": "^2.0.0",
"@skeletonlabs/tw-plugin": "^0.1.0",
"@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/adapter-static": "^2.0.3",
"@sveltejs/kit": "^1.5.0",
"@tailwindcss/forms": "^0.5.6",
"@tauri-apps/cli": "2.0.0-alpha.14",
"@types/cookie": "^0.5.1",
"@types/js-cookie": "^3.0.3",
"@types/jsonwebtoken": "^9.0.2",
"@types/node": "^20.5.8",
"autoprefixer": "^10.4.15",
"concurrently": "^7.6.0",
"dayjs": "^1.11.9",
"postcss": "^8.4.29",
"svelte": "^3.54.0",
"svelte-check": "^3.0.1",
"svelte-time": "^0.8.0",
"tailwindcss": "^3.3.3",
"tslib": "^2.4.1",
"typescript": "^5.0.0",
"vite": "^4.2.0",
"wait-on": "^7.0.1"
},
"dependencies": {
"@floating-ui/dom": "^1.5.1",
"@graphql-tools/graphql-file-loader": "^8.0.0",
"@graphql-tools/load": "^8.0.0",
"@iconify/icons-ion": "^1.2.10",
"@iconify/svelte": "^3.1.4",
"@lit-protocol/auth-helpers": "^2.2.50",
"@lit-protocol/constants": "^2.2.50",
"@lit-protocol/lit-auth-client": "^2.2.50",
"@lit-protocol/lit-node-client": "^2.2.50",
"@lit-protocol/pkp-client": "^2.2.50",
"@lit-protocol/pkp-ethers": "^2.2.54",
"@lit-protocol/types": "^2.2.50",
"@tanstack/svelte-query": "^4.29.1",
"@wagmi/core": "^1.3.9",
"@wundergraph/sdk": "^0.174.5",
"@wundergraph/svelte-query": "^0.3.10",
"@xstate/svelte": "^2.1.0",
"ai": "^2.2.13",
"axios": "^1.4.0",
"cookie": "^0.5.0",
"dotenv": "^16.3.1",
"ethers": "^6.7.1",
"graphql": "^16.8.0",
"js-cookie": "^3.0.5",
"jsonwebtoken": "^9.0.1",
"jwks-rsa": "^3.0.1",
"node-jose": "^2.2.0",
"openai": "^4.8.0",
"path": "^0.12.7",
"url": "^0.11.1"
"sqlite3": "^5.1.6",
"svelte-kit-cookie-session": "^4.0.0",
"svelte-pdf-simple": "^2.0.0",
"url": "^0.11.1",
"xstate": "^4.38.2"
},
"type": "module"
}

8247
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

6
postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

3
src-tauri/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Generated by Cargo
# will have compiled files and executables
/target/

3510
src-tauri/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

26
src-tauri/Cargo.toml Normal file
View File

@ -0,0 +1,26 @@
[package]
name = "app"
version = "0.1.0"
description = "A Tauri App"
authors = ["you"]
license = ""
repository = ""
default-run = "app"
edition = "2021"
rust-version = "1.60"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "1.4.0", features = [] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "1.4.0", features = [] }
[features]
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.
# If you use cargo directly instead of tauri's cli you can use this feature flag to switch between tauri's `dev` and `build` modes.
# DO NOT REMOVE!!
custom-protocol = [ "tauri/custom-protocol" ]

3
src-tauri/build.rs Normal file
View File

@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}

BIN
src-tauri/icons/128x128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
src-tauri/icons/32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
src-tauri/icons/icon.icns Normal file

Binary file not shown.

BIN
src-tauri/icons/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
src-tauri/icons/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

8
src-tauri/src/main.rs Normal file
View File

@ -0,0 +1,8 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
tauri::Builder::default()
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

66
src-tauri/tauri.conf.json Normal file
View File

@ -0,0 +1,66 @@
{
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
"build": {
"beforeBuildCommand": "pnpm run build",
"beforeDevCommand": "pnpm run dev",
"devPath": "http://localhost:3001",
"distDir": "../build"
},
"package": {
"productName": "°",
"version": "0.1.0"
},
"tauri": {
"allowlist": {
"all": false
},
"bundle": {
"active": true,
"category": "DeveloperTool",
"copyright": "",
"deb": {
"depends": []
},
"externalBin": [],
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"identifier": "com.tauri.dev",
"longDescription": "",
"macOS": {
"entitlements": null,
"exceptionDomain": "",
"frameworks": [],
"providerShortName": null,
"signingIdentity": null
},
"resources": [],
"shortDescription": "",
"targets": "all",
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": ""
}
},
"security": {
"csp": null
},
"updater": {
"active": false
},
"windows": [
{
"fullscreen": false,
"height": 600,
"resizable": true,
"title": "wundergraph-sveltekit°",
"width": 800
}
]
}
}

3
src/app.css Normal file
View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

15
src/app.d.ts vendored
View File

@ -1,10 +1,21 @@
import type { Session } from 'svelte-kit-cookie-session';
type SessionData = {
views: number;
};
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
interface Locals {
session: Session<SessionData>;
}
interface PageData {
// can add any properties here, return it from your root layout
session: SessionData;
}
// interface Platform {}
}
}

View File

@ -6,7 +6,15 @@
<meta name="viewport" content="width=device-width" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<script>
global = window;
window.process = {
env: {
NODE_ENV: 'development'
}
}
</script>
<body data-theme="wintry" data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

5
src/hooks.server.ts Normal file
View File

@ -0,0 +1,5 @@
import { handleSession } from 'svelte-kit-cookie-session';
export const handle = handleSession({
secret: 'SOME_COMPLEX_SECRET_32_CHARSLONG'
});

74
src/lib/ACCs.svelte Normal file
View File

@ -0,0 +1,74 @@
<script lang="ts">
import Cookies from "js-cookie";
import {
createACC,
deleteACC,
} from "./services/mutateAccessControlConditions.ts";
let signingConditions =
JSON.parse(localStorage.getItem("signingConditions")) || [];
let newParameter = ":userAddress";
let newComparator = "=";
let newValue = "";
async function handleCreateNewACC() {
await createACC(newParameter, newComparator, newValue);
signingConditions =
JSON.parse(localStorage.getItem("signingConditions")) || [];
Cookies.set("signingConditions", JSON.stringify(signingConditions));
}
async function handleDeleteACC(index) {
await deleteACC(index);
signingConditions =
JSON.parse(localStorage.getItem("signingConditions")) || [];
}
</script>
{#each signingConditions as condition, index (index)}
{#each condition.accs as acc}
<div
class="card"
style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;"
>
<div class="p-4">
<h2 class="text-xl">
<b>{condition.resourceId.baseUrl}{condition.resourceId.path}</b>
</h2>
<p>
{acc.parameters.join(", ")}
{acc.returnValueTest.comparator}
{acc.returnValueTest.value}
</p>
</div>
<button
on:click={() => handleDeleteACC(index)}
class="btn variant-filled-error mt-2">Delete ACC</button
>
</div>
{/each}
{/each}
<div class="mt-4 flex">
<input
bind:value={newParameter}
placeholder="Parameter"
class="input mr-2"
style="width: max-content; max-width: 125px;"
readonly
/>
<input
bind:value={newComparator}
placeholder="Comparator"
class="input mr-2"
style="width: max-content; max-width: 36px;"
readonly
/>
<input
bind:value={newValue}
placeholder="Value"
class="input mr-2 flex-grow"
/>
<button on:click={handleCreateNewACC} class="btn variant-filled flex-grow"
>Create New ACC</button
>
</div>

49
src/lib/Ai.svelte Normal file
View File

@ -0,0 +1,49 @@
<script>
import { useChat } from "ai/svelte";
import Icon from "@iconify/svelte";
const { input, handleSubmit, messages } = useChat();
</script>
<div class="chat-grid h-full p-4">
<ul class="overflow-auto flex flex-col p-4 text-sm">
{#each $messages as message}
<li class="message {message.role === 'user' ? 'user' : 'assistant'} p-2">
{message.content}
</li>
{/each}
</ul>
<div class="w-full">
<form on:submit={handleSubmit} class="flex items-center">
<input bind:value={$input} class="input flex-grow mr-4" type="text" />
<button class="btn-icon variant-filled-success" type="submit">
<div class="px-4">
<Icon icon="carbon:send-alt-filled" class="" width="24" height="24" />
</div>
</button>
</form>
</div>
</div>
<style>
.chat-grid {
display: grid;
grid-template-rows: 1fr auto;
height: 100%;
}
.message {
max-width: 80%;
padding: px;
margin: 10px;
border-radius: 10px;
}
.user {
align-self: flex-end;
background-color: #0d6efd;
color: white;
}
.assistant {
align-self: flex-start;
background-color: lightblue;
}
</style>

6
src/lib/IProvider.ts Normal file
View File

@ -0,0 +1,6 @@
export interface IProvider {
mintPKPThroughRelayer(authMethod: any): Promise<string>;
relay: {
pollRequestUntilTerminalState(txHash: string): Promise<any>;
};
}

17
src/lib/JWT.svelte Normal file
View File

@ -0,0 +1,17 @@
<script>
import { createJWT } from "$lib/services/createJWT";
import Cookies from "js-cookie";
let jwt = "";
async function handleCreateClick() {
jwt = await createJWT();
Cookies.set("token", jwt);
}
</script>
<div class="p-12">
<button on:click={handleCreateClick} class="btn variant-filled-primary"
>Wundergraph Login</button
>
</div>

View File

@ -0,0 +1,5 @@
<script>
export let json;
</script>
<pre>{JSON.stringify(json, null, 2)}</pre>

View File

@ -1,17 +0,0 @@
<script>
import Cookies from "js-cookie";
async function login() {
const response = await fetch("/api/auth", { method: "POST" });
if (!response.ok) {
alert("Login failed");
} else {
let { token } = await response.json();
Cookies.set("token", token);
alert(`Login Success: ${token}`);
console.log(token);
}
}
</script>
<button on:click={login}>Login</button>

14
src/lib/MailViewer.svelte Normal file
View File

@ -0,0 +1,14 @@
<script>
function shadowroot(node, { html }) {
node.attachShadow({ mode: "open" }).innerHTML = html;
return {
update({ html }) {
node.shadowRoot.innerHTML = html;
},
};
}
export let html = "<h1>Some custom HTML</h1>";
</script>
<div use:shadowroot={{ html }} />

62
src/lib/Signer.svelte Normal file
View File

@ -0,0 +1,62 @@
<script lang="ts">
import { signMessageWithPKP } from "$lib/services/signMessage";
import { walletState, signRequest } from "$lib/stores.js";
let currentPKP;
let sessionSigs;
let message;
let signature;
let status = "WAITING FOR SIGNATURE";
walletState.subscribe((value) => {
currentPKP = value.pkps[0];
sessionSigs = value.sessionSigs;
});
signRequest.subscribe((value) => {
message = value.messageToSign;
signature = value.signature;
});
async function signMessage() {
const result = await signMessageWithPKP(sessionSigs, currentPKP, message);
if (result.error) {
console.error(result.error);
} else {
(status = "SIGNED"),
signRequest.set({
messageToSign: message,
signature: result.messageSignature,
drawer: true,
});
}
}
function declineSign() {
signRequest.set({
messageToSign: {},
signature: null,
drawer: false,
});
}
</script>
<div class="flex flex-col items-center justify-center h-full space-y-4">
<p class="text-sm font-bold">{status}</p>
<p class="text-lg break-words max-w-2/3">{JSON.stringify(message)}</p>
{#if signature}
<p class="text-sm font-bold break-words">
Signature: {JSON.stringify(signature)}
</p>
{/if}
<div
class="absolute bottom-0 flex items-center justify-center w-full pb-4 space-x-4"
>
<button on:click={declineSign} class="btn variant-filled-error"
>Decline</button
>
<button on:click={signMessage} class="btn variant-filled-success"
>Sign</button
>
</div>
</div>

View File

@ -1,27 +0,0 @@
<script>
import { onMount } from "svelte";
import Cookies from "js-cookie";
let user = null;
onMount(async () => {
const token = Cookies.get("token");
const response = await fetch("/api/auth/session", {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (response.ok) {
user = await response.json();
} else {
console.error("Failed to fetch user data");
}
});
</script>
{#if user}
<p>Welcome, {user.name}!</p>
<p>Your roles: {user.roles.join(", ")}</p>
{:else}
<p>Loading...</p>
{/if}

126
src/lib/Wallet.svelte Normal file
View File

@ -0,0 +1,126 @@
<script>
import { useMachine } from "@xstate/svelte";
import walletMachine from "./machines/walletMachine";
import { onMount } from "svelte";
import Icon from "@iconify/svelte";
import { walletState, signRequest } from "./stores";
import {
signInWithGoogle,
startSignIn as startSignInService,
} from "./services/signInWithGoogle";
import { getDrawerStore } from "@skeletonlabs/skeleton";
import { goto } from "$app/navigation";
const { state, send } = useMachine(walletMachine);
const drawerStore = getDrawerStore();
let search = "";
$: walletState.set($state.context);
$: {
if ($state.context.pkps && $state.context.sessionSigs) {
localStorage.setItem(
"me",
JSON.stringify({
pkps: $state.context.pkps,
sessionSigs: $state.context.sessionSigs,
})
);
}
}
onMount(() => {
const me = JSON.parse(localStorage.getItem("me"));
if (me && me.pkps && me.sessionSigs) {
send({ type: "RELOAD", ...me });
}
});
async function startSignIn() {
startSignInService.set(true);
await signInWithGoogle();
}
function clearSession() {
send("LOGOUT");
}
function signRequestTrigger() {
signRequest.set({
status: "SIGN REQUEST",
messageToSign: { hello: "test" },
signature: null,
drawer: true,
});
}
$: if ($signRequest.drawer) {
const settings = { position: "bottom", id: "signMessage" };
drawerStore.open(settings);
} else {
drawerStore.close();
}
</script>
{#if $state.matches("sessionAvailable") || $state.matches("creatingSession") || $state.matches("signIn")}
{#if $state.matches("signIn")}
<div class="flex items-center justify-center pb-4">
<div class="flex flex-col items-center w-1/3">
<button
on:click={startSignIn}
class="btn variant-filled flex items-center justify-center py-2"
>
<span class="mr-2"><Icon icon="flat-color-icons:google" /></span>
<span>Sign in with Google</span>
</button>
</div>
</div>
{:else if $state.context.pkps}
<div class="flex flex-col items-center p-3 space-y-4">
<div class="flex items-center justify-between w-full space-x-4">
<div class="w-full h-full overflow-hidden grid grid-cols-6">
<aside class="col-span-1">
<a href="/me" on:click|preventDefault={() => goto("/me")}>
<div class="flex items-center space-x-2">
<Icon
icon="iconamoon:profile-circle-fill"
class="w-12 h-12 text-gray-500"
/>
<div>
<div class="text-sm">
<div class="font-semibold">Address</div>
{$state.context.pkps[0].ethAddress.substring(0, 8) + "..."}
</div>
</div>
</div>
</a>
</aside>
<div class="col-span-5 w-full">
<div class="flex justify-end space-x-4">
<button
on:click={signRequestTrigger}
type="button"
class="btn variant-filled">SignRequest</button
>
<button
type="button"
class="btn variant-filled"
on:click={clearSession}>Logout</button
>
</div>
</div>
</div>
</div>
</div>
{:else if $state.matches("sessionExpired")}
<div class="p-10 bg-white">
<p>Error creating session. Please try again.</p>
<pre>{JSON.stringify($state.context.error, null, 2)}</pre>
</div>
{/if}
{:else}
<div class="flex justify-center items-center w-full pb-4">
<div class="animate-spin">
<Icon icon="la:spinner" width="48" height="48" />
</div>
</div>
{/if}

View File

@ -0,0 +1,23 @@
import type { IProvider } from '$lib/services/provider/IProvider';
import { LitAccessControlConditionResource, LitAbility } from '@lit-protocol/auth-helpers';
export async function createLitSession(
provider: IProvider,
pkpPublicKey: string,
authMethod: any,
): Promise<any> {
const litResource = new LitAccessControlConditionResource('*');
return await provider.getSessionSigs({
pkpPublicKey,
authMethod,
sessionSigsParams: {
chain: 'xdai',
resourceAbilityRequests: [
{
resource: litResource,
ability: LitAbility.PKPSigning
}
]
}
});
}

View File

@ -1,16 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-3 -3 30 30">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M12 2C6.47715 2 2 6.47715 2 12C2 17.5229 6.47715 22 12 22C17.5229 22 22 17.5229 22 12C22 6.47715 17.5229 2 12 2ZM0 12C0 5.3726 5.3726 0 12 0C18.6274 0 24 5.3726 24 12C24 18.6274 18.6274 24 12 24C5.3726 24 0 18.6274 0 12Z"
fill="rgba(0,0,0,0.7)"
stroke="none"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M9.59162 22.7357C9.49492 22.6109 9.49492 21.4986 9.59162 19.399C8.55572 19.4347 7.90122 19.3628 7.62812 19.1833C7.21852 18.9139 6.80842 18.0833 6.44457 17.4979C6.08072 16.9125 5.27312 16.8199 4.94702 16.6891C4.62091 16.5582 4.53905 16.0247 5.84562 16.4282C7.15222 16.8316 7.21592 17.9303 7.62812 18.1872C8.04032 18.4441 9.02572 18.3317 9.47242 18.1259C9.91907 17.9201 9.88622 17.1538 9.96587 16.8503C10.0666 16.5669 9.71162 16.5041 9.70382 16.5018C9.26777 16.5018 6.97697 16.0036 6.34772 13.7852C5.71852 11.5669 6.52907 10.117 6.96147 9.49369C7.24972 9.07814 7.22422 8.19254 6.88497 6.83679C8.11677 6.67939 9.06732 7.06709 9.73672 7.99999C9.73737 8.00534 10.6143 7.47854 12.0001 7.47854C13.386 7.47854 13.8777 7.90764 14.2571 7.99999C14.6365 8.09234 14.94 6.36699 17.2834 6.83679C16.7942 7.79839 16.3844 8.99999 16.6972 9.49369C17.0099 9.98739 18.2372 11.5573 17.4833 13.7852C16.9807 15.2706 15.9927 16.1761 14.5192 16.5018C14.3502 16.5557 14.2658 16.6427 14.2658 16.7627C14.2658 16.9427 14.4942 16.9624 14.8233 17.8058C15.0426 18.368 15.0585 19.9739 14.8708 22.6234C14.3953 22.7445 14.0254 22.8257 13.7611 22.8673C13.2924 22.9409 12.7835 22.9822 12.2834 22.9982C11.7834 23.0141 11.6098 23.0123 10.9185 22.948C10.4577 22.9051 10.0154 22.8343 9.59162 22.7357Z"
fill="rgba(0,0,0,0.7)"
stroke="none"
/>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.1566,22.8189c-10.4-14.8851-30.94-19.2971-45.7914-9.8348L22.2825,29.6078A29.9234,29.9234,0,0,0,8.7639,49.6506a31.5136,31.5136,0,0,0,3.1076,20.2318A30.0061,30.0061,0,0,0,7.3953,81.0653a31.8886,31.8886,0,0,0,5.4473,24.1157c10.4022,14.8865,30.9423,19.2966,45.7914,9.8348L84.7167,98.3921A29.9177,29.9177,0,0,0,98.2353,78.3493,31.5263,31.5263,0,0,0,95.13,58.117a30,30,0,0,0,4.4743-11.1824,31.88,31.88,0,0,0-5.4473-24.1157" style="fill:#ff3e00"/><path d="M45.8171,106.5815A20.7182,20.7182,0,0,1,23.58,98.3389a19.1739,19.1739,0,0,1-3.2766-14.5025,18.1886,18.1886,0,0,1,.6233-2.4357l.4912-1.4978,1.3363.9815a33.6443,33.6443,0,0,0,10.203,5.0978l.9694.2941-.0893.9675a5.8474,5.8474,0,0,0,1.052,3.8781,6.2389,6.2389,0,0,0,6.6952,2.485,5.7449,5.7449,0,0,0,1.6021-.7041L69.27,76.281a5.4306,5.4306,0,0,0,2.4506-3.631,5.7948,5.7948,0,0,0-.9875-4.3712,6.2436,6.2436,0,0,0-6.6978-2.4864,5.7427,5.7427,0,0,0-1.6.7036l-9.9532,6.3449a19.0329,19.0329,0,0,1-5.2965,2.3259,20.7181,20.7181,0,0,1-22.2368-8.2427,19.1725,19.1725,0,0,1-3.2766-14.5024,17.9885,17.9885,0,0,1,8.13-12.0513L55.8833,23.7472a19.0038,19.0038,0,0,1,5.3-2.3287A20.7182,20.7182,0,0,1,83.42,29.6611a19.1739,19.1739,0,0,1,3.2766,14.5025,18.4,18.4,0,0,1-.6233,2.4357l-.4912,1.4978-1.3356-.98a33.6175,33.6175,0,0,0-10.2037-5.1l-.9694-.2942.0893-.9675a5.8588,5.8588,0,0,0-1.052-3.878,6.2389,6.2389,0,0,0-6.6952-2.485,5.7449,5.7449,0,0,0-1.6021.7041L37.73,51.719a5.4218,5.4218,0,0,0-2.4487,3.63,5.7862,5.7862,0,0,0,.9856,4.3717,6.2437,6.2437,0,0,0,6.6978,2.4864,5.7652,5.7652,0,0,0,1.602-.7041l9.9519-6.3425a18.978,18.978,0,0,1,5.2959-2.3278,20.7181,20.7181,0,0,1,22.2368,8.2427,19.1725,19.1725,0,0,1,3.2766,14.5024,17.9977,17.9977,0,0,1-8.13,12.0532L51.1167,104.2528a19.0038,19.0038,0,0,1-5.3,2.3287" style="fill:#fff"/></svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

View File

@ -0,0 +1,10 @@
<div class="py-6 px-12 w-full h-full">
<header>
<h3 class="text-xl font-bold mb-4 uppercase">
<slot name="header" />
</h3>
</header>
<main class="w-full h-full overflow-hidden">
<slot name="main" />
</main>
</div>

View File

@ -0,0 +1,116 @@
// src/lib/machines/walletMachine.ts
import { createMachine, assign } from 'xstate';
import { signInWithGoogle } from '../services/signInWithGoogle';
import { createSession } from '../services/createSession';
const walletMachine = createMachine({
id: 'wallet',
initial: 'signIn',
context: {
provider: null,
providerName: null,
authMethod: null,
pkps: [],
sessionSigs: null,
redirect: false
},
states: {
signIn: {
on: {
RELOAD: {
target: 'sessionAvailable',
actions: assign({
pkps: (_, event) => event.pkps,
sessionSigs: (_, event) => event.sessionSigs,
}),
},
},
invoke: {
src: 'signInWithGoogle',
onDone: {
target: 'authenticated',
actions: assign({
providerName: (_, event) => event.data.providerName,
provider: (_, event) => event.data.provider,
authMethod: (_, event) => event.data.authMethod,
}),
},
},
},
authenticated: {
invoke: {
src: async (context) => {
const pkps = await context.provider.fetchPKPsThroughRelayer(context.authMethod);
if (pkps.length === 0) {
const newPKP = await context.provider.mintPKP(context.authMethod);
pkps.push(newPKP);
}
return pkps;
},
onDone: {
target: 'creatingSession',
actions: [
assign({
pkps: (_, event) => event.data,
}),
],
},
onError: 'authenticated',
},
},
creatingSession: {
invoke: {
src: async (context) => {
const { pkps, sessionSigs } = await createSession(context.provider, context.authMethod, context.pkps);
return { pkps, sessionSigs };
},
onDone: {
target: 'sessionAvailable',
actions: [
assign({
pkps: (_, event) => event.data.pkps,
sessionSigs: (_, event) => event.data.sessionSigs,
}),
],
},
onError: {
target: 'sessionExpired',
actions: assign({
error: (_, event) => event.data,
}),
},
},
},
sessionAvailable: {
on: {
EXPIRED: {
target: 'sessionExpired',
cond: (context) => context.sessionSigs && Object.values(context.sessionSigs).every(sig => sig.expired)
},
LOGOUT: 'sessionExpired'
}
},
sessionExpired: {
entry: assign({
sessionSigs: null,
redirect: true
}),
after: {
0: {
target: 'signIn',
actions: () => {
localStorage.removeItem('me');
window.location.href = '/';
}
}
}
},
},
}, {
services: {
signInWithGoogle,
createSession
},
});
export default walletMachine;

18
src/lib/mintPkp.ts Normal file
View File

@ -0,0 +1,18 @@
import type { IRelayPKP } from '@lit-protocol/types';
import type { IProvider } from '$lib/IProvider';
export async function mintPkp(provider: IProvider, authMethod: any): Promise<IRelayPKP> {
const txHash: string = await provider.mintPKPThroughRelayer(authMethod);
const response = await provider.relay.pollRequestUntilTerminalState(txHash);
if (response.status !== 'Succeeded') {
throw new Error('Minting failed');
}
const newPKP: IRelayPKP = {
tokenId: response.pkpTokenId,
publicKey: response.pkpPublicKey,
ethAddress: response.pkpEthAddress
};
return newPKP;
}

8
src/lib/mockApps.js Normal file
View File

@ -0,0 +1,8 @@
export const mockApps = [
{ name: 'cloud' },
{ name: 'pass'},
{ name: 'directus' },
{ name: 'penpot'},
{ name: 'paperless'}
];

View File

@ -0,0 +1,57 @@
import { LitNodeClient } from "@lit-protocol/lit-node-client";
import type { AccsEVMParams } from "@lit-protocol/types";
export const createJWT = async () => {
const litNodeClient = new LitNodeClient({ litNetwork: "serrano" });
await litNodeClient.connect();
const me = JSON.parse(localStorage.getItem('me'));
if (!me || !me.sessionSigs) {
throw new Error('No sessionSigs found in local storage');
}
const resourceId = {
baseUrl: "https://localhost:3000/",
path: "wunderauth",
orgId: "°",
role: "owner",
extraData: ""
}
const sessionSigs = me.sessionSigs;
const unifiedAccessControlConditions: AccsEVMParams[] = [
{
conditionType: 'evmBasic',
contractAddress: '',
standardContractType: '',
chain: 'xdai',
method: '',
parameters: [
':userAddress',
],
returnValueTest: {
comparator: '=',
value: '0x4b975F10baf1153A5CC688B52d55809cd2d8BB57'
}
},
];
await litNodeClient.saveSigningCondition({
unifiedAccessControlConditions,
sessionSigs,
resourceId,
chain: "litSessionSign",
});
const jwt = await litNodeClient.getSignedToken({
unifiedAccessControlConditions,
chain: 'xdai',
sessionSigs,
resourceId
});
return jwt;
};

View File

@ -0,0 +1,25 @@
// src/lib/services/createSession.ts
import { createLitSession } from '$lib/createLitSession';
import type { IRelayPKP } from "@lit-protocol/types";
export const createSession = async (provider, authMethod, pkps: IRelayPKP[]) => {
try {
let currentPKP;
if (pkps.length === 0) {
currentPKP = await provider.mintPKP(authMethod);
pkps = [...pkps, currentPKP];
} else {
currentPKP = pkps[0];
}
const sessionSigs = await createLitSession(
provider,
currentPKP.publicKey,
authMethod,
);
return { pkps, sessionSigs };
} catch (error) {
throw new Error(`Failed to create session: ${error.message}`);
}
};

View File

@ -0,0 +1,66 @@
import { LitNodeClient } from "@lit-protocol/lit-node-client";
import type { AccsEVMParams } from "@lit-protocol/types";
export const createACC = async (newParameter, newComparator, newValue) => {
const litNodeClient = new LitNodeClient({ litNetwork: "serrano" });
await litNodeClient.connect();
const me = JSON.parse(localStorage.getItem('me'));
if (!me || !me.sessionSigs) {
throw new Error('No sessionSigs found in local storage');
}
const newACC = {
conditionType: "evmBasic",
contractAddress: "",
standardContractType: "",
chain: "xdai",
method: "",
parameters: [newParameter],
returnValueTest: {
comparator: newComparator,
value: newValue,
},
};
const resourceId = {
baseUrl: "https://localhost:3000",
path: "/server/wundergraph",
orgId: "°",
role: "owner",
extraData: "",
};
const sessionSigs = me.sessionSigs;
await litNodeClient.saveSigningCondition({
unifiedAccessControlConditions: [newACC],
sessionSigs,
resourceId,
chain: "litSessionSign",
});
const jwt = await litNodeClient.getSignedToken({
unifiedAccessControlConditions: [newACC],
chain: 'xdai',
sessionSigs,
resourceId
});
let signingConditions = JSON.parse(localStorage.getItem("signingConditions")) || [];
signingConditions = [
...signingConditions,
{
accs: [newACC],
resourceId,
jwt,
},
];
localStorage.setItem("signingConditions", JSON.stringify(signingConditions));
};
export const deleteACC = async (index) => {
let signingConditions = JSON.parse(localStorage.getItem("signingConditions")) || [];
signingConditions = signingConditions.filter((_, i) => i !== index);
localStorage.setItem("signingConditions", JSON.stringify(signingConditions));
};

View File

@ -0,0 +1,30 @@
import { PKPEthersWallet } from '@lit-protocol/pkp-ethers';
export async function sendTxWithPKPWallet(pkp, sessionSigs) {
const pkpWallet = new PKPEthersWallet({
controllerSessionSigs: sessionSigs,
pkpPubKey: pkp.publicKey,
rpc: "https://rpc.gnosischain.com/"
});
await pkpWallet.init();
const from = pkpWallet.address;
const to = '0x1A5cfC9EA11afb50011F847fb7dC07bA1e18b05A';
const value = BigInt(100000000000000000);
const gasLimit = 21000;
const tx = {
from,
to,
value,
gasLimit,
};
console.log('transaction created: ' + tx);
const signedTx = await pkpWallet.signTransaction(tx);
console.log('transaction signed: ' + signedTx);
await pkpWallet.sendTransaction(signedTx);
console.log('transaction sent');
}

View File

@ -0,0 +1,38 @@
// src/lib/services/signInWithGoogle.ts
import { connectProvider } from "$lib/setupLit";
import { isSignInRedirect, getProviderFromUrl } from "@lit-protocol/lit-auth-client";
import { writable } from 'svelte/store';
export let startSignIn = writable(false);
let providerName;
export const signInWithGoogle = async () => {
if (typeof window !== 'undefined') {
try {
let provider = await connectProvider();
if (isSignInRedirect("http://localhost:3000/")) {
providerName = getProviderFromUrl();
if (providerName) {
const authMethod = await provider.authenticate();
return { authMethod, provider, providerName };
}
} else {
let shouldStartSignIn = false;
startSignIn.subscribe(value => {
shouldStartSignIn = value;
});
if (!providerName && shouldStartSignIn) {
await provider.signIn();
}
}
} catch (err) {
console.error('Error during sign-in:', err);
throw err;
} finally {
startSignIn.set(false);
}
} else {
throw new Error("Cannot sign in with Google in a non-browser environment.");
}
};

View File

@ -0,0 +1,41 @@
// $lib/services/signMessage.ts
import { ethers } from "ethers";
export async function signMessageWithPKP(sessionSigs, currentPKP, messageToSign) {
try {
const jsonString = JSON.stringify(messageToSign);
const toSign = ethers.getBytes(ethers.hashMessage(jsonString));
const litActionCode = `
const go = async () => {
const sigShare = await LitActions.signEcdsa({ toSign, publicKey, sigName });
};
go();
`;
const litNodeClient = new LitNodeClient({ litNetwork: "serrano" });
await litNodeClient.connect();
const results = await litNodeClient.executeJs({
code: litActionCode,
sessionSigs: sessionSigs,
jsParams: {
toSign: toSign,
publicKey: currentPKP.publicKey,
sigName: "sig1",
},
});
const result = results.signatures["sig1"];
const messageSignature = ethers.Signature.from({
r: "0x" + result.r,
s: "0x" + result.s,
v: result.recid,
});
return { messageSignature };
} catch (err) {
console.error(err);
return { error: err };
}
}

View File

@ -0,0 +1,58 @@
import { configureChains, createConfig } from '@wagmi/core';
import { gnosis } from '@wagmi/core/chains';
import { publicProvider } from '@wagmi/core/providers/public';
import { InjectedConnector } from '@wagmi/core/connectors/injected';
import { jsonRpcProvider } from '@wagmi/core/providers/jsonRpc';
// const chronicleChain = {
// id: 175177,
// name: 'Chronicle',
// network: 'chronicle',
// nativeCurrency: {
// decimals: 18,
// name: 'Chronicle - Lit Protocol Testnet',
// symbol: 'LIT'
// },
// rpcUrls: {
// default: {
// http: ['https://chain-rpc.litprotocol.com/http']
// },
// public: {
// http: ['https://chain-rpc.litprotocol.com/http']
// }
// },
// blockExplorers: {
// default: {
// name: 'Chronicle - Lit Protocol Testnet',
// url: 'https://chain.litprotocol.com'
// }
// },
// testnet: true
// };
export function initChainProvider() {
const { chains, publicClient } = configureChains(
[gnosis],
[
jsonRpcProvider({
rpc: (chain) => ({ http: chain.rpcUrls.default.http[0] })
}),
jsonRpcProvider({
rpc: () => ({
http: `https://rpc.ankr.com/gnosis`,
wss: `wss://rpc.gnosischain.com/wss`
})
}),
publicProvider()
]
);
createConfig({
autoConnect: true,
connectors: [
new InjectedConnector({ chains }),
],
publicClient
});
}

30
src/lib/setupLit.ts Normal file
View File

@ -0,0 +1,30 @@
// litProviderSetup.ts
import {
LitAuthClient,
GoogleProvider,
BaseProvider,
} from '@lit-protocol/lit-auth-client';
import { ProviderType } from '@lit-protocol/constants';
import { LitNodeClient } from '@lit-protocol/lit-node-client';
let provider: BaseProvider | undefined;
export async function connectProvider() {
const litNodeClient = new LitNodeClient({
litNetwork: 'serrano',
debug: false
});
await litNodeClient.connect();
const litAuthClient = new LitAuthClient({
litRelayConfig: {
relayApiKey: 'test-api-key'
},
litNodeClient
});
provider = litAuthClient.initProvider<GoogleProvider>(ProviderType.Google);
return provider;
}

15
src/lib/stores.js Normal file
View File

@ -0,0 +1,15 @@
import { writable } from 'svelte/store';
export const redirectStore = writable(false);
export const walletState = writable(null);
export const signRequest = writable({
messageToSign: {},
signature: null,
drawer: false
});
export const googleSession = writable({
activeSession: false
});

View File

@ -1,19 +1,85 @@
<script lang="ts">
import "../app.css";
import { QueryClientProvider } from "@tanstack/svelte-query";
import type { LayoutData } from "./$types";
import { client } from "$lib/wundergraph";
import Cookies from "js-cookie";
import { onMount } from "svelte";
import { initChainProvider } from "$lib/setupChainProvider";
import { googleSession } from "$lib/stores.js";
import Wallet from "$lib/Wallet.svelte";
import { Drawer, initializeStores } from "@skeletonlabs/skeleton";
import { getDrawerStore } from "@skeletonlabs/skeleton";
import Signer from "$lib/Signer.svelte";
initializeStores();
const drawerStore = getDrawerStore();
let activeSession = false;
export let data: LayoutData;
const token = Cookies.get("token");
const signingConditionsCookie = Cookies.get("signingConditions");
let signingConditions = signingConditionsCookie
? JSON.parse(signingConditionsCookie)
: [];
let correctCondition = signingConditions
? signingConditions.find(
(condition) =>
condition.resourceId.baseUrl === "https://localhost:3000" &&
condition.resourceId.path === "/server/wundergraph" &&
condition.resourceId.role === "owner"
)
: null;
const token = correctCondition ? correctCondition.jwt : null;
googleSession.subscribe((value) => {
activeSession = value.activeSession;
});
onMount(() => {
initChainProvider();
});
// Set the Authorization header token
if (token) {
client.setAuthorizationToken(token);
}
</script>
<QueryClientProvider client={data.queryClient}>
<Drawer>
{#if $drawerStore.id === "signMessage"}
<Signer messageToSign={{ hello: "me" }} />
{:else}
<!-- (fallback contents) -->
{/if}</Drawer
>
<div class="grid h-screen grid-layout bg-color">
<QueryClientProvider client={data.queryClient} class="main">
<slot />
</QueryClientProvider>
<footer>
<Wallet />
</footer>
</div>
<style>
.bg-color {
background-color: #e6e7e1;
}
.grid-layout {
grid-template-areas:
"main "
"footer ";
grid-template-rows: 1fr auto;
}
.main {
grid-area: main;
}
footer {
grid-area: footer;
}
</style>

View File

@ -1,6 +1,8 @@
import { browser } from '$app/environment';
import { QueryClient } from '@tanstack/svelte-query';
import type { LayoutLoad } from './$types';
export const prerender = true
export const ssr = false
export const load: LayoutLoad = async () => {
const queryClient = new QueryClient({
@ -10,6 +12,5 @@ export const load: LayoutLoad = async () => {
},
},
});
return { queryClient };
};

View File

@ -0,0 +1,10 @@
import type { ServerLoad } from '@sveltejs/kit';
export const load: ServerLoad = async ({ locals }) => {
await locals.session.set({ myPKP: "hello1" });
return {
myPKP: locals.session.data.myPKP
};
};

View File

@ -1,24 +1,37 @@
<script lang="ts">
import { createQuery } from "../lib/wundergraph";
import Login from "$lib/Login.svelte";
import User from "$lib/User.svelte";
// import { signRequest } from "$lib/stores.js";
const projectsQuery = createQuery({
operationName: "Projects",
});
// function trigger() {
// signRequest.set({ json: { hello: "test" } });
// }
// import type { PageData } from "./$types";
// export let data: PageData;
</script>
<Login />
<User />
<div class="w-full h-full p-6 overflow-hidden">
<div class="w-full h-full overflow-hidden bg-white rounded-xl">
<section
class="flex flex-col items-center justify-center min-h-screen p-8 space-y-8 text-white bg-green-500"
>
<h2 class="text-4xl font-bold text-center">
Unleash Your Full Potential and Transform Your Life <br />
</h2>
<h1 class="text-6xl font-bold text-center">Become a Vision Architect</h1>
<br />
Projects
<div class="results">
{#if $projectsQuery.isLoading}
<p>Loading...</p>
{:else if $projectsQuery.error}
<pre>Error: {JSON.stringify($projectsQuery.error, null, 2)}</pre>
{:else}
<pre>{JSON.stringify($projectsQuery.data, null, 2)}</pre>
{/if}
<p class="text-xl text-center">
We are committed to creating an amazing life experience for every human
on the planet. Our mission is to foster a world where everyone can
thrive in abundance, excitement, and creativity. We envision a
sustainable green planet and future cities where innovation and nature
coexist harmoniously.
</p>
<h3 class="text-2xl">
Stand Up NOW, Break Free And Follow Your Passions
</h3>
</section>
</div>
</div>
<!-- <div class="bg-white">myPKP {data.myPKP}</div> -->
<!-- <button on:click={trigger}>Sign Request</button> -->

View File

@ -1,13 +0,0 @@
import { prefetchQuery } from '$lib/wundergraph';
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ parent }) => {
const { queryClient } = await parent();
await prefetchQuery(
{
operationName: 'Dragons',
},
queryClient
);
};

View File

@ -1,13 +0,0 @@
// src/routes/api/auth/+server.ts
import jwt from 'jsonwebtoken';
import { error } from '@sveltejs/kit';
const secretKey = 'mysecrettestkey';
export async function POST() {
const token = jwt.sign({ name: 'Samuel', loggedIn: true, roles: ['admin'] }, secretKey);
if (!token) {
throw error(400, 'No token created.');
}
return new Response(JSON.stringify({ token }), { status: 200 });
}

View File

@ -1,24 +0,0 @@
// src/routes/api/session/+server.ts
import jwt from 'jsonwebtoken';
import { error } from '@sveltejs/kit';
const secretKey = 'mysecrettestkey';
export async function GET({ request }) {
const authHeader = request.headers.get('Authorization');
if (!authHeader) {
throw error(401, 'No Authorization header provided.');
}
const token = authHeader.split(' ')[1];
if (!token) {
throw error(401, 'No token provided.');
}
try {
const user = jwt.verify(token, secretKey);
return new Response(JSON.stringify(user), { status: 200 });
} catch (err) {
throw error(401, 'Invalid token.');
}
}

View File

@ -0,0 +1,34 @@
import OpenAI from 'openai';
import { OpenAIStream, StreamingTextResponse } from 'ai';
import { env } from '$env/dynamic/private';
// You may want to replace the above with a static private env variable
// for dead-code elimination and build-time type-checking:
// import { OPENAI_API_KEY } from '$env/static/private'
import type { RequestHandler } from './$types';
// Create an OpenAI API client
const openai = new OpenAI({
apiKey: env.OPENAI_API_KEY || '',
});
export const POST = (async ({ request }) => {
// Extract the `prompt` from the body of the request
const { messages } = await request.json();
// Ask OpenAI for a streaming chat completion given the prompt
const response = await openai.chat.completions.create({
model: 'gpt-3.5-turbo',
stream: true,
messages: messages.map((message: any) => ({
content: message.content,
role: message.role,
})),
});
// Convert the response into a friendly text-stream
const stream = OpenAIStream(response);
// Respond with the stream
return new StreamingTextResponse(stream);
}) satisfies RequestHandler;

View File

@ -0,0 +1,95 @@
<script>
import Icon from "@iconify/svelte";
</script>
<div class="w-full h-full overflow-hidden grid grid-cols-6">
<aside class="col-span-1">
<nav class="list-nav p-2">
<h3 class="text-xl font-bold mb-4">MY HOME</h3>
<ul>
<li>
<a href="/me">
<Icon
icon="iconamoon:profile-circle-fill"
class="w-8 h-8 text-gray-500"
/>
Dashboard
</a>
</li>
<li>
<a href="/me/projects">
<Icon
icon="iconamoon:profile-circle-fill"
class="w-8 h-8 text-gray-500"
/>
My Projects
</a>
</li>
<li>
<a href="/me/contacts">
<Icon
icon="iconamoon:profile-circle-fill"
class="w-8 h-8 text-gray-500"
/>
Contacts
</a>
</li>
<li>
<a href="/me/conversations">
<Icon
icon="iconamoon:profile-circle-fill"
class="w-8 h-8 text-gray-500"
/>
Email
</a>
</li>
<li>
<a href="/me/chatGPT">
<Icon
icon="iconamoon:profile-circle-fill"
class="w-8 h-8 text-gray-500"
/>
ChatGPT
</a>
</li>
<li>
<a href="/me/banking">
<Icon
icon="iconamoon:profile-circle-fill"
class="w-8 h-8 text-gray-500"
/>
Banking
</a>
</li>
<li>
<a href="/me/paperless">
<Icon
icon="iconamoon:profile-circle-fill"
class="w-8 h-8 text-gray-500"
/>
Paperless
</a>
</li>
<li>
<a href="/me/acc">
<Icon
icon="iconamoon:profile-circle-fill"
class="w-8 h-8 text-gray-500"
/>
Access Control
</a>
</li>
</ul>
</nav>
</aside>
<div class="col-span-5 w-full h-full overflow-hidden bg-white rounded-bl-3xl">
<slot />
</div>
</div>
<style>
.collapsed {
width: 3rem; /* Adjust as needed */
}
</style>

View File

@ -0,0 +1,48 @@
<script lang="ts">
import HeaderMain from "$lib/layouts/HeaderMain.svelte";
import { Avatar } from "@skeletonlabs/skeleton";
import { createQuery } from "../../lib/wundergraph";
const meQuery = createQuery({
operationName: "Me",
});
</script>
<HeaderMain>
<div slot="header">
<h1>Profile</h1>
</div>
<div slot="main" class="h-full w-full overflow-scroll">
<div class="w-full h-full overflow-scroll">
{#if $meQuery.isLoading}
<p>Loading...</p>
{:else if $meQuery.error}
<pre>Error: {JSON.stringify($meQuery.error, null, 2)}</pre>
{:else}
<div class="container mx-auto p-8 space-y-8">
<div class="flex items-center space-x-4">
<Avatar
class="rounded-full w-24"
src={`https://directus.andert.me/assets/${
$meQuery.data.system_db_users_me?.avatar.id
}?access_token=${
import.meta.env.VITE_DIRECTUS_TEMPORARY_ACCESS_TOKEN
}`}
/>
<div>
<h3 class="h3">Welcome back</h3>
<h2 class="h2">
{$meQuery.data.system_db_users_me?.first_name}
{$meQuery.data.system_db_users_me?.last_name}
</h2>
{$meQuery.data.system_db_users_me?.external_identifier}
<p />
</div>
</div>
</div>
{/if}
</div>
</div>
</HeaderMain>

View File

@ -0,0 +1,14 @@
<script>
import ACCs from "$lib/ACCs.svelte";
import HeaderMain from "$lib/layouts/HeaderMain.svelte";
</script>
<HeaderMain>
<div slot="header">
<h1>Access Control</h1>
</div>
<div slot="main">
<ACCs />
</div>
</HeaderMain>

View File

@ -0,0 +1,13 @@
<!-- src/routes/apps/+page.svelte -->
<script>
import { mockApps } from "$lib/mockApps.js";
</script>
<h1>Apps List</h1>
<ul>
{#each mockApps as app}
<li>
<a href="/me/apps/{app.name}">{app.name}</a>
</li>
{/each}
</ul>

View File

@ -0,0 +1,12 @@
import { mockApps } from '$lib/mockApps';
export function load({ params }) {
const { name } = params;
const app = mockApps.find(a => a.name == name);
if (app) {
return { props: { app } };
}
return { status: 404 };
}

View File

@ -0,0 +1,10 @@
<script>
/** @type {import('./$types').PageData} */
export let data;
</script>
<iframe
src={`https://${data.props.app.name}.andert.me`}
width="100%"
height="100%"
/>

View File

@ -0,0 +1,98 @@
<script lang="ts">
import HeaderMain from "$lib/layouts/HeaderMain.svelte";
import { createQuery } from "$lib/wundergraph";
import Time from "svelte-time";
import Icon from "@iconify/svelte";
import { sendTxWithPKPWallet } from "$lib/services/sendTxWithPKPWallet";
let me = JSON.parse(localStorage.getItem("me"));
const getBalanceQuery = createQuery({
operationName: "getBalance",
input: {
address: me.pkps[0].ethAddress,
},
});
const getTransactionsQuery = createQuery({
operationName: "getTransactions",
input: {
address: me.pkps[0].ethAddress,
},
});
function fromWei(wei: BigInt): string {
return (Number(wei) / 10 ** 18).toFixed(2);
}
function handleSendTx() {
sendTxWithPKPWallet(me.pkps[0], me.sessionSigs);
}
</script>
<HeaderMain>
<div slot="header">
<h1>Banking</h1>
</div>
<div slot="main">
<div class="pb-4">
ACCOUNT
<p class="text-2xl">{me.pkps[0].ethAddress}</p>
</div>
<div class="pb-4">
{#if $getBalanceQuery.isLoading}
<p>Loading...</p>
{:else if $getBalanceQuery.error}
<pre>Error: {JSON.stringify($getBalanceQuery.error, null, 2)}</pre>
{:else}
BALANCE
<p class="text-5xl">${fromWei($getBalanceQuery.data.balance)}</p>
{/if}
</div>
<div class="pb-4">
<button
class="btn variant-filled-primary flex-grow"
on:click={handleSendTx}
>
SEND $0.10
</button>
</div>
<div class="pb-4">
{#if $getTransactionsQuery.isLoading}
<p>Loading...</p>
{:else if $getTransactionsQuery.error}
<pre>Error: {JSON.stringify($getTransactionsQuery.error, null, 2)}</pre>
{:else}
<div class="pb-4">TRANSACTIONS</div>
<ul class="list-disc">
{#each $getTransactionsQuery.data.transactions as transaction (transaction.hash)}
<li class="flex justify-between items-center mb-4">
<div class="flex items-center">
<div class="pr-3">
<Icon icon="ri:user-received-2-line" width="44" height="44" />
</div>
<div>
<p class="text-xl">
from {transaction.from.substring(0, 10)}...
</p>
<p class="text-sm text-gray-500">
{transaction.hash}
</p>
</div>
</div>
<div class="text-right">
<p class="text-2xl">
${fromWei(BigInt(transaction.value))}
</p>
<p class="text-sm text-gray-500">
<Time timestamp={transaction.timestamp} relative />
</p>
</div>
</li>
{/each}
</ul>
{/if}
</div>
</div>
</HeaderMain>

View File

@ -0,0 +1,40 @@
<script lang="ts">
import HeaderMain from "$lib/layouts/HeaderMain.svelte";
import { createQuery } from "../../../lib/wundergraph";
const bookmarksQuery = createQuery({
operationName: "getBookmarks",
});
</script>
<HeaderMain>
<div slot="header">
<h1>Bookmarks</h1>
</div>
<div slot="main">
<div class="w-full h-full overflow-y-auto">
<div class="w-full h-full results">
{#if $bookmarksQuery.isLoading}
<p>Loading...</p>
{:else if $bookmarksQuery.error}
<pre>Error: {JSON.stringify($bookmarksQuery.error, null, 2)}</pre>
{:else}
{#each $bookmarksQuery.data.db_bookmarks as bookmark (bookmark.id)}
<a href={bookmark.url} class="text-blue-500 hover:underline">
{bookmark.name}
</a>
<div class="mt-2">
{#each bookmark.tags as tag (tag)}
<span
class="inline-block bg-blue-200 text-blue-800 px-2 py-1 rounded-full mr-2 mb-2"
>{tag}</span
>
{/each}
</div>
{/each}
{/if}
</div>
</div>
</div>
</HeaderMain>

View File

@ -0,0 +1,5 @@
<script>
import Ai from "$lib/Ai.svelte";
</script>
<Ai />

View File

@ -0,0 +1,36 @@
<script lang="ts">
import JsonViewer from "$lib/JsonViewer.svelte";
import HeaderMain from "$lib/layouts/HeaderMain.svelte";
import { createQuery } from "../../../lib/wundergraph";
const contactsQuery = createQuery({
operationName: "getChatwootContacts",
});
</script>
<HeaderMain>
<div slot="header">
<h1>Contacts</h1>
</div>
<div slot="main" class="h-full w-full overflow-scroll">
<div class="w-full h-full overflow-scroll">
{#if $contactsQuery.isLoading}
<p>Loading...</p>
{:else if $contactsQuery.error}
<pre>Error: {JSON.stringify($contactsQuery.error, null, 2)}</pre>
{:else}
<div class="grid grid-cols-3 gap-4">
{#each $contactsQuery.data.payload as contact (contact.id)}
<div class="card">
<header class="card-header">{contact.name}</header>
<section class="p-4">
ID: {contact.id}<br />{contact.email}
</section>
</div>
{/each}
</div>
{/if}
</div>
</div>
</HeaderMain>

View File

@ -0,0 +1,92 @@
<script lang="ts">
import MailViewer from "$lib/MailViewer.svelte";
import { createQuery } from "../../../lib/wundergraph";
import JsonViewer from "$lib/JsonViewer.svelte";
const conversationsQuery = createQuery({
operationName: "getChatwootConversations",
});
let selectedConversation = null;
let messagesQuery;
function selectConversation(conversation) {
selectedConversation = conversation;
}
$: if ($conversationsQuery.data && !selectedConversation) {
selectedConversation = $conversationsQuery.data.data.payload[0];
}
$: if (selectedConversation) {
messagesQuery = createQuery({
operationName: "getChatwootMessages",
input: { conversationId: selectedConversation.id },
});
}
</script>
<div class="h-full w-full overflow-scroll flex">
<div class="w-1/3 h-full overflow-scroll">
{#if $conversationsQuery.isLoading}
<p>Loading...</p>
{:else if $conversationsQuery.error}
Error: <JsonViewer json="$conversationsQuery.error}" />
{:else}
{#each $conversationsQuery.data.data.payload as conversation (conversation.id)}
<div
class="m-1 p-2 border border-gray-200 rounded-md hover:bg-gray-100 cursor-pointer"
on:click={() => selectConversation(conversation)}
>
<h2 class="text-lg font-semibold">
{conversation.meta.sender.name}
</h2>
<p>{conversation.messages[0].content.slice(0, 100)}...</p>
</div>
{/each}
{/if}
</div>
<div class="w-2/3 h-full overflow-scroll relative">
{#if selectedConversation}
<div class="p-4 bg-white z-10 sticky top-0 left-0">
<h2 class="font-bold text-xl">
{selectedConversation.meta.sender.name}
</h2>
<p>
{selectedConversation.id} - {selectedConversation.meta.sender.email}
</p>
</div>
<div class="space-y-4 px-4">
{#if $messagesQuery && $messagesQuery.data}
{#each $messagesQuery.data.payload as message (message.id)}
{#if message.content_attributes.email && (message.content_attributes.email.content_type.includes("text/html") || message.content_attributes.email.content_type.includes("multipart/alternative"))}
<MailViewer
html={message.content_attributes.email.html_content.full}
/>
{:else}
<div
class="p-4 max-w-xs mx-auto bg-blue-100 rounded-xl shadow-md flex items-center space-x-4"
>
<p class="text-black">{message.content}</p>
</div>
{/if}
{/each}
{/if}
</div>
{:else}
<p>Select a conversation to view its details.</p>
{/if}
</div>
</div>
<style>
.btn {
display: inline-block;
padding: 0.5em 1em;
background-color: #007bff;
color: white;
text-decoration: none;
border-radius: 0.25em;
}
</style>

View File

@ -0,0 +1,57 @@
<script lang="ts">
import { createQuery } from "../../../lib/wundergraph";
import HeaderMain from "$lib/layouts/HeaderMain.svelte";
const filesQuery = createQuery({
operationName: "Files",
});
</script>
<HeaderMain>
<div slot="header">
<h1>Files</h1>
</div>
<div slot="main" class="h-full w-full overflow-scroll">
<div class="w-full h-full overflow-scroll">
{#if $filesQuery.isLoading}
<p>Loading...</p>
{:else if $filesQuery.error}
<pre>Error: {JSON.stringify($filesQuery.error, null, 2)}</pre>
{:else}
<div class="space-y-4">
{#each $filesQuery.data.system_db_files as file}
<div class="flex">
<div class="w-1/4">
{#if file.type === "application/pdf"}
<object
data={`https://directus.andert.me/assets/${
file.id
}?access_token=${
import.meta.env.VITE_DIRECTUS_TEMPORARY_ACCESS_TOKEN
}`}
type="application/pdf"
width="100%"
height="200px"
/>
{:else}
<img
src={`https://directus.andert.me/assets/${
file.id
}?access_token=${
import.meta.env.VITE_DIRECTUS_TEMPORARY_ACCESS_TOKEN
}`}
width="200"
/>
{/if}
</div>
<div class="w-3/4 p-4">
<h2>{file.title}</h2>
<p>{file.id}</p>
</div>
</div>
{/each}
</div>
{/if}
</div>
</div>
</HeaderMain>

View File

@ -0,0 +1,67 @@
<script lang="ts">
import HeaderMain from "$lib/layouts/HeaderMain.svelte";
import { createQuery } from "../../../lib/wundergraph";
const paperlessQuery = createQuery({
operationName: "getPaperless",
});
let pdfDataUrls = [];
let selectedPdfUrl = null;
$: if ($paperlessQuery.data) {
pdfDataUrls = $paperlessQuery.data.map((document) => {
const pdfData = atob(document.pdfData);
const pdfDataArray = new Uint8Array(pdfData.length);
for (let i = 0; i < pdfData.length; i++) {
pdfDataArray[i] = pdfData.charCodeAt(i);
}
const blob = new Blob([pdfDataArray], { type: "application/pdf" });
return URL.createObjectURL(blob);
});
}
function selectPdf(i) {
selectedPdfUrl = pdfDataUrls[i];
}
</script>
<HeaderMain>
<div slot="main" class="h-full w-full overflow-scroll flex">
<div class="w-1/4 h-full overflow-scroll">
{#if $paperlessQuery.isLoading}
<p>Loading...</p>
{:else if $paperlessQuery.error}
<pre>Error: {JSON.stringify($paperlessQuery.error, null, 2)}</pre>
{:else}
{#each $paperlessQuery.data as document, i}
<a href="#" on:click|preventDefault={() => selectPdf(i)}>
<div
class="my-4 p-2 border border-gray-200 rounded-md hover:bg-gray-100 cursor-pointer"
>
<h2 class="text-lg font-semibold">
{document.archived_file_name}
</h2>
<p>Correspondent: {document.correspondent?.name || "N/A"}</p>
<p>
Tags: {document.tags?.map((tag) => tag.name).join(", ") ||
"N/A"}
</p>
<p>{document.documentType?.name || "N/A"}</p>
</div>
</a>
{/each}
{/if}
</div>
<div class="w-3/4 h-full overflow-scroll">
{#if selectedPdfUrl}
<object
data={selectedPdfUrl}
type="application/pdf"
width="100%"
height="100%"
/>
{/if}
</div>
</div>
</HeaderMain>

View File

@ -0,0 +1,26 @@
<script lang="ts">
import HeaderMain from "$lib/layouts/HeaderMain.svelte";
import { createQuery } from "../../../lib/wundergraph";
const projectsQuery = createQuery({
operationName: "Projects",
});
</script>
<HeaderMain>
<div slot="header">
<h1>Projects</h1>
</div>
<div slot="main" class="h-full w-full overflow-scroll">
<div class="w-full h-full overflow-scroll">
{#if $projectsQuery.isLoading}
<p>Loading...</p>
{:else if $projectsQuery.error}
<pre>Error: {JSON.stringify($projectsQuery.error, null, 2)}</pre>
{:else}
<pre>{JSON.stringify($projectsQuery.data, null, 2)}</pre>
{/if}
</div>
</div>
</HeaderMain>

View File

@ -0,0 +1,34 @@
import OpenAI from 'openai';
import { OpenAIStream, StreamingTextResponse } from 'ai';
import { env } from '$env/dynamic/private';
// You may want to replace the above with a static private env variable
// for dead-code elimination and build-time type-checking:
// import { OPENAI_API_KEY } from '$env/static/private'
import type { RequestHandler } from '../../server/api/chat/$types';
// Create an OpenAI API client
const openai = new OpenAI({
apiKey: env.OPENAI_API_KEY || '',
});
export const POST = (async ({ request }) => {
// Extract the `prompt` from the body of the request
const { messages } = await request.json();
// Ask OpenAI for a streaming chat completion given the prompt
const response = await openai.chat.completions.create({
model: 'gpt-3.5-turbo',
stream: true,
messages: messages.map((message: any) => ({
content: message.content,
role: message.role,
})),
});
// Convert the response into a friendly text-stream
const stream = OpenAIStream(response);
// Respond with the stream
return new StreamingTextResponse(stream);
}) satisfies RequestHandler;

Some files were not shown because too many files have changed in this diff Show More