mirror of
https://github.com/usatiuk/photos.git
synced 2025-10-28 23:37:48 +01:00
sort pictures by most recent
This commit is contained in:
@@ -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">
|
||||
|
||||
@@ -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
14
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 |
15
src/util.ts
15
src/util.ts
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user