diff --git a/docs/pages/getting-started/adapters/dgraph.mdx b/docs/pages/getting-started/adapters/dgraph.mdx
index aee857758e..0d86f3dba6 100644
--- a/docs/pages/getting-started/adapters/dgraph.mdx
+++ b/docs/pages/getting-started/adapters/dgraph.mdx
@@ -92,6 +92,15 @@ app.use(
+### Schema
+
+
+ Note that this adapter is designed so that it uses Dgraph internal ID's, if
+ you are interested in using external id's you should modify your schema `id:
+ ID` to `id: String @id` for instance and modify the adapter
+ methods(createUser, linkAccount...).
+
+
### Unsecure Schema
The quickest way to use Dgraph is by applying the unsecure schema to your [local](https://dgraph.io/docs/graphql/admin/#modifying-a-schema) Dgraph instance or if using Dgraph [cloud](https://dgraph.io/docs/cloud/cloud-quick-start/#the-schema) you can paste the schema in the codebox to update.
@@ -235,7 +244,7 @@ type VerificationToken
expires: DateTime
}
-# Dgraph.Authorization {"VerificationKey":"","Header":"","Namespace":"","Algo":"HS256"}
+# Dgraph.Authorization {"VerificationKey":"","Header":"","Namespace":"","Algo":"HS512"}
```
### Dgraph.Authorization
@@ -244,7 +253,7 @@ In order to secure your graphql backend define the `Dgraph.Authorization` object
bottom of your schema and provide `authHeader` and `jwtSecret` values to the DgraphClient.
```js
-# Dgraph.Authorization {"VerificationKey":"","Header":"","Namespace":"YOUR CUSTOM NAMESPACE HERE","Algo":"HS256"}
+# Dgraph.Authorization {"VerificationKey":"","Header":"","Namespace":"YOUR CUSTOM NAMESPACE HERE","Algo":"HS512"}
```
### VerificationKey and jwtSecret
@@ -276,31 +285,4 @@ type VerificationRequest
### JWT session and `@auth` directive
-Dgraph only works with HS256 or RS256 algorithms. If you want to use session jwt to securely interact with your dgraph
-database you must customize next-auth `encode` and `decode` functions, as the default algorithm is HS512. You can
-further customize the jwt with roles if you want to implement [`RBAC logic`](https://dgraph.io/docs/graphql/authorization/directive/#role-based-access-control).
-
-```js filename="./auth.js"
-import NextAuth from "next-auth"
-import * as jwt from "jsonwebtoken"
-
-export const { handlers, auth, signIn, signOut } = NextAuth({
- session: {
- strategy: "jwt",
- },
- jwt: {
- secret: process.env.SECRET,
- encode: async ({ secret, token }) => {
- return jwt.sign({ ...token, userId: token.id }, secret, {
- algorithm: "HS256",
- expiresIn: 30 * 24 * 60 * 60, // 30 days
- })
- },
- decode: async ({ secret, token }) => {
- return jwt.verify(token, secret, { algorithms: ["HS256"] })
- },
- },
-})
-```
-
-Once your `Dgraph.Authorization` is defined in your schema and the JWT settings are set, this will allow you to define [`@auth rules`](https://dgraph.io/docs/graphql/authorization/authorization-overview/) for every part of your schema.
+Once your `Dgraph.Authorization` is defined in your schema and the JWT settings are set, this will allow you to define [`@auth rules`](https://dgraph.io/docs/graphql/schema/directives/auth/) for every part of your schema.
diff --git a/packages/adapter-dgraph/src/index.ts b/packages/adapter-dgraph/src/index.ts
index 677a16c527..deee52860a 100644
--- a/packages/adapter-dgraph/src/index.ts
+++ b/packages/adapter-dgraph/src/index.ts
@@ -15,10 +15,10 @@
* @module @auth/dgraph-adapter
*/
import { client as dgraphClient } from "./lib/client.js"
-import { isDate, type Adapter } from "@auth/core/adapters"
import type { DgraphClientParams } from "./lib/client.js"
import * as defaultFragments from "./lib/graphql/fragments.js"
-import {
+import type {
+ Adapter,
AdapterAccount,
AdapterSession,
AdapterUser,
@@ -49,11 +49,13 @@ export function DgraphAdapter(
options?: DgraphAdapterOptions
): Adapter {
const c = dgraphClient(client)
-
const fragments = { ...defaultFragments, ...options?.fragments }
+
return {
- async createUser(input: AdapterUser) {
- const result = await c.run<{ user: any[] }>(
+ async createUser(user: AdapterUser) {
+ // Remove the id from the user object so that it can be generated by Dgraph
+ const { id, ...userWithoutId } = user
+ const result = await c.run<{ user: AdapterUser[] }>(
/* GraphQL */ `
mutation ($input: [AddUserInput!]!) {
addUser(input: $input) {
@@ -64,13 +66,18 @@ export function DgraphAdapter(
}
${fragments.User}
`,
- { input }
+ { input: [userWithoutId] }
)
- return format.from(result?.user[0])
+ if (!result || !result.user) {
+ throw new Error("Failed to create user")
+ }
+
+ return result.user[0]
},
+
async getUser(id: string) {
- const result = await c.run(
+ return await c.run(
/* GraphQL */ `
query ($id: ID!) {
getUser(id: $id) {
@@ -81,13 +88,12 @@ export function DgraphAdapter(
`,
{ id }
)
-
- return format.from(result)
},
+
async getUserByEmail(email: string) {
- const [user] = await c.run(
+ const result = await c.run<{ user: AdapterUser[] }>(
/* GraphQL */ `
- query ($email: String = "") {
+ query ($email: String!) {
queryUser(filter: { email: { eq: $email } }) {
...UserFragment
}
@@ -96,40 +102,45 @@ export function DgraphAdapter(
`,
{ email }
)
- return format.from(user)
+ return result?.user?.[0] || null
},
- async getUserByAccount(provider_providerAccountId: {
+
+ async getUserByAccount({
+ provider,
+ providerAccountId,
+ }: {
provider: string
providerAccountId: string
}) {
- const [account] = await c.run(
+ const result = await c.run<{ user: AdapterUser }[]>(
/* GraphQL */ `
- query ($providerAccountId: String = "", $provider: String = "") {
+ query ($providerAccountId: String!, $provider: String!) {
queryAccount(
filter: {
- and: {
- providerAccountId: { eq: $providerAccountId }
- provider: { eq: $provider }
- }
+ and: [
+ { providerAccountId: { eq: $providerAccountId } }
+ { provider: { eq: $provider } }
+ ]
}
) {
user {
...UserFragment
}
- id
}
}
${fragments.User}
`,
- provider_providerAccountId
+ { providerAccountId, provider }
)
- return format.from(account?.user)
+ return result?.[0]?.user || null
},
- async updateUser({ id, ...input }: { id: string }) {
- const result = await c.run(
+
+ async updateUser(user: Partial & { id: string }) {
+ const { id, ...update } = user
+ const result = await c.run<{ user: AdapterUser[] }>(
/* GraphQL */ `
- mutation ($id: [ID!] = "", $input: UserPatch) {
- updateUser(input: { filter: { id: $id }, set: $input }) {
+ mutation ($id: ID!, $input: UserPatch!) {
+ updateUser(input: { filter: { id: [$id] }, set: $input }) {
user {
...UserFragment
}
@@ -137,55 +148,92 @@ export function DgraphAdapter(
}
${fragments.User}
`,
- { id, input }
+ { id, input: update }
)
- return format.from(result.user[0])
+
+ if (!result?.user?.[0]) {
+ throw new Error("Failed to update user")
+ }
+
+ return result.user[0]
},
- async deleteUser(id: string) {
- const result = await c.run(
+
+ async deleteUser(userId: string) {
+ const fetchResult = await c.run<{
+ user: (AdapterUser & {
+ accounts: { id: string }[]
+ sessions: { sessionToken: string }[]
+ })[]
+ }>(
/* GraphQL */ `
- mutation ($id: [ID!] = "") {
- deleteUser(filter: { id: $id }) {
- numUids
- user {
- accounts {
- id
- }
- sessions {
- id
- }
+ query ($userId: ID!) {
+ getUser(id: $userId) {
+ ...UserFragment
+ accounts {
+ id
+ }
+ sessions {
+ sessionToken
}
}
}
+ ${fragments.User}
`,
- { id }
+ { userId }
)
- const deletedUser = format.from(result.user[0])
+ const user = fetchResult?.user?.[0]
+ if (!user) {
+ return null // User not found
+ }
- await c.run(
+ const deleteResult = await c.run<{ user: AdapterUser[] }>(
/* GraphQL */ `
- mutation ($accounts: [ID!], $sessions: [ID!]) {
- deleteAccount(filter: { id: $accounts }) {
- numUids
- }
- deleteSession(filter: { id: $sessions }) {
- numUids
+ mutation ($userId: [ID!]!) {
+ deleteUser(filter: { id: $userId }) {
+ user {
+ ...UserFragment
+ }
}
}
+ ${fragments.User}
`,
- {
- sessions: deletedUser.sessions.map((x: any) => x.id),
- accounts: deletedUser.accounts.map((x: any) => x.id),
- }
+ { userId: [userId] }
)
+ const deletedUser = deleteResult?.user?.[0]
+ if (!deletedUser) {
+ throw new Error("Failed to delete user")
+ }
+
+ if (user.accounts.length > 0 || user.sessions.length > 0) {
+ await c.run<{
+ deleteAccount: { numUids: number }
+ deleteSession: { numUids: number }
+ }>(
+ /* GraphQL */ `
+ mutation ($accountIds: [ID!], $sessionTokens: [String!]) {
+ deleteAccount(filter: { id: $accountIds }) {
+ numUids
+ }
+ deleteSession(filter: { sessionToken: $sessionTokens }) {
+ numUids
+ }
+ }
+ `,
+ {
+ accountIds: user.accounts.map((x) => x.id),
+ sessionTokens: user.sessions.map((x) => x.sessionToken),
+ }
+ )
+ }
+
return deletedUser
},
- async linkAccount(data: AdapterAccount) {
- const { userId, ...input } = data
- await c.run(
+ async linkAccount(account: AdapterAccount) {
+ const { id, userId, ...inputWithoutId } = account
+ const result = await c.run<{ account: AdapterAccount[] }>(
/* GraphQL */ `
mutation ($input: [AddAccountInput!]!) {
addAccount(input: $input) {
@@ -196,64 +244,84 @@ export function DgraphAdapter(
}
${fragments.Account}
`,
- { input: { ...input, user: { id: userId } } }
+ { input: [{ ...inputWithoutId, user: { id: userId } }] }
)
- return data
+
+ if (!result?.account?.[0]) {
+ throw new Error("Failed to link account")
+ }
+
+ return result.account[0]
},
- async unlinkAccount(provider_providerAccountId: {
- provider: string
- providerAccountId: string
- }) {
- await c.run(
+
+ async unlinkAccount(
+ account: Pick
+ ) {
+ const { provider, providerAccountId } = account
+ const result = await c.run<{ account: AdapterAccount[] }>(
/* GraphQL */ `
- mutation ($providerAccountId: String = "", $provider: String = "") {
+ mutation ($providerAccountId: String!, $provider: String!) {
deleteAccount(
filter: {
- and: {
- providerAccountId: { eq: $providerAccountId }
- provider: { eq: $provider }
- }
+ and: [
+ { providerAccountId: { eq: $providerAccountId } }
+ { provider: { eq: $provider } }
+ ]
}
) {
- numUids
+ account {
+ ...AccountFragment
+ }
}
}
+ ${fragments.Account}
`,
- provider_providerAccountId
+ { providerAccountId, provider }
)
+
+ return result?.account?.[0]
},
async getSessionAndUser(sessionToken: string) {
- const [sessionAndUser] = await c.run(
+ const result = await c.run<{
+ getSessionAndUser: {
+ session: AdapterSession
+ user: AdapterUser
+ }[]
+ }>(
/* GraphQL */ `
- query ($sessionToken: String = "") {
- querySession(filter: { sessionToken: { eq: $sessionToken } }) {
- ...SessionFragment
+ query GetSessionAndUser($sessionToken: String!) {
+ getSessionAndUser(filter: { sessionToken: { eq: $sessionToken } }) {
+ session {
+ ...SessionFragment
+ }
user {
...UserFragment
}
}
}
- ${fragments.User}
${fragments.Session}
+ ${fragments.User}
`,
{ sessionToken }
)
- if (!sessionAndUser) return null
-
- const { user, ...session } = sessionAndUser
- return {
- user: format.from(user),
- session: { ...format.from(session), userId: user.id },
+ const sessionAndUser = result?.getSessionAndUser?.[0]
+ if (!sessionAndUser || !sessionAndUser.user || !sessionAndUser.session) {
+ return null
}
+
+ return sessionAndUser
},
- async createSession(data: AdapterSession) {
- const { userId, ...input } = data
- await c.run(
+ async createSession({ sessionToken, userId, expires }: AdapterSession) {
+ if (userId === undefined) {
+ throw new Error("userId is undefined in createSession")
+ }
+
+ const result = await c.run<{ session: AdapterSession[] }>(
/* GraphQL */ `
- mutation ($input: [AddSessionInput!]!) {
+ mutation CreateSession($input: [AddSessionInput!]!) {
addSession(input: $input) {
session {
...SessionFragment
@@ -262,73 +330,74 @@ export function DgraphAdapter(
}
${fragments.Session}
`,
- { input: { ...input, user: { id: userId } } }
+ { input: [{ sessionToken, expires, user: { id: userId } }] }
)
- return data as any
+ if (!result?.session?.[0]) {
+ throw new Error("Failed to create session")
+ }
+
+ return result.session[0]
},
- async updateSession({ sessionToken, ...input }: { sessionToken: string }) {
- const result = await c.run(
+
+ async deleteSession(sessionToken: string) {
+ const result = await c.run<{ session: AdapterSession[] }>(
/* GraphQL */ `
- mutation ($input: SessionPatch = {}, $sessionToken: String) {
- updateSession(
- input: {
- filter: { sessionToken: { eq: $sessionToken } }
- set: $input
- }
- ) {
+ mutation DeleteSession($sessionToken: String!) {
+ deleteSession(filter: { sessionToken: { eq: $sessionToken } }) {
session {
...SessionFragment
- user {
- id
- }
}
}
}
${fragments.Session}
`,
- { sessionToken, input }
- )
- const session = format.from(result.session[0])
-
- if (!session?.user?.id) return null
-
- return { ...session, userId: session.user.id }
- },
- async deleteSession(sessionToken: string) {
- await c.run(
- /* GraphQL */ `
- mutation ($sessionToken: String = "") {
- deleteSession(filter: { sessionToken: { eq: $sessionToken } }) {
- numUids
- }
- }
- `,
{ sessionToken }
)
+
+ return result?.session?.[0] || null
},
async createVerificationToken(input: VerificationToken) {
- const result = await c.run(
+ const result = await c.run<{
+ verificationToken: VerificationToken[]
+ }>(
/* GraphQL */ `
- mutation ($input: [AddVerificationTokenInput!]!) {
+ mutation CreateVerificationToken(
+ $input: [AddVerificationTokenInput!]!
+ ) {
addVerificationToken(input: $input) {
- numUids
+ verificationToken {
+ identifier
+ token
+ expires
+ }
}
}
`,
- { input }
+ { input: [input] }
)
- return format.from(result)
+
+ const createdToken = result?.verificationToken?.[0]
+ if (!createdToken) {
+ throw new Error("Failed to create verification token")
+ }
+
+ return createdToken
},
async useVerificationToken(params: { identifier: string; token: string }) {
- const result = await c.run(
+ const result = await c.run<{
+ verificationToken: VerificationToken[]
+ }>(
/* GraphQL */ `
- mutation ($token: String = "", $identifier: String = "") {
+ mutation UseVerificationToken($token: String!, $identifier: String!) {
deleteVerificationToken(
filter: {
- and: { token: { eq: $token }, identifier: { eq: $identifier } }
+ and: [
+ { token: { eq: $token } }
+ { identifier: { eq: $identifier } }
+ ]
}
) {
verificationToken {
@@ -341,24 +410,7 @@ export function DgraphAdapter(
params
)
- return format.from(result.verificationToken[0])
+ return result?.verificationToken?.[0] || null
},
}
}
-
-export const format = {
- from(object?: Record): T | null {
- const newObject: Record = {}
- if (!object) return null
- for (const key in object) {
- const value = object[key]
- if (isDate(value)) {
- newObject[key] = new Date(value)
- } else {
- newObject[key] = value
- }
- }
-
- return newObject as T
- },
-}
diff --git a/packages/adapter-dgraph/src/lib/client.ts b/packages/adapter-dgraph/src/lib/client.ts
index de0fd1b143..a9c366d50b 100644
--- a/packages/adapter-dgraph/src/lib/client.ts
+++ b/packages/adapter-dgraph/src/lib/client.ts
@@ -8,14 +8,19 @@ export interface DgraphClientParams {
* [Dgraph Cloud Authentication](https://dgraph.io/docs/cloud/cloud-api/overview/#dgraph-cloud-authentication)
*/
authToken: string
- /** [Using JWT and authorization claims](https://dgraph.io/docs/graphql/authorization/authorization-overview#using-jwts-and-authorization-claims) */
+ /**
+ * [Using JWT and authorization claims](https://dgraph.io/docs/graphql/authorization/authorization-overview#using-jwts-and-authorization-claims)
+ */
jwtSecret?: string
/**
- * @default "RS256"
+ * @default "HS512"
+ *
+ * Note: The default JWT algorithm is now HS512, since [Dgraph now supports HS512 algorithm](https://github.com/dgraph-io/dgraph/pull/8912) and it aligns with NextAuth.js defaults.
+ * HS256 and RS256 are still supported for backward compatibility.
*
* [Using JWT and authorization claims](https://dgraph.io/docs/graphql/authorization/authorization-overview#using-jwts-and-authorization-claims)
*/
- jwtAlgorithm?: "HS256" | "RS256"
+ jwtAlgorithm?: "HS512" | "HS256" | "RS256"
/**
* @default "Authorization"
*
@@ -26,9 +31,16 @@ export interface DgraphClientParams {
export class DgraphClientError extends Error {
name = "DgraphClientError"
+ query: string
+ variables: any
+ originalErrors: any[]
+
constructor(errors: any[], query: string, variables: any) {
- super(errors.map((error) => error.message).join("\n"))
- console.error({ query, variables })
+ super(`GraphQL query failed with ${errors.length} errors.`)
+ this.originalErrors = errors
+ this.query = query
+ this.variables = variables
+ Error.captureStackTrace(this, this.constructor)
}
}
@@ -46,15 +58,16 @@ export function client(params: DgraphClientParams) {
endpoint,
authToken,
jwtSecret,
- jwtAlgorithm = "HS256",
+ jwtAlgorithm = "HS512",
authHeader = "Authorization",
} = params
+
const headers: HeadersInit = {
"Content-Type": "application/json",
"X-Auth-Token": authToken,
}
- if (authHeader && jwtSecret) {
+ if (jwtSecret) {
headers[authHeader] = jwt.sign({ nextAuth: true }, jwtSecret, {
algorithm: jwtAlgorithm,
})
@@ -65,17 +78,32 @@ export function client(params: DgraphClientParams) {
query: string,
variables?: Record
): Promise {
- const response = await fetch(endpoint, {
- method: "POST",
- headers,
- body: JSON.stringify({ query, variables }),
- })
+ try {
+ const response = await fetch(endpoint, {
+ method: "POST",
+ headers,
+ body: JSON.stringify({ query, variables }),
+ })
+
+ if (!response.ok) {
+ throw new Error(
+ `HTTP error ${response.status}: ${response.statusText}`
+ )
+ }
+
+ const { data = {}, errors } = await response.json()
+ if (errors?.length) {
+ throw new DgraphClientError(errors, query, variables)
+ }
- const { data = {}, errors } = await response.json()
- if (errors?.length) {
- throw new DgraphClientError(errors, query, variables)
+ return Object.values(data)[0] as T | null
+ } catch (error) {
+ console.error(`Error executing GraphQL query: ${error}`, {
+ query,
+ variables,
+ })
+ throw error
}
- return Object.values(data)[0] as any
},
}
}
diff --git a/packages/adapter-dgraph/test/hs512.key b/packages/adapter-dgraph/test/hs512.key
new file mode 100644
index 0000000000..b8cab49b38
--- /dev/null
+++ b/packages/adapter-dgraph/test/hs512.key
@@ -0,0 +1 @@
+GnaXu1rdxsquR+3y17VWU+/o4rs+URBZJqQwEizWLec=
diff --git a/packages/adapter-dgraph/test/index.test.ts b/packages/adapter-dgraph/test/index.test.ts
index e6047784d2..8f1978da79 100644
--- a/packages/adapter-dgraph/test/index.test.ts
+++ b/packages/adapter-dgraph/test/index.test.ts
@@ -7,13 +7,22 @@ import path from "path"
import type { DgraphClientParams } from "../src"
+let jwtSecret;
+try {
+ jwtSecret = fs.readFileSync(path.join(process.cwd(), "/test/hs512.key"), {
+ encoding: "utf8",
+ });
+ console.log("Loaded JWT secret from file", jwtSecret);
+} catch (error) {
+ console.error("Failed to load JWT secret from file:", error);
+ process.exit(1);
+}
+
const params: DgraphClientParams = {
endpoint: "http://localhost:8080/graphql",
authToken: "test",
- jwtAlgorithm: "RS256",
- jwtSecret: fs.readFileSync(path.join(process.cwd(), "/test/private.key"), {
- encoding: "utf8",
- }),
+ jwtAlgorithm: "HS512",
+ jwtSecret: jwtSecret,
}
/** TODO: Add test to `dgraphClient` */
diff --git a/packages/adapter-dgraph/test/private.key b/packages/adapter-dgraph/test/private.key
deleted file mode 100644
index 285573fee3..0000000000
--- a/packages/adapter-dgraph/test/private.key
+++ /dev/null
@@ -1,51 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-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
------END RSA PRIVATE KEY-----
\ No newline at end of file
diff --git a/packages/adapter-dgraph/test/public.key b/packages/adapter-dgraph/test/public.key
deleted file mode 100644
index 9c694ed1ba..0000000000
--- a/packages/adapter-dgraph/test/public.key
+++ /dev/null
@@ -1,14 +0,0 @@
------BEGIN PUBLIC KEY-----
-MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxqyvd82VacXMBLUADZt+
-euSNUNJ276XgvH4HW4ms5iQZDgYIPKxyaZ+wk8EMYSB1dymJ3WQpm0JKHqgTW+z/
-edfYFQXkduHN/zoIpxMAMyZGsTBidGo0xJSHTCDCdYCCBlG9R1ljjhf0l9ChBP7W
-7lSXaRU/XS/tMH1qYMpsUwDav4G/RDI3A4t29JRGqU4mnFa5o3XBCxU4ANCp1JaQ
-evzAYox8EGPZ1YZGmhRgca51dBeed9QKqWjfXP4wboC1ppglm+kPgFUaCiXB8Kyf
-IixhlvzZiO4RLvZw+cILt586vXGzNy49eVUTiIOoTZuG/79pCeBS8BCbB4l6y274
-y42hUN83gHxQ32Y++DI40jz5iGN85Dj6yDDjKwvwqVhCx/kVJFrmyrTJz/E0cp38
-FeIi7D6e0eXj7G97K+wkNdc4oTs1DsDPzhO/7wxQOZIjvNp+DJAfxin5MbM+UKoo
-pvJj3sUMHVrTteWxZg94mmLjg2KnJYBuSn8kiFPYQ0F5MjE7df4tDDTGJ/VEFIG5
-EkQffaNYhW0Z5ORLvW1R1Yd1/ew3UWo+mZ7XAUGLF6clsWSQvzSrrNMYzCk5Fa0L
-wvMtQdEVLL3q7/KsEHD7N78EVlmEDlOtC21UidUqXnawCE1QIjAHqFsNNPR2j0lg
-OoEjrGdzrvUg6hNV9m6CbSECAwEAAQ==
------END PUBLIC KEY-----
\ No newline at end of file
diff --git a/packages/adapter-dgraph/test/test.sh b/packages/adapter-dgraph/test/test.sh
index 46f4b62054..60020ee21c 100755
--- a/packages/adapter-dgraph/test/test.sh
+++ b/packages/adapter-dgraph/test/test.sh
@@ -1,29 +1,102 @@
#!/usr/bin/env bash
-CONTAINER_NAME=authjs-dgraph
+# ==============================================================================
+# Initial Configuration
+# ==============================================================================
+CONTAINER_NAME="authjs-dgraph"
+SCHEMA_FILE="src/lib/graphql/schema.gql"
+HS512_KEY=$(test/test.schema.gql
-PUBLIC_KEY=$(sed 's/$/\\n/' test/public.key | tr -d '\n')
-echo "# Dgraph.Authorization {\"VerificationKey\":\"$PUBLIC_KEY\",\"Namespace\":\"https://dgraph.io/jwt/claims\",\"Header\":\"Authorization\",\"Algo\":\"RS256\"}" >>test/test.schema.gql
+ echo "----------------------------------------"
+ for ((i=1; i<=max_attempts; i++)); do
+ if $command; then
+ echo "${success_message}"
+ # Interactive or non-interactive handling
+ # [[ -t 0 ]] && read -p "Pausing, press any key to continue..."
+ return 0
+ else
+ echo "${fail_message} attempt $i"
+ fi
+ sleep ${sleep_duration}
+ done
+ echo "${fail_message}: Condition not met after ${max_attempts} attempts."
+ echo "----------------------------------------"
+ return 1
+}
-curl -X POST localhost:8080/admin/schema --data-binary '@test/test.schema.gql'
+# Checks if Dgraph server is up and accessible
+function check_dgraph_ready {
+ curl -sSf "http://localhost:${PORT_8080}/health" >/dev/null 2>&1
+}
-printf "\nWaiting 5s for schema to be uploaded..." && sleep 5
+# Function to upload schema and check success
+function upload_schema_and_check {
+ local response=$(echo "${FINAL_SCHEMA}" | curl -s -w "%{http_code}" -o /tmp/dgraph_response.json -X POST "http://localhost:${PORT_8080}/admin/schema" --data-binary "@-")
+ local http_code=$(echo "${response}" | tail -n1) # Extract only the HTTP status code
+ [[ "$http_code" -eq 200 ]]
+}
-# Always stop container, but exit with 1 when tests are failing
-if vitest run -c ../utils/vitest.config.ts; then
- docker stop "${CONTAINER_NAME}"
+# ==============================================================================
+# Main Execution
+# ==============================================================================
+
+# Check and remove any existing container
+if docker ps -a | grep -q "${CONTAINER_NAME}"; then
+ echo "Stopping and removing existing container..."
+ docker stop "${CONTAINER_NAME}"
+ docker rm "${CONTAINER_NAME}"
+fi
+
+# Start the Dgraph container
+echo "----------------------------------------"
+echo "Starting Dgraph container..."
+docker run -d --rm -p "${PORT_8080}:${PORT_8080}" -p "${PORT_9080}:${PORT_9080}" --name "${CONTAINER_NAME}" "${CONTAINER_IMAGE}"
+
+# Wait for Dgraph to start
+if ! wait_for_condition check_dgraph_ready "Dgraph is up!" "Dgraph not up..."; then
+ echo "Dgraph failed to start."
+ docker stop "${CONTAINER_NAME}"
+ exit 1
+fi
+
+# Prepare the Dgraph schema without the last line
+SCHEMA=$(sed '$ d' "${SCHEMA_FILE}")
+
+# Append the Dgraph authorization header
+FINAL_SCHEMA="${SCHEMA}
+# Dgraph.Authorization {\"VerificationKey\":\"${HS512_KEY}\",\"Namespace\":\"https://dgraph.io/jwt/claims\",\"Header\":\"Authorization\",\"Algo\":\"HS512\"}"
+
+# Proceed with uploading the schema to Dgraph, include proper handling for multiline JSON
+if echo "${FINAL_SCHEMA}" | curl -s -w "%{http_code}" -o /tmp/dgraph_response.json -X POST "http://localhost:${PORT_8080}/admin/schema" --data-binary "@-"; then
+ echo "Schema has been successfully uploaded."
+else
+ echo "Failed to upload schema."
+ exit 1
+fi
+
+# Run tests
+if vitest run -c "../utils/vitest.config.ts"; then
+ echo "Tests passed."
else
- docker stop "${CONTAINER_NAME}" && exit 1
+ echo "Tests failed."
fi
-rm test/test.schema.gql
+# Always stop container after tests
+echo "----------------------------------------"
+echo "Stopping Dgraph container..."
+docker stop "${CONTAINER_NAME}"
\ No newline at end of file