Skip to main content
ElastiCORE 3.0 is a release candidate: 3.0.261022-rc1 on Maven Central. Projects on the 2.2 line: see the 2.2 documentation.
Version: 3.0

Step 2: customers who sign in

Values: an address and a card​

blueprint/petstore/model/entity.yml
  # ---- 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​

blueprint/petstore/model/entity.yml
  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.)

blueprint/petstore/model/enum.yml
  UserRole:
meta: enum @db(code) @json(code) -- 사용자 권한
fields:
code: string(1) @code
label: string(20)
enum:
USER: U, User
ADMIN: A, Administrator
PieceEffect
@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
@passwordhashed (PBKDF2) on every write path; never answered or searched
@login + @passwordauthenticate(login, password) in the core service and use case
@signinPOST /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, @past400 for a bad address or a birthday in the future
homeAddress: Address! @Embeddedthe 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:

adapter/in/web/shop/AccountController.java (hand-written)
@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​

DemoAccounts.java (hand-written, abridged)
@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.
  • me answers 401 for a visitor and after signout.
  • Another customer's GET /api/petstore/customer/{id} answers 404.

Next: orders.