Skip to content

Commit

Permalink
break: Remove luxon (#160)
Browse files Browse the repository at this point in the history
* feat: Remove luxon

* puuu

---------

Co-authored-by: Nicola Marcacci Rossi <nicola@smartive.ch>
  • Loading branch information
nickredmark and nicola-smartive committed Apr 30, 2024
1 parent e2a1695 commit f9885e3
Show file tree
Hide file tree
Showing 30 changed files with 494 additions and 152 deletions.
6 changes: 4 additions & 2 deletions .gqmrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@
"modelsPath": "tests/utils/models.ts",
"generatedFolderPath": "tests/generated",
"graphqlQueriesPath": "tests",
"gqlModule": "../../../src"
}
"gqlModule": "../../../src",
"knexfilePath": "knexfile.ts",
"dateLibrary": "luxon"
}
1 change: 0 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,5 @@ services:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_HOST_AUTH_METHOD: trust
TZ: 'Europe/Zurich'
ports:
- '5432:5432'
12 changes: 6 additions & 6 deletions docs/docs/1-tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,10 @@ const nextConfig = {
};
```

Install `@smartive/graphql-magic`:
Install `@smartive/graphql-magic` and needed dependencies:

```bash
npm install @smartive/graphql-magic
npm install @smartive/graphql-magic @graphql-codegen/typescript-compatibility
```

Run the gqm cli:
Expand All @@ -123,7 +123,6 @@ services:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_HOST_AUTH_METHOD: trust
TZ: 'Europe/Zurich'
ports:
- '5432:5432'
```
Expand Down Expand Up @@ -213,14 +212,14 @@ Now let's implement the `// TODO: get user` part in the `src/graphql/execute.ts`
```ts
const session = await getSession();
if (session) {
let dbUser = await db('User').where({ authId: session.user.sid }).first();
let dbUser = await db('User').where({ authId: session.user.sub }).first();
if (!user) {
await db('User').insert({
id: randomUUID(),
authId: session.user.sid,
authId: session.user.sub,
username: session.user.nickname
})
dbUser = await db('User').where({ authId: session.user.sid }).first();
dbUser = await db('User').where({ authId: session.user.sub }).first();
}
user = {
...dbUser!,
Expand Down Expand Up @@ -322,6 +321,7 @@ Let's make a blog out of this app by adding new models in `src/config/models.ts`
updatable: true,
}
]
}
```

Generate and run the new migrations and generate the new models:
Expand Down
4 changes: 1 addition & 3 deletions docs/docs/6-graphql-server.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Graphql server

## `executeQuery`
## `executeGraphql`

`graphql-magic` generates an `execute.ts` file for you, with this structure:

Expand All @@ -9,7 +9,6 @@ import knexConfig from "@/knexfile";
import { Context, User, execute } from "@smartive/graphql-magic";
import { randomUUID } from "crypto";
import { knex } from 'knex';
import { DateTime } from "luxon";
import { models } from "../config/models";

export const executeGraphql = async <T, V = undefined>(
Expand All @@ -32,7 +31,6 @@ export const executeGraphql = async <T, V = undefined>(
user,
models: models,
permissions: { ADMIN: true, UNAUTHENTICATED: true },
now: DateTime.local(),
});
await db.destroy();

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/7-graphql-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ module.exports = {

### Server side

On the server side, and with `next.js` server actions, a graphql api becomes unnecessary, and you can execute query directly using `executeQuery`:
On the server side, and with `next.js` server actions, a graphql api becomes unnecessary, and you can execute queries directly using `executeGraphql`:

```tsx
import { GetMeQuery, GetPostsQuery } from "@/generated/client";
Expand Down
145 changes: 145 additions & 0 deletions docs/docs/8-permissions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# Permissions

Permissions are an object provided to the `execute` function.
The root keys of the objects are the user roles (including the special role `UNAUTHENTICATED` for when the user object is undefined).

```ts
execute({
...
user: {
// ...
role: 'USER' // this is the role that will apply
},
permissions: {
ADMIN: ... // admin permissions
USER: ... // user permissions
UNAUTHENTICATED: ... // permissions for unauthenticated users
}
})
```

## Grant all permissions

```ts
ADMIN: true
```

## Actions

- READ
- CREATE
- UPDATE
- DELETE
- RESTORE
- LINK

Grant all READ permissions on a specific table:

```ts
User: {} // same as User: { READ: true }
```

Grant actions other than READ on a specific table:

```ts
User: { CREATE: true }
```

## Linking

The LINK permission doesn't give one permission to modify these records,
but to use them as options for foreign keys in _other_ records that one does have the permission to CREATE/UPDATE.

So, for example, if you want a manager to be able to assign a user to a task, you would model it like this:

```ts
MANAGER: {
User: { LINK: true }
Task: { UPDATE: true }
}
```

## Narrowing the record set

Use WHERE, which accepts simple table column/value pairs that are then used as sql where filter.

```ts
GUEST: {
Post: {
WHERE: { published: true }
}
}
```

## Derivative permissions

In the following way you can define permissions that follow the relational structure.

"If I can read a board (because it is public), then I can follow all threads and their authors, and their replies, which I can like."

```ts
GUEST: {
Board: {
WHERE: { public: true }
RELATIONS: {
threads: {
RELATIONS: {
LINK: true,
author: {},
replies: {
CREATE: true,
LINK: true,
likes: {
CREATE: true
}
}
}
}
}
}
}
```

## Me

You can use `me` as a special `User` record set containing just yourself.

```ts
EMPLOYEE: {
me: {
UPDATE: true,
LINK: true,
RELATIONS: {
tasks: {
UPDATE: true
}
}
}
}
```

Note: for ownership patterns (I own what I create, I can update what I own),
one must use implicitly generated relationships such as `createdPosts`, `updatedPosts`, `deletedPosts`:

```ts
GUEST: {
me: {
LINK: true,
RELATIONS: {
createdPosts: {
// "guests can create a post with the field createdBy === me"
CREATE: true,
UPDATE: true
},
updatedPosts: {
// this is necessary or it won't be possible to create a post
// "guests can create a post with the field updatedBy === me"
CREATE: true

// this is *not* necessary because the user can already update posts they created
// UPDATE: true
}
}
}
}
```
4 changes: 2 additions & 2 deletions knexfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ for (const oid of Object.values(numberOids)) {
types.setTypeParser(oid, Number);
}

const config = {
const knexConfig = {
client: 'postgresql',
connection: {
host: process.env.DATABASE_HOST,
Expand All @@ -28,4 +28,4 @@ const config = {
},
} as const;

export default config;
export default knexConfig;
45 changes: 37 additions & 8 deletions migrations/20230912185644_setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@ import { Knex } from 'knex';
export const up = async (knex: Knex) => {
await knex.raw(`CREATE TYPE "someEnum" AS ENUM ('A','B','C')`);

await knex.raw(`CREATE TYPE "role" AS ENUM ('ADMIN','USER')`);

await knex.raw(`CREATE TYPE "reactionType" AS ENUM ('Review','Question','Answer')`);

await knex.schema.createTable('User', (table) => {
table.uuid('id').notNullable().primary();
table.string('username', undefined).nullable();
await knex.schema.alterTable('User', (table) => {
table.string('username', undefined);
});

await knex.schema.alterTable('User', (table) => {
table.enum('role', null as any, {
useNative: true,
existingType: true,
enumName: 'role',
}).nullable();
}).nullable().alter();
});

await knex.schema.createTable('AnotherObject', (table) => {
Expand Down Expand Up @@ -101,9 +101,29 @@ export const up = async (knex: Knex) => {
table.decimal('rating', undefined, undefined).nullable();
});

await knex.schema.alterTable('User', (table) => {
table.dropColumn('createdAt');
table.dropColumn('updatedAt');
});

};

export const down = async (knex: Knex) => {
await knex.schema.alterTable('User', (table) => {
table.timestamp('createdAt');
table.timestamp('updatedAt');
});

await knex('User').update({
createdAt: 'TODO',
updatedAt: 'TODO',
});

await knex.schema.alterTable('User', (table) => {
table.timestamp('createdAt').notNullable().alter();
table.timestamp('updatedAt').notNullable().alter();
});

await knex.schema.dropTable('ReviewRevision');

await knex.schema.dropTable('Review');
Expand All @@ -118,10 +138,19 @@ export const down = async (knex: Knex) => {

await knex.schema.dropTable('AnotherObject');

await knex.schema.dropTable('User');
await knex.schema.alterTable('User', (table) => {
table.enum('role', null as any, {
useNative: true,
existingType: true,
enumName: 'role',
}).notNullable().alter();
});

await knex.schema.alterTable('User', (table) => {
table.dropColumn('username');
});

await knex.raw('DROP TYPE "reactionType"');
await knex.raw('DROP TYPE "role"');
await knex.raw('DROP TYPE "someEnum"');
};

16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,14 @@
"@graphql-codegen/typescript-resolvers": "^4.0.1",
"code-block-writer": "^12.0.0",
"commander": "^11.0.0",
"dayjs": "^1.11.10",
"dotenv": "^16.3.1",
"graphql": "^15.8.0",
"inflection": "^2.0.1",
"knex": "^3.0.1",
"knex-schema-inspector": "^3.1.0",
"lodash": "^4.17.21",
"luxon": "^3.3.0",
"luxon": "^3.4.4",
"pg": "^8.11.3",
"simple-git": "^3.21.0",
"ts-morph": "^19.0.0",
Expand Down

0 comments on commit f9885e3

Please sign in to comment.