move sample apps outside graphql-engine

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/8904
GitOrigin-RevId: d0c3000dff8615ff48f33c6a8ebf083d055ac0c3
This commit is contained in:
Praveen Durairaju 2023-05-23 21:08:18 +05:30 committed by hasura-bot
parent 84ff37f8fb
commit 3d533bee61
1030 changed files with 1 additions and 562157 deletions

View File

@ -0,0 +1 @@
## This project has been moved to [hasura/sample-apps](https://github.com/hasura/sample-apps)

View File

@ -1,4 +0,0 @@
firebase.json
.firebaserc
node_modules
config.js

View File

@ -1,64 +0,0 @@
# Firebase Auth + Hasura JWT
Barebones example to show how to have Firebase Auth integrated with Hasura JWT mode.
## Firebase Auth
Firebase has few ways of implementing custom JWT claims in Firebase Auth:
1. Have firebase generate the JWTs, then customize them from your backend using
Firebase Admin SDK [[docs]](https://firebase.google.com/docs/auth/admin/custom-claims#defining_roles_via_an_http_request)
2. Use Firebase cloud functions, and listen to user creation events to add
custom claims to generated JWT [[docs]](https://firebase.google.com/docs/auth/admin/custom-claims#defining_roles_via_firebase_functions_on_user_creation)
3. Have your own backend server, which generates custom tokens [[docs]](https://firebase.google.com/docs/auth/admin/create-custom-tokens)
4. Have your own backend scripts (not initiated by the client) to update user custom claims [[docs]](https://firebase.google.com/docs/auth/admin/custom-claims#defining_roles_via_backend_script)
## Add custom claims in Firebase
In this example, we are choosing the option 2 from above. But this can be done via any of the above methods. [Firebase docs](https://firebase.google.com/docs/auth/admin/custom-claims) have extensive documentation on how to achieve this via different methods.
This example is adapted from [this guide](https://firebase.google.com/docs/auth/admin/custom-claims#defining_roles_via_firebase_functions_on_user_creation).
### Pre-requisites
This example assumes that you already have Firebase Auth setup for your app.
### Add the cloud function
Deploy the cloud function inside `functions/` folder:
```shell
firebase deploy --only functions
```
Customize the code to add your logic of assigning roles in the custom claims.
This cloud function is using the `onCreate` trigger. So whenever a user is created, this function is run.
### Client-side code
The client-side code is in `app/` folder.
## Configure Hasura to start in JWT mode
- Deploy GraphQL Engine on Hasura Cloud and setup PostgreSQL via Heroku:
[![Deploy to Hasura Cloud](https://graphql-engine-cdn.hasura.io/img/deploy_to_hasura.png)](https://cloud.hasura.io/signup)
After deploying, add the following environment variables to configure JWT mode:
```
HASURA_GRAPHQL_ADMIN_SECRET : youradminsecretkey
```
```
HASURA_GRAPHQL_JWT_SECRET: {"type":"RS256", "jwk_url": "https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com", "audience": "<firebase-project-id>", "issuer": "https://securetoken.google.com/<firebase-project-id>"}
```
## Sending JWT to Hasura
Now, whenever you make a request to Hasura GraphQL engine (as an authenticated user), send the `id_token` in `Authorization` header:
```
Authorization: Bearer <firebase-id-token>
```

View File

@ -1,17 +0,0 @@
<html>
<head>
<title> Firebase Auth + Hasura JWT example </title>
</head>
<body>
<h1> Firebase Auth + Hasura JWT example </h1>
<form id="login-form">
Email: <input id="email" type="email"/>
Password: <input id="password" type="password" />
<button type="submit">Login</button>
</form>
<button id="get-token"> Get ID token </button>
<div id="id-token"></div>
<script src="https://www.gstatic.com/firebasejs/5.5.3/firebase.js"></script>
<script src="main.js"></script>
</body>
</html>

View File

@ -1,60 +0,0 @@
// Initialize Firebase
var config = {
apiKey: "xxxxxxxxxxxxxxxxxxxxxxxxxx",
authDomain: "<your-app>.firebaseapp.com",
databaseURL: "https://<your-app>.firebaseio.com",
projectId: "<your-app>",
storageBucket: "<your-app>.appspot.com",
messagingSenderId: "xxxxxxxxxxxx"
};
firebase.initializeApp(config);
document.getElementById('login-form').onsubmit = function(event) {
event.preventDefault();
let email = document.getElementById('email').value;
let pass = document.getElementById('password').value;
login(email, pass);
};
document.getElementById('get-token').onclick = function(event) {
event.preventDefault();
firebase.auth().currentUser.getIdToken(true).
then(token => document.getElementById('id-token').innerHTML = token);
};
function login(email, password) {
firebase.auth().signInWithEmailAndPassword(email, password)
.then(function(user) {
console.log('login success');
})
.catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
console.log(error);
});
let callback = null;
let metadataRef = null;
firebase.auth().onAuthStateChanged(user => {
// Remove previous listener.
if (callback) {
metadataRef.off('value', callback);
}
// On user login add new listener.
if (user) {
// Check if refresh is required.
metadataRef = firebase.database().ref('metadata/' + user.uid + '/refreshTime');
callback = (snapshot) => {
// Force refresh to pick up the latest custom claims changes.
// Note this is always triggered on first call. Further optimization could be
// added to avoid the initial trigger when the token is issued and already contains
// the latest claims.
user.getIdToken(true);
};
// Subscribe new listener to changes on that node.
metadataRef.on('value', callback);
}
});
}

View File

@ -1,41 +0,0 @@
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
// On sign up.
exports.processSignUp = functions.auth.user().onCreate(user => {
console.log(user);
// Check if user meets role criteria:
// Your custom logic here: to decide what roles and other `x-hasura-*` should the user get
let customClaims;
if (user.email && user.email.indexOf('@hasura.io') !== -1) {
customClaims = {
'https://hasura.io/jwt/claims': {
'x-hasura-default-role': 'admin',
'x-hasura-allowed-roles': ['user', 'admin'],
'x-hasura-user-id': user.uid
}
};
}
else {
customClaims = {
'https://hasura.io/jwt/claims': {
'x-hasura-default-role': 'user',
'x-hasura-allowed-roles': ['user'],
'x-hasura-user-id': user.uid
}
};
}
// Set custom user claims on this newly created user.
return admin.auth().setCustomUserClaims(user.uid, customClaims)
.then(() => {
// Update real-time database to notify client to force refresh.
const metadataRef = admin.database().ref("metadata/" + user.uid);
// Set the refresh time to the current UTC timestamp.
// This will be captured on the client to force a token refresh.
return metadataRef.set({refreshTime: new Date().getTime()});
})
.catch(error => {
console.log(error);
});
});

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +0,0 @@
{
"name": "functions",
"description": "Cloud Functions for Firebase",
"scripts": {
"serve": "firebase serve --only functions",
"shell": "firebase functions:shell",
"start": "npm run shell",
"deploy": "firebase deploy --only functions",
"logs": "firebase functions:log"
},
"dependencies": {
"firebase-admin": "~6.0.0",
"firebase-functions": "^2.0.3"
},
"private": true
}

View File

@ -1,49 +0,0 @@
# gatsby-contentful-auth0
This is the sample music playlist application demonstrating the Gatsby + Contentful Remote Join with Hasura GraphQL.
## Getting started
If you've cloned this repository, navigate into the directory and install the npm modules using this command:
```bash
npm install
```
> Note: if you clone this project through the Gatsby CLI, it will install the modules for you.
## Auth0
This application uses Auth0 to manage identity. Refer to the [Auth0 integration guide](https://hasura.io/docs/latest/graphql/core/guides/integrations/auth0-jwt.html) for the configuration.
### Modify auth config
Rename `.env.EXAMPLE` to `.env.development` (or `.env.production`) and replace `<value>` for `AUTH0_DOMAIN` and `AUTH0_CLIENTID` with your Auth0 domain prefix and your client ID. These can be found on your [client dashboard](https://manage.auth0.com/#/clients).
Replace the `<value>` for `AUTH0_CALLBACK` with the URL for your callback route. The default for development is `http://localhost:8000/callback`.
## Contentful
Contentful remote schema is added as part of the migration. Configure the environment variables in Hasura GraphQL Engine server for the types to get merged.
- `CONTENTFUL_API_KEY`
- `CONTENTFUL_API_ENDPOINT` which is of the format https://graphql.contentful.com/content/v1/spaces/<space-id>
## Migrations
Execute the following command inside `hasura` to apply the migrations
```bash
hasura metadata apply
hasura migrate apply
hasura metadata reload
```
This will create all the necessary tables, relationships and remote joins.
## Run the app
You can start the development server with the following command:
```bash
gatsby develop
```
The app runs at `localhost:8000` by default.

View File

@ -1,5 +0,0 @@
# ./.env
# Get these values at https://manage.auth0.com and create a new file called .env.development
AUTH0_DOMAIN=<value>
AUTH0_CLIENTID=<value>
AUTH0_CALLBACK=<value>

View File

@ -1,71 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# dotenv environment variables file
.env
.env.*
!.env.EXAMPLE
# gatsby files
.cache/
public
# Mac files
.DS_Store
# Yarn
yarn-error.log
.pnp/
.pnp.js
# Yarn Integrity file
.yarn-integrity

View File

@ -1,7 +0,0 @@
{
"endOfLine": "lf",
"semi": false,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5"
}

View File

@ -1,28 +0,0 @@
# gatsby-contentful-auth0
This is the sample music playlist application demonstrating the Gatsby + Contentful Remote Join with Hasura GraphQL.
## Getting started
If you've cloned this repository, navigate into the directory and install the npm modules using this command:
```bash
npm install
```
> Note: if you clone this project through the Gatsby CLI, it will install the modules for you.
## Modify auth config
Rename `.env.EXAMPLE` to `.env.development` (or `.env.production`) and replace `<value>` for `AUTH0_DOMAIN` and `AUTH0_CLIENTID` with your Auth0 domain prefix and your client ID. These can be found on your [client dashboard](https://manage.auth0.com/#/clients).
Replace the `<value>` for `AUTH0_CALLBACK` with the URL for your callback route. The default for development is `http://localhost:8000/callback`.
## Run the app
You can start the development server with the following command:
```bash
gatsby develop
```
The app runs at `localhost:8000` by default.

View File

@ -1,31 +0,0 @@
import React from "react"
import { silentAuth } from "./src/utils/auth"
class SessionCheck extends React.Component {
constructor(props) {
super(props)
this.state = {
loading: true,
}
}
handleCheckSession = () => {
this.setState({ loading: false })
}
componentDidMount() {
silentAuth(this.handleCheckSession)
}
render() {
return (
this.state.loading === false && (
<React.Fragment>{this.props.children}</React.Fragment>
)
)
}
}
export const wrapRootElement = ({ element }) => {
return <SessionCheck>{element}</SessionCheck>
}

View File

@ -1,22 +0,0 @@
const fetch = require(`node-fetch`)
const { createHttpLink } = require(`apollo-link-http`)
module.exports = {
plugins: [
{
resolve: 'gatsby-source-graphql',
options: {
typeName: 'HASURA',
fieldName: 'hasura',
createLink: (pluginOptions) => {
return createHttpLink({
uri: 'http://localhost:8080/v1/graphql',
headers: {
},
fetch,
})
},
},
},
]
};

View File

@ -1,37 +0,0 @@
// ./gatsby-node.js
// Implement the Gatsby API “onCreatePage”. This is
// called after every page is created.
exports.onCreatePage = async ({ page, actions }) => {
const { createPage } = actions
// page.matchPath is a special key that's used for matching pages
// only on the client.
if (page.path.match(/^\/account/)) {
page.matchPath = "/account/*"
// Update the page.
createPage(page)
}
}
exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
if (stage === "build-html") {
/*
* During the build step, `auth0-js` will break because it relies on
* browser-specific APIs. Fortunately, we dont need it during the build.
* Using Webpacks null loader, were able to effectively ignore `auth0-js`
* during the build. (See `src/utils/auth.js` to see how we prevent this
* from breaking the app.)
*/
actions.setWebpackConfig({
module: {
rules: [
{
test: /auth0-js/,
use: loaders.null(),
},
],
},
})
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,35 +0,0 @@
{
"name": "gatsby-contentful-hasura",
"private": true,
"description": "A music playlist app built with Gatsby, Contentful and Hasura GraphQL",
"version": "0.1.0",
"license": "MIT",
"scripts": {
"build": "gatsby build",
"develop": "gatsby develop",
"format": "prettier --write src/**/*.{js,jsx}",
"start": "npm run develop",
"serve": "gatsby serve",
"test": "echo \"Write tests! -> https://gatsby.dev/unit-testing\""
},
"dependencies": {
"@reach/router": "^1.2.1",
"apollo-cache-inmemory": "^1.6.2",
"apollo-client": "^2.6.3",
"apollo-link-http": "^1.5.15",
"apollo-link-ws": "^1.0.18",
"auth0-js": "^9.10.1",
"gatsby": "^2.3.16",
"gatsby-source-graphql": "^2.1.3",
"graphql": "^14.4.2",
"graphql-tag": "^2.10.1",
"node-fetch": "^2.6.0",
"react": "^16.8.6",
"react-apollo": "^2.5.8",
"react-dom": "^16.8.6",
"subscriptions-transport-ws": "^0.9.16"
},
"devDependencies": {
"prettier": "^1.16.4"
}
}

View File

@ -1,116 +0,0 @@
import React, { useState } from "react"
import { ApolloProvider } from 'react-apollo';
import {Query} from 'react-apollo';
import gql from 'graphql-tag';
import './style.css';
const query = gql`
query PlaylistQuery {
playlist {
name
tracks {
track_details {
name
}
track {
items {
track {
url
}
}
}
}
}
}
`;
const mutation = gql`
mutation insert_playlist($name: String!) {
insert_playlist(objects: [{
name: $name
}]) {
affected_rows
}
}
`;
const Playlist = ({ client }) => {
const [showNewPlaylist, updateShowNewPlaylist] = useState(false);
const [playlistName, setPlaylistName] = useState('');
const createPlaylist = (playlistName) => {
client.mutate({
mutation: mutation,
variables: {name: playlistName}
}).then((data) => {
//TODO: update cache
})
}
return (
<ApolloProvider client={client}>
<Query query={query}>
{({ loading, error, data, client}) => {
if (loading) {
return (<div>Loading...</div>);
}
/*
if (error) {
console.error(error);
return (<div>Error!</div>);
}
*/
return (
<div>
<div className={'newPlaylist'}>
<button
className={'playlistBtn'}
onClick={() => updateShowNewPlaylist(true)}
>+ New Playlist</button>
{showNewPlaylist ?
(
<div>
Playlist name:
<input type="text" onChange={(e) => setPlaylistName(e.target.value)} />
<button onClick={() => createPlaylist(playlistName)}>Create</button>
</div>
)
:
null
}
</div>
<h2>My Playlist</h2>
{data && data.playlist.length ? null : <div>No playlists available</div>}
{data && data.playlist.map((p, i) => (
<div key={i+p.name}>
<b>{p.name}</b>
{p.tracks.map((t,j) => {
let elem = null;
if (t.track) {
elem = (
<div key={t+j}>
<div>{t.track_details.name}</div>
<div>
{t.track.items[0] ? (
<audio controls>
<source src={t.track.items[0].track.url} type="audio/mp3" />
Your browser does not support the audio element.
</audio>
) : null}
</div>
</div>
)
}
return elem;
})}
</div>
))}
</div>
);
}}
</Query>
</ApolloProvider>
)
};
export default Playlist;

View File

@ -1,14 +0,0 @@
nav > a {
padding: 10px 5px;
}
.newPlaylistSection {
padding-top: 10px;
}
.newPlaylist {
padding: 10px 0px;
}
.playlistBtn {
padding: 5px 10px;
border-radius: 2px;
border-color: lightgreen;
}

View File

@ -1,59 +0,0 @@
import React from "react"
import { Router } from "@reach/router"
import { login, logout, isAuthenticated, getProfile } from "../utils/auth"
import { Link } from "gatsby"
import Playlist from "../components/Playlist";
import ApolloClient from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
const createApolloClient = (authToken) => {
return new ApolloClient({
link: new HttpLink({
uri: 'http://localhost:8080/v1/graphql',
headers: {
Authorization: `Bearer ${authToken}`
// 'X-Hasura-Admin-Secret': 'myadminsecretkey'
}
}),
cache: new InMemoryCache(),
});
};
const Home = ({ user }) => {
return <p>Hi, {user.name ? user.name : "friend"}!</p>
}
const Account = ({ data }) => {
if (!isAuthenticated()) {
login()
return <p>Redirecting to login...</p>
}
const user = getProfile();
const client = createApolloClient(user.idToken);
return [
<nav key="links">
<Link to="/account">Home</Link>{" "}
<Link to="/account/playlist">Playlist</Link>{" "}
<a
href="#logout"
onClick={e => {
logout()
e.preventDefault()
}}
>
Log Out
</a>
</nav>,
<Router key="router">
<Home path="/account" user={user} />
<Playlist client={client} path="/account/playlist" />
</Router>
];
}
export default Account;

View File

@ -1,10 +0,0 @@
import React from "react"
import { handleAuthentication } from "../utils/auth"
const Callback = () => {
handleAuthentication()
return <p>Loading...</p>
}
export default Callback

View File

@ -1,9 +0,0 @@
import React from "react"
import { Link } from "gatsby"
export default () => (
<div>
<p>Hello Gatsby!</p>
<Link to="/account">Go to your account</Link>
</div>
)

View File

@ -1,84 +0,0 @@
import auth0 from "auth0-js"
import { navigate } from "gatsby"
const isBrowser = typeof window !== "undefined"
const AUTH0_DOMAIN = process.env.AUTH0_DOMAIN;
const AUTH0_CLIENTID = process.env.AUTH0_CLIENTID;
const AUTH0_CALLBACK = process.env.AUTH0_CALLBACK;
const auth = isBrowser
? new auth0.WebAuth({
domain: AUTH0_DOMAIN,
clientID: AUTH0_CLIENTID,
redirectUri: AUTH0_CALLBACK,
responseType: "token id_token",
scope: "openid profile email",
})
: {}
const tokens = {
accessToken: false,
idToken: false,
expiresAt: false,
}
let user = {}
export const isAuthenticated = () => {
if (!isBrowser) {
return
}
return localStorage.getItem("isLoggedIn") === "true"
}
export const login = () => {
if (!isBrowser) {
return
}
auth.authorize()
}
const setSession = (cb = () => {}) => (err, authResult) => {
if (err) {
navigate("/")
cb()
return
}
if (authResult && authResult.accessToken && authResult.idToken) {
let expiresAt = authResult.expiresIn * 1000 + new Date().getTime()
tokens.accessToken = authResult.accessToken
tokens.idToken = authResult.idToken
tokens.expiresAt = expiresAt
user = authResult.idTokenPayload
user.idToken = authResult.idToken
localStorage.setItem("isLoggedIn", true)
navigate("/account")
cb()
}
}
export const silentAuth = callback => {
if (!isAuthenticated()) return callback()
auth.checkSession({}, setSession(callback))
}
export const handleAuthentication = () => {
if (!isBrowser) {
return
}
auth.parseHash(setSession())
}
export const getProfile = () => {
return user
}
export const logout = () => {
localStorage.setItem("isLoggedIn", false)
auth.logout()
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -1,32 +0,0 @@
function userSyncRule(user, context, callback) {
const userId = user.user_id;
const nickname = user.nickname;
const mutation = `mutation($userId: String!, $nickname: String) {
insert_users(objects: [{
id: $userId,
name: $nickname
}],
on_conflict: {
constraint: users_pkey,
update_columns: [name]
}) {
affected_rows
}
}`;
request.post(
{
headers: {
"content-type": "application/json",
"x-hasura-admin-secret": configuration.ADMIN_SECRET
},
url: "https://<your-app-domain>/v1/graphql",
body: JSON.stringify({ query: mutation, variables: { userId, nickname } })
},
function(error, response, body) {
console.log(body);
callback(error, user, context);
}
);
}

View File

@ -1,19 +0,0 @@
version: 3
endpoint: http://localhost:8080/
api_paths:
v1_query: v1/query
v2_query: v2/query
v1_metadata: v1/metadata
graphql: v1/graphql
config: v1alpha1/config
pg_dump: v1alpha1/pg_dump
version: v1/version
metadata_directory: metadata
migrations_directory: migrations
seeds_directory: seeds
actions:
kind: synchronous
handler_webhook_baseurl: http://localhost:3000
codegen:
framework: ""
output_dir: ""

View File

@ -1,6 +0,0 @@
actions: []
custom_types:
enums: []
input_objects: []
objects: []
scalars: []

View File

@ -1,14 +0,0 @@
- name: default
kind: postgres
configuration:
connection_info:
database_url:
from_env: SAMPLE_APPS_DATABASE_URL
isolation_level: read-committed
pool_settings:
connection_lifetime: 600
idle_timeout: 180
max_connections: 50
retries: 1
use_prepared_statements: false
tables: "!include default/tables/tables.yaml"

View File

@ -1,11 +0,0 @@
table:
name: album
schema: public
array_relationships:
- name: tracks
using:
foreign_key_constraint_on:
column: album_id
table:
name: track
schema: public

View File

@ -1,15 +0,0 @@
table:
name: playlist
schema: public
object_relationships:
- name: user
using:
foreign_key_constraint_on: user_id
array_relationships:
- name: playlist_tracks
using:
foreign_key_constraint_on:
column: playlist_id
table:
name: playlist_track
schema: public

View File

@ -1,10 +0,0 @@
table:
name: playlist_track
schema: public
object_relationships:
- name: playlist
using:
foreign_key_constraint_on: playlist_id
- name: track
using:
foreign_key_constraint_on: track_id

View File

@ -1,15 +0,0 @@
table:
name: track
schema: public
object_relationships:
- name: album
using:
foreign_key_constraint_on: album_id
array_relationships:
- name: playlist_tracks
using:
foreign_key_constraint_on:
column: track_id
table:
name: playlist_track
schema: public

View File

@ -1,11 +0,0 @@
table:
name: users
schema: public
array_relationships:
- name: playlists
using:
foreign_key_constraint_on:
column: user_id
table:
name: playlist
schema: public

View File

@ -1,5 +0,0 @@
- "!include public_album.yaml"
- "!include public_playlist.yaml"
- "!include public_playlist_track.yaml"
- "!include public_track.yaml"
- "!include public_users.yaml"

View File

@ -1,70 +0,0 @@
CREATE TABLE public.album (
id integer NOT NULL,
name text NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL
);
CREATE SEQUENCE public.album_id_seq
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE public.album_id_seq OWNED BY public.album.id;
CREATE TABLE public.playlist (
id integer NOT NULL,
name text NOT NULL,
user_id text NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL
);
CREATE SEQUENCE public.playlist_id_seq
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE public.playlist_id_seq OWNED BY public.playlist.id;
CREATE TABLE public.playlist_track (
track_id integer NOT NULL,
playlist_id integer NOT NULL
);
CREATE TABLE public.track (
id integer NOT NULL,
name text NOT NULL,
album_id integer NOT NULL
);
CREATE SEQUENCE public.track_id_seq
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE public.track_id_seq OWNED BY public.track.id;
CREATE TABLE public.users (
id text NOT NULL,
name text NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL
);
ALTER TABLE ONLY public.album ALTER COLUMN id SET DEFAULT nextval('public.album_id_seq'::regclass);
ALTER TABLE ONLY public.playlist ALTER COLUMN id SET DEFAULT nextval('public.playlist_id_seq'::regclass);
ALTER TABLE ONLY public.track ALTER COLUMN id SET DEFAULT nextval('public.track_id_seq'::regclass);
ALTER TABLE ONLY public.album
ADD CONSTRAINT album_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.playlist
ADD CONSTRAINT playlist_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.playlist_track
ADD CONSTRAINT playlist_track_pkey PRIMARY KEY (track_id, playlist_id);
ALTER TABLE ONLY public.track
ADD CONSTRAINT track_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.users
ADD CONSTRAINT users_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.playlist_track
ADD CONSTRAINT playlist_track_playlist_id_fkey FOREIGN KEY (playlist_id) REFERENCES public.playlist(id) ON UPDATE RESTRICT ON DELETE RESTRICT;
ALTER TABLE ONLY public.playlist_track
ADD CONSTRAINT playlist_track_track_id_fkey FOREIGN KEY (track_id) REFERENCES public.track(id) ON UPDATE RESTRICT ON DELETE RESTRICT;
ALTER TABLE ONLY public.playlist
ADD CONSTRAINT playlist_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON UPDATE RESTRICT ON DELETE RESTRICT;
ALTER TABLE ONLY public.track
ADD CONSTRAINT track_album_id_fkey FOREIGN KEY (album_id) REFERENCES public.album(id) ON UPDATE RESTRICT ON DELETE RESTRICT;

View File

@ -1,64 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.cache/
public
yarn-error.log
package-lock.json

View File

@ -1,213 +0,0 @@
# gatsby-postgres-graphql
Boilerplate to get started with Gatsby, Hasura GraphQL engine as CMS and postgres as database using the awesome plugin [gatsby-source-graphql](https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-source-graphql).
[![Edit gatsby-postgres-graphql](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/hasura/graphql-engine/tree/master/community/sample-apps/gatsby-postgres-graphql?fontsize=14)
![Gatsby Postgres GraphQL](./assets/gatsby-postgres-graphql.png)
# Tutorial
- Deploy GraphQL Engine on Hasura Cloud and setup PostgreSQL via Heroku:
[![Deploy to Hasura Cloud](https://graphql-engine-cdn.hasura.io/img/deploy_to_hasura.png)](https://cloud.hasura.io/signup)
- Get the Hasura app URL (say `gatsby-graphql.hasura.app`)
- Clone this repo:
```bash
git clone https://github.com/hasura/graphql-engine
cd graphql-engine/community/sample-apps/gatsby-postgres-graphql
```
- Create `author` table:
Open Hasura console: visit https://gatsby-graphql.hasura.app on a browser
Navigate to `Data` section in the top nav bar and create a table as follows:
![Create author table](./assets/add_table.jpg)
- Insert sample data into `author` table:
![Insert data into author table](./assets/insert_data.jpg)
Verify if the row is inserted successfully
![Insert data into author table](./assets/browse_rows.jpg)
- Install npm modules:
```bash
npm install
```
- Configure gatsby to use `gatsby-source-graphql` plugin and a connection GraphQL url to stitch the schema.
```js
{
plugins: [
{
resolve: "gatsby-source-graphql", // <- Configure plugin
options: {
typeName: "HASURA",
fieldName: "hasura", // <- fieldName under which schema will be stitched
url: process.env.GATSBY_HASURA_GRAPHQL_URL,
refetchInterval: 10 // Refresh every 10 seconds for new data
}
}
];
}
```
- Run the app:
```bash
GATSBY_HASURA_GRAPHQL_URL=https://gatsby-graphql.hasura.app/v1/graphql npm run develop
```
- Test the app
Visit [http://localhost:8000](http://localhost:8000) to view the app
![Demo app](./assets/test_app.jpg)
# Make a GraphQL query from your component using hooks
1. Create a component named `AuthorList.js`:
```js
import React from "react";
import { useQuery } from "@apollo/react-hooks";
import { gql } from "apollo-boost";
const GET_AUTHORS = gql`
query {
author {
id
name
}
}
`;
const AuthorList = () => {
const { loading, error, data } = useQuery(GET_AUTHORS);
if (loading) return "loading...";
if (error) return `error: ${error.message}`;
return (
<div>
{data.author.map((author, index) => (
<div key={index}>
<h2>{author.name}</h2>
</div>
))}
</div>
);
};
export default AuthorList;
export { GET_AUTHORS };
```
# Make a GraphQL mutation using hooks
Additional packages are needed to be added to support mutations: <br/>
`npm install @apollo/react-hooks apollo-boost isomorphic-fetch`
1. Create an `apollo.js` util file:
```js
import ApolloClient from "apollo-boost";
import fetch from "isomorphic-fetch";
export const client = new ApolloClient({
uri: process.env.GATSBY_HASURA_GRAPHQL_URL,
fetch
});
```
2. Create `gatsby-browser.js` and `gatsby-ssr.js`
```js
import React from "react";
import { ApolloProvider } from "@apollo/react-hooks";
import { client } from "./src/utils/apollo";
export const wrapRootElement = ({ element }) => (
<ApolloProvider client={client}>{element}</ApolloProvider>
);
```
3. Create an `AddAuthor.js` component to add mutations:
```js
import React, { useState } from "react";
import { useMutation } from "@apollo/react-hooks";
import { gql } from "apollo-boost";
import { GET_AUTHORS } from "./AuthorList";
const ADD_AUTHOR = gql`
mutation insert_author($name: String!) {
insert_author(objects: { name: $name }) {
returning {
id
name
}
}
}
`;
const AddAuthor = () => {
const [author, setAuthor] = useState("");
const [insert_author, { loading, error }] = useMutation(ADD_AUTHOR, {
update: (cache, { data }) => {
setAuthor("");
const existingAuthors = cache.readQuery({
query: GET_AUTHORS
});
// Add the new author to the cache
const newAuthor = data.insert_author.returning[0];
cache.writeQuery({
query: GET_AUTHORS,
data: {author: [newAuthor, ...existingAuthors.author]}
});
}
});
if (loading) return "loading...";
if (error) return `error: ${error.message}`;
const handleSubmit = event => {
event.preventDefault();
insert_author({
variables: {
name: author
}
});
};
return (
<form onSubmit={handleSubmit}>
<label htmlFor="author">
Add Author:
<input
name="author"
value={author}
onChange={event => setAuthor(event.target.value)}
/>
</label>
<button type="submit">ADD</button>
</form>
);
};
export default AddAuthor;
```
4. Run the app and test mutation. New data will be added to the top via a cache update.
# Contributing
Checkout the [contributing guide](../../../CONTRIBUTING.md#community-content) for more details.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View File

@ -1 +0,0 @@
endpoint: https://gatsby-graphql.hasura.app

View File

@ -1,7 +0,0 @@
import React from "react";
import { ApolloProvider } from "@apollo/react-hooks";
import { client } from "./src/utils/apollo";
export const wrapRootElement = ({ element }) => (
<ApolloProvider client={client}>{element}</ApolloProvider>
);

View File

@ -1,13 +0,0 @@
module.exports = {
plugins: [
{
resolve: "gatsby-source-graphql",
options: {
typeName: "HASURA",
fieldName: "hasura",
url: process.env.GATSBY_HASURA_GRAPHQL_URL,
refetchInterval: 10 // Refresh every 60 seconds for new data
}
}
]
};

View File

@ -1,7 +0,0 @@
import React from "react";
import { ApolloProvider } from "@apollo/react-hooks";
import { client } from "./src/utils/apollo";
export const wrapRootElement = ({ element }) => (
<ApolloProvider client={client}>{element}</ApolloProvider>
);

View File

@ -1,19 +0,0 @@
version: 3
endpoint: http://localhost:8080/
api_paths:
v1_query: v1/query
v2_query: v2/query
v1_metadata: v1/metadata
graphql: v1/graphql
config: v1alpha1/config
pg_dump: v1alpha1/pg_dump
version: v1/version
metadata_directory: metadata
migrations_directory: migrations
seeds_directory: seeds
actions:
kind: synchronous
handler_webhook_baseurl: http://localhost:3000
codegen:
framework: ""
output_dir: ""

View File

@ -1,6 +0,0 @@
actions: []
custom_types:
enums: []
input_objects: []
objects: []
scalars: []

View File

@ -1,13 +0,0 @@
- name: default
kind: postgres
configuration:
connection_info:
database_url:
from_env: SAMPLE_APPS_DATABASE_URL
pool_settings:
idle_timeout: 180
max_connections: 50
retries: 1
tables:
- "!include default/tables/public_author.yaml"
functions: []

View File

@ -1,15 +0,0 @@
CREATE TABLE public.author (
id integer NOT NULL,
name text NOT NULL
);
CREATE SEQUENCE public.author_id_seq
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE public.author_id_seq OWNED BY public.author.id;
ALTER TABLE ONLY public.author ALTER COLUMN id SET DEFAULT nextval('public.author_id_seq'::regclass);
ALTER TABLE ONLY public.author
ADD CONSTRAINT author_pkey PRIMARY KEY (id);

View File

@ -1,21 +0,0 @@
{
"name": "gatsby-postgres-graphql",
"description": "Gatsby simple source hasura graphql cms",
"license": "MIT",
"scripts": {
"develop": "gatsby develop",
"build": "gatsby build",
"serve": "gatsby serve"
},
"dependencies": {
"@hot-loader/react-dom": "^16.11.0",
"@apollo/react-hooks": "^3.1.3",
"apollo-boost": "^0.4.7",
"gatsby": "^2.18.17",
"gatsby-link": "^2.2.27",
"gatsby-source-graphql": "^2.1.29",
"isomorphic-fetch": "^2.2.1",
"react": "^16.12",
"react-dom": "^16.12"
}
}

View File

@ -1,62 +0,0 @@
import React, { useState } from "react";
import { useMutation } from "@apollo/react-hooks";
import { gql } from "apollo-boost";
import { GET_AUTHORS } from "./AuthorList";
const ADD_AUTHOR = gql`
mutation insert_author($name: String!) {
insert_author(objects: { name: $name }) {
returning {
id
name
}
}
}
`;
const AddAuthor = () => {
const [author, setAuthor] = useState("");
const [insert_author, { loading, error }] = useMutation(ADD_AUTHOR, {
update: (cache, { data }) => {
setAuthor("");
const existingAuthors = cache.readQuery({
query: GET_AUTHORS
});
// Add the new author to the cache
const newAuthor = data.insert_author.returning[0];
cache.writeQuery({
query: GET_AUTHORS,
data: {author: [newAuthor, ...existingAuthors.author]}
});
}
});
if (loading) return "loading...";
if (error) return `error: ${error.message}`;
const handleSubmit = event => {
event.preventDefault();
insert_author({
variables: {
name: author
}
});
};
return (
<form onSubmit={handleSubmit}>
<label htmlFor="author">
Add Author:
<input
name="author"
value={author}
onChange={event => setAuthor(event.target.value)}
/>
</label>
<button type="submit">ADD</button>
</form>
);
};
export default AddAuthor;

View File

@ -1,32 +0,0 @@
import React from "react";
import { useQuery } from "@apollo/react-hooks";
import { gql } from "apollo-boost";
const GET_AUTHORS = gql`
query {
author {
id
name
}
}
`;
const AuthorList = () => {
const { loading, error, data } = useQuery(GET_AUTHORS);
if (loading) return "loading...";
if (error) return `error: ${error.message}`;
return (
<div>
{data.author.map((author, index) => (
<div key={index}>
<h2>{author.name}</h2>
</div>
))}
</div>
);
};
export default AuthorList;
export { GET_AUTHORS };

View File

@ -1,14 +0,0 @@
import React from "react";
import AddAuthor from "../components/AddAuthor";
import AuthorList from "../components/AuthorList";
const Index = () => (
<div>
<h1>My Authors</h1>
<AddAuthor />
<AuthorList />
</div>
);
export default Index;

View File

@ -1,7 +0,0 @@
import ApolloClient from "apollo-boost";
import fetch from "isomorphic-fetch";
export const client = new ApolloClient({
uri: process.env.GATSBY_HASURA_GRAPHQL_URL,
fetch
});

View File

@ -1,15 +0,0 @@
# GraphQL Benchmarking
Uses the [Chinook sample database](https://github.com/lerocha/chinook-database). Tested on macOS Ventura 13.1.
## Instructions
1. Checkout [https://github.com/hasura/graphql-bench](https://github.com/hasura/graphql-bench) and build the docker container with `make build_local_docker_image`
1. Run `docker compose -f docker-compose.hasura.yml up -d` to bootstrap Postgres with Chinook.
1. After a few minutes check the Hasura docker logs to see if Hasura is running, which implies the database is now bootstrapped. Run `docker compose -f docker-compose.hasura.yml down`
1. Run the benchmarks `sh benchmark.sh`
1. Open the results on the GraphQL bench website [https://hasura.github.io/graphql-bench/app/web-app/](https://hasura.github.io/graphql-bench/app/web-app/)

View File

@ -1,31 +0,0 @@
#!/bin/sh
docker compose -f docker-compose.hasura.yml up -d --build
echo "Running Hasura Benchmark"
sleep 5
docker run --net=host -v "$PWD":/app/tmp -it \
graphql-bench-local query \
--config="./tmp/config.query.hasura.yaml" \
--outfile="./tmp/report.hasura.json"
docker compose -f docker-compose.hasura.yml down
echo "Hasura Benchmark done"
docker compose -f docker-compose.node.yml up -d --build
echo "Running Nodejs Benchmark"
sleep 5
docker run --net=host -v "$PWD":/app/tmp -it \
graphql-bench-local query \
--config="./tmp/config.query.node.yaml" \
--outfile="./tmp/report.nodejs.json"
docker compose -f docker-compose.node.yml down
echo "Node.js Benchmark done"

View File

@ -1,64 +0,0 @@
url: "http://host.docker.internal:8080/v1/graphql"
# url: https://benchmark-hasura-rso3d2ja7a-uc.a.run.app/v1/graphql
headers:
content-type: application/json
# "Debug" mode enables request and response logging for Autocannon and K6
# This lets you see what is happening and confirm proper behavior.
# This should be disabled for genuine benchmarks, and only used for debugging/visibility.
debug: false
queries:
# Name: Unique name for the query
- name: GetAllArtistsAlbumsAndTracks
# Tools: List of benchmarking tools to run: ['autocannon', 'k6', 'wrk2']
tools: [k6]
execution_strategy: REQUESTS_PER_SECOND
rps: 2000
duration: 10s
connections: 50
query: |
query GetAllArtistsAlbumsTracks_Genres {
Artist {
ArtistId
Name
Albums {
AlbumId
Title
Tracks {
TrackId
Name
Composer
Genre {
GenreId
Name
}
}
}
}
}
- name: AlbumByPK
tools: [k6]
execution_strategy: FIXED_REQUEST_NUMBER
requests: 10000
query: |
query AlbumByPK {
Album_by_pk(AlbumId: 1) {
AlbumId
Title
}
}
- name: AlbumByPKMultiStage
tools: [k6]
execution_strategy: MULTI_STAGE
initial_rps: 0
stages:
- duration: 5s
target: 100
- duration: 5s
target: 1000
query: |
query AlbumByPK {
Album_by_pk(AlbumId: 1) {
AlbumId
Title
}
}

View File

@ -1,64 +0,0 @@
url: "http://host.docker.internal:8080/v1/graphql"
# url: https://benchmark-node-rso3d2ja7a-ul.a.run.app/v1/graphql
headers:
content-type: application/json
# "Debug" mode enables request and response logging for Autocannon and K6
# This lets you see what is happening and confirm proper behavior.
# This should be disabled for genuine benchmarks, and only used for debugging/visibility.
debug: false
queries:
# Name: Unique name for the query
- name: GetAllArtistsAlbumsAndTracks
# Tools: List of benchmarking tools to run: ['autocannon', 'k6', 'wrk2']
tools: [k6]
execution_strategy: REQUESTS_PER_SECOND
rps: 2000
duration: 10s
connections: 10
query: |
query GetAllArtistsAlbumsTracks_Genres {
Artist {
ArtistId
Name
Albums {
AlbumId
Title
Tracks {
TrackId
Name
Composer
Genre {
GenreId
Name
}
}
}
}
}
- name: AlbumByPK
tools: [k6]
execution_strategy: FIXED_REQUEST_NUMBER
requests: 10000
query: |
query AlbumByPK {
Album_by_pk(AlbumId: 1) {
AlbumId
Title
}
}
- name: AlbumByPKMultiStage
tools: [k6]
execution_strategy: MULTI_STAGE
initial_rps: 0
stages:
- duration: 5s
target: 100
- duration: 5s
target: 1000
query: |
query AlbumByPK {
Album_by_pk(AlbumId: 1) {
AlbumId
Title
}
}

View File

@ -1,36 +0,0 @@
services:
postgres:
image: postgres:15
restart: always
volumes:
- db_data:/var/lib/postgresql/data
ports:
- "5432:5432"
environment:
POSTGRES_PASSWORD: postgrespassword
graphql-engine:
image: hasura/graphql-engine:v2.18.0.cli-migrations-v3
volumes:
- ./hasura/migrations:/hasura-migrations
- ./hasura/metadata:/hasura-metadata
ports:
- "8080:8080"
depends_on:
- "postgres"
restart: always
environment:
## postgres database to store Hasura metadata
HASURA_GRAPHQL_METADATA_DATABASE_URL: postgres://postgres:postgrespassword@postgres:5432/postgres
## this env var can be used to add the above postgres database to Hasura as a data source. this can be removed/updated based on your needs
PG_DATABASE_URL: postgres://postgres:postgrespassword@postgres:5432/postgres
## enable the console served by server
HASURA_GRAPHQL_ENABLE_CONSOLE: "true" # set to "false" to disable console
## enable debugging mode. It is recommended to disable this in production
HASURA_GRAPHQL_DEV_MODE: "true"
HASURA_GRAPHQL_ENABLED_LOG_TYPES: startup, http-log, webhook-log, websocket-log, query-log
## uncomment next line to run console offline (i.e load console assets from server instead of CDN)
# HASURA_GRAPHQL_CONSOLE_ASSETS_DIR: /srv/console-assets
## uncomment next line to set an admin secret
# HASURA_GRAPHQL_ADMIN_SECRET: myadminsecretkey
volumes:
db_data:

View File

@ -1,18 +0,0 @@
services:
postgres:
image: postgres:15
restart: always
volumes:
- db_data:/var/lib/postgresql/data
ports:
- "5432:5432"
environment:
POSTGRES_PASSWORD: postgrespassword
node:
build: ./nodejs
ports:
- "8080:8080"
depends_on:
- "postgres"
volumes:
db_data:

View File

@ -1,6 +0,0 @@
version: 3
endpoint: http://localhost:8080
metadata_directory: metadata
actions:
kind: synchronous
handler_webhook_baseurl: http://localhost:3000

View File

@ -1,6 +0,0 @@
actions: []
custom_types:
enums: []
input_objects: []
objects: []
scalars: []

View File

@ -1,9 +0,0 @@
- name: default
kind: postgres
configuration:
connection_info:
database_url:
from_env: PG_DATABASE_URL
isolation_level: read-committed
use_prepared_statements: false
tables: "!include default/tables/tables.yaml"

View File

@ -1,15 +0,0 @@
table:
name: Album
schema: public
object_relationships:
- name: Artist
using:
foreign_key_constraint_on: ArtistId
array_relationships:
- name: Tracks
using:
foreign_key_constraint_on:
column: AlbumId
table:
name: Track
schema: public

View File

@ -1,11 +0,0 @@
table:
name: Artist
schema: public
array_relationships:
- name: Albums
using:
foreign_key_constraint_on:
column: ArtistId
table:
name: Album
schema: public

View File

@ -1,15 +0,0 @@
table:
name: Customer
schema: public
object_relationships:
- name: Employee
using:
foreign_key_constraint_on: SupportRepId
array_relationships:
- name: Invoices
using:
foreign_key_constraint_on:
column: CustomerId
table:
name: Invoice
schema: public

View File

@ -1,22 +0,0 @@
table:
name: Employee
schema: public
object_relationships:
- name: Employee
using:
foreign_key_constraint_on: ReportsTo
array_relationships:
- name: Customers
using:
foreign_key_constraint_on:
column: SupportRepId
table:
name: Customer
schema: public
- name: Employees
using:
foreign_key_constraint_on:
column: ReportsTo
table:
name: Employee
schema: public

View File

@ -1,11 +0,0 @@
table:
name: Genre
schema: public
array_relationships:
- name: Tracks
using:
foreign_key_constraint_on:
column: GenreId
table:
name: Track
schema: public

View File

@ -1,15 +0,0 @@
table:
name: Invoice
schema: public
object_relationships:
- name: Customer
using:
foreign_key_constraint_on: CustomerId
array_relationships:
- name: InvoiceLines
using:
foreign_key_constraint_on:
column: InvoiceId
table:
name: InvoiceLine
schema: public

View File

@ -1,10 +0,0 @@
table:
name: InvoiceLine
schema: public
object_relationships:
- name: Invoice
using:
foreign_key_constraint_on: InvoiceId
- name: Track
using:
foreign_key_constraint_on: TrackId

View File

@ -1,11 +0,0 @@
table:
name: MediaType
schema: public
array_relationships:
- name: Tracks
using:
foreign_key_constraint_on:
column: MediaTypeId
table:
name: Track
schema: public

View File

@ -1,11 +0,0 @@
table:
name: Playlist
schema: public
array_relationships:
- name: PlaylistTracks
using:
foreign_key_constraint_on:
column: PlaylistId
table:
name: PlaylistTrack
schema: public

View File

@ -1,10 +0,0 @@
table:
name: PlaylistTrack
schema: public
object_relationships:
- name: Playlist
using:
foreign_key_constraint_on: PlaylistId
- name: Track
using:
foreign_key_constraint_on: TrackId

View File

@ -1,28 +0,0 @@
table:
name: Track
schema: public
object_relationships:
- name: Album
using:
foreign_key_constraint_on: AlbumId
- name: Genre
using:
foreign_key_constraint_on: GenreId
- name: MediaType
using:
foreign_key_constraint_on: MediaTypeId
array_relationships:
- name: InvoiceLines
using:
foreign_key_constraint_on:
column: TrackId
table:
name: InvoiceLine
schema: public
- name: PlaylistTracks
using:
foreign_key_constraint_on:
column: TrackId
table:
name: PlaylistTrack
schema: public

View File

@ -1,11 +0,0 @@
- "!include public_Album.yaml"
- "!include public_Artist.yaml"
- "!include public_Customer.yaml"
- "!include public_Employee.yaml"
- "!include public_Genre.yaml"
- "!include public_Invoice.yaml"
- "!include public_InvoiceLine.yaml"
- "!include public_MediaType.yaml"
- "!include public_Playlist.yaml"
- "!include public_PlaylistTrack.yaml"
- "!include public_Track.yaml"

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