Partner portal
How an agent sets up the member portal on engine-dmo for partner businesses, declaring membership tiers, the member_editable rules that say what each tier may change, an extra portal screen, an organisation with a membership and an invitation, and how to verify each part.
engine-dmo
The portal is where a partner business’s people sign in (with a link sent by email, or a passkey) to edit their listings, see their enquiries and their report, and manage their membership. It is served by the engine under /members on each site’s own host and works out of the box with the engine’s reference screens. Your work is to say who may change what, add any screens the brief needs, and invite the first partners.
Start from the DMO directory, whose listing has the fields named below. Examples use the locale code en; replace it with your source locale.
Declare the tiers
Tiers are ranked lowest first:
engine-dmo dev tier add standard --label Standard --apply
engine-dmo dev tier add silver --after standard --label Silver --apply
engine-dmo dev tier add gold --after silver --label Gold --apply # config/memberships.yaml
tiers:
- key: standard
id: tier_qxgx1sr7ff
label: { en: Standard }
- { key: silver, id: tier_brwtq46ckb, label: { en: Silver } }
- key: gold
id: tier_q9nx12bh8p
label: { en: Gold } none is reserved for an organisation without an active membership; rules may name it.
Say what members may edit
Without a member_editable rule, members cannot edit documents of the type. Add one to the type’s schema file:
# config/schema/listing.yaml (top level, after fields:)
member_editable:
tiers:
none:
fields: [phone, website, openingHours]
approval: required
standard:
fields: [summary, description, phone, website, openingHours, heroImage]
approval: required
gold:
fields: "*"
except: [slug, area, category]
approval: auto
review_window: 24h
always_review: [primaryTag] approval: required sends every change to the admin’s moderation queue; auto publishes at once and staff review within review_window. Every field named here is on the directory’s listing, or is core. check reports an unknown field or tier, so name only your own type’s fields. engine-dmo describe type listing prints the rules.
Add a portal screen
The reference screens (dashboard, organisation, listing edit, photos, hours, enquiries, report, people, membership and more) need nothing from you. Add a screen of your own with a verb:
engine-dmo dev portal screen add offers --nav-order 30 --min-tier silver \
--data 'listings=documents type=listing states=draft,published sort=-updatedAt limit=20' \
--scaffold --apply # config/sites/main/portal.yaml (written)
screens:
- { key: offers, id: scr_yry1y0gjkw, path: "/organisations/{org}/offers", template: offers.html, nav: { group: business, order: 30 }, tiers: { min: silver }, data: { listings: { documents: { type: listing, states: [draft, published], sort: -updatedAt, limit: 20 } } } } --scaffold writes templates/main/portal/offers.html, a complete page using the portal’s stylesheet with data.listings.items in a list. Edit it; give the page one h1. Forms posting to the portal are declared as actions (engine-dmo dev portal action add <subject.verb>); see Portal screens and actions. To restyle every portal screen, engine-dmo portal scaffold --site main copies the reference templates into the project. Always pass --site: without it the command writes to templates/portal/, which is on no site’s template chain, so nothing there renders and check warns that it is unused.
engine-dmo portal check
engine-dmo portal status portal check checks your portal templates against the portal context; portal status lists every screen with its path and whether the engine or your project supplies its template.
Add a partner and invite them
In a seed, an organisation is a file under seed/organisations/ with its contacts, memberships and invitations, and a listing names it with a top-level organisation: <key>. Organisations and people has the file format, with an example. On a running database:
engine-dmo crm organisation create '{"name":"The Wharf Hotel","email":"stay@wharf.example","status":"active"}'
engine-dmo crm membership set <organisation-id> --tier gold --reason "Joined at the spring trade fair"
engine-dmo crm invite --organisation <organisation-id> --email owner@wharf.example --name "Sam Owner" --role "General manager" organisation create prints the new id; membership set starts an active membership on the default site (--site for another); invite links the person as the organisation’s owner and queues the invitation mail. It prints no link. Linking an existing listing to an organisation is done in the admin’s document editor (the Organisation panel) or in the seed.
Verify
engine-dmo check
engine-dmo portal check
engine-dmo schema plan
engine-dmo describe type listing The portal’s screens need a signed-in member, so render cannot show them. To see one you need the sign-in mail, and how you read it depends on your mail settings.
Read the mail
The log driver, the default, writes to, subject and tag at the info level, with no body and no link. Choose one of these in development:
# config/engine.yaml, to read the body in the server log
logging:
format: ${LOG_FORMAT:-text}
level: debug # config/engine.yaml, to send to a local SMTP catcher such as Mailpit on port 1025
mail:
driver: smtp
from: hello@example.org
smtp:
host: localhost
port: 1025
tls: none If you can do neither, say so and leave this step to the person.
Sign in
The invitation does not carry a token. Its link goes to /members/login, on the site’s first domain without a port, so in development it reads http://localhost/members/login and goes nowhere: open /members/login on your server’s own address. There the person asks for a sign-in link, and only that second mail has a token (/members/auth/<token>, on the address they asked from). It lasts 15 minutes.
A sign-in link works only in the browser that asked for it, which holds a __Host-mrd_link cookie. Opened anywhere else it answers 200 with Open this link in the browser where you requested it, and signs nobody in. So from a shell, keep one cookie jar across both requests:
curl -s -c jar -b jar -o /dev/null -X POST -d 'email=owner@wharf.example' http://localhost:8080/members/login
# read the /members/auth/<token> link from the mail, then:
curl -s -c jar -b jar -o /dev/null -w '%{http_code} %{redirect_url}\n' 'http://localhost:8080/members/auth/<token>'
curl -s -b jar http://localhost:8080/members The second command answers 303 to /members. POST /members/login answers the same page whether or not the address is known, so an address that is not a person’s gets no mail and no error. See Partners and the portal.
DMO directory
How an agent builds a destination directory on engine-dmo, extending the core listing and area types, loading the tag vocabulary, routing areas beside pages at /{slug}, filtering by place facets, printing structured data, seeding complete listings with hours, address and phone, and verifying it all.
Landing pages
How an agent declares place-and-category landing pages on an engine-dmo listing route, what makes a page live, the optional landing template, and how to verify the configuration and the pages.