I am designing a RESTful API for an applicaiton ,the resources is project-level, and people should create project before they create a resource.
first, I design the path as below,
path = api/v1/projects ,METHOD = ['POST'] # create a project
path = api/v1/projects/$PROJECT-ID/users , METHOD=['POST'] # add a user to a project
path = api/v1/projects/$PROJECT-ID/books , METHOD=['POST'] # add a book to a project
path = api/v1/users METHOD=['POST'] # user register
it seems good, each path of an api is unique, but while I design the permission model ,It confused me. it is hard to map a permission from api path.
in order to differentiate create project and add a user to a project api, I create table for permission include parent column whose value may be like 'projects/$project-ID', So while a request sent to the api gateway, I can verify which permission the request match by check if it has a parent(prefix) via Re.
# permission model one
CREATE TABLE `permission` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(128) NOT NULL DEFAULT '' COMMENT 'permission name',
`action` varchar(32) NOT NULL DEFAULT '' COMMENT ' http method DELETE, POST,GET,PUT ',
`resource` varchar(64) NOT NULL DEFAULT '' COMMENT 'resource type such as user',
`parent` varchar(64) NOT NULL DEFAULT '' COMMENT 'the parent class of resources, not all the resource has parent ',
PRIMARY KEY (`id`),
UNIQUE KEY `name_unique` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT 'permisson model';
I think it can work ,but the design is ugly , what do you think about it?
Related
I am writing a resource server based on OAuth2 structure. I have a table user, and I will be storing the sub claim of JWT response into that table as a unique key for other table to reference it. I tried to do this in order to avoid checking the database for each incoming request.
If that key(sso_id) is already unique, do I still need to have a id key that will be generated automatically? Is there any performance gain/loss for omitting that key?
DROP TABLE IF EXISTS "user";
CREATE TABLE "user" (
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
sso_id VARCHAR(100) NOT NULL UNIQUE
);
No, you do not need to generate a different Primary Key. A primary must meet 2 requirements, nothing else. It must not be null or partially null, and it must be unique within the table. As long as it meets those requirements, it is valid. Some would argue that you should generate a PK (has some validity), but that is a philosophical argument a specified requirement. You could actually use the value passed as the PK, and perhaps even a FK. I am torn between assuming that you single sign-on (sso_id) and the passed jwt_id are the same or not. Why would you define it as 100 characters, which is much larger than required if they were, but it also seems logical that they would be. I will assume they are not. Also I assume that reference to originating table is available. With that then the following would be perfectly valid.
create table "user" (
jwt_id uuid
, sso_id varchar(100) not null unique
, constraint user_pk
primary_key(jwt_id)
, constraint user_2_jwt_fk
foreign key (jwt_id)
references jwt(jwt_id)
);
I am trying to convert a MySQL database to PostgreSQL and I created this
CREATE TYPE location_m as enum('England','Japan','France','Usa','China','Canada');
CREATE TABLE airport (
id int NOT NULL,
owner varchar(40) NOT NULL DEFAULT '',
location location_m NOT NULL DEFAULT 'England',
travel_prices varchar(100) NOT NULL DEFAULT '100-100-100-100-100-100',
profit varchar(100) NOT NULL DEFAULT '0-0-0-0-0-0'
) ;
INSERT INTO airport (id, owner, location, travel_prices, profit) VALUES
(1, 'Mafia', 'Japan', '1000-1000-1000-1000-1000-1000', '0-18000-34000-15500-11000-13000');
What I run the insert it returns with this
psql:main_db.sql:43: ERROR: type "location_m" already exists
I tried looking it up but can't really find anything. I don't understand why it is saying it already exists.
I thought I was doing the enum correctly based on the docs and the other Stack Overflow posts.
That is my entire file so far, except I have DROP TABLE IF EXISTS airport; at the beginning.
If you're doing that, you will also want to place a DROP TYPE IF EXISTS location_m; at the beginning of the script. (Possibly with the CASCADE option if it's already used in a table definition, or make sure to drop all such tables first).
Alternatively, have a look at Check if a user-defined type already exists in PostgreSQL for various other workarounds for CREATE OR REPLACE TYPE …, although if you are working with a migration script that will run only once and expect an empty database, it's probably no harm to just drop and recreate them.
A far better idea, use 2 scripts. Put your creates in 1 script, it gets run 1 time. The other script contains only the Insert. If needed delete and re-run as many times as needed. Further IMHO never use 'if exists' on ddl, keep in mind that errors can be your friend. Consider the following scenario.
Your script:
drop type if exists location_m cascade;
create type location_m as enum('England','Japan','France','Usa','China','Canada');
drop table if exists airport ;
create table airport (
id int not null,
owner varchar(40) not null default '',
location location_m not null default 'England',
travel_prices varchar(100) not null default '100-100-100-100-100-100',
profit varchar(100) not null default '0-0-0-0-0-0'
) ;
insert into airport (id, owner, location, travel_prices, profit) VALUES
(1, 'Mafia', 'Japan', '1000-1000-1000-1000-1000-1000', '0-18000-34000-15500-11000-13000');
The another developer in your organization working on a different project, but using the same naming conventions comes along with their script:
drop type if exists location_m cascade;
create type location_m as enum('USA', 'Canada', 'Brazil');
create table hotel( id integer generated always as identity
, name text
, location location_m not null default 'Canada'
, corporate_rate money
) ;
Now what does the following give you?
select *
from airport
where location = 'Japan';
See demo here. So what happened? Have fun trying to find this why this happened. Oh well never mind just rerun your script. But soon you are both posting to SO wanting to know what Postgres bug in causing your problem - even though the problem is not on the Postgres side at all.
How would one go about defining extended permissions and authorization on a many-to-many relationship using Postgraphile?
Imagine I have this many-to-many relationship (graciously borrowed from official docs and adjusted with simplified permissions):
create table post (
id serial primary key,
headline text,
body text,
summary text
);
create table author (
id serial primary key,
name text
);
create type post_permission as enum (
'owner', -- assume owner can change `headline`, `summary` and `body` of a post
'editor' -- assume editor can modify `summary` of a post
);
create table post_author (
post_id integer references post,
author_id integer references author,
permission post_permission,
primary key (post_id, author_id)
);
I can make a Row Level Security Policy like:
create policy update_post on post for update to app_user using (
EXISTS(
SELECT 1
FROM post_author as pa
WHERE pa.post_id = post.id
AND pa.author_id = app_hidden.current_user_id()
AND pa.permission = 'owner'
)
);
-- Assume `app_hidden.current_user_id()` returns a logged in user id
But as I am a recent MySQL convert to PostgreSQL I am trying to see if I can make the policy check pa.permission above in relation to the attempted change and only allow permission = owner to update all fields of a post, whereas a user with permission = editor can just update summary.
I am aware that this is often handled in the app layer and not database, but figured I would see whats possible first.
Thanks!
See also related topic here.
Based on investigation and trial-and-error, this seems to be something that is best solved with a custom function for updating posts.
An owner can use this function via GraphQL/Postgraphile:
create function updatePost(
headline text,
body text,
summary text
) returns post as $$
-- implement this function to check that the user found via
-- app_hidden.current_user_id() exists in join table
-- with an `owner` permission
-- then modify post
$$ language plpgsql strict security definer;
An editor can use this function via GraphQL/Postgraphile:
create function updatePostMeta(
summary text
) returns post as $$
-- implement this function to check that the user found via
-- app_hidden.current_user_id() exists in join table
-- with an `editor` or `owner` permission
-- then modify post
$$ language plpgsql strict security definer;
Additionally, using RLS, one would want to prevent anyone from changing a post directly via GraphQL/Postgraphile, so we'd only let users SELECT from post
I have a table named 'Restaurant' with following fields:
CREATE TABLE IF NOT EXISTS `restaurant` (
`restaurant_id` int(11) NOT NULL AUTO_INCREMENT COMMENT,
`restaurant_id_name` varchar(100) NOT NULL COMMENT ,
`restaurant_name` varchar(100) NOT NULL COMMENT,
`restaurant_score` int(10) NOT NULL,
....
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=95 ;
I need to utilize the SOAP server using the ZF2 component Zend\Soap\Server using WSDL for concrete entity/mapper/model(ex Restaurant)(insert/update/delete/retrieve).
Yes I have read the documentation, but I haven't searched for more examples on how to develop a SOAP server in module(Controller\View\Model). If you have any concrete examples or detailed explanations using instances of Zend\Soap\Server and/or Zend\Soap\Client I would be appreciative of reviewing those to help me out.
I'm trying to make a Spring MVC application using Spring Security. I have a problem with JPA - I have a code like this (part of register action):
userDao.addUser(user);
UserDetailsAdapter userDetails = new UserDetailsAdapter(user);
String password = userDetails.getPassword();
Object salt = saltSource.getSalt(userDetails);
user.setPassword(passwordEncoder.encodePassword(password, salt));
After last line there should be a call to update the password field in database (addUser is JPA'a entity manager's persist), but I don't know how to achieve this without making additional selects.
My user table looks like this:
CREATE TABLE `user` (
`user_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(30) NOT NULL,
`password` varchar(64) NOT NULL,
`rank_id` tinyint(4) unsigned DEFAULT '1',
`email` varchar(45) NOT NULL,
`registration_date` datetime NOT NULL,
`last_login_date` datetime DEFAULT NULL,
`admin` bit(1) DEFAULT b'0',
`active` bit(1) DEFAULT b'1',
PRIMARY KEY (`user_id`),
KEY `users_rank_id_pk` (`rank_id`)
) ENGINE=InnoDB AUTO_INCREMENT=34 DEFAULT CHARSET=utf8$$
The problem is that before calling addUser the user object doesn't have the default values set (rank_id, active, admin, last_login_date). If I call the merge on that object I get those values nullified because they don't get set default.
If I select the User object again using
User test = userDao.findUserById(user.getUserId());
test.setPassword(passwordEncoder.encodePassword(password, salt));
userDao.updateUser(test);
The whole thing works but it makes few more database queries. Is there another way around? I tried setting default values in mapped classes directly, but it didn't work out. I'm using spring's HibernateJpaVendorAdapter and #Transactional annotation on my DAO classes
Regards,
Marcin