Creating and Deleting
Oct 31, 2021
See the code for this post on GitHub
I got sidetracked by some other projects this month, but I was able to knock out the C and D on my Observation CRUD checklist recently. I ran into a couple unanticipated hurdles along the way.
I was wrong about one of my assumptions for CosmosDB. I figured a call to the Cosmos client container's CreateItemAsync
method would automatically add an ID to the record, since (if I recall correctly) the data import for the Celestial Objects did that. I got an error when trying to insert records without the ID set though, so now I'm manually adding an ID in the repository class right before the item gets saved. I also had to return the new ID from the method so it can be referenced in the client app.
public async Task<string> AddItemAsync<TEntity>(TEntity item) where TEntity : Entity
{
item.Id = Guid.NewGuid().ToString();
var result = await _container.CreateItemAsync(item);
return result?.Resource.Id;
}
Implementing the delete functionality was pretty straightforward. One security concern I had to address was how any logged-in user could pass a known item ID to the API and delete a record, regardless of whether it was one they created. I added some logic in the service layer to first fetch the item by its ID and the logged-in user's ID.
public async Task<bool> DeleteObservationForUserAsync(string observationId, string userId)
{
bool deleted = false;
var observation = await GetObservationForUserByIdAsync(userId, observationId);
if (observation != null)
{
await _repository.DeleteItemAsync(_mapper.Map<Observation>(observation));
deleted = true;
}
return deleted;
}
Setting the user's ID is handled in the Function endpoint itself, so it should be pretty tough to spoof from the client.
In this latest commit I also included a fix for a bug concerning the order of observations in the observation list. Before, I was using a Javascript Map data structure for a view model of the observations. That didn't make it as easy to sort the dates when they get added in different orders, though. Instead I switched to using a standard Javascript object with date strings as keys, and create a sorted array of just its keys to iterate through in the template.
interface ObservationsViewModel {
[date: string]: ObservationModel[]
};
function mapObservationsToViewModel(observations: ObservationModel[]): ObservationsViewModel {
const observationMap: ObservationsViewModel = {};
observations.map(observation => {
const dateKey: string = observation.dateTime.toISOString().split('T')[0];
if (observationMap[dateKey]) {
observationMap[dateKey].push(observation);
} else {
observationMap[dateKey] = [observation];
}
});
return observationMap;
}
{#each Object.keys(observationsViewModel).sort((a, b) => b > a ? 1 : -1) as date}
<Tile>
<h2>{dateFormatter.format(observationsViewModel[date][0].dateTime)}</h2>
{#each observationsViewModel[date] as observation}
<Observation observation={observation}
on:observationDelete={handleObservationDeleteButton}
on:observationEdit={handleObservationEdit} />
{/each}
</Tile>
In the client app code, the calls to the server to add and delete observations are currently included in the observationStore.ts file. I don't really like mixing those concerns, though, so I'm planning on moving those out to a separate service class in the TypeScript. I'm planning on including that in my next commit along with the final piece of my MVP CRUD app puzzle: updating observations!
StarLog Links
All Posts
- MVP Wrap-Up [11/12/2021]
- Creating and Deleting [10/31/2021]
- The Vertical Slice [10/5/2021]
- Celestial Searches Revisited [9/23/2021]
- Authentication [9/17/2021]
- The SvelteKit Refactor [9/12/2021]
- Celestial Searches [8/29/2021]
- The Carbon Refactor [8/20/2021]
- Proof of Concept [6/16/2021]
- Laying the Foundation [5/24/2021]
- The Project Roadmap [5/19/2021]
- Introduction [5/18/2021]