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!

© 2022 Andrew Iafrate. Contact me for questions or comments.