diff --git a/explorer-ui/codegen.ts b/explorer-ui/codegen.ts index c4e6552..c4ec452 100644 --- a/explorer-ui/codegen.ts +++ b/explorer-ui/codegen.ts @@ -1,19 +1,18 @@ import { type CodegenConfig } from '@graphql-codegen/cli' const config: CodegenConfig = { - schema: 'http://localhost:10701/branch/main/graphql', + schema: 'http://localhost:10700/branch/main/graphql', documents: ['src/queries.ts'], ignoreNoDocuments: true, // for better experience with the watcher generates: { './src/gql/graphql.ts': { - // preset: 'client', plugins: [ 'typescript', 'typescript-operations', 'typed-document-node', ] - } - } + }, + }, } export default config diff --git a/explorer-ui/package.json b/explorer-ui/package.json index a85c2c8..3b7a280 100644 --- a/explorer-ui/package.json +++ b/explorer-ui/package.json @@ -11,11 +11,14 @@ "codegen": "graphql-code-generator --config ./codegen.ts" }, "dependencies": { + "@graphql-tools/load": "^8.0.2", + "@graphql-tools/url-loader": "^8.0.2", "@graphql-typed-document-node/core": "^3.2.0", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-slot": "^1.1.0", "@t3-oss/env-nextjs": "^0.10.1", "@tanstack/react-query": "^5.45.1", + "axios": "^1.7.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "geist": "^1.3.0", diff --git a/explorer-ui/pnpm-lock.yaml b/explorer-ui/pnpm-lock.yaml index 3acfe1e..8f969c1 100644 --- a/explorer-ui/pnpm-lock.yaml +++ b/explorer-ui/pnpm-lock.yaml @@ -8,6 +8,12 @@ importers: .: dependencies: + '@graphql-tools/load': + specifier: ^8.0.2 + version: 8.0.2(graphql@16.9.0) + '@graphql-tools/url-loader': + specifier: ^8.0.2 + version: 8.0.2(@types/node@20.14.8)(graphql@16.9.0) '@graphql-typed-document-node/core': specifier: ^3.2.0 version: 3.2.0(graphql@16.9.0) @@ -23,6 +29,9 @@ importers: '@tanstack/react-query': specifier: ^5.45.1 version: 5.45.1(react@18.3.1) + axios: + specifier: ^1.7.2 + version: 1.7.2 class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -700,8 +709,8 @@ packages: peerDependencies: graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 - '@graphql-codegen/schema-ast@4.0.2': - resolution: {integrity: sha512-5mVAOQQK3Oz7EtMl/l3vOQdc2aYClUzVDHHkMvZlunc+KlGgl81j8TLa+X7ANIllqU4fUEsQU3lJmk4hXP6K7Q==} + '@graphql-codegen/schema-ast@4.1.0': + resolution: {integrity: sha512-kZVn0z+th9SvqxfKYgztA6PM7mhnSZaj4fiuBWvMTqA+QqQ9BBed6Pz41KuD/jr0gJtnlr2A4++/0VlpVbCTmQ==} peerDependencies: graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 @@ -1549,6 +1558,9 @@ packages: async-sema@3.1.1: resolution: {integrity: sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + auto-bind@4.0.0: resolution: {integrity: sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ==} engines: {node: '>=8'} @@ -1561,6 +1573,9 @@ packages: resolution: {integrity: sha512-QbUdXJVTpvUTHU7871ppZkdOLBeGUKBQWHkHrvN2V9IQWGMt61zf3B45BtzjxEJzYuj0JBjBZP/hmYS/R9pmAw==} engines: {node: '>=4'} + axios@1.7.2: + resolution: {integrity: sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==} + axobject-query@3.1.1: resolution: {integrity: sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==} @@ -1758,6 +1773,10 @@ packages: colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + commander@11.1.0: resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} engines: {node: '>=16'} @@ -1907,6 +1926,10 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + delegates@1.0.0: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} @@ -2495,6 +2518,15 @@ packages: flatted@3.3.1: resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + follow-redirects@1.15.6: + resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} @@ -2502,6 +2534,10 @@ packages: resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==} engines: {node: '>=14'} + form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + fs-extra@11.1.0: resolution: {integrity: sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==} engines: {node: '>=14.14'} @@ -3158,6 +3194,14 @@ packages: resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} engines: {node: '>=8.6'} + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + mime@3.0.0: resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} engines: {node: '>=10.0.0'} @@ -3676,6 +3720,9 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + pump@3.0.0: resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} @@ -5146,7 +5193,7 @@ snapshots: lodash: 4.17.21 tslib: 2.6.3 - '@graphql-codegen/schema-ast@4.0.2(graphql@16.9.0)': + '@graphql-codegen/schema-ast@4.1.0(graphql@16.9.0)': dependencies: '@graphql-codegen/plugin-helpers': 5.0.4(graphql@16.9.0) '@graphql-tools/utils': 10.2.2(graphql@16.9.0) @@ -5180,7 +5227,7 @@ snapshots: '@graphql-codegen/typescript@4.0.7(graphql@16.9.0)': dependencies: '@graphql-codegen/plugin-helpers': 5.0.4(graphql@16.9.0) - '@graphql-codegen/schema-ast': 4.0.2(graphql@16.9.0) + '@graphql-codegen/schema-ast': 4.1.0(graphql@16.9.0) '@graphql-codegen/visitor-plugin-common': 5.2.0(graphql@16.9.0) auto-bind: 4.0.0 graphql: 16.9.0 @@ -6279,6 +6326,8 @@ snapshots: async-sema@3.1.1: {} + asynckit@0.4.0: {} + auto-bind@4.0.0: {} available-typed-arrays@1.0.7: @@ -6287,6 +6336,14 @@ snapshots: axe-core@4.9.1: {} + axios@1.7.2: + dependencies: + follow-redirects: 1.15.6 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + axobject-query@3.1.1: dependencies: deep-equal: 2.2.3 @@ -6545,6 +6602,10 @@ snapshots: colorette@2.0.20: {} + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + commander@11.1.0: {} commander@4.1.1: {} @@ -6685,6 +6746,8 @@ snapshots: defu@6.1.4: {} + delayed-stream@1.0.0: {} + delegates@1.0.0: {} depd@1.1.2: {} @@ -7384,6 +7447,8 @@ snapshots: flatted@3.3.1: {} + follow-redirects@1.15.6: {} + for-each@0.3.3: dependencies: is-callable: 1.2.7 @@ -7393,6 +7458,12 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 + form-data@4.0.0: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + fs-extra@11.1.0: dependencies: graceful-fs: 4.2.11 @@ -8055,6 +8126,12 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + mime@3.0.0: {} mimic-fn@2.1.0: {} @@ -8502,6 +8579,8 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + proxy-from-env@1.1.0: {} + pump@3.0.0: dependencies: end-of-stream: 1.4.4 diff --git a/explorer-ui/src/gql/graphql.ts b/explorer-ui/src/gql/graphql.ts index 5efecb8..ed7de31 100644 --- a/explorer-ui/src/gql/graphql.ts +++ b/explorer-ui/src/gql/graphql.ts @@ -30,6 +30,17 @@ export type Admin_Type = Admin & BaseObject & Object & SolanaAccount & { pubkey: Scalars['String']['output']; }; +export type AuthUser = { + id: Scalars['ID']['output']; + is_admin: Scalars['Boolean']['output']; +}; + +export type AuthUser_Type = AuthUser & BaseObject & Object & { + __typename?: 'AuthUser_Type'; + id: Scalars['ID']['output']; + is_admin: Scalars['Boolean']['output']; +}; + /** Root object type. */ export type BaseObject = { id: Scalars['ID']['output']; @@ -237,6 +248,14 @@ export type FilterAdmin = { pubkey?: InputMaybe; }; +export type FilterAuthUser = { + and?: InputMaybe>; + id?: InputMaybe; + is_admin?: InputMaybe; + not?: InputMaybe; + or?: InputMaybe>; +}; + export type FilterBaseObject = { and?: InputMaybe>; id?: InputMaybe; @@ -510,10 +529,22 @@ export type FilterWithIx = { tx?: InputMaybe; }; +export type Filtercurrent_Auth_User = { + and?: InputMaybe>; + id?: InputMaybe; + is_admin?: InputMaybe; + not?: InputMaybe; + or?: InputMaybe>; +}; + export type InsertAdmin = { pubkey: Scalars['String']['input']; }; +export type InsertAuthUser = { + is_admin?: InputMaybe; +}; + export type InsertDid = { device: NestedInsertDevice; metadata?: InputMaybe; @@ -580,6 +611,7 @@ export enum JsonEmpty { export type Mutation = { __typename?: 'Mutation'; delete_Admin?: Maybe>; + delete_AuthUser?: Maybe>; delete_DID?: Maybe>; delete_Device?: Maybe>; delete_Product?: Maybe>; @@ -588,7 +620,9 @@ export type Mutation = { delete_Transaction?: Maybe>; delete_User?: Maybe>; delete_Vendor?: Maybe>; + delete_current_auth_user?: Maybe>; insert_Admin?: Maybe>; + insert_AuthUser?: Maybe>; insert_DID?: Maybe>; insert_Device?: Maybe>; insert_Product?: Maybe>; @@ -598,6 +632,7 @@ export type Mutation = { insert_User?: Maybe>; insert_Vendor?: Maybe>; update_Admin?: Maybe>; + update_AuthUser?: Maybe>; update_DID?: Maybe>; update_Device?: Maybe>; update_Product?: Maybe>; @@ -623,6 +658,16 @@ export type MutationDelete_AdminArgs = { }; +export type MutationDelete_AuthUserArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + order?: InputMaybe; +}; + + export type MutationDelete_DidArgs = { after?: InputMaybe; before?: InputMaybe; @@ -703,11 +748,26 @@ export type MutationDelete_VendorArgs = { }; +export type MutationDelete_Current_Auth_UserArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + order?: InputMaybe; +}; + + export type MutationInsert_AdminArgs = { data: Array; }; +export type MutationInsert_AuthUserArgs = { + data: Array; +}; + + export type MutationInsert_DidArgs = { data: Array; }; @@ -759,6 +819,17 @@ export type MutationUpdate_AdminArgs = { }; +export type MutationUpdate_AuthUserArgs = { + after?: InputMaybe; + before?: InputMaybe; + data: UpdateAuthUser; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + order?: InputMaybe; +}; + + export type MutationUpdate_DidArgs = { after?: InputMaybe; before?: InputMaybe; @@ -1141,6 +1212,11 @@ export type OrderAdmin = { pubkey?: InputMaybe; }; +export type OrderAuthUser = { + id?: InputMaybe; + is_admin?: InputMaybe; +}; + export type OrderBaseObject = { id?: InputMaybe; }; @@ -1242,6 +1318,11 @@ export type OrderWithIx = { tx?: InputMaybe; }; +export type Ordercurrent_Auth_User = { + id?: InputMaybe; + is_admin?: InputMaybe; +}; + export type Ordering = { dir: DirectionEnum; nulls?: InputMaybe; @@ -1436,6 +1517,7 @@ export type Program_TypeVendorsArgs = { export type Query = { __typename?: 'Query'; Admin?: Maybe>; + AuthUser?: Maybe>; BaseObject?: Maybe>; DID?: Maybe>; Device?: Maybe>; @@ -1450,6 +1532,7 @@ export type Query = { User?: Maybe>; Vendor?: Maybe>; WithIx?: Maybe>; + current_auth_user?: Maybe>; }; @@ -1463,6 +1546,16 @@ export type QueryAdminArgs = { }; +export type QueryAuthUserArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + order?: InputMaybe; +}; + + export type QueryBaseObjectArgs = { after?: InputMaybe; before?: InputMaybe; @@ -1602,6 +1695,16 @@ export type QueryWithIxArgs = { order?: InputMaybe; }; + +export type QueryCurrent_Auth_UserArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + order?: InputMaybe; +}; + export type SolanaAccount = { id: Scalars['ID']['output']; pubkey: Scalars['String']['output']; @@ -1669,6 +1772,10 @@ export type UpdateAdmin = { pubkey?: InputMaybe; }; +export type UpdateAuthUser = { + is_admin?: InputMaybe; +}; + export type UpdateDid = { device?: InputMaybe; metadata?: InputMaybe; @@ -1708,6 +1815,10 @@ export type UpdateOp_Err_Transaction = { slice?: InputMaybe>; }; +export type UpdateOp_Is_Admin_AuthUser = { + set?: InputMaybe; +}; + export type UpdateOp_Metadata_Did = { clear?: InputMaybe; set?: InputMaybe; @@ -2080,6 +2191,12 @@ export type WithIxTxArgs = { order?: InputMaybe; }; +export type Current_Auth_User = { + __typename?: 'current_auth_user'; + id: Scalars['ID']['output']; + is_admin: Scalars['Boolean']['output']; +}; + /** Enum value used to specify ordering direction. */ export enum DirectionEnum { Asc = 'ASC', diff --git a/explorer-ui/src/pages/products.tsx b/explorer-ui/src/pages/products.tsx index f697b48..204efaf 100644 --- a/explorer-ui/src/pages/products.tsx +++ b/explorer-ui/src/pages/products.tsx @@ -42,7 +42,7 @@ export default function Products() {
-
+
{data ? ( @@ -61,16 +61,13 @@ export default function Products() { Name Mint Account Url + Detail {data.Product?.map((product, i) => ( - - - {page * limit + i + 1} - - + {page * limit + i + 1} {product.metadata?.name} ) : null} + + + + + ))} diff --git a/indexer/.env.example b/indexer/.env.example new file mode 100644 index 0000000..f3810f5 --- /dev/null +++ b/indexer/.env.example @@ -0,0 +1 @@ +CURRENT_AUTH_USER_ID= \ No newline at end of file diff --git a/indexer/.gitignore b/indexer/.gitignore index 3e51163..8492bbd 100644 --- a/indexer/.gitignore +++ b/indexer/.gitignore @@ -2,3 +2,7 @@ src/generated bun.lockb dbschema/edgeql-js + +# local env files +.env +.env.local \ No newline at end of file diff --git a/indexer/dbschema/default.esdl b/indexer/dbschema/default.esdl index ef64632..bb56529 100644 --- a/indexer/dbschema/default.esdl +++ b/indexer/dbschema/default.esdl @@ -1,6 +1,11 @@ using extension graphql; module default { + global current_auth_user_id: uuid; + alias current_auth_user := ( + select AuthUser filter .id = global current_auth_user_id + ); + type Transaction { required signature: str { constraint exclusive; @@ -11,6 +16,15 @@ module default { required processed: bool { default := false; }; + + access policy auth_user_has_full_access + allow all + using (current_auth_user.is_admin ?? false) { + errmessage := 'Admin Only' + }; + access policy non_admins_can_only_select + allow select + using (not (current_auth_user.is_admin ?? false)); } type TokenMetadata { @@ -103,5 +117,9 @@ module default { owner: User; } + + type AuthUser { + required is_admin: bool { default := false }; + } } diff --git a/indexer/dbschema/migrations/00005-m15kthi.edgeql b/indexer/dbschema/migrations/00005-m15kthi.edgeql new file mode 100644 index 0000000..324465b --- /dev/null +++ b/indexer/dbschema/migrations/00005-m15kthi.edgeql @@ -0,0 +1,24 @@ +CREATE MIGRATION m15kthikemftuzpiq3klfmggarwau2g7qffcsytvaqxwm4cgie2v4a + ONTO m1y4v37muib7b3dndnsf45elqoyywj5azwwoksspwpj2crxet5uo2a +{ + CREATE GLOBAL default::current_auth_user_id -> std::uuid; + CREATE TYPE default::AuthUser { + CREATE REQUIRED PROPERTY is_admin: std::bool { + SET default := false; + }; + }; + CREATE ALIAS default::current_auth_user := ( + SELECT + default::AuthUser + FILTER + (.id = GLOBAL default::current_auth_user_id) + ); + ALTER TYPE default::Transaction { + CREATE ACCESS POLICY auth_user_has_full_access + ALLOW ALL USING ((default::current_auth_user.is_admin ?? false)) { + SET errmessage := 'Admin Only'; + }; + CREATE ACCESS POLICY non_admins_can_only_select + ALLOW SELECT USING (NOT ((default::current_auth_user.is_admin ?? false))); + }; +}; diff --git a/indexer/src/indexer.ts b/indexer/src/indexer.ts index c1aace3..45eb6cd 100644 --- a/indexer/src/indexer.ts +++ b/indexer/src/indexer.ts @@ -109,6 +109,9 @@ export class Indexer { public async ensureConnected() { this.db = await createClient(this.databaseUrl) + .withGlobals({ + current_auth_user_id: process.env.CURRENT_AUTH_USER_ID + }) .withRetryOptions({ attempts: 3, backoff: (attemptNo: number) => {