@@ -12,6 +12,7 @@ import '../account/backend.dart';
1212import '../account/consent_backend.dart' ;
1313import '../shared/email.dart' ;
1414import '../shared/exceptions.dart' ;
15+ import 'domain_verifier.dart' show domainVerifier;
1516
1617import 'models.dart' ;
1718
@@ -60,6 +61,87 @@ class PublisherBackend {
6061 });
6162 }
6263
64+ /// Create publisher.
65+ Future <api.PublisherInfo > createPublisher (
66+ String publisherId,
67+ api.CreatePublisherRequest body,
68+ ) async {
69+ // Sanity check that domains are:
70+ // - lowercase (because we want that in pub.dev)
71+ // - consist of a-z, 0-9 and dashes
72+ // We do not care if they end in dash, as such domains can't be verified.
73+ InvalidInputException .checkMatchPattern (
74+ publisherId,
75+ 'publisherId' ,
76+ RegExp (r'^[a-z0-9-]{1,63}\.[a-z0-9-]{1,63}$' ),
77+ );
78+ InvalidInputException .checkStringLength (
79+ publisherId,
80+ 'publisherId' ,
81+ maximum: 255 , // Some upper limit for sanity.
82+ );
83+ InvalidInputException .checkNotNull (body.accessToken, 'accessToken' );
84+ InvalidInputException .checkStringLength (
85+ body.accessToken,
86+ 'accessToken' ,
87+ minimum: 1 ,
88+ maximum: 4096 ,
89+ );
90+
91+ // Verify ownership of domain.
92+ final isOwner = await domainVerifier.verifyDomainOwnership (
93+ publisherId,
94+ body.accessToken,
95+ );
96+ if (! isOwner) {
97+ throw AuthorizationException .userIsNotDomainOwner (publisherId);
98+ }
99+
100+ // Create the publisher
101+ final now = DateTime .now ().toUtc ();
102+ await _db.withTransaction ((tx) async {
103+ final key = _db.emptyKey.append (Publisher , id: publisherId);
104+ final p = (await tx.lookup <Publisher >([key])).single;
105+ if (p != null ) {
106+ // Check that publisher is the same as what we would create.
107+ if (p.created.isBefore (now.subtract (Duration (minutes: 10 ))) ||
108+ p.updated.isBefore (now.subtract (Duration (minutes: 10 ))) ||
109+ p.contactEmail != authenticatedUser.email ||
110+ p.description != '' ||
111+ p.websiteUrl != 'https://$publisherId ' ) {
112+ throw ConflictException .publisherAlreadyExists (publisherId);
113+ }
114+ // Avoid creating the same publisher again, this end-point is idempotent
115+ // if we just do nothing here.
116+ return ;
117+ }
118+
119+ // Create publisher
120+ tx.queueMutations (inserts: [
121+ Publisher ()
122+ ..parentKey = _db.emptyKey
123+ ..id = publisherId
124+ ..created = now
125+ ..description = ''
126+ ..contactEmail = authenticatedUser.email
127+ ..updated = now
128+ ..websiteUrl = 'https://$publisherId ' ,
129+ PublisherMember ()
130+ ..parentKey = _db.emptyKey.append (Publisher , id: publisherId)
131+ ..id = authenticatedUser.userId
132+ ..created = now
133+ ..updated = now
134+ ..role = PublisherMemberRole .admin
135+ ]);
136+ await tx.commit ();
137+ });
138+
139+ // Return publisher as it was created
140+ final key = _db.emptyKey.append (Publisher , id: publisherId);
141+ final p = (await _db.lookup <Publisher >([key])).single;
142+ return _asPublisherInfo (p);
143+ }
144+
63145 /// Gets the publisher data
64146 Future <api.PublisherInfo > getPublisher (String publisherId) async {
65147 final p = await _getPublisher (publisherId);
0 commit comments