Compare commits
37 Commits
2f40f3f5ac
...
main
Author | SHA1 | Date | |
---|---|---|---|
a7eed530a3 | |||
af15eb1d0b | |||
19674310e7 | |||
5e3631c49d | |||
0312f50d51 | |||
804a618053 | |||
5041a3c5a3 | |||
91b351ef2e | |||
b1185b44dd | |||
fd534bfae7 | |||
d55e4bb203 | |||
563e9945c3 | |||
aaf4a7461a | |||
102f4fa855 | |||
35b3167509 | |||
53e68aa362 | |||
8a4ec6cc73 | |||
4ae46a2a75 | |||
c13047e281 | |||
61ba4d0e4f | |||
9b9ac7d89e | |||
293180c2b5 | |||
24b83ec99e | |||
879f7aad3f | |||
efc2b41f9e | |||
7652938c74 | |||
b8012cf9fd | |||
978e438854 | |||
9aa3bfc0d2 | |||
042f9209ed | |||
95566d6f36 | |||
7c9e1129c7 | |||
a749abe7ed | |||
370ff822d0 | |||
e2a3c680d1 | |||
405c564880 | |||
7803294d6f |
@ -1,6 +0,0 @@
|
||||
query Continents {
|
||||
countries_continents {
|
||||
name
|
||||
code
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
query Countries($filter: countries_CountryFilterInput) {
|
||||
countries_countries(filter: $filter) {
|
||||
code
|
||||
name
|
||||
capital
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
query Dragons {
|
||||
spacex_dragons {
|
||||
name
|
||||
active
|
||||
}
|
||||
}
|
8
.wundergraph/operations/Files.graphql
Normal file
@ -0,0 +1,8 @@
|
||||
query {
|
||||
system_db_files {
|
||||
id
|
||||
title
|
||||
type
|
||||
filename_disk
|
||||
}
|
||||
}
|
11
.wundergraph/operations/Me.graphql
Normal file
@ -0,0 +1,11 @@
|
||||
query {
|
||||
system_db_users_me {
|
||||
id
|
||||
first_name
|
||||
last_name
|
||||
avatar {
|
||||
id
|
||||
}
|
||||
external_identifier
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
query Projects {
|
||||
directus_projects {
|
||||
query {
|
||||
db_projects {
|
||||
id
|
||||
text
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
query Todos {
|
||||
directus_todos {
|
||||
query {
|
||||
db_todos {
|
||||
id
|
||||
task
|
||||
user_created {
|
||||
|
27
.wundergraph/operations/getBalance.ts
Normal 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),
|
||||
};
|
||||
},
|
||||
});
|
8
.wundergraph/operations/getBookmarks.graphql
Normal file
@ -0,0 +1,8 @@
|
||||
query {
|
||||
db_bookmarks {
|
||||
id
|
||||
name
|
||||
url
|
||||
tags
|
||||
}
|
||||
}
|
20
.wundergraph/operations/getChatwootContacts.ts
Normal 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;
|
||||
},
|
||||
});
|
19
.wundergraph/operations/getChatwootConversations.ts
Normal 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;
|
||||
},
|
||||
});
|
20
.wundergraph/operations/getChatwootMessages.ts
Normal 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;
|
||||
},
|
||||
});
|
70
.wundergraph/operations/getPaperless.ts
Normal 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;
|
||||
},
|
||||
});
|
27
.wundergraph/operations/getTransactions.ts
Normal 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(),
|
||||
})),
|
||||
};
|
||||
},
|
||||
});
|
5195
.wundergraph/schemas/cloudron.json
Normal 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
|
||||
}
|
2366
.wundergraph/schemas/directus_system.graphql
Normal 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);
|
181
.wundergraph/schemas/placeholder.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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'],
|
||||
},
|
||||
});
|
||||
|
33
package.json
@ -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
6
postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
3
src-tauri/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
3510
src-tauri/Cargo.lock
generated
Normal file
26
src-tauri/Cargo.toml
Normal 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
@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
BIN
src-tauri/icons/128x128.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
src-tauri/icons/128x128@2x.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
src-tauri/icons/32x32.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
src-tauri/icons/Square107x107Logo.png
Normal file
After Width: | Height: | Size: 9.0 KiB |
BIN
src-tauri/icons/Square142x142Logo.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
src-tauri/icons/Square150x150Logo.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
src-tauri/icons/Square284x284Logo.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
src-tauri/icons/Square30x30Logo.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
src-tauri/icons/Square310x310Logo.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
src-tauri/icons/Square44x44Logo.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
src-tauri/icons/Square71x71Logo.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
src-tauri/icons/Square89x89Logo.png
Normal file
After Width: | Height: | Size: 7.4 KiB |
BIN
src-tauri/icons/StoreLogo.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
src-tauri/icons/icon.icns
Normal file
BIN
src-tauri/icons/icon.ico
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
src-tauri/icons/icon.png
Normal file
After Width: | Height: | Size: 49 KiB |
8
src-tauri/src/main.rs
Normal 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
@ -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
@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
17
src/app.d.ts
vendored
@ -1,12 +1,23 @@
|
||||
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 {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
export { };
|
10
src/app.html
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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>
|
5
src/lib/JsonViewer.svelte
Normal file
@ -0,0 +1,5 @@
|
||||
<script>
|
||||
export let json;
|
||||
</script>
|
||||
|
||||
<pre>{JSON.stringify(json, null, 2)}</pre>
|
@ -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
@ -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
@ -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>
|
@ -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
@ -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}
|
23
src/lib/createLitSession.ts
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
}
|
@ -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 |
@ -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 |
Before Width: | Height: | Size: 352 KiB |
Before Width: | Height: | Size: 113 KiB |
10
src/lib/layouts/HeaderMain.svelte
Normal 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>
|
116
src/lib/machines/walletMachine.ts
Normal 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
@ -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
@ -0,0 +1,8 @@
|
||||
export const mockApps = [
|
||||
{ name: 'cloud' },
|
||||
{ name: 'pass'},
|
||||
{ name: 'directus' },
|
||||
{ name: 'penpot'},
|
||||
{ name: 'paperless'}
|
||||
];
|
||||
|
57
src/lib/services/createJWT.ts
Normal 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;
|
||||
};
|
25
src/lib/services/createSession.ts
Normal 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}`);
|
||||
}
|
||||
};
|
66
src/lib/services/mutateAccessControlConditions.ts
Normal 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));
|
||||
};
|
30
src/lib/services/sendTxWithPKPWallet.ts
Normal 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');
|
||||
}
|
38
src/lib/services/signInWithGoogle.ts
Normal 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.");
|
||||
}
|
||||
};
|
41
src/lib/services/signMessage.ts
Normal 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 };
|
||||
}
|
||||
}
|
58
src/lib/setupChainProvider.ts
Normal 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
@ -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
@ -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
|
||||
});
|
@ -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}>
|
||||
<slot />
|
||||
</QueryClientProvider>
|
||||
<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>
|
||||
|
@ -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 };
|
||||
};
|
||||
|
10
src/routes/+page.server.ts
Normal 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
|
||||
};
|
||||
};
|
||||
|
@ -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> -->
|
||||
|
@ -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
|
||||
);
|
||||
};
|
@ -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 });
|
||||
}
|
@ -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.');
|
||||
}
|
||||
}
|
34
src/routes/api/chat/+server.ts
Normal 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;
|
95
src/routes/me/+layout.svelte
Normal 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>
|
48
src/routes/me/+page.svelte
Normal 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>
|
14
src/routes/me/acc/+page.svelte
Normal 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>
|
13
src/routes/me/apps/+page.svelte
Normal 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>
|
12
src/routes/me/apps/[name]/+page.js
Normal 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 };
|
||||
}
|
10
src/routes/me/apps/[name]/+page.svelte
Normal 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%"
|
||||
/>
|
98
src/routes/me/banking/+page.svelte
Normal 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>
|
40
src/routes/me/bookmarks/+page.svelte
Normal 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>
|
5
src/routes/me/chatGPT/+page.svelte
Normal file
@ -0,0 +1,5 @@
|
||||
<script>
|
||||
import Ai from "$lib/Ai.svelte";
|
||||
</script>
|
||||
|
||||
<Ai />
|
36
src/routes/me/contacts/+page.svelte
Normal 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>
|
92
src/routes/me/conversations/+page.svelte
Normal 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>
|
57
src/routes/me/documents/+page.svelte
Normal 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>
|
67
src/routes/me/paperless/+page.svelte
Normal 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>
|
26
src/routes/me/projects/+page.svelte
Normal 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>
|
34
src/routes/server/api/chat/+server.ts
Normal 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;
|