1+ using System . Linq . Expressions ;
12using System . Net . Mime ;
3+ using Asp . Versioning ;
24using Microsoft . AspNetCore . Mvc ;
35using Microsoft . EntityFrameworkCore ;
46using OpenShock . API . Models . Requests ;
@@ -13,25 +15,57 @@ namespace OpenShock.API.Controller.Tokens;
1315
1416public sealed partial class TokensController
1517{
18+ private static readonly Expression < Func < ApiToken , TokenResponse > > ToTokenResponse = x => new TokenResponse
19+ {
20+ CreatedOn = x . CreatedAt ,
21+ ValidUntil = x . ValidUntil ,
22+ LastUsed = x . LastUsed ?? default ,
23+ Permissions = x . Permissions ,
24+ Name = x . Name ,
25+ Id = x . Id
26+ } ;
27+
28+ private static readonly Expression < Func < ApiToken , TokenResponseV2 > > ToTokenResponseV2 = x => new TokenResponseV2
29+ {
30+ CreatedOn = x . CreatedAt ,
31+ ValidUntil = x . ValidUntil ,
32+ LastUsed = x . LastUsed ,
33+ Permissions = x . Permissions ,
34+ Name = x . Name ,
35+ Id = x . Id
36+ } ;
37+
38+ /// <summary>
39+ /// Tokens belonging to the current user that have not expired.
40+ /// </summary>
41+ private IQueryable < ApiToken > CurrentUserValidTokens => _db . ApiTokens
42+ . Where ( x => x . UserId == CurrentUser . Id && ( x . ValidUntil == null || x . ValidUntil > DateTime . UtcNow ) ) ;
43+
1644 /// <summary>
1745 /// List all tokens for the current user
1846 /// </summary>
1947 /// <response code="200">All tokens for the current user</response>
2048 [ HttpGet ]
49+ [ MapToApiVersion ( "1" ) ]
2150 public IAsyncEnumerable < TokenResponse > ListTokens ( )
2251 {
23- return _db . ApiTokens
24- . Where ( x => x . UserId == CurrentUser . Id && ( x . ValidUntil == null || x . ValidUntil > DateTime . UtcNow ) )
52+ return CurrentUserValidTokens
53+ . OrderBy ( x => x . CreatedAt )
54+ . Select ( ToTokenResponse )
55+ . AsAsyncEnumerable ( ) ;
56+ }
57+
58+ /// <summary>
59+ /// List all tokens for the current user
60+ /// </summary>
61+ /// <response code="200">All tokens for the current user</response>
62+ [ HttpGet ]
63+ [ MapToApiVersion ( "2" ) ]
64+ public IAsyncEnumerable < TokenResponseV2 > ListTokensV2 ( )
65+ {
66+ return CurrentUserValidTokens
2567 . OrderBy ( x => x . CreatedAt )
26- . Select ( x => new TokenResponse
27- {
28- CreatedOn = x . CreatedAt ,
29- ValidUntil = x . ValidUntil ,
30- LastUsed = x . LastUsed ,
31- Permissions = x . Permissions ,
32- Name = x . Name ,
33- Id = x . Id
34- } )
68+ . Select ( ToTokenResponseV2 )
3569 . AsAsyncEnumerable ( ) ;
3670 }
3771
@@ -43,23 +77,39 @@ public IAsyncEnumerable<TokenResponse> ListTokens()
4377 /// <response code="404">The token does not exist or you do not have access to it.</response>
4478 [ HttpGet ( "{tokenId}" ) ]
4579 [ ProducesResponseType < TokenResponse > ( StatusCodes . Status200OK , MediaTypeNames . Application . Json ) ]
46- [ ProducesResponseType < OpenShockProblem > ( StatusCodes . Status404NotFound , MediaTypeNames . Application . ProblemJson ) ] // ApiTokenNotFound
80+ [ ProducesResponseType < OpenShockProblem > ( StatusCodes . Status404NotFound , MediaTypeNames . Application . ProblemJson ) ] // ApiTokenNotFound
81+ [ MapToApiVersion ( "1" ) ]
4782 public async Task < IActionResult > GetTokenById ( [ FromRoute ] Guid tokenId )
4883 {
49- var apiToken = await _db . ApiTokens
50- . Where ( x => x . UserId == CurrentUser . Id && x . Id == tokenId && ( x . ValidUntil == null || x . ValidUntil > DateTime . UtcNow ) )
51- . Select ( x => new TokenResponse
52- {
53- CreatedOn = x . CreatedAt ,
54- ValidUntil = x . ValidUntil ,
55- Permissions = x . Permissions ,
56- LastUsed = x . LastUsed ,
57- Name = x . Name ,
58- Id = x . Id
59- } ) . FirstOrDefaultAsync ( ) ;
60-
84+ var apiToken = await CurrentUserValidTokens
85+ . Where ( x => x . Id == tokenId )
86+ . Select ( ToTokenResponse )
87+ . FirstOrDefaultAsync ( ) ;
88+
6189 if ( apiToken is null ) return Problem ( ApiTokenError . ApiTokenNotFound ) ;
62-
90+
91+ return Ok ( apiToken ) ;
92+ }
93+
94+ /// <summary>
95+ /// Get a token by id
96+ /// </summary>
97+ /// <param name="tokenId"></param>
98+ /// <response code="200">The token</response>
99+ /// <response code="404">The token does not exist or you do not have access to it.</response>
100+ [ HttpGet ( "{tokenId}" ) ]
101+ [ ProducesResponseType < TokenResponseV2 > ( StatusCodes . Status200OK , MediaTypeNames . Application . Json ) ]
102+ [ ProducesResponseType < OpenShockProblem > ( StatusCodes . Status404NotFound , MediaTypeNames . Application . ProblemJson ) ] // ApiTokenNotFound
103+ [ MapToApiVersion ( "2" ) ]
104+ public async Task < IActionResult > GetTokenByIdV2 ( [ FromRoute ] Guid tokenId )
105+ {
106+ var apiToken = await CurrentUserValidTokens
107+ . Where ( x => x . Id == tokenId )
108+ . Select ( ToTokenResponseV2 )
109+ . FirstOrDefaultAsync ( ) ;
110+
111+ if ( apiToken is null ) return Problem ( ApiTokenError . ApiTokenNotFound ) ;
112+
63113 return Ok ( apiToken ) ;
64114 }
65115
@@ -71,6 +121,7 @@ public async Task<IActionResult> GetTokenById([FromRoute] Guid tokenId)
71121 [ HttpPost ]
72122 [ Consumes ( MediaTypeNames . Application . Json ) ]
73123 [ Produces ( MediaTypeNames . Application . Json ) ]
124+ [ MapToApiVersion ( "1" ) ]
74125 public async Task < TokenCreatedResponse > CreateToken ( [ FromBody ] CreateTokenRequest body )
75126 {
76127 var token = CryptoUtils . RandomAlphaNumericString ( AuthConstants . ApiTokenLength ) ;
@@ -95,7 +146,7 @@ public async Task<TokenCreatedResponse> CreateToken([FromBody] CreateTokenReques
95146 Token = token ,
96147 CreatedAt = tokenDto . CreatedAt ,
97148 ValidUntil = tokenDto . ValidUntil ,
98- LastUsed = tokenDto . LastUsed ,
149+ LastUsed = tokenDto . LastUsed ?? default ,
99150 Permissions = tokenDto . Permissions
100151 } ;
101152 }
@@ -111,10 +162,10 @@ public async Task<TokenCreatedResponse> CreateToken([FromBody] CreateTokenReques
111162 [ Consumes ( MediaTypeNames . Application . Json ) ]
112163 [ ProducesResponseType ( StatusCodes . Status200OK ) ]
113164 [ ProducesResponseType < OpenShockProblem > ( StatusCodes . Status404NotFound , MediaTypeNames . Application . ProblemJson ) ] // ApiTokenNotFound
165+ [ MapToApiVersion ( "1" ) ]
114166 public async Task < IActionResult > EditToken ( [ FromRoute ] Guid tokenId , [ FromBody ] EditTokenRequest body )
115167 {
116- var token = await _db . ApiTokens
117- . FirstOrDefaultAsync ( x => x . UserId == CurrentUser . Id && x . Id == tokenId && ( x . ValidUntil == null || x . ValidUntil > DateTime . UtcNow ) ) ;
168+ var token = await CurrentUserValidTokens . FirstOrDefaultAsync ( x => x . Id == tokenId ) ;
118169 if ( token is null ) return Problem ( ApiTokenError . ApiTokenNotFound ) ;
119170
120171 token . Name = body . Name ;
0 commit comments