forked from graphile/graphile.github.io
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathschema-design.sql
248 lines (193 loc) · 9.93 KB
/
schema-design.sql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
-- This file was automatically generated from
-- /src/pages/postgraphile/postgresql-schema-design.md which contains a complete
-- explanation of how this schema works and why certain decisions were made. If
-- you are looking for a comprehensive tutorial, definitely check it out as this
-- file is a little tough to read.
--
-- If you want to contribute to this file, please change the
-- /src/pages/postgraphile/postgresql-schema-design.md file and then rebuild
-- this file :)
begin;
create schema forum_example;
create schema forum_example_private;
create table forum_example.person (
id serial primary key,
first_name text not null check (char_length(first_name) < 80),
last_name text check (char_length(last_name) < 80),
about text,
created_at timestamp default now()
);
comment on table forum_example.person is 'A user of the forum.';
comment on column forum_example.person.id is 'The primary unique identifier for the person.';
comment on column forum_example.person.first_name is 'The person’s first name.';
comment on column forum_example.person.last_name is 'The person’s last name.';
comment on column forum_example.person.about is 'A short description about the user, written by the user.';
comment on column forum_example.person.created_at is 'The time this person was created.';
create type forum_example.post_topic as enum (
'discussion',
'inspiration',
'help',
'showcase'
);
create table forum_example.post (
id serial primary key,
author_id integer not null references forum_example.person(id),
headline text not null check (char_length(headline) < 280),
body text,
topic forum_example.post_topic,
created_at timestamp default now()
);
comment on table forum_example.post is 'A forum post written by a user.';
comment on column forum_example.post.id is 'The primary key for the post.';
comment on column forum_example.post.headline is 'The title written by the user.';
comment on column forum_example.post.author_id is 'The id of the author user.';
comment on column forum_example.post.topic is 'The topic this has been posted in.';
comment on column forum_example.post.body is 'The main body text of our post.';
comment on column forum_example.post.created_at is 'The time this post was created.';
create function add(a int, b int) returns int as $$
select a + b
$$ language sql stable;
create function forum_example.person_full_name(person forum_example.person) returns text as $$
select person.first_name || ' ' || person.last_name
$$ language sql stable;
comment on function forum_example.person_full_name(forum_example.person) is 'A person’s full name which is a concatenation of their first and last name.';
create function forum_example.post_summary(
post forum_example.post,
length int default 50,
omission text default '…'
) returns text as $$
select case
when post.body is null then null
else substr(post.body, 0, length) || omission
end
$$ language sql stable;
comment on function forum_example.post_summary(forum_example.post, int, text) is 'A truncated version of the body for summaries.';
create function forum_example.person_latest_post(person forum_example.person) returns forum_example.post as $$
select post.*
from forum_example.post as post
where post.author_id = person.id
order by created_at desc
limit 1
$$ language sql stable;
comment on function forum_example.person_latest_post(forum_example.person) is 'Get’s the latest post written by the person.';
create function forum_example.search_posts(search text) returns setof forum_example.post as $$
select post.*
from forum_example.post as post
where position(search in post.headline) > 0 or position(search in post.body) > 0
$$ language sql stable;
comment on function forum_example.search_posts(text) is 'Returns posts containing a given search term.';
alter table forum_example.person add column updated_at timestamp default now();
alter table forum_example.post add column updated_at timestamp default now();
create function forum_example_private.set_updated_at() returns trigger as $$
begin
new.updated_at := current_timestamp;
return new;
end;
$$ language plpgsql;
create trigger person_updated_at before update
on forum_example.person
for each row
execute procedure forum_example_private.set_updated_at();
create trigger post_updated_at before update
on forum_example.post
for each row
execute procedure forum_example_private.set_updated_at();
create table forum_example_private.person_account (
person_id integer primary key references forum_example.person(id) on delete cascade,
email text not null unique check (email ~* '^.+@.+\..+$'),
password_hash text not null
);
comment on table forum_example_private.person_account is 'Private information about a person’s account.';
comment on column forum_example_private.person_account.person_id is 'The id of the person associated with this account.';
comment on column forum_example_private.person_account.email is 'The email address of the person.';
comment on column forum_example_private.person_account.password_hash is 'An opaque hash of the person’s password.';
create extension if not exists "pgcrypto";
create function forum_example.register_person(
first_name text,
last_name text,
email text,
password text
) returns forum_example.person as $$
declare
person forum_example.person;
begin
insert into forum_example.person (first_name, last_name) values
(first_name, last_name)
returning * into person;
insert into forum_example_private.person_account (person_id, email, password_hash) values
(person.id, email, crypt(password, gen_salt('bf')));
return person;
end;
$$ language plpgsql strict security definer;
comment on function forum_example.register_person(text, text, text, text) is 'Registers a single user and creates an account in our forum.';
create role forum_example_postgraphile login password 'xyz';
create role forum_example_anonymous;
grant forum_example_anonymous to forum_example_postgraphile;
create role forum_example_person;
grant forum_example_person to forum_example_postgraphile;
set local jwt.claims.a to 1;
set local jwt.claims.b to 2;
set local jwt.claims.c to 3;
select current_setting('jwt.claims.a', true);
set local role to 'forum_example_person'
set local jwt.claims.role to 'forum_example_person'
create type forum_example.jwt_token as (
role text,
person_id integer
);
create function forum_example.authenticate(
email text,
password text
) returns forum_example.jwt_token as $$
declare
account forum_example_private.person_account;
begin
select a.* into account
from forum_example_private.person_account as a
where a.email = $1;
if account.password_hash = crypt(password, account.password_hash) then
return ('forum_example_person', account.person_id)::forum_example.jwt_token;
else
return null;
end if;
end;
$$ language plpgsql strict security definer;
comment on function forum_example.authenticate(text, text) is 'Creates a JWT token that will securely identify a person and give them certain permissions.';
create function forum_example.current_person() returns forum_example.person as $$
select *
from forum_example.person
where id = nullif(current_setting('jwt.claims.person_id', true), '')::integer
$$ language sql stable;
comment on function forum_example.current_person() is 'Gets the person who was identified by our JWT.';
-- after schema creation and before function creation
alter default privileges revoke execute on functions from public;
grant usage on schema forum_example to forum_example_anonymous, forum_example_person;
grant select on table forum_example.person to forum_example_anonymous, forum_example_person;
grant update, delete on table forum_example.person to forum_example_person;
grant select on table forum_example.post to forum_example_anonymous, forum_example_person;
grant insert, update, delete on table forum_example.post to forum_example_person;
grant usage on sequence forum_example.post_id_seq to forum_example_person;
grant execute on function forum_example.person_full_name(forum_example.person) to forum_example_anonymous, forum_example_person;
grant execute on function forum_example.post_summary(forum_example.post, integer, text) to forum_example_anonymous, forum_example_person;
grant execute on function forum_example.person_latest_post(forum_example.person) to forum_example_anonymous, forum_example_person;
grant execute on function forum_example.search_posts(text) to forum_example_anonymous, forum_example_person;
grant execute on function forum_example.authenticate(text, text) to forum_example_anonymous, forum_example_person;
grant execute on function forum_example.current_person() to forum_example_anonymous, forum_example_person;
grant execute on function forum_example.register_person(text, text, text, text) to forum_example_anonymous;
alter table forum_example.person enable row level security;
alter table forum_example.post enable row level security;
create policy select_person on forum_example.person for select
using (true);
create policy select_post on forum_example.post for select
using (true);
create policy update_person on forum_example.person for update to forum_example_person
using (id = nullif(current_setting('jwt.claims.person_id', true), '')::integer);
create policy delete_person on forum_example.person for delete to forum_example_person
using (id = nullif(current_setting('jwt.claims.person_id', true), '')::integer);
create policy insert_post on forum_example.post for insert to forum_example_person
with check (author_id = nullif(current_setting('jwt.claims.person_id', true), '')::integer);
create policy update_post on forum_example.post for update to forum_example_person
using (author_id = nullif(current_setting('jwt.claims.person_id', true), '')::integer);
create policy delete_post on forum_example.post for delete to forum_example_person
using (author_id = nullif(current_setting('jwt.claims.person_id', true), '')::integer);
commit;