Browse Source

Add VendorEvents
Change DateTime to DateTimeOffset where necessary

Andrew Klopper 8 years ago
parent
commit
bfdd066f69

+ 8 - 8
BulkPrintingAPI/Controllers/BatchesController.cs

55
         }
55
         }
56
 
56
 
57
         [HttpGet]
57
         [HttpGet]
58
-        public async Task<Page<Batch>> GetBatches([FromQuery] int page = 1,
58
+        public async Task<Page<Batch>> GetBatchesAsync([FromQuery] int page = 1,
59
             [FromQuery] int pageSize = 100)
59
             [FromQuery] int pageSize = 100)
60
         {
60
         {
61
-            var credentials = await Utils.GetLoginCredentialsFromRequest(HttpContext, _context);
61
+            var credentials = await Utils.GetLoginCredentialsFromRequestAsync(HttpContext, _context);
62
             return await Page<Batch>.GetPageAsync(
62
             return await Page<Batch>.GetPageAsync(
63
                 BatchesForVendor(credentials.Vendor.Id).OrderByDescending(b => b.OrderDate),
63
                 BatchesForVendor(credentials.Vendor.Id).OrderByDescending(b => b.OrderDate),
64
                 page, pageSize);
64
                 page, pageSize);
65
         }
65
         }
66
 
66
 
67
         [HttpGet("{id}")]
67
         [HttpGet("{id}")]
68
-        public async Task<IActionResult> GetBatch([FromRoute] int id)
68
+        public async Task<IActionResult> GetBatchAsync([FromRoute] int id)
69
         {
69
         {
70
             if (!ModelState.IsValid)
70
             if (!ModelState.IsValid)
71
             {
71
             {
72
                 return BadRequest(ModelState);
72
                 return BadRequest(ModelState);
73
             }
73
             }
74
 
74
 
75
-            var credentials = await Utils.GetLoginCredentialsFromRequest(HttpContext, _context);
75
+            var credentials = await Utils.GetLoginCredentialsFromRequestAsync(HttpContext, _context);
76
             var batch = await BatchesForVendor(credentials.Vendor.Id)
76
             var batch = await BatchesForVendor(credentials.Vendor.Id)
77
                 .Include(b => b.Account)
77
                 .Include(b => b.Account)
78
                 .SingleOrDefaultAsync(m => m.Id == id);
78
                 .SingleOrDefaultAsync(m => m.Id == id);
91
         }
91
         }
92
 
92
 
93
         [HttpPost]
93
         [HttpPost]
94
-        public async Task<IActionResult> PlaceOrder([FromBody] OrderRequest order)
94
+        public async Task<IActionResult> PlaceOrderAsync([FromBody] OrderRequest order)
95
         {
95
         {
96
             if (!ModelState.IsValid)
96
             if (!ModelState.IsValid)
97
             {
97
             {
98
                 return BadRequest(ModelState);
98
                 return BadRequest(ModelState);
99
             }
99
             }
100
 
100
 
101
-            var credentials = await Utils.GetLoginCredentialsFromRequest(HttpContext, _context);
101
+            var credentials = await Utils.GetLoginCredentialsFromRequestAsync(HttpContext, _context);
102
             var catalogue = await Utils.GetProductCatalogueAsync(_clientFactory, _logger, _cache,
102
             var catalogue = await Utils.GetProductCatalogueAsync(_clientFactory, _logger, _cache,
103
-                credentials);
103
+                credentials, false);
104
 
104
 
105
             Product product;
105
             Product product;
106
             if (!catalogue.ProductMap.TryGetValue(order.ProductId.Value, out product))
106
             if (!catalogue.ProductMap.TryGetValue(order.ProductId.Value, out product))
107
             {
107
             {
108
-                return BadRequest("Invalid product ID");
108
+                return BadRequest(new { error = "Invalid product ID" });
109
             }
109
             }
110
 
110
 
111
             var orderResponse = await MAX.Utils.PlaceOrderAsync(_clientFactory, _logger,
111
             var orderResponse = await MAX.Utils.PlaceOrderAsync(_clientFactory, _logger,

+ 90 - 18
BulkPrintingAPI/Controllers/LoginController.cs

90
             MAX.LoginCredentials credentials;
90
             MAX.LoginCredentials credentials;
91
             try
91
             try
92
             {
92
             {
93
-                credentials = await Utils.SyncUserAndVendorWithDb(_context, _dataEncryptionOptions,
93
+                credentials = await Utils.SyncUserAndVendorWithDbAsync(_context, _dataEncryptionOptions,
94
                     user, loginRequest.Password, loginRequest.VendorId.Value, loginRequest.SerialNumber);
94
                     user, loginRequest.Password, loginRequest.VendorId.Value, loginRequest.SerialNumber);
95
             }
95
             }
96
             catch (Exception e)
96
             catch (Exception e)
103
                 return Unauthorized();
103
                 return Unauthorized();
104
             }
104
             }
105
 
105
 
106
-            var now = DateTime.UtcNow;
107
-            var encodedJwt = new JwtSecurityTokenHandler().CreateEncodedJwt(
108
-                issuer: _tokenAuthenticationOptions.Issuer,
109
-                audience: _tokenAuthenticationOptions.Audience,
110
-                subject: new ClaimsIdentity(new Claim[] {
111
-                       new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
112
-                       new Claim("xpwd", loginRequest.Password),
113
-                       new Claim("xvid", loginRequest.VendorId.ToString()),
114
-                       new Claim(JwtRegisteredClaimNames.Jti, await _tokenAuthenticationOptions.NonceGenerator())
115
-                }),
116
-                notBefore: now,
117
-                expires: now.Add(_tokenAuthenticationOptions.Lifetime),
118
-                issuedAt: now,
119
-                signingCredentials: _tokenAuthenticationOptions.SigningCredentials,
120
-                encryptingCredentials: _tokenAuthenticationOptions.EncryptingCredentials
121
-            );
106
+            var now = DateTimeOffset.UtcNow;
107
+
108
+            _context.VendorEvents.Add(new MAX.Models.VendorEvent()
109
+            {
110
+                EventDate = now,
111
+                EventType = MAX.Models.VendorEvent.VendorEventType.Login,
112
+                UserId = credentials.User.Id,
113
+                VendorId = credentials.Vendor.Id
114
+            });
115
+            await _context.SaveChangesAsync();
116
+
117
+            var encodedJwt = await GetJwtAsync(now.DateTime, credentials);
122
 
118
 
123
             using (var derivedBytes = DataEncryptionOptions.DeriveKey(credentials.Password +
119
             using (var derivedBytes = DataEncryptionOptions.DeriveKey(credentials.Password +
124
                 _dataEncryptionOptions.PasswordNoise, _dataEncryptionOptions.SaltLength,
120
                 _dataEncryptionOptions.PasswordNoise, _dataEncryptionOptions.SaltLength,
130
                     JsonConvert.SerializeObject(
126
                     JsonConvert.SerializeObject(
131
                         new
127
                         new
132
                         {
128
                         {
133
-                            date = DateTime.Now,
129
+                            date = now,
134
                             nonce = await _tokenAuthenticationOptions.NonceGenerator(),
130
                             nonce = await _tokenAuthenticationOptions.NonceGenerator(),
135
                             user = credentials.User,
131
                             user = credentials.User,
136
                             vendor = credentials.Vendor,
132
                             vendor = credentials.Vendor,
156
 
152
 
157
                 return Ok(new
153
                 return Ok(new
158
                 {
154
                 {
155
+                    date = now,
159
                     access_token = encodedJwt,
156
                     access_token = encodedJwt,
160
                     credentials = new
157
                     credentials = new
161
                     {
158
                     {
168
                 });
165
                 });
169
             }
166
             }
170
         }
167
         }
168
+
169
+        [HttpGet("extend")]
170
+        public async Task<IActionResult> ExtendTokenAsync()
171
+        {
172
+            var credentials = await Utils.GetLoginCredentialsFromRequestAsync(HttpContext, _context);
173
+
174
+            MAX.Models.User user;
175
+            try
176
+            {
177
+                user = await MAX.Utils.AuthenticateUserAsync(_clientFactory, _logger,
178
+                    credentials.User.Id, credentials.User.Username, credentials.Vendor.Id,
179
+                    credentials.Vendor.SerialNumber, credentials.Password);
180
+            }
181
+            catch (Exception e)
182
+            {
183
+                _logger.LogError(
184
+                    "AuthenticateUserAsync failed for {0}: {1}",
185
+                    MAX.LoginCredentials.Format(credentials.User.Id, credentials.User.Username, credentials.Vendor.Id,
186
+                        credentials.Vendor.SerialNumber), e.Message);
187
+                return Unauthorized();
188
+            }
189
+
190
+            MAX.LoginCredentials possiblyUpdatedCredentials;
191
+            try
192
+            {
193
+                possiblyUpdatedCredentials = await Utils.SyncUserAndVendorWithDbAsync(_context, _dataEncryptionOptions,
194
+                    user, credentials.Password, credentials.Vendor.Id, credentials.Vendor.SerialNumber);
195
+            }
196
+            catch (Exception e)
197
+            {
198
+                _logger.LogError(
199
+                    "SyncUserAndVendorWithDb failed for {0}: {1}",
200
+                    MAX.LoginCredentials.Format(credentials.User.Id, credentials.User.Username, credentials.Vendor.Id,
201
+                        credentials.Vendor.SerialNumber), e.Message);
202
+                return Unauthorized();
203
+            }
204
+
205
+            var now = DateTimeOffset.UtcNow;
206
+
207
+            _context.VendorEvents.Add(new MAX.Models.VendorEvent()
208
+            {
209
+                EventDate = now,
210
+                EventType = MAX.Models.VendorEvent.VendorEventType.ExtendLogin,
211
+                UserId = possiblyUpdatedCredentials.User.Id,
212
+                VendorId = possiblyUpdatedCredentials.Vendor.Id
213
+            });
214
+            await _context.SaveChangesAsync();
215
+
216
+            return Ok(new
217
+            {
218
+                date = now,
219
+                access_token = await GetJwtAsync(now.DateTime, possiblyUpdatedCredentials),
220
+                user = possiblyUpdatedCredentials.User,
221
+                expires_in = (int)_tokenAuthenticationOptions.Lifetime.TotalSeconds
222
+            });
223
+        }
224
+
225
+        private async Task<string> GetJwtAsync(DateTime date, MAX.LoginCredentials credentials)
226
+        {
227
+            return new JwtSecurityTokenHandler().CreateEncodedJwt(
228
+                issuer: _tokenAuthenticationOptions.Issuer,
229
+                audience: _tokenAuthenticationOptions.Audience,
230
+                subject: new ClaimsIdentity(new Claim[] {
231
+                       new Claim(JwtRegisteredClaimNames.Sub, credentials.User.Id.ToString()),
232
+                       new Claim("xpwd", credentials.Password),
233
+                       new Claim("xvid", credentials.Vendor.Id.ToString()),
234
+                       new Claim(JwtRegisteredClaimNames.Jti, await _tokenAuthenticationOptions.NonceGenerator())
235
+                }),
236
+                notBefore: date,
237
+                expires: date.Add(_tokenAuthenticationOptions.Lifetime),
238
+                issuedAt: date,
239
+                signingCredentials: _tokenAuthenticationOptions.SigningCredentials,
240
+                encryptingCredentials: _tokenAuthenticationOptions.EncryptingCredentials
241
+            );
242
+        }
171
     }
243
     }
172
 }
244
 }

+ 3 - 3
BulkPrintingAPI/Controllers/ProductsController.cs

26
         }
26
         }
27
 
27
 
28
         [HttpGet]
28
         [HttpGet]
29
-        public async Task<List<Network>> Get()
29
+        public async Task<List<Network>> GetAsync([FromQuery] bool refresh = false)
30
         {
30
         {
31
-            var credentials = await Utils.GetLoginCredentialsFromRequest(HttpContext, _context);
31
+            var credentials = await Utils.GetLoginCredentialsFromRequestAsync(HttpContext, _context);
32
             var catalogue = await Utils.GetProductCatalogueAsync(_clientFactory, _logger, _cache,
32
             var catalogue = await Utils.GetProductCatalogueAsync(_clientFactory, _logger, _cache,
33
-                credentials);
33
+                credentials, refresh);
34
             return catalogue.Networks;
34
             return catalogue.Networks;
35
         }
35
         }
36
     }
36
     }

+ 9 - 6
BulkPrintingAPI/Controllers/Utils.cs

125
             }
125
             }
126
         }
126
         }
127
 
127
 
128
-        public static async Task<MAX.LoginCredentials> SyncUserAndVendorWithDb(MAXContext context,
128
+        public static async Task<MAX.LoginCredentials> SyncUserAndVendorWithDbAsync(MAXContext context,
129
             DataEncryptionOptions dataEncryptionOptions, User user, string password, int vendorId,
129
             DataEncryptionOptions dataEncryptionOptions, User user, string password, int vendorId,
130
             string serialNumber)
130
             string serialNumber)
131
         {
131
         {
219
             };
219
             };
220
         }
220
         }
221
 
221
 
222
-        public static async Task<MAX.LoginCredentials> GetLoginCredentialsFromRequest(
222
+        public static async Task<MAX.LoginCredentials> GetLoginCredentialsFromRequestAsync(
223
             HttpContext httpContext, MAXContext dbContext)
223
             HttpContext httpContext, MAXContext dbContext)
224
         {
224
         {
225
             var authInfo = await httpContext.Authentication.GetAuthenticateInfoAsync("Bearer");
225
             var authInfo = await httpContext.Authentication.GetAuthenticateInfoAsync("Bearer");
273
 
273
 
274
         public static async Task<ProductCatalogue> GetProductCatalogueAsync(
274
         public static async Task<ProductCatalogue> GetProductCatalogueAsync(
275
             MAX.ClientFactory clientFactory, ILogger logger, IMemoryCache cache,
275
             MAX.ClientFactory clientFactory, ILogger logger, IMemoryCache cache,
276
-            MAX.LoginCredentials credentials)
276
+            MAX.LoginCredentials credentials, bool forceRefresh)
277
         {
277
         {
278
-            return await cache.GetOrCreateAsync(
279
-                CacheKeys.GetAccountProductsKey(credentials.User.AccountId),
280
-                entry =>
278
+            var key = CacheKeys.GetAccountProductsKey(credentials.User.AccountId);
279
+            if (forceRefresh)
280
+            {
281
+                cache.Remove(key);
282
+            }
283
+            return await cache.GetOrCreateAsync(key, entry =>
281
                 {
284
                 {
282
                     // TODO: set expiry time, etc.
285
                     // TODO: set expiry time, etc.
283
                     return MAX.Utils.GetProductCatalogueAsync(clientFactory, logger, credentials);
286
                     return MAX.Utils.GetProductCatalogueAsync(clientFactory, logger, credentials);

+ 117 - 0
BulkPrintingAPI/Controllers/VendorEventsController.cs

1
+using BulkPrintingAPI.Pagination;
2
+using MAX.Models;
3
+using Microsoft.AspNetCore.Mvc;
4
+using System;
5
+using System.Collections.Generic;
6
+using System.ComponentModel.DataAnnotations;
7
+using System.Linq;
8
+using System.Threading.Tasks;
9
+
10
+namespace BulkPrintingAPI.Controllers
11
+{
12
+    [Produces("application/json")]
13
+    [Route("api/[controller]")]
14
+    public class VendorEventsController : Controller
15
+    {
16
+        private readonly MAXContext _context;
17
+
18
+        public class RemoteVendorEvent
19
+        {
20
+            [Required]
21
+            public int? Id { get; set; }
22
+
23
+            [Required]
24
+            public int? VendorId { get; set; }
25
+
26
+            [Required]
27
+            public int? UserId { get; set; }
28
+
29
+            public int? VoucherId { get; set; }
30
+
31
+            [Required]
32
+            public DateTimeOffset? EventDate { get; set; }
33
+
34
+            [Required]
35
+            public VendorEvent.VendorEventType? EventType { get; set; }
36
+        }
37
+
38
+        public VendorEventsController(MAXContext context)
39
+        {
40
+            _context = context;
41
+        }
42
+
43
+        [HttpGet]
44
+        public async Task<Page<VendorEvent>> GetVendorEventsAsync([FromQuery] int page = 1,
45
+            [FromQuery] int pageSize = 100)
46
+        {
47
+            var credentials = await Utils.GetLoginCredentialsFromRequestAsync(HttpContext, _context);
48
+            return await Page<VendorEvent>.GetPageAsync(
49
+                VendorEventsForVendor(credentials.Vendor.Id).OrderByDescending(e => e.EventDate),
50
+                page, pageSize);
51
+        }
52
+
53
+        [HttpPost]
54
+        public async Task<IActionResult> PostRemoteVendorEventsAsync([FromBody] List<RemoteVendorEvent> remoteVendorEvents)
55
+        {
56
+            if (! ModelState.IsValid)
57
+            {
58
+                return BadRequest(ModelState);
59
+            }
60
+
61
+            var credentials = await Utils.GetLoginCredentialsFromRequestAsync(HttpContext, _context);
62
+            var vendorId = credentials.Vendor.Id;
63
+            var lastVendorEventRemoteId = credentials.Vendor.LastVendorEventRemoteId;
64
+            var maxRemoteId = lastVendorEventRemoteId;
65
+            foreach (var remoteEvent in remoteVendorEvents)
66
+            {
67
+                if (vendorId != remoteEvent.VendorId)
68
+                {
69
+                    return BadRequest(new { error = "Vendor ID mismatch" });
70
+                }
71
+                if (lastVendorEventRemoteId.HasValue && (lastVendorEventRemoteId.Value >= remoteEvent.Id.Value))
72
+                {
73
+                    return BadRequest(new { error = "A supplied ID value is less than or equal to lastVendorRemoteId" });
74
+                }
75
+                if (! maxRemoteId.HasValue || (remoteEvent.Id.Value > maxRemoteId.Value))
76
+                {
77
+                    maxRemoteId = remoteEvent.Id.Value;
78
+                }
79
+                _context.VendorEvents.Add(new VendorEvent()
80
+                {
81
+                    RemoteId = remoteEvent.Id.Value,
82
+                    UserId = remoteEvent.UserId.Value,
83
+                    VendorId = remoteEvent.VendorId.Value,
84
+                    VoucherId = remoteEvent.VoucherId,
85
+                    EventType = remoteEvent.EventType.Value,
86
+                    EventDate = remoteEvent.EventDate.Value.ToUniversalTime()
87
+                });
88
+            }
89
+
90
+            // FIXME: race condition for simultaneous requests, although we should never have any from the same vendor
91
+            credentials.Vendor.LastVendorEventRemoteId = maxRemoteId;
92
+            await _context.SaveChangesAsync();
93
+
94
+            return Ok(new
95
+            {
96
+                lastVendorEventRemoteId = credentials.Vendor.LastVendorEventRemoteId
97
+            });
98
+        }
99
+
100
+        [HttpGet("meta")]
101
+        public async Task<IActionResult> GetVendorEventMetadata()
102
+        {
103
+            var credentials = await Utils.GetLoginCredentialsFromRequestAsync(HttpContext, _context);
104
+            return Ok(new
105
+            {
106
+                date = DateTimeOffset.UtcNow,
107
+                vendorId = credentials.Vendor.Id,
108
+                lastVendorEventRemoteId = credentials.Vendor.LastVendorEventRemoteId
109
+            });
110
+        }
111
+
112
+        private IQueryable<VendorEvent> VendorEventsForVendor(int vendorId)
113
+        {
114
+            return _context.VendorEvents.Where(e => e.VendorId == vendorId);
115
+        }
116
+    }
117
+}

+ 33 - 6
BulkPrintingAPI/Controllers/VouchersController.cs

4
 using Microsoft.AspNetCore.Mvc;
4
 using Microsoft.AspNetCore.Mvc;
5
 using Microsoft.EntityFrameworkCore;
5
 using Microsoft.EntityFrameworkCore;
6
 using Microsoft.Extensions.Logging;
6
 using Microsoft.Extensions.Logging;
7
+using System;
7
 using System.Linq;
8
 using System.Linq;
8
 using System.Threading.Tasks;
9
 using System.Threading.Tasks;
9
 
10
 
26
         }
27
         }
27
 
28
 
28
         [HttpGet]
29
         [HttpGet]
29
-        public async Task<IActionResult> GetVouchers([FromRoute] int batchId,
30
+        public async Task<IActionResult> GetVouchersAsync([FromRoute] int batchId,
30
             [FromQuery] int page = 1, [FromQuery] int pageSize = 100)
31
             [FromQuery] int page = 1, [FromQuery] int pageSize = 100)
31
         {
32
         {
32
             if (!ModelState.IsValid)
33
             if (!ModelState.IsValid)
34
                 return BadRequest(ModelState);
35
                 return BadRequest(ModelState);
35
             }
36
             }
36
 
37
 
37
-            var credentials = await Utils.GetLoginCredentialsFromRequest(HttpContext, _context);
38
-            return Ok(await Page<Voucher>.GetPageAsync(
38
+            var credentials = await Utils.GetLoginCredentialsFromRequestAsync(HttpContext, _context);
39
+            var result = await Page<Voucher>.GetPageAsync(
39
                 VouchersForBatchAndVendor(batchId, credentials.Vendor.Id)
40
                 VouchersForBatchAndVendor(batchId, credentials.Vendor.Id)
40
                     .OrderBy(v => new { v.BatchId, v.SequenceNumber }),
41
                     .OrderBy(v => new { v.BatchId, v.SequenceNumber }),
41
-                page, pageSize));
42
+                page, pageSize);
43
+
44
+            var now = DateTimeOffset.UtcNow;
45
+            foreach (var voucher in result.Items)
46
+            {
47
+                _context.VendorEvents.Add(new VendorEvent()
48
+                {
49
+                    EventDate = now,
50
+                    EventType = VendorEvent.VendorEventType.DownloadVoucher,
51
+                    UserId = credentials.User.Id,
52
+                    VendorId = credentials.Vendor.Id,
53
+                    VoucherId = voucher.Id
54
+                });
55
+            }
56
+            await _context.SaveChangesAsync();
57
+
58
+            return Ok(result);
42
         }
59
         }
43
 
60
 
44
         [HttpGet("{sequenceNumber}")]
61
         [HttpGet("{sequenceNumber}")]
45
-        public async Task<IActionResult> GetVoucher([FromRoute] int batchId, [FromRoute] int sequenceNumber)
62
+        public async Task<IActionResult> GetVoucherAsync([FromRoute] int batchId, [FromRoute] int sequenceNumber)
46
         {
63
         {
47
             if (!ModelState.IsValid)
64
             if (!ModelState.IsValid)
48
             {
65
             {
49
                 return BadRequest(ModelState);
66
                 return BadRequest(ModelState);
50
             }
67
             }
51
 
68
 
52
-            var credentials = await Utils.GetLoginCredentialsFromRequest(HttpContext, _context);
69
+            var credentials = await Utils.GetLoginCredentialsFromRequestAsync(HttpContext, _context);
53
             var voucher = await VouchersForBatchAndVendor(batchId, credentials.Vendor.Id)
70
             var voucher = await VouchersForBatchAndVendor(batchId, credentials.Vendor.Id)
54
                 .SingleOrDefaultAsync(v => (v.BatchId == batchId) && (v.SequenceNumber == sequenceNumber));
71
                 .SingleOrDefaultAsync(v => (v.BatchId == batchId) && (v.SequenceNumber == sequenceNumber));
55
 
72
 
58
                 return NotFound();
75
                 return NotFound();
59
             }
76
             }
60
 
77
 
78
+            _context.VendorEvents.Add(new VendorEvent()
79
+            {
80
+                EventDate = DateTimeOffset.UtcNow,
81
+                EventType = VendorEvent.VendorEventType.DownloadVoucher,
82
+                UserId = credentials.User.Id,
83
+                VendorId = credentials.Vendor.Id,
84
+                VoucherId = voucher.Id
85
+            });
86
+            await _context.SaveChangesAsync();
87
+
61
             return Ok(voucher);
88
             return Ok(voucher);
62
         }
89
         }
63
 
90
 

+ 35 - 3
BulkPrintingAPI/Migrations/20170705205151_InitialCreate.Designer.cs

8
 namespace BulkPrintingAPI.Migrations
8
 namespace BulkPrintingAPI.Migrations
9
 {
9
 {
10
     [DbContext(typeof(MAXContext))]
10
     [DbContext(typeof(MAXContext))]
11
-    [Migration("20170705205151_InitialCreate")]
11
+    [Migration("20170711104915_InitialCreate")]
12
     partial class InitialCreate
12
     partial class InitialCreate
13
     {
13
     {
14
         protected override void BuildTargetModel(ModelBuilder modelBuilder)
14
         protected override void BuildTargetModel(ModelBuilder modelBuilder)
62
                         .IsRequired()
62
                         .IsRequired()
63
                         .HasMaxLength(20);
63
                         .HasMaxLength(20);
64
 
64
 
65
-                    b.Property<DateTime>("OrderDate");
65
+                    b.Property<DateTimeOffset>("OrderDate");
66
+
67
+                    b.Property<Guid?>("OrderGuid");
66
 
68
 
67
                     b.Property<string>("OrderReference")
69
                     b.Property<string>("OrderReference")
68
                         .IsRequired()
70
                         .IsRequired()
88
 
90
 
89
                     b.HasIndex("AccountId");
91
                     b.HasIndex("AccountId");
90
 
92
 
93
+                    b.HasIndex("OrderGuid")
94
+                        .IsUnique();
95
+
91
                     b.HasIndex("ReadyForDownload", "OrderDate");
96
                     b.HasIndex("ReadyForDownload", "OrderDate");
92
 
97
 
93
                     b.ToTable("Batches");
98
                     b.ToTable("Batches");
113
                         .IsRequired()
118
                         .IsRequired()
114
                         .HasMaxLength(50);
119
                         .HasMaxLength(50);
115
 
120
 
116
-                    b.Property<DateTime>("LastLogin");
121
+                    b.Property<DateTimeOffset>("LastLogin");
117
 
122
 
118
                     b.Property<int>("Level");
123
                     b.Property<int>("Level");
119
 
124
 
156
                         .IsRequired()
161
                         .IsRequired()
157
                         .HasMaxLength(32);
162
                         .HasMaxLength(32);
158
 
163
 
164
+                    b.Property<int?>("LastVendorEventRemoteId");
165
+
159
                     b.Property<string>("SerialNumber")
166
                     b.Property<string>("SerialNumber")
160
                         .IsRequired()
167
                         .IsRequired()
161
                         .HasMaxLength(50);
168
                         .HasMaxLength(50);
167
                     b.ToTable("Vendors");
174
                     b.ToTable("Vendors");
168
                 });
175
                 });
169
 
176
 
177
+            modelBuilder.Entity("MAX.Models.VendorEvent", b =>
178
+                {
179
+                    b.Property<int>("Id")
180
+                        .ValueGeneratedOnAdd();
181
+
182
+                    b.Property<DateTimeOffset>("EventDate");
183
+
184
+                    b.Property<int>("EventType");
185
+
186
+                    b.Property<int?>("RemoteId");
187
+
188
+                    b.Property<int>("UserId");
189
+
190
+                    b.Property<int>("VendorId");
191
+
192
+                    b.Property<int?>("VoucherId");
193
+
194
+                    b.HasKey("Id");
195
+
196
+                    b.HasIndex("VendorId", "RemoteId")
197
+                        .IsUnique();
198
+
199
+                    b.ToTable("VendorEvents");
200
+                });
201
+
170
             modelBuilder.Entity("MAX.Models.Voucher", b =>
202
             modelBuilder.Entity("MAX.Models.Voucher", b =>
171
                 {
203
                 {
172
                     b.Property<int>("Id");
204
                     b.Property<int>("Id");

+ 38 - 2
BulkPrintingAPI/Migrations/20170705205151_InitialCreate.cs

1
 using System;
1
 using System;
2
 using System.Collections.Generic;
2
 using System.Collections.Generic;
3
 using Microsoft.EntityFrameworkCore.Migrations;
3
 using Microsoft.EntityFrameworkCore.Migrations;
4
+using Microsoft.EntityFrameworkCore.Metadata;
4
 
5
 
5
 namespace BulkPrintingAPI.Migrations
6
 namespace BulkPrintingAPI.Migrations
6
 {
7
 {
9
         protected override void Up(MigrationBuilder migrationBuilder)
10
         protected override void Up(MigrationBuilder migrationBuilder)
10
         {
11
         {
11
             migrationBuilder.CreateTable(
12
             migrationBuilder.CreateTable(
13
+                name: "VendorEvents",
14
+                columns: table => new
15
+                {
16
+                    Id = table.Column<int>(nullable: false)
17
+                        .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
18
+                    EventDate = table.Column<DateTimeOffset>(nullable: false),
19
+                    EventType = table.Column<int>(nullable: false),
20
+                    RemoteId = table.Column<int>(nullable: true),
21
+                    UserId = table.Column<int>(nullable: false),
22
+                    VendorId = table.Column<int>(nullable: false),
23
+                    VoucherId = table.Column<int>(nullable: true)
24
+                },
25
+                constraints: table =>
26
+                {
27
+                    table.PrimaryKey("PK_VendorEvents", x => x.Id);
28
+                });
29
+
30
+            migrationBuilder.CreateTable(
12
                 name: "Warehouses",
31
                 name: "Warehouses",
13
                 columns: table => new
32
                 columns: table => new
14
                 {
33
                 {
54
                     FaceValue = table.Column<decimal>(nullable: false),
73
                     FaceValue = table.Column<decimal>(nullable: false),
55
                     NetworkId = table.Column<int>(nullable: false),
74
                     NetworkId = table.Column<int>(nullable: false),
56
                     NetworkName = table.Column<string>(maxLength: 20, nullable: false),
75
                     NetworkName = table.Column<string>(maxLength: 20, nullable: false),
57
-                    OrderDate = table.Column<DateTime>(nullable: false),
76
+                    OrderDate = table.Column<DateTimeOffset>(nullable: false),
77
+                    OrderGuid = table.Column<Guid>(nullable: true),
58
                     OrderReference = table.Column<string>(maxLength: 20, nullable: false),
78
                     OrderReference = table.Column<string>(maxLength: 20, nullable: false),
59
                     OrderedById = table.Column<int>(nullable: false),
79
                     OrderedById = table.Column<int>(nullable: false),
60
                     ProductDescription = table.Column<string>(maxLength: 50, nullable: false),
80
                     ProductDescription = table.Column<string>(maxLength: 50, nullable: false),
87
                     CanReprintOnline = table.Column<bool>(nullable: false),
107
                     CanReprintOnline = table.Column<bool>(nullable: false),
88
                     Enabled = table.Column<bool>(nullable: false),
108
                     Enabled = table.Column<bool>(nullable: false),
89
                     FirstName = table.Column<string>(maxLength: 50, nullable: false),
109
                     FirstName = table.Column<string>(maxLength: 50, nullable: false),
90
-                    LastLogin = table.Column<DateTime>(nullable: false),
110
+                    LastLogin = table.Column<DateTimeOffset>(nullable: false),
91
                     Level = table.Column<int>(nullable: false),
111
                     Level = table.Column<int>(nullable: false),
92
                     OfflinePrintValue = table.Column<decimal>(nullable: false),
112
                     OfflinePrintValue = table.Column<decimal>(nullable: false),
93
                     OfflineReprintValue = table.Column<decimal>(nullable: false),
113
                     OfflineReprintValue = table.Column<decimal>(nullable: false),
116
                     AccountId = table.Column<int>(nullable: false),
136
                     AccountId = table.Column<int>(nullable: false),
117
                     EncryptedDatabasePassword = table.Column<byte[]>(maxLength: 32, nullable: false),
137
                     EncryptedDatabasePassword = table.Column<byte[]>(maxLength: 32, nullable: false),
118
                     EncryptedVoucherKey = table.Column<byte[]>(maxLength: 32, nullable: false),
138
                     EncryptedVoucherKey = table.Column<byte[]>(maxLength: 32, nullable: false),
139
+                    LastVendorEventRemoteId = table.Column<int>(nullable: true),
119
                     SerialNumber = table.Column<string>(maxLength: 50, nullable: false)
140
                     SerialNumber = table.Column<string>(maxLength: 50, nullable: false)
120
                 },
141
                 },
121
                 constraints: table =>
142
                 constraints: table =>
162
                 column: "AccountId");
183
                 column: "AccountId");
163
 
184
 
164
             migrationBuilder.CreateIndex(
185
             migrationBuilder.CreateIndex(
186
+                name: "IX_Batches_OrderGuid",
187
+                table: "Batches",
188
+                column: "OrderGuid",
189
+                unique: true);
190
+
191
+            migrationBuilder.CreateIndex(
165
                 name: "IX_Batches_ReadyForDownload_OrderDate",
192
                 name: "IX_Batches_ReadyForDownload_OrderDate",
166
                 table: "Batches",
193
                 table: "Batches",
167
                 columns: new[] { "ReadyForDownload", "OrderDate" });
194
                 columns: new[] { "ReadyForDownload", "OrderDate" });
177
                 column: "AccountId");
204
                 column: "AccountId");
178
 
205
 
179
             migrationBuilder.CreateIndex(
206
             migrationBuilder.CreateIndex(
207
+                name: "IX_VendorEvents_VendorId_RemoteId",
208
+                table: "VendorEvents",
209
+                columns: new[] { "VendorId", "RemoteId" },
210
+                unique: true);
211
+
212
+            migrationBuilder.CreateIndex(
180
                 name: "IX_Vouchers_BatchId_SequenceNumber",
213
                 name: "IX_Vouchers_BatchId_SequenceNumber",
181
                 table: "Vouchers",
214
                 table: "Vouchers",
182
                 columns: new[] { "BatchId", "SequenceNumber" },
215
                 columns: new[] { "BatchId", "SequenceNumber" },
192
                 name: "Vendors");
225
                 name: "Vendors");
193
 
226
 
194
             migrationBuilder.DropTable(
227
             migrationBuilder.DropTable(
228
+                name: "VendorEvents");
229
+
230
+            migrationBuilder.DropTable(
195
                 name: "Vouchers");
231
                 name: "Vouchers");
196
 
232
 
197
             migrationBuilder.DropTable(
233
             migrationBuilder.DropTable(

+ 34 - 2
BulkPrintingAPI/Migrations/MAXContextModelSnapshot.cs

61
                         .IsRequired()
61
                         .IsRequired()
62
                         .HasMaxLength(20);
62
                         .HasMaxLength(20);
63
 
63
 
64
-                    b.Property<DateTime>("OrderDate");
64
+                    b.Property<DateTimeOffset>("OrderDate");
65
+
66
+                    b.Property<Guid?>("OrderGuid");
65
 
67
 
66
                     b.Property<string>("OrderReference")
68
                     b.Property<string>("OrderReference")
67
                         .IsRequired()
69
                         .IsRequired()
87
 
89
 
88
                     b.HasIndex("AccountId");
90
                     b.HasIndex("AccountId");
89
 
91
 
92
+                    b.HasIndex("OrderGuid")
93
+                        .IsUnique();
94
+
90
                     b.HasIndex("ReadyForDownload", "OrderDate");
95
                     b.HasIndex("ReadyForDownload", "OrderDate");
91
 
96
 
92
                     b.ToTable("Batches");
97
                     b.ToTable("Batches");
112
                         .IsRequired()
117
                         .IsRequired()
113
                         .HasMaxLength(50);
118
                         .HasMaxLength(50);
114
 
119
 
115
-                    b.Property<DateTime>("LastLogin");
120
+                    b.Property<DateTimeOffset>("LastLogin");
116
 
121
 
117
                     b.Property<int>("Level");
122
                     b.Property<int>("Level");
118
 
123
 
155
                         .IsRequired()
160
                         .IsRequired()
156
                         .HasMaxLength(32);
161
                         .HasMaxLength(32);
157
 
162
 
163
+                    b.Property<int?>("LastVendorEventRemoteId");
164
+
158
                     b.Property<string>("SerialNumber")
165
                     b.Property<string>("SerialNumber")
159
                         .IsRequired()
166
                         .IsRequired()
160
                         .HasMaxLength(50);
167
                         .HasMaxLength(50);
166
                     b.ToTable("Vendors");
173
                     b.ToTable("Vendors");
167
                 });
174
                 });
168
 
175
 
176
+            modelBuilder.Entity("MAX.Models.VendorEvent", b =>
177
+                {
178
+                    b.Property<int>("Id")
179
+                        .ValueGeneratedOnAdd();
180
+
181
+                    b.Property<DateTimeOffset>("EventDate");
182
+
183
+                    b.Property<int>("EventType");
184
+
185
+                    b.Property<int?>("RemoteId");
186
+
187
+                    b.Property<int>("UserId");
188
+
189
+                    b.Property<int>("VendorId");
190
+
191
+                    b.Property<int?>("VoucherId");
192
+
193
+                    b.HasKey("Id");
194
+
195
+                    b.HasIndex("VendorId", "RemoteId")
196
+                        .IsUnique();
197
+
198
+                    b.ToTable("VendorEvents");
199
+                });
200
+
169
             modelBuilder.Entity("MAX.Models.Voucher", b =>
201
             modelBuilder.Entity("MAX.Models.Voucher", b =>
170
                 {
202
                 {
171
                     b.Property<int>("Id");
203
                     b.Property<int>("Id");

+ 20 - 0
BulkPrintingAPI/Properties/PublishProfiles/FolderProfile.pubxml

1
+<?xml version="1.0" encoding="utf-8"?>
2
+<!--
3
+This file is used by the publish/package process of your Web project. You can customize the behavior of this process
4
+by editing this MSBuild file. In order to learn more about this please visit https://go.microsoft.com/fwlink/?LinkID=208121. 
5
+-->
6
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
7
+  <PropertyGroup>
8
+    <WebPublishMethod>FileSystem</WebPublishMethod>
9
+    <PublishProvider>FileSystem</PublishProvider>
10
+    <LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
11
+    <LastUsedPlatform>Any CPU</LastUsedPlatform>
12
+    <SiteUrlToLaunchAfterPublish />
13
+    <LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>
14
+    <ExcludeApp_Data>False</ExcludeApp_Data>
15
+    <PublishFramework />
16
+    <ProjectGuid>92eeb85e-10ff-4902-9f73-ba4d58344824</ProjectGuid>
17
+    <publishUrl>bin\Release\PublishOutput</publishUrl>
18
+    <DeleteExistingFiles>False</DeleteExistingFiles>
19
+  </PropertyGroup>
20
+</Project>

+ 1 - 2
BulkPrintingAPI/appsettings.json

11
   "TokenAuthentication": {
11
   "TokenAuthentication": {
12
     "Audience": "bulk",
12
     "Audience": "bulk",
13
     "Issuer": "bulk",
13
     "Issuer": "bulk",
14
-    "SecretKey": "SET IN secrets.json",
15
-    "Salt": "SET IN secrets.json",
14
+    "Key": "SET IN secrets.json",
16
     "TokenLifetime": 86400
15
     "TokenLifetime": 86400
17
   }
16
   }
18
 }
17
 }

+ 1 - 1
MAXClient/Client.cs

380
                         DiscountPercentage = product.DiscountPercentage,
380
                         DiscountPercentage = product.DiscountPercentage,
381
                         NetworkId = product.Network.Id,
381
                         NetworkId = product.Network.Id,
382
                         NetworkName = product.Network.Name,
382
                         NetworkName = product.Network.Name,
383
-                        OrderDate = DateTime.Now,
383
+                        OrderDate = DateTimeOffset.UtcNow,
384
                         OrderedById = _userId,
384
                         OrderedById = _userId,
385
                         ReadyForDownload = false
385
                         ReadyForDownload = false
386
                     },
386
                     },

+ 4 - 4
MAXData/Models/Account.cs

9
     {
9
     {
10
         public enum AccountStatus
10
         public enum AccountStatus
11
         {
11
         {
12
-            Unknown,
13
-            Enabled,
14
-            Suspended,
15
-            Closed
12
+            Unknown = 0,
13
+            Enabled = 1,
14
+            Suspended = 2,
15
+            Closed = 3
16
         }
16
         }
17
 
17
 
18
         [DatabaseGenerated(DatabaseGeneratedOption.None)]
18
         [DatabaseGenerated(DatabaseGeneratedOption.None)]

+ 5 - 3
MAXData/Models/Batch.cs

11
         public enum Vouchertype
11
         public enum Vouchertype
12
         {
12
         {
13
             Voucher = 1,
13
             Voucher = 1,
14
-            SMS,
15
-            Data
14
+            SMS = 2,
15
+            Data = 3
16
         }
16
         }
17
 
17
 
18
         [DatabaseGenerated(DatabaseGeneratedOption.None)]
18
         [DatabaseGenerated(DatabaseGeneratedOption.None)]
38
         //[IgnoreDataMember]
38
         //[IgnoreDataMember]
39
         //public User OrderedBy { get; set; }
39
         //public User OrderedBy { get; set; }
40
 
40
 
41
-        public DateTime OrderDate { get; set; }
41
+        public DateTimeOffset OrderDate { get; set; }
42
+
43
+        public Guid? OrderGuid { get; set; }
42
 
44
 
43
         [Required, MaxLength(20)]
45
         [Required, MaxLength(20)]
44
         public string OrderReference { get; set; }
46
         public string OrderReference { get; set; }

+ 10 - 1
MAXData/Models/MAXContext.cs

1
 using Microsoft.EntityFrameworkCore;
1
 using Microsoft.EntityFrameworkCore;
2
-using Microsoft.EntityFrameworkCore.Metadata;
3
 
2
 
4
 namespace MAX.Models
3
 namespace MAX.Models
5
 {
4
 {
14
             modelBuilder.Entity<Batch>()
13
             modelBuilder.Entity<Batch>()
15
                 .HasIndex(b => new { b.ReadyForDownload, b.OrderDate });
14
                 .HasIndex(b => new { b.ReadyForDownload, b.OrderDate });
16
 
15
 
16
+            modelBuilder.Entity<Batch>()
17
+                .HasIndex(b => new { b.OrderGuid })
18
+                .IsUnique();
19
+
20
+            modelBuilder.Entity<VendorEvent>()
21
+                .HasIndex(e => new { e.VendorId, e.RemoteId })
22
+                .IsUnique();
23
+
17
             modelBuilder.Entity<Voucher>()
24
             modelBuilder.Entity<Voucher>()
18
                 .HasIndex(v => new { v.BatchId, v.SequenceNumber })
25
                 .HasIndex(v => new { v.BatchId, v.SequenceNumber })
19
                 .IsUnique();
26
                 .IsUnique();
25
 
32
 
26
         public DbSet<User> Users { get; set; }
33
         public DbSet<User> Users { get; set; }
27
 
34
 
35
+        public DbSet<VendorEvent> VendorEvents { get; set; }
36
+
28
         public DbSet<Vendor> Vendors { get; set; }
37
         public DbSet<Vendor> Vendors { get; set; }
29
 
38
 
30
         public DbSet<Voucher> Vouchers { get; set; }
39
         public DbSet<Voucher> Vouchers { get; set; }

+ 1 - 1
MAXData/Models/User.cs

37
 
37
 
38
         public int System { get; set; }
38
         public int System { get; set; }
39
 
39
 
40
-        public DateTime LastLogin { get; set; }
40
+        public DateTimeOffset LastLogin { get; set; }
41
 
41
 
42
         public bool CanPrintOnline { get; set; }
42
         public bool CanPrintOnline { get; set; }
43
 
43
 

+ 2 - 0
MAXData/Models/Vendor.cs

24
         [IgnoreDataMember]
24
         [IgnoreDataMember]
25
         [Required, MaxLength(32)]
25
         [Required, MaxLength(32)]
26
         public byte[] EncryptedVoucherKey { get; set; }
26
         public byte[] EncryptedVoucherKey { get; set; }
27
+
28
+        public int? LastVendorEventRemoteId { get; set; }
27
     }
29
     }
28
 }
30
 }

+ 33 - 0
MAXData/Models/VendorEvent.cs

1
+using System;
2
+
3
+namespace MAX.Models
4
+{
5
+    public class VendorEvent
6
+    {
7
+        public enum VendorEventType
8
+        {
9
+            Unknown = 0,
10
+            Login = 1,
11
+            OfflineLogin = 2,
12
+            ExtendLogin = 3,
13
+            Logout = 4,
14
+            DownloadVoucher = 5,
15
+            PrintVoucher = 6,
16
+            ViewVoucherPIN = 7
17
+        }
18
+
19
+        public int Id { get; set; }
20
+
21
+        public int VendorId { get; set; }
22
+
23
+        public int UserId { get; set; }
24
+
25
+        public int? VoucherId { get; set; }
26
+
27
+        public int? RemoteId { get; set; }
28
+
29
+        public DateTimeOffset EventDate { get; set; }
30
+
31
+        public VendorEventType EventType { get; set; }
32
+    }
33
+}