Clean up after Client deletion, finish cleaning up after Profile deletion.
6f473576c6ae started cleaning up after Profiles when they're deleted, but
didn't clean up the Clients created by that Profile. This fixes that, and also
fixes a BUG note about cleaning up after a Client when it's deleted.
Extend the authorizationCodeStore to have a deleteAuthorizationCodesByClientID
method that will delete the AuthorizationCodes that have been granted by the
Client specified by the passed ID. We also implemented this in memstore and
postgres, so tests continue to pass.
Extend the clientStore to have a deleteClientsByOwner method that will delete
the Clients that were created by the Profile specified by the passed ID. We also
implemented this in memstore and postgres, so tests continue to pass.
Extend the clientStore to have a removeEndpointsByClientID method that will
delete the Endpoints that belong(ed) to a the Client specified by the passed ID.
We also implemented this in memstore and postgres, so tests continue to pass.
Extend the tokenStore to have a revokeTokensByClientID method that will revoke
all the Tokens that were granted to the Client specified by the passed ID. We
also implemented this in memstore and postgres, so tests continue to pass.
When listing Clients by their owner, allow setting the num argument (which
controls how many to return) to 0 or lower, and using that to signal "return all
Clients belonging to this owner", instead of paging. This is useful when
deleting the Clients belonging to a Profile as part of the cleanup after
deleting the Profile.
Create a cleanUpAfterClientDeletion helper function that will delete the
Endpoints and AuthorizationCodes belonging to a Client, and revoke the Tokens
belonging to a Client, as part of cleaning up after a Client has been deleted.
Add a check in the handler for listing Clients owned by a Profile to disallow
the num argument to be lower than 1, because the API should be forced to page.
Call our cleanUpAfterClientDeletion once the Client has been deleted in the
appropriate handler.
Fill out our Context with new methods to wrap all the new methods we're adding
to our *Stores.
In cleanUpAfterProfileDeletion, obtain a list of clients belonging to the owner,
use our new DeleteClientsByOwner method to remove all of them, and then use the
list to run our new cleanUpAfterClientDeletion function to clear away the final
remnants of a Profile when it's deleted.
4 "code.secondbit.org/uuid.hg"
6 "github.com/secondbit/pan"
9 func (c Client) GetSQLTableName() string {
13 func (e Endpoint) GetSQLTableName() string {
17 func (p *postgres) getClientSQL(id uuid.ID) *pan.Query {
19 fields, _ := pan.GetFields(client)
20 query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(client))
22 query.Include(pan.GetUnquotedColumn(client, "ID")+" = ? AND "+pan.GetUnquotedColumn(client, "Deleted")+" = ?", id, false)
23 return query.FlushExpressions(" ")
26 func (p *postgres) getClient(id uuid.ID) (Client, error) {
27 query := p.getClientSQL(id)
28 rows, err := p.db.Query(query.String(), query.Args...)
35 err := pan.Unmarshal(rows, &client)
41 if err = rows.Err(); err != nil {
45 return client, ErrClientNotFound
50 func (p *postgres) saveClientSQL(client Client) *pan.Query {
51 fields, values := pan.GetFields(client)
52 query := pan.New(pan.POSTGRES, "INSERT INTO "+pan.GetTableName(client))
53 query.Include("(" + pan.QueryList(fields) + ")")
54 query.Include("VALUES")
55 query.Include("("+pan.VariableList(len(values))+")", values...)
56 return query.FlushExpressions(" ")
59 func (p *postgres) saveClient(client Client) error {
60 query := p.saveClientSQL(client)
61 _, err := p.db.Exec(query.String(), query.Args...)
62 if e, ok := err.(*pq.Error); ok && e.Constraint == "clients_pkey" {
63 err = ErrClientAlreadyExists
68 func (p *postgres) updateClientSQL(id uuid.ID, change ClientChange) *pan.Query {
70 query := pan.New(pan.POSTGRES, "UPDATE "+pan.GetTableName(client)+" SET ")
71 query.IncludeIfNotNil(pan.GetUnquotedColumn(client, "Secret")+" = ?", change.Secret)
72 query.IncludeIfNotNil(pan.GetUnquotedColumn(client, "OwnerID")+" = ?", change.OwnerID)
73 query.IncludeIfNotNil(pan.GetUnquotedColumn(client, "Name")+" = ?", change.Name)
74 query.IncludeIfNotNil(pan.GetUnquotedColumn(client, "Logo")+" = ?", change.Logo)
75 query.IncludeIfNotNil(pan.GetUnquotedColumn(client, "Website")+" = ?", change.Website)
76 query.IncludeIfNotNil(pan.GetUnquotedColumn(client, "Deleted")+" = ?", change.Deleted)
77 query.FlushExpressions(", ")
79 query.Include(pan.GetUnquotedColumn(client, "ID")+" = ?", id)
80 return query.FlushExpressions(" ")
83 func (p *postgres) updateClient(id uuid.ID, change ClientChange) error {
87 query := p.updateClientSQL(id, change)
88 _, err := p.db.Exec(query.String(), query.Args...)
92 func (p *postgres) listClientsByOwnerSQL(ownerID uuid.ID, num, offset int) *pan.Query {
94 fields, _ := pan.GetFields(client)
95 query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(client))
97 query.Include(pan.GetUnquotedColumn(client, "OwnerID")+" = ? AND "+pan.GetUnquotedColumn(client, "Deleted")+" = ?", ownerID, false)
99 query.IncludeLimit(int64(num))
101 query.IncludeOffset(int64(offset))
102 return query.FlushExpressions(" ")
105 func (p *postgres) listClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error) {
106 query := p.listClientsByOwnerSQL(ownerID, num, offset)
107 rows, err := p.db.Query(query.String(), query.Args...)
109 return []Client{}, err
114 err = pan.Unmarshal(rows, &client)
118 clients = append(clients, client)
120 if err = rows.Err(); err != nil {
126 func (p *postgres) deleteClientsByOwnerSQL(ownerID uuid.ID) *pan.Query {
128 query := pan.New(pan.POSTGRES, "UPDATE "+pan.GetTableName(client)+" SET")
129 query.Include(pan.GetUnquotedColumn(client, "Deleted")+"= ?", true)
131 query.Include(pan.GetUnquotedColumn(client, "OwnerID")+" = ?", ownerID)
132 return query.FlushExpressions(" ")
135 func (p *postgres) deleteClientsByOwner(ownerID uuid.ID) error {
136 query := p.deleteClientsByOwnerSQL(ownerID)
137 _, err := p.db.Exec(query.String(), query.Args...)
141 func (p *postgres) addEndpointsSQL(endpoints []Endpoint) *pan.Query {
142 fields, _ := pan.GetFields(endpoints[0])
143 query := pan.New(pan.POSTGRES, "INSERT INTO "+pan.GetTableName(endpoints[0]))
144 query.Include("(" + pan.QueryList(fields) + ")")
145 query.Include("VALUES")
146 query.FlushExpressions(" ")
147 for _, endpoint := range endpoints {
148 _, values := pan.GetFields(endpoint)
149 query.Include("("+pan.VariableList(len(values))+")", values...)
151 return query.FlushExpressions(", ")
154 func (p *postgres) addEndpoints(endpoints []Endpoint) error {
155 if len(endpoints) < 1 {
158 query := p.addEndpointsSQL(endpoints)
159 _, err := p.db.Exec(query.String(), query.Args...)
160 if e, ok := err.(*pq.Error); ok && e.Constraint == "endpoints_pkey" {
161 return ErrEndpointAlreadyExists
166 func (p *postgres) removeEndpointSQL(client, endpoint uuid.ID) *pan.Query {
168 query := pan.New(pan.POSTGRES, "DELETE FROM "+pan.GetTableName(e))
170 query.Include(pan.GetUnquotedColumn(e, "ID")+" = ? AND "+pan.GetUnquotedColumn(e, "ClientID")+" = ?", endpoint, client)
171 return query.FlushExpressions(" ")
174 func (p *postgres) removeEndpoint(client, endpoint uuid.ID) error {
175 query := p.removeEndpointSQL(client, endpoint)
176 res, err := p.db.Exec(query.String(), query.Args...)
180 rows, err := res.RowsAffected()
185 return ErrEndpointNotFound
190 func (p *postgres) removeEndpointsByClientIDSQL(client uuid.ID) *pan.Query {
192 query := pan.New(pan.POSTGRES, "DELETE FROM "+pan.GetTableName(e))
194 query.Include(pan.GetUnquotedColumn(e, "ClientID")+" = ?", client)
195 return query.FlushExpressions(" ")
198 func (p *postgres) removeEndpointsByClientID(client uuid.ID) error {
199 query := p.removeEndpointsByClientIDSQL(client)
200 res, err := p.db.Exec(query.String(), query.Args...)
204 rows, err := res.RowsAffected()
209 return ErrClientNotFound
214 func (p *postgres) getEndpointSQL(client, endpoint uuid.ID) *pan.Query {
216 fields, _ := pan.GetFields(e)
217 query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(e))
219 query.FlushExpressions(" ")
220 query.Include(pan.GetUnquotedColumn(e, "ID")+" = ?", endpoint)
221 query.Include(pan.GetUnquotedColumn(e, "ClientID")+" = ?", client)
222 return query.FlushExpressions(" AND ")
225 func (p *postgres) getEndpoint(client, endpoint uuid.ID) (Endpoint, error) {
226 query := p.getEndpointSQL(client, endpoint)
227 rows, err := p.db.Query(query.String(), query.Args...)
229 return Endpoint{}, err
234 err := pan.Unmarshal(rows, &e)
240 if err = rows.Err(); err != nil {
244 return e, ErrEndpointNotFound
249 func (p *postgres) checkEndpointSQL(client uuid.ID, endpoint string) *pan.Query {
251 fields, _ := pan.GetFields(e)
252 query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(e))
254 query.FlushExpressions(" ")
255 query.Include(pan.GetUnquotedColumn(e, "ClientID")+" = ?", client)
256 query.Include(pan.GetUnquotedColumn(e, "NormalizedURI")+" = ?", endpoint)
257 return query.FlushExpressions(" AND ")
260 func (p *postgres) checkEndpoint(client uuid.ID, endpoint string) (bool, error) {
261 query := p.checkEndpointSQL(client, endpoint)
262 rows, err := p.db.Query(query.String(), query.Args...)
270 if err = rows.Err(); err != nil {
276 func (p *postgres) listEndpointsSQL(client uuid.ID, num, offset int) *pan.Query {
277 var endpoint Endpoint
278 fields, _ := pan.GetFields(endpoint)
279 query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(endpoint))
281 query.Include(pan.GetUnquotedColumn(endpoint, "ClientID")+" = ?", client)
282 query.IncludeLimit(int64(num))
283 query.IncludeOffset(int64(offset))
284 return query.FlushExpressions(" ")
287 func (p *postgres) listEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error) {
288 query := p.listEndpointsSQL(client, num, offset)
289 rows, err := p.db.Query(query.String(), query.Args...)
291 return []Endpoint{}, err
293 var endpoints []Endpoint
295 var endpoint Endpoint
296 err = pan.Unmarshal(rows, &endpoint)
298 return endpoints, err
300 endpoints = append(endpoints, endpoint)
302 if err = rows.Err(); err != nil {
303 return endpoints, err
305 return endpoints, nil
308 func (p *postgres) countEndpointsSQL(client uuid.ID) *pan.Query {
309 var endpoint Endpoint
310 query := pan.New(pan.POSTGRES, "SELECT COUNT(*) FROM "+pan.GetTableName(endpoint))
312 query.Include(pan.GetUnquotedColumn(endpoint, "ClientID")+" = ?", client)
313 return query.FlushExpressions(" ")
316 func (p *postgres) countEndpoints(client uuid.ID) (int64, error) {
317 query := p.countEndpointsSQL(client)
318 rows, err := p.db.Query(query.String(), query.Args...)
324 err = pan.Unmarshal(rows, &results)
329 if err = rows.Err(); err != nil {