Step 2: customers who sign in
Values: an address and a card
# ---- value types ---------------------------------------------------------
Address:
meta: entity @embeddable -- 주소
fields:
street1: string(50)!
street2: string(50)
city: string(50)!
state: string(50)
zipcode: string(10)!
countryCode: string(2)!
CreditCard:
meta: entity @embeddable -- 결제 카드
fields:
creditCardNumber: string(30)! @writeonly # accepted, never sent back
creditCardType: CreditCardType!
creditCardExpDate: string(5)! # MM/YY
@embeddable makes a value object: no identity, its columns live in the table of whoever holds it,
the DTO carries it as a nested object with its own checks. @writeonly takes the card number and
never answers it - and the Ossie export leaves the column out.
The customer
Customer:
# 가입은 누구나, 읽기·수정은 본인(과 관리자), 삭제는 관리자
meta: entity @root @editable @expose(20) @signin @owner(customerId) @access(read=owner, create=public, update=owner) @terms(member, 회원, 손님)
@Index(name="uk_ps_customer_login", columnList="login", unique="true") -- 고객
fields:
customerId: long @id @sequence
login: string(10)! @search(eq) @login
password: string(255)! @password # 저장은 해시로만, 응답·검색에 없음
firstName: string(50)! @search(like)
lastName: string(50)! @search(like)
telephone: string(30)
email: string(100) @email
dateOfBirth: date @past
role: UserRole @default(UserRole.USER) @access(admin) # 관리자만 바꿈
homeAddress: Address! @Embedded # 집 주소는 필수 (기본 배송지)
(The sample's comments are in Korean: anyone signs up, reading and changing are the customer's own and the administrator's, deleting is the administrator's; the password is stored as a hash only; only the administrator changes the role; the home address is required - the default delivery address.)
UserRole:
meta: enum @db(code) @json(code) -- 사용자 권한
fields:
code: string(1) @code
label: string(20)
enum:
USER: U, User
ADMIN: A, Administrator
| Piece | Effect |
|---|---|
@owner(customerId) @access(read=owner, create=public, update=owner) | anyone signs up; a customer reads and changes only their own row (another's answers 404); delete is not named, so it is the administrator's |
@password | hashed (PBKDF2) on every write path; never answered or searched |
@login + @password | authenticate(login, password) in the core service and use case |
@signin | POST /api/petstore/customer/signin, POST .../signout, GET .../me - and signIn, signOut, me() in the TypeScript client |
role ... @access(admin) | the session's role - only the administrator writes it; a sign-up sending "role":"A" is stored as U |
@Index(... unique="true") | a taken login answers 409 |
@email, @past | 400 for a bad address or a birthday in the future |
homeAddress: Address! @Embedded | the address is required; its required fields answer 400 |
@signin is generated here because only the customer and the administrator may change the row
(update=owner with the key as @owner) - otherwise the build would warn and not generate it.
Sign-up and being signed in at once
The generated API signs in; signing up and being signed in in one request is the shop's choice, so it is the one controller written by hand:
@RestController
@RequestMapping("/api/petstore/account")
public class AccountController {
private final CustomerUseCase customers;
public AccountController(CustomerUseCase customers) {
this.customers = customers;
}
/** A new customer, signed in. The service keeps the role out of a visitor's hands. */
@PostMapping("/signup")
@ResponseStatus(HttpStatus.CREATED)
public CustomerDTO signUp(@Valid @RequestBody CustomerDTO customer, HttpServletRequest request) {
customer.setCustomerId(null);
CustomerDTO created = customers.save(customer);
SessionCurrentUser.signIn(request, created.getCustomerId(), created.getRole().name());
return created;
}
}
Demo accounts
@Component
@ConditionalOnProperty(name = "petstore.demo-accounts", havingValue = "true")
class DemoAccounts implements ApplicationRunner {
/** As the application itself: only it may make an administrator. */
@Override
public void run(ApplicationArguments args) {
CurrentUser.runAsSystem(() -> {
create("admin", "Admin", "The Admin Alley", UserRole.ADMIN);
create("user", "User", "The User Boulevard", UserRole.USER);
});
}
// create(...) saves a CustomerDTO through CustomerService - the password is hashed like any other
}
The public demo's administrator is xsko / xsko; locally admin / admin.
Try it
# sign in and keep the session cookie
curl -s -c jar -b jar -X POST https://petstore.elasticore.io/api/petstore/customer/signin \
-H 'Content-Type: application/json' -d '{"login":"user","password":"user"}'
curl -s -c jar -b jar https://petstore.elasticore.io/api/petstore/customer/me
curl -s -c jar -b jar -X POST https://petstore.elasticore.io/api/petstore/customer/signout
- Signing in answers the customer without the password; a wrong password 401
Wrong login or password. meanswers 401 for a visitor and aftersignout.- Another customer's
GET /api/petstore/customer/{id}answers 404.
Next: orders.