mirror of
https://github.com/usatiuk/photos.git
synced 2025-10-28 15:27:49 +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} />;
|
return <LoadingStub spinner={props.fetchingSpinner} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const photos = props.photos.map((photo) => (
|
const photos = props.photos
|
||||||
<PhotoCard key={photo.id} photo={photo} />
|
.sort((a, b) => b.shotAt - a.shotAt)
|
||||||
));
|
.map((photo) => <PhotoCard key={photo.id} photo={photo} />);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="overview">
|
<div id="overview">
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export class PhotoCardComponent extends React.PureComponent<
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
const isUploaded = this.props.photo.uploaded;
|
const fileExists = this.props.photo.uploaded;
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
className="photoCard"
|
className="photoCard"
|
||||||
@@ -53,7 +53,7 @@ export class PhotoCardComponent extends React.PureComponent<
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
>
|
>
|
||||||
{isUploaded ? (
|
{fileExists ? (
|
||||||
<img src={getPhotoThumbPath(this.props.photo, 512)}></img>
|
<img src={getPhotoThumbPath(this.props.photo, 512)}></img>
|
||||||
) : (
|
) : (
|
||||||
<Spinner />
|
<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",
|
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
||||||
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="
|
"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": {
|
"expand-template": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
|
||||||
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
|
"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": {
|
"xtend": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
"eslint-plugin-import": "^2.22.1",
|
"eslint-plugin-import": "^2.22.1",
|
||||||
"eslint-plugin-mocha": "^8.0.0",
|
"eslint-plugin-mocha": "^8.0.0",
|
||||||
"eslint-plugin-prettier": "^3.1.4",
|
"eslint-plugin-prettier": "^3.1.4",
|
||||||
|
"exifreader": "^3.12.3",
|
||||||
"hasha": "^5.2.2",
|
"hasha": "^5.2.2",
|
||||||
"husky": "^4.3.0",
|
"husky": "^4.3.0",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import {
|
|||||||
validateOrReject,
|
validateOrReject,
|
||||||
} from "class-validator";
|
} from "class-validator";
|
||||||
import { config } from "~config";
|
import { config } from "~config";
|
||||||
import { resizeTo } from "~util";
|
import { getShotDate, resizeTo } from "~util";
|
||||||
|
|
||||||
export interface IPhotoJSON {
|
export interface IPhotoJSON {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -69,6 +69,9 @@ export class Photo extends BaseEntity {
|
|||||||
@IsMimeType()
|
@IsMimeType()
|
||||||
public format: string;
|
public format: string;
|
||||||
|
|
||||||
|
@Column({ default: false })
|
||||||
|
public uploaded: boolean;
|
||||||
|
|
||||||
@Column({ type: "set", enum: ThumbSizes, default: [] })
|
@Column({ type: "set", enum: ThumbSizes, default: [] })
|
||||||
public generatedThumbs: string[];
|
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 {
|
try {
|
||||||
await fs.access(this.getPath(), fsConstants.F_OK);
|
await fs.access(this.getPath(), fsConstants.F_OK);
|
||||||
|
if (!this.uploaded) {
|
||||||
|
this.uploaded = true;
|
||||||
|
await this.save();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (this.uploaded) {
|
||||||
|
this.uploaded = false;
|
||||||
|
this.generatedThumbs = [];
|
||||||
|
await this.save();
|
||||||
|
}
|
||||||
return false;
|
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> {
|
private async generateThumbnail(size: number): Promise<void> {
|
||||||
if (!(await this.isUploaded())) {
|
if (!(await this.fileExists())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await resizeTo(this.getPath(), this.getThumbPath(size), size);
|
await resizeTo(this.getPath(), this.getThumbPath(size), size);
|
||||||
@@ -211,7 +235,7 @@ export class Photo extends BaseEntity {
|
|||||||
createdAt: this.createdAt.getTime(),
|
createdAt: this.createdAt.getTime(),
|
||||||
editedAt: this.editedAt.getTime(),
|
editedAt: this.editedAt.getTime(),
|
||||||
shotAt: this.shotAt.getTime(),
|
shotAt: this.shotAt.getTime(),
|
||||||
uploaded: await this.isUploaded(),
|
uploaded: this.uploaded,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ photosRouter.post("/photos/upload/:id", async (ctx) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await photo.isUploaded()) {
|
if (await photo.fileExists()) {
|
||||||
ctx.throw(400, "Already uploaded");
|
ctx.throw(400, "Already uploaded");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -108,6 +108,7 @@ photosRouter.post("/photos/upload/:id", async (ctx) => {
|
|||||||
try {
|
try {
|
||||||
// TODO: actually move file if it's on different filesystems
|
// TODO: actually move file if it's on different filesystems
|
||||||
await fs.rename(file.path, photo.getPath());
|
await fs.rename(file.path, photo.getPath());
|
||||||
|
await photo.processUpload();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
ctx.throw(500);
|
ctx.throw(500);
|
||||||
@@ -236,7 +237,7 @@ photosRouter.get("/photos/showByID/:id/:token", async (ctx) => {
|
|||||||
user: { id: user },
|
user: { id: user },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!photo || !(await photo.isUploaded())) {
|
if (!photo || !(await photo.fileExists())) {
|
||||||
ctx.throw(404);
|
ctx.throw(404);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -267,7 +268,7 @@ photosRouter.get("/photos/showByID/:id", async (ctx) => {
|
|||||||
|
|
||||||
const photo = await Photo.findOne({ id, user });
|
const photo = await Photo.findOne({ id, user });
|
||||||
|
|
||||||
if (!photo || !(await photo.isUploaded())) {
|
if (!photo || !(await photo.fileExists())) {
|
||||||
ctx.throw(404);
|
ctx.throw(404);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -299,7 +300,7 @@ photosRouter.get("/photos/getShowByIDToken/:id", async (ctx) => {
|
|||||||
const { user } = ctx.state;
|
const { user } = ctx.state;
|
||||||
|
|
||||||
const photo = await Photo.findOne({ id, user });
|
const photo = await Photo.findOne({ id, user });
|
||||||
if (!photo || !(await photo.isUploaded())) {
|
if (!photo || !(await photo.fileExists())) {
|
||||||
ctx.throw(404);
|
ctx.throw(404);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ describe("photos", function () {
|
|||||||
expect(response.body.error).to.be.equal("Not Found");
|
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)
|
const response = await request(callback)
|
||||||
.post("/photos/new")
|
.post("/photos/new")
|
||||||
.set({
|
.set({
|
||||||
@@ -203,7 +203,7 @@ describe("photos", function () {
|
|||||||
});
|
});
|
||||||
expect(dbPhoto.hash).to.be.equal(dogHash);
|
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)
|
await request(callback)
|
||||||
.post(`/photos/upload/${photo.id}`)
|
.post(`/photos/upload/${photo.id}`)
|
||||||
@@ -214,7 +214,15 @@ describe("photos", function () {
|
|||||||
.attach("photo", dogPath)
|
.attach("photo", dogPath)
|
||||||
.expect(200);
|
.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)
|
const showResp = await request(callback)
|
||||||
.get(`/photos/showByID/${photo.id}`)
|
.get(`/photos/showByID/${photo.id}`)
|
||||||
@@ -290,7 +298,7 @@ describe("photos", function () {
|
|||||||
});
|
});
|
||||||
expect(dbPhoto.hash).to.be.equal(dogHash);
|
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)
|
await request(callback)
|
||||||
.post(`/photos/upload/${photo.id}`)
|
.post(`/photos/upload/${photo.id}`)
|
||||||
@@ -301,7 +309,7 @@ describe("photos", function () {
|
|||||||
.attach("photo", dogPath)
|
.attach("photo", dogPath)
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
expect(await dbPhoto.isUploaded()).to.be.equal(true);
|
expect(await dbPhoto.fileExists()).to.be.equal(true);
|
||||||
|
|
||||||
await request(callback)
|
await request(callback)
|
||||||
.post(`/photos/upload/${photo.id}`)
|
.post(`/photos/upload/${photo.id}`)
|
||||||
@@ -349,7 +357,7 @@ describe("photos", function () {
|
|||||||
});
|
});
|
||||||
expect(dbPhoto.hash).to.be.equal(dogHash);
|
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)
|
await request(callback)
|
||||||
.post(`/photos/upload/${photo.id}`)
|
.post(`/photos/upload/${photo.id}`)
|
||||||
@@ -360,7 +368,7 @@ describe("photos", function () {
|
|||||||
.attach("photo", catPath)
|
.attach("photo", catPath)
|
||||||
.expect(400);
|
.expect(400);
|
||||||
|
|
||||||
expect(await dbPhoto.isUploaded()).to.be.equal(false);
|
expect(await dbPhoto.fileExists()).to.be.equal(false);
|
||||||
|
|
||||||
const showResp = await request(callback)
|
const showResp = await request(callback)
|
||||||
.get(`/photos/showByID/${photo.id}`)
|
.get(`/photos/showByID/${photo.id}`)
|
||||||
@@ -394,7 +402,7 @@ describe("photos", function () {
|
|||||||
user: seed.user1.id as any,
|
user: seed.user1.id as any,
|
||||||
});
|
});
|
||||||
expect(dbPhoto.hash).to.be.equal(dogHash);
|
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)
|
await request(callback)
|
||||||
.post(`/photos/upload/${photo.id}`)
|
.post(`/photos/upload/${photo.id}`)
|
||||||
@@ -405,7 +413,7 @@ describe("photos", function () {
|
|||||||
.attach("photo", dogPath)
|
.attach("photo", dogPath)
|
||||||
.expect(404);
|
.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 () {
|
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,
|
user: seed.user1.id as any,
|
||||||
});
|
});
|
||||||
expect(dbPhoto.hash).to.be.equal(dogHash);
|
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)
|
await request(callback)
|
||||||
.post(`/photos/upload/${photo.id}`)
|
.post(`/photos/upload/${photo.id}`)
|
||||||
@@ -443,7 +451,7 @@ describe("photos", function () {
|
|||||||
.attach("photo", dogPath)
|
.attach("photo", dogPath)
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
expect(await dbPhoto.isUploaded()).to.be.equal(true);
|
expect(await dbPhoto.fileExists()).to.be.equal(true);
|
||||||
|
|
||||||
await request(callback)
|
await request(callback)
|
||||||
.get(`/photos/showByID/${photo.id}`)
|
.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 deasync = require("deasync");
|
||||||
import { fromFile } from "hasha";
|
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> {
|
export async function getHash(file: string): Promise<string> {
|
||||||
return await fromFile(file, {
|
return await fromFile(file, {
|
||||||
@@ -18,6 +20,17 @@ export async function getSize(file: string): Promise<string> {
|
|||||||
return `${metadata.width}x${metadata.height}`;
|
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(
|
export async function resizeTo(
|
||||||
inPath: string,
|
inPath: string,
|
||||||
outPath: string,
|
outPath: string,
|
||||||
|
|||||||
Reference in New Issue
Block a user