sort pictures by most recent

This commit is contained in:
2020-10-15 14:53:41 +03:00
committed by Stepan Usatiuk
parent 0b44f10a56
commit 3313c52086
10 changed files with 86 additions and 25 deletions

View File

@@ -29,9 +29,9 @@ export const OverviewComponent: React.FunctionComponent<IOverviewComponentProps>
return <LoadingStub spinner={props.fetchingSpinner} />;
}
const photos = props.photos.map((photo) => (
<PhotoCard key={photo.id} photo={photo} />
));
const photos = props.photos
.sort((a, b) => b.shotAt - a.shotAt)
.map((photo) => <PhotoCard key={photo.id} photo={photo} />);
return (
<div id="overview">

View File

@@ -42,7 +42,7 @@ export class PhotoCardComponent extends React.PureComponent<
}
*/
public render(): JSX.Element {
const isUploaded = this.props.photo.uploaded;
const fileExists = this.props.photo.uploaded;
return (
<Card
className="photoCard"
@@ -53,7 +53,7 @@ export class PhotoCardComponent extends React.PureComponent<
}
*/
>
{isUploaded ? (
{fileExists ? (
<img src={getPhotoThumbPath(this.props.photo, 512)}></img>
) : (
<Spinner />

14
package-lock.json generated
View File

@@ -2177,6 +2177,14 @@
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="
},
"exifreader": {
"version": "3.12.3",
"resolved": "https://registry.npmjs.org/exifreader/-/exifreader-3.12.3.tgz",
"integrity": "sha512-8CtkjDC8xcREleBoFE1xZzkbaEiuLngORo6Xbxn3rPl/NTznTFHK0F9zg5bz5nwWmGDfPArPUXpUyJeHQHy+fA==",
"requires": {
"xmldom": "^0.1.31"
}
},
"expand-template": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
@@ -5648,6 +5656,12 @@
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
},
"xmldom": {
"version": "0.1.31",
"resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.31.tgz",
"integrity": "sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ==",
"optional": true
},
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",

View File

@@ -38,6 +38,7 @@
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-mocha": "^8.0.0",
"eslint-plugin-prettier": "^3.1.4",
"exifreader": "^3.12.3",
"hasha": "^5.2.2",
"husky": "^4.3.0",
"jsonwebtoken": "^8.5.1",

View File

@@ -29,7 +29,7 @@ import {
validateOrReject,
} from "class-validator";
import { config } from "~config";
import { resizeTo } from "~util";
import { getShotDate, resizeTo } from "~util";
export interface IPhotoJSON {
id: number;
@@ -69,6 +69,9 @@ export class Photo extends BaseEntity {
@IsMimeType()
public format: string;
@Column({ default: false })
public uploaded: boolean;
@Column({ type: "set", enum: ThumbSizes, default: [] })
public generatedThumbs: string[];
@@ -137,17 +140,38 @@ export class Photo extends BaseEntity {
}
}
public async isUploaded(): Promise<boolean> {
// Checks if file exists and updates the DB
public async fileExists(): Promise<boolean> {
try {
await fs.access(this.getPath(), fsConstants.F_OK);
if (!this.uploaded) {
this.uploaded = true;
await this.save();
}
return true;
} catch (e) {
if (this.uploaded) {
this.uploaded = false;
this.generatedThumbs = [];
await this.save();
}
return false;
}
}
public async processUpload(): Promise<void> {
await this.fileExists();
const date = await getShotDate(this.getPath());
if (date !== null) {
this.shotAt = date;
} else {
this.shotAt = new Date();
}
await this.save();
}
private async generateThumbnail(size: number): Promise<void> {
if (!(await this.isUploaded())) {
if (!(await this.fileExists())) {
return;
}
await resizeTo(this.getPath(), this.getThumbPath(size), size);
@@ -211,7 +235,7 @@ export class Photo extends BaseEntity {
createdAt: this.createdAt.getTime(),
editedAt: this.editedAt.getTime(),
shotAt: this.shotAt.getTime(),
uploaded: await this.isUploaded(),
uploaded: this.uploaded,
};
}

View File

@@ -84,7 +84,7 @@ photosRouter.post("/photos/upload/:id", async (ctx) => {
return;
}
if (await photo.isUploaded()) {
if (await photo.fileExists()) {
ctx.throw(400, "Already uploaded");
return;
}
@@ -108,6 +108,7 @@ photosRouter.post("/photos/upload/:id", async (ctx) => {
try {
// TODO: actually move file if it's on different filesystems
await fs.rename(file.path, photo.getPath());
await photo.processUpload();
} catch (e) {
console.log(e);
ctx.throw(500);
@@ -236,7 +237,7 @@ photosRouter.get("/photos/showByID/:id/:token", async (ctx) => {
user: { id: user },
});
if (!photo || !(await photo.isUploaded())) {
if (!photo || !(await photo.fileExists())) {
ctx.throw(404);
return;
}
@@ -267,7 +268,7 @@ photosRouter.get("/photos/showByID/:id", async (ctx) => {
const photo = await Photo.findOne({ id, user });
if (!photo || !(await photo.isUploaded())) {
if (!photo || !(await photo.fileExists())) {
ctx.throw(404);
return;
}
@@ -299,7 +300,7 @@ photosRouter.get("/photos/getShowByIDToken/:id", async (ctx) => {
const { user } = ctx.state;
const photo = await Photo.findOne({ id, user });
if (!photo || !(await photo.isUploaded())) {
if (!photo || !(await photo.fileExists())) {
ctx.throw(404);
return;
}

View File

@@ -178,7 +178,7 @@ describe("photos", function () {
expect(response.body.error).to.be.equal("Not Found");
});
it("should create, upload and show a photo", async function () {
it("should create, upload and show a photo with a shot date", async function () {
const response = await request(callback)
.post("/photos/new")
.set({
@@ -203,7 +203,7 @@ describe("photos", function () {
});
expect(dbPhoto.hash).to.be.equal(dogHash);
expect(await dbPhoto.isUploaded()).to.be.equal(false);
expect(await dbPhoto.fileExists()).to.be.equal(false);
await request(callback)
.post(`/photos/upload/${photo.id}`)
@@ -214,7 +214,15 @@ describe("photos", function () {
.attach("photo", dogPath)
.expect(200);
expect(await dbPhoto.isUploaded()).to.be.equal(true);
const dbPhotoUpl = await Photo.findOneOrFail({
id: photo.id,
user: seed.user1.id as any,
});
expect(dbPhotoUpl.hash).to.be.equal(dogHash);
expect(await dbPhotoUpl.fileExists()).to.be.equal(true);
expect(dbPhotoUpl.shotAt.toISOString()).to.be.equal(
new Date("2020-10-05T14:20:18").toISOString(),
);
const showResp = await request(callback)
.get(`/photos/showByID/${photo.id}`)
@@ -290,7 +298,7 @@ describe("photos", function () {
});
expect(dbPhoto.hash).to.be.equal(dogHash);
expect(await dbPhoto.isUploaded()).to.be.equal(false);
expect(await dbPhoto.fileExists()).to.be.equal(false);
await request(callback)
.post(`/photos/upload/${photo.id}`)
@@ -301,7 +309,7 @@ describe("photos", function () {
.attach("photo", dogPath)
.expect(200);
expect(await dbPhoto.isUploaded()).to.be.equal(true);
expect(await dbPhoto.fileExists()).to.be.equal(true);
await request(callback)
.post(`/photos/upload/${photo.id}`)
@@ -349,7 +357,7 @@ describe("photos", function () {
});
expect(dbPhoto.hash).to.be.equal(dogHash);
expect(await dbPhoto.isUploaded()).to.be.equal(false);
expect(await dbPhoto.fileExists()).to.be.equal(false);
await request(callback)
.post(`/photos/upload/${photo.id}`)
@@ -360,7 +368,7 @@ describe("photos", function () {
.attach("photo", catPath)
.expect(400);
expect(await dbPhoto.isUploaded()).to.be.equal(false);
expect(await dbPhoto.fileExists()).to.be.equal(false);
const showResp = await request(callback)
.get(`/photos/showByID/${photo.id}`)
@@ -394,7 +402,7 @@ describe("photos", function () {
user: seed.user1.id as any,
});
expect(dbPhoto.hash).to.be.equal(dogHash);
expect(await dbPhoto.isUploaded()).to.be.equal(false);
expect(await dbPhoto.fileExists()).to.be.equal(false);
await request(callback)
.post(`/photos/upload/${photo.id}`)
@@ -405,7 +413,7 @@ describe("photos", function () {
.attach("photo", dogPath)
.expect(404);
expect(await dbPhoto.isUploaded()).to.be.equal(false);
expect(await dbPhoto.fileExists()).to.be.equal(false);
});
it("should create, upload but not show a photo to another user", async function () {
@@ -432,7 +440,7 @@ describe("photos", function () {
user: seed.user1.id as any,
});
expect(dbPhoto.hash).to.be.equal(dogHash);
expect(await dbPhoto.isUploaded()).to.be.equal(false);
expect(await dbPhoto.fileExists()).to.be.equal(false);
await request(callback)
.post(`/photos/upload/${photo.id}`)
@@ -443,7 +451,7 @@ describe("photos", function () {
.attach("photo", dogPath)
.expect(200);
expect(await dbPhoto.isUploaded()).to.be.equal(true);
expect(await dbPhoto.fileExists()).to.be.equal(true);
await request(callback)
.get(`/photos/showByID/${photo.id}`)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 72 KiB

View File

@@ -1,6 +1,8 @@
import deasync = require("deasync");
import { fromFile } from "hasha";
import sharp = require("sharp");
import * as ExifReader from "exifreader";
import * as sharp from "sharp";
import * as fs from "fs/promises";
export async function getHash(file: string): Promise<string> {
return await fromFile(file, {
@@ -18,6 +20,17 @@ export async function getSize(file: string): Promise<string> {
return `${metadata.width}x${metadata.height}`;
}
export async function getShotDate(file: string): Promise<Date | null> {
const tags = ExifReader.load(await fs.readFile(file));
const imageDate = tags["DateTimeOriginal"].description;
if (!imageDate) {
return null;
}
const dateStr = imageDate.split(" ")[0].replace(/:/g, "-");
const date = new Date(dateStr + "T" + imageDate.split(" ")[1]);
return date;
}
export async function resizeTo(
inPath: string,
outPath: string,